Skip to content

Commit

Permalink
Collapse and expand variables in debugger view
Browse files Browse the repository at this point in the history
  • Loading branch information
Nimaoth committed Jun 12, 2024
1 parent 81d0a1b commit 8f1668f
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 16 deletions.
13 changes: 13 additions & 0 deletions config/default_config.nim
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,21 @@ proc loadDefaultKeybindings*(clearExisting: bool = false) =

addCommand "debugger", "<C-k>", "prev-debugger-view"
addCommand "debugger", "<C-h>", "next-debugger-view"

addCommand "debugger", "<UP>", "prev-variable"
addCommand "debugger", "<DOWN>", "next-variable"
addCommand "debugger", "<RIGHT>", "expand-variable"
addCommand "debugger", "<LEFT>", "collapse-variable"
addCommand "debugger", "<HOME>", "select-first-variable"
addCommand "debugger", "<END>", "select-last-variable"

addCommandBlock "debugger", "<C-u>":
for i in 0..10:
prevVariable()

addCommandBlock "debugger", "<C-d>":
for i in 0..10:
nextVariable()

# addCommand "editor.text", "<C-SPACE>ts", "reload-treesitter"

Expand Down
35 changes: 35 additions & 0 deletions scripting/debugger_api_wasm.nim
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@ proc nextDebuggerView*() =
argsJsonString.cstring)


proc debugger_selectFirstVariable_void_Debugger_wasm(arg: cstring): cstring {.
importc.}
proc selectFirstVariable*() =
var argsJson = newJArray()
let argsJsonString = $argsJson
let res {.used.} = debugger_selectFirstVariable_void_Debugger_wasm(
argsJsonString.cstring)


proc debugger_selectLastVariable_void_Debugger_wasm(arg: cstring): cstring {.
importc.}
proc selectLastVariable*() =
var argsJson = newJArray()
let argsJsonString = $argsJson
let res {.used.} = debugger_selectLastVariable_void_Debugger_wasm(
argsJsonString.cstring)


proc debugger_prevVariable_void_Debugger_wasm(arg: cstring): cstring {.importc.}
proc prevVariable*() =
var argsJson = newJArray()
Expand All @@ -38,6 +56,23 @@ proc nextVariable*() =
argsJsonString.cstring)


proc debugger_expandVariable_void_Debugger_wasm(arg: cstring): cstring {.importc.}
proc expandVariable*() =
var argsJson = newJArray()
let argsJsonString = $argsJson
let res {.used.} = debugger_expandVariable_void_Debugger_wasm(
argsJsonString.cstring)


proc debugger_collapseVariable_void_Debugger_wasm(arg: cstring): cstring {.
importc.}
proc collapseVariable*() =
var argsJson = newJArray()
let argsJsonString = $argsJson
let res {.used.} = debugger_collapseVariable_void_Debugger_wasm(
argsJsonString.cstring)


proc debugger_stopDebugSession_void_Debugger_wasm(arg: cstring): cstring {.
importc.}
proc stopDebugSession*() =
Expand Down
84 changes: 77 additions & 7 deletions src/text/language/debugger.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import std/[strutils, options, json, tables, sugar, strtabs, streams]
import std/[strutils, options, json, tables, sugar, strtabs, streams, sets]
import misc/[id, custom_async, custom_logger, util, connection, myjsonutils, event, response]
import scripting/expose
import dap_client, dispatch_tables, app_interface, config_provider, selector_popup_builder, events, view
Expand Down Expand Up @@ -34,15 +34,17 @@ type
activeView*: ActiveView = Variables
currentThreadIndex: int
currentFrameIndex: int
variablesCursor: VariableCursor
variablesScrollOffset*: float
maxVariablesScrollOffset*: float
eventHandler: EventHandler
threadsEventHandler: EventHandler
stackTraceEventHandler: EventHandler
variablesEventHandler: EventHandler
outputEventHandler: EventHandler

variablesCursor: VariableCursor
variablesScrollOffset*: float
collapsedVariables: HashSet[VariablesReference]

lastEditor: Option[TextDocumentEditor]
outputEditor*: TextDocumentEditor

Expand All @@ -57,6 +59,7 @@ type

proc applyBreakpointSignsToEditor(self: Debugger, editor: TextDocumentEditor)
proc handleAction(self: Debugger, action: string, arg: string): EventResponse
proc updateVariables(self: Debugger, variablesReference: VariablesReference, maxDepth: int) {.async.}

var gDebugger: Debugger = nil

Expand Down Expand Up @@ -98,7 +101,7 @@ proc handleEditorRegistered*(self: Debugger, editor: DocumentEditor) =
self.applyBreakpointSignsToEditor(editor)

proc createDebugger*(app: AppInterface, state: JsonNode) =
gDebugger = Debugger(app: app)
gDebugger = Debugger(app: app, collapsedVariables: initHashSet[VariablesReference]())

