summaryrefslogtreecommitdiff
path: root/platform/script-debugger/backend/src/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/backend/src/debugger
New upstream version 0~183.5153.4+dfsg
Diffstat (limited to 'platform/script-debugger/backend/src/debugger')
-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
55 files changed, 3687 insertions, 0 deletions
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