Skip to content

Commit 06a0576

Browse files
committed
enhance the debuger by adding variables and call stack
1 parent 761790f commit 06a0576

File tree

2 files changed

+247
-90
lines changed

2 files changed

+247
-90
lines changed

lib/python-debugger-view.coffee

+232-87
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ class PythonDebuggerView extends View
1313
debuggedFileArgs: []
1414
backendDebuggerPath: null
1515
backendDebuggerName: "atom_pdb.py"
16+
flagActionStarted: false
17+
flagVarsNeedUpdate: false
18+
flagCallstackNeedsUpdate: false
19+
# 0 - normal output, 1 - print variables, 2 - print call stack
20+
currentState: 0
21+
varScrollTop: 0
22+
callStackScrollTop: 0
1623

1724
getCurrentFilePath: ->
1825
editor = atom.workspace.getActivePaneItem()
@@ -32,42 +39,90 @@ class PythonDebuggerView extends View
3239
@subview "commandEntryView", new TextEditorView
3340
mini: true,
3441
placeholderText: "> Enter debugger commands here"
35-
@button outlet: "breakpointBtn", click: "toggleBreak", class: "btn", =>
36-
@span "break point"
37-
@button class: "btn", =>
38-
@span " "
39-
@button outlet: "runBtn", click: "runApp", class: "btn", =>
40-
@span "run"
41-
@button outlet: "stopBtn", click: "stopApp", class: "btn", =>
42-
@span "stop"
43-
@button class: "btn", =>
44-
@span " "
45-
@button outlet: "stepOverBtn", click: "stepOverBtnPressed", class: "btn", =>
46-
@span "next"
47-
@button outlet: "stepInBtn", click: "stepInBtnPressed", class: "btn", =>
48-
@span "step"
49-
@button outlet: "varBtn", click: "varBtnPressed", class: "btn", =>
50-
@span "variables"
51-
@button class: "btn", =>
52-
@span " "
53-
@button outlet: "returnBtn", click: "returnBtnPressed", class: "btn", =>
54-
@span "return"
55-
@button outlet: "continueBtn", click: "continueBtnPressed", class: "btn", =>
56-
@span "continue"
57-
@button class: "btn", =>
58-
@span " "
59-
@button outlet: "upBtn", click: "upBtnPressed", class: "btn", =>
60-
@span "up"
61-
@button outlet: "callstackBtn", click: "callstackBtnPressed", class: "btn", =>
62-
@span "callstack"
63-
@button outlet: "downBtn", click: "downBtnPressed", class: "btn", =>
64-
@span "down"
65-
@button class: "btn", =>
66-
@span " "
67-
@button outlet: "clearBtn", click: "clearOutput", class: "btn", =>
68-
@span "clear"
69-
@div class: "panel-body", outlet: "outputContainer", =>
70-
@pre class: "command-output", outlet: "output"
42+
@div class: "btn-toolbar", =>
43+
@div class: "btn-group", =>
44+
@button outlet: "breakpointBtn", click: "toggleBreak", class: "btn", =>
45+
@span "break point"
46+
@div class: "btn-group", =>
47+
@button outlet: "runBtn", click: "runApp", class: "btn", =>
48+
@span "run"
49+
@button outlet: "stopBtn", click: "stopApp", class: "btn", =>
50+
@span "stop"
51+
@div class: "btn-group", =>
52+
@button outlet: "stepOverBtn", click: "stepOverBtnPressed", class: "btn", =>
53+
@span "next"
54+
@button outlet: "stepInBtn", click: "stepInBtnPressed", class: "btn", =>
55+
@span "step"
56+
@button outlet: "returnBtn", click: "returnBtnPressed", class: "btn", =>
57+
@span "return"
58+
@button outlet: "continueBtn", click: "continueBtnPressed", class: "btn", =>
59+
@span "continue"
60+
@div class: "btn-group", =>
61+
@button outlet: "upBtn", click: "upBtnPressed", class: "btn", =>
62+
@span "up"
63+
@button outlet: "downBtn", click: "downBtnPressed", class: "btn", =>
64+
@span "down"
65+
@div class: "btn-group", =>
66+
@button outlet: "clearBtn", click: "clearOutput", class: "btn", =>
67+
@span "clear"
68+
@input class : "input-checkbox", type: "checkbox", id: "ck_input", outlet: "showInput", click: "toggleInput"
69+
@label class : "label", for: "ck_input", =>
70+
@span "Input"
71+
@input class : "input-checkbox", type: "checkbox", id: "ck_vars", outlet: "showVars", click: "toggleVars"
72+
@label class : "label", for: "ck_vars", =>
73+
@span "Variables"
74+
@input class : "input-checkbox", type: "checkbox", id: "ck_callstack", outlet: "showCallstack", click: "toggleCallstack"
75+
@label class : "label", for: "ck_callstack", =>
76+
@span "Call stack"
77+
@div class: "block", outlet: "bottomPane", =>
78+
@div class: "inline-block panel", id: "outputPane", outlet: "outputPane", =>
79+
@pre class: "command-output", outlet: "output"
80+
@div class: "inline-block panel", id: "variablesPane", outlet: "variablesPane", =>
81+
@pre class: "command-output", outlet: "variables"
82+
@div class: "inline-block panel", id: "callstackPane", outlet: "callstackPane", =>
83+
@pre class: "command-output", outlet: "callstack"
84+
85+
toggleInput: ->
86+
if @backendDebugger
87+
@argsEntryView.hide()
88+
if @showInput.prop('checked')
89+
@commandEntryView.show()
90+
else
91+
@commandEntryView.hide()
92+
else
93+
if @showInput.prop('checked')
94+
@argsEntryView.show()
95+
else
96+
@argsEntryView.hide()
97+
@commandEntryView.hide()
98+
99+
toggleVars: ->
100+
@togglePanes()
101+
102+
toggleCallstack: ->
103+
@togglePanes()
104+
105+
togglePanes: ->
106+
n = 1
107+
if @showVars.prop('checked')
108+
@variablesPane.show()
109+
n = n+1
110+
else
111+
@variablesPane.hide()
112+
if @showCallstack.prop('checked')
113+
@callstackPane.show()
114+
n = n+1
115+
else
116+
@callstackPane.hide()
117+
width = ''+(100/n)+'%'
118+
@outputPane.css('width', width)
119+
if @showVars.prop('checked')
120+
@variablesPane.css('width', width)
121+
if @showCallstack.prop('checked')
122+
@callstackPane.css('width', width)
123+
# the following statements are used to update the information in the variables/callstack
124+
@setFlags()
125+
@backendDebugger?.stdin.write("print 'display option changed.'\n")
71126

