Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement #33

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/breakpoint-store.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class BreakpointStore

addDecoration = true
if breakpointSearched
@breakpoints.splice(breakpointSearched, 1)
@breakpoints.splice(@breakpoints.indexOf(breakpointSearched), 1)
addDecoration = false
else
@breakpoints.push(breakpoint)
Expand All @@ -21,12 +21,14 @@ class BreakpointStore
d = editor.decorateMarker(marker, type: "line-number", class: "line-number-red")
d.setProperties(type: "line-number", class: "line-number-red")
breakpoint.decoration = d
return "b"
else
editor = atom.workspace.getActiveTextEditor()
ds = editor.getLineNumberDecorations(type: "line-number", class: "line-number-red")
for d in ds
marker = d.getMarker()
marker.destroy() if marker.getBufferRange().start.row == breakpoint.lineNumber-1
return "cl"

containsBreakpoint: (bp) ->
for breakpoint in @breakpoints
Expand Down
272 changes: 248 additions & 24 deletions lib/python-debugger-view.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ class PythonDebuggerView extends View
debuggedFileArgs: []
backendDebuggerPath: null
backendDebuggerName: "atom_pdb.py"
flagActionStarted: false
flagVarsNeedUpdate: false
flagCallstackNeedsUpdate: false
# 0 - normal output, 1 - print variables, 2 - print call stack
currentState: 0
varScrollTop: 0
callStackScrollTop: 0

getCurrentFilePath: ->
editor = atom.workspace.getActivePaneItem()
Expand All @@ -32,35 +39,151 @@ class PythonDebuggerView extends View
@subview "commandEntryView", new TextEditorView
mini: true,
placeholderText: "> Enter debugger commands here"
@button outlet: "runBtn", click: "runApp", class: "btn", =>
@span "run"
@button outlet: "stopBtn", click: "stopApp", class: "btn", =>
@span "stop"
@button outlet: "clearBtn", click: "clearOutput", class: "btn", =>
@span "clear"
@button outlet: "stepOverBtn", click: "stepOverBtnPressed", class: "btn", =>
@span "next"
@button outlet: "stepInBtn", click: "stepInBtnPressed", class: "btn", =>
@span "step"
@button outlet: "continueBtn", click: "continueBtnPressed", class: "btn", =>
@span "continue"
@button outlet: "returnBtn", click: "returnBtnPressed", class: "btn", =>
@span "return"
@div class: "panel-body", outlet: "outputContainer", =>
@pre class: "command-output", outlet: "output"
@div class: "btn-toolbar", =>
@div class: "btn-group", =>
@button outlet: "breakpointBtn", click: "toggleBreak", class: "btn", =>
@span "break point"
@div class: "btn-group", =>
@button outlet: "runBtn", click: "runApp", class: "btn", =>
@span "run"
@button outlet: "stopBtn", click: "stopApp", class: "btn", =>
@span "stop"
@div class: "btn-group", =>
@button outlet: "stepOverBtn", click: "stepOverBtnPressed", class: "btn", =>
@span "next"
@button outlet: "stepInBtn", click: "stepInBtnPressed", class: "btn", =>
@span "step"
@button outlet: "returnBtn", click: "returnBtnPressed", class: "btn", =>
@span "return"
@button outlet: "continueBtn", click: "continueBtnPressed", class: "btn", =>
@span "continue"
@div class: "btn-group", =>
@button outlet: "upBtn", click: "upBtnPressed", class: "btn", =>
@span "up"
@button outlet: "downBtn", click: "downBtnPressed", class: "btn", =>
@span "down"
@div class: "btn-group", =>
@button outlet: "clearBtn", click: "clearOutput", class: "btn", =>
@span "clear"
@input class : "input-checkbox", type: "checkbox", id: "ck_input", outlet: "showInput", click: "toggleInput"
@label class : "label", for: "ck_input", =>
@span "Input"
@input class : "input-checkbox", type: "checkbox", id: "ck_vars", outlet: "showVars", click: "toggleVars"
@label class : "label", for: "ck_vars", =>
@span "Variables"
@input class : "input-checkbox", type: "checkbox", id: "ck_callstack", outlet: "showCallstack", click: "toggleCallstack"
@label class : "label", for: "ck_callstack", =>
@span "Call stack"
@div class: "block", outlet: "bottomPane", =>
@div class: "inline-block panel", id: "outputPane", outlet: "outputPane", =>
@pre class: "command-output", outlet: "output"
@div class: "inline-block panel", id: "variablesPane", outlet: "variablesPane", =>
@pre class: "command-output", outlet: "variables"
@div class: "inline-block panel", id: "callstackPane", outlet: "callstackPane", =>
@pre class: "command-output", outlet: "callstack"