let document = newTextDocument(app.configProvider, createLanguageServer=false)
gDebugger.outputEditor = newTextEditor(document, app, app.configProvider)
Expand Down Expand Up @@ -140,6 +143,9 @@ proc getStackTrace*(self: Debugger, threadId: ThreadId): Option[StackTraceRespon
return self.stackTraces[threadId].some
return StackTraceResponse.none

proc isCollapsed*(self: Debugger, variablesReference: VariablesReference): bool =
variablesReference in self.collapsedVariables

proc prevDebuggerView*(self: Debugger) {.expose("debugger").} =
self.activeView = case self.activeView
of Threads: ActiveView.Output
Expand All @@ -161,6 +167,10 @@ proc isSelected*(self: Debugger, r: VariablesReference, index: int): bool =
proc isScopeSelected*(self: Debugger, index: int): bool =
return self.variablesCursor.path.len == 0 and self.variablesCursor.scope == index

proc selectedVariable*(self: Debugger): Option[tuple[index: int, varRef: VariablesReference]] =
if self.variablesCursor.path.len > 0:
return self.variablesCursor.path[self.variablesCursor.path.high].some

proc reevaluateCursorRefs*(self: Debugger, cursor: VariableCursor): VariableCursor =
result.scope = cursor.scope.clamp(0, self.scopes.scopes.high)
if self.scopes.scopes.len == 0:
Expand Down Expand Up @@ -209,6 +219,10 @@ proc lastChild*(self: Debugger, cursor: VariableCursor): VariableCursor =
result = cursor
if result.path.len == 0:
let scope = self.scopes.scopes[result.scope]

if self.isCollapsed(scope.variablesReference):
return

if not self.variables.contains(scope.variablesReference):
return

Expand All @@ -225,11 +239,26 @@ proc lastChild*(self: Debugger, cursor: VariableCursor): VariableCursor =
if variables.variables.len == 0:
return
let childRef = variables.variables[index.clamp(0, variables.variables.high)].variablesReference
if self.isCollapsed(childRef):
return
if not self.variables.contains(childRef) or self.variables[childRef].variables.len == 0:
return

result.path.add (self.variables[childRef].variables.high, childRef)

proc selectFirstVariable*(self: Debugger) {.expose("debugger").} =
self.variablesScrollOffset = 0
self.variablesCursor = VariableCursor()

proc selectLastVariable*(self: Debugger) {.expose("debugger").} =
if self.scopes.scopes.len == 0 or self.variables.len == 0:
self.variablesScrollOffset = 0
self.variablesCursor = VariableCursor()
return

self.variablesCursor = self.lastChild(VariableCursor(scope: self.scopes.scopes.high))
self.variablesScrollOffset = self.maxVariablesScrollOffset

proc prevVariable*(self: Debugger) {.expose("debugger").} =
if self.scopes.scopes.len == 0 or self.variables.len == 0:
return
Expand Down Expand Up @@ -279,7 +308,10 @@ proc nextVariable*(self: Debugger) {.expose("debugger").} =

if self.variablesCursor.path.len == 0:
let scope = self.scopes.scopes[self.variablesCursor.scope]
if self.variables.contains(scope.variablesReference) and self.variables[scope.variablesReference].variables.len > 0:
let collapsed = self.isCollapsed(scope.variablesReference)
if self.variables.contains(scope.variablesReference) and
self.variables[scope.variablesReference].variables.len > 0 and
not collapsed:
self.variablesCursor.path.add (0, scope.variablesReference)
if self.variablesScrollOffset < self.maxVariablesScrollOffset:
self.variablesScrollOffset += self.app.platform.totalLineHeight
Expand All @@ -302,8 +334,10 @@ proc nextVariable*(self: Debugger) {.expose("debugger").} =

if index < variables.variables.len:
let childrenRef = variables.variables[index].variablesReference
let collapsed = self.isCollapsed(childrenRef)
if descending and childrenRef != 0.VariablesReference and self.variables.contains(childrenRef) and
self.variables[childrenRef].variables.len > 0:
self.variables[childrenRef].variables.len > 0 and
not collapsed:
self.variablesCursor.path.add (0, childrenRef)
if self.variablesScrollOffset < self.maxVariablesScrollOffset:
self.variablesScrollOffset += self.app.platform.totalLineHeight
Expand All @@ -329,6 +363,42 @@ proc nextVariable*(self: Debugger) {.expose("debugger").} =
path: @[(int.high, self.scopes.scopes[self.scopes.scopes.high].variablesReference)],
))

proc expandVariable*(self: Debugger) {.expose("debugger").} =
if self.scopes.scopes.len == 0 or self.variables.len == 0:
return

self.clampCurrentCursor()
if self.selectedVariable().getSome(v):
if self.variables.contains(v.varRef):
let va {.cursor.} = self.variables[v.varRef].variables[v.index]

if va.variablesReference != 0.VariablesReference:
self.collapsedVariables.excl va.variablesReference
asyncCheck self.updateVariables(va.variablesReference, 0)

else:
self.collapsedVariables.excl self.scopes.scopes[self.variablesCursor.scope].variablesReference

proc collapseVariable*(self: Debugger) {.expose("debugger").} =
if self.scopes.scopes.len == 0 or self.variables.len == 0:
return