72127
toggleBreak: ->
73128
editor = atom.workspace.getActiveTextEditor()
@@ -81,30 +136,54 @@ class PythonDebuggerView extends View
81136
for breakpoint in @breakpointStore.breakpoints
82137
@output.append(breakpoint.toCommand() + "\n")
83138

84-
upBtnPressed: ->
85-
@output.empty()
86-
@backendDebugger?.stdin.write("up\nbt\n")
87-
88-
callstackBtnPressed: ->
89-
@output.empty()
90-
@backendDebugger?.stdin.write("bt\n")
91-
92-
downBtnPressed: ->
93-
@output.empty()
94-
@backendDebugger?.stdin.write("down\nbt\n")
95-
96139
stepOverBtnPressed: ->
140+
@setFlags()
97141
@backendDebugger?.stdin.write("n\n")
98142

99143
stepInBtnPressed: ->
144+
@setFlags()
100145
@backendDebugger?.stdin.write("s\n")
101146

102147
continueBtnPressed: ->
148+
@setFlags()
103149
@backendDebugger?.stdin.write("c\n")
104150

105151
returnBtnPressed: ->
152+
@setFlags()
106153
@backendDebugger?.stdin.write("r\n")
107154

155+
upBtnPressed: ->
156+
@setFlags()
157+
@backendDebugger?.stdin.write("up\n")
158+
159+
downBtnPressed: ->
160+
@setFlags()
161+
@backendDebugger?.stdin.write("down\n")
162+
163+
printVars: ->
164+
@variables.empty()
165+
@backendDebugger?.stdin.write("print ('@{variables_start}')\n")
166+
@backendDebugger?.stdin.write("for (__k, __v) in [(__k, __v) for __k, __v in globals().items() if not __k.startswith('__')]: print __k, '=', __v\n")
167+
@backendDebugger?.stdin.write("print '-------------'\n")
168+
@backendDebugger?.stdin.write("for (__k, __v) in [(__k, __v) for __k, __v in locals().items() if __k != 'self' and not __k.startswith('__')]: print __k, '=', __v\n")
169+
@backendDebugger?.stdin.write("for (__k, __v) in [(__k, __v) for __k, __v in (self.__dict__ if 'self' in locals().keys() else {}).items()]: print 'self.{0}'.format(__k), '=', __v\n")
170+
@backendDebugger?.stdin.write("print ('@{variables_end}')\n")
171+
172+
printCallstack: ->
173+
@callstack.empty()
174+
@backendDebugger?.stdin.write("print ('@{callstack_start}')\n")
175+
@backendDebugger?.stdin.write("bt\n")
176+
@backendDebugger?.stdin.write("print ('@{callstack_end}')\n")
177+
178+
setFlags: ->
179+
@flagActionStarted = true
180+
if @showVars.prop('checked')
181+
@varScrollTop = @variables.prop('scrollTop')
182+
@flagVarsNeedUpdate = true
183+
if @showCallstack.prop('checked')
184+
@callStackScrollTop = @callstack.prop('scrollTop')
185+
@flagCallstackNeedsUpdate = true
186+
108187
workspacePath: ->
109188
editor = atom.workspace.getActiveTextEditor()
110189
activePath = editor.getPath()
@@ -120,41 +199,52 @@ class PythonDebuggerView extends View
120199
if @pathsNotSet()
121200
@askForPaths()
122201
return
202+
@setFlags()
123203
@runBackendDebugger()
204+
@toggleInput()
124205

