summaryrefslogtreecommitdiff
path: root/platform/script-debugger
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
New upstream version 0~183.5153.4+dfsg
Diffstat (limited to 'platform/script-debugger')
-rw-r--r--platform/script-debugger/backend/intellij.platform.scriptDebugger.backend.iml16
-rwxr-xr-xplatform/script-debugger/backend/src/debugger/Breakpoint.kt79
-rw-r--r--platform/script-debugger/backend/src/debugger/BreakpointBase.kt82
-rw-r--r--platform/script-debugger/backend/src/debugger/BreakpointManager.kt99
-rw-r--r--platform/script-debugger/backend/src/debugger/BreakpointManagerBase.kt134
-rw-r--r--platform/script-debugger/backend/src/debugger/BreakpointTarget.java128
-rwxr-xr-xplatform/script-debugger/backend/src/debugger/CallFrame.kt59
-rw-r--r--platform/script-debugger/backend/src/debugger/CallFrameBase.kt43
-rwxr-xr-xplatform/script-debugger/backend/src/debugger/DebugEventListener.java74
-rw-r--r--platform/script-debugger/backend/src/debugger/DeclarativeScope.kt22
-rw-r--r--platform/script-debugger/backend/src/debugger/EvaluateContext.kt52
-rw-r--r--platform/script-debugger/backend/src/debugger/EvaluateContextBase.kt26
-rw-r--r--platform/script-debugger/backend/src/debugger/ExceptionCatchMode.java36
-rwxr-xr-xplatform/script-debugger/backend/src/debugger/ExceptionData.java45
-rw-r--r--platform/script-debugger/backend/src/debugger/ExceptionDataBase.java31
-rw-r--r--platform/script-debugger/backend/src/debugger/ObjectProperty.kt35
-rw-r--r--platform/script-debugger/backend/src/debugger/ObjectPropertyImpl.kt42
-rw-r--r--platform/script-debugger/backend/src/debugger/Scope.kt55
-rwxr-xr-xplatform/script-debugger/backend/src/debugger/Script.kt50
-rwxr-xr-xplatform/script-debugger/backend/src/debugger/ScriptBase.kt39
-rw-r--r--platform/script-debugger/backend/src/debugger/ScriptManager.kt45
-rw-r--r--platform/script-debugger/backend/src/debugger/ScriptManagerBase.kt51
-rw-r--r--platform/script-debugger/backend/src/debugger/ScriptManagerBaseEx.kt50
-rw-r--r--platform/script-debugger/backend/src/debugger/ScriptRegExpBreakpointTarget.kt41
-rw-r--r--platform/script-debugger/backend/src/debugger/ScriptRegExpSupportVisitor.java26
-rw-r--r--platform/script-debugger/backend/src/debugger/StandaloneVmHelper.kt84
-rwxr-xr-xplatform/script-debugger/backend/src/debugger/SuspendContext.kt66
-rw-r--r--platform/script-debugger/backend/src/debugger/SuspendContextBase.kt19
-rw-r--r--platform/script-debugger/backend/src/debugger/SuspendContextManager.kt80
-rw-r--r--platform/script-debugger/backend/src/debugger/SuspendContextManagerBase.kt64
-rw-r--r--platform/script-debugger/backend/src/debugger/ValueModifier.kt32
-rw-r--r--platform/script-debugger/backend/src/debugger/ValueModifierUtil.kt78
-rwxr-xr-xplatform/script-debugger/backend/src/debugger/Variable.java49
-rw-r--r--platform/script-debugger/backend/src/debugger/VariableImpl.java75
-rw-r--r--platform/script-debugger/backend/src/debugger/VariablesHost.java88
-rw-r--r--platform/script-debugger/backend/src/debugger/Vm.kt51
-rw-r--r--platform/script-debugger/backend/src/debugger/VmBase.kt29
-rw-r--r--platform/script-debugger/backend/src/debugger/sourcemap/Base64VLQ.java79
-rw-r--r--platform/script-debugger/backend/src/debugger/sourcemap/MappingEntry.kt37
-rw-r--r--platform/script-debugger/backend/src/debugger/sourcemap/MappingList.kt210
-rw-r--r--platform/script-debugger/backend/src/debugger/sourcemap/NestedSourceMap.kt119
-rw-r--r--platform/script-debugger/backend/src/debugger/sourcemap/SourceMap.kt81
-rw-r--r--platform/script-debugger/backend/src/debugger/sourcemap/SourceMapDecoder.kt372
-rw-r--r--platform/script-debugger/backend/src/debugger/sourcemap/SourceResolver.kt186
-rw-r--r--platform/script-debugger/backend/src/debugger/util.kt112
-rw-r--r--platform/script-debugger/backend/src/debugger/values/ArrayValue.kt28
-rw-r--r--platform/script-debugger/backend/src/debugger/values/FunctionValue.kt45
-rw-r--r--platform/script-debugger/backend/src/debugger/values/IndexedVariablesConsumer.kt28
-rwxr-xr-xplatform/script-debugger/backend/src/debugger/values/ObjectValue.kt53
-rw-r--r--platform/script-debugger/backend/src/debugger/values/ObjectValueBase.kt66
-rw-r--r--platform/script-debugger/backend/src/debugger/values/PrimitiveValue.kt45
-rw-r--r--platform/script-debugger/backend/src/debugger/values/StringValue.kt29
-rwxr-xr-xplatform/script-debugger/backend/src/debugger/values/Value.kt28
-rw-r--r--platform/script-debugger/backend/src/debugger/values/ValueBase.kt18
-rw-r--r--platform/script-debugger/backend/src/debugger/values/ValueManager.kt43
-rw-r--r--platform/script-debugger/backend/src/debugger/values/ValueType.kt49
-rw-r--r--platform/script-debugger/backend/src/rpc/CommandProcessor.kt57
-rw-r--r--platform/script-debugger/backend/src/rpc/CommandSenderBase.kt40
-rw-r--r--platform/script-debugger/backend/src/rpc/MessageManager.kt121
-rw-r--r--platform/script-debugger/backend/src/rpc/MessageManagerBase.kt38
-rw-r--r--platform/script-debugger/backend/src/rpc/UnsafeSetResult.java15
-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
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/generated/ProtocolSchemaReaderImpl.kt355
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/intellij.javascript.protocolModelGenerator.iml19
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/BoxableType.kt21
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/ClassNameScheme.kt48
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/ClassScope.kt28
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/CreateStandaloneTypeBindingVisitorBase.kt27
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/DomainGenerator.kt289
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/Enums.kt33
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/FileSet.kt52
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/Generator.kt275
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/InputClassScope.kt73
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/ListType.kt31
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/MemberScope.kt37
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/MyCreateStandaloneTypeBindingVisitorBase.kt40
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/NamePath.kt29
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/OutputClassScope.kt61
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/ParserRootInterfaceItem.kt27
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/ResolveAndGenerateScope.kt13
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/StandaloneType.kt49
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/StandaloneTypeBinding.kt33
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/TypeData.kt104
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/TypeDescriptor.kt30
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/TypeMap.kt50
-rw-r--r--platform/script-debugger/protocol/protocol-model-generator/src/TypeVisitor.kt25
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/intellij.platform.scriptDebugger.protocolReaderRuntime.iml15
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/annotations.kt41
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/EventMap.kt50
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonObjectBased.java15
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonReaders.java291
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonSubtype.java14
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonWriters.kt21
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/MapFactory.java19
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/ObjectFactory.java7
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/OutMessage.kt276
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/Request.kt31
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/RequestImpl.kt41
-rw-r--r--platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/StringIntPair.java15
-rw-r--r--platform/script-debugger/protocol/protocol-reader/intellij.javascript.protocolReader.iml16
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/ArrayReader.kt23
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/ClassScope.kt7
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/EnumReader.kt12
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/ExistingSubtypeAspect.kt33
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/FieldProcessor.kt197
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/FileScope.kt9
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/FileUpdater.kt34
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/GlobalScope.kt63
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/InterfaceReader.kt221
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/JsonProtocolModelParseException.kt8
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/LazyCachedMethodHandler.kt51
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/MapReader.kt34
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/MethodHandler.kt31
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/ObjectValueReader.kt43
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/PrimitiveValueReader.kt44
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/RawValueReader.kt14
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/ReaderGenerator.kt117
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/ReaderRoot.kt105
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/StringIntPairValueReader.kt16
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/TextOutput.kt137
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/TypeWriter.kt258
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/Util.kt50
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/ValueReader.kt38
-rw-r--r--platform/script-debugger/protocol/protocol-reader/src/VolatileFieldBinding.kt19
-rw-r--r--platform/script-debugger/protocol/schema-reader-generator/intellij.javascript.schemaReaderGenerator.iml15
-rw-r--r--platform/script-debugger/protocol/schema-reader-generator/src/ProtocolMetaModel.kt127
-rw-r--r--platform/script-debugger/protocol/schema-reader-generator/src/SchemaReaderGenerator.kt12
164 files changed, 11713 insertions, 0 deletions
diff --git a/platform/script-debugger/backend/intellij.platform.scriptDebugger.backend.iml b/platform/script-debugger/backend/intellij.platform.scriptDebugger.backend.iml
new file mode 100644
index 00000000..5ebae223
--- /dev/null
+++ b/platform/script-debugger/backend/intellij.platform.scriptDebugger.backend.iml
@@ -0,0 +1,16 @@
+<?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" packagePrefix="org.jetbrains" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="intellij.platform.core" />
+ <orderEntry type="module" module-name="intellij.platform.scriptDebugger.protocolReaderRuntime" />
+ <orderEntry type="module" module-name="intellij.platform.ide" />
+ <orderEntry type="module" module-name="intellij.platform.ide.impl" />
+ <orderEntry type="library" name="netty-codec-http" level="project" />
+ </component>
+</module> \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/Breakpoint.kt b/platform/script-debugger/backend/src/debugger/Breakpoint.kt
new file mode 100755
index 00000000..24de5d2b
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/Breakpoint.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 org.jetbrains.debugger
+
+/**
+ * A breakpoint in the browser JavaScript virtual machine. The `set*`
+ * method invocations will not take effect until
+ * [.flush] is called.
+ */
+interface Breakpoint {
+ companion object {
+ /**
+ * This value is used when the corresponding parameter is absent
+ */
+ const val EMPTY_VALUE: Int = -1
+
+ /**
+ * A breakpoint has this ID if it does not reflect an actual breakpoint in a
+ * JavaScript VM debugger.
+ */
+ const val INVALID_ID: Int = -1
+ }
+
+ val target: BreakpointTarget
+
+ val line: Int
+
+ val column: Int
+
+ /**
+ * @return whether this breakpoint is enabled
+ */
+ /**
+ * Sets whether this breakpoint is enabled.
+ * Requires subsequent [.flush] call.
+ */
+ var enabled: Boolean
+
+ /**
+ * Sets the breakpoint condition as plain JavaScript (`null` to clear).
+ * Requires subsequent [.flush] call.
+ */
+ var condition: String?
+
+ val isResolved: Boolean
+
+ /**
+ * Be aware! V8 doesn't provide reliable debugger API, so, sometimes actual locations is empty - in this case this methods return "true".
+ * V8 debugger doesn't report about resolved breakpoint if it is happened after initial breakpoint set. So, you cannot trust "actual locations".
+ */
+ fun isActualLineCorrect(): Boolean = true
+}
+
+/**
+ * Visitor interface that includes all extensions.
+ */
+interface TargetExtendedVisitor<R> : FunctionVisitor<R>, ScriptRegExpSupportVisitor<R>
+
+
+/**
+ * Additional interface that user visitor may implement for [BreakpointTarget.accept]
+ * method.
+ */
+interface FunctionVisitor<R> : BreakpointTarget.Visitor<R> {
+ fun visitFunction(expression: String): R
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/BreakpointBase.kt b/platform/script-debugger/backend/src/debugger/BreakpointBase.kt
new file mode 100644
index 00000000..cd995190
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/BreakpointBase.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.util.containers.ContainerUtil
+import org.jetbrains.concurrency.Promise
+
+abstract class BreakpointBase<L : Any>(override val target: BreakpointTarget,
+ override var line: Int,
+ override val column: Int,
+ condition: String?,
+ enabled: Boolean) : Breakpoint {
+ val actualLocations: MutableList<L> = ContainerUtil.createLockFreeCopyOnWriteList<L>()
+
+ /**
+ * Whether the breakpoint data have changed with respect
+ * to the JavaScript VM data
+ */
+ @Volatile
+ protected var dirty: Boolean = false
+
+ override val isResolved: Boolean
+ get() = !actualLocations.isEmpty()
+
+ override var condition: String? = condition
+ set(value) {
+ if (field != value) {
+ field = value
+ dirty = true
+ }
+ }
+
+ override var enabled: Boolean = enabled
+ set(value) {
+ if (value != field) {
+ field = value
+ dirty = true
+ }
+ }
+
+ fun setActualLocations(value: List<L>?) {
+ actualLocations.clear()
+ if (!ContainerUtil.isEmpty(value)) {
+ actualLocations.addAll(value!!)
+ }
+ }
+
+ fun setActualLocation(value: L?) {
+ actualLocations.clear()
+ if (value != null) {
+ actualLocations.add(value)
+ }
+ }
+
+ abstract fun isVmRegistered(): Boolean
+
+ override fun hashCode(): Int {
+ var result = line
+ result *= 31 + column
+ result *= 31 + (if (enabled) 1 else 0)
+ if (condition != null) {
+ result *= 31 + condition!!.hashCode()
+ }
+ result *= 31 + target.hashCode()
+ return result
+ }
+
+ abstract fun flush(breakpointManager: BreakpointManager): Promise<*>
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/BreakpointManager.kt b/platform/script-debugger/backend/src/debugger/BreakpointManager.kt
new file mode 100644
index 00000000..2f1e355e
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/BreakpointManager.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.util.Url
+import org.jetbrains.concurrency.Promise
+import java.util.*
+
+interface BreakpointManager {
+ enum class MUTE_MODE {
+ ALL,
+ ONE,
+ NONE
+ }
+
+ val breakpoints: Iterable<Breakpoint>
+
+ val regExpBreakpointSupported: Boolean
+ get() = false
+
+ @Deprecated("use another overload")
+ fun setBreakpoint(target: BreakpointTarget,
+ line: Int,
+ condition: String? = null): Breakpoint {
+ throw UnsupportedOperationException()
+ }
+
+ fun setBreakpoint(target: BreakpointTarget,
+ line: Int,
+ column: Int = Breakpoint.EMPTY_VALUE,
+ url: Url? = null,
+ condition: String? = null,
+ ignoreCount: Int = Breakpoint.EMPTY_VALUE): SetBreakpointResult
+
+ fun remove(breakpoint: Breakpoint): Promise<*>
+
+ /**
+ * Supports targets that refer to function text in form of function-returning
+ * JavaScript expression.
+ * E.g. you can set a breakpoint on the 5th line of user method addressed as
+ * 'PropertiesDialog.prototype.loadData'.
+ * Expression is calculated immediately and never recalculated again.
+ */
+ val functionSupport: ((expression: String) -> BreakpointTarget)?
+ get() = null
+
+ // Could be called multiple times for breakpoint
+ fun addBreakpointListener(listener: BreakpointListener)
+
+ fun removeAll(): Promise<*>
+
+ fun getMuteMode(): MUTE_MODE = BreakpointManager.MUTE_MODE.ONE
+
+ /**
+ * Flushes the breakpoint parameter changes (set* methods) into the browser
+ * and invokes the callback once the operation has finished. This method must
+ * be called for the set* method invocations to take effect.
+
+ */
+ fun flush(breakpoint: Breakpoint): Promise<*>
+
+ /**
+ * Asynchronously enables or disables all breakpoints on remote. 'Enabled' means that
+ * breakpoints behave as normal, 'disabled' means that VM doesn't stop on breakpoints.
+ * It doesn't update individual properties of [Breakpoint]s. Method call
+ * with a null value and not null callback simply returns current value.
+ */
+ fun enableBreakpoints(enabled: Boolean): Promise<*>
+
+ fun setBreakOnFirstStatement()
+
+ fun isBreakOnFirstStatement(context: SuspendContext<*>): Boolean
+
+ interface SetBreakpointResult
+ data class BreakpointExist(val existingBreakpoint: Breakpoint) : SetBreakpointResult
+ data class BreakpointCreated(val breakpoint: Breakpoint, val isResolved: Promise<out Breakpoint>) : SetBreakpointResult
+}
+
+interface BreakpointListener : EventListener {
+ fun resolved(breakpoint: Breakpoint)
+
+ fun errorOccurred(breakpoint: Breakpoint, errorMessage: String?)
+
+ fun nonProvisionalBreakpointRemoved(breakpoint: Breakpoint) {
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/BreakpointManagerBase.kt b/platform/script-debugger/backend/src/debugger/BreakpointManagerBase.kt
new file mode 100644
index 00000000..19f4749c
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/BreakpointManagerBase.kt
@@ -0,0 +1,134 @@
+// 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.concurrency.ConcurrentCollectionFactory
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.util.EventDispatcher
+import com.intellij.util.SmartList
+import com.intellij.util.Url
+import com.intellij.util.containers.ContainerUtil
+import gnu.trove.TObjectHashingStrategy
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.all
+import org.jetbrains.concurrency.nullPromise
+import org.jetbrains.concurrency.rejectedPromise
+import java.util.concurrent.ConcurrentMap
+
+abstract class BreakpointManagerBase<T : BreakpointBase<*>> : BreakpointManager {
+ override val breakpoints: MutableSet<T> = ContainerUtil.newConcurrentSet<T>()
+
+ protected val breakpointDuplicationByTarget: ConcurrentMap<T, T> = ConcurrentCollectionFactory.createMap<T, T>(object : TObjectHashingStrategy<T> {
+ override fun computeHashCode(b: T): Int {
+ var result = b.line
+ result *= 31 + b.column
+ if (b.condition != null) {
+ result *= 31 + b.condition!!.hashCode()
+ }
+ result *= 31 + b.target.hashCode()
+ return result
+ }
+
+ override fun equals(b1: T, b2: T) =
+ b1.target.javaClass == b2.target.javaClass &&
+ b1.target == b2.target &&
+ b1.line == b2.line &&
+ b1.column == b2.column &&
+ StringUtil.equals(b1.condition, b2.condition)
+ })
+
+ protected val dispatcher: EventDispatcher<BreakpointListener> = EventDispatcher.create(BreakpointListener::class.java)
+
+ protected abstract fun createBreakpoint(target: BreakpointTarget, line: Int, column: Int, condition: String?, ignoreCount: Int, enabled: Boolean): T
+
+ protected abstract fun doSetBreakpoint(target: BreakpointTarget, url: Url?, breakpoint: T): Promise<out Breakpoint>
+
+ override fun setBreakpoint(target: BreakpointTarget,
+ line: Int,
+ column: Int,
+ url: Url?,
+ condition: String?,
+ ignoreCount: Int): BreakpointManager.SetBreakpointResult {
+ val breakpoint = createBreakpoint(target, line, column, condition, ignoreCount, true)
+ val existingBreakpoint = breakpointDuplicationByTarget.putIfAbsent(breakpoint, breakpoint)
+ if (existingBreakpoint != null) {
+ return BreakpointManager.BreakpointExist(existingBreakpoint)
+ }
+
+ breakpoints.add(breakpoint)
+ val promise = doSetBreakpoint(target, url, breakpoint)
+ .onError { dispatcher.multicaster.errorOccurred(breakpoint, it.message ?: it.toString()) }
+ return BreakpointManager.BreakpointCreated(breakpoint, promise)
+ }
+
+ final override fun remove(breakpoint: Breakpoint): Promise<*> {
+ @Suppress("UNCHECKED_CAST")
+ val b = breakpoint as T
+ val existed = breakpoints.remove(b)
+ if (existed) {
+ breakpointDuplicationByTarget.remove(b)
+ }
+ return if (!existed || !b.isVmRegistered()) nullPromise() else doClearBreakpoint(b)
+ }
+
+ final override fun removeAll(): Promise<*> {
+ val list = breakpoints.toList()
+ breakpoints.clear()
+ breakpointDuplicationByTarget.clear()
+ val promises = SmartList<Promise<*>>()
+ for (b in list) {
+ if (b.isVmRegistered()) {
+ promises.add(doClearBreakpoint(b))
+ }
+ }
+ return promises.all()
+ }
+
+ protected abstract fun doClearBreakpoint(breakpoint: T): Promise<*>
+
+ final override fun addBreakpointListener(listener: BreakpointListener) {
+ dispatcher.addListener(listener)
+ }
+
+ protected fun notifyBreakpointResolvedListener(breakpoint: T) {
+ if (breakpoint.isResolved) {
+ dispatcher.multicaster.resolved(breakpoint)
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun flush(breakpoint: Breakpoint): Promise<*> = (breakpoint as T).flush(this)
+
+ override fun enableBreakpoints(enabled: Boolean): Promise<*> = rejectedPromise<Any?>("Unsupported")
+
+ override fun setBreakOnFirstStatement() {
+ }
+
+ override fun isBreakOnFirstStatement(context: SuspendContext<*>): Boolean = false
+}
+
+// used in goland
+@Suppress("unused")
+class DummyBreakpointManager : BreakpointManager {
+ override val breakpoints: Iterable<Breakpoint>
+ get() = emptyList()
+
+ override fun setBreakpoint(target: BreakpointTarget, line: Int, column: Int, url: Url?, condition: String?, ignoreCount: Int): BreakpointManager.SetBreakpointResult {
+ throw UnsupportedOperationException()
+ }
+
+ override fun remove(breakpoint: Breakpoint): Promise<*> = nullPromise()
+
+ override fun addBreakpointListener(listener: BreakpointListener) {
+ }
+
+ override fun removeAll(): Promise<*> = nullPromise()
+
+ override fun flush(breakpoint: Breakpoint): Promise<*> = nullPromise()
+
+ override fun enableBreakpoints(enabled: Boolean): Promise<*> = nullPromise()
+
+ override fun setBreakOnFirstStatement() {
+ }
+
+ override fun isBreakOnFirstStatement(context: SuspendContext<*>): Boolean = false
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/BreakpointTarget.java b/platform/script-debugger/backend/src/debugger/BreakpointTarget.java
new file mode 100644
index 00000000..55b952b6
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/BreakpointTarget.java
@@ -0,0 +1,128 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+
+/**
+ * A reference to some JavaScript text that you can set breakpoints on. The reference may
+ * be in form of script name, script id etc.
+ * This type is essentially an Algebraic Type with several cases. Additional cases are provided
+ * in form of optional extensions.
+ *
+ * @see ScriptName
+ * @see ScriptId
+ */
+public abstract class BreakpointTarget {
+ /**
+ * Dispatches call on the actual Target type.
+ *
+ * @param visitor user-provided {@link Visitor} that may also implement some additional
+ * interfaces (for extended types) that is checked on runtime
+ */
+ public abstract <R> R accept(Visitor<R> visitor);
+
+ public interface Visitor<R> {
+ R visitScriptName(String scriptName);
+
+ R visitScript(Script script);
+
+ R visitUnknown(BreakpointTarget target);
+ }
+
+ /**
+ * A target that refers to a script by its id
+ */
+ public static final class ScriptId extends BreakpointTarget {
+ public final Script script;
+
+ public ScriptId(@NotNull Script script) {
+ this.script = script;
+ }
+
+ @Override
+ public <R> R accept(Visitor<R> visitor) {
+ return visitor.visitScript(script);
+ }
+
+ @Override
+ public String toString() {
+ return script.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ return script.equals(((ScriptId)o).script);
+ }
+
+ @Override
+ public int hashCode() {
+ return script.hashCode();
+ }
+ }
+
+ public abstract String toString();
+
+ /**
+ * A target that refers to a script by its name. Breakpoint will be set on every matching script currently loaded in VM.
+ */
+ public static final class ScriptName extends BreakpointTarget {
+ private final String name;
+
+ public ScriptName(@NotNull String name) {
+ this.name = name;
+ }
+
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public <R> R accept(@NotNull Visitor<R> visitor) {
+ return visitor.visitScriptName(name);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ return name.equals(((ScriptName)o).name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/CallFrame.kt b/platform/script-debugger/backend/src/debugger/CallFrame.kt
new file mode 100755
index 00000000..3fde012a
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/CallFrame.kt
@@ -0,0 +1,59 @@
+/*
+ * 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 org.jetbrains.concurrency.Promise
+
+interface CallFrame {
+ /**
+ * @return the scopes known in this frame
+ */
+ val variableScopes: List<Scope>
+
+ val hasOnlyGlobalScope: Boolean
+
+ /**
+ * receiver variable known in this frame ("this" variable)
+ * Computed variable must be null if no receiver variable
+ */
+ val receiverVariable: Promise<Variable?>
+
+ val line: Int
+
+ val column: Int
+
+ /**
+ * @return the name of the current function of this frame
+ */
+ val functionName: String?
+
+ /**
+ * @return context for evaluating expressions in scope of this frame
+ */
+ val evaluateContext: EvaluateContext
+
+ /**
+ * @see com.intellij.xdebugger.frame.XStackFrame.getEqualityObject
+ */
+ val equalityObject: Any
+
+ /**
+ * Name of function which scheduled some handler for top frames of async stack.
+ */
+ val asyncFunctionName: String?
+
+ val isFromAsyncStack: Boolean
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/CallFrameBase.kt b/platform/script-debugger/backend/src/debugger/CallFrameBase.kt
new file mode 100644
index 00000000..46d73486
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/CallFrameBase.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.NotNullLazyValue
+
+const val RECEIVER_NAME: String = "this"
+
+@Deprecated("")
+/**
+ * Use kotlin - base class is not required in this case (no boilerplate code)
+ */
+/**
+ * You must initialize [.scopes] or override [.getVariableScopes]
+ */
+abstract class CallFrameBase(override val functionName: String?, override val line: Int, override val column: Int, override val evaluateContext: EvaluateContext) : CallFrame {
+ protected var scopes: NotNullLazyValue<List<Scope>>? = null
+
+ override var hasOnlyGlobalScope: Boolean = false
+ protected set(value: Boolean) {
+ field = value
+ }
+
+ override val variableScopes: List<Scope>
+ get() = scopes!!.value
+
+ override val asyncFunctionName: String? = null
+
+ override val isFromAsyncStack: Boolean = false
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/DebugEventListener.java b/platform/script-debugger/backend/src/debugger/DebugEventListener.java
new file mode 100755
index 00000000..6a3a5a65
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/DebugEventListener.java
@@ -0,0 +1,74 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.EventListener;
+
+public interface DebugEventListener extends EventListener {
+ /**
+ * Reports the virtual machine has suspended (on hitting
+ * breakpoints or a step end). The {@code context} can be used to access the
+ * current backtrace.
+ */
+ default void suspended(@NotNull SuspendContext<?> context) {
+ }
+
+ /**
+ * Reports the virtual machine has resumed. This can happen
+ * asynchronously, due to a user action in the browser (without explicitly resuming the VM through
+ * @param vm
+ */
+ default void resumed(@NotNull Vm vm) {
+ }
+
+ /**
+ * Reports that a new script has been loaded.
+ */
+ default void scriptAdded(@NotNull Vm vm, @NotNull Script script, @Nullable String sourceMapUrl) {
+ }
+
+ /**
+ * Reports that the script has been collected and is no longer used in VM.
+ */
+ default void scriptRemoved(@NotNull Script script) {
+ }
+
+ default void scriptsCleared() {
+ }
+
+ /**
+ * Reports that script source has been altered in remote VM.
+ */
+ default void scriptContentChanged(@NotNull Script newScript) {
+ }
+
+ /**
+ * Reports a navigation event on the target.
+ *
+ * @param newUrl the new URL of the debugged target
+ */
+ default void navigated(String newUrl) {
+ }
+
+ default void errorOccurred(@NotNull String errorMessage) {
+ }
+
+ default void childVmAdded(@NotNull Vm childVm) {
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/DeclarativeScope.kt b/platform/script-debugger/backend/src/debugger/DeclarativeScope.kt
new file mode 100644
index 00000000..b413c249
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/DeclarativeScope.kt
@@ -0,0 +1,22 @@
+// 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 org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.cancelledPromise
+import org.jetbrains.debugger.values.ObjectValue
+import org.jetbrains.debugger.values.ValueManager
+
+abstract class DeclarativeScope<VALUE_MANAGER : ValueManager>(type: ScopeType, description: String? = null) : ScopeBase(type, description) {
+ protected abstract val childrenManager: VariablesHost<VALUE_MANAGER>
+
+ override val variablesHost: VariablesHost<*>
+ get() = childrenManager
+
+ protected fun loadScopeObjectProperties(value: ObjectValue): Promise<List<Variable>> {
+ if (childrenManager.valueManager.isObsolete) {
+ return cancelledPromise()
+ }
+
+ return value.properties.onSuccess { childrenManager.updateCacheStamp() }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/EvaluateContext.kt b/platform/script-debugger/backend/src/debugger/EvaluateContext.kt
new file mode 100644
index 00000000..3c82383a
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/EvaluateContext.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.project.Project
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.values.Value
+
+data class EvaluateResult(val value: Value, val wasThrown: Boolean = false)
+
+/**
+ * A context in which watch expressions may be evaluated. Typically corresponds to stack frame
+ * of suspended process, but may also be detached from any stack frame
+ */
+interface EvaluateContext {
+ /**
+ * Evaluates an arbitrary `expression` in the particular context.
+ * Previously loaded [org.jetbrains.debugger.values.ObjectValue]s can be addressed from the expression if listed in
+ * additionalContext parameter.
+ */
+ fun evaluate(expression: String, additionalContext: Map<String, Any>? = null, enableBreak: Boolean = false, project: Project? = null): Promise<EvaluateResult>
+
+ /**
+ * optional to implement, some protocols, WIP for example, require you to release remote objects
+ */
+ fun withValueManager(objectGroup: String): EvaluateContext
+
+ /**
+ * If you evaluate "foo.bar = 4" and want to update Variables view (and all other clients), you can use use this task
+ * @param promise
+ */
+ fun refreshOnDone(promise: Promise<*>): Promise<*>
+
+ /**
+ * call only if withLoader was called before
+ */
+ fun releaseObjects() {
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/EvaluateContextBase.kt b/platform/script-debugger/backend/src/debugger/EvaluateContextBase.kt
new file mode 100644
index 00000000..cc622e9a
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/EvaluateContextBase.kt
@@ -0,0 +1,26 @@
+/*
+ * 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 org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.thenRun
+import org.jetbrains.debugger.values.ValueManager
+
+abstract class EvaluateContextBase<VALUE_MANAGER : ValueManager>(val valueManager: VALUE_MANAGER) : EvaluateContext {
+ override fun withValueManager(objectGroup: String): EvaluateContextBase<VALUE_MANAGER> = this
+
+ override fun refreshOnDone(promise: Promise<*>): Promise<Unit> = promise.thenRun { valueManager.clearCaches() }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ExceptionCatchMode.java b/platform/script-debugger/backend/src/debugger/ExceptionCatchMode.java
new file mode 100644
index 00000000..1b757939
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ExceptionCatchMode.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Defines when VM will break on exception throw (before stack unwind happened)
+ */
+public enum ExceptionCatchMode {
+ /**
+ * VM always breaks when exception is being thrown
+ */
+ ALL,
+
+ /**
+ * VM breaks when exception is being thrown without try-catch that is going to catch it
+ */
+ UNCAUGHT,
+
+ /**
+ * VM doesn't break when exception is being thrown
+ */
+ NONE
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ExceptionData.java b/platform/script-debugger/backend/src/debugger/ExceptionData.java
new file mode 100755
index 00000000..f423b5a9
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ExceptionData.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util.ThreeState;
+import org.jetbrains.debugger.values.Value;
+
+/**
+ * A JavaScript exception data holder for exceptions reported by a JavaScript
+ * virtual machine.
+ */
+public interface ExceptionData {
+ /**
+ * @return the thrown exception value
+ */
+ Value getExceptionValue();
+
+ /**
+ * @return whether this exception is uncaught
+ */
+ ThreeState isUncaught();
+
+ /**
+ * @return the text of the source line where the exception was thrown or null
+ */
+ String getSourceText();
+
+ /**
+ * @return the exception description (plain text)
+ */
+ String getExceptionMessage();
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ExceptionDataBase.java b/platform/script-debugger/backend/src/debugger/ExceptionDataBase.java
new file mode 100644
index 00000000..2dd0f5d7
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ExceptionDataBase.java
@@ -0,0 +1,31 @@
+/*
+ * 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 org.jetbrains.debugger.values.Value;
+
+public abstract class ExceptionDataBase implements ExceptionData {
+ private final Value exceptionValue;
+
+ protected ExceptionDataBase(Value exceptionValue) {
+ this.exceptionValue = exceptionValue;
+ }
+
+ @Override
+ public final Value getExceptionValue() {
+ return exceptionValue;
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ObjectProperty.kt b/platform/script-debugger/backend/src/debugger/ObjectProperty.kt
new file mode 100644
index 00000000..8162f245
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ObjectProperty.kt
@@ -0,0 +1,35 @@
+/*
+ * 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 org.jetbrains.debugger.values.FunctionValue
+
+/**
+ * Exposes additional data if variable is a property of object and its property descriptor
+ * is available.
+ */
+interface ObjectProperty : Variable {
+ val isWritable: Boolean
+
+ val getter: FunctionValue?
+
+ val setter: FunctionValue?
+
+
+ val isConfigurable: Boolean
+
+ val isEnumerable: Boolean
+}
diff --git a/platform/script-debugger/backend/src/debugger/ObjectPropertyImpl.kt b/platform/script-debugger/backend/src/debugger/ObjectPropertyImpl.kt
new file mode 100644
index 00000000..2fd3f0aa
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ObjectPropertyImpl.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.util.BitUtil
+import org.jetbrains.debugger.values.FunctionValue
+import org.jetbrains.debugger.values.Value
+
+class ObjectPropertyImpl(name: String,
+ value: Value?,
+ override val getter: FunctionValue? = null,
+ override val setter: FunctionValue? = null,
+ valueModifier: ValueModifier? = null,
+ private val flags: Int = 0) : VariableImpl(name, value, valueModifier), ObjectProperty {
+ companion object {
+ val WRITABLE: Int = 0x01
+ val CONFIGURABLE: Int = 0x02
+ val ENUMERABLE: Int = 0x04
+ }
+
+ override val isWritable: Boolean
+ get() = BitUtil.isSet(flags, WRITABLE)
+
+ override val isConfigurable: Boolean
+ get() = BitUtil.isSet(flags, CONFIGURABLE)
+
+ override val isEnumerable: Boolean
+ get() = BitUtil.isSet(flags, ENUMERABLE)
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/Scope.kt b/platform/script-debugger/backend/src/debugger/Scope.kt
new file mode 100644
index 00000000..36e145dc
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/Scope.kt
@@ -0,0 +1,55 @@
+/*
+ * 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 org.jetbrains.debugger.values.ObjectValue
+
+enum class ScopeType {
+ GLOBAL,
+ LOCAL,
+ WITH,
+ CLOSURE,
+ CATCH,
+ LIBRARY,
+ CLASS,
+ INSTANCE,
+ BLOCK,
+ SCRIPT,
+ UNKNOWN
+}
+
+interface Scope {
+ val type: ScopeType
+
+ /**
+ * Class or function or file name
+ */
+ val description: String?
+
+ val variablesHost: VariablesHost<*>
+
+ val isGlobal: Boolean
+}
+
+abstract class ScopeBase(override val type: ScopeType, override val description: String?) : Scope {
+ override val isGlobal: Boolean
+ get() = type === ScopeType.GLOBAL || type === ScopeType.LIBRARY
+}
+
+class ObjectScope(type: ScopeType, private val value: ObjectValue) : ScopeBase(type, value.valueString), Scope {
+ override val variablesHost: VariablesHost<*>
+ get() = value.variablesHost
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/Script.kt b/platform/script-debugger/backend/src/debugger/Script.kt
new file mode 100755
index 00000000..e2d57e08
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/Script.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.UserDataHolderEx
+import com.intellij.util.Url
+import org.jetbrains.debugger.sourcemap.SourceMap
+
+interface Script : UserDataHolderEx {
+ enum class Type {
+ /** A native, internal JavaScript VM script */
+ NATIVE,
+
+ /** A script supplied by an extension */
+ EXTENSION,
+
+ /** A normal user script */
+ NORMAL
+ }
+
+ val type: Type
+
+ var sourceMap: SourceMap?
+
+ val url: Url
+
+ val functionName: String?
+ get() = null
+
+ val line: Int
+
+ val column: Int
+
+ val endLine: Int
+
+ val isWorker: Boolean
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ScriptBase.kt b/platform/script-debugger/backend/src/debugger/ScriptBase.kt
new file mode 100755
index 00000000..0f41a9cf
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ScriptBase.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.UserDataHolderBase
+import com.intellij.util.Url
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.sourcemap.SourceMap
+
+abstract class ScriptBase(override val type: Script.Type,
+ override val url: Url,
+ line: Int,
+ override val column: Int,
+ override val endLine: Int) : UserDataHolderBase(), Script {
+ override val line: Int = Math.max(line, 0)
+
+ @SuppressWarnings("UnusedDeclaration")
+ @Volatile
+ private var source: Promise<String>? = null
+
+ override var sourceMap: SourceMap? = null
+
+ override fun toString(): String = "[url=$url, lineRange=[$line;$endLine]]"
+
+ override val isWorker: Boolean = false
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ScriptManager.kt b/platform/script-debugger/backend/src/debugger/ScriptManager.kt
new file mode 100644
index 00000000..a8cf29a6
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ScriptManager.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.util.Processor
+import com.intellij.util.Url
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.values.FunctionValue
+
+const val VM_SCHEME: String = "vm"
+
+interface ScriptManager {
+ fun getSource(script: Script): Promise<String>
+
+ fun hasSource(script: Script): Boolean
+
+ fun containsScript(script: Script): Boolean
+
+ fun forEachScript(scriptProcessor: (Script) -> Boolean)
+
+ fun forEachScript(scriptProcessor: Processor<Script>): Unit = forEachScript { scriptProcessor.process(it)}
+
+ fun getScript(function: FunctionValue): Promise<Script>
+
+ fun getScript(frame: CallFrame): Script?
+
+ fun findScriptByUrl(rawUrl: String): Script?
+
+ fun findScriptByUrl(url: Url): Script?
+
+ fun findScriptById(id: String): Script? = null
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ScriptManagerBase.kt b/platform/script-debugger/backend/src/debugger/ScriptManagerBase.kt
new file mode 100644
index 00000000..d863f7bf
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ScriptManagerBase.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.util.Url
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.PromiseManager
+import org.jetbrains.concurrency.rejectedPromise
+
+abstract class ScriptManagerBase<SCRIPT : ScriptBase> : ScriptManager {
+ @Suppress("UNCHECKED_CAST")
+ @SuppressWarnings("unchecked")
+ private val scriptSourceLoader = object : PromiseManager<ScriptBase, String>(ScriptBase::class.java) {
+ override fun load(script: ScriptBase) = loadScriptSource(script as SCRIPT)
+ }
+
+ protected abstract fun loadScriptSource(script: SCRIPT): Promise<String>
+
+ override fun getSource(script: Script): Promise<String> {
+ if (!containsScript(script)) {
+ return rejectedPromise("No Script")
+ }
+ @Suppress("UNCHECKED_CAST")
+ return scriptSourceLoader.get(script as SCRIPT)
+ }
+
+ override fun hasSource(script: Script): Boolean {
+ @Suppress("UNCHECKED_CAST")
+ return scriptSourceLoader.has(script as SCRIPT)
+ }
+
+ fun setSource(script: SCRIPT, source: String?) {
+ scriptSourceLoader.set(script, source)
+ }
+}
+
+val Url.isSpecial: Boolean
+ get() = !isInLocalFileSystem && (scheme == null || scheme == VM_SCHEME || authority == null) \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ScriptManagerBaseEx.kt b/platform/script-debugger/backend/src/debugger/ScriptManagerBaseEx.kt
new file mode 100644
index 00000000..a20ef260
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ScriptManagerBaseEx.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.util.Url
+import com.intellij.util.Urls
+import com.intellij.util.containers.ContainerUtil
+import java.util.concurrent.ConcurrentMap
+
+abstract class ScriptManagerBaseEx<SCRIPT : ScriptBase> : ScriptManagerBase<SCRIPT>() {
+ protected val idToScript: ConcurrentMap<String, SCRIPT> = ContainerUtil.newConcurrentMap<String, SCRIPT>()
+
+ final override fun forEachScript(scriptProcessor: (Script) -> Boolean) {
+ for (script in idToScript.values) {
+ if (!scriptProcessor(script)) {
+ return
+ }
+ }
+ }
+
+ final override fun findScriptById(id: String): SCRIPT? = idToScript[id]
+
+ fun clear(listener: DebugEventListener) {
+ idToScript.clear()
+ listener.scriptsCleared()
+ }
+
+ final override fun findScriptByUrl(rawUrl: String): SCRIPT? = findScriptByUrl(rawUrlToOurUrl(rawUrl))
+
+ final override fun findScriptByUrl(url: Url): SCRIPT? {
+ return idToScript.values.find { url == it.url }
+ // TODO Searching ignoring parameters may be fragile, because parameters define script e.g. in webpack. Consider dropping it.
+ ?: idToScript.values.find { url.equalsIgnoreParameters(it.url) }
+ }
+
+ open fun rawUrlToOurUrl(rawUrl: String): Url = Urls.parseEncoded(rawUrl)!!
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ScriptRegExpBreakpointTarget.kt b/platform/script-debugger/backend/src/debugger/ScriptRegExpBreakpointTarget.kt
new file mode 100644
index 00000000..bfd9d98e
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ScriptRegExpBreakpointTarget.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
+
+class ScriptRegExpBreakpointTarget(private val regExp: String, val language: String? = null) : BreakpointTarget() {
+ override fun <R> accept(visitor: BreakpointTarget.Visitor<R>): R {
+ if (visitor is ScriptRegExpSupportVisitor<*>) {
+ return (visitor as ScriptRegExpSupportVisitor<R>).visitRegExp(this)
+ }
+ else {
+ return visitor.visitUnknown(this)
+ }
+ }
+
+ override fun toString(): String = regExp
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+ if (other == null || javaClass != other.javaClass) {
+ return false
+ }
+ return regExp == (other as ScriptRegExpBreakpointTarget).regExp
+ }
+
+ override fun hashCode(): Int = regExp.hashCode()
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ScriptRegExpSupportVisitor.java b/platform/script-debugger/backend/src/debugger/ScriptRegExpSupportVisitor.java
new file mode 100644
index 00000000..9ab738d0
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ScriptRegExpSupportVisitor.java
@@ -0,0 +1,26 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+
+/**
+ * Additional interface that user visitor may implement for {@link BreakpointTarget#accept}
+ * method.
+ */
+public interface ScriptRegExpSupportVisitor<R> extends BreakpointTarget.Visitor<R> {
+ R visitRegExp(@NotNull ScriptRegExpBreakpointTarget target);
+}
diff --git a/platform/script-debugger/backend/src/debugger/StandaloneVmHelper.kt b/platform/script-debugger/backend/src/debugger/StandaloneVmHelper.kt
new file mode 100644
index 00000000..1d7889ef
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/StandaloneVmHelper.kt
@@ -0,0 +1,84 @@
+// 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.io.addChannelListener
+import com.intellij.util.io.shutdownIfOio
+import io.netty.channel.Channel
+import org.jetbrains.concurrency.AsyncPromise
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.errorIfNotMessage
+import org.jetbrains.concurrency.nullPromise
+import org.jetbrains.jsonProtocol.Request
+import org.jetbrains.rpc.CONNECTION_CLOSED_MESSAGE
+import org.jetbrains.rpc.LOG
+import org.jetbrains.rpc.MessageProcessor
+
+open class StandaloneVmHelper(private val vm: Vm, private val messageProcessor: MessageProcessor, channel: Channel) : AttachStateManager {
+ @Volatile
+ private var channel: Channel? = channel
+
+ fun getChannelIfActive(): Channel? {
+ val currentChannel = channel
+ return if (currentChannel == null || !currentChannel.isActive) null else currentChannel
+ }
+
+ fun write(content: Any): Boolean {
+ val channel = getChannelIfActive()
+ return channel != null && !channel.writeAndFlush(content).isCancelled
+ }
+
+ interface VmEx : Vm {
+ fun createDisconnectRequest(): Request<out Any>?
+ }
+
+ override val isAttached: Boolean
+ get() = channel != null
+
+ override fun detach(): Promise<*> {
+ val currentChannel = channel ?: return nullPromise()
+
+ messageProcessor.cancelWaitingRequests()
+ val disconnectRequest = (vm as? VmEx)?.createDisconnectRequest()
+ val promise = AsyncPromise<Any?>()
+ if (disconnectRequest == null) {
+ messageProcessor.closed()
+ channel = null
+ }
+ else {
+ messageProcessor.send(disconnectRequest)
+ .onError {
+ if (it.message != CONNECTION_CLOSED_MESSAGE) {
+ LOG.errorIfNotMessage(it)
+ }
+ }
+ // we don't wait response because 1) no response to "disconnect" message (V8 for example) 2) closed message manager just ignore any incoming messages
+ currentChannel.flush()
+ messageProcessor.closed()
+ channel = null
+ messageProcessor.cancelWaitingRequests()
+ }
+ closeChannel(currentChannel, promise)
+ return promise
+ }
+
+ protected open fun closeChannel(channel: Channel, promise: AsyncPromise<Any?>) {
+ doCloseChannel(channel, promise)
+ }
+}
+
+fun doCloseChannel(channel: Channel, promise: AsyncPromise<Any?>) {
+ channel.close().addChannelListener {
+ try {
+ it.channel().eventLoop().shutdownIfOio()
+ }
+ finally {
+ val error = it.cause()
+ if (error == null) {
+ promise.setResult(null)
+ }
+ else {
+ promise.setError(error)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/SuspendContext.kt b/platform/script-debugger/backend/src/debugger/SuspendContext.kt
new file mode 100755
index 00000000..2b280e2e
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/SuspendContext.kt
@@ -0,0 +1,66 @@
+// 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 org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.values.ValueManager
+
+/**
+ * An object that matches the execution state of the VM while suspended
+ */
+interface SuspendContext<out CALL_FRAME : CallFrame> {
+
+ val script: Script?
+ get() = topFrame?.let { vm.scriptManager.getScript(it) }
+
+ /**
+ * @return the current exception state if execution was paused because of exception, or `null` otherwise.
+ */
+ val exceptionData: ExceptionData?
+ get() = null
+
+ val topFrame: CALL_FRAME?
+
+ /**
+ * Call frames for the current suspended state (from the innermost (top) frame to the main (bottom) frame)
+ */
+ val frames: Promise<Array<CallFrame>>
+
+ /**
+ * list of the breakpoints hit on VM suspension with which this
+ * context is associated. An empty collection if the suspension was
+ * not related to hitting breakpoints (e.g. a step end)
+ */
+ val breakpointsHit: List<Breakpoint>
+
+ val hasUnresolvedBreakpointsHit: Boolean
+ get() = false
+
+ val valueManager: ValueManager
+
+ val vm: Vm
+ get() = throw UnsupportedOperationException()
+}
+
+abstract class ContextDependentAsyncResultConsumer<T>(private val context: SuspendContext<*>) : java.util.function.Consumer<T> {
+ final override fun accept(result: T) {
+ val vm = context.vm
+ if (vm.attachStateManager.isAttached && !vm.suspendContextManager.isContextObsolete(context)) {
+ accept(result, vm)
+ }
+ }
+
+ protected abstract fun accept(result: T, vm: Vm)
+}
+
+
+inline fun <T> Promise<T>.onSuccess(context: SuspendContext<*>, crossinline handler: (result: T) -> Unit): Promise<T> {
+ return onSuccess(object : ContextDependentAsyncResultConsumer<T>(context) {
+ override fun accept(result: T, vm: Vm) = handler(result)
+ })
+}
+
+inline fun Promise<*>.onError(context: SuspendContext<*>, crossinline handler: (error: Throwable) -> Unit): Promise<out Any> {
+ return onError(object : ContextDependentAsyncResultConsumer<Throwable>(context) {
+ override fun accept(result: Throwable, vm: Vm) = handler(result)
+ })
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/SuspendContextBase.kt b/platform/script-debugger/backend/src/debugger/SuspendContextBase.kt
new file mode 100644
index 00000000..5e7018a5
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/SuspendContextBase.kt
@@ -0,0 +1,19 @@
+/*
+ * 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
+
+abstract class SuspendContextBase<F : CallFrame> : SuspendContext<F> {
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/SuspendContextManager.kt b/platform/script-debugger/backend/src/debugger/SuspendContextManager.kt
new file mode 100644
index 00000000..275ba088
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/SuspendContextManager.kt
@@ -0,0 +1,80 @@
+/*
+ * 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 org.jetbrains.concurrency.Promise
+
+interface SuspendContextManager<CALL_FRAME : CallFrame> {
+ /**
+ * Tries to suspend VM. If successful, [DebugEventListener.suspended] will be called.
+ */
+ fun suspend(): Promise<*>
+
+ val context: SuspendContext<CALL_FRAME>?
+
+ val contextOrFail: SuspendContext<CALL_FRAME>
+
+ fun isContextObsolete(context: SuspendContext<*>): Boolean = this.context !== context
+
+ fun setOverlayMessage(message: String?)
+
+ /**
+ * Resumes the VM execution. This context becomes invalid until another context is supplied through the
+ * [DebugEventListener.suspended] event.
+ * @param stepAction to perform
+ * *
+ * @param stepCount steps to perform (not used if `stepAction == CONTINUE`)
+ */
+ fun continueVm(stepAction: StepAction, stepCount: Int = 1): Promise<*>
+
+ val isRestartFrameSupported: Boolean
+
+ /**
+ * Restarts a frame (all frames above are dropped from the stack, this frame is started over).
+ * for success the boolean parameter
+ * is true if VM has been resumed and is expected to get suspended again in a moment (with
+ * a standard 'resumed' notification), and is false if call frames list is already updated
+ * without VM state change (this case presently is never actually happening)
+ */
+ fun restartFrame(callFrame: CALL_FRAME): Promise<Boolean>
+
+ /**
+ * @return whether reset operation is supported for the particular callFrame
+ */
+ fun canRestartFrame(callFrame: CallFrame): Boolean
+}
+
+enum class StepAction {
+ /**
+ * Resume the JavaScript execution.
+ */
+ CONTINUE,
+
+ /**
+ * Step into the current statement.
+ */
+ IN,
+
+ /**
+ * Step over the current statement.
+ */
+ OVER,
+
+ /**
+ * Step out of the current function.
+ */
+ OUT
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/SuspendContextManagerBase.kt b/platform/script-debugger/backend/src/debugger/SuspendContextManagerBase.kt
new file mode 100644
index 00000000..3b3b3dea
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/SuspendContextManagerBase.kt
@@ -0,0 +1,64 @@
+// 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 org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.rejectedPromise
+import org.jetbrains.concurrency.resolvedPromise
+import java.util.concurrent.atomic.AtomicReference
+
+abstract class SuspendContextManagerBase<T : SuspendContext<CALL_FRAME>, CALL_FRAME : CallFrame> : SuspendContextManager<CALL_FRAME> {
+ val contextRef: AtomicReference<T> = AtomicReference()
+
+ protected abstract val debugListener: DebugEventListener
+
+ fun setContext(newContext: T) {
+ if (!contextRef.compareAndSet(null, newContext)) {
+ throw IllegalStateException("Attempt to set context, but current suspend context is already exists")
+ }
+ }
+
+ open fun updateContext(newContext: SuspendContext<*>) {
+ }
+
+ // dismiss context on resumed
+ protected fun dismissContext() {
+ contextRef.get()?.let {
+ contextDismissed(it)
+ }
+ }
+
+ protected fun dismissContextOnDone(promise: Promise<*>): Promise<*> {
+ val context = contextOrFail
+ promise.onSuccess { contextDismissed(context) }
+ return promise
+ }
+
+ fun contextDismissed(context: T) {
+ if (!contextRef.compareAndSet(context, null)) {
+ throw IllegalStateException("Expected $context, but another suspend context exists")
+ }
+ context.valueManager.markObsolete()
+ debugListener.resumed(context.vm)
+ }
+
+ override val context: SuspendContext<CALL_FRAME>?
+ get() = contextRef.get()
+
+ override val contextOrFail: T
+ get() = contextRef.get() ?: throw IllegalStateException("No current suspend context")
+
+ override fun suspend(): Promise<out Any?> = if (context == null) doSuspend() else resolvedPromise()
+
+ protected abstract fun doSuspend(): Promise<*>
+
+ override fun setOverlayMessage(message: String?) {
+ }
+
+ override fun restartFrame(callFrame: CALL_FRAME): Promise<Boolean> = restartFrame(callFrame, contextOrFail)
+
+ protected open fun restartFrame(callFrame: CALL_FRAME, currentContext: T): Promise<Boolean> = rejectedPromise<Boolean>("Unsupported")
+
+ override fun canRestartFrame(callFrame: CallFrame): Boolean = false
+
+ override val isRestartFrameSupported: Boolean = false
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ValueModifier.kt b/platform/script-debugger/backend/src/debugger/ValueModifier.kt
new file mode 100644
index 00000000..4691fc81
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ValueModifier.kt
@@ -0,0 +1,32 @@
+/*
+ * 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 org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.values.Value
+
+interface ValueModifier {
+ // expression can contains reference to another variables in current scope, so, we should evaluate it before set
+ // https://youtrack.jetbrains.com/issue/WEB-2342#comment=27-512122
+
+ // we don't worry about performance in case of simple primitive values - boolean/string/numbers,
+ // it works quickly and we don't want to complicate our code and debugger SDK
+ fun setValue(variable: Variable, newValue: String, evaluateContext: EvaluateContext): Promise<*>
+
+ fun setValue(variable: Variable, newValue: Value, evaluateContext: EvaluateContext): Promise<*>
+
+ fun evaluateGet(variable: Variable, evaluateContext: EvaluateContext): Promise<Value>
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/ValueModifierUtil.kt b/platform/script-debugger/backend/src/debugger/ValueModifierUtil.kt
new file mode 100644
index 00000000..bdd75c63
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/ValueModifierUtil.kt
@@ -0,0 +1,78 @@
+/*
+ * 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 org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.thenAsyncAccept
+import org.jetbrains.debugger.values.Value
+import org.jetbrains.io.JsonUtil
+import java.util.*
+import java.util.regex.Pattern
+
+private val KEY_NOTATION_PROPERTY_NAME_PATTERN = Pattern.compile("[\\p{L}_$]+[\\d\\p{L}_$]*")
+
+object ValueModifierUtil {
+ fun setValue(variable: Variable,
+ newValue: String,
+ evaluateContext: EvaluateContext,
+ modifier: ValueModifier): Promise<Any?> = evaluateContext.evaluate(newValue)
+ .thenAsyncAccept { modifier.setValue(variable, it.value, evaluateContext) }
+
+ fun evaluateGet(variable: Variable,
+ host: Any,
+ evaluateContext: EvaluateContext,
+ selfName: String): Promise<Value> {
+ val builder = StringBuilder(selfName)
+ appendUnquotedName(builder, variable.name)
+ return evaluateContext.evaluate(builder.toString(), Collections.singletonMap(selfName, host), false)
+ .then {
+ variable.value = it.value
+ it.value
+ }
+ }
+
+ fun propertyNamesToString(list: List<String>, quotedAware: Boolean): String {
+ val builder = StringBuilder()
+ for (i in list.indices.reversed()) {
+ val name = list[i]
+ doAppendName(builder, name, quotedAware && (name[0] == '"' || name[0] == '\''))
+ }
+ return builder.toString()
+ }
+
+ fun appendUnquotedName(builder: StringBuilder, name: String) {
+ doAppendName(builder, name, false)
+ }
+}
+
+private fun doAppendName(builder: StringBuilder, name: String, quoted: Boolean) {
+ val isProperty = !builder.isEmpty()
+ if (isProperty) {
+ val useKeyNotation = !quoted && KEY_NOTATION_PROPERTY_NAME_PATTERN.matcher(name).matches()
+ if (useKeyNotation) {
+ builder.append('.').append(name)
+ }
+ else {
+ builder.append('[')
+ if (quoted) builder.append(name)
+ else JsonUtil.escape(name, builder)
+ builder.append(']')
+ }
+ }
+ else {
+ builder.append(name)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/Variable.java b/platform/script-debugger/backend/src/debugger/Variable.java
new file mode 100755
index 00000000..98cfe3e9
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/Variable.java
@@ -0,0 +1,49 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.debugger.values.Value;
+
+public interface Variable {
+ /**
+ * @return whether it is possible to read this variable
+ */
+ boolean isReadable();
+
+ /**
+ * Returns the value of this variable.
+ *
+ * @return a Value corresponding to this variable. {@code null} if the property has accessor descriptor
+ * @see #isReadable()
+ */
+ @Nullable
+ Value getValue();
+
+ void setValue(Value value);
+
+ @NotNull
+ String getName();
+
+ /**
+ * @return whether it is possible to modify this variable
+ */
+ boolean isMutable();
+
+ @Nullable
+ ValueModifier getValueModifier();
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/VariableImpl.java b/platform/script-debugger/backend/src/debugger/VariableImpl.java
new file mode 100644
index 00000000..178420c3
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/VariableImpl.java
@@ -0,0 +1,75 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.debugger.values.Value;
+
+public class VariableImpl implements Variable {
+ protected volatile Value value;
+ private final String name;
+
+ private final ValueModifier valueModifier;
+
+ public VariableImpl(@NotNull String name, @Nullable Value value, @Nullable ValueModifier valueModifier) {
+ this.name = name;
+ this.value = value;
+ this.valueModifier = valueModifier;
+ }
+
+ public VariableImpl(@NotNull String name, @NotNull Value value) {
+ this(name, value, null);
+ }
+
+ @Nullable
+ @Override
+ public final ValueModifier getValueModifier() {
+ return valueModifier;
+ }
+
+ @NotNull
+ @Override
+ public final String getName() {
+ return name;
+ }
+
+ @Nullable
+ @Override
+ public final Value getValue() {
+ return value;
+ }
+
+ @Override
+ public void setValue(Value value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean isMutable() {
+ return valueModifier != null;
+ }
+
+ @Override
+ public boolean isReadable() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "[Variable: name=" + getName() + ", value=" + getValue() + ']';
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/VariablesHost.java b/platform/script-debugger/backend/src/debugger/VariablesHost.java
new file mode 100644
index 00000000..ba9823b6
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/VariablesHost.java
@@ -0,0 +1,88 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.concurrency.Promise;
+import org.jetbrains.concurrency.PromiseManager;
+import org.jetbrains.concurrency.Promises;
+import org.jetbrains.debugger.values.ValueManager;
+
+import java.util.List;
+
+public abstract class VariablesHost<VALUE_MANAGER extends ValueManager> {
+ @SuppressWarnings("unchecked")
+ private static final PromiseManager<VariablesHost, List<Variable>> VARIABLES_LOADER =
+ new PromiseManager<VariablesHost, List<Variable>>(VariablesHost.class) {
+ @Override
+ public boolean isUpToDate(@NotNull VariablesHost host, @NotNull List<Variable> data) {
+ return host.valueManager.getCacheStamp() == host.cacheStamp;
+ }
+
+ @NotNull
+ @Override
+ public Promise load(@NotNull VariablesHost host) {
+ return host.valueManager.isObsolete() ? Promises.cancelledPromise() : host.load();
+ }
+ };
+
+ @SuppressWarnings("UnusedDeclaration")
+ private volatile Promise<List<Variable>> result;
+
+ private volatile int cacheStamp = -1;
+
+ public final VALUE_MANAGER valueManager;
+
+ public VariablesHost(@NotNull VALUE_MANAGER manager) {
+ valueManager = manager;
+ }
+
+ /**
+ * You must call {@link #updateCacheStamp()} when data loaded
+ */
+ @NotNull
+ public final Promise<List<Variable>> get() {
+ return VARIABLES_LOADER.get(this);
+ }
+
+ @Nullable
+ public final Promise.State getState() {
+ return VARIABLES_LOADER.getState(this);
+ }
+
+ public final void set(@NotNull List<Variable> result) {
+ updateCacheStamp();
+ VARIABLES_LOADER.set(this, result);
+ }
+
+ @NotNull
+ protected abstract Promise<List<Variable>> load();
+
+ public final void updateCacheStamp() {
+ cacheStamp = valueManager.getCacheStamp();
+ }
+
+ /**
+ * Some backends requires to reload the whole call stack on scope variable modification, but not all API is asynchronous (compromise, to not increase complexity),
+ * for example, {@link CallFrame#getVariableScopes()} is not asynchronous method. So, you must use returned callback to postpone your code working with updated data.
+ */
+ public Promise<?> clearCaches() {
+ cacheStamp = -1;
+ VARIABLES_LOADER.reset(this);
+ return Promises.resolvedPromise();
+ }
+}
diff --git a/platform/script-debugger/backend/src/debugger/Vm.kt b/platform/script-debugger/backend/src/debugger/Vm.kt
new file mode 100644
index 00000000..b7ef99b4
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/Vm.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.UserDataHolderEx
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.nullPromise
+
+interface AttachStateManager {
+ fun detach(): Promise<*> = nullPromise()
+
+ val isAttached: Boolean
+ get() = true
+}
+
+interface Vm : UserDataHolderEx {
+ val debugListener: DebugEventListener
+
+ val attachStateManager: AttachStateManager
+
+ val evaluateContext: EvaluateContext?
+
+ val scriptManager: ScriptManager
+
+ val breakpointManager: BreakpointManager
+
+ val suspendContextManager: SuspendContextManager<out CallFrame>
+
+ /**
+ * Controls whether VM stops on exceptions
+ */
+ fun setBreakOnException(catchMode: ExceptionCatchMode): Promise<*> = nullPromise()
+
+ val presentableName: String
+ get() = "main loop"
+
+ val childVMs: MutableList<Vm>
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/VmBase.kt b/platform/script-debugger/backend/src/debugger/VmBase.kt
new file mode 100644
index 00000000..6af35c76
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/VmBase.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.UserDataHolderBase
+import com.intellij.util.containers.ContainerUtil
+
+abstract class VmBase(override val debugListener: DebugEventListener) : Vm, AttachStateManager, UserDataHolderBase() {
+ override val evaluateContext: EvaluateContext? by lazy(LazyThreadSafetyMode.NONE) { computeEvaluateContext() }
+
+ override val attachStateManager: AttachStateManager = this
+
+ protected open fun computeEvaluateContext(): EvaluateContext? = null
+
+ override val childVMs: MutableList<Vm> = ContainerUtil.createConcurrentList()
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/sourcemap/Base64VLQ.java b/platform/script-debugger/backend/src/debugger/sourcemap/Base64VLQ.java
new file mode 100644
index 00000000..7576f500
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/sourcemap/Base64VLQ.java
@@ -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 org.jetbrains.debugger.sourcemap;
+
+final class Base64VLQ {
+ private Base64VLQ() {
+ }
+
+ interface CharIterator {
+ boolean hasNext();
+ char next();
+ }
+
+ // A Base64 VLQ digit can represent 5 bits, so it is base-32.
+ private static final int VLQ_BASE_SHIFT = 5;
+ private static final int VLQ_BASE = 1 << VLQ_BASE_SHIFT;
+
+ // A mask of bits for a VLQ digit (11111), 31 decimal.
+ private static final int VLQ_BASE_MASK = VLQ_BASE - 1;
+
+ // The continuation bit is the 6th bit.
+ private static final int VLQ_CONTINUATION_BIT = VLQ_BASE;
+
+ /**
+ * Decodes the next VLQValue from the provided CharIterator.
+ */
+ public static int decode(CharIterator in) {
+ int result = 0;
+ int shift = 0;
+ int digit;
+ do {
+ digit = Base64.BASE64_DECODE_MAP[in.next()];
+ assert (digit != -1) : "invalid char";
+
+ result += (digit & VLQ_BASE_MASK) << shift;
+ shift += VLQ_BASE_SHIFT;
+ }
+ while ((digit & VLQ_CONTINUATION_BIT) != 0);
+
+ boolean negate = (result & 1) == 1;
+ result >>= 1;
+ return negate ? -result : result;
+ }
+
+ private static final class Base64 {
+ /**
+ * A map used to convert integer values in the range 0-63 to their base64
+ * values.
+ */
+ private static final String BASE64_MAP =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+ "abcdefghijklmnopqrstuvwxyz" +
+ "0123456789+/";
+
+ /**
+ * A map used to convert base64 character into integer values.
+ */
+ private static final int[] BASE64_DECODE_MAP = new int[256];
+
+ static {
+ for (int i = 0; i < BASE64_MAP.length(); i++) {
+ BASE64_DECODE_MAP[BASE64_MAP.charAt(i)] = i;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/sourcemap/MappingEntry.kt b/platform/script-debugger/backend/src/debugger/sourcemap/MappingEntry.kt
new file mode 100644
index 00000000..8f9aad86
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/sourcemap/MappingEntry.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.sourcemap
+
+/**
+ * Mapping entry in the source map
+ */
+interface MappingEntry {
+ val generatedColumn: Int
+
+ val generatedLine: Int
+
+ val sourceLine: Int
+
+ val sourceColumn: Int
+
+ val source: Int
+ get() = -1
+
+ val name: String?
+ get() = null
+
+ val nextGenerated: MappingEntry
+}
diff --git a/platform/script-debugger/backend/src/debugger/sourcemap/MappingList.kt b/platform/script-debugger/backend/src/debugger/sourcemap/MappingList.kt
new file mode 100644
index 00000000..c87786c0
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/sourcemap/MappingList.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.sourcemap
+
+import com.intellij.openapi.editor.Document
+import java.util.*
+
+interface Mappings {
+ fun get(line: Int, column: Int): MappingEntry?
+
+ fun getNextOnTheSameLine(index: Int, skipIfColumnEquals: Boolean = true): MappingEntry?
+
+ fun getNext(mapping: MappingEntry): MappingEntry?
+
+ fun getNextOnTheSameLine(mapping: MappingEntry): MappingEntry? {
+ val nextMapping = getNext(mapping)
+ return if (nextMapping != null && getLine(nextMapping) == getLine(mapping)) nextMapping else null
+ }
+
+ fun indexOf(line: Int, column: Int): Int
+
+ fun getByIndex(index: Int): MappingEntry
+
+ fun getLine(mapping: MappingEntry): Int
+
+ fun getColumn(mapping: MappingEntry): Int
+}
+
+abstract class MappingList(private val mappings: List<MappingEntry>) : Mappings {
+ val size: Int
+ get() = mappings.size
+
+ protected abstract val comparator: Comparator<MappingEntry>
+
+ override fun indexOf(line: Int, column: Int): Int {
+ var low = 0
+ var high = mappings.size - 1
+ if (mappings.isEmpty() || getLine(mappings[low]) > line || getLine(mappings[high]) < line) {
+ return -1
+ }
+
+ while (low <= high) {
+ val middle = (low + high).ushr(1)
+ val mapping = mappings[middle]
+ val mappingLine = getLine(mapping)
+ if (line == mappingLine) {
+ if (column == getColumn(mapping)) {
+ // find first
+ var firstIndex = middle
+ while (firstIndex > 0) {
+ val prevMapping = mappings[firstIndex - 1]
+ if (getLine(prevMapping) == line && getColumn(prevMapping) == column) {
+ firstIndex--
+ }
+ else {
+ break
+ }
+ }
+ return firstIndex
+ }
+ else if (column < getColumn(mapping)) {
+ if (column == 0 || column == -1) {
+ // find first
+ var firstIndex = middle
+ while (firstIndex > 0 && getLine(mappings[firstIndex - 1]) == line) {
+ firstIndex--
+ }
+ return firstIndex
+ }
+
+ if (middle == 0) {
+ return -1
+ }
+
+ val prevMapping = mappings[middle - 1]
+ when {
+ line != getLine(prevMapping) -> return -1
+ column >= getColumn(prevMapping) -> return middle - 1
+ else -> high = middle - 1
+ }
+ }
+ else {
+ // https://code.google.com/p/google-web-toolkit/issues/detail?id=9103
+ // We skipIfColumnEquals because GWT has two entries - source position equals, but generated no. We must use first entry (at least, in case of GWT it is correct)
+ val nextMapping = getNextOnTheSameLine(middle)
+ if (nextMapping == null) {
+ return middle
+ }
+ else {
+ low = middle + 1
+ }
+ }
+ }
+ else if (line > mappingLine) {
+ low = middle + 1
+ }
+ else {
+ high = middle - 1
+ }
+ }
+
+ return -1
+ }
+
+ // todo honor Google Chrome bug related to paused location
+ override fun get(line: Int, column: Int): MappingEntry? = mappings.getOrNull(indexOf(line, column))
+
+ private fun getNext(index: Int) = mappings.getOrNull(index + 1)
+
+ override fun getNext(mapping: MappingEntry): MappingEntry? {
+ if (comparator == MAPPING_COMPARATOR_BY_GENERATED_POSITION) {
+ return mapping.nextGenerated
+ }
+
+ var index = mappings.binarySearch(mapping, comparator)
+ if (index < 0) {
+ return null
+ }
+ index++
+
+ var result: MappingEntry?
+ do {
+ result = mappings.getOrNull(index++)
+ }
+ while (mapping === result)
+ return result
+ }
+
+ override fun getNextOnTheSameLine(index: Int, skipIfColumnEquals: Boolean): MappingEntry? {
+ var nextMapping = getNext(index) ?: return null
+
+ val mapping = getByIndex(index)
+ if (getLine(nextMapping) != getLine(mapping)) {
+ return null
+ }
+
+ if (skipIfColumnEquals) {
+ var i = index
+ // several generated segments can point to one source segment, so, in mapping list ordered by source, could be several mappings equal in terms of source position
+ while (getColumn(nextMapping) == getColumn(mapping)) {
+ nextMapping = getNextOnTheSameLine(++i, false) ?: return null
+ }
+ }
+
+ return nextMapping
+ }
+
+ fun getEndOffset(mapping: MappingEntry, lineStartOffset: Int, document: Document): Int {
+ val nextMapping = getNextOnTheSameLine(Collections.binarySearch(mappings, mapping, comparator))
+ return if (nextMapping == null) document.getLineEndOffset(getLine(mapping)) else lineStartOffset + getColumn(nextMapping)
+ }
+
+ override fun getByIndex(index: Int): MappingEntry = mappings.get(index)
+
+ // entries will be processed in this list order
+ fun processMappingsInLine(line: Int, entryProcessor: MappingsProcessorInLine): Boolean {
+ var low = 0
+ var high = mappings.size - 1
+ while (low <= high) {
+ val middle = (low + high).ushr(1)
+ val mapping = mappings.get(middle)
+ val mappingLine = getLine(mapping)
+ when {
+ line == mappingLine -> {
+ // find first
+ var firstIndex = middle
+ while (firstIndex > 0 && getLine(mappings.get(firstIndex - 1)) == line) {
+ firstIndex--
+ }
+
+ var entry: MappingEntry? = mappings.get(firstIndex)
+ do {
+ var nextEntry = mappings.getOrNull(++firstIndex)
+ if (nextEntry != null && getLine(nextEntry) != line) {
+ nextEntry = null
+ }
+
+ if (!entryProcessor.process(entry!!, nextEntry)) {
+ return true
+ }
+
+ entry = nextEntry
+ }
+ while (entry != null)
+ return true
+ }
+ line > mappingLine -> low = middle + 1
+ else -> high = middle - 1
+ }
+ }
+ return false
+ }
+}
+
+interface MappingsProcessorInLine {
+ fun process(entry: MappingEntry, nextEntry: MappingEntry?): Boolean
+}
diff --git a/platform/script-debugger/backend/src/debugger/sourcemap/NestedSourceMap.kt b/platform/script-debugger/backend/src/debugger/sourcemap/NestedSourceMap.kt
new file mode 100644
index 00000000..c4a5ebfc
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/sourcemap/NestedSourceMap.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.sourcemap
+
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.util.Url
+import gnu.trove.THashMap
+
+class NestedSourceMap(private val childMap: SourceMap, private val parentMap: SourceMap) : SourceMap {
+ override val sourceResolver: SourceResolver
+ get() = parentMap.sourceResolver
+
+ override val sources: Array<Url>
+ get() = parentMap.sources
+
+ private val sourceIndexToSourceMappings = arrayOfNulls<Mappings>(parentMap.sources.size)
+
+ private val childMappingToTransformed = THashMap<MappingEntry, MappingEntry>()
+
+ override val outFile: String?
+ get() = childMap.outFile
+
+ override val hasNameMappings: Boolean
+ get() = childMap.hasNameMappings || parentMap.hasNameMappings
+
+ override val generatedMappings: Mappings by lazy {
+ NestedMappings(childMap.generatedMappings, parentMap.generatedMappings, false)
+ }
+
+ override fun findSourceMappings(sourceIndex: Int): Mappings {
+ var result = sourceIndexToSourceMappings.get(sourceIndex)
+ if (result == null) {
+ result = NestedMappings(childMap.findSourceMappings(sourceIndex), parentMap.findSourceMappings(sourceIndex), true)
+ sourceIndexToSourceMappings.set(sourceIndex, result)
+ }
+ return result
+ }
+
+ override fun findSourceIndex(sourceFile: VirtualFile, localFileUrlOnly: Boolean): Int = parentMap.findSourceIndex(sourceFile, localFileUrlOnly)
+
+ override fun findSourceIndex(sourceUrls: List<Url>,
+ sourceFile: VirtualFile?,
+ resolver: Lazy<SourceFileResolver?>?,
+ localFileUrlOnly: Boolean): Int = parentMap.findSourceIndex(sourceUrls, sourceFile, resolver, localFileUrlOnly)
+
+ override fun processSourceMappingsInLine(sourceIndex: Int, sourceLine: Int, mappingProcessor: MappingsProcessorInLine): Boolean {
+ val childSourceMappings = childMap.findSourceMappings(sourceIndex)
+ return (parentMap.findSourceMappings(sourceIndex) as MappingList).processMappingsInLine(sourceLine, object: MappingsProcessorInLine {
+ override fun process(entry: MappingEntry, nextEntry: MappingEntry?): Boolean {
+ val childIndex = childSourceMappings.indexOf(entry.generatedLine, entry.generatedColumn)
+ if (childIndex == -1) {
+ return true
+ }
+
+ val childEntry = childSourceMappings.getByIndex(childIndex)
+ // todo not clear - should we resolve next child entry by current child index or by provided parent nextEntry?
+ val nextChildEntry = if (nextEntry == null) null else childSourceMappings.getNextOnTheSameLine(childIndex)
+ return mappingProcessor.process(childMappingToTransformed.getOrPut(childEntry) { NestedMappingEntry(childEntry, entry) },
+ nextChildEntry?.let { childMappingToTransformed.getOrPut(it) { NestedMappingEntry(it, entry) } })
+ }
+ })
+ }
+}
+
+private class NestedMappings(private val child: Mappings, private val parent: Mappings, private val isSourceMappings: Boolean) : Mappings {
+ override fun getNextOnTheSameLine(index: Int, skipIfColumnEquals: Boolean) = parent.getNextOnTheSameLine(index, skipIfColumnEquals)
+
+ override fun getNext(mapping: MappingEntry) = parent.getNext(mapping)
+
+ override fun indexOf(line: Int, column: Int) = parent.indexOf(line, column)
+
+ override fun getByIndex(index: Int) = parent.getByIndex(index)
+
+ override fun getLine(mapping: MappingEntry) = parent.getLine(mapping)
+
+ override fun getColumn(mapping: MappingEntry) = parent.getColumn(mapping)
+
+ override fun get(line: Int, column: Int): MappingEntry? {
+ return if (isSourceMappings) {
+ parent.get(line, column)?.let { child.get(it.generatedLine, it.generatedColumn) }
+ }
+ else {
+ child.get(line, column)?.let { parent.get(it.sourceLine, it.sourceColumn) }
+ }
+ }
+}
+
+private data class NestedMappingEntry(private val child: MappingEntry, private val parent: MappingEntry) : MappingEntry {
+ override val generatedLine: Int
+ get() = child.generatedLine
+
+ override val generatedColumn: Int
+ get() = child.generatedColumn
+
+ override val sourceLine: Int
+ get() = parent.sourceLine
+
+ override val sourceColumn: Int
+ get() = parent.sourceColumn
+
+ override val name: String?
+ get() = parent.name
+
+ override val nextGenerated: MappingEntry
+ get() = child.nextGenerated
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/sourcemap/SourceMap.kt b/platform/script-debugger/backend/src/debugger/sourcemap/SourceMap.kt
new file mode 100644
index 00000000..283a5b6c
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/sourcemap/SourceMap.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.sourcemap
+
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.util.Url
+
+// sources - is not originally specified, but canonicalized/normalized
+// lines and columns are zero-based according to specification
+interface SourceMap {
+ val outFile: String?
+
+ /**
+ * note: Nested map returns only parent sources
+ */
+ val sources: Array<Url>
+
+ val generatedMappings: Mappings
+ val hasNameMappings: Boolean
+ val sourceResolver: SourceResolver
+
+ fun findSourceMappings(sourceIndex: Int): Mappings
+
+ fun findSourceIndex(sourceUrls: List<Url>, sourceFile: VirtualFile?, resolver: Lazy<SourceFileResolver?>?, localFileUrlOnly: Boolean): Int
+
+ fun findSourceMappings(sourceUrls: List<Url>, sourceFile: VirtualFile?, resolver: Lazy<SourceFileResolver?>?, localFileUrlOnly: Boolean): Mappings? {
+ val sourceIndex = findSourceIndex(sourceUrls, sourceFile, resolver, localFileUrlOnly)
+ return if (sourceIndex >= 0) findSourceMappings(sourceIndex) else null
+ }
+
+ fun getSourceLineByRawLocation(rawLine: Int, rawColumn: Int): Int = generatedMappings.get(rawLine, rawColumn)?.sourceLine ?: -1
+
+ fun findSourceIndex(sourceFile: VirtualFile, localFileUrlOnly: Boolean): Int
+
+ fun processSourceMappingsInLine(sourceIndex: Int, sourceLine: Int, mappingProcessor: MappingsProcessorInLine): Boolean
+
+ fun processSourceMappingsInLine(sourceUrls: List<Url>, sourceLine: Int, mappingProcessor: MappingsProcessorInLine, sourceFile: VirtualFile?, resolver: Lazy<SourceFileResolver?>?, localFileUrlOnly: Boolean): Boolean {
+ val sourceIndex = findSourceIndex(sourceUrls, sourceFile, resolver, localFileUrlOnly)
+ return sourceIndex >= 0 && processSourceMappingsInLine(sourceIndex, sourceLine, mappingProcessor)
+ }
+}
+
+
+class OneLevelSourceMap(override val outFile: String?,
+ override val generatedMappings: Mappings,
+ private val sourceIndexToMappings: Array<MappingList?>,
+ override val sourceResolver: SourceResolver,
+ override val hasNameMappings: Boolean) : SourceMap {
+ override val sources: Array<Url>
+ get() = sourceResolver.canonicalizedUrls
+
+ override fun findSourceIndex(sourceUrls: List<Url>, sourceFile: VirtualFile?, resolver: Lazy<SourceFileResolver?>?, localFileUrlOnly: Boolean): Int {
+ val index = sourceResolver.findSourceIndex(sourceUrls, sourceFile, localFileUrlOnly)
+ if (index == -1 && resolver != null) {
+ return resolver.value?.let { sourceResolver.findSourceIndex(it) } ?: -1
+ }
+ return index
+ }
+
+ // returns SourceMappingList
+ override fun findSourceMappings(sourceIndex: Int): MappingList = sourceIndexToMappings.get(sourceIndex)!!
+
+ override fun findSourceIndex(sourceFile: VirtualFile, localFileUrlOnly: Boolean): Int = sourceResolver.findSourceIndexByFile(sourceFile, localFileUrlOnly)
+
+ override fun processSourceMappingsInLine(sourceIndex: Int, sourceLine: Int, mappingProcessor: MappingsProcessorInLine): Boolean {
+ return findSourceMappings(sourceIndex).processMappingsInLine(sourceLine, mappingProcessor)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/sourcemap/SourceMapDecoder.kt b/platform/script-debugger/backend/src/debugger/sourcemap/SourceMapDecoder.kt
new file mode 100644
index 00000000..3ecdd792
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/sourcemap/SourceMapDecoder.kt
@@ -0,0 +1,372 @@
+/*
+ * 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.sourcemap
+
+import com.google.gson.stream.JsonToken
+import com.intellij.openapi.diagnostic.logger
+import com.intellij.openapi.util.registry.Registry
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.openapi.util.text.StringUtilRt
+import com.intellij.util.PathUtil
+import com.intellij.util.SmartList
+import com.intellij.util.UriUtil
+import com.intellij.util.containers.isNullOrEmpty
+import org.jetbrains.debugger.sourcemap.Base64VLQ.CharIterator
+import org.jetbrains.io.JsonReaderEx
+import java.io.IOException
+import java.util.*
+import kotlin.properties.Delegates.notNull
+
+private val MAPPING_COMPARATOR_BY_SOURCE_POSITION = Comparator<MappingEntry> { o1, o2 ->
+ if (o1.sourceLine == o2.sourceLine) {
+ o1.sourceColumn - o2.sourceColumn
+ }
+ else {
+ o1.sourceLine - o2.sourceLine
+ }
+}
+
+val MAPPING_COMPARATOR_BY_GENERATED_POSITION: Comparator<MappingEntry> = Comparator { o1, o2 ->
+ if (o1.generatedLine == o2.generatedLine) {
+ o1.generatedColumn - o2.generatedColumn
+ }
+ else {
+ o1.generatedLine - o2.generatedLine
+ }
+}
+
+internal const val UNMAPPED = -1
+
+// https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?hl=en_US
+fun decodeSourceMap(`in`: CharSequence, sourceResolverFactory: (sourceUrls: List<String>, sourceContents: List<String?>?) -> SourceResolver): SourceMap? {
+ if (`in`.isEmpty()) {
+ throw IOException("source map contents cannot be empty")
+ }
+
+ val reader = JsonReaderEx(`in`)
+ reader.isLenient = true
+ return parseMap(reader, 0, 0, ArrayList(), sourceResolverFactory)
+}
+
+private fun parseMap(reader: JsonReaderEx,
+ line: Int,
+ column: Int,
+ mappings: MutableList<MappingEntry>,
+ sourceResolverFactory: (sourceUrls: List<String>, sourceContents: List<String?>?) -> SourceResolver): SourceMap? {
+ reader.beginObject()
+ var sourceRoot: String? = null
+ var sourcesReader: JsonReaderEx? = null
+ var names: List<String>? = null
+ var encodedMappings: String? = null
+ var file: String? = null
+ var version = -1
+ var sourcesContent: MutableList<String?>? = null
+ while (reader.hasNext()) {
+ when (reader.nextName()) {
+ "sections" -> throw IOException("sections is not supported yet")
+ "version" -> {
+ version = reader.nextInt()
+ }
+ "sourceRoot" -> {
+ sourceRoot = StringUtil.nullize(readSourcePath(reader))
+ if (sourceRoot != null && sourceRoot != "/") {
+ sourceRoot = UriUtil.trimTrailingSlashes(sourceRoot)
+ }
+ }
+ "sources" -> {
+ sourcesReader = reader.subReader()
+ reader.skipValue()
+ }
+ "names" -> {
+ reader.beginArray()
+ if (reader.hasNext()) {
+ names = ArrayList()
+ do {
+ if (reader.peek() == JsonToken.BEGIN_OBJECT) {
+ // polymer map
+ reader.skipValue()
+ names.add("POLYMER UNKNOWN NAME")
+ }
+ else {
+ names.add(reader.nextString(true))
+ }
+ }
+ while (reader.hasNext())
+ }
+ else {
+ names = emptyList()
+ }
+ reader.endArray()
+ }
+ "mappings" -> {
+ encodedMappings = reader.nextString()
+ }
+ "file" -> {
+ file = reader.nextNullableString()
+ }
+ "sourcesContent" -> {
+ reader.beginArray()
+ if (reader.peek() != JsonToken.END_ARRAY) {
+ sourcesContent = SmartList<String>()
+ do {
+ if (reader.peek() == JsonToken.STRING) {
+ sourcesContent.add(StringUtilRt.convertLineSeparators(reader.nextString()))
+ }
+ else if (reader.peek() == JsonToken.NULL) {
+ // null means source file should be resolved by url
+ sourcesContent.add(null)
+ reader.nextNull()
+ }
+ else {
+ logger<SourceMap>().warn("Unknown sourcesContent element: ${reader.peek().name}")
+ reader.skipValue()
+ }
+ }
+ while (reader.hasNext())
+ }
+ reader.endArray()
+ }
+ else -> {
+ // skip file or extensions
+ reader.skipValue()
+ }
+ }
+ }
+ reader.close()
+
+ // check it before other checks, probably it is not a sourcemap file
+ if (encodedMappings.isNullOrEmpty()) {
+ // empty map
+ return null
+ }
+
+ if (Registry.`is`("js.debugger.fix.jspm.source.maps", false) && encodedMappings!!.startsWith(";") && file != null && file.endsWith(".ts!transpiled")) {
+ encodedMappings = encodedMappings.substring(1)
+ }
+
+ if (version != 3) {
+ throw IOException("Unsupported sourcemap version: $version")
+ }
+
+ if (sourcesReader == null) {
+ throw IOException("sources is not specified")
+ }
+
+ val sources = readSources(sourcesReader, sourceRoot)
+ if (sources.isEmpty()) {
+ // empty map, meteor can report such ugly maps
+ return null
+ }
+
+ val reverseMappingsBySourceUrl = arrayOfNulls<MutableList<MappingEntry>?>(sources.size)
+ readMappings(encodedMappings!!, line, column, mappings, reverseMappingsBySourceUrl, names)
+
+ val sourceToEntries = Array<MappingList?>(reverseMappingsBySourceUrl.size) {
+ val entries = reverseMappingsBySourceUrl[it]
+ if (entries == null) {
+ null
+ }
+ else {
+ entries.sortWith(MAPPING_COMPARATOR_BY_SOURCE_POSITION)
+ SourceMappingList(entries)
+ }
+ }
+ return OneLevelSourceMap(file, GeneratedMappingList(mappings), sourceToEntries, sourceResolverFactory(sources, sourcesContent), !names.isNullOrEmpty())
+}
+
+private fun readSourcePath(reader: JsonReaderEx): String = PathUtil.toSystemIndependentName(reader.nextString().trim { it <= ' ' })
+
+private fun readMappings(value: String,
+ initialLine: Int,
+ initialColumn: Int,
+ mappings: MutableList<MappingEntry>,
+ reverseMappingsBySourceUrl: Array<MutableList<MappingEntry>?>,
+ names: List<String>?) {
+ if (value.isEmpty()) {
+ return
+ }
+
+ var line = initialLine
+ var column = initialColumn
+ val charIterator = CharSequenceIterator(value)
+ var sourceIndex = 0
+ var reverseMappings: MutableList<MappingEntry> = getMapping(reverseMappingsBySourceUrl, sourceIndex)
+ var sourceLine = 0
+ var sourceColumn = 0
+ var nameIndex = 0
+ var prevEntry: MutableEntry? = null
+
+ fun addEntry(entry: MutableEntry) {
+ if (prevEntry != null) {
+ prevEntry!!.nextGenerated = entry
+ }
+ prevEntry = entry
+ mappings.add(entry)
+ }
+
+ while (charIterator.hasNext()) {
+ if (charIterator.peek() == ',') {
+ charIterator.next()
+ }
+ else {
+ while (charIterator.peek() == ';') {
+ line++
+ column = 0
+ charIterator.next()
+ if (!charIterator.hasNext()) {
+ return
+ }
+ }
+ }
+
+ column += Base64VLQ.decode(charIterator)
+ if (isSeparator(charIterator)) {
+ addEntry(UnmappedEntry(line, column))
+ continue
+ }
+
+ val sourceIndexDelta = Base64VLQ.decode(charIterator)
+ if (sourceIndexDelta != 0) {
+ sourceIndex += sourceIndexDelta
+ reverseMappings = getMapping(reverseMappingsBySourceUrl, sourceIndex)
+ }
+ sourceLine += Base64VLQ.decode(charIterator)
+ sourceColumn += Base64VLQ.decode(charIterator)
+
+ val entry: MutableEntry
+ if (isSeparator(charIterator)) {
+ entry = UnnamedEntry(line, column, sourceIndex, sourceLine, sourceColumn)
+ }
+ else {
+ nameIndex += Base64VLQ.decode(charIterator)
+ assert(names != null)
+ entry = NamedEntry(names!![nameIndex], line, column, sourceIndex, sourceLine, sourceColumn)
+ }
+ reverseMappings.add(entry)
+ addEntry(entry)
+ }
+}
+
+private fun readSources(reader: JsonReaderEx, sourceRoot: String?): List<String> {
+ reader.beginArray()
+ val sources: List<String>
+ if (reader.peek() == JsonToken.END_ARRAY) {
+ sources = emptyList()
+ }
+ else {
+ sources = SmartList<String>()
+ do {
+ var sourceUrl: String = readSourcePath(reader)
+ if (!sourceRoot.isNullOrEmpty()) {
+ if (sourceRoot == "/") {
+ sourceUrl = "/$sourceUrl"
+ }
+ else {
+ sourceUrl = "$sourceRoot/$sourceUrl"
+ }
+ }
+ sources.add(sourceUrl)
+ }
+ while (reader.hasNext())
+ }
+ reader.endArray()
+ return sources
+}
+
+private fun getMapping(reverseMappingsBySourceUrl: Array<MutableList<MappingEntry>?>, sourceIndex: Int): MutableList<MappingEntry> {
+ var reverseMappings = reverseMappingsBySourceUrl.get(sourceIndex)
+ if (reverseMappings == null) {
+ reverseMappings = ArrayList()
+ reverseMappingsBySourceUrl.set(sourceIndex, reverseMappings)
+ }
+ return reverseMappings
+}
+
+private fun isSeparator(charIterator: CharSequenceIterator): Boolean {
+ if (!charIterator.hasNext()) {
+ return true
+ }
+
+ val current = charIterator.peek()
+ return current == ',' || current == ';'
+}
+
+interface MutableEntry : MappingEntry {
+ override var nextGenerated: MappingEntry
+}
+
+/**
+ * Not mapped to a section in the original source.
+ */
+private data class UnmappedEntry(override val generatedLine: Int, override val generatedColumn: Int) : MappingEntry, MutableEntry {
+ override val sourceLine = UNMAPPED
+
+ override val sourceColumn = UNMAPPED
+
+ override var nextGenerated: MappingEntry by notNull()
+}
+
+/**
+ * Mapped to a section in the original source.
+ */
+private data class UnnamedEntry(override val generatedLine: Int,
+ override val generatedColumn: Int,
+ override val source: Int,
+ override val sourceLine: Int,
+ override val sourceColumn: Int) : MappingEntry, MutableEntry {
+ override var nextGenerated: MappingEntry by notNull()
+}
+
+/**
+ * Mapped to a section in the original source, and is associated with a name.
+ */
+private data class NamedEntry(override val name: String,
+ override val generatedLine: Int,
+ override val generatedColumn: Int,
+ override val source: Int,
+ override val sourceLine: Int,
+ override val sourceColumn: Int) : MappingEntry, MutableEntry {
+ override var nextGenerated: MappingEntry by notNull()
+}
+
+// java CharacterIterator is ugly, next() impl, so, we reinvent
+private class CharSequenceIterator(private val content: CharSequence) : CharIterator {
+ private val length = content.length
+ private var current = 0
+
+ override fun next() = content.get(current++)
+
+ internal fun peek() = content.get(current)
+
+ override fun hasNext() = current < length
+}
+
+private class SourceMappingList(mappings: List<MappingEntry>) : MappingList(mappings) {
+ override fun getLine(mapping: MappingEntry) = mapping.sourceLine
+
+ override fun getColumn(mapping: MappingEntry) = mapping.sourceColumn
+
+ override val comparator = MAPPING_COMPARATOR_BY_SOURCE_POSITION
+}
+
+private class GeneratedMappingList(mappings: List<MappingEntry>) : MappingList(mappings) {
+ override fun getLine(mapping: MappingEntry) = mapping.generatedLine
+
+ override fun getColumn(mapping: MappingEntry) = mapping.generatedColumn
+
+ override val comparator = MAPPING_COMPARATOR_BY_GENERATED_POSITION
+}
+
diff --git a/platform/script-debugger/backend/src/debugger/sourcemap/SourceResolver.kt b/platform/script-debugger/backend/src/debugger/sourcemap/SourceResolver.kt
new file mode 100644
index 00000000..2a9c868f
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/sourcemap/SourceResolver.kt
@@ -0,0 +1,186 @@
+// 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.sourcemap
+
+import com.intellij.openapi.util.SystemInfo
+import com.intellij.openapi.util.io.FileUtil
+import com.intellij.openapi.vfs.StandardFileSystems
+import com.intellij.openapi.vfs.VfsUtilCore
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.util.Url
+import com.intellij.util.Urls
+import com.intellij.util.containers.ObjectIntHashMap
+import com.intellij.util.containers.isNullOrEmpty
+import com.intellij.util.io.URLUtil
+import java.io.File
+
+interface SourceFileResolver {
+ /**
+ * Return -1 if no match
+ */
+ fun resolve(map: ObjectIntHashMap<Url>): Int = -1
+ fun resolve(rawSources: List<String>): Int = -1
+}
+
+class SourceResolver(private val rawSources: List<String>,
+ trimFileScheme: Boolean,
+ baseUrl: Url?,
+ private val sourceContents: List<String?>?,
+ baseUrlIsFile: Boolean = true) {
+ companion object {
+ fun isAbsolute(path: String): Boolean = path.startsWith('/') || (SystemInfo.isWindows && (path.length > 2 && path[1] == ':'))
+ }
+
+ val canonicalizedUrls: Array<Url> by lazy {
+ Array(rawSources.size) { canonicalizeUrl(rawSources[it], baseUrl, trimFileScheme, baseUrlIsFile) }
+ }
+
+ private val canonicalizedUrlToSourceIndex: ObjectIntHashMap<Url> by lazy {
+ (
+ if (SystemInfo.isFileSystemCaseSensitive) ObjectIntHashMap(rawSources.size)
+ else ObjectIntHashMap(rawSources.size, Urls.caseInsensitiveUrlHashingStrategy)
+ ).also {
+ for (i in rawSources.indices) {
+ it.put(canonicalizedUrls[i], i)
+ }
+ }
+ }
+
+ fun getSource(entry: MappingEntry): Url? {
+ val index = entry.source
+ return if (index < 0) null else canonicalizedUrls[index]
+ }
+
+ fun getSourceContent(entry: MappingEntry): String? {
+ if (sourceContents.isNullOrEmpty()) {
+ return null
+ }
+
+ val index = entry.source
+ return if (index < 0 || index >= sourceContents!!.size) null else sourceContents[index]
+ }
+
+ fun getSourceContent(sourceIndex: Int): String? {
+ if (sourceContents.isNullOrEmpty()) {
+ return null
+ }
+ return if (sourceIndex < 0 || sourceIndex >= sourceContents!!.size) null else sourceContents[sourceIndex]
+ }
+
+ fun getSourceIndex(url: Url): Int = canonicalizedUrlToSourceIndex[url]
+
+ fun getRawSource(entry: MappingEntry): String? {
+ val index = entry.source
+ return if (index < 0) null else rawSources[index]
+ }
+
+ internal fun findSourceIndex(resolver: SourceFileResolver): Int {
+ val resolveByCanonicalizedUrls = resolver.resolve(canonicalizedUrlToSourceIndex)
+ return if (resolveByCanonicalizedUrls != -1) resolveByCanonicalizedUrls else resolver.resolve(rawSources)
+ }
+
+ fun findSourceIndex(sourceUrls: List<Url>, sourceFile: VirtualFile?, localFileUrlOnly: Boolean): Int {
+ for (sourceUrl in sourceUrls) {
+ val index = canonicalizedUrlToSourceIndex.get(sourceUrl)
+ if (index != -1) {
+ return index
+ }
+ }
+
+ if (sourceFile != null) {
+ return findSourceIndexByFile(sourceFile, localFileUrlOnly)
+ }
+ return -1
+ }
+
+ internal fun findSourceIndexByFile(sourceFile: VirtualFile, localFileUrlOnly: Boolean): Int {
+ if (!localFileUrlOnly) {
+ val index = canonicalizedUrlToSourceIndex.get(Urls.newFromVirtualFile(sourceFile).trimParameters())
+ if (index != -1) {
+ return index
+ }
+ }
+
+ if (!sourceFile.isInLocalFileSystem) {
+ return -1
+ }
+
+ // local file url - without "file" scheme, just path
+ val index = canonicalizedUrlToSourceIndex.get(Urls.newLocalFileUrl(sourceFile))
+ if (index != -1) {
+ return index
+ }
+
+ // ok, search by canonical path
+ val canonicalFile = sourceFile.canonicalFile
+ if (canonicalFile != null && canonicalFile != sourceFile) {
+ for (i in canonicalizedUrls.indices) {
+ val url = canonicalizedUrls.get(i)
+ if (Urls.equalsIgnoreParameters(url, canonicalFile)) {
+ return i
+ }
+ }
+ }
+ return -1
+ }
+
+ fun getUrlIfLocalFile(entry: MappingEntry): Url? = canonicalizedUrls.getOrNull(entry.source)?.let { if (it.isInLocalFileSystem) it else null }
+}
+
+fun canonicalizePath(url: String, baseUrl: Url, baseUrlIsFile: Boolean): String {
+ var path = url
+ if (!FileUtil.isAbsolute(url) && !url.isEmpty() && url[0] != '/') {
+ val basePath = baseUrl.path
+ if (baseUrlIsFile) {
+ val lastSlashIndex = basePath.lastIndexOf('/')
+ val pathBuilder = StringBuilder()
+ if (lastSlashIndex == -1) {
+ pathBuilder.append('/')
+ }
+ else {
+ pathBuilder.append(basePath, 0, lastSlashIndex + 1)
+ }
+ path = pathBuilder.append(url).toString()
+ }
+ else {
+ path = "$basePath/$url"
+ }
+ }
+ return FileUtil.toCanonicalPath(path, '/')
+}
+
+// see canonicalizeUri kotlin impl and https://trac.webkit.org/browser/trunk/Source/WebCore/inspector/front-end/ParsedURL.js completeURL
+fun canonicalizeUrl(url: String, baseUrl: Url?, trimFileScheme: Boolean, baseUrlIsFile: Boolean = true): Url {
+ if (trimFileScheme && url.startsWith(StandardFileSystems.FILE_PROTOCOL_PREFIX)) {
+ return Urls.newLocalFileUrl(FileUtil.toCanonicalPath(VfsUtilCore.toIdeaUrl(url, true).substring(StandardFileSystems.FILE_PROTOCOL_PREFIX.length), '/'))
+ }
+ else if (baseUrl == null || url.contains(URLUtil.SCHEME_SEPARATOR) || url.startsWith("data:") || url.startsWith("blob:") ||
+ url.startsWith("javascript:") || url.startsWith("webpack:")) {
+ // consider checking :/ instead of :// because scheme may be followed by path, not by authority
+ // https://tools.ietf.org/html/rfc3986#section-1.1.2
+ // be careful with windows paths: C:/Users
+ return Urls.parseEncoded(url) ?: Urls.newUri(null, url)
+ }
+ else {
+ return doCanonicalize(url, baseUrl, baseUrlIsFile, true)
+ }
+}
+
+fun doCanonicalize(url: String, baseUrl: Url, baseUrlIsFile: Boolean, asLocalFileIfAbsoluteAndExists: Boolean): Url {
+ val path = canonicalizePath(url, baseUrl, baseUrlIsFile)
+ if ((baseUrl.scheme == null && baseUrl.isInLocalFileSystem) ||
+ asLocalFileIfAbsoluteAndExists && SourceResolver.isAbsolute(path) && File(path).exists()) {
+ // file:///home/user/foo.js.map, foo.ts -> /home/user/foo.ts (baseUrl is in local fs)
+ // http://localhost/home/user/foo.js.map, foo.ts -> /home/user/foo.ts (File(path) exists)
+ return Urls.newLocalFileUrl(path)
+ }
+ else if (!path.startsWith("/")) {
+ // http://localhost/source.js.map, C:/foo.ts webpack-dsj3c45 -> C:/foo.ts webpack-dsj3c45
+ // (we can't append path suffixes unless they start with /
+ return Urls.parse(path, true) ?: Urls.newUnparsable(path)
+ }
+ else {
+ // new url from path and baseUrl's scheme and authority
+ val split = path.split('?', limit = 2)
+ return Urls.newUrl(baseUrl.scheme!!, baseUrl.authority!!, split[0], if (split.size > 1) '?' + split[1] else null)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/util.kt b/platform/script-debugger/backend/src/debugger/util.kt
new file mode 100644
index 00000000..7930261d
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/util.kt
@@ -0,0 +1,112 @@
+// 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.application.ApplicationManager
+import com.intellij.openapi.util.io.FileUtil
+import com.intellij.openapi.util.io.FileUtilRt
+import com.intellij.openapi.util.registry.Registry
+import com.intellij.util.concurrency.AppExecutorUtil
+import com.intellij.util.io.CharSequenceBackedByChars
+import com.intellij.util.io.addChannelListener
+import io.netty.buffer.ByteBuf
+import io.netty.channel.Channel
+import org.jetbrains.annotations.PropertyKey
+import java.io.File
+import java.io.FileOutputStream
+import java.nio.CharBuffer
+import java.text.SimpleDateFormat
+import java.util.concurrent.Future
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+
+internal class LogEntry(val message: CharSequence, val marker: String) {
+ internal val time = System.currentTimeMillis()
+}
+
+class MessagingLogger internal constructor(debugFile: String) {
+ private val processFuture: Future<*>
+ private val queue = LinkedBlockingQueue<LogEntry>()
+
+ init {
+ processFuture = ApplicationManager.getApplication().executeOnPooledThread {
+ val file = File(FileUtil.expandUserHome(debugFile))
+ FileUtilRt.createParentDirs(file)
+ val out = FileOutputStream(file)
+ val writer = out.writer()
+ writer.write("[\n")
+ writer.flush()
+ val fileChannel = out.channel
+
+ val dateFormatter = SimpleDateFormat("HH.mm.ss,SSS")
+
+ try {
+ while (true) {
+ val entry = queue.take()
+
+ writer.write("""{"timestamp": "${dateFormatter.format(entry.time)}", """)
+ val message = entry.message
+ writer.write("\"${entry.marker}\": ")
+ writer.flush()
+
+ if (message is CharSequenceBackedByChars) {
+ fileChannel.write(message.byteBuffer)
+ }
+ else {
+ fileChannel.write(Charsets.UTF_8.encode(CharBuffer.wrap(message)))
+ }
+
+ writer.write("},\n")
+ writer.flush()
+ }
+ }
+ catch (e: InterruptedException) {
+ }
+ finally {
+ writer.write("]")
+ writer.flush()
+ out.close()
+ }
+ }
+ }
+
+ fun add(message: CharSequence, marker: String = "IN") {
+ // ignore Network events
+ if (!message.startsWith("{\"method\":\"Network.")) {
+ queue.add(LogEntry(message, marker))
+ }
+ }
+
+ fun add(outMessage: ByteBuf, marker: String = "OUT") {
+ val charSequence = outMessage.getCharSequence(outMessage.readerIndex(), outMessage.readableBytes(), Charsets.UTF_8)
+ add(charSequence, marker)
+ }
+
+ fun close() {
+ AppExecutorUtil.getAppScheduledExecutorService().schedule(fun() {
+ processFuture.cancel(true)
+ }, 1, TimeUnit.SECONDS)
+ }
+
+ fun closeOnChannelClose(channel: Channel) {
+ channel.closeFuture().addChannelListener {
+ try {
+ add("\"Closed\"", "Channel")
+ }
+ finally {
+ close()
+ }
+ }
+ }
+}
+
+fun createDebugLogger(@PropertyKey(resourceBundle = Registry.REGISTRY_BUNDLE) key: String, suffix: String = ""): MessagingLogger? {
+ var debugFile = Registry.stringValue(key)
+ if (debugFile.isEmpty()) {
+ return null
+ }
+
+ if (!suffix.isEmpty()) {
+ debugFile = debugFile.replace(".json", "$suffix.json")
+ }
+ return MessagingLogger(debugFile)
+}
diff --git a/platform/script-debugger/backend/src/debugger/values/ArrayValue.kt b/platform/script-debugger/backend/src/debugger/values/ArrayValue.kt
new file mode 100644
index 00000000..ceed459a
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/ArrayValue.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.values
+
+interface ArrayValue : ObjectValue {
+ /**
+ * Be aware - it is not equals to java array length.
+ * In case of sparse array `var sparseArray = [3, 4];
+ * sparseArray[45] = 34;
+ * sparseArray[40999995] = &quot;foo&quot;;
+ ` *
+ * length will be equal to 40999995.
+ */
+ val length: Int
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/values/FunctionValue.kt b/platform/script-debugger/backend/src/debugger/values/FunctionValue.kt
new file mode 100644
index 00000000..2825bc1e
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/FunctionValue.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.values
+
+import com.intellij.util.ThreeState
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.Scope
+
+interface FunctionValue : ObjectValue {
+ /**
+ * You must invoke [.resolve] to use any function value methods
+ */
+ fun resolve(): Promise<FunctionValue>
+
+ /**
+ * Returns position of opening parenthesis of function arguments. Position is absolute
+ * within resource (not relative to script start position).
+
+ * @return position or null if position is not available
+ */
+ val openParenLine: Int
+
+ val openParenColumn: Int
+
+ val scopes: Array<Scope>?
+
+ /**
+ * Method could be called (it is normal and expected) for unresolved function.
+ * It must return quickly. Return [com.intellij.util.ThreeState.UNSURE] otherwise.
+ */
+ fun hasScopes(): ThreeState = ThreeState.UNSURE
+}
diff --git a/platform/script-debugger/backend/src/debugger/values/IndexedVariablesConsumer.kt b/platform/script-debugger/backend/src/debugger/values/IndexedVariablesConsumer.kt
new file mode 100644
index 00000000..aad2f00d
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/IndexedVariablesConsumer.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.values
+
+import org.jetbrains.debugger.Variable
+
+abstract class IndexedVariablesConsumer {
+ // null if array is not sparse
+ abstract fun consumeRanges(ranges: IntArray?)
+
+ abstract fun consumeVariables(variables: List<Variable>)
+
+ open val isObsolete: Boolean
+ get() = false
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/values/ObjectValue.kt b/platform/script-debugger/backend/src/debugger/values/ObjectValue.kt
new file mode 100755
index 00000000..55917c42
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/ObjectValue.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.values
+
+import com.intellij.util.ThreeState
+import org.jetbrains.concurrency.Obsolescent
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.EvaluateContext
+import org.jetbrains.debugger.Variable
+import org.jetbrains.debugger.VariablesHost
+
+/**
+ * A compound value that has zero or more properties
+ */
+interface ObjectValue : Value {
+ val className: String?
+
+ val properties: Promise<List<Variable>>
+
+ fun getProperties(names: List<String>, evaluateContext: EvaluateContext, obsolescent: Obsolescent): Promise<List<Variable>>
+
+ val variablesHost: VariablesHost<ValueManager>
+
+ /**
+ * from (inclusive) to (exclusive) ranges of array elements or elements if less than bucketThreshold
+
+ * "to" could be -1 (sometimes length is unknown, so, you can pass -1 instead of actual elements size)
+ */
+ fun getIndexedProperties(from: Int, to: Int, bucketThreshold: Int, consumer: IndexedVariablesConsumer, componentType: ValueType? = null): Promise<*>
+
+ /**
+ * It must return quickly. Return [com.intellij.util.ThreeState.UNSURE] otherwise.
+ */
+ fun hasProperties(): ThreeState = ThreeState.UNSURE
+
+ /**
+ * It must return quickly. Return [com.intellij.util.ThreeState.UNSURE] otherwise.
+ */
+ fun hasIndexedProperties(): ThreeState = ThreeState.NO
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/values/ObjectValueBase.kt b/platform/script-debugger/backend/src/debugger/values/ObjectValueBase.kt
new file mode 100644
index 00000000..f56adc43
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/ObjectValueBase.kt
@@ -0,0 +1,66 @@
+// 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.values
+
+import com.intellij.util.SmartList
+import org.jetbrains.concurrency.*
+import org.jetbrains.debugger.EvaluateContext
+import org.jetbrains.debugger.Variable
+import org.jetbrains.debugger.VariablesHost
+import java.util.*
+
+abstract class ObjectValueBase<VALUE_LOADER : ValueManager>(type: ValueType) : ValueBase(type), ObjectValue {
+ protected abstract val childrenManager: VariablesHost<VALUE_LOADER>
+
+ override val properties: Promise<List<Variable>>
+ get() = childrenManager.get()
+
+ internal abstract inner class MyObsolescentAsyncFunction<PARAM, RESULT>(private val obsolescent: Obsolescent) : ObsolescentFunction<PARAM, Promise<RESULT>> {
+ override fun isObsolete() = obsolescent.isObsolete || childrenManager.valueManager.isObsolete
+ }
+
+ override fun getProperties(names: List<String>, evaluateContext: EvaluateContext, obsolescent: Obsolescent): Promise<List<Variable>> = properties
+ .thenAsync(object : MyObsolescentAsyncFunction<List<Variable>, List<Variable>>(obsolescent) {
+ override fun `fun`(variables: List<Variable>) = getSpecifiedProperties(variables, names, evaluateContext)
+ })
+
+ override val valueString: String? = null
+
+ override fun getIndexedProperties(from: Int, to: Int, bucketThreshold: Int, consumer: IndexedVariablesConsumer, componentType: ValueType?): Promise<*> = rejectedPromise<Any?>()
+
+ @Suppress("UNCHECKED_CAST")
+ override val variablesHost: VariablesHost<ValueManager>
+ get() = childrenManager as VariablesHost<ValueManager>
+}
+
+fun getSpecifiedProperties(variables: List<Variable>, names: List<String>, evaluateContext: EvaluateContext): Promise<List<Variable>> {
+ val properties = SmartList<Variable>()
+ var getterCount = 0
+ for (property in variables) {
+ if (!property.isReadable || !names.contains(property.name)) {
+ continue
+ }
+
+ if (!properties.isEmpty()) {
+ Collections.sort(properties) { o1, o2 -> names.indexOf(o1.name) - names.indexOf(o2.name) }
+ }
+
+ properties.add(property)
+ if (property.value == null) {
+ getterCount++
+ }
+ }
+
+ if (getterCount == 0) {
+ return resolvedPromise(properties)
+ }
+ else {
+ val promises = SmartList<Promise<*>>()
+ for (variable in properties) {
+ if (variable.value == null) {
+ val valueModifier = variable.valueModifier!!
+ promises.add(valueModifier.evaluateGet(variable, evaluateContext))
+ }
+ }
+ return promises.all(properties)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/values/PrimitiveValue.kt b/platform/script-debugger/backend/src/debugger/values/PrimitiveValue.kt
new file mode 100644
index 00000000..995e905c
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/PrimitiveValue.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.values
+
+open class PrimitiveValue(type: ValueType, override val valueString: String) : ValueBase(type) {
+
+ constructor(type: ValueType, value: Int) : this(type, Integer.toString(value)) {
+ }
+
+ constructor(type: ValueType, value: Long) : this(type, java.lang.Long.toString(value)) {
+ }
+
+ companion object {
+ val NA_N_VALUE: String = "NaN"
+ val INFINITY_VALUE: String = "Infinity"
+
+ @JvmField
+ val NULL: PrimitiveValue = PrimitiveValue(ValueType.NULL, "null")
+ @JvmField
+ val UNDEFINED: PrimitiveValue = PrimitiveValue(ValueType.UNDEFINED, "undefined")
+
+ val NAN: PrimitiveValue = PrimitiveValue(ValueType.NUMBER, NA_N_VALUE)
+ val INFINITY: PrimitiveValue = PrimitiveValue(ValueType.NUMBER, INFINITY_VALUE)
+
+ private val TRUE = PrimitiveValue(ValueType.BOOLEAN, "true")
+ private val FALSE = PrimitiveValue(ValueType.BOOLEAN, "false")
+
+ fun bool(value: String): PrimitiveValue {
+ return if (value == "true") TRUE else FALSE
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/values/StringValue.kt b/platform/script-debugger/backend/src/debugger/values/StringValue.kt
new file mode 100644
index 00000000..8a1e8c48
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/StringValue.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.values
+
+import org.jetbrains.concurrency.Promise
+
+interface StringValue : Value {
+ val isTruncated: Boolean
+
+ val length: Int
+
+ /**
+ * Asynchronously reloads object value with extended size limit
+ */
+ val fullString: Promise<String>
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/values/Value.kt b/platform/script-debugger/backend/src/debugger/values/Value.kt
new file mode 100755
index 00000000..79846ce9
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/Value.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.values
+
+/**
+ * An object that represents a VM variable value (compound or atomic).
+ */
+interface Value {
+ val type: ValueType
+
+ /**
+ * @return a string representation of this value
+ */
+ val valueString: String?
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/values/ValueBase.kt b/platform/script-debugger/backend/src/debugger/values/ValueBase.kt
new file mode 100644
index 00000000..2d62611a
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/ValueBase.kt
@@ -0,0 +1,18 @@
+/*
+ * 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.values
+
+abstract class ValueBase(override val type: ValueType) : Value \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/values/ValueManager.kt b/platform/script-debugger/backend/src/debugger/values/ValueManager.kt
new file mode 100644
index 00000000..4d7eb248
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/ValueManager.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.values
+
+import org.jetbrains.concurrency.Obsolescent
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * The main idea of this class - don't create value for remote value handle if already exists. So,
+ * implementation of this class keep map of value to remote value handle.
+ * Also, this class maintains cache timestamp.
+
+ * Currently WIP implementation doesn't keep such map due to protocol issue. But V8 does.
+ */
+abstract class ValueManager() : Obsolescent {
+ private val cacheStamp = AtomicInteger()
+ @Volatile private var obsolete = false
+
+ open fun clearCaches() {
+ cacheStamp.incrementAndGet()
+ }
+
+ fun getCacheStamp(): Int = cacheStamp.get()
+
+ final override fun isObsolete(): Boolean = obsolete
+
+ fun markObsolete() {
+ obsolete = true
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/debugger/values/ValueType.kt b/platform/script-debugger/backend/src/debugger/values/ValueType.kt
new file mode 100644
index 00000000..591e827b
--- /dev/null
+++ b/platform/script-debugger/backend/src/debugger/values/ValueType.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.values
+
+private val VALUE_TYPES = ValueType.values()
+
+/**
+ * Don't forget to update NashornDebuggerSupport.ValueType and DebuggerSupport.ts respectively also
+ */
+enum class ValueType {
+ OBJECT,
+ NUMBER,
+ STRING,
+ FUNCTION,
+ BOOLEAN,
+ BIGINT,
+
+ ARRAY,
+ NODE,
+
+ UNDEFINED,
+ NULL,
+ SYMBOL;
+
+ /**
+ * Returns whether `type` corresponds to a JsObject. Note that while 'null' is an object
+ * in JavaScript world, here for API consistency it has bogus type [.NULL] and is
+ * not a [ObjectValue]
+ */
+ val isObjectType: Boolean
+ get() = this == OBJECT || this == ARRAY || this == FUNCTION || this == NODE
+
+ companion object {
+ fun fromIndex(index: Int): ValueType = VALUE_TYPES.get(index)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/rpc/CommandProcessor.kt b/platform/script-debugger/backend/src/rpc/CommandProcessor.kt
new file mode 100644
index 00000000..6c5cff13
--- /dev/null
+++ b/platform/script-debugger/backend/src/rpc/CommandProcessor.kt
@@ -0,0 +1,57 @@
+// 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.rpc
+
+import com.intellij.openapi.diagnostic.Logger
+import io.netty.buffer.ByteBuf
+import org.jetbrains.concurrency.createError
+import org.jetbrains.jsonProtocol.Request
+import java.util.concurrent.atomic.AtomicInteger
+
+val LOG: Logger = Logger.getInstance(CommandProcessor::class.java)
+
+abstract class CommandProcessor<INCOMING, INCOMING_WITH_SEQ : Any, SUCCESS_RESPONSE : Any?> : CommandSenderBase<SUCCESS_RESPONSE>(),
+ MessageManager.Handler<Request<*>, INCOMING, INCOMING_WITH_SEQ, SUCCESS_RESPONSE>,
+ ResultReader<SUCCESS_RESPONSE>,
+ MessageProcessor {
+ private val currentSequence = AtomicInteger()
+ @Suppress("LeakingThis")
+ protected val messageManager: MessageManager<Request<*>, INCOMING, INCOMING_WITH_SEQ, SUCCESS_RESPONSE> = MessageManager(this)
+
+ override fun cancelWaitingRequests() {
+ messageManager.cancelWaitingRequests()
+ }
+
+ override fun closed() {
+ messageManager.closed()
+ }
+
+ override fun getUpdatedSequence(message: Request<*>): Int {
+ val id = currentSequence.incrementAndGet()
+ message.finalize(id)
+ return id
+ }
+
+ final override fun <RESULT> doSend(message: Request<RESULT>, callback: RequestPromise<SUCCESS_RESPONSE, RESULT>) {
+ messageManager.send(message, callback)
+ }
+}
+
+fun requestToByteBuf(message: Request<*>, isDebugEnabled: Boolean = LOG.isDebugEnabled): ByteBuf {
+ val content = message.buffer
+ if (isDebugEnabled) {
+ LOG.debug("OUT: ${content.toString(Charsets.UTF_8)}")
+ }
+ return content
+}
+
+interface ResultReader<in RESPONSE> {
+ fun <RESULT> readResult(readMethodName: String, successResponse: RESPONSE): RESULT?
+}
+
+interface RequestCallback<SUCCESS_RESPONSE> {
+ fun onSuccess(response: SUCCESS_RESPONSE?, resultReader: ResultReader<SUCCESS_RESPONSE>?)
+
+ fun onError(error: Throwable)
+
+ fun onError(error: String): Unit = onError(createError(error))
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/rpc/CommandSenderBase.kt b/platform/script-debugger/backend/src/rpc/CommandSenderBase.kt
new file mode 100644
index 00000000..40751ebe
--- /dev/null
+++ b/platform/script-debugger/backend/src/rpc/CommandSenderBase.kt
@@ -0,0 +1,40 @@
+// 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.rpc
+
+import org.jetbrains.concurrency.AsyncPromise
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.catchError
+import org.jetbrains.jsonProtocol.Request
+
+abstract class CommandSenderBase<SUCCESS_RESPONSE> {
+ protected abstract fun <RESULT> doSend(message: Request<RESULT>, callback: RequestPromise<SUCCESS_RESPONSE, RESULT>)
+
+ fun <RESULT : Any?> send(message: Request<RESULT>): Promise<RESULT> {
+ val callback = RequestPromise<SUCCESS_RESPONSE, RESULT>(message.methodName)
+ doSend(message, callback)
+ return callback
+ }
+}
+
+class RequestPromise<SUCCESS_RESPONSE, RESULT : Any?>(private val methodName: String?) : AsyncPromise<RESULT>(), RequestCallback<SUCCESS_RESPONSE> {
+ override fun onSuccess(response: SUCCESS_RESPONSE?, resultReader: ResultReader<SUCCESS_RESPONSE>?) {
+ catchError {
+ val r: Any?
+ if (resultReader == null || response == null) {
+ r = response
+ }
+ else if (methodName == null) {
+ r = null
+ }
+ else {
+ r = resultReader.readResult(methodName, response)
+ }
+
+ UnsafeSetResult.setResult(this, r)
+ }
+ }
+
+ override fun onError(error: Throwable) {
+ setError(error)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/rpc/MessageManager.kt b/platform/script-debugger/backend/src/rpc/MessageManager.kt
new file mode 100644
index 00000000..325ab331
--- /dev/null
+++ b/platform/script-debugger/backend/src/rpc/MessageManager.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.rpc
+
+import com.intellij.util.containers.ContainerUtil
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.jsonProtocol.Request
+import java.io.IOException
+import java.util.*
+
+interface MessageProcessor {
+ fun cancelWaitingRequests()
+
+ fun closed()
+
+ fun <RESULT> send(message: Request<RESULT>): Promise<RESULT>
+}
+
+class MessageManager<REQUEST, INCOMING, INCOMING_WITH_SEQ : Any, SUCCESS>(private val handler: MessageManager.Handler<REQUEST, INCOMING, INCOMING_WITH_SEQ, SUCCESS>) : MessageManagerBase() {
+ private val callbackMap = ContainerUtil.createConcurrentIntObjectMap<RequestCallback<SUCCESS>>()
+
+ interface Handler<OUTGOING, INCOMING, INCOMING_WITH_SEQ : Any, SUCCESS> {
+ fun getUpdatedSequence(message: OUTGOING): Int
+
+ @Throws(IOException::class)
+ fun write(message: OUTGOING): Boolean
+
+ fun readIfHasSequence(incoming: INCOMING): INCOMING_WITH_SEQ?
+
+ fun getSequence(incomingWithSeq: INCOMING_WITH_SEQ): Int = throw AbstractMethodError()
+
+ fun getSequence(incomingWithSeq: INCOMING_WITH_SEQ, incoming: INCOMING): Int = getSequence(incomingWithSeq)
+
+ fun acceptNonSequence(incoming: INCOMING)
+
+ fun call(response: INCOMING_WITH_SEQ, callback: RequestCallback<SUCCESS>)
+ }
+
+ fun send(message: REQUEST, callback: RequestCallback<SUCCESS>) {
+ if (rejectIfClosed(callback)) {
+ return
+ }
+
+ val sequence = handler.getUpdatedSequence(message)
+ callbackMap.put(sequence, callback)
+
+ val success: Boolean
+ try {
+ success = handler.write(message)
+ }
+ catch (e: Throwable) {
+ try {
+ failedToSend(sequence)
+ }
+ finally {
+ LOG.error("Failed to send", e)
+ }
+ return
+ }
+
+ if (!success) {
+ failedToSend(sequence)
+ }
+ }
+
+ private fun failedToSend(sequence: Int) {
+ callbackMap.remove(sequence)?.onError("Failed to send")
+ }
+
+ fun processIncoming(incomingParsed: INCOMING) {
+ val commandResponse = handler.readIfHasSequence(incomingParsed)
+ if (commandResponse == null) {
+ if (closed) {
+ // just ignore
+ LOG.info("Connection closed, ignore incoming")
+ }
+ else {
+ handler.acceptNonSequence(incomingParsed)
+ }
+ return
+ }
+
+ val callback = getCallbackAndRemove(handler.getSequence(commandResponse, incomingParsed))
+ if (rejectIfClosed(callback)) {
+ return
+ }
+
+ try {
+ handler.call(commandResponse, callback)
+ }
+ catch (e: Throwable) {
+ callback.onError(e)
+ LOG.error("Failed to dispatch response to callback", e)
+ }
+ }
+
+ fun getCallbackAndRemove(id: Int): RequestCallback<SUCCESS> = callbackMap.remove(id) ?: throw IllegalArgumentException("Cannot find callback with id $id")
+
+ fun cancelWaitingRequests() {
+ // we should call them in the order they have been submitted
+ val map = callbackMap
+ val keys = map.keys()
+ Arrays.sort(keys)
+ for (key in keys) {
+ map.get(key)?.reject()
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/rpc/MessageManagerBase.kt b/platform/script-debugger/backend/src/rpc/MessageManagerBase.kt
new file mode 100644
index 00000000..b92be849
--- /dev/null
+++ b/platform/script-debugger/backend/src/rpc/MessageManagerBase.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.rpc
+
+const val CONNECTION_CLOSED_MESSAGE: String = "Connection closed"
+
+abstract class MessageManagerBase {
+ @Volatile protected var closed: Boolean = false
+
+ protected fun rejectIfClosed(callback: RequestCallback<*>): Boolean {
+ if (closed) {
+ callback.onError("Connection closed")
+ return true
+ }
+ return false
+ }
+
+ fun closed() {
+ closed = true
+ }
+}
+
+fun RequestCallback<*>.reject() {
+ onError(CONNECTION_CLOSED_MESSAGE)
+} \ No newline at end of file
diff --git a/platform/script-debugger/backend/src/rpc/UnsafeSetResult.java b/platform/script-debugger/backend/src/rpc/UnsafeSetResult.java
new file mode 100644
index 00000000..aeed0568
--- /dev/null
+++ b/platform/script-debugger/backend/src/rpc/UnsafeSetResult.java
@@ -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.rpc;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.concurrency.AsyncPromise;
+
+// we cannot fix all WIP types to be nullable for now,
+// but don't want to to use explicitly nullable result type for method setResult
+class UnsafeSetResult {
+ static <T> void setResult(@NotNull AsyncPromise<T> promise, @Nullable Object result) {
+ //noinspection unchecked
+ promise.setResult((T)result);
+ }
+}
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
diff --git a/platform/script-debugger/protocol/protocol-model-generator/generated/ProtocolSchemaReaderImpl.kt b/platform/script-debugger/protocol/protocol-model-generator/generated/ProtocolSchemaReaderImpl.kt
new file mode 100644
index 00000000..352738cf
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/generated/ProtocolSchemaReaderImpl.kt
@@ -0,0 +1,355 @@
+// Generated source
+package org.jetbrains.jsonProtocol
+
+import org.jetbrains.jsonProtocol.*
+
+import org.jetbrains.io.JsonReaderEx
+
+import org.jetbrains.jsonProtocol.JsonReaders.*
+
+internal class ProtocolSchemaReaderImpl : org.jetbrains.jsonProtocol.ProtocolSchemaReader {
+ override fun parseRoot(reader: org.jetbrains.io.JsonReaderEx): org.jetbrains.jsonProtocol.ProtocolMetaModel.Root = M0(reader, null)
+
+ private class M0(reader: JsonReaderEx, preReadName: String?) : org.jetbrains.jsonProtocol.ProtocolMetaModel.Root {
+ override var version: org.jetbrains.jsonProtocol.ProtocolMetaModel.Version? = null
+ private var _domains: List<org.jetbrains.jsonProtocol.ProtocolMetaModel.Domain>? = null
+
+ init {
+ var _n = preReadName
+ if (_n == null && reader.hasNext() && reader.beginObject().hasNext()) {
+ _n = reader.nextName()
+ }
+
+ loop@ while (_n != null) {
+ when (_n) {
+ "version" -> version = M1(reader, null)
+ "domains" -> _domains = readObjectArray(reader, FM2())
+ else -> reader.skipValue()
+ }
+ _n = reader.nextNameOrNull()
+ }
+
+ reader.endObject()
+ }
+
+ override fun domains() = _domains!!
+
+ override fun equals(other: Any?): Boolean = other is M0 && version == other.version && _domains == other._domains
+ }
+
+ private class M1(reader: JsonReaderEx, preReadName: String?) : org.jetbrains.jsonProtocol.ProtocolMetaModel.Version {
+ private var _major: String? = null
+ private var _minor: String? = null
+
+ init {
+ var _n = preReadName
+ if (_n == null && reader.hasNext() && reader.beginObject().hasNext()) {
+ _n = reader.nextName()
+ }
+
+ loop@ while (_n != null) {
+ when (_n) {
+ "major" -> _major = reader.nextString()
+ "minor" -> _minor = reader.nextString()
+ else -> reader.skipValue()
+ }
+ _n = reader.nextNameOrNull()
+ }
+
+ reader.endObject()
+ }
+
+ override fun major() = _major!!
+
+ override fun minor() = _minor!!
+
+ override fun equals(other: Any?): Boolean = other is M1 && _major == other._major && _minor == other._minor
+ }
+
+ private class M2(reader: JsonReaderEx, preReadName: String?) : org.jetbrains.jsonProtocol.ProtocolMetaModel.Domain {
+ override var description: String? = null
+ override var events: List<org.jetbrains.jsonProtocol.ProtocolMetaModel.Event>? = null
+ override var experimental = false
+ override var hidden = false
+ override var types: List<org.jetbrains.jsonProtocol.ProtocolMetaModel.StandaloneType>? = null
+ private var _commands: List<org.jetbrains.jsonProtocol.ProtocolMetaModel.Command>? = null
+ private var _domain: String? = null
+
+ init {
+ var _n = preReadName
+ if (_n == null && reader.hasNext() && reader.beginObject().hasNext()) {
+ _n = reader.nextName()
+ }
+
+ loop@ while (_n != null) {
+ when (_n) {
+ "description" -> description = reader.nextNullableString()
+ "events" -> events = readObjectArray(reader, FM5())
+ "experimental" -> experimental = reader.nextBoolean()
+ "hidden" -> hidden = reader.nextBoolean()
+ "types" -> types = readObjectArray(reader, FM6())
+ "commands" -> _commands = readObjectArray(reader, FM3())
+ "domain" -> _domain = reader.nextString()
+ else -> reader.skipValue()
+ }
+ _n = reader.nextNameOrNull()
+ }
+
+ reader.endObject()
+ }
+
+ override fun commands() = _commands!!
+
+ override fun domain() = _domain!!
+
+ override fun equals(other: Any?): Boolean = other is M2 && experimental == other.experimental && hidden == other.hidden && description == other.description && _domain == other._domain && events == other.events && types == other.types && _commands == other._commands
+ }
+
+ private class M3(reader: JsonReaderEx, preReadName: String?) : org.jetbrains.jsonProtocol.ProtocolMetaModel.Command {
+ override var async = false
+ override var description: String? = null
+ override var hidden = false
+ override var parameters: List<org.jetbrains.jsonProtocol.ProtocolMetaModel.Parameter>? = null
+ override var returns: List<org.jetbrains.jsonProtocol.ProtocolMetaModel.Parameter>? = null
+ private var _name: String? = null
+
+ init {
+ var _n = preReadName
+ if (_n == null && reader.hasNext() && reader.beginObject().hasNext()) {
+ _n = reader.nextName()
+ }
+
+ loop@ while (_n != null) {
+ when (_n) {
+ "async" -> async = reader.nextBoolean()
+ "description" -> description = reader.nextNullableString()
+ "hidden" -> hidden = reader.nextBoolean()
+ "parameters" -> parameters = readObjectArray(reader, FM4())
+ "returns" -> returns = readObjectArray(reader, FM4())
+ "name" -> _name = reader.nextString()
+ else -> reader.skipValue()
+ }
+ _n = reader.nextNameOrNull()
+ }
+
+ reader.endObject()
+ }
+
+ override fun name() = _name!!
+
+ override fun equals(other: Any?): Boolean = other is M3 && async == other.async && hidden == other.hidden && description == other.description && _name == other._name && parameters == other.parameters && returns == other.returns
+ }
+
+ private class M4(reader: JsonReaderEx, preReadName: String?) : org.jetbrains.jsonProtocol.ProtocolMetaModel.Parameter {
+ override var default: String? = null
+ override var hidden = false
+ override var optional = false
+ override var shortName: String? = null
+ private var _name: String? = null
+ override var ref: String? = null
+ override var description: String? = null
+ override var enum: List<String>? = null
+ override var items: org.jetbrains.jsonProtocol.ProtocolMetaModel.ArrayItemType? = null
+ override var type: String? = null
+
+ init {
+ var _n = preReadName
+ if (_n == null && reader.hasNext() && reader.beginObject().hasNext()) {
+ _n = reader.nextName()
+ }
+
+ loop@ while (_n != null) {
+ when (_n) {
+ "default" -> default = readRawString(reader)
+ "hidden" -> hidden = reader.nextBoolean()
+ "optional" -> optional = reader.nextBoolean()
+ "shortName" -> shortName = reader.nextNullableString()
+ "name" -> _name = reader.nextString()
+ "\$ref" -> ref = reader.nextNullableString()
+ "description" -> description = reader.nextNullableString()
+ "enum" -> enum = nextList(reader)
+ "items" -> items = M7(reader, null)
+ "type" -> type = reader.nextNullableString()
+ else -> reader.skipValue()
+ }
+ _n = reader.nextNameOrNull()
+ }
+
+ reader.endObject()
+ }
+
+ override fun name() = _name!!
+
+ override fun equals(other: Any?): Boolean = other is M4 && hidden == other.hidden && optional == other.optional && default == other.default && shortName == other.shortName && _name == other._name && ref == other.ref && description == other.description && type == other.type && enum == other.enum && items == other.items
+ }
+
+ private class M5(reader: JsonReaderEx, preReadName: String?) : org.jetbrains.jsonProtocol.ProtocolMetaModel.Event {
+ override var description: String? = null
+ override var hidden = false
+ override var optionalData = false
+ override var parameters: List<org.jetbrains.jsonProtocol.ProtocolMetaModel.Parameter>? = null
+ private var _name: String? = null
+
+ init {
+ var _n = preReadName
+ if (_n == null && reader.hasNext() && reader.beginObject().hasNext()) {
+ _n = reader.nextName()
+ }
+
+ loop@ while (_n != null) {
+ when (_n) {
+ "description" -> description = reader.nextNullableString()
+ "hidden" -> hidden = reader.nextBoolean()
+ "optionalData" -> optionalData = reader.nextBoolean()
+ "parameters" -> parameters = readObjectArray(reader, FM4())
+ "name" -> _name = reader.nextString()
+ else -> reader.skipValue()
+ }
+ _n = reader.nextNameOrNull()
+ }
+
+ reader.endObject()
+ }
+
+ override fun name() = _name!!
+
+ override fun equals(other: Any?): Boolean = other is M5 && hidden == other.hidden && optionalData == other.optionalData && description == other.description && _name == other._name && parameters == other.parameters
+ }
+
+ private class M6(reader: JsonReaderEx, preReadName: String?) : org.jetbrains.jsonProtocol.ProtocolMetaModel.StandaloneType {
+ override var hidden = false
+ private var _id: String? = null
+ override var properties: List<org.jetbrains.jsonProtocol.ProtocolMetaModel.ObjectProperty>? = null
+ override var description: String? = null
+ override var enum: List<String>? = null
+ override var items: org.jetbrains.jsonProtocol.ProtocolMetaModel.ArrayItemType? = null
+ override var type: String? = null
+
+ init {
+ var _n = preReadName
+ if (_n == null && reader.hasNext() && reader.beginObject().hasNext()) {
+ _n = reader.nextName()
+ }
+
+ loop@ while (_n != null) {
+ when (_n) {
+ "hidden" -> hidden = reader.nextBoolean()
+ "id" -> _id = reader.nextString()
+ "properties" -> properties = readObjectArray(reader, FM8())
+ "description" -> description = reader.nextNullableString()
+ "enum" -> enum = nextList(reader)
+ "items" -> items = M7(reader, null)
+ "type" -> type = reader.nextNullableString()
+ else -> reader.skipValue()
+ }
+ _n = reader.nextNameOrNull()
+ }
+
+ reader.endObject()
+ }
+
+ override fun id() = _id!!
+
+ override fun equals(other: Any?): Boolean = other is M6 && hidden == other.hidden && _id == other._id && description == other.description && type == other.type && properties == other.properties && enum == other.enum && items == other.items
+ }
+
+ private class M7(reader: JsonReaderEx, preReadName: String?) : org.jetbrains.jsonProtocol.ProtocolMetaModel.ArrayItemType {
+ override var optional = false
+ override var properties: List<org.jetbrains.jsonProtocol.ProtocolMetaModel.ObjectProperty>? = null
+ override var description: String? = null
+ override var enum: List<String>? = null
+ override var items: org.jetbrains.jsonProtocol.ProtocolMetaModel.ArrayItemType? = null
+ override var type: String? = null
+ override var ref: String? = null
+
+ init {
+ var _n = preReadName
+ if (_n == null && reader.hasNext() && reader.beginObject().hasNext()) {
+ _n = reader.nextName()
+ }
+
+ loop@ while (_n != null) {
+ when (_n) {
+ "optional" -> optional = reader.nextBoolean()
+ "properties" -> properties = readObjectArray(reader, FM8())
+ "description" -> description = reader.nextNullableString()
+ "enum" -> enum = nextList(reader)
+ "items" -> items = M7(reader, null)
+ "type" -> type = reader.nextNullableString()
+ "\$ref" -> ref = reader.nextNullableString()
+ else -> reader.skipValue()
+ }
+ _n = reader.nextNameOrNull()
+ }
+
+ reader.endObject()
+ }
+
+ override fun equals(other: Any?): Boolean = other is M7 && optional == other.optional && description == other.description && type == other.type && ref == other.ref && properties == other.properties && enum == other.enum && items == other.items
+ }
+
+ private class M8(reader: JsonReaderEx, preReadName: String?) : org.jetbrains.jsonProtocol.ProtocolMetaModel.ObjectProperty {
+ override var hidden = false
+ private var _name: String? = null
+ override var optional = false
+ override var shortName: String? = null
+ override var ref: String? = null
+ override var description: String? = null
+ override var enum: List<String>? = null
+ override var items: org.jetbrains.jsonProtocol.ProtocolMetaModel.ArrayItemType? = null
+ override var type: String? = null
+
+ init {
+ var _n = preReadName
+ if (_n == null && reader.hasNext() && reader.beginObject().hasNext()) {
+ _n = reader.nextName()
+ }
+
+ loop@ while (_n != null) {
+ when (_n) {
+ "hidden" -> hidden = reader.nextBoolean()
+ "name" -> _name = reader.nextString()
+ "optional" -> optional = reader.nextBoolean()
+ "shortName" -> shortName = reader.nextNullableString()
+ "\$ref" -> ref = reader.nextNullableString()
+ "description" -> description = reader.nextNullableString()
+ "enum" -> enum = nextList(reader)
+ "items" -> items = M7(reader, null)
+ "type" -> type = reader.nextNullableString()
+ else -> reader.skipValue()
+ }
+ _n = reader.nextNameOrNull()
+ }
+
+ reader.endObject()
+ }
+
+ override fun name() = _name!!
+
+ override fun equals(other: Any?): Boolean = other is M8 && hidden == other.hidden && optional == other.optional && _name == other._name && shortName == other.shortName && ref == other.ref && description == other.description && type == other.type && enum == other.enum && items == other.items
+ }
+
+ private class FM2 : ObjectFactory<org.jetbrains.jsonProtocol.ProtocolMetaModel.Domain>() {
+ override fun read(reader: JsonReaderEx): org.jetbrains.jsonProtocol.ProtocolMetaModel.Domain = M2(reader, null)
+ }
+
+ private class FM5 : ObjectFactory<org.jetbrains.jsonProtocol.ProtocolMetaModel.Event>() {
+ override fun read(reader: JsonReaderEx): org.jetbrains.jsonProtocol.ProtocolMetaModel.Event = M5(reader, null)
+ }
+
+ private class FM6 : ObjectFactory<org.jetbrains.jsonProtocol.ProtocolMetaModel.StandaloneType>() {
+ override fun read(reader: JsonReaderEx): org.jetbrains.jsonProtocol.ProtocolMetaModel.StandaloneType = M6(reader, null)
+ }
+
+ private class FM3 : ObjectFactory<org.jetbrains.jsonProtocol.ProtocolMetaModel.Command>() {
+ override fun read(reader: JsonReaderEx): org.jetbrains.jsonProtocol.ProtocolMetaModel.Command = M3(reader, null)
+ }
+
+ private class FM4 : ObjectFactory<org.jetbrains.jsonProtocol.ProtocolMetaModel.Parameter>() {
+ override fun read(reader: JsonReaderEx): org.jetbrains.jsonProtocol.ProtocolMetaModel.Parameter = M4(reader, null)
+ }
+
+ private class FM8 : ObjectFactory<org.jetbrains.jsonProtocol.ProtocolMetaModel.ObjectProperty>() {
+ override fun read(reader: JsonReaderEx): org.jetbrains.jsonProtocol.ProtocolMetaModel.ObjectProperty = M8(reader, null)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/intellij.javascript.protocolModelGenerator.iml b/platform/script-debugger/protocol/protocol-model-generator/intellij.javascript.protocolModelGenerator.iml
new file mode 100644
index 00000000..8b9e75fb
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/intellij.javascript.protocolModelGenerator.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" packagePrefix="org.jetbrains.protocolModelGenerator" />
+ <sourceFolder url="file://$MODULE_DIR$/generated" isTestSource="false" generated="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="gson" level="project" />
+ <orderEntry type="module" module-name="intellij.javascript.schemaReaderGenerator" />
+ <orderEntry type="module" module-name="intellij.javascript.protocolReader" />
+ <orderEntry type="library" name="Trove4j" level="project" />
+ <orderEntry type="module" module-name="intellij.platform.util.rt" />
+ <orderEntry type="module" module-name="intellij.platform.ide.impl" />
+ <orderEntry type="library" name="KotlinJavaRuntime" level="project" />
+ </component>
+</module> \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/BoxableType.kt b/platform/script-debugger/protocol/protocol-model-generator/src/BoxableType.kt
new file mode 100644
index 00000000..8fda9d35
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/BoxableType.kt
@@ -0,0 +1,21 @@
+package org.jetbrains.protocolModelGenerator
+
+interface BoxableType {
+ val defaultValue: String?
+
+ val fullText: CharSequence
+
+ fun getShortText(contextNamespace: NamePath): String
+
+ val writeMethodName: String
+
+ companion object {
+ val STRING: StandaloneType = StandaloneType(NamePath("String"), "writeString", "null")
+ val ANY_STRING: StandaloneType = StandaloneType(NamePath("String"), "writeString", "null")
+ val INT: StandaloneType = StandaloneType(NamePath("Int"), "writeInt", null)
+ val LONG: StandaloneType = StandaloneType(NamePath("Long"), "writeLong", null)
+ val NUMBER: StandaloneType = StandaloneType(NamePath("Double"), "writeDouble", "null")
+ val BOOLEAN: StandaloneType = StandaloneType(NamePath("Boolean"), "writeBoolean", null)
+ val MAP: StandaloneType = StandaloneType(NamePath("Map<String, String>"), "writeMap", "null")
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/ClassNameScheme.kt b/platform/script-debugger/protocol/protocol-model-generator/src/ClassNameScheme.kt
new file mode 100644
index 00000000..64571610
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/ClassNameScheme.kt
@@ -0,0 +1,48 @@
+package org.jetbrains.protocolModelGenerator
+
+fun getPackageName(rootPackage: String, domain: String): String {
+ if (domain.isEmpty()) {
+ return rootPackage
+ }
+ return "$rootPackage.${domain.toLowerCase()}"
+}
+
+abstract class ClassNameScheme(private val suffix: String, private val rootPackage: String) {
+ fun getFullName(domainName: String, baseName: String): NamePath {
+ return NamePath(getShortName(baseName), NamePath(getPackageNameVirtual(domainName)))
+ }
+
+ fun getShortName(baseName: String): String {
+ return if (baseName.endsWith("Descriptor")) baseName else String(getShortNameChars(baseName))
+ }
+
+ private fun getShortNameChars(baseName: String): CharArray {
+ val name = CharArray(baseName.length + suffix.length)
+ baseName.toCharArray(name, 0, 0, baseName.length)
+ if (!suffix.isEmpty()) {
+ suffix.toCharArray(name, baseName.length, 0, suffix.length)
+ }
+ if (Character.isLowerCase(name[0])) {
+ name[0] = Character.toUpperCase(name[0])
+ }
+ if (baseName.endsWith("breakpoint")) {
+ name[baseName.length - "breakpoint".length] = 'B'
+ }
+ else if (baseName.endsWith("breakpoints")) {
+ name[baseName.length - "breakpoints".length] = 'B'
+ }
+ return name
+ }
+
+ fun getPackageNameVirtual(domainName: String): String = getPackageName(rootPackage, domainName)
+
+ class Input(suffix: String, rootPackage: String) : ClassNameScheme(suffix, rootPackage) {
+ fun getParseMethodName(domain: String, name: String): String {
+ return "read" + capitalizeFirstChar(domain) + getShortName(name)
+ }
+ }
+
+ class Output(suffix: String, rootPackage: String) : ClassNameScheme(suffix, rootPackage)
+
+ class Common(suffix: String, rootPackage: String) : ClassNameScheme(suffix, rootPackage)
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/ClassScope.kt b/platform/script-debugger/protocol/protocol-model-generator/src/ClassScope.kt
new file mode 100644
index 00000000..74c89b5f
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/ClassScope.kt
@@ -0,0 +1,28 @@
+package org.jetbrains.protocolModelGenerator
+
+import com.intellij.util.SmartList
+import org.jetbrains.jsonProtocol.ItemDescriptor
+import org.jetbrains.protocolReader.TextOutput
+
+internal abstract class ClassScope(val generator: DomainGenerator, val classContextNamespace: NamePath) {
+ private val additionalMemberTexts = SmartList<(out: TextOutput) -> Unit>()
+
+ fun addMember(appender: (out: TextOutput) -> Unit) {
+ additionalMemberTexts.add(appender)
+ }
+
+ fun writeAdditionalMembers(out: TextOutput) {
+ if (additionalMemberTexts.isEmpty()) {
+ return
+ }
+
+ out.newLine()
+ for (deferredWriter in additionalMemberTexts) {
+ deferredWriter(out)
+ }
+ }
+
+ abstract val typeDirection: TypeData.Direction
+}
+
+fun ItemDescriptor.Named.getName(): String = shortName ?: name() \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/CreateStandaloneTypeBindingVisitorBase.kt b/platform/script-debugger/protocol/protocol-model-generator/src/CreateStandaloneTypeBindingVisitorBase.kt
new file mode 100644
index 00000000..15168456
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/CreateStandaloneTypeBindingVisitorBase.kt
@@ -0,0 +1,27 @@
+package org.jetbrains.protocolModelGenerator
+
+import org.jetbrains.jsonProtocol.ProtocolMetaModel
+
+internal abstract class CreateStandaloneTypeBindingVisitorBase(private val generator: DomainGenerator, protected val type: ProtocolMetaModel.StandaloneType) : TypeVisitor<StandaloneTypeBinding> {
+ override fun visitString(): StandaloneTypeBinding {
+ return generator.createTypedefTypeBinding(type, PredefinedTarget.STRING, generator.generator.naming.commonTypedef, null)
+ }
+
+ override fun visitInteger() = generator.createTypedefTypeBinding(type, PredefinedTarget.INT, generator.generator.naming.commonTypedef, null)
+
+ override fun visitRef(refName: String) = throw RuntimeException()
+
+ override fun visitBoolean() = throw RuntimeException()
+
+ override fun visitNumber(): StandaloneTypeBinding {
+ return generator.createTypedefTypeBinding(type, PredefinedTarget.NUMBER, generator.generator.naming.commonTypedef, null)
+ }
+
+ override fun visitMap(): StandaloneTypeBinding {
+ return generator.createTypedefTypeBinding(type, PredefinedTarget.MAP, generator.generator.naming.commonTypedef, null)
+ }
+
+ override fun visitUnknown(): StandaloneTypeBinding {
+ throw RuntimeException()
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/DomainGenerator.kt b/platform/script-debugger/protocol/protocol-model-generator/src/DomainGenerator.kt
new file mode 100644
index 00000000..b5ff9814
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/DomainGenerator.kt
@@ -0,0 +1,289 @@
+package org.jetbrains.protocolModelGenerator
+
+import com.intellij.util.SmartList
+import com.intellij.util.containers.isNullOrEmpty
+import org.jetbrains.jsonProtocol.ItemDescriptor
+import org.jetbrains.jsonProtocol.ProtocolMetaModel
+import org.jetbrains.protocolReader.FileUpdater
+import org.jetbrains.protocolReader.JSON_READER_PARAMETER_DEF
+import org.jetbrains.protocolReader.TextOutput
+import org.jetbrains.protocolReader.appendEnums
+
+internal class DomainGenerator(val generator: Generator, val domain: ProtocolMetaModel.Domain, val fileUpdater: FileUpdater) {
+ fun registerTypes() {
+ domain.types?.let {
+ for (type in it) {
+ generator.typeMap.getTypeData(domain.domain(), type.id()).type = type
+ }
+ }
+ }
+
+ fun generateCommandsAndEvents() {
+ val commands = domain.commands().sortedBy(ProtocolMetaModel.Command::name)
+ for (command in commands) {
+ val hasResponse = command.returns != null
+ val returnType = if (hasResponse) generator.naming.commandResult.getShortName(command.name()) else "Unit"
+ generateTopLevelOutputClass(generator.naming.params, command.name(), command.description, "${generator.naming.requestClassName}<$returnType>", {
+ append('"')
+ if (!domain.domain().isEmpty()) {
+ append(domain.domain()).append('.')
+ }
+ append(command.name()).append('"')
+ }, command.parameters)
+
+ if (hasResponse) {
+ generateJsonProtocolInterface(generator.naming.commandResult.getShortName(command.name()), command.description, command.returns, null)
+ generator.jsonProtocolParserClassNames.add(generator.naming.commandResult.getFullName(domain.domain(), command.name()).getFullText())
+ generator.parserRootInterfaceItems.add(ParserRootInterfaceItem(domain.domain(), command.name(), generator.naming.commandResult))
+ }
+ }
+
+ if (domain.events != null) {
+ val events = domain.events!!.sortedBy(ProtocolMetaModel.Event::name)
+ for (event in events) {
+ generateEvenData(event)
+ generator.jsonProtocolParserClassNames.add(generator.naming.eventData.getFullName(domain.domain(), event.name()).getFullText())
+ generator.parserRootInterfaceItems.add(ParserRootInterfaceItem(domain.domain(), event.name(), generator.naming.eventData))
+ }
+ }
+ }
+
+ fun generateCommandAdditionalParam(type: ProtocolMetaModel.StandaloneType) {
+ generateTopLevelOutputClass(generator.naming.additionalParam, type.id(), type.description, null, null, type.properties)
+ }
+
+ private fun <P : ItemDescriptor.Named> generateTopLevelOutputClass(nameScheme: ClassNameScheme, baseName: String, description: String?, baseType: String?, methodName: (TextOutput.() -> Unit)?, properties: List<P>?) {
+ generateOutputClass(fileUpdater.out.newLine().newLine(), nameScheme.getFullName(domain.domain(), baseName), description, baseType, methodName, properties)
+ }
+
+ private fun <P : ItemDescriptor.Named> generateOutputClass(out: TextOutput, classNamePath: NamePath, description: String?, baseType: String?, methodName: (TextOutput.() -> Unit)?, properties: List<P>?) {
+ out.doc(description)
+
+ out.append(if (baseType == null) "class" else "fun").space().append(classNamePath.lastComponent).append('(')
+
+ val classScope = OutputClassScope(this, classNamePath)
+ val (mandatoryParameters, optionalParameters) = getParametersInfo(classScope, properties)
+ if (properties.isNullOrEmpty()) {
+ assert(baseType != null)
+
+ out.append(") = ")
+ out.append(baseType ?: "org.jetbrains.jsonProtocol.OutMessage")
+ out.append('(')
+ methodName?.invoke(out)
+ out.append(')')
+ return
+ }
+ else {
+ classScope.writeMethodParameters(out, mandatoryParameters, false)
+ classScope.writeMethodParameters(out, optionalParameters, mandatoryParameters.isNotEmpty())
+
+ out.append(')')
+ out.append(" : ").append(baseType ?: "org.jetbrains.jsonProtocol.OutMessage")
+ if (baseType == null) {
+ out.append("()").openBlock().append("init")
+ }
+ }
+
+ out.block(baseType != null) {
+ if (baseType != null) {
+ out.append("val m = ").append(baseType)
+ out.append('(')
+ methodName?.invoke(out)
+ out.append(')')
+ }
+
+ if (!properties.isNullOrEmpty()) {
+ val qualifier = if (baseType == null) null else "m"
+ classScope.writeWriteCalls(out, mandatoryParameters, qualifier)
+ classScope.writeWriteCalls(out, optionalParameters, qualifier)
+ if (baseType != null) {
+ out.newLine().append("return m")
+ }
+ }
+ }
+
+ if (baseType == null) {
+ // close class
+ out.closeBlock()
+ }
+
+ classScope.writeAdditionalMembers(out)
+ }
+
+ private fun <P : ItemDescriptor.Named> getParametersInfo(classScope: OutputClassScope, properties: List<P>?): Pair<List<Pair<P, BoxableType>>, List<Pair<P, BoxableType>>> {
+ if (properties.isNullOrEmpty()) {
+ return Pair(emptyList(), emptyList())
+ }
+
+ val mandatoryParameters = SmartList<Pair<P, BoxableType>>()
+ val optionalParameters = SmartList<Pair<P, BoxableType>>()
+ if (properties != null) {
+ for (parameter in properties) {
+ val type = MemberScope(classScope, parameter.name()).resolveType(parameter).type
+ if (parameter.optional) {
+ optionalParameters.add(parameter to type)
+ }
+ else {
+ mandatoryParameters.add(parameter to type)
+ }
+ }
+ }
+ return Pair(mandatoryParameters, optionalParameters)
+ }
+
+ fun createStandaloneOutputTypeBinding(type: ProtocolMetaModel.StandaloneType, name: String) = switchByType(type, MyCreateStandaloneTypeBindingVisitorBase(this, type, name))
+
+ fun createStandaloneInputTypeBinding(type: ProtocolMetaModel.StandaloneType): StandaloneTypeBinding {
+ return switchByType(type, object : CreateStandaloneTypeBindingVisitorBase(this, type) {
+ override fun visitObject(properties: List<ProtocolMetaModel.ObjectProperty>?) = createStandaloneObjectInputTypeBinding(type, properties)
+
+ override fun visitEnum(enumConstants: List<String>): StandaloneTypeBinding {
+ val name = type.id()
+ return object : StandaloneTypeBinding {
+ override fun getJavaType() = StandaloneType(generator.naming.inputEnum.getFullName(domain.domain(), name), "writeEnum")
+
+ override fun generate() {
+ fileUpdater.out.doc(type.description)
+ appendEnums(enumConstants, generator.naming.inputEnum.getShortName(name), true, fileUpdater.out)
+ }
+
+ override fun getDirection() = TypeData.Direction.INPUT
+ }
+ }
+
+ override fun visitArray(items: ProtocolMetaModel.ArrayItemType): StandaloneTypeBinding {
+ val resolveAndGenerateScope = object : ResolveAndGenerateScope {
+ // This class is responsible for generating ad hoc type.
+ // If we ever are to do it, we should generate into string buffer and put strings
+ // inside TypeDef class.
+ override fun getDomainName() = domain.domain()
+
+ override fun getTypeDirection() = TypeData.Direction.INPUT
+
+ override fun generateNestedObject(description: String?, properties: List<ProtocolMetaModel.ObjectProperty>?) = throw UnsupportedOperationException()
+ }
+
+ val arrayType = ListType(generator.resolveType(items, resolveAndGenerateScope).type)
+ return createTypedefTypeBinding(type, object : Target {
+ override fun resolve(context: Target.ResolveContext) = arrayType
+ }, generator.naming.inputTypedef, TypeData.Direction.INPUT)
+ }
+ })
+ }
+
+ fun createStandaloneObjectInputTypeBinding(type: ProtocolMetaModel.StandaloneType, properties: List<ProtocolMetaModel.ObjectProperty>?): StandaloneTypeBinding {
+ val name = type.id()
+ val fullTypeName = generator.naming.inputValue.getFullName(domain.domain(), name)
+ generator.jsonProtocolParserClassNames.add(fullTypeName.getFullText())
+
+ return object : StandaloneTypeBinding {
+ override fun getJavaType() = subMessageType(fullTypeName)
+
+ override fun generate() {
+ val className = generator.naming.inputValue.getFullName(domain.domain(), name)
+ val out = fileUpdater.out
+ out.newLine().newLine()
+ descriptionAndRequiredImport(type.description, out)
+
+ out.append("interface ").append(className.lastComponent).openBlock()
+ val classScope = InputClassScope(this@DomainGenerator, className)
+ if (properties != null) {
+ classScope.generateDeclarationBody(out, properties)
+ }
+ classScope.writeAdditionalMembers(out)
+ out.closeBlock()
+ }
+
+ override fun getDirection() = TypeData.Direction.INPUT
+ }
+ }
+
+ /**
+ * Typedef is an empty class that just holds description and
+ * refers to an actual type (such as String).
+ */
+ fun createTypedefTypeBinding(type: ProtocolMetaModel.StandaloneType, target: Target, nameScheme: ClassNameScheme, direction: TypeData.Direction?): StandaloneTypeBinding {
+ val name = type.id()
+ val typedefJavaName = nameScheme.getFullName(domain.domain(), name)
+ val actualJavaType = target.resolve(object : Target.ResolveContext {
+ override fun generateNestedObject(shortName: String, description: String?, properties: List<ProtocolMetaModel.ObjectProperty>?): BoxableType {
+ if (direction == null) {
+ throw RuntimeException("Unsupported")
+ }
+
+ when (direction) {
+ TypeData.Direction.INPUT -> throw RuntimeException("TODO")
+ TypeData.Direction.OUTPUT -> generateOutputClass(TextOutput(StringBuilder()), NamePath(shortName, typedefJavaName), description, null, null, properties)
+ }
+ return subMessageType(NamePath(shortName, typedefJavaName))
+ }
+ })
+
+ return object : StandaloneTypeBinding {
+ override fun getJavaType() = actualJavaType
+
+ override fun generate() {
+ }
+
+ override fun getDirection() = direction
+ }
+ }
+
+ private fun generateEvenData(event: ProtocolMetaModel.Event) {
+ val className = generator.naming.eventData.getShortName(event.name())
+ val domainName = domain.domain()
+ val fullName = generator.naming.eventData.getFullName(domainName, event.name()).getFullText()
+ generateJsonProtocolInterface(className, event.description, event.parameters) { out ->
+ out.newLine().append("companion object TYPE : org.jetbrains.jsonProtocol.EventType<").append(fullName)
+ if (event.optionalData || event.parameters.isNullOrEmpty()) {
+ out.append('?')
+ }
+ out.append(", ").append(generator.naming.inputPackage).append('.').append(READER_INTERFACE_NAME).append('>')
+ out.append("(\"")
+ if (!domainName.isNullOrEmpty()) {
+ out.append(domainName).append('.')
+ }
+ out.append(event.name()).append("\")").block() {
+ out.append("override fun read(protocolReader: ")
+ out.append(generator.naming.inputPackage).append('.').append(READER_INTERFACE_NAME).append(", ").append(JSON_READER_PARAMETER_DEF).append(")")
+ out.append(" = protocolReader.").append(generator.naming.eventData.getParseMethodName(domainName, event.name())).append("(reader)")
+ }
+ }
+ }
+
+ private fun generateJsonProtocolInterface(className: String, description: String?, parameters: List<ProtocolMetaModel.Parameter>?, additionalMembersText: ((out: TextOutput) -> Unit)?) {
+ val out = fileUpdater.out
+ out.newLine().newLine()
+ descriptionAndRequiredImport(description, out)
+
+ var hasNodeId = false
+ val parametersToAdd = parameters?.filter(fun(p: ProtocolMetaModel.Parameter): Boolean {
+ if (p.name() == "nodeId" && p.ref == "NodeId") {
+ hasNodeId = true
+ return false
+ }
+ else return true
+ })
+ out.append("interface ").append(className)
+ if (hasNodeId) out.append(" : ").append("org.jetbrains.wip.protocol.NodeIdentifiable")
+ out.block {
+ val classScope = InputClassScope(this, NamePath(className, NamePath(getPackageName(generator.naming.inputPackage, domain.domain()))))
+ if (additionalMembersText != null) {
+ classScope.addMember(additionalMembersText)
+ }
+ if (parametersToAdd != null) {
+ classScope.generateDeclarationBody(out, parametersToAdd)
+ }
+ classScope.writeAdditionalMembers(out)
+ }
+ }
+
+ private fun descriptionAndRequiredImport(description: String?, out: TextOutput) {
+ if (description != null) {
+ out.doc(description)
+ }
+// out.append("@JsonType").newLine()
+ }
+}
+
+fun subMessageType(namePath: NamePath): StandaloneType = StandaloneType(namePath, "writeMessage", null) \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/Enums.kt b/platform/script-debugger/protocol/protocol-model-generator/src/Enums.kt
new file mode 100644
index 00000000..5cfaec18
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/Enums.kt
@@ -0,0 +1,33 @@
+package org.jetbrains.protocolReader
+
+import org.jetbrains.jsonProtocol.JsonReaders
+
+internal fun appendEnums(enumConstants: List<String>, enumName: String, input: Boolean, out: TextOutput) {
+ out.append("enum class ").append(enumName)
+
+ if (!input) {
+ out.append("(private val protocolValue: String)")
+ }
+
+ out.openBlock()
+ for (constant in enumConstants) {
+ out.append(JsonReaders.convertRawEnumName(constant))
+ if (!input) {
+ out.append("(\"").append(constant).append("\")")
+ if (enumConstants.get(enumConstants.size - 1) != constant) {
+ out.comma()
+ }
+ }
+ else {
+ out.comma()
+ }
+ }
+
+ if (input) {
+ out.append("NO_ENUM_CONST")
+ }
+ else {
+ out.append(';').newLine().newLine().append("override fun toString() = protocolValue")
+ }
+ out.closeBlock()
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/FileSet.kt b/platform/script-debugger/protocol/protocol-model-generator/src/FileSet.kt
new file mode 100644
index 00000000..a79acfde
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/FileSet.kt
@@ -0,0 +1,52 @@
+package org.jetbrains.protocolModelGenerator
+
+import gnu.trove.THashSet
+import gnu.trove.TObjectProcedure
+import org.jetbrains.protocolReader.FileUpdater
+import java.nio.file.FileVisitResult
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.SimpleFileVisitor
+import java.nio.file.attribute.BasicFileAttributes
+
+/**
+ * Records a list of files in the root directory and deletes files that were not re-generated.
+ */
+class FileSet(private val rootDir: Path) {
+ private val unusedFiles = THashSet<Path>()
+
+ init {
+ Files.walkFileTree(rootDir, object : SimpleFileVisitor<Path>() {
+ override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {
+ return if (Files.isHidden(dir)) FileVisitResult.SKIP_SUBTREE else FileVisitResult.CONTINUE
+ }
+
+ override fun visitFile(path: Path, attrs: BasicFileAttributes): FileVisitResult {
+ if (!Files.isHidden(path)) {
+ unusedFiles.add(path)
+ }
+ return FileVisitResult.CONTINUE
+ }
+ })
+ }
+
+ fun createFileUpdater(filePath: String): FileUpdater {
+ val file = rootDir.resolve(filePath)
+ unusedFiles.remove(file)
+ return FileUpdater(file)
+ }
+
+ fun deleteOtherFiles() {
+ unusedFiles.forEach(TObjectProcedure<Path> { it ->
+ if (Files.deleteIfExists(it)) {
+ val parent = it.parent
+ Files.newDirectoryStream(parent).use { stream ->
+ if (!stream.iterator().hasNext()) {
+ Files.delete(parent)
+ }
+ }
+ }
+ true
+ })
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/Generator.kt b/platform/script-debugger/protocol/protocol-model-generator/src/Generator.kt
new file mode 100644
index 00000000..b93c45fa
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/Generator.kt
@@ -0,0 +1,275 @@
+package org.jetbrains.protocolModelGenerator
+
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.util.containers.ContainerUtil
+import com.intellij.util.containers.isNullOrEmpty
+import gnu.trove.THashMap
+import org.jetbrains.io.JsonReaderEx
+import org.jetbrains.jsonProtocol.*
+import org.jetbrains.protocolReader.TextOutput
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.net.URL
+import java.nio.file.FileSystems
+import java.nio.file.Files
+import java.util.*
+
+fun main(args: Array<String>) {
+ val outputDir = args[0]
+ val roots = IntRange(3, args.size - 1).map {
+ val schemaUrl = args[it]
+ val bytes: ByteArray
+ if (schemaUrl.startsWith("http")) {
+ bytes = loadBytes(URL(schemaUrl).openStream())
+ }
+ else {
+ bytes = Files.readAllBytes(FileSystems.getDefault().getPath(schemaUrl))
+ }
+ val reader = JsonReaderEx(bytes.toString(Charsets.UTF_8))
+ reader.isLenient = true
+ ProtocolSchemaReaderImpl().parseRoot(reader)
+ }
+ val mergedRoot = if (roots.size == 1) roots[0] else object : ProtocolMetaModel.Root {
+ override val version: ProtocolMetaModel.Version?
+ get() = roots[0].version
+
+ override fun domains(): List<ProtocolMetaModel.Domain> {
+ return ContainerUtil.concat(roots.map { it.domains() })
+ }
+ }
+ Generator(outputDir, args[1], args[2], mergedRoot)
+}
+
+private fun loadBytes(stream: InputStream): ByteArray {
+ val buffer = ByteArrayOutputStream(Math.max(stream.available(), 16 * 1024))
+ val bytes = ByteArray(1024 * 20)
+ while (true) {
+ val n = stream.read(bytes, 0, bytes.size)
+ if (n <= 0) {
+ break
+ }
+ buffer.write(bytes, 0, n)
+ }
+ buffer.close()
+ return buffer.toByteArray()
+}
+
+internal class Naming(val inputPackage: String, val requestClassName: String) {
+ val params = ClassNameScheme.Output("", inputPackage)
+ val additionalParam = ClassNameScheme.Output("", inputPackage)
+ val outputTypedef: ClassNameScheme = ClassNameScheme.Output("Typedef", inputPackage)
+
+ val commandResult = ClassNameScheme.Input("Result", inputPackage)
+ val eventData = ClassNameScheme.Input("EventData", inputPackage)
+ val inputValue = ClassNameScheme.Input("Value", inputPackage)
+ val inputEnum = ClassNameScheme.Input("", inputPackage)
+ val inputTypedef = ClassNameScheme.Input("Typedef", inputPackage)
+
+ val commonTypedef = ClassNameScheme.Common("Typedef", inputPackage)
+}
+
+/**
+ * Read metamodel and generates set of files with Java classes/interfaces for the protocol.
+ */
+internal class Generator(outputDir: String, private val rootPackage: String, requestClassName: String, metamodel: ProtocolMetaModel.Root) {
+ val jsonProtocolParserClassNames = ArrayList<String>()
+ val parserRootInterfaceItems = ArrayList<ParserRootInterfaceItem>()
+ val typeMap = TypeMap()
+
+ val nestedTypeMap = THashMap<NamePath, StandaloneType>()
+
+ val fileSet = FileSet(FileSystems.getDefault().getPath(outputDir))
+ val naming = Naming(rootPackage, requestClassName)
+
+ init {
+ val domainList = metamodel.domains()
+ val domainGeneratorMap = THashMap<String, DomainGenerator>()
+
+ for (domain in domainList) {
+ if (!INCLUDED_DOMAINS.contains(domain.domain())) {
+ System.out.println("Domain skipped: ${domain.domain()}")
+ continue
+ }
+
+ val fileUpdater = fileSet.createFileUpdater("${StringUtil.nullize(domain.domain()) ?: "protocol"}.kt")
+ val out = fileUpdater.out
+
+ out.append("// Generated source").newLine().append("package ").append(getPackageName(rootPackage, domain.domain())).newLine().newLine()
+ out.append("import org.jetbrains.jsonProtocol.*").newLine()
+ out.append("import org.jetbrains.io.JsonReaderEx").newLine()
+
+ val domainGenerator = DomainGenerator(this, domain, fileUpdater)
+ domainGeneratorMap.put(domain.domain(), domainGenerator)
+ domainGenerator.registerTypes()
+
+ out.newLine()
+
+ System.out.println("Domain generated: ${domain.domain()}")
+ }
+
+ typeMap.domainGeneratorMap = domainGeneratorMap
+
+ for (domainGenerator in domainGeneratorMap.values) {
+ domainGenerator.generateCommandsAndEvents()
+ }
+
+ val sharedFileUpdater = if (domainGeneratorMap.size == 1) {
+ domainGeneratorMap.values.first().fileUpdater
+ }
+ else {
+ val fileUpdater = fileSet.createFileUpdater("protocol.kt")
+ val out = fileUpdater.out
+ out.append("// Generated source").newLine().append("package ").append(rootPackage).newLine().newLine()
+ out.append("import org.jetbrains.jsonProtocol.*").newLine()
+ out.append("import org.jetbrains.io.JsonReaderEx").newLine()
+ fileUpdater
+ }
+ typeMap.generateRequestedTypes()
+ generateParserInterfaceList(sharedFileUpdater.out)
+ generateParserRoot(parserRootInterfaceItems, sharedFileUpdater.out)
+ fileSet.deleteOtherFiles()
+
+ for (domainGenerator in domainGeneratorMap.values) {
+ domainGenerator.fileUpdater.update()
+ }
+
+ if (domainGeneratorMap.size != 1) {
+ sharedFileUpdater.update()
+ }
+ }
+
+ fun resolveType(itemDescriptor: ItemDescriptor, scope: ResolveAndGenerateScope): TypeDescriptor {
+ return switchByType(itemDescriptor, object : TypeVisitor<TypeDescriptor> {
+ override fun visitRef(refName: String) = TypeDescriptor(resolveRefType(scope.getDomainName(), refName, scope.getTypeDirection()), itemDescriptor)
+
+ override fun visitBoolean() = TypeDescriptor(BoxableType.BOOLEAN, itemDescriptor)
+
+ override fun visitEnum(enumConstants: List<String>): TypeDescriptor {
+ assert(scope is MemberScope)
+ return TypeDescriptor((scope as MemberScope).generateEnum(itemDescriptor.description, enumConstants), itemDescriptor)
+ }
+
+ override fun visitString() = TypeDescriptor(BoxableType.STRING, itemDescriptor)
+
+ override fun visitInteger() = TypeDescriptor(BoxableType.INT, itemDescriptor)
+
+ override fun visitNumber() = TypeDescriptor(BoxableType.NUMBER, itemDescriptor)
+
+ override fun visitMap() = TypeDescriptor(BoxableType.MAP, itemDescriptor)
+
+ override fun visitArray(items: ProtocolMetaModel.ArrayItemType): TypeDescriptor {
+ val type = scope.resolveType(items).type
+ return TypeDescriptor(ListType(type), itemDescriptor, type == BoxableType.ANY_STRING)
+ }
+
+ override fun visitObject(properties: List<ProtocolMetaModel.ObjectProperty>?) = TypeDescriptor(scope.generateNestedObject(itemDescriptor.description, properties), itemDescriptor)
+
+ override fun visitUnknown() = TypeDescriptor(BoxableType.STRING, itemDescriptor, true)
+ })
+ }
+
+ private fun generateParserInterfaceList(out: TextOutput) {
+ // write classes in stable order
+ Collections.sort(jsonProtocolParserClassNames)
+
+ out.newLine().newLine().append("val PARSER_CLASSES = arrayOf(").newLine()
+ for (name in jsonProtocolParserClassNames) {
+ out.append(" ").append(name).append("::class.java")
+ if (name != jsonProtocolParserClassNames.last()) {
+ out.append(',')
+ }
+ out.newLine()
+ }
+ out.append(')')
+ }
+
+ private fun generateParserRoot(parserRootInterfaceItems: List<ParserRootInterfaceItem>, out: TextOutput) {
+ // write classes in stable order
+ Collections.sort<ParserRootInterfaceItem>(parserRootInterfaceItems)
+
+ out.newLine().newLine().append("interface ").append(READER_INTERFACE_NAME).append(" : org.jetbrains.jsonProtocol.ResponseResultReader").openBlock()
+ for (item in parserRootInterfaceItems) {
+ item.writeCode(out)
+ out.newLine()
+ }
+ out.append("override fun readResult(methodName: String, reader: org.jetbrains.io.JsonReaderEx): Any? = ")
+ out.append("when (methodName)").block {
+ for (item in parserRootInterfaceItems) {
+ out.append('"')
+ if (!item.domain.isEmpty()) {
+ out.append(item.domain).append('.')
+ }
+ out.append(item.name).append('"').append(" -> ")
+ item.appendReadMethodName(out)
+ out.append("(reader)").newLine()
+ }
+ out.append("else -> null")
+ }
+
+ out.closeBlock()
+ }
+
+ /**
+ * Resolve absolute (DOMAIN.TYPE) or relative (TYPE) type name
+ */
+ private fun resolveRefType(scopeDomainName: String, refName: String, direction: TypeData.Direction): BoxableType {
+ val pos = refName.indexOf('.')
+ val domainName: String
+ val shortName: String
+ if (pos == -1) {
+ domainName = scopeDomainName
+ shortName = refName
+ }
+ else {
+ domainName = refName.substring(0, pos)
+ shortName = refName.substring(pos + 1)
+ }
+ return typeMap.resolve(domainName, shortName, direction)!!
+ }
+}
+
+val READER_INTERFACE_NAME: String = "ProtocolResponseReader"
+
+private val INCLUDED_DOMAINS = arrayOf("CSS", "Debugger", "DOM", "Inspector", "Log", "Network", "Page", "Runtime", "ServiceWorker",
+ "Tracing", "Target", "Overlay", "Console", "DOMDebugger", "Profiler", "HeapProfiler", "NodeWorker")
+
+fun generateMethodNameSubstitute(originalName: String, out: TextOutput): String {
+ if (originalName != "this") {
+ return originalName
+ }
+ out.append("@org.jetbrains.jsonProtocol.ProtocolName(\"").append(originalName).append("\")").newLine()
+ return "get${Character.toUpperCase(originalName.get(0))}${originalName.substring(1)}"
+}
+
+fun capitalizeFirstChar(s: String): String {
+ if (!s.isEmpty() && s.get(0).isLowerCase()) {
+ return s.get(0).toUpperCase() + s.substring(1)
+ }
+ return s
+}
+
+fun <R> switchByType(typedObject: ItemDescriptor, visitor: TypeVisitor<R>): R {
+ val refName = if (typedObject is ItemDescriptor.Referenceable) typedObject.ref else null
+ if (refName != null) {
+ return visitor.visitRef(refName)
+ }
+ val typeName = typedObject.type
+ return when (typeName) {
+ BOOLEAN_TYPE -> visitor.visitBoolean()
+ STRING_TYPE -> if (typedObject.enum == null) visitor.visitString() else visitor.visitEnum(typedObject.enum!!)
+ INTEGER_TYPE, "int" -> visitor.visitInteger()
+ NUMBER_TYPE -> visitor.visitNumber()
+ ARRAY_TYPE -> visitor.visitArray(typedObject.items!!)
+ OBJECT_TYPE -> {
+ if (typedObject !is ItemDescriptor.Type) {
+ visitor.visitObject(null)
+ }
+ else {
+ val properties = typedObject.properties
+ return if (properties.isNullOrEmpty()) visitor.visitMap() else visitor.visitObject(properties)
+ }
+ }
+ ANY_TYPE, UNKNOWN_TYPE -> return visitor.visitUnknown()
+ else -> throw RuntimeException("Unrecognized type $typeName")
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/InputClassScope.kt b/platform/script-debugger/protocol/protocol-model-generator/src/InputClassScope.kt
new file mode 100644
index 00000000..a8bf4470
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/InputClassScope.kt
@@ -0,0 +1,73 @@
+package org.jetbrains.protocolModelGenerator
+
+import org.jetbrains.jsonProtocol.ItemDescriptor
+import org.jetbrains.jsonProtocol.ProtocolMetaModel
+import org.jetbrains.protocolReader.TextOutput
+
+internal class InputClassScope(generator: DomainGenerator, namePath: NamePath) : ClassScope(generator, namePath) {
+ override val typeDirection = TypeData.Direction.INPUT
+
+ fun generateDeclarationBody(out: TextOutput, list: List<ItemDescriptor.Named>) {
+ for (i in 0 until list.size) {
+ val named = list[i]
+ if (named.description != null) {
+ out.doc(named.description)
+ }
+
+ val name = named.getName()
+ val declarationName = generateMethodNameSubstitute(name, out)
+
+ if (classContextNamespace.lastComponent == "RemoteObjectValue" && name == "value") {
+ // specification says it is string, but it can be map or any other JSON too, so set Any? type
+ out.append("@org.jetbrains.jsonProtocol.JsonField(allowAnyPrimitiveValue=true)\n" +
+ " @Optional\n" +
+ " fun value(): Any?\n\n ")
+ continue
+ }
+
+ val typeDescriptor = InputMemberScope(name).resolveType(named)
+ writeMember(out, typeDescriptor, declarationName)
+ if (i != (list.size - 1)) {
+ out.newLine().newLine()
+ }
+ }
+ }
+
+ inner class InputMemberScope(memberName: String) : MemberScope(this@InputClassScope, memberName) {
+ override fun generateNestedObject(description: String?, properties: List<ProtocolMetaModel.ObjectProperty>?): BoxableType {
+ val objectName = capitalizeFirstChar(memberName)
+ addMember { out ->
+ out.newLine().newLine().doc(description)
+ if (properties == null) {
+ out.append("interface ").append(objectName).append(" : JsonObjectBased").openBlock()
+ }
+ else {
+ out.append("@JsonType").newLine()
+ out.append("interface ").append(objectName).openBlock()
+ for (property in properties) {
+ out.doc(property.description)
+
+ val methodName = generateMethodNameSubstitute(property.getName(), out)
+ val memberScope = InputMemberScope(property.getName())
+ val typeDescriptor = memberScope.resolveType(property)
+ writeMember(out, typeDescriptor, methodName)
+ }
+ }
+ out.closeBlock()
+ }
+ return subMessageType(NamePath(objectName, classContextNamespace))
+ }
+ }
+
+ private fun writeMember(out: TextOutput, typeDescriptor: TypeDescriptor, name: String) {
+ typeDescriptor.writeAnnotations(out)
+ val asProperty = typeDescriptor.isPrimitive || typeDescriptor.isNullableType
+ out.append(if (asProperty) "val" else "fun")
+ out.append(" ").appendEscapedName(name)
+ out.append(if (asProperty) ": " else "(): ")
+ out.append(typeDescriptor.type.getShortText(classContextNamespace))
+ if (typeDescriptor.isNullableType) {
+ out.append('?')
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/ListType.kt b/platform/script-debugger/protocol/protocol-model-generator/src/ListType.kt
new file mode 100644
index 00000000..24c3d3d6
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/ListType.kt
@@ -0,0 +1,31 @@
+package org.jetbrains.protocolModelGenerator
+
+open class ListType(private val itemType: BoxableType) : BoxableType {
+ override val defaultValue: Nothing? = null
+
+ override val writeMethodName: String
+ get() = when {
+ itemType == BoxableType.STRING -> "writeStringList"
+ itemType == BoxableType.LONG -> "writeLongArray"
+ itemType == BoxableType.INT -> "writeIntArray"
+ itemType == BoxableType.NUMBER -> "writeDoubleArray"
+ itemType == BoxableType.NUMBER -> "writeDoubleArray"
+ itemType is StandaloneType && itemType.writeMethodName == "writeEnum" -> "writeEnumList"
+ else -> "writeList"
+ }
+
+ override val fullText: CharSequence
+ get() {
+ if (itemType == BoxableType.LONG || itemType == BoxableType.INT || itemType == BoxableType.NUMBER) {
+ return "Array<${itemType.fullText}>"
+ }
+ return "List<${itemType.fullText}>"
+ }
+
+ override fun getShortText(contextNamespace: NamePath): String {
+ if (itemType == BoxableType.LONG || itemType == BoxableType.INT || itemType == BoxableType.NUMBER) {
+ return "${itemType.fullText}Array"
+ }
+ return "List<${itemType.getShortText(contextNamespace)}>"
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/MemberScope.kt b/platform/script-debugger/protocol/protocol-model-generator/src/MemberScope.kt
new file mode 100644
index 00000000..44c8de3e
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/MemberScope.kt
@@ -0,0 +1,37 @@
+package org.jetbrains.protocolModelGenerator
+
+import com.intellij.openapi.util.text.StringUtil
+import org.jetbrains.jsonProtocol.ItemDescriptor
+import org.jetbrains.protocolReader.appendEnums
+
+/**
+ * Member scope is used to generate additional types that are used only from method.
+ * These types will be named after this method.
+ */
+internal open class MemberScope(private val classScope: ClassScope, protected val memberName: String) : ResolveAndGenerateScope {
+ override fun <T : ItemDescriptor> resolveType(typedObject: T) = classScope.generator.generator.resolveType(typedObject, this)
+
+ fun generateEnum(description: String?, enumConstants: List<String>): BoxableType {
+ var enumName = capitalizeFirstChar(memberName)
+ if (StringUtil.equalsIgnoreCase(enumName, "TYPE") &&
+ classScope.classContextNamespace.lastComponent.endsWith("EventData") ) {
+ // to avoid same name with companion object TYPE
+ enumName = "EventType"
+ }
+ val namePath = NamePath(enumName, classScope.classContextNamespace)
+ var type = classScope.generator.generator.nestedTypeMap.get(namePath)
+ if (type == null) {
+ type = StandaloneType(namePath, "writeEnum")
+ classScope.generator.generator.nestedTypeMap.put(namePath, type)
+ classScope.addMember { out ->
+ out.newLine().doc(description)
+ appendEnums(enumConstants, enumName, classScope.typeDirection == TypeData.Direction.INPUT, out)
+ }
+ }
+ return type
+ }
+
+ override fun getDomainName() = classScope.generator.domain.domain()
+
+ override fun getTypeDirection() = classScope.typeDirection
+}
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/MyCreateStandaloneTypeBindingVisitorBase.kt b/platform/script-debugger/protocol/protocol-model-generator/src/MyCreateStandaloneTypeBindingVisitorBase.kt
new file mode 100644
index 00000000..1dcbe8f0
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/MyCreateStandaloneTypeBindingVisitorBase.kt
@@ -0,0 +1,40 @@
+package org.jetbrains.protocolModelGenerator
+
+import org.jetbrains.jsonProtocol.ProtocolMetaModel
+import org.jetbrains.protocolReader.appendEnums
+
+internal class MyCreateStandaloneTypeBindingVisitorBase(private val generator: DomainGenerator, type: ProtocolMetaModel.StandaloneType, private val name: String) : CreateStandaloneTypeBindingVisitorBase(generator, type) {
+ override fun visitObject(properties: List<ProtocolMetaModel.ObjectProperty>?): StandaloneTypeBinding {
+ return object : StandaloneTypeBinding {
+ override fun getJavaType() = subMessageType(generator.generator.naming.additionalParam.getFullName(generator.domain.domain(), name))
+
+ override fun generate() = generator.generateCommandAdditionalParam(type)
+
+ override fun getDirection() = TypeData.Direction.OUTPUT
+ }
+ }
+
+ override fun visitEnum(enumConstants: List<String>): StandaloneTypeBinding {
+ return object : StandaloneTypeBinding {
+ override fun getJavaType(): BoxableType = StandaloneType(generator.generator.naming.additionalParam.getFullName(generator.domain.domain(), name), "writeEnum")
+
+ override fun generate() = appendEnums(enumConstants, name, false, generator.fileUpdater.out.newLine().newLine())
+
+ override fun getDirection() = TypeData.Direction.OUTPUT
+ }
+ }
+
+ override fun visitArray(items: ProtocolMetaModel.ArrayItemType) = generator.createTypedefTypeBinding(type, object : Target {
+ override fun resolve(context: Target.ResolveContext): BoxableType {
+ return ListType(generator.generator.resolveType(items, object : ResolveAndGenerateScope {
+ // This class is responsible for generating ad hoc type.
+ // If we ever are to do it, we should generate into string buffer and put strings inside TypeDef class
+ override fun getDomainName() = generator.domain.domain()
+
+ override fun getTypeDirection() = TypeData.Direction.OUTPUT
+
+ override fun generateNestedObject(description: String?, properties: List<ProtocolMetaModel.ObjectProperty>?) = context.generateNestedObject("Item", description, properties)
+ }).type)
+ }
+ }, generator.generator.naming.outputTypedef, TypeData.Direction.OUTPUT)
+}
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/NamePath.kt b/platform/script-debugger/protocol/protocol-model-generator/src/NamePath.kt
new file mode 100644
index 00000000..aab52a4f
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/NamePath.kt
@@ -0,0 +1,29 @@
+package org.jetbrains.protocolModelGenerator
+
+data class NamePath(val lastComponent: String, val parent: NamePath? = null) {
+ fun getLength(): Int {
+ var res = 1
+ run {
+ var current: NamePath? = this
+ while (current != null) {
+ res++
+ current = current.parent
+ }
+ }
+ return res
+ }
+
+ fun getFullText(): String {
+ val result = StringBuilder()
+ fillFullPath(result)
+ return result.toString()
+ }
+
+ private fun fillFullPath(result: StringBuilder) {
+ if (parent != null) {
+ parent.fillFullPath(result)
+ result.append('.')
+ }
+ result.append(lastComponent)
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/OutputClassScope.kt b/platform/script-debugger/protocol/protocol-model-generator/src/OutputClassScope.kt
new file mode 100644
index 00000000..55d6f089
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/OutputClassScope.kt
@@ -0,0 +1,61 @@
+package org.jetbrains.protocolModelGenerator
+
+import org.jetbrains.jsonProtocol.ItemDescriptor
+import org.jetbrains.jsonProtocol.ProtocolMetaModel
+import org.jetbrains.protocolReader.TextOutput
+
+internal class OutputClassScope(generator: DomainGenerator, classNamePath: NamePath) : ClassScope(generator, classNamePath) {
+ fun <P : ItemDescriptor.Named> writeWriteCalls(out: TextOutput, parameters: List<Pair<P, BoxableType>>, qualifier: String?) {
+ for ((descriptor, type) in parameters) {
+ out.newLine()
+ if (qualifier != null) {
+ out.append(qualifier).append('.')
+ }
+ appendWriteValueInvocation(out, descriptor, descriptor.name(), type)
+ }
+ }
+
+ fun <P : ItemDescriptor.Named> writeMethodParameters(out: TextOutput, parameters: List<Pair<P, BoxableType>>, prependComma: Boolean) {
+ var needComa = prependComma
+ for ((descriptor, type) in parameters) {
+ if (needComa) {
+ out.comma()
+ }
+ else {
+ needComa = true
+ }
+
+ val shortText = type.getShortText(classContextNamespace)
+ out.append(descriptor.name()).append(": ")
+ out.append(if (shortText == "String") "CharSequence" else shortText)
+ if (descriptor.optional) {
+ val defaultValue: String
+ if (descriptor is ProtocolMetaModel.Parameter && descriptor.default != null) {
+ defaultValue = descriptor.default!!
+ }
+ else {
+ defaultValue = type.defaultValue ?: "null"
+ if (defaultValue == "null") {
+ out.append('?')
+ }
+ }
+ out.append(" = ").append(defaultValue)
+ }
+ }
+ }
+
+ private fun appendWriteValueInvocation(out: TextOutput, descriptor: ItemDescriptor.Named, valueRefName: String, type: BoxableType) {
+ // todo CallArgument (we should allow write null as value)
+ val allowNullableString = descriptor.name() == "value" && type.writeMethodName == "writeString"
+ out.append(if (allowNullableString) "writeNullableString" else type.writeMethodName).append('(')
+ out.quote(descriptor.name()).comma().append(valueRefName)
+
+ if (!allowNullableString && descriptor.optional && type.defaultValue != null && type != BoxableType.MAP) {
+ out.comma().append(type.defaultValue!!)
+ }
+
+ out.append(')')
+ }
+
+ override val typeDirection = TypeData.Direction.OUTPUT
+}
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/ParserRootInterfaceItem.kt b/platform/script-debugger/protocol/protocol-model-generator/src/ParserRootInterfaceItem.kt
new file mode 100644
index 00000000..3854bb0e
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/ParserRootInterfaceItem.kt
@@ -0,0 +1,27 @@
+package org.jetbrains.protocolModelGenerator
+
+import org.jetbrains.protocolReader.JSON_READER_PARAMETER_DEF
+import org.jetbrains.protocolReader.TextOutput
+
+class ParserRootInterfaceItem(val domain: String, val name: String, private val nameScheme: ClassNameScheme.Input) : Comparable<ParserRootInterfaceItem> {
+ val fullName: String
+
+ init {
+ fullName = nameScheme.getFullName(domain, name).getFullText()
+ }
+
+ fun writeCode(out: TextOutput) {
+ out.append("@JsonParseMethod").newLine()
+ out.append("fun ")
+ appendReadMethodName(out)
+ out.append('(').append(JSON_READER_PARAMETER_DEF).append("): ").append(fullName).newLine()
+ }
+
+ fun appendReadMethodName(out: TextOutput) {
+ out.append(nameScheme.getParseMethodName(domain, name))
+ }
+
+ override fun compareTo(other: ParserRootInterfaceItem): Int {
+ return fullName.compareTo(other.fullName)
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/ResolveAndGenerateScope.kt b/platform/script-debugger/protocol/protocol-model-generator/src/ResolveAndGenerateScope.kt
new file mode 100644
index 00000000..87042eb3
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/ResolveAndGenerateScope.kt
@@ -0,0 +1,13 @@
+package org.jetbrains.protocolModelGenerator
+
+import org.jetbrains.jsonProtocol.ItemDescriptor
+import org.jetbrains.jsonProtocol.ProtocolMetaModel
+
+internal interface ResolveAndGenerateScope {
+ fun getDomainName(): String
+ fun getTypeDirection(): TypeData.Direction
+
+ fun <T : ItemDescriptor> resolveType(typedObject: T): TypeDescriptor = throw UnsupportedOperationException()
+
+ open fun generateNestedObject(description: String?, properties: List<ProtocolMetaModel.ObjectProperty>?): BoxableType = throw UnsupportedOperationException()
+}
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/StandaloneType.kt b/platform/script-debugger/protocol/protocol-model-generator/src/StandaloneType.kt
new file mode 100644
index 00000000..76964aab
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/StandaloneType.kt
@@ -0,0 +1,49 @@
+package org.jetbrains.protocolModelGenerator
+
+class StandaloneType(private val namePath: NamePath, override val writeMethodName: String, override val defaultValue: String? = "null") : BoxableType {
+ override val fullText: String = namePath.getFullText()
+
+ override fun getShortText(contextNamespace: NamePath): String {
+ val nameLength = namePath.getLength()
+ val contextLength = contextNamespace.getLength()
+ if (nameLength > contextLength) {
+ val builder = subtractContextRecursively(namePath, nameLength - contextLength, contextNamespace)
+ if (builder != null) {
+ return builder.toString()
+ }
+ }
+ return namePath.getFullText()
+ }
+
+ private fun subtractContextRecursively(namePos: NamePath?, count: Int, prefix: NamePath): StringBuilder? {
+ if (count > 1) {
+ val result = subtractContextRecursively(namePos!!.parent, count - 1, prefix) ?: return null
+ result.append('.')
+ result.append(namePos.lastComponent)
+ return result
+ }
+ else {
+ var namePos = namePos
+ var prefix = prefix
+ val nameComponent = namePos!!.lastComponent
+ namePos = namePos.parent
+ do {
+ if (namePos!!.lastComponent != prefix.lastComponent) {
+ return null
+ }
+
+ namePos = namePos.parent
+ if (namePos == null) {
+ break
+ }
+
+ prefix = prefix.parent!!
+ }
+ while (true)
+
+ val result = StringBuilder()
+ result.append(nameComponent)
+ return result
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/StandaloneTypeBinding.kt b/platform/script-debugger/protocol/protocol-model-generator/src/StandaloneTypeBinding.kt
new file mode 100644
index 00000000..1754c339
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/StandaloneTypeBinding.kt
@@ -0,0 +1,33 @@
+package org.jetbrains.protocolModelGenerator
+
+import org.jetbrains.jsonProtocol.ProtocolMetaModel
+
+internal interface StandaloneTypeBinding {
+ fun getJavaType(): BoxableType
+
+ fun generate()
+
+ /**
+ * @return null if not direction-specific
+ */
+ fun getDirection(): TypeData.Direction?
+}
+
+internal interface Target {
+ fun resolve(context: ResolveContext): BoxableType
+
+ interface ResolveContext {
+ fun generateNestedObject(shortName: String, description: String?, properties: List<ProtocolMetaModel.ObjectProperty>?): BoxableType
+ }
+}
+
+internal class PredefinedTarget(private val resolvedType: BoxableType) : Target {
+ override fun resolve(context: Target.ResolveContext) = resolvedType
+
+ companion object {
+ val STRING = PredefinedTarget(BoxableType.STRING)
+ val INT = PredefinedTarget(BoxableType.INT)
+ val NUMBER = PredefinedTarget(BoxableType.NUMBER)
+ val MAP = PredefinedTarget(BoxableType.MAP)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/TypeData.kt b/platform/script-debugger/protocol/protocol-model-generator/src/TypeData.kt
new file mode 100644
index 00000000..934309b5
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/TypeData.kt
@@ -0,0 +1,104 @@
+package org.jetbrains.protocolModelGenerator
+
+import org.jetbrains.jsonProtocol.ProtocolMetaModel
+
+internal val ANY = object : StandaloneTypeBinding {
+ override fun getJavaType() = BoxableType.ANY_STRING
+
+ override fun generate() {
+ }
+
+ override fun getDirection() = null
+}
+
+internal class TypeData(private val name: String) {
+ val input by lazy { Input() }
+
+ val output by lazy { Output() }
+
+ internal var type: ProtocolMetaModel.StandaloneType? = null
+
+ private var commonBinding: StandaloneTypeBinding? = null
+
+ enum class Direction {
+ INPUT {
+ override fun get(typeData: TypeData) = typeData.input
+ },
+ OUTPUT {
+ override fun get(typeData: TypeData) = typeData.output
+ };
+
+ abstract fun get(typeData: TypeData): TypeRef
+ }
+
+ abstract inner class TypeRef {
+ private var oneDirectionBinding: StandaloneTypeBinding? = null
+
+ fun resolve(typeMap: TypeMap, domainGenerator: DomainGenerator): BoxableType? {
+ if (commonBinding != null) {
+ return commonBinding!!.getJavaType()
+ }
+ if (oneDirectionBinding != null) {
+ return oneDirectionBinding!!.getJavaType()
+ }
+ val binding = resolveImpl(domainGenerator) ?: return null
+
+ if (binding.getDirection() == null) {
+ commonBinding = binding
+ }
+ else {
+ oneDirectionBinding = binding
+ }
+ typeMap.addTypeToGenerate(binding)
+ return binding.getJavaType()
+ }
+
+ abstract fun resolveImpl(domainGenerator: DomainGenerator): StandaloneTypeBinding?
+ }
+
+ inner class Output : TypeRef() {
+ override fun resolveImpl(domainGenerator: DomainGenerator): StandaloneTypeBinding? {
+ if (type == null) {
+ if (name == "int") {
+ return object : StandaloneTypeBinding {
+ override fun getJavaType() = BoxableType.INT
+
+ override fun generate() {
+ }
+
+ override fun getDirection() = null
+ }
+ }
+ else if (name == "any") {
+ return ANY
+ }
+
+ throw RuntimeException()
+ }
+ return domainGenerator.createStandaloneOutputTypeBinding(type!!, name)
+ }
+ }
+
+ inner class Input : TypeRef() {
+ override fun resolveImpl(domainGenerator: DomainGenerator): StandaloneTypeBinding? {
+ if (type == null) {
+ if (name == "int") {
+ return object : StandaloneTypeBinding {
+ override fun getJavaType() = BoxableType.INT
+
+ override fun generate() {
+ }
+
+ override fun getDirection() = null
+ }
+ }
+ else if (name == "any") {
+ return ANY
+ }
+
+ throw RuntimeException()
+ }
+ return domainGenerator.createStandaloneInputTypeBinding(type!!)
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/TypeDescriptor.kt b/platform/script-debugger/protocol/protocol-model-generator/src/TypeDescriptor.kt
new file mode 100644
index 00000000..13d7b28c
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/TypeDescriptor.kt
@@ -0,0 +1,30 @@
+package org.jetbrains.protocolModelGenerator
+
+import org.jetbrains.jsonProtocol.ItemDescriptor
+import org.jetbrains.jsonProtocol.ProtocolMetaModel
+import org.jetbrains.protocolReader.TextOutput
+
+internal class TypeDescriptor(val type: BoxableType, val descriptor: ItemDescriptor, private val asRawString: Boolean = false) {
+ private val optional = descriptor is ItemDescriptor.Named && descriptor.optional
+
+ fun writeAnnotations(out: TextOutput) {
+ val default = (descriptor as? ProtocolMetaModel.Parameter)?.default
+ if (default != null) {
+ out.append("@Optional").append("(\"").append(default).append("\")").newLine()
+ }
+
+ if (asRawString) {
+ out.append("@org.jetbrains.jsonProtocol.JsonField(")
+ if (asRawString) {
+ out.append("allowAnyPrimitiveValue=true")
+ }
+ out.append(")").newLine()
+ }
+ }
+
+ val isNullableType: Boolean
+ get() = !isPrimitive && (optional || asRawString)
+
+ val isPrimitive: Boolean
+ get() = type == BoxableType.BOOLEAN || type == BoxableType.INT || type == BoxableType.LONG || type == BoxableType.NUMBER
+}
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/TypeMap.kt b/platform/script-debugger/protocol/protocol-model-generator/src/TypeMap.kt
new file mode 100644
index 00000000..662b545e
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/TypeMap.kt
@@ -0,0 +1,50 @@
+package org.jetbrains.protocolModelGenerator
+
+import gnu.trove.THashMap
+import java.util.*
+
+/**
+ * Keeps track of all referenced types.
+ * A type may be used and resolved (generated or hard-coded).
+ */
+internal class TypeMap {
+ private val map = THashMap<Pair<String, String>, TypeData>()
+
+ var domainGeneratorMap: Map<String, DomainGenerator>? = null
+
+ private val typesToGenerate = ArrayDeque<StandaloneTypeBinding>()
+
+ fun resolve(domainName: String, typeName: String, direction: TypeData.Direction): BoxableType? {
+ val domainGenerator = domainGeneratorMap!!.get(domainName)
+ if (domainGenerator == null) {
+ val qName = "$domainName.$typeName";
+ if (qName == "IO.StreamHandle" ||
+ qName == "Security.SecurityState" ||
+ qName == "Security.CertificateId" ||
+ qName == "Emulation.ScreenOrientation" ||
+ qName == "Security.MixedContentType"
+ ) {
+ return BoxableType.ANY_STRING // ignore
+ }
+ throw RuntimeException("Failed to find domain generator: $domainName for type $typeName")
+ }
+ return direction.get(getTypeData(domainName, typeName)).resolve(this, domainGenerator)
+ }
+
+ fun addTypeToGenerate(binding: StandaloneTypeBinding) {
+ typesToGenerate.offer(binding)
+ }
+
+ fun generateRequestedTypes() {
+ // size may grow during iteration
+ val createdTypes = HashSet<CharSequence>()
+ while (typesToGenerate.isNotEmpty()) {
+ val binding = typesToGenerate.poll()
+ if (createdTypes.add(binding.getJavaType().fullText)) {
+ binding.generate()
+ }
+ }
+ }
+
+ fun getTypeData(domainName: String, typeName: String) = map.getOrPut(Pair(domainName, typeName)) { TypeData(typeName) }
+}
diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/TypeVisitor.kt b/platform/script-debugger/protocol/protocol-model-generator/src/TypeVisitor.kt
new file mode 100644
index 00000000..87531bb2
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-model-generator/src/TypeVisitor.kt
@@ -0,0 +1,25 @@
+package org.jetbrains.protocolModelGenerator
+
+import org.jetbrains.jsonProtocol.ProtocolMetaModel
+
+interface TypeVisitor<R> {
+ fun visitRef(refName: String): R
+
+ fun visitBoolean(): R
+
+ fun visitEnum(enumConstants: List<String>): R
+
+ fun visitString(): R
+
+ fun visitInteger(): R
+
+ fun visitNumber(): R
+
+ fun visitArray(items: ProtocolMetaModel.ArrayItemType): R
+
+ fun visitObject(properties: List<ProtocolMetaModel.ObjectProperty>?): R
+
+ fun visitMap(): R
+
+ fun visitUnknown(): R
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/intellij.platform.scriptDebugger.protocolReaderRuntime.iml b/platform/script-debugger/protocol/protocol-reader-runtime/intellij.platform.scriptDebugger.protocolReaderRuntime.iml
new file mode 100644
index 00000000..20648d86
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/intellij.platform.scriptDebugger.protocolReaderRuntime.iml
@@ -0,0 +1,15 @@
+<?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" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" exported="" name="gson" level="project" />
+ <orderEntry type="module" module-name="intellij.platform.util" />
+ <orderEntry type="module" module-name="intellij.platform.ide.impl" />
+ <orderEntry type="library" name="netty-codec-http" level="project" />
+ </component>
+</module> \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/annotations.kt b/platform/script-debugger/protocol/protocol-reader-runtime/src/annotations.kt
new file mode 100644
index 00000000..33667d71
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/annotations.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.jsonProtocol
+
+@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
+annotation class JsonField(
+ val allowAnyPrimitiveValue: Boolean = false, // read any primitive value as String (true as true, number as string - don't try to parse)
+ val allowAnyPrimitiveValueAndMap: Boolean = false,
+ val primitiveValue: String = "")
+
+@Target(AnnotationTarget.CLASS)
+annotation class JsonType()
+
+@Target(AnnotationTarget.FUNCTION)
+annotation class JsonSubtypeCasting(val reinterpret: Boolean = false)
+
+@Target(AnnotationTarget.FUNCTION)
+annotation class JsonParseMethod
+
+/**
+ * For field-reading method specifies that the field is optional and may safely be absent in
+ * JSON object. By default fields are not optional.
+ */
+@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
+annotation class Optional(val default: String = "")
+
+@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
+annotation class ProtocolName(val name: String) \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/EventMap.kt b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/EventMap.kt
new file mode 100644
index 00000000..a2fad496
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/EventMap.kt
@@ -0,0 +1,50 @@
+// 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.jsonProtocol
+
+import com.intellij.util.containers.ContainerUtil
+import org.jetbrains.io.JsonReaderEx
+
+abstract class EventType<T, R : ResponseResultReader>(val methodName: String) {
+ abstract fun read(protocolReader: R, reader: JsonReaderEx): T
+}
+
+class EventMap<R : ResponseResultReader>(private val protocolReader: R) {
+ private val nameToHandler = ContainerUtil.newConcurrentMap<String, MutableList<(Any?) -> Unit>>()
+ private val nameToType = ContainerUtil.newConcurrentMap<String, EventType<*, R>>()
+
+ fun <T : Any?> add(type: EventType<T, R>, handler: (T) -> Unit) {
+ nameToType.put(type.methodName, type)
+ @Suppress("UNCHECKED_CAST")
+ nameToHandler.getOrPut(type.methodName, { ContainerUtil.createLockFreeCopyOnWriteList() }).add(handler as (Any?) -> Unit)
+ }
+
+ fun <T : Any?> remove(type: EventType<T, R>, handler: (T) -> Unit) {
+ @Suppress("UNCHECKED_CAST")
+ nameToHandler[type.methodName]?.remove(handler as (Any?) -> Unit)
+ }
+
+ fun <T> addMulti(vararg types: EventType<out T, R>, eventHandler: (T) -> Unit) {
+ for (type in types) {
+ add(type, eventHandler)
+ }
+ }
+
+ fun handleEvent(method: String, data: JsonReaderEx?) {
+ val handlers = nameToHandler.get(method)
+ if (handlers == null || handlers.isEmpty()) {
+ return
+ }
+
+ val eventData = data?.let { nameToType[method]!!.read(protocolReader, it) }
+ for (handler in handlers) {
+ handler(eventData)
+ }
+ }
+
+ fun <T : Any?> handleEvent(type: EventType<T, R>, event: T) {
+ val handlers = nameToHandler.get(type.methodName) ?: return
+ for (handler in handlers) {
+ handler(event)
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonObjectBased.java b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonObjectBased.java
new file mode 100644
index 00000000..99fe39c8
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonObjectBased.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.jetbrains.jsonProtocol;
+
+import org.jetbrains.io.JsonReaderEx;
+
+/**
+ * Optional base interface for JSON type interface. Underlying JSON object becomes available
+ * to user this way.
+ */
+public interface JsonObjectBased {
+ JsonReaderEx getDeferredReader();
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonReaders.java b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonReaders.java
new file mode 100644
index 00000000..a20a2f48
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonReaders.java
@@ -0,0 +1,291 @@
+package org.jetbrains.jsonProtocol;
+
+import com.google.gson.stream.JsonToken;
+import com.intellij.util.ArrayUtilRt;
+import gnu.trove.TDoubleArrayList;
+import gnu.trove.THashMap;
+import gnu.trove.TIntArrayList;
+import gnu.trove.TLongArrayList;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.io.JsonReaderEx;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public final class JsonReaders {
+ public static final ObjectFactory<String> STRING_OBJECT_FACTORY = new ObjectFactory<String>() {
+ @Override
+ public String read(JsonReaderEx reader) {
+ return reader.nextString();
+ }
+ };
+
+ private JsonReaders() {
+ }
+
+ public static <T> ObjectFactory<Map<String, T>> mapFactory(@NotNull ObjectFactory<? extends T> valueFactory) {
+ return new MapFactory<>(valueFactory);
+ }
+
+ private static void checkIsNull(JsonReaderEx reader, String fieldName) {
+ if (reader.peek() == JsonToken.NULL) {
+ throw new RuntimeException("Field is not nullable" + (fieldName == null ? "" : (": " + fieldName)));
+ }
+ }
+
+ public static String readRawString(JsonReaderEx reader) {
+ return reader.nextString(true);
+ }
+
+ public static Object readRawStringOrMap(JsonReaderEx reader) {
+ if (reader.peek() == JsonToken.BEGIN_OBJECT) {
+ return readMap(reader, null);
+ }
+ else {
+ return reader.nextString(true);
+ }
+ }
+
+ // Don't use Guava CaseFormat.*! ObjectWithURL must be converted to OBJECT_WITH_URL
+ public static String convertRawEnumName(@NotNull String enumValue) {
+ int n = enumValue.length();
+ StringBuilder builder = new StringBuilder(n + 4);
+ boolean prevIsLowerCase = false;
+ for (int i = 0; i < n; i++) {
+ char c = enumValue.charAt(i);
+ if (c == '-' || c == ' ') {
+ builder.append('_');
+ continue;
+ }
+
+ if (Character.isUpperCase(c)) {
+ // second check handle "CSPViolation" (transform to CSP_VIOLATION)
+ if (prevIsLowerCase || (i != 0 && (i + 1) < n && Character.isLowerCase(enumValue.charAt(i + 1)))) {
+ builder.append('_');
+ }
+ builder.append(c);
+ prevIsLowerCase = false;
+ }
+ else {
+ builder.append(Character.toUpperCase(c));
+ prevIsLowerCase = true;
+ }
+ }
+ return builder.toString();
+ }
+
+ public static <T extends Enum<T>> T readEnum(@NotNull JsonReaderEx reader, @NotNull Class<T> enumClass) {
+ if (reader.peek() == JsonToken.NULL) {
+ reader.skipValue();
+ return null;
+ }
+
+ try {
+ return Enum.valueOf(enumClass, convertRawEnumName(reader.nextString()));
+ }
+ catch (IllegalArgumentException ignored) {
+ return Enum.valueOf(enumClass, "NO_ENUM_CONST");
+ }
+ }
+
+ public static <T> List<T> readObjectArray(@NotNull JsonReaderEx reader, @NotNull ObjectFactory<? extends T> factory) {
+ if (reader.peek() == JsonToken.NULL) {
+ reader.skipValue();
+ return null;
+ }
+
+ reader.beginArray();
+ if (!reader.hasNext()) {
+ reader.endArray();
+ return Collections.emptyList();
+ }
+
+ List<T> result = new ArrayList<>();
+ do {
+ result.add(factory.read(reader));
+ }
+ while (reader.hasNext());
+ reader.endArray();
+ return result;
+ }
+
+ public static <T> Map<String, T> readMap(@NotNull JsonReaderEx reader, @Nullable ObjectFactory<? extends T> factory) {
+ if (reader.peek() == JsonToken.NULL) {
+ reader.skipValue();
+ return null;
+ }
+
+ reader.beginObject();
+ if (!reader.hasNext()) {
+ reader.endObject();
+ return Collections.emptyMap();
+ }
+
+ Map<String, T> map = new THashMap<>();
+ while (reader.hasNext()) {
+ if (factory == null) {
+ //noinspection unchecked
+ map.put(reader.nextName(), (T)read(reader));
+ }
+ else {
+ map.put(reader.nextName(), factory.read(reader));
+ }
+ }
+ reader.endObject();
+ return map;
+ }
+
+ public static Object read(JsonReaderEx reader) {
+ switch (reader.peek()) {
+ case BEGIN_ARRAY:
+ return nextList(reader);
+
+ case BEGIN_OBJECT:
+ reader.beginObject();
+ return nextObject(reader);
+
+ case STRING:
+ return reader.nextString();
+
+ case NUMBER:
+ return reader.nextDouble();
+
+ case BOOLEAN:
+ return reader.nextBoolean();
+
+ case NULL:
+ reader.nextNull();
+ return null;
+
+ default: throw new IllegalStateException();
+ }
+ }
+
+ public static Map<String, Object> nextObject(JsonReaderEx reader) {
+ Map<String, Object> map = new THashMap<>();
+ while (reader.hasNext()) {
+ map.put(reader.nextName(), read(reader));
+ }
+ reader.endObject();
+ return map;
+ }
+
+ public static <T> List<T> nextList(JsonReaderEx reader) {
+ reader.beginArray();
+ if (!reader.hasNext()) {
+ reader.endArray();
+ return Collections.emptyList();
+ }
+
+ List<T> list = new ArrayList<>();
+ do {
+ //noinspection unchecked
+ list.add((T)read(reader));
+ }
+ while (reader.hasNext());
+ reader.endArray();
+ return list;
+ }
+
+ public static List<String> readRawStringArray(JsonReaderEx reader) {
+ reader.beginArray();
+ if (!reader.hasNext()) {
+ reader.endArray();
+ return Collections.emptyList();
+ }
+
+ List<String> list = new ArrayList<>();
+ do {
+ list.add(reader.nextString(true));
+ }
+ while (reader.hasNext());
+ reader.endArray();
+ return list;
+ }
+
+ public static long[] readLongArray(JsonReaderEx reader) {
+ checkIsNull(reader, null);
+ reader.beginArray();
+ if (!reader.hasNext()) {
+ reader.endArray();
+ return ArrayUtilRt.EMPTY_LONG_ARRAY;
+ }
+
+ TLongArrayList result = new TLongArrayList();
+ do {
+ result.add(reader.nextLong());
+ }
+ while (reader.hasNext());
+ reader.endArray();
+ return result.toNativeArray();
+ }
+
+ public static double[] readDoubleArray(JsonReaderEx reader) {
+ checkIsNull(reader, null);
+ reader.beginArray();
+ if (!reader.hasNext()) {
+ reader.endArray();
+ return new double[]{0};
+ }
+
+ TDoubleArrayList result = new TDoubleArrayList();
+ do {
+ result.add(reader.nextDouble());
+ }
+ while (reader.hasNext());
+ reader.endArray();
+ return result.toNativeArray();
+ }
+
+ public static int[] readIntArray(JsonReaderEx reader) {
+ checkIsNull(reader, null);
+ reader.beginArray();
+ if (!reader.hasNext()) {
+ reader.endArray();
+ return ArrayUtilRt.EMPTY_INT_ARRAY;
+ }
+
+ TIntArrayList result = new TIntArrayList();
+ do {
+ result.add(reader.nextInt());
+ }
+ while (reader.hasNext());
+ reader.endArray();
+ return result.toNativeArray();
+ }
+
+ public static List<StringIntPair> readIntStringPairs(JsonReaderEx reader) {
+ checkIsNull(reader, null);
+ reader.beginArray();
+ if (!reader.hasNext()) {
+ reader.endArray();
+ return Collections.emptyList();
+ }
+
+ List<StringIntPair> result = new ArrayList<>();
+ do {
+ reader.beginArray();
+ result.add(new StringIntPair(reader.nextInt(), reader.nextString()));
+ reader.endArray();
+ }
+ while (reader.hasNext());
+ reader.endArray();
+ return result;
+ }
+
+ public static boolean findBooleanField(String name, JsonReaderEx reader) {
+ reader.beginObject();
+ while (reader.hasNext()) {
+ if (reader.nextName().equals(name)) {
+ return reader.nextBoolean();
+ }
+ else {
+ reader.skipValue();
+ }
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonSubtype.java b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonSubtype.java
new file mode 100644
index 00000000..23879bbf
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonSubtype.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.jetbrains.jsonProtocol;
+
+/**
+ * A base interface for JSON subtype interface. This inheritance serves 2 purposes:
+ * it declares base type (visible to human and to interface analyzer) and adds {@link #getBase()}
+ * getter that may be directly used in programs.
+ */
+public interface JsonSubtype<BASE> {
+ BASE getBase();
+}
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonWriters.kt b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonWriters.kt
new file mode 100644
index 00000000..cbdd2a4c
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/JsonWriters.kt
@@ -0,0 +1,21 @@
+package org.jetbrains.jsonProtocol
+
+import com.google.gson.stream.JsonWriter
+import java.lang.reflect.Method
+
+object JsonWriters {
+ val JSON_WRITE_DEFERRED_NAME: Method
+
+ init {
+ JSON_WRITE_DEFERRED_NAME = JsonWriter::class.java.getDeclaredMethod("writeDeferredName")
+ JSON_WRITE_DEFERRED_NAME.isAccessible = true
+ }
+
+ fun writeStringList(writer: JsonWriter, name: String, value: Collection<String>) {
+ writer.name(name).beginArray()
+ for (item in value) {
+ writer.value(item)
+ }
+ writer.endArray()
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/MapFactory.java b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/MapFactory.java
new file mode 100644
index 00000000..d602df9e
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/MapFactory.java
@@ -0,0 +1,19 @@
+package org.jetbrains.jsonProtocol;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.io.JsonReaderEx;
+
+import java.util.Map;
+
+final class MapFactory<T> extends ObjectFactory<Map<String, T>> {
+ private final ObjectFactory<? extends T> valueFactory;
+
+ MapFactory(@NotNull ObjectFactory<? extends T> valueFactory) {
+ this.valueFactory = valueFactory;
+ }
+
+ @Override
+ public Map<String, T> read(JsonReaderEx reader) {
+ return JsonReaders.readMap(reader, valueFactory);
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/ObjectFactory.java b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/ObjectFactory.java
new file mode 100644
index 00000000..2676fd4e
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/ObjectFactory.java
@@ -0,0 +1,7 @@
+package org.jetbrains.jsonProtocol;
+
+import org.jetbrains.io.JsonReaderEx;
+
+public abstract class ObjectFactory<T> {
+ public abstract T read(JsonReaderEx reader);
+}
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/OutMessage.kt b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/OutMessage.kt
new file mode 100644
index 00000000..b8fea3b0
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/OutMessage.kt
@@ -0,0 +1,276 @@
+/*
+ * 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.jsonProtocol
+
+import com.google.gson.stream.JsonWriter
+import com.intellij.openapi.vfs.CharsetToolkit
+import com.intellij.util.containers.isNullOrEmpty
+import com.intellij.util.io.writeUtf8
+import gnu.trove.TIntArrayList
+import gnu.trove.TIntHashSet
+import io.netty.buffer.ByteBuf
+import io.netty.buffer.ByteBufAllocator
+import io.netty.buffer.ByteBufUtf8Writer
+import org.jetbrains.io.JsonUtil
+
+open class OutMessage() {
+ val buffer: ByteBuf = ByteBufAllocator.DEFAULT.heapBuffer()
+ val writer: JsonWriter = JsonWriter(ByteBufUtf8Writer(buffer))
+
+ private var finalized: Boolean = false
+
+ init {
+ writer.beginObject()
+ }
+
+ open fun beginArguments() {
+ }
+
+ fun writeMap(name: String, value: Map<String, String>? = null) {
+ if (value == null) return
+
+ beginArguments()
+ writer.name(name)
+ writer.beginObject()
+ for ((key, value1) in value) {
+ writer.name(key).value(value1)
+ }
+ writer.endObject()
+ }
+
+ protected fun writeLongArray(name: String, value: LongArray) {
+ beginArguments()
+ writer.name(name)
+ writer.beginArray()
+ for (v in value) {
+ writer.value(v)
+ }
+ writer.endArray()
+ }
+
+ fun writeDoubleArray(name: String, value: DoubleArray) {
+ beginArguments()
+ writer.name(name)
+ writer.beginArray()
+ for (v in value) {
+ writer.value(v)
+ }
+ writer.endArray()
+ }
+
+ fun writeIntArray(name: String, value: IntArray? = null) {
+ if (value == null) {
+ return
+ }
+
+ beginArguments()
+ writer.name(name)
+ writer.beginArray()
+ for (v in value) {
+ writer.value(v.toLong())
+ }
+ writer.endArray()
+ }
+
+ fun writeIntSet(name: String, value: TIntHashSet) {
+ beginArguments()
+ writer.name(name)
+ writer.beginArray()
+ value.forEach { value ->
+ writer.value(value.toLong())
+ true
+ }
+ writer.endArray()
+ }
+
+ fun writeIntList(name: String, value: TIntArrayList) {
+ beginArguments()
+ writer.name(name)
+ writer.beginArray()
+ for (i in 0..value.size() - 1) {
+ writer.value(value.getQuick(i).toLong())
+ }
+ writer.endArray()
+ }
+
+ fun writeSingletonIntArray(name: String, value: Int) {
+ beginArguments()
+ writer.name(name)
+ writer.beginArray()
+ writer.value(value.toLong())
+ writer.endArray()
+ }
+
+ fun <E : OutMessage> writeList(name: String, value: List<E>?) {
+ if (value.isNullOrEmpty()) {
+ return
+ }
+
+ beginArguments()
+ writer.name(name)
+ writer.beginArray()
+ var isNotFirst = false
+ for (item in value!!) {
+ if (isNotFirst) {
+ buffer.writeByte(','.toInt()).writeByte(' '.toInt())
+ }
+ else {
+ isNotFirst = true
+ }
+
+ if (!item.finalized) {
+ item.finalized = true
+ try {
+ item.writer.endObject()
+ }
+ catch (e: IllegalStateException) {
+ if ("Nesting problem." == e.message) {
+ throw RuntimeException(item.buffer.toString(CharsetToolkit.UTF8_CHARSET) + "\nparent:\n" + buffer.toString(CharsetToolkit.UTF8_CHARSET), e)
+ }
+ else {
+ throw e
+ }
+ }
+
+ }
+
+ buffer.writeBytes(item.buffer)
+ }
+ writer.endArray()
+ }
+
+ fun writeStringList(name: String, value: Collection<String>?) {
+ if (value == null) return
+ beginArguments()
+ JsonWriters.writeStringList(writer, name, value)
+ }
+
+ fun writeEnumList(name: String, values: Collection<Enum<*>>) {
+ beginArguments()
+ writer.name(name).beginArray()
+ for (item in values) {
+ writer.value(item.toString())
+ }
+ writer.endArray()
+ }
+
+ fun writeMessage(name: String, value: OutMessage?) {
+ if (value == null) {
+ return
+ }
+
+ beginArguments()
+ prepareWriteRaw(this, name)
+
+ if (!value.finalized) {
+ value.close()
+ }
+ buffer.writeBytes(value.buffer)
+ }
+
+ fun close() {
+ assert(!finalized)
+ finalized = true
+ writer.endObject()
+ writer.close()
+ }
+
+ protected fun writeLong(name: String, value: Long) {
+ beginArguments()
+ writer.name(name).value(value)
+ }
+
+ fun writeString(name: String, value: String?) {
+ if (value != null) {
+ writeNullableString(name, value)
+ }
+ }
+
+ fun writeNullableString(name: String, value: CharSequence?) {
+ beginArguments()
+ writer.name(name).value(value?.toString())
+ }
+}
+
+fun prepareWriteRaw(message: OutMessage, name: String) {
+ message.writer.name(name).nullValue()
+ val itemBuffer = message.buffer
+ itemBuffer.writerIndex(itemBuffer.writerIndex() - "null".length)
+}
+
+fun doWriteRaw(message: OutMessage, rawValue: String) {
+ message.buffer.writeUtf8(rawValue)
+}
+
+fun OutMessage.writeEnum(name: String, value: Enum<*>?, defaultValue: Enum<*>?) {
+ if (value != null && value != defaultValue) {
+ writeEnum(name, value)
+ }
+}
+
+fun OutMessage.writeEnum(name: String, value: Enum<*>) {
+ beginArguments()
+ writer.name(name).value(value.toString())
+}
+
+fun OutMessage.writeString(name: String, value: CharSequence?, defaultValue: CharSequence?) {
+ if (value != null && value != defaultValue) {
+ writeString(name, value)
+ }
+}
+
+fun OutMessage.writeString(name: String, value: CharSequence) {
+ beginArguments()
+ prepareWriteRaw(this, name)
+ JsonUtil.escape(value, buffer)
+}
+
+fun OutMessage.writeInt(name: String, value: Int, defaultValue: Int) {
+ if (value != defaultValue) {
+ writeInt(name, value)
+ }
+}
+
+fun OutMessage.writeInt(name: String, value: Int?) {
+ if (value != null) {
+ beginArguments()
+ writer.name(name).value(value.toLong())
+ }
+}
+
+fun OutMessage.writeBoolean(name: String, value: Boolean, defaultValue: Boolean) {
+ if (value != defaultValue) {
+ writeBoolean(name, value)
+ }
+}
+
+fun OutMessage.writeBoolean(name: String, value: Boolean?) {
+ if (value != null) {
+ beginArguments()
+ writer.name(name).value(value)
+ }
+}
+
+fun OutMessage.writeDouble(name: String, value: Double?, defaultValue: Double?) {
+ if (value != null && value != defaultValue) {
+ writeDouble(name, value)
+ }
+}
+
+fun OutMessage.writeDouble(name: String, value: Double) {
+ beginArguments()
+ writer.name(name).value(value)
+}
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/Request.kt b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/Request.kt
new file mode 100644
index 00000000..0276b3ff
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/Request.kt
@@ -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.jsonProtocol
+
+import io.netty.buffer.ByteBuf
+import org.jetbrains.io.JsonReaderEx
+
+interface Request<RESULT> {
+ val buffer: ByteBuf
+
+ val methodName: String
+
+ fun finalize(id: Int)
+}
+
+interface ResponseResultReader {
+ fun readResult(methodName: String, reader: JsonReaderEx): Any?
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/RequestImpl.kt b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/RequestImpl.kt
new file mode 100644
index 00000000..c5cffef6
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/RequestImpl.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.jsonProtocol
+
+abstract class RequestImpl<T> : OutMessage(), Request<T> {
+ private var argumentsObjectStarted = false
+
+ protected abstract val idKeyName: String
+
+ protected abstract fun argumentsKeyName(): String
+
+ override fun beginArguments() {
+ if (!argumentsObjectStarted) {
+ argumentsObjectStarted = true
+ writer.name(argumentsKeyName())
+ writer.beginObject()
+ }
+ }
+
+ override fun finalize(id: Int) {
+ if (argumentsObjectStarted) {
+ writer.endObject()
+ }
+ writer.name(idKeyName).value(id.toLong())
+ writer.endObject()
+ writer.close()
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/StringIntPair.java b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/StringIntPair.java
new file mode 100644
index 00000000..cda23f49
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader-runtime/src/org/jetbrains/jsonProtocol/StringIntPair.java
@@ -0,0 +1,15 @@
+package org.jetbrains.jsonProtocol;
+
+public final class StringIntPair {
+ public final String name;
+ public final int value;
+
+ public StringIntPair(String name, int value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public StringIntPair(int value, String name) {
+ this(name, value);
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/intellij.javascript.protocolReader.iml b/platform/script-debugger/protocol/protocol-reader/intellij.javascript.protocolReader.iml
new file mode 100644
index 00000000..4bbaa5d5
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/intellij.javascript.protocolReader.iml
@@ -0,0 +1,16 @@
+<?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" packagePrefix="org.jetbrains.protocolReader" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="Trove4j" level="project" />
+ <orderEntry type="library" name="gson" level="project" />
+ <orderEntry type="module" module-name="intellij.platform.scriptDebugger.protocolReaderRuntime" exported="" />
+ <orderEntry type="module" module-name="intellij.platform.ide.impl" />
+ <orderEntry type="library" name="KotlinJavaRuntime" level="project" />
+ </component>
+</module> \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader/src/ArrayReader.kt b/platform/script-debugger/protocol/protocol-reader/src/ArrayReader.kt
new file mode 100644
index 00000000..29f4861d
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/ArrayReader.kt
@@ -0,0 +1,23 @@
+package org.jetbrains.protocolReader
+
+internal class ArrayReader(private val componentParser: ValueReader, private val isList: Boolean) : ValueReader() {
+ override fun appendFinishedValueTypeName(out: TextOutput) {
+ if (isList) {
+ out.append("List<")
+ }
+ else {
+ if (componentParser is PrimitiveValueReader && (componentParser.className == "Int" || componentParser.className == "Double" || componentParser.className == "Float")) {
+ out.append(componentParser.className).append("Array")
+ return
+ }
+
+ out.append("Array<")
+ }
+ componentParser.appendFinishedValueTypeName(out)
+ out.append('>')
+ }
+
+ override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ componentParser.writeArrayReadCode(scope, subtyping, out)
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/ClassScope.kt b/platform/script-debugger/protocol/protocol-reader/src/ClassScope.kt
new file mode 100644
index 00000000..16c73feb
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/ClassScope.kt
@@ -0,0 +1,7 @@
+package org.jetbrains.protocolReader
+
+internal class ClassScope(fileScope: FileScope, private val parentClass: ClassScope?) : FileScope(fileScope.output, fileScope) {
+ fun getRootClassScope(): ClassScope = if (parentClass == null) this else parentClass.getRootClassScope()
+
+ override fun asClassScope() = this
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader/src/EnumReader.kt b/platform/script-debugger/protocol/protocol-reader/src/EnumReader.kt
new file mode 100644
index 00000000..3f2e3b7b
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/EnumReader.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.protocolReader
+
+internal class EnumReader(private val enumClass: Class<Enum<*>>) : ValueReader() {
+ override fun appendFinishedValueTypeName(out: TextOutput) {
+ out.append(enumClass.canonicalName)
+ }
+
+ override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ beginReadCall("Enum", subtyping, out)
+ out.comma().append(enumClass.canonicalName).append("::class.java)")
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/ExistingSubtypeAspect.kt b/platform/script-debugger/protocol/protocol-reader/src/ExistingSubtypeAspect.kt
new file mode 100644
index 00000000..b0fd3039
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/ExistingSubtypeAspect.kt
@@ -0,0 +1,33 @@
+package org.jetbrains.protocolReader
+
+private val BASE_VALUE_PREFIX = "baseMessage"
+
+internal class ExistingSubtypeAspect(private val jsonSuperClass: TypeRef<*>) {
+ private var subtypeCaster: SubtypeCaster? = null
+
+ fun setSubtypeCaster(subtypeCaster: SubtypeCaster) {
+ this.subtypeCaster = subtypeCaster
+ }
+
+ fun writeGetSuperMethodJava(out: TextOutput) {
+ out.newLine().append("override fun getBase() = ").append(BASE_VALUE_PREFIX)
+ }
+
+ fun writeSuperFieldJava(out: TextOutput) {
+ out.append(", private val ").append(BASE_VALUE_PREFIX).append(": ").append(jsonSuperClass.type!!.typeClass.canonicalName)
+ }
+
+ fun writeParseMethod(scope: ClassScope, out: TextOutput) {
+ out.newLine().append("companion object").block {
+ out.append("fun parse").append('(').append(JSON_READER_PARAMETER_DEF).append(", name: String?").append(") = ")
+ jsonSuperClass.type!!.writeInstantiateCode(scope, out)
+ out.append('(').append(READER_NAME).append(", name)").append('.')
+ subtypeCaster!!.writeJava(out)
+ }
+ out.newLine()
+ }
+
+ fun writeInstantiateCode(className: String, out: TextOutput) {
+ out.append(className).append(".parse")
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/FieldProcessor.kt b/platform/script-debugger/protocol/protocol-reader/src/FieldProcessor.kt
new file mode 100644
index 00000000..9bd7ee14
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/FieldProcessor.kt
@@ -0,0 +1,197 @@
+package org.jetbrains.protocolReader
+
+import com.intellij.openapi.util.text.StringUtil
+import gnu.trove.THashSet
+import org.jetbrains.jsonProtocol.JsonField
+import org.jetbrains.jsonProtocol.JsonSubtypeCasting
+import org.jetbrains.jsonProtocol.Optional
+import org.jetbrains.jsonProtocol.ProtocolName
+import java.lang.reflect.Method
+import java.lang.reflect.ParameterizedType
+import java.util.*
+import kotlin.reflect.KCallable
+import kotlin.reflect.KFunction
+import kotlin.reflect.KProperty
+import kotlin.reflect.jvm.javaGetter
+import kotlin.reflect.jvm.javaMethod
+import kotlin.reflect.jvm.javaType
+
+internal class FieldLoader(val name: String, val jsonName: String, val valueReader: ValueReader, val skipRead: Boolean, val asImpl: Boolean, val defaultValue: String?)
+
+internal fun TextOutput.appendName(loader: FieldLoader): TextOutput {
+ if (!loader.asImpl) {
+ append(FIELD_PREFIX)
+ }
+ append(loader.name)
+ return this
+}
+
+internal class FieldProcessor(private val reader: InterfaceReader, typeClass: Class<*>) {
+ val fieldLoaders = ArrayList<FieldLoader>()
+ val methodHandlerMap = LinkedHashMap<Method, MethodHandler>()
+ val volatileFields = ArrayList<VolatileFieldBinding>()
+ var lazyRead: Boolean = false
+
+ init {
+ val methods = typeClass.methods
+ // todo sort by source location
+ Arrays.sort(methods, { o1, o2 -> o1.name.compareTo(o2.name) })
+
+ val skippedNames = THashSet<String>()
+ for (method in methods) {
+ val annotation = method.getAnnotation<JsonField>(JsonField::class.java)
+ if (annotation != null && !annotation.primitiveValue.isEmpty()) {
+ skippedNames.add(annotation.primitiveValue)
+ skippedNames.add("${annotation.primitiveValue}Type")
+ }
+ }
+
+ val classPackage = typeClass.`package`
+ val kClass = typeClass.kotlin
+ for (member in kClass.members) {
+ val method = if (member is KProperty<*>) {
+ member.javaGetter!!
+ }
+ else if (member is KFunction<*>) {
+ member.javaMethod!!
+ }
+ else {
+ continue
+ }
+
+ val methodClass = method.declaringClass
+ // use method from super if super located in the same package
+ if (methodClass != typeClass) {
+ val methodPackage = methodClass.`package`
+ if (methodPackage != classPackage && !classPackage.name.startsWith("${methodPackage.name}.")) {
+ continue
+ }
+ }
+
+ if (method.parameterCount != 0) {
+ throw JsonProtocolModelParseException("No parameters expected in $method")
+ }
+
+ try {
+ val methodHandler: MethodHandler
+ val jsonSubtypeCaseAnnotation = method.getAnnotation(JsonSubtypeCasting::class.java)
+ if (jsonSubtypeCaseAnnotation == null) {
+ methodHandler = createMethodHandler(member, method, skippedNames.contains(method.name)) ?: continue
+ }
+ else {
+ methodHandler = processManualSubtypeMethod(member, method, jsonSubtypeCaseAnnotation)
+ lazyRead = true
+ }
+ methodHandlerMap.put(method, methodHandler)
+ }
+ catch (e: Exception) {
+ throw JsonProtocolModelParseException("Problem with method $method", e)
+ }
+ }
+ }
+
+ private fun createMethodHandler(member: KCallable<*>, method: Method, skipRead: Boolean): MethodHandler? {
+ var protocolName = member.annotation<ProtocolName>()?.name ?: member.name
+ val genericReturnType = member.returnType.javaType
+ val isNotNull: Boolean
+ val isPrimitive = if (genericReturnType is Class<*>) genericReturnType.isPrimitive else genericReturnType !is ParameterizedType
+ val optionalAnnotation = member.annotation<Optional>()
+ if (isPrimitive || optionalAnnotation != null) {
+ isNotNull = false
+ }
+ else {
+ val fieldAnnotation = member.annotation<JsonField>()
+ if (fieldAnnotation == null) {
+ isNotNull = !member.returnType.isMarkedNullable
+ }
+ else {
+ isNotNull = !fieldAnnotation.allowAnyPrimitiveValue && !fieldAnnotation.allowAnyPrimitiveValueAndMap
+ }
+ }
+
+ val fieldTypeParser = reader.getFieldTypeParser(member, genericReturnType, false, method)
+ val isProperty = member is KProperty<*>
+ val isAsImpl = isProperty && !isNotNull
+ if (fieldTypeParser != VOID_PARSER) {
+ fieldLoaders.add(FieldLoader(member.name, protocolName, fieldTypeParser, skipRead, isAsImpl, StringUtil.nullize(optionalAnnotation?.default)))
+ }
+
+ if (isAsImpl) {
+ return null
+ }
+
+ val effectiveFieldName = if (fieldTypeParser == VOID_PARSER) null else member.name
+ return object : MethodHandler {
+ override fun writeMethodImplementationJava(scope: ClassScope, method: Method, out: TextOutput) {
+ out.append("override ").append(if (isProperty) "val" else "fun")
+ out.append(" ").appendEscapedName(method.name)
+
+ if (isProperty) {
+ out.newLine()
+ out.indentIn()
+ out.append("get()")
+ // todo append type name
+ }
+ else {
+ out.append("()")
+ }
+
+ if (effectiveFieldName == null) {
+ out.openBlock()
+ out.closeBlock()
+ }
+ else {
+ out.append(" = ").append(FIELD_PREFIX).append(effectiveFieldName)
+ if (isNotNull) {
+ out.append("!!")
+ }
+
+ if (isProperty) {
+ out.indentOut()
+ }
+ }
+ }
+ }
+ }
+
+ private fun processManualSubtypeMethod(member: KCallable<*>, m: Method, jsonSubtypeCaseAnn: JsonSubtypeCasting): MethodHandler {
+ val fieldTypeParser = reader.getFieldTypeParser(member, m.genericReturnType, !jsonSubtypeCaseAnn.reinterpret, null)
+ val fieldInfo = allocateVolatileField(fieldTypeParser, true)
+ val handler = LazyCachedMethodHandler(fieldTypeParser, fieldInfo)
+ val parserAsObjectValueParser = fieldTypeParser.asJsonTypeParser()
+ if (parserAsObjectValueParser != null && parserAsObjectValueParser.isSubtyping()) {
+ reader.subtypeCasters.add(object : SubtypeCaster(parserAsObjectValueParser.type) {
+ override fun writeJava(out: TextOutput) {
+ out.append(m.name).append("()")
+ }
+ })
+ }
+ return handler
+ }
+
+ private fun allocateVolatileField(fieldTypeParser: ValueReader, internalType: Boolean): VolatileFieldBinding {
+ val position = volatileFields.size
+ val fieldTypeInfo: (scope: FileScope, out: TextOutput)->Unit
+ if (internalType) {
+ fieldTypeInfo = {scope, out -> fieldTypeParser.appendInternalValueTypeName(scope, out)}
+ }
+ else {
+ fieldTypeInfo = {scope, out -> fieldTypeParser.appendFinishedValueTypeName(out)}
+ }
+ val binding = VolatileFieldBinding(position, fieldTypeInfo)
+ volatileFields.add(binding)
+ return binding
+ }
+}
+
+internal inline fun <reified T : Annotation> KCallable<*>.annotation(): T? = annotations.firstOrNull() { it is T } as? T ?: (this as? KFunction<*>)?.javaMethod?.getAnnotation<T>(T::class.java)
+
+/**
+ * An internal facility for navigating from object of base type to object of subtype. Used only
+ * when user wants to parse JSON object as subtype.
+ */
+internal abstract class SubtypeCaster(private val subtypeRef: TypeRef<*>) {
+ abstract fun writeJava(out: TextOutput)
+
+ fun getSubtypeHandler() = subtypeRef.type!!
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader/src/FileScope.kt b/platform/script-debugger/protocol/protocol-reader/src/FileScope.kt
new file mode 100644
index 00000000..eb16195e
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/FileScope.kt
@@ -0,0 +1,9 @@
+package org.jetbrains.protocolReader
+
+internal fun FileScope(globalScope: GlobalScope, stringBuilder: StringBuilder) = FileScope(TextOutput(stringBuilder), globalScope)
+
+internal open class FileScope(val output: TextOutput, globalScope: GlobalScope) : GlobalScope(globalScope.state) {
+ fun newClassScope() = ClassScope(this, asClassScope())
+
+ protected open fun asClassScope(): ClassScope? = null
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader/src/FileUpdater.kt b/platform/script-debugger/protocol/protocol-reader/src/FileUpdater.kt
new file mode 100644
index 00000000..d088a16a
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/FileUpdater.kt
@@ -0,0 +1,34 @@
+package org.jetbrains.protocolReader
+
+import java.nio.file.Files
+import java.nio.file.Path
+import java.util.*
+
+/**
+ * A class that makes accurate java source file update. If only header
+ * (with source file revision and other comments) changed, the file is left intact.
+ * <p>User first writes all the content into a {@link Writer} provided and then
+ * calls {@link #update()}.
+ */
+class FileUpdater(private val file: Path) {
+ val builder: StringBuilder = StringBuilder()
+ val out: TextOutput = TextOutput(builder)
+
+ fun update() {
+ if (builder.length == 0) {
+ Files.delete(file)
+ return
+ }
+
+ val newContent = builder.toString().toByteArray()
+ if (Files.exists(file)) {
+ if (Arrays.equals(Files.readAllBytes(file), newContent)) {
+ return
+ }
+ }
+ else {
+ Files.createDirectories(file.parent)
+ }
+ Files.write(file, newContent)
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/GlobalScope.kt b/platform/script-debugger/protocol/protocol-reader/src/GlobalScope.kt
new file mode 100644
index 00000000..13ffceb3
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/GlobalScope.kt
@@ -0,0 +1,63 @@
+package org.jetbrains.protocolReader
+
+import gnu.trove.THashMap
+import gnu.trove.THashSet
+import java.util.*
+
+internal fun GlobalScope(typeWriters: Collection<TypeWriter<*>?>, basePackages: Collection<Map<Class<*>, String>>) = GlobalScope(State(typeWriters, basePackages))
+
+internal open class GlobalScope(val state: State) {
+ fun getTypeImplReference(typeWriter: TypeWriter<*>) = state.getTypeImplReference(typeWriter)
+
+ fun requireFactoryGenerationAndGetName(typeWriter: TypeWriter<*>) = state.requireFactoryGenerationAndGetName(typeWriter)
+
+ fun getTypeImplShortName(typeWriter: TypeWriter<*>) = state.getTypeImplShortName(typeWriter)
+
+ fun newFileScope(output: StringBuilder) = FileScope(this, output)
+
+ fun getTypeFactories() = state.typesWithFactoriesList
+}
+
+internal class State(typeWriters: Collection<TypeWriter<*>?>, private val basePackages: Collection<Map<Class<*>, String>>) {
+ private var typeToName: Map<TypeWriter<*>, String>
+ private val typesWithFactories = THashSet<TypeWriter<*>>()
+ val typesWithFactoriesList = ArrayList<TypeWriter<*>>();
+
+ init {
+ var uniqueCode = 0
+ val result = THashMap<TypeWriter<*>, String>(typeWriters.size)
+ for (handler in typeWriters) {
+ val conflict = result.put(handler, "M${Integer.toString(uniqueCode++, Character.MAX_RADIX)}")
+ if (conflict != null) {
+ throw RuntimeException()
+ }
+ }
+ typeToName = result
+ }
+
+ fun getTypeImplReference(typeWriter: TypeWriter<*>): String {
+ val localName = typeToName.get(typeWriter)
+ if (localName != null) {
+ return localName
+ }
+
+ for (base in basePackages) {
+ val result = base.get(typeWriter.typeClass)
+ if (result != null) {
+ return result
+ }
+ }
+
+ throw RuntimeException()
+ }
+
+ fun requireFactoryGenerationAndGetName(typeWriter: TypeWriter<*>): String {
+ val name = getTypeImplShortName(typeWriter)
+ if (typesWithFactories.add(typeWriter)) {
+ typesWithFactoriesList.add(typeWriter)
+ }
+ return name
+ }
+
+ fun getTypeImplShortName(typeWriter: TypeWriter<*>) = typeToName.get(typeWriter)!!
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/InterfaceReader.kt b/platform/script-debugger/protocol/protocol-reader/src/InterfaceReader.kt
new file mode 100644
index 00000000..88162cb7
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/InterfaceReader.kt
@@ -0,0 +1,221 @@
+package org.jetbrains.protocolReader
+
+import gnu.trove.THashSet
+import org.jetbrains.io.JsonReaderEx
+import org.jetbrains.jsonProtocol.JsonField
+import org.jetbrains.jsonProtocol.JsonSubtype
+import org.jetbrains.jsonProtocol.Optional
+import org.jetbrains.jsonProtocol.StringIntPair
+import java.lang.reflect.Method
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+import java.lang.reflect.WildcardType
+import java.util.*
+import kotlin.reflect.KCallable
+
+internal fun InterfaceReader(protocolInterfaces: List<Class<*>>): InterfaceReader {
+ val map = LinkedHashMap<Class<*>, TypeWriter<*>?>(protocolInterfaces.size)
+ for (typeClass in protocolInterfaces) {
+ map.put(typeClass, null)
+ }
+ return InterfaceReader(map)
+}
+
+private val LONG_PARSER = PrimitiveValueReader("Long", "-1L")
+
+private val INTEGER_PARSER = PrimitiveValueReader("Int", "-1")
+
+private val BOOLEAN_PARSER = PrimitiveValueReader("Boolean", "false")
+private val FLOAT_PARSER = PrimitiveValueReader("Float")
+
+private val NUMBER_PARSER = PrimitiveValueReader("Double", "Double.NaN")
+
+private val STRING_PARSER = PrimitiveValueReader("String")
+private val NULLABLE_STRING_PARSER = PrimitiveValueReader(className = "String", nullable = true)
+
+private val RAW_STRING_PARSER = PrimitiveValueReader("String", null, true)
+private val RAW_STRING_OR_MAP_PARSER = object : PrimitiveValueReader("Any", null, true) {
+ override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ out.append("readRawStringOrMap(")
+ addReaderParameter(subtyping, out)
+ out.append(')')
+ }
+}
+
+private val JSON_PARSER = RawValueReader()
+
+private val STRING_INT_PAIR_PARSER = StringIntPairValueReader()
+
+internal val VOID_PARSER: ValueReader = object : ValueReader() {
+ override fun appendFinishedValueTypeName(out: TextOutput) {
+ out.append("void")
+ }
+
+ override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ out.append("null")
+ }
+}
+
+internal fun createHandler(typeToTypeHandler: LinkedHashMap<Class<*>, TypeWriter<*>?>, aClass: Class<*>): TypeWriter<*> {
+ val reader = InterfaceReader(typeToTypeHandler)
+ reader.processed.addAll(typeToTypeHandler.keys)
+ reader.go(arrayOf(aClass))
+ return typeToTypeHandler.get(aClass)!!
+}
+
+internal class InterfaceReader(val typeToTypeHandler: LinkedHashMap<Class<*>, TypeWriter<*>?>) {
+ val processed = THashSet<Class<*>>()
+ private val refs = ArrayList<TypeRef<*>>()
+ val subtypeCasters = ArrayList<SubtypeCaster>()
+
+ fun go(classes: Array<Class<*>> = typeToTypeHandler.keys.toTypedArray()): LinkedHashMap<Class<*>, TypeWriter<*>?> {
+ for (typeClass in classes) {
+ createIfNotExists(typeClass)
+ }
+
+ var hasUnresolved = true
+ while (hasUnresolved) {
+ hasUnresolved = false
+ // refs can be modified - new items can be added
+ for (i in 0..refs.size - 1) {
+ val ref: TypeRef<out Any?> = refs.get(i)
+ val typeClass: Class<out Any?> = ref.typeClass
+ (ref as TypeRef<Any?>).type = typeToTypeHandler.get(typeClass) as TypeWriter<Any?>?
+ if (ref.type == null) {
+ createIfNotExists(typeClass)
+ hasUnresolved = true
+ (ref as TypeRef<Any?>).type = typeToTypeHandler.get(typeClass) as TypeWriter<Any?>? ?: throw IllegalStateException()
+ }
+ }
+ }
+
+ for (subtypeCaster in subtypeCasters) {
+ subtypeCaster.getSubtypeHandler().subtypeAspect?.setSubtypeCaster(subtypeCaster)
+ }
+
+ return typeToTypeHandler
+ }
+
+ private fun createIfNotExists(typeClass: Class<*>) {
+ if (typeClass == Map::class.java || typeClass == List::class.java || !typeClass.isInterface) {
+ return
+ }
+
+ if (!processed.add(typeClass)) {
+ return
+ }
+
+ typeToTypeHandler.put(typeClass, null)
+
+ for (aClass in typeClass.declaredClasses) {
+ createIfNotExists(aClass)
+ }
+
+ if (!typeClass.isInterface) {
+ throw JsonProtocolModelParseException("Json model type should be interface: ${typeClass.name}")
+ }
+
+ val fields = FieldProcessor(this, typeClass)
+ for (method in fields.methodHandlerMap.keys) {
+ val returnType = method.returnType
+ if (returnType != typeClass) {
+ createIfNotExists(returnType)
+ }
+ }
+
+ val typeWriter = TypeWriter(typeClass, getSuperclassRef(typeClass), fields.volatileFields, fields.methodHandlerMap, fields.fieldLoaders, fields.lazyRead)
+ for (ref in refs) {
+ if (ref.typeClass == typeClass) {
+ assert(ref.type == null)
+ (ref as TypeRef<Any?>).type = typeWriter as TypeWriter<Any?>
+ break
+ }
+ }
+ typeToTypeHandler.put(typeClass, typeWriter)
+ }
+
+ fun getFieldTypeParser(member: KCallable<*>?, type: Type, isSubtyping: Boolean, method: Method?): ValueReader {
+ if (type is Class<*>) {
+ @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+ return when {
+ type == java.lang.Long.TYPE -> LONG_PARSER
+ type == Integer.TYPE || type == Integer::class.java -> INTEGER_PARSER
+ type == java.lang.Boolean.TYPE -> BOOLEAN_PARSER
+ type == java.lang.Float.TYPE -> FLOAT_PARSER
+ type == Number::class.java || type == java.lang.Double.TYPE || type == java.lang.Double::class.java -> NUMBER_PARSER
+ type == Void.TYPE -> VOID_PARSER
+ type == String::class.java -> {
+ if (method != null) {
+ val jsonField = member?.annotation<JsonField>()
+ if (jsonField != null && jsonField.allowAnyPrimitiveValue) {
+ return RAW_STRING_PARSER
+ }
+ else if ((member?.returnType?.isMarkedNullable ?: false) || method.getAnnotation<Optional>(Optional::class.java) != null) {
+ return NULLABLE_STRING_PARSER
+ }
+ }
+ return STRING_PARSER
+ }
+ type == Any::class.java -> RAW_STRING_OR_MAP_PARSER
+ type == JsonReaderEx::class.java -> JSON_PARSER
+ type == StringIntPair::class.java -> STRING_INT_PAIR_PARSER
+ type.isArray -> ArrayReader(getFieldTypeParser(null, type.componentType, false, null), false)
+ type.isEnum -> EnumReader(type as Class<Enum<*>>)
+ else -> {
+ val ref = getTypeRef(type) ?: throw UnsupportedOperationException("Method return type $type (simple class) not supported")
+ ObjectValueReader(ref, isSubtyping, method?.getAnnotation<JsonField>(JsonField::class.java)?.primitiveValue)
+ }
+ }
+ }
+ else if (type is ParameterizedType) {
+ val isList = type.rawType == List::class.java
+ if (isList || type.rawType == Map::class.java) {
+ var argumentType = type.actualTypeArguments[if (isList) 0 else 1]
+ if (argumentType is WildcardType) {
+ val wildcard = argumentType
+ if (wildcard.lowerBounds.size == 0 && wildcard.upperBounds.size == 1) {
+ argumentType = wildcard.upperBounds[0]
+ }
+ }
+ val componentParser = getFieldTypeParser(null, argumentType, false, method)
+ return if (isList) ArrayReader(componentParser, true) else MapReader(componentParser)
+ }
+ else {
+ throw UnsupportedOperationException("Method return type $type (generic) not supported")
+ }
+ }
+ else {
+ throw UnsupportedOperationException("Method return type $type not supported")
+ }
+ }
+
+ fun <T> getTypeRef(typeClass: Class<T>): TypeRef<T>? {
+ val result = TypeRef(typeClass)
+ refs.add(result)
+ return result
+ }
+
+ private fun getSuperclassRef(typeClass: Class<*>): TypeRef<*>? {
+ var result: TypeRef<*>? = null
+ for (interfaceGeneric in typeClass.genericInterfaces) {
+ if (interfaceGeneric !is ParameterizedType) {
+ continue
+ }
+ if (interfaceGeneric.rawType != JsonSubtype::class.java) {
+ continue
+ }
+ val param = interfaceGeneric.actualTypeArguments[0]
+ if (param !is Class<*>) {
+ throw JsonProtocolModelParseException("Unexpected type of superclass $param")
+ }
+ if (result != null) {
+ throw JsonProtocolModelParseException("Already has superclass ${result.typeClass.name}")
+ }
+ result = getTypeRef(param)
+ if (result == null) {
+ throw JsonProtocolModelParseException("Unknown base class ${param.name}")
+ }
+ }
+ return result
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader/src/JsonProtocolModelParseException.kt b/platform/script-debugger/protocol/protocol-reader/src/JsonProtocolModelParseException.kt
new file mode 100644
index 00000000..5007fd49
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/JsonProtocolModelParseException.kt
@@ -0,0 +1,8 @@
+package org.jetbrains.protocolReader
+
+/**
+ * Signals that JSON model has some problem in it.
+ */
+fun JsonProtocolModelParseException(message: String): JsonProtocolModelParseException = JsonProtocolModelParseException(message, null)
+
+class JsonProtocolModelParseException(message: String, cause: Throwable?) : RuntimeException(message, cause)
diff --git a/platform/script-debugger/protocol/protocol-reader/src/LazyCachedMethodHandler.kt b/platform/script-debugger/protocol/protocol-reader/src/LazyCachedMethodHandler.kt
new file mode 100644
index 00000000..3e51e1aa
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/LazyCachedMethodHandler.kt
@@ -0,0 +1,51 @@
+package org.jetbrains.protocolReader
+
+import java.lang.reflect.Method
+
+/**
+ * Basic implementation of the method that parses value on demand and store it for a future use
+ */
+internal class LazyCachedMethodHandler(private val parser: ValueReader, private val fieldBinding: VolatileFieldBinding) : MethodHandler {
+ protected fun writeReturnTypeJava(scope: ClassScope, m: Method, out: TextOutput) {
+ val objectValueParser = parser.asJsonTypeParser()
+ if (objectValueParser == null) {
+ writeJavaTypeName(m.genericReturnType, out)
+ }
+ else {
+ out.append(scope.getTypeImplReference(objectValueParser.type.type!!))
+ }
+ }
+
+ override fun writeMethodImplementationJava(scope: ClassScope, method: Method, out: TextOutput) {
+ out.append("override fun ")
+ appendMethodSignatureJava(method, listOf(), out)
+ out.append(": ")
+ writeReturnTypeJava(scope, method, out)
+
+ out.openBlock()
+ out.append("if (")
+ fieldBinding.writeGetExpression(out)
+ out.append(" == null)").block {
+ if (parser.isThrowsIOException()) {
+ out.append("try").openBlock()
+ }
+
+ fieldBinding.writeGetExpression(out)
+ out.append(" = ")
+ parser.writeReadCode(scope, true, scope.output)
+
+ if (parser.isThrowsIOException()) {
+ out.closeBlock()
+ out.newLine().append("catch (e: IOException)").openBlock()
+ out.append("throw com.google.gson.JsonParseException(e)").closeBlock()
+ }
+ out.newLine().append(PENDING_INPUT_READER_NAME).append(" = null")
+ }
+
+ out.newLine().append("return ")
+ fieldBinding.writeGetExpression(out)
+ out.append("!!")
+
+ out.closeBlock()
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/MapReader.kt b/platform/script-debugger/protocol/protocol-reader/src/MapReader.kt
new file mode 100644
index 00000000..d251d92a
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/MapReader.kt
@@ -0,0 +1,34 @@
+package org.jetbrains.protocolReader
+
+internal class MapReader(private val componentParser: ValueReader) : ValueReader() {
+ override fun appendFinishedValueTypeName(out: TextOutput) {
+ out.append("Map")
+ out.append('<')
+ out.append("String, ")
+ componentParser.appendFinishedValueTypeName(out)
+ out.append('>')
+ }
+
+ override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ beginReadCall("Map", subtyping, out)
+ if (componentParser is ObjectValueReader) {
+ componentParser.writeFactoryArgument(scope, out)
+ }
+ else {
+ out.comma().append("null")
+ }
+ out.append(')')
+ }
+
+ override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ beginReadCall("ObjectArray", subtyping, out)
+ out.comma().append("mapFactory(")
+ if (componentParser is ObjectValueReader) {
+ componentParser.writeFactoryNewExpression(scope, out)
+ }
+ else {
+ out.append("STRING_OBJECT_FACTORY")
+ }
+ out.append("))")
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/MethodHandler.kt b/platform/script-debugger/protocol/protocol-reader/src/MethodHandler.kt
new file mode 100644
index 00000000..f0dd55c6
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/MethodHandler.kt
@@ -0,0 +1,31 @@
+package org.jetbrains.protocolReader
+
+import java.lang.reflect.Method
+
+internal fun appendMethodSignatureJava(method: Method, paramNames: List<String>, out: TextOutput) {
+ out.append(method.name).append('(')
+ var firstArg = true
+ val types = method.genericParameterTypes
+ for (i in 0..types.size - 1) {
+ val arg = types[i]
+ if (firstArg) {
+ firstArg = false
+ }
+ else {
+ out.comma()
+ }
+ out.append(paramNames.get(i))
+ out.append(": ")
+ writeJavaTypeName(arg, out)
+ }
+ out.append(')')
+}
+
+fun writeMethodDeclarationJava(out: TextOutput, m: Method, paramNames: List<String> = listOf<String>()) {
+ out.append("override fun ")
+ appendMethodSignatureJava(m, paramNames, out)
+}
+
+internal interface MethodHandler {
+ fun writeMethodImplementationJava(scope: ClassScope, method: Method, out: TextOutput)
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/ObjectValueReader.kt b/platform/script-debugger/protocol/protocol-reader/src/ObjectValueReader.kt
new file mode 100644
index 00000000..ef6e02f2
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/ObjectValueReader.kt
@@ -0,0 +1,43 @@
+package org.jetbrains.protocolReader
+
+internal class ObjectValueReader(val type: TypeRef<*>, private val isSubtyping: Boolean, primitiveValueName: String?) : ValueReader() {
+ val primitiveValueName = if (primitiveValueName == null || primitiveValueName.isEmpty()) null else primitiveValueName
+
+ override fun asJsonTypeParser() = this
+
+ fun isSubtyping() = isSubtyping
+
+ override fun appendFinishedValueTypeName(out: TextOutput) {
+ out.append(type.typeClass.canonicalName)
+ }
+
+ override fun appendInternalValueTypeName(scope: FileScope, out: TextOutput) {
+ out.append(scope.getTypeImplReference(type.type!!))
+ }
+
+ override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ type.type!!.writeInstantiateCode(scope.getRootClassScope(), subtyping, out)
+ out.append('(')
+ addReaderParameter(subtyping, out)
+ out.comma().append("null")
+ if (subtyping && type.type!!.subtypeAspect != null) {
+ out.comma().append("this")
+ }
+ out.append(')')
+ }
+
+ override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ beginReadCall("ObjectArray", subtyping, out)
+ writeFactoryArgument(scope, out)
+ out.append(')')
+ }
+
+ fun writeFactoryArgument(scope: ClassScope, out: TextOutput) {
+ out.comma()
+ writeFactoryNewExpression(scope, out)
+ }
+
+ fun writeFactoryNewExpression(scope: ClassScope, out: TextOutput) {
+ out.append(TYPE_FACTORY_NAME_PREFIX).append(scope.requireFactoryGenerationAndGetName(type.type!!)).append("()")
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/PrimitiveValueReader.kt b/platform/script-debugger/protocol/protocol-reader/src/PrimitiveValueReader.kt
new file mode 100644
index 00000000..3a126108
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/PrimitiveValueReader.kt
@@ -0,0 +1,44 @@
+package org.jetbrains.protocolReader
+
+internal open class PrimitiveValueReader(val className: String, val defaultValue: String? = null, private val asRawString: Boolean = false, private val nullable: Boolean = false) : ValueReader() {
+ private val readPostfix: String
+
+ init {
+ if (Character.isLowerCase(className.get(0))) {
+ readPostfix = "${Character.toUpperCase(className.get(0))}${className.substring(1)}"
+ }
+ else {
+ readPostfix = if (asRawString) ("Raw$className") else className
+ }
+ }
+
+ override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ if (asRawString) {
+ out.append("readRawString(")
+ addReaderParameter(subtyping, out)
+ out.append(')')
+ }
+ else {
+ addReaderParameter(subtyping, out)
+ out.append(".next");
+ if (nullable) {
+ out.append("Nullable");
+ }
+ out.append(readPostfix).append("()")
+ }
+ }
+
+ override fun appendFinishedValueTypeName(out: TextOutput) {
+ out.append(className)
+ }
+
+ override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ if (readPostfix == "String") {
+ out.append("nextList")
+ }
+ else {
+ out.append("read").append(readPostfix).append("Array")
+ }
+ out.append('(').append(READER_NAME).append(')')
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/RawValueReader.kt b/platform/script-debugger/protocol/protocol-reader/src/RawValueReader.kt
new file mode 100644
index 00000000..88a3ecda
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/RawValueReader.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.protocolReader
+
+import org.jetbrains.io.JsonReaderEx
+
+internal class RawValueReader() : ValueReader() {
+ override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ addReaderParameter(subtyping, out)
+ out.append(".createSubReaderAndSkipValue()")
+ }
+
+ override fun appendFinishedValueTypeName(out: TextOutput) {
+ out.append(JsonReaderEx::class.java.canonicalName)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader/src/ReaderGenerator.kt b/platform/script-debugger/protocol/protocol-reader/src/ReaderGenerator.kt
new file mode 100644
index 00000000..7397d6fa
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/ReaderGenerator.kt
@@ -0,0 +1,117 @@
+package org.jetbrains.protocolReader
+
+import gnu.trove.THashMap
+import java.nio.file.Paths
+import java.util.*
+
+class GenerateConfiguration<ROOT>(val packageName: String, val className: String, readerRootClass: Class<ROOT>, protocolInterfaces: List<Class<*>>, basePackagesMap: Map<Class<*>, String>? = null) {
+ val basePackagesMap: List<Map<Class<*>, String>> = if (basePackagesMap == null) listOf<Map<Class<*>, String>>() else listOf(basePackagesMap)
+
+ internal val typeToTypeHandler = InterfaceReader(protocolInterfaces).go()
+ internal val root = ReaderRoot(readerRootClass, typeToTypeHandler)
+}
+
+fun generate(args: Array<String>, configuration: GenerateConfiguration<*>) {
+ val fileUpdater = FileUpdater(Paths.get(parseArgs(args), "${configuration.className}.kt"))
+ generate(configuration, fileUpdater.builder)
+ fileUpdater.update()
+}
+
+private fun parseArgs(args: Array<String>): String {
+ val outputDirParam = StringParam()
+
+ val paramMap = HashMap<String, StringParam>(3)
+ paramMap.put("output-dir", outputDirParam)
+
+ for (arg in args) {
+ if (!arg.startsWith("--")) {
+ throw IllegalArgumentException("Unrecognized param: $arg")
+ }
+ val equalsPos = arg.indexOf('=', 2)
+ val key: String
+ val value: String?
+ if (equalsPos == -1) {
+ key = arg.substring(2).trim()
+ value = null
+ }
+ else {
+ key = arg.substring(2, equalsPos).trim()
+ value = arg.substring(equalsPos + 1).trim()
+ }
+ val paramListener = paramMap.get(key) ?: throw IllegalArgumentException("Unrecognized param name: $key")
+ try {
+ paramListener.value = value
+ }
+ catch (e: IllegalArgumentException) {
+ throw IllegalArgumentException("Failed to set value of $key", e)
+ }
+
+ }
+ for (en in paramMap.entries) {
+ if (en.value.value == null) {
+ en.value.value = "generated"
+ }
+ }
+
+ return outputDirParam.value!!
+}
+
+private class StringParam {
+ var value: String? = null
+}
+
+fun buildParserMap(configuration: GenerateConfiguration<*>): Map<Class<*>, String> {
+ val fileScope = generate(configuration, StringBuilder())
+
+ val typeToImplClassName = THashMap<Class<*>, String>()
+ for (typeWriter in configuration.typeToTypeHandler.values) {
+ typeToImplClassName.put(typeWriter!!.typeClass, "${configuration.packageName}.${configuration.className}.${fileScope.getTypeImplShortName(typeWriter)}")
+ }
+ return typeToImplClassName
+}
+
+private fun generate(configuration: GenerateConfiguration<*>, stringBuilder: StringBuilder): FileScope {
+ val globalScope = GlobalScope(configuration.typeToTypeHandler.values, configuration.basePackagesMap)
+ val fileScope = globalScope.newFileScope(stringBuilder)
+ val out = fileScope.output
+ out.append("// Generated source")
+ out.newLine().append("package ").append(configuration.packageName)
+ out.newLine().newLine().append("import org.jetbrains.jsonProtocol.*")
+
+ out.newLine()
+ out.newLine().append("import org.jetbrains.io.JsonReaderEx")
+
+ out.newLine().newLine().append("import org.jetbrains.jsonProtocol.JsonReaders.*")
+ out.newLine().newLine().append("internal class ").append(configuration.className).space()
+ out.append(':').space().append(configuration.root.type.canonicalName).append(if (configuration.root.type.isInterface) "" else "()").openBlock(false)
+
+ val rootClassScope = fileScope.newClassScope()
+ configuration.root.write(rootClassScope)
+
+ for (typeWriter in configuration.typeToTypeHandler.values) {
+ out.newLine()
+ typeWriter!!.write(rootClassScope)
+ out.newLine()
+ }
+
+ var isFirst = true
+ for (typeWriter in globalScope.getTypeFactories()) {
+ if (isFirst) {
+ isFirst = false
+ }
+ else {
+ out.newLine()
+ }
+
+ val originName = typeWriter.typeClass.canonicalName
+ out.newLine().append("private class ").append(TYPE_FACTORY_NAME_PREFIX).append(globalScope.getTypeImplShortName(typeWriter)).append(" : ObjectFactory<")
+ out.append(originName).append(">()").openBlock()
+ out.append("override fun read(").append(JSON_READER_PARAMETER_DEF).append("): ").append(originName).append(" = ")
+ typeWriter.writeInstantiateCode(rootClassScope, out)
+ out.append('(').append(READER_NAME).append(", null)")
+ out.closeBlock()
+ }
+
+ out.closeBlock()
+ return fileScope
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/ReaderRoot.kt b/platform/script-debugger/protocol/protocol-reader/src/ReaderRoot.kt
new file mode 100644
index 00000000..c00abbe3
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/ReaderRoot.kt
@@ -0,0 +1,105 @@
+package org.jetbrains.protocolReader
+
+import gnu.trove.THashSet
+import org.jetbrains.io.JsonReaderEx
+import org.jetbrains.jsonProtocol.JsonParseMethod
+import java.lang.reflect.Method
+import java.lang.reflect.ParameterizedType
+import java.util.*
+
+internal class ReaderRoot<R>(val type: Class<R>, private val typeToTypeHandler: LinkedHashMap<Class<*>, TypeWriter<*>?>) {
+ private val visitedInterfaces = THashSet<Class<*>>(1)
+ val methodMap = LinkedHashMap<Method, ReadDelegate>()
+
+ init {
+ readInterfaceRecursive(type)
+ }
+
+ private fun readInterfaceRecursive(clazz: Class<*>) {
+ if (visitedInterfaces.contains(clazz)) {
+ return
+ }
+ visitedInterfaces.add(clazz)
+
+ // todo sort by source location
+ val methods = clazz.methods
+ Arrays.sort<Method>(methods, { o1, o2 -> o1.name.compareTo(o2.name) })
+
+ for (m in methods) {
+ m.getAnnotation<JsonParseMethod>(JsonParseMethod::class.java) ?: continue
+
+ val exceptionTypes = m.exceptionTypes
+ if (exceptionTypes.size > 1) {
+ throw JsonProtocolModelParseException("Too many exception declared in $m")
+ }
+
+ var returnType = m.genericReturnType
+ var isList = false
+ if (returnType is ParameterizedType) {
+ val parameterizedType = returnType
+ if (parameterizedType.rawType == List::class.java) {
+ isList = true
+ returnType = parameterizedType.actualTypeArguments[0]
+ }
+ }
+
+ //noinspection SuspiciousMethodCalls
+ var typeWriter: TypeWriter<*>? = typeToTypeHandler.get(returnType as Any?)
+ if (typeWriter == null) {
+ typeWriter = createHandler(typeToTypeHandler, m.returnType)
+ }
+
+ val arguments = m.genericParameterTypes
+ if (arguments.size > 2) {
+ throw JsonProtocolModelParseException("Exactly one argument is expected in $m")
+ }
+ val argument = arguments[0]
+ if (argument == JsonReaderEx::class.java || argument == Any::class.java) {
+ methodMap.put(m, ReadDelegate(typeWriter, isList, arguments.size != 1))
+ }
+ else {
+ throw JsonProtocolModelParseException("Unrecognized argument type in $m")
+ }
+ }
+
+ for (baseType in clazz.genericInterfaces) {
+ if (baseType !is Class<*>) {
+ throw JsonProtocolModelParseException("Base interface must be class in $clazz")
+ }
+ readInterfaceRecursive(baseType)
+ }
+ }
+
+ fun write(scope: ClassScope) {
+ val out = scope.output
+ for (entry in methodMap.entries) {
+ out.newLine()
+ entry.value.write(scope, entry.key, out)
+ out.newLine()
+ }
+ }
+}
+
+private val STATIC_METHOD_PARAM_NAME_LIST = listOf(READER_NAME)
+private val STATIC_METHOD_PARAM_NAME_LIST2 = Arrays.asList(READER_NAME, "nextName")
+
+internal class ReadDelegate(private val typeHandler: TypeWriter<*>, private val isList: Boolean, hasNextNameParam: Boolean) {
+ private val paramNames = if (hasNextNameParam) STATIC_METHOD_PARAM_NAME_LIST2 else STATIC_METHOD_PARAM_NAME_LIST
+
+ fun write(scope: ClassScope, method: Method, out: TextOutput) {
+ writeMethodDeclarationJava(out, method, paramNames)
+ out.append(": ")
+ writeJavaTypeName(method.genericReturnType, out)
+ out.append(" = ")
+ if (isList) {
+ out.append("readObjectArray(").append(READER_NAME).append(", ").append(TYPE_FACTORY_NAME_PREFIX).append(scope.requireFactoryGenerationAndGetName(typeHandler)).append("()").append(")")
+ }
+ else {
+ typeHandler.writeInstantiateCode(scope, out)
+ out.append('(').append(READER_NAME)
+ out.comma().space()
+ out.append(if (paramNames.size == 1) "null" else "nextName")
+ out.append(')')
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader/src/StringIntPairValueReader.kt b/platform/script-debugger/protocol/protocol-reader/src/StringIntPairValueReader.kt
new file mode 100644
index 00000000..c72976fe
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/StringIntPairValueReader.kt
@@ -0,0 +1,16 @@
+package org.jetbrains.protocolReader
+
+internal class StringIntPairValueReader : ValueReader() {
+ override fun appendFinishedValueTypeName(out: TextOutput) {
+ out.append("StringIntPair")
+ }
+
+ override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ }
+
+ override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ out.append("read").append("IntStringPairs").append('(')
+ addReaderParameter(subtyping, out)
+ out.append(')')
+ }
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/TextOutput.kt b/platform/script-debugger/protocol/protocol-reader/src/TextOutput.kt
new file mode 100644
index 00000000..fc7e8277
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/TextOutput.kt
@@ -0,0 +1,137 @@
+package org.jetbrains.protocolReader
+
+import java.util.*
+
+val EMPTY_CHARS: CharArray = CharArray(0)
+private val indentGranularity = 2
+
+class TextOutput(val out: StringBuilder) {
+ private var identLevel: Int = 0
+ private var indents = arrayOf(EMPTY_CHARS)
+ private var justNewLined: Boolean = false
+
+ fun indentIn(): TextOutput {
+ ++identLevel
+ if (identLevel >= indents.size) {
+ // Cache a new level of indentation string.
+ val newIndentLevel = CharArray(identLevel * indentGranularity)
+ Arrays.fill(newIndentLevel, ' ')
+ val newIndents = arrayOfNulls<CharArray>(indents.size + 1)
+ System.arraycopy(indents, 0, newIndents, 0, indents.size)
+ newIndents[identLevel] = newIndentLevel
+ indents = newIndents as Array<CharArray>
+ }
+ return this
+ }
+
+ fun indentOut(): TextOutput {
+ --identLevel
+ return this
+ }
+
+ fun newLine(): TextOutput {
+ out.append('\n')
+ justNewLined = true
+ return this
+ }
+
+ fun append(value: Double): TextOutput {
+ maybeIndent()
+ out.append(value)
+ return this
+ }
+
+ fun append(value: Boolean): TextOutput {
+ maybeIndent()
+ out.append(value)
+ return this
+ }
+
+ fun append(value: Int): TextOutput {
+ maybeIndent()
+ out.append(value)
+ return this
+ }
+
+ fun append(c: Char): TextOutput {
+ maybeIndent()
+ out.append(c)
+ return this
+ }
+
+ fun append(s: CharArray) {
+ maybeIndent()
+ out.append(s)
+ }
+
+ fun append(s: CharSequence): TextOutput {
+ maybeIndent()
+ out.append(s)
+ return this
+ }
+
+ fun append(s: CharSequence, start: Int): TextOutput {
+ maybeIndent()
+ out.append(s, start, s.length)
+ return this
+ }
+
+ fun openBlock(): TextOutput {
+ return openBlock(true)
+ }
+
+ inline fun block(addNewLine: Boolean = true, writer: () -> Unit): TextOutput {
+ openBlock(addNewLine)
+ writer()
+ closeBlock()
+ return this
+ }
+
+ fun openBlock(addNewLine: Boolean): TextOutput {
+ space().append('{')
+ if (addNewLine) {
+ newLine()
+ }
+ indentIn()
+ return this
+ }
+
+ fun closeBlock(): TextOutput {
+ indentOut().newLine().append('}')
+ return this
+ }
+
+ fun comma(): TextOutput = append(',').space()
+
+ fun space(): TextOutput = append(' ')
+
+ fun doc(description: String?): TextOutput {
+ if (description == null) {
+ return this
+ }
+ return append("/**").newLine().append(" * ").append(description).newLine().append(" */").newLine()
+ }
+
+ fun quote(s: CharSequence): TextOutput = append('"').append(s).append('"')
+
+ fun maybeIndent() {
+ if (justNewLined) {
+ out.append(indents[identLevel])
+ justNewLined = false
+ }
+ }
+
+ fun appendEscapedName(name: String): TextOutput {
+ val isEscapeName = name == "object" || name == "fun"
+ if (isEscapeName) {
+ out.append('`')
+ }
+ out.append(name)
+ if (isEscapeName) {
+ out.append('`')
+ }
+ return this
+ }
+
+ operator fun plus(value: String): TextOutput = append(value)
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/TypeWriter.kt b/platform/script-debugger/protocol/protocol-reader/src/TypeWriter.kt
new file mode 100644
index 00000000..ddf40950
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/TypeWriter.kt
@@ -0,0 +1,258 @@
+package org.jetbrains.protocolReader
+
+import org.jetbrains.jsonProtocol.JsonObjectBased
+import java.lang.reflect.Method
+import java.util.*
+
+internal val FIELD_PREFIX = '_'
+
+internal val NAME_VAR_NAME = "_n"
+
+private fun assignField(out: TextOutput, fieldName: String) = out.append(FIELD_PREFIX).append(fieldName).append(" = ")
+
+internal class TypeRef<T>(val typeClass: Class<T>) {
+ var type: TypeWriter<T>? = null
+}
+
+internal class TypeWriter<T>(val typeClass: Class<T>, jsonSuperClass: TypeRef<*>?, private val volatileFields: List<VolatileFieldBinding>, private val methodHandlerMap: LinkedHashMap<Method, MethodHandler>,
+ /** Loaders that should read values and save them in field array on parse time. */
+ private val fieldLoaders: List<FieldLoader>, private val hasLazyFields: Boolean) {
+
+ /** Subtype aspects of the type or null */
+ val subtypeAspect = if (jsonSuperClass == null) null else ExistingSubtypeAspect(jsonSuperClass)
+
+ fun writeInstantiateCode(scope: ClassScope, out: TextOutput) {
+ writeInstantiateCode(scope, false, out)
+ }
+
+ fun writeInstantiateCode(scope: ClassScope, deferredReading: Boolean, out: TextOutput) {
+ val className = scope.getTypeImplReference(this)
+ if (deferredReading || subtypeAspect == null) {
+ out.append(className)
+ }
+ else {
+ subtypeAspect.writeInstantiateCode(className, out)
+ }
+ }
+
+ fun write(fileScope: FileScope) {
+ val out = fileScope.output
+ val valueImplClassName = fileScope.getTypeImplShortName(this)
+ out.append("private class ").append(valueImplClassName).append('(').append(JSON_READER_PARAMETER_DEF).comma().append("preReadName: String?")
+ subtypeAspect?.writeSuperFieldJava(out)
+ out.append(") : ").append(typeClass.canonicalName).openBlock()
+
+ if (hasLazyFields || JsonObjectBased::class.java.isAssignableFrom(typeClass)) {
+ out.append("private var ").append(PENDING_INPUT_READER_NAME).append(": ").append(JSON_READER_CLASS_NAME).append("? = reader.subReader()!!").newLine()
+ }
+
+ val classScope = fileScope.newClassScope()
+ for (field in volatileFields) {
+ field.writeFieldDeclaration(classScope, out)
+ out.newLine()
+ }
+
+ for (loader in fieldLoaders) {
+ if (loader.asImpl) {
+ out.append("override")
+ }
+ else {
+ out.append("private")
+ }
+ out.append(" var ").appendName(loader)
+
+ fun addType() {
+ out.append(": ")
+ loader.valueReader.appendFinishedValueTypeName(out)
+ out.append("? = null")
+ }
+
+ if (loader.valueReader is PrimitiveValueReader) {
+ val defaultValue = loader.defaultValue ?: loader.valueReader.defaultValue
+ if (defaultValue != null) {
+ out.append(" = ").append(defaultValue)
+ }
+ else {
+ addType()
+ }
+ }
+ else {
+ addType()
+ }
+ out.newLine()
+ }
+
+ if (fieldLoaders.isNotEmpty()) {
+ out.newLine()
+ }
+ writeConstructorMethod(classScope, out)
+ out.newLine()
+
+ subtypeAspect?.writeParseMethod(classScope, out)
+
+ for ((key, value) in methodHandlerMap.entries) {
+ out.newLine()
+ value.writeMethodImplementationJava(classScope, key, out)
+ out.newLine()
+ }
+
+ writeBaseMethods(out)
+ subtypeAspect?.writeGetSuperMethodJava(out)
+
+ writeEqualsMethod(valueImplClassName, out)
+
+ out.indentOut().append('}')
+ }
+
+ /**
+ * Generates Java implementation of standard methods of JSON type class (if needed):
+ * {@link org.jetbrains.jsonProtocol.JsonObjectBased#getDeferredReader()}
+ */
+ private fun writeBaseMethods(out: TextOutput) {
+ val method: Method
+ try {
+ method = typeClass.getMethod("getDeferredReader")
+ }
+ catch (ignored: NoSuchMethodException) {
+ // Method not found, skip.
+ return
+ }
+
+ out.newLine()
+ writeMethodDeclarationJava(out, method)
+ out.append(" = ").append(PENDING_INPUT_READER_NAME)
+ }
+
+ private fun writeEqualsMethod(valueImplClassName: String, out: TextOutput) {
+ if (fieldLoaders.isEmpty()) {
+ return
+ }
+
+ out.newLine().append("override fun equals(other: Any?): Boolean = ")
+ out.append("other is ").append(valueImplClassName)
+
+ // at first we should compare primitive values, then enums, then string, then objects
+ fun fieldWeight(reader: ValueReader): Int {
+ var w = 10
+ if (reader is PrimitiveValueReader) {
+ w--
+ if (reader.className != "String") {
+ w--
+ }
+ }
+ else if (reader is EnumReader) {
+ // -1 as primitive, -1 as not a string
+ w -= 2
+ }
+ return w
+ }
+
+ for (loader in fieldLoaders.sortedWith(Comparator<FieldLoader> { f1, f2 -> fieldWeight((f1.valueReader)) - fieldWeight((f2.valueReader))})) {
+ out.append(" && ")
+ out.appendName(loader).append(" == ").append("other.").appendName(loader)
+ }
+ out.newLine()
+ }
+
+ private fun writeConstructorMethod(classScope: ClassScope, out: TextOutput) {
+ out.append("init").block {
+ if (fieldLoaders.isEmpty()) {
+ out.append(READER_NAME).append(".skipValue()")
+ }
+ else {
+ out.append("var ").append(NAME_VAR_NAME).append(" = preReadName")
+ out.newLine().append("if (").append(NAME_VAR_NAME).append(" == null && reader.hasNext() && reader.beginObject().hasNext())").block {
+ out.append(NAME_VAR_NAME).append(" = reader.nextName()")
+ }
+ out.newLine()
+
+ writeReadFields(out, classScope)
+
+ // we don't read all data if we have lazy fields, so, we should not check end of stream
+ //if (!hasLazyFields) {
+ out.newLine().newLine().append(READER_NAME).append(".endObject()")
+ //}
+ }
+ }
+ }
+
+ private fun writeReadFields(out: TextOutput, classScope: ClassScope) {
+ val stopIfAllFieldsWereRead = hasLazyFields
+ val hasOnlyOneFieldLoader = fieldLoaders.size == 1
+ val isTracedStop = stopIfAllFieldsWereRead && !hasOnlyOneFieldLoader
+ if (isTracedStop) {
+ out.newLine().append("var i = 0")
+ }
+
+ out.newLine().append("loop@ while (").append(NAME_VAR_NAME).append(" != null)").block {
+ (out + "when (" + NAME_VAR_NAME + ")").block {
+ var isFirst = true
+ for (fieldLoader in fieldLoaders) {
+ if (fieldLoader.skipRead) {
+ continue
+ }
+
+ if (!isFirst) {
+ out.newLine()
+ }
+
+ out.append('"')
+ if (fieldLoader.jsonName.first() == '$') {
+ out.append('\\')
+ }
+ out.append(fieldLoader.jsonName).append('"').append(" -> ")
+
+ if (stopIfAllFieldsWereRead && !isTracedStop) {
+ out.openBlock()
+ }
+
+ val primitiveValueName = if (fieldLoader.valueReader is ObjectValueReader) fieldLoader.valueReader.primitiveValueName else null
+ if (primitiveValueName != null) {
+ out.append("if (reader.peek() == com.google.gson.stream.JsonToken.BEGIN_OBJECT)").openBlock()
+ }
+ out.appendName(fieldLoader).append(" = ")
+
+ fieldLoader.valueReader.writeReadCode(classScope, false, out)
+
+ if (primitiveValueName != null) {
+ out.closeBlock().newLine().append("else").block {
+ assignField(out, "${primitiveValueName}Type")
+ out.append("reader.peek()").newLine()
+
+ assignField(out, primitiveValueName)
+ out + "reader.nextString(true)"
+ }
+ }
+
+ if (stopIfAllFieldsWereRead && !isTracedStop) {
+ out.newLine().append(READER_NAME).append(".skipValues()").newLine().append("break@loop").closeBlock()
+ }
+
+ if (isFirst) {
+ isFirst = false
+ }
+ }
+
+ out.newLine().append("else ->")
+ if (isTracedStop) {
+ out.block {
+ out.append("reader.skipValue()")
+ out.newLine() + NAME_VAR_NAME + " = reader.nextNameOrNull()"
+ out.newLine() + "continue@loop"
+ }
+ }
+ else {
+ out.space().append("reader.skipValue()")
+ }
+ }
+
+ out.newLine() + NAME_VAR_NAME + " = reader.nextNameOrNull()"
+
+ if (isTracedStop) {
+ out.newLine().newLine().append("if (i++ == ").append(fieldLoaders.size - 1).append(")").block {
+ (out + READER_NAME + ".skipValues()").newLine() + "break"
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader/src/Util.kt b/platform/script-debugger/protocol/protocol-reader/src/Util.kt
new file mode 100644
index 00000000..994ae5e1
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/Util.kt
@@ -0,0 +1,50 @@
+package org.jetbrains.protocolReader
+
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+import java.lang.reflect.WildcardType
+
+val TYPE_FACTORY_NAME_PREFIX: Char = 'F'
+
+val READER_NAME: String = "reader"
+val PENDING_INPUT_READER_NAME: String = "inputReader"
+
+val JSON_READER_CLASS_NAME: String = "JsonReaderEx"
+val JSON_READER_PARAMETER_DEF: String = "$READER_NAME: $JSON_READER_CLASS_NAME"
+
+/**
+ * Generate Java type name of the passed type. Type may be parameterized.
+ */
+internal fun writeJavaTypeName(arg: Type, out: TextOutput) {
+ if (arg is Class<*>) {
+ val name = arg.canonicalName
+ out.append(
+ if (name == "java.util.List") "List"
+ else if (name == "java.lang.String") "String?"
+ else name
+ )
+ }
+ else if (arg is ParameterizedType) {
+ writeJavaTypeName(arg.rawType, out)
+ out.append('<')
+ val params = arg.actualTypeArguments
+ for (i in params.indices) {
+ if (i != 0) {
+ out.comma()
+ }
+ writeJavaTypeName(params[i], out)
+ }
+ out.append('>')
+ }
+ else if (arg is WildcardType) {
+ val upperBounds = arg.upperBounds!!
+ if (upperBounds.size != 1) {
+ throw RuntimeException()
+ }
+ out.append("? extends ")
+ writeJavaTypeName(upperBounds.first(), out)
+ }
+ else {
+ out.append(arg.toString())
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/protocol-reader/src/ValueReader.kt b/platform/script-debugger/protocol/protocol-reader/src/ValueReader.kt
new file mode 100644
index 00000000..a291d4e4
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/ValueReader.kt
@@ -0,0 +1,38 @@
+package org.jetbrains.protocolReader
+
+fun addReaderParameter(subtyping: Boolean, out: TextOutput) {
+ if (subtyping) {
+ out.append(PENDING_INPUT_READER_NAME).append("!!")
+ }
+ else {
+ out.append(READER_NAME)
+ }
+}
+
+/**
+ * A parser that accepts value of JSON field and outputs value in another form (e.g. string
+ * is converted to enum constant) to serve field getters in JsonType interfaces.
+ */
+internal abstract class ValueReader {
+ open fun asJsonTypeParser(): ObjectValueReader? = null
+
+ abstract fun appendFinishedValueTypeName(out: TextOutput)
+
+ open fun appendInternalValueTypeName(scope: FileScope, out: TextOutput) {
+ appendFinishedValueTypeName(out)
+ }
+
+ abstract fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput)
+
+ open fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
+ throw UnsupportedOperationException()
+ }
+
+ protected fun beginReadCall(readPostfix: String, subtyping: Boolean, out: TextOutput) {
+ out.append("read")
+ out.append(readPostfix).append('(')
+ addReaderParameter(subtyping, out)
+ }
+
+ fun isThrowsIOException() = false
+}
diff --git a/platform/script-debugger/protocol/protocol-reader/src/VolatileFieldBinding.kt b/platform/script-debugger/protocol/protocol-reader/src/VolatileFieldBinding.kt
new file mode 100644
index 00000000..1dad7920
--- /dev/null
+++ b/platform/script-debugger/protocol/protocol-reader/src/VolatileFieldBinding.kt
@@ -0,0 +1,19 @@
+package org.jetbrains.protocolReader
+
+import java.util.concurrent.atomic.AtomicReferenceArray
+
+internal class VolatileFieldBinding(private val position: Int, private val fieldTypeInfo: (scope: FileScope, out: TextOutput) -> Unit) {
+ fun get(atomicReferenceArray: AtomicReferenceArray<Any>) = atomicReferenceArray.get(position)
+
+ fun writeGetExpression(out: TextOutput) {
+ out.append("lazy_").append(position)
+ }
+
+ fun writeFieldDeclaration(scope: ClassScope, out: TextOutput) {
+ out.append("private var ")
+ writeGetExpression(out)
+ out.append(": ")
+ fieldTypeInfo(scope, out)
+ out.append("? = null")
+ }
+}
diff --git a/platform/script-debugger/protocol/schema-reader-generator/intellij.javascript.schemaReaderGenerator.iml b/platform/script-debugger/protocol/schema-reader-generator/intellij.javascript.schemaReaderGenerator.iml
new file mode 100644
index 00000000..de0edaa8
--- /dev/null
+++ b/platform/script-debugger/protocol/schema-reader-generator/intellij.javascript.schemaReaderGenerator.iml
@@ -0,0 +1,15 @@
+<?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" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="gson" level="project" />
+ <orderEntry type="module" module-name="intellij.javascript.protocolReader" />
+ <orderEntry type="library" name="KotlinJavaRuntime" level="project" />
+ <orderEntry type="module" module-name="intellij.platform.ide.impl" />
+ </component>
+</module> \ No newline at end of file
diff --git a/platform/script-debugger/protocol/schema-reader-generator/src/ProtocolMetaModel.kt b/platform/script-debugger/protocol/schema-reader-generator/src/ProtocolMetaModel.kt
new file mode 100644
index 00000000..39cd44db
--- /dev/null
+++ b/platform/script-debugger/protocol/schema-reader-generator/src/ProtocolMetaModel.kt
@@ -0,0 +1,127 @@
+package org.jetbrains.jsonProtocol
+
+import org.jetbrains.io.JsonReaderEx
+
+val STRING_TYPE: String = "string"
+val INTEGER_TYPE: String = "integer"
+val NUMBER_TYPE: String = "number"
+val BOOLEAN_TYPE: String = "boolean"
+public val OBJECT_TYPE: String = "object"
+val ARRAY_TYPE: String = "array"
+val UNKNOWN_TYPE: String = "unknown"
+val ANY_TYPE: String = "any"
+
+interface ItemDescriptor {
+ val description: String?
+
+ val type: String?
+
+ val enum: List<String>?
+
+ val items: ProtocolMetaModel.ArrayItemType?
+
+ interface Named : Referenceable {
+ fun name(): String
+
+ val shortName: String?
+
+ val optional: Boolean
+ }
+
+ interface Referenceable : ItemDescriptor {
+ @ProtocolName("\$ref")
+ val ref: String?
+ }
+
+ interface Type : ItemDescriptor {
+ val properties: List<ProtocolMetaModel.ObjectProperty>?
+ }
+}
+
+interface ProtocolSchemaReader {
+ @JsonParseMethod
+ fun parseRoot(reader: JsonReaderEx): ProtocolMetaModel.Root
+}
+
+/**
+ * Defines schema of WIP metamodel defined in http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/inspector/Inspector.json
+ */
+interface ProtocolMetaModel {
+ @JsonType
+ interface Root {
+ val version: Version?
+
+ fun domains(): List<Domain>
+ }
+
+ interface Version {
+ fun major(): String
+ fun minor(): String
+ }
+
+ interface Domain {
+ fun domain(): String
+
+ val types: List<StandaloneType>?
+
+ fun commands(): List<Command>
+
+ val events: List<Event>?
+
+ val description: String?
+
+ val hidden: Boolean
+
+ val experimental: Boolean
+ }
+
+ interface Command {
+ fun name(): String
+
+ val parameters: List<Parameter>?
+
+ val returns: List<Parameter>?
+
+ val description: String?
+
+ val hidden: Boolean
+
+ val async: Boolean
+ }
+
+ interface Parameter : ItemDescriptor.Named {
+ val hidden: Boolean
+
+ @JsonField(allowAnyPrimitiveValue = true)
+ val default: String?
+ }
+
+ interface Event {
+ fun name(): String
+
+ val parameters: List<Parameter>?
+
+ val description: String?
+
+ val hidden: Boolean
+
+ val optionalData: Boolean
+ }
+
+ @JsonType
+ interface StandaloneType : ItemDescriptor.Type {
+ fun id(): String
+
+ val hidden: Boolean
+ }
+
+ interface ArrayItemType : ItemDescriptor.Type, ItemDescriptor.Referenceable {
+ val optional: Boolean
+ }
+
+ interface ObjectProperty : ItemDescriptor.Named {
+ override fun name(): String
+
+ val hidden: Boolean
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/protocol/schema-reader-generator/src/SchemaReaderGenerator.kt b/platform/script-debugger/protocol/schema-reader-generator/src/SchemaReaderGenerator.kt
new file mode 100644
index 00000000..06236fbc
--- /dev/null
+++ b/platform/script-debugger/protocol/schema-reader-generator/src/SchemaReaderGenerator.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.jsonProtocol
+
+import org.jetbrains.protocolReader.GenerateConfiguration
+import org.jetbrains.protocolReader.generate
+
+fun main(args: Array<String>) {
+ generate(if (args.isEmpty()) arrayOf("--output-dir=community/platform/script-debugger/protocol/protocol-model-generator/generated") else args,
+ GenerateConfiguration("org.jetbrains.jsonProtocol",
+ "ProtocolSchemaReaderImpl",
+ ProtocolSchemaReader::class.java,
+ ProtocolMetaModel::class.java.declaredClasses.asList()))
+} \ No newline at end of file