self.clampCurrentCursor()
if self.selectedVariable().getSome(v):
if self.variables.contains(v.varRef):
let va {.cursor.} = self.variables[v.varRef].variables[v.index]

if va.variablesReference == 0.VariablesReference or
va.variablesReference in self.collapsedVariables or
not self.variables.contains(va.variablesReference):
discard self.variablesCursor.path.pop

elif va.variablesReference != 0.VariablesReference:
self.collapsedVariables.incl va.variablesReference

else:
self.collapsedVariables.incl self.scopes.scopes[self.variablesCursor.scope].variablesReference

proc stopDebugSession*(self: Debugger) {.expose("debugger").} =
debugf"[stopDebugSession] Stopping session"
if self.client.isNone:
Expand Down Expand Up @@ -465,7 +535,7 @@ proc updateScopes(self: Debugger, threadId: ThreadId) {.async.} =
self.scopes = scopes.result
let futures = collect:
for scope in self.scopes.scopes:
self.updateVariables(scope.variablesReference, 2)
self.updateVariables(scope.variablesReference, 1)

await futures.all

Expand Down
44 changes: 35 additions & 9 deletions src/ui/widget_builder_debugger.nim
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,21 @@ proc createVariables*(self: DebuggerView, builder: UINodeBuilder, app: App, debu

let variables {.cursor.} = debugger.variables[variablesReference]
for i, variable in variables.variables:
let collapsed = debugger.isCollapsed(variable.variablesReference)
let hasChildren = variable.variablesReference != 0.VariablesReference
let childrenCached = debugger.variables.contains(variable.variablesReference)
let showChildren = hasChildren and childrenCached and not collapsed

builder.panel(&{SizeToContentY, FillX, LayoutHorizontal}):
let typeText = variable.`type`.mapIt(": " & it).get("")
let text = fmt"{variable.name}{typeText} = {variable.value}"
let collapsedText = if hasChildren and showChildren:
"-"
elif hasChildren and not showChildren:
"+"
else:
" "

let text = fmt"{collapsedText} {variable.name}{typeText} = {variable.value}"

let isSelected = debugger.isSelected(variablesReference, i)

Expand All @@ -132,11 +144,11 @@ proc createVariables*(self: DebuggerView, builder: UINodeBuilder, app: App, debu
output.selectedNode = currentNode
else:
builder.panel(&{SizeToContentY, SizeToContentX, DrawText}, text = text, textColor = textColor)
# builder.panel(&{SizeToContentY, SizeToContentX, DrawText}, text = variable.name, textColor = textColor)
# builder.panel(&{SizeToContentY, SizeToContentX, DrawText}, text = " = ", textColor = textColor)
# builder.panel(&{SizeToContentY, SizeToContentX, DrawText}, text = variable.value, textColor = textColor)
if variable.variablesReference != 0.VariablesReference and
debugger.variables.contains(variable.variablesReference):

if collapsed:
builder.panel(&{SizeToContentY, SizeToContentX, DrawText}, text = "...", textColor = textColor)

if showChildren:
builder.panel(&{SizeToContentY, FillX, LayoutVertical}, x = 2 * builder.charWidth):
self.createVariables(builder, app, debugger, variable.variablesReference, backgroundColor,
selectedBackgroundColor, textColor, output)
Expand All @@ -147,15 +159,29 @@ proc createScope*(self: DebuggerView, builder: UINodeBuilder, app: App, debugger

let scope {.cursor.} = debugger.scopes.scopes[scopeId]
builder.panel(&{SizeToContentY, FillX, LayoutVertical}):
let collapsed = debugger.isCollapsed(scope.variablesReference)
let hasChildren = scope.variablesReference != 0.VariablesReference
let childrenCached = debugger.variables.contains(scope.variablesReference)
let showChildren = hasChildren and childrenCached and not collapsed

let collapsedText = if hasChildren and showChildren:
"-"
elif hasChildren and not showChildren:
"+"
else:
" "

let text = &"{collapsedText} {scope.name}"

let isSelected = debugger.isScopeSelected(scopeId)
if isSelected:
builder.panel(&{SizeToContentY, FillX, FillBackground}, backgroundColor = color(0.6, 0.5, 0.2, 0.3)):
builder.panel(&{SizeToContentY, FillX, DrawText}, text = scope.name, textColor = textColor)
builder.panel(&{SizeToContentY, FillX, DrawText}, text = text, textColor = textColor)
output.selectedNode = currentNode
else:
builder.panel(&{SizeToContentY, FillX, DrawText}, text = scope.name, textColor = textColor)
builder.panel(&{SizeToContentY, FillX, DrawText}, text = text, textColor = textColor)

if debugger.variables.contains(scope.variablesReference):
if showChildren:
builder.panel(&{SizeToContentY, FillX, LayoutVertical}, x = 2 * builder.charWidth):
self.createVariables(builder, app, debugger, scope.variablesReference, backgroundColor,
headerColor, textColor, output)
Expand Down

0 comments on commit 8f1668f

Please sign in to comment.