125-
varBtnPressed: ->
126-
@output.empty()
206+
highlightLineInEditor: (fileName, lineNumber) ->
207+
if lineNumber && fileName
208+
lineNumber = parseInt(lineNumber)
209+
editor = atom.workspace.getActiveTextEditor()
210+
if fileName.toLowerCase() == editor.getPath().toLowerCase()
211+
position = Point(lineNumber-1, 0)
212+
editor.setCursorBufferPosition(position)
213+
editor.unfoldBufferRow(lineNumber)
214+
editor.scrollToBufferPosition(position)
215+
else
216+
options = {initialLine: lineNumber-1, initialColumn:0}
217+
atom.workspace.open(fileName, options) if fs.existsSync(fileName)
218+
# TODO: add decoration to current line?
127219

128-
@backendDebugger?.stdin.write("for (__k, __v) in [(__k, __v) for __k, __v in globals().items() if not __k.startswith('__')]: print __k, '=', __v\n")
129-
@backendDebugger?.stdin.write("print '-------------'\n")
130-
@backendDebugger?.stdin.write("for (__k, __v) in [(__k, __v) for __k, __v in locals().items() if __k != 'self' and not __k.startswith('__')]: print __k, '=', __v\n")
131-
@backendDebugger?.stdin.write("for (__k, __v) in [(__k, __v) for __k, __v in (self.__dict__ if 'self' in locals().keys() else {}).items()]: print 'self.{0}'.format(__k), '=', __v\n")
132-
133-
220+
processNormalOutput: (data_str) ->
134221

135-
# Extract the file name and line number output by the debugger.
136-
processDebuggerOutput: (data) ->
137-
data_str = data.toString().trim()
138222
lineNumber = null
139223
fileName = null
140-
call_stack_str = "Call stack: \n"
141224

142-
m = /[^-]> (.*[.]py)[(]([0-9]*)[)].*/.exec(data_str)
143-
if m
144-
[fileName, lineNumber] = [m[1], m[2]]
145-
`
146-
re = /[\n](>*)[ \t]*(.*[.]py)[(]([0-9]*)[)]([^\n]*)[\n]([^\n]*)/gi;
147-
while ((match = re.exec(data_str)))
148-
{
149-
if (match[1].includes('>'))
150-
call_stack_str += '--> ';
151-
else
152-
call_stack_str += ' ';
153-
call_stack_str += match[5].replace("->", "") + " in " + match[4] + " @ " + match [2] + ": " + match[3] + "\n";
154-
}
155-
`
156-
data_str = call_stack_str
157-
225+
# print the action_end string
226+
if @flagActionStarted
227+
@backendDebugger?.stdin.write("print ('@{action_end}')\n")
228+
@flagActionStarted = false
229+
230+
# detect predefined flag strings
231+
isActionEnd = data_str.includes('@{action_end}')
232+
isVarsStart = data_str.includes('@{variables_start}')
233+
isCallstackStart = data_str.includes('@{callstack_start}')
234+
235+
# variables print started
236+
if isVarsStart
237+
@currentState = 1
238+
@processVariables(data_str)
239+
return
240+
241+
# call stack print started
242+
if isCallstackStart
243+
@currentState = 2
244+
@processCallstack(data_str)
245+
return
246+
247+
# handle normal output
158248
[data_str, tail] = data_str.split("line:: ")
159249
if tail
160250
[lineNumber, tail] = tail.split("\n")
@@ -167,20 +257,72 @@ class PythonDebuggerView extends View
167257
fileName = fileName.trim() if fileName
168258
fileName = null if fileName == "<string>"
169259