toggleInput: ->
if @backendDebugger
@argsEntryView.hide()
if @showInput.prop('checked')
@commandEntryView.show()
else
@commandEntryView.hide()
else
if @showInput.prop('checked')
@argsEntryView.show()
else
@argsEntryView.hide()
@commandEntryView.hide()

toggleVars: ->
@togglePanes()

toggleCallstack: ->
@togglePanes()

togglePanes: ->
n = 1
if @showVars.prop('checked')
@variablesPane.show()
n = n+1
else
@variablesPane.hide()
if @showCallstack.prop('checked')
@callstackPane.show()
n = n+1
else
@callstackPane.hide()
width = ''+(100/n)+'%'
@outputPane.css('width', width)
if @showVars.prop('checked')
@variablesPane.css('width', width)
if @showCallstack.prop('checked')
@callstackPane.css('width', width)
# the following statements are used to update the information in the variables/callstack
@setFlags()
@backendDebugger?.stdin.write("print 'display option changed.'\n")

toggleBreak: ->
editor = atom.workspace.getActiveTextEditor()
filename = editor.getTitle()
lineNumber = editor.getCursorBufferPosition().row + 1
breakpoint = new Breakpoint(filename, lineNumber)
cmd = @breakpointStore.toggle(breakpoint)
if @backendDebugger
@backendDebugger.stdin.write(cmd + " " + @getCurrentFilePath() + ":" + lineNumber + "\n")
@output.empty()
for breakpoint in @breakpointStore.breakpoints
@output.append(breakpoint.toCommand() + "\n")

stepOverBtnPressed: ->
@setFlags()
@backendDebugger?.stdin.write("n\n")

stepInBtnPressed: ->
@setFlags()
@backendDebugger?.stdin.write("s\n")

continueBtnPressed: ->
@setFlags()
@backendDebugger?.stdin.write("c\n")

returnBtnPressed: ->
@setFlags()
@backendDebugger?.stdin.write("r\n")

upBtnPressed: ->
@setFlags()
@backendDebugger?.stdin.write("up\n")

downBtnPressed: ->
@setFlags()
@backendDebugger?.stdin.write("down\n")

printVars: ->
@variables.empty()
@backendDebugger?.stdin.write("print ('@{variables_start}')\n")
@backendDebugger?.stdin.write("for (__k, __v) in [(__k, __v) for __k, __v in globals().items() if not __k.startswith('__')]: print __k, '=', __v\n")
@backendDebugger?.stdin.write("print '-------------'\n")
@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")
@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")
@backendDebugger?.stdin.write("print ('@{variables_end}')\n")

printCallstack: ->
@callstack.empty()
@backendDebugger?.stdin.write("print ('@{callstack_start}')\n")
@backendDebugger?.stdin.write("bt\n")
@backendDebugger?.stdin.write("print ('@{callstack_end}')\n")

setFlags: ->
@flagActionStarted = true
if @showVars.prop('checked')
@varScrollTop = @variables.prop('scrollTop')
@flagVarsNeedUpdate = true
if @showCallstack.prop('checked')
@callStackScrollTop = @callstack.prop('scrollTop')
@flagCallstackNeedsUpdate = true

workspacePath: ->
editor = atom.workspace.getActiveTextEditor()
activePath = editor.getPath()
Expand All @@ -72,17 +195,56 @@ class PythonDebuggerView extends View
@stopApp() if @backendDebugger
@debuggedFileArgs = @getInputArguments()
console.log @debuggedFileArgs
@debuggedFileName = @getCurrentFilePath()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the following if be removed and replaced with a call to askForPaths() then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is adding the last line, which set the file to debug as the current active file.

The if is to check whether we need to stop the debugging (if the debugger is already running). This is the original code. It has nothing to do with the change.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I'm saying is that, because you set debuggedFileName, the next if will never be active (pathsNotSet() will always return false).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I misunderstood your comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is up to you incorporate the changes. I am happy, as long as it works. :-)

if @pathsNotSet()
@askForPaths()
return
@setFlags()
@runBackendDebugger()
@toggleInput()

highlightLineInEditor: (fileName, lineNumber) ->
if lineNumber && fileName
lineNumber = parseInt(lineNumber)
editor = atom.workspace.getActiveTextEditor()
if fileName.toLowerCase() == editor.getPath().toLowerCase()
position = Point(lineNumber-1, 0)
editor.setCursorBufferPosition(position)
editor.unfoldBufferRow(lineNumber)
editor.scrollToBufferPosition(position)
else
options = {initialLine: lineNumber-1, initialColumn:0}
atom.workspace.open(fileName, options) if fs.existsSync(fileName)
# TODO: add decoration to current line?

processNormalOutput: (data_str) ->