260+
# highlight the current line
170261
if lineNumber && fileName
171-
lineNumber = parseInt(lineNumber)
172-
editor = atom.workspace.getActiveTextEditor()
173-
if fileName.toLowerCase() == editor.getPath().toLowerCase()
174-
position = Point(lineNumber-1, 0)
175-
editor.setCursorBufferPosition(position)
176-
editor.unfoldBufferRow(lineNumber)
177-
editor.scrollToBufferPosition(position)
262+
@highlightLineInEditor(fileName, lineNumber)
263+
264+
# print the output
265+
@addOutput(data_str.trim().replace('@{action_end}', ''))
266+
267+
# if action end, trigger the follow up actions
268+
if isActionEnd
269+
if @flagVarsNeedUpdate
270+
@printVars()
271+
@flagVarsNeedUpdate = false
178272
else
179-
options = {initialLine: lineNumber-1, initialColumn:0}
180-
atom.workspace.open(fileName, options) if fs.existsSync(fileName)
181-
# TODO: add decoration to current line?
273+
if @flagCallstackNeedsUpdate
274+
@printCallstack()
275+
@flagCallstackNeedsUpdate = false
276+
277+
processVariables: (data_str) ->
278+
isVarsEnd = data_str.includes('@{variables_end}')
279+
for line in data_str.split '\n'
280+
if ! line.includes("@{variable")
281+
@variables.append(@createOutputNode(line))
282+
@variables.append('\n')
283+
if isVarsEnd
284+
@variables.prop('scrollTop', @varScrollTop)
285+
@currentState = 0
286+
if @flagCallstackNeedsUpdate
287+
@printCallstack()
288+
@flagCallstackNeedsUpdate = false
289+
290+
processCallstack: (data_str) ->
291+
lineNumber = null
292+
fileName = null
293+
isCallstackEnd = data_str.includes('@{callstack_end}')
294+
m = /[^-]> (.*[.]py)[(]([0-9]*)[)].*/.exec(data_str)
295+
if m
296+
[fileName, lineNumber] = [m[1], m[2]]
297+
callstack_pre = @callstack
298+
`
299+
re = /[\n](>*)[ \t]*(.*[.]py)[(]([0-9]*)[)]([^\n]*)[\n]([^\n]*)/gi;
300+
while ((match = re.exec(data_str)))
301+
{
302+
if (match[5].includes('exec cmd in globals, locals')) continue;
303+
if (match[1].includes('>'))
304+
item = "<b><u>"+match[5].replace("->", "")+"</u></b>";
305+
else
306+
item = match[5].replace("->", "");
307+
callstack_pre.append(item);
308+
callstack_pre.append('\n');
309+
}
310+
`
311+
if lineNumber && fileName
312+
@highlightLineInEditor(fileName, lineNumber)
313+
if isCallstackEnd
314+
@currentState = 0
315+
@callstack.prop('scrollTop', @callStackScrollTop)
182316

183-
@addOutput(data_str.trim())
317+
# Extract the file name and line number output by the debugger.
318+
processDebuggerOutput: (data) ->
319+
data_str = data.toString().trim()
320+
if @currentState == 1
321+
@processVariables(data_str)
322+
else if @currentState == 2
323+
@processCallstack(data_str)
324+
else
325+
@processNormalOutput(data_str)
184326

185327
runBackendDebugger: ->
186328
args = [path.join(@backendDebuggerPath, @backendDebuggerName)]
@@ -208,6 +350,7 @@ class PythonDebuggerView extends View
208350
@backendDebugger?.stdin.write("\nexit()\n")
209351
@backendDebugger = null
210352
console.log "debugger stopped"
353+
@toggleInput()
211354

212355
clearOutput: ->
213356
@output.empty()
@@ -234,6 +377,8 @@ class PythonDebuggerView extends View
234377
@breakpointStore = breakpointStore
235378
@debuggedFileName = @getCurrentFilePath()
236379
@backendDebuggerPath = @getDebuggerPath()
380+
@toggleInput()
381+
@togglePanes()
237382
@addOutput("Welcome to Python Debugger for Atom!")
238383
@addOutput("The file being debugged is: " + @debuggedFileName)
239384
@askForPaths()

0 commit comments

Comments
 (0)