# Extract the file name and line number output by the debugger.
processDebuggerOutput: (data) ->
data_str = data.toString().trim()
lineNumber = null
fileName = null

# print the action_end string
if @flagActionStarted
@backendDebugger?.stdin.write("print ('@{action_end}')\n")
@flagActionStarted = false

# detect predefined flag strings
isActionEnd = data_str.includes('@{action_end}')
isVarsStart = data_str.includes('@{variables_start}')
isCallstackStart = data_str.includes('@{callstack_start}')

# variables print started
if isVarsStart
@currentState = 1
@processVariables(data_str)
return

# call stack print started
if isCallstackStart
@currentState = 2
@processCallstack(data_str)
return

# handle normal output
[data_str, tail] = data_str.split("line:: ")
if tail
[lineNumber, tail] = tail.split("\n")
Expand All @@ -95,13 +257,72 @@ class PythonDebuggerView extends View
fileName = fileName.trim() if fileName
fileName = null if fileName == "<string>"

# highlight the current line
if lineNumber && fileName
lineNumber = parseInt(lineNumber)
options = {initialLine: lineNumber-1, initialColumn:0}
atom.workspace.open(fileName, options) if fs.existsSync(fileName)
# TODO: add decoration to current line?
@highlightLineInEditor(fileName, lineNumber)

# print the output
@addOutput(data_str.trim().replace('@{action_end}', ''))

# if action end, trigger the follow up actions
if isActionEnd
if @flagVarsNeedUpdate
@printVars()
@flagVarsNeedUpdate = false
else
if @flagCallstackNeedsUpdate
@printCallstack()
@flagCallstackNeedsUpdate = false

processVariables: (data_str) ->
isVarsEnd = data_str.includes('@{variables_end}')
for line in data_str.split '\n'
if ! line.includes("@{variable")
@variables.append(@createOutputNode(line))
@variables.append('\n')
if isVarsEnd
@variables.prop('scrollTop', @varScrollTop)
@currentState = 0
if @flagCallstackNeedsUpdate
@printCallstack()
@flagCallstackNeedsUpdate = false

processCallstack: (data_str) ->
lineNumber = null
fileName = null
isCallstackEnd = data_str.includes('@{callstack_end}')
m = /[^-]> (.*[.]py)[(]([0-9]*)[)].*/.exec(data_str)
if m
[fileName, lineNumber] = [m[1], m[2]]
callstack_pre = @callstack
`
re = /[\n](>*)[ \t]*(.*[.]py)[(]([0-9]*)[)]([^\n]*)[\n]([^\n]*)/gi;
while ((match = re.exec(data_str)))
{
if (match[5].includes('exec cmd in globals, locals')) continue;
if (match[1].includes('>'))
item = "<b><u>"+match[5].replace("->", "")+"</u></b>";
else
item = match[5].replace("->", "");
callstack_pre.append(item);
callstack_pre.append('\n');
}
`
if lineNumber && fileName
@highlightLineInEditor(fileName, lineNumber)
if isCallstackEnd
@currentState = 0
@callstack.prop('scrollTop', @callStackScrollTop)

@addOutput(data_str.trim())
# Extract the file name and line number output by the debugger.
processDebuggerOutput: (data) ->
data_str = data.toString().trim()
if @currentState == 1
@processVariables(data_str)
else if @currentState == 2
@processCallstack(data_str)
else
@processNormalOutput(data_str)

runBackendDebugger: ->
args = [path.join(@backendDebuggerPath, @backendDebuggerName)]
Expand Down Expand Up @@ -129,6 +350,7 @@ class PythonDebuggerView extends View
@backendDebugger?.stdin.write("\nexit()\n")
@backendDebugger = null
console.log "debugger stopped"
@toggleInput()

clearOutput: ->
@output.empty()
Expand All @@ -155,6 +377,8 @@ class PythonDebuggerView extends View
@breakpointStore = breakpointStore
@debuggedFileName = @getCurrentFilePath()
@backendDebuggerPath = @getDebuggerPath()
@toggleInput()
@togglePanes()
@addOutput("Welcome to Python Debugger for Atom!")
@addOutput("The file being debugged is: " + @debuggedFileName)
@askForPaths()
Expand Down
18 changes: 15 additions & 3 deletions styles/python-debugger.less
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,20 @@
@import "octicon-mixins";

.command-output {
background-color: transparent;
height: 100px;
max-height: 100px;
height: 100%;
overflow-y: scroll;
padding: 5px;
margin: 0px;
}

#outputPane, #variablesPane, #callstackPane {
width: 33%;
height: 200px;
max-height: 200px;
margin: 0px;
}

#ck_input, #ck_vars, #ck_callstack {
margin-left: 10px;
margin-right: 5px;
}