├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .travis.yml ├── LICENSE.md ├── README.md ├── appveyor.yml ├── example ├── some_module.py └── test.py ├── keymaps └── python-debugger.cson ├── lib ├── breakpoint-store.coffee ├── breakpoint.coffee ├── python-debugger-view.coffee └── python-debugger.coffee ├── menus └── python-debugger.cson ├── package.json ├── resources └── atom_pdb.py ├── screenshots └── atom-python-debugger-demo.gif ├── spec └── python-debugger-spec.coffee └── styles ├── decoration.atom-text-editor.less └── python-debugger.less /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | 19 | Cc: @dpo 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: 3 | on_success: never 4 | on_failure: change 5 | 6 | script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 7 | 8 | git: 9 | depth: 10 10 | 11 | sudo: false 12 | 13 | os: 14 | - linux 15 | - osx 16 | 17 | env: 18 | global: 19 | - APM_TEST_PACKAGES="" 20 | 21 | matrix: 22 | - ATOM_CHANNEL=stable 23 | - ATOM_CHANNEL=beta 24 | 25 | addons: 26 | apt: 27 | packages: 28 | - build-essential 29 | - git 30 | - libgnome-keyring-dev 31 | - fakeroot 32 | 33 | branches: 34 | only: 35 | - master 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python-Debugger package 2 | 3 | [![Build Status](https://travis-ci.org/dpo/atom-python-debugger.svg?branch=master)](https://travis-ci.org/dpo/atom-python-debugger) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/x3bskgp134oaxsxm/branch/master?svg=true)](https://ci.appveyor.com/project/dpo/atom-python-debugger/branch/master) 5 | 6 | An Atom package for an IDE-like Python debugging experience. 7 | 8 | This package is a modification of [swift-debugger](https://atom.io/packages/swift-debugger). Thanks to [aciidb0md3r](https://atom.io/users/aciidb0mb3r)! 9 | 10 | ## Keyboard Shortcuts 11 | 12 | - `alt-r`/`option-r`: hide/show the debugger view 13 | - `alt-shift-r`/`option-shift-r`: toggle breakpoint at the current line 14 | 15 | ## How to use 16 | 17 | 1. Install using APM 18 | ``` 19 | $ apm install python-debugger language-python 20 | ``` 21 | The `language-python` package provides syntax highlighting 22 | 2. Open the Python file to debug and insert breakpoints 23 | 3. Press `alt-r` to show the debugger view 24 | 4. Insert input arguments in the input arguments field if applicable 25 | 5. Hit the `Run` button. Focus moves to the first breakpoint. 26 | 6. Use the buttons provided to navigate through your source. You can enter debugger commands directly in the command field. 27 | 28 | The current version should support Python 2.5 and higher, including Python 3. 29 | The Python executable to be used while debugging can be changed in the settings. 30 | 31 | ![Atom Python Debugger](https://github.com/dpo/atom-python-debugger/raw/master/screenshots/atom-python-debugger-demo.gif) 32 | 33 | ## Current limitations 34 | 35 | - Breakpoints inserted with `alt-shift-r` or the command palette after starting the debugger are not taken into account. If you need to add breakpoints mid-course, use an explicit debugger command (e.g., `b 25`). The downside is that they won't be highlighted in the editor. 36 | - No remote debugging. 37 | - No watched variables or expressions. 38 | 39 | Pull requests welcome! 40 | 41 | Happy debugging! 42 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | platform: x64 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | skip_tags: true 10 | 11 | environment: 12 | APM_TEST_PACKAGES: 13 | 14 | matrix: 15 | - ATOM_CHANNEL: stable 16 | - ATOM_CHANNEL: beta 17 | 18 | install: 19 | - ps: Install-Product node 5 20 | 21 | build_script: 22 | - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) 23 | 24 | test: off 25 | deploy: off 26 | -------------------------------------------------------------------------------- /example/some_module.py: -------------------------------------------------------------------------------- 1 | """Helper module.""" 2 | 3 | 4 | def some_function(t): 5 | """Another silly function.""" 6 | return t + " python" 7 | -------------------------------------------------------------------------------- /example/test.py: -------------------------------------------------------------------------------- 1 | """Sample program. 2 | 3 | Bring up the debugger with option-r. 4 | """ 5 | 6 | from some_module import some_function 7 | 8 | 9 | def do_something(x): 10 | """A silly function.""" 11 | y = x 12 | z = some_function(y) # Stepping in opens the relevant file 13 | return z + " rules!" 14 | 15 | if __name__ == "__main__": 16 | import sys 17 | x = sys.argv[1] # Set a breakpoint with option-shift-R or palette 18 | y = do_something(x) 19 | print(y) 20 | -------------------------------------------------------------------------------- /keymaps/python-debugger.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to 3 | # execute. 4 | # 5 | # Below is a basic keybinding that registers on all platforms by applying to 6 | # the root workspace element. 7 | 8 | # For more detailed documentation see 9 | # https://atom.io/docs/latest/behind-atom-keymaps-in-depth 10 | "atom-text-editor[data-grammar='source python']": 11 | "alt-r": "python-debugger:toggle" 12 | "alt-shift-r": "python-debugger:breakpoint" 13 | -------------------------------------------------------------------------------- /lib/breakpoint-store.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require "atom" 2 | module.exports = 3 | class BreakpointStore 4 | constructor: (gutter) -> 5 | @breakpoints = [] 6 | 7 | toggle: (breakpoint) -> 8 | breakpointSearched = @containsBreakpoint(breakpoint) 9 | 10 | addDecoration = true 11 | if breakpointSearched 12 | # remove breakpoint from list 13 | @breakpoints.splice(@breakpoints.indexOf(breakpointSearched), 1) 14 | addDecoration = false 15 | else 16 | @breakpoints.push(breakpoint) 17 | 18 | editor = atom.workspace.getActiveTextEditor() 19 | 20 | if addDecoration 21 | marker = editor.markBufferPosition([breakpoint.lineNumber-1, 0]) 22 | d = editor.decorateMarker(marker, type: "line-number", class: "line-number-red") 23 | d.setProperties(type: "line-number", class: "line-number-red") 24 | breakpoint.decoration = d 25 | return breakpoint.addCommand() 26 | else 27 | editor = atom.workspace.getActiveTextEditor() 28 | ds = editor.getLineNumberDecorations(type: "line-number", class: "line-number-red") 29 | for d in ds 30 | marker = d.getMarker() 31 | marker.destroy() if marker.getBufferRange().start.row == breakpoint.lineNumber-1 32 | return breakpoint.clearCommand() 33 | 34 | containsBreakpoint: (bp) -> 35 | for breakpoint in @breakpoints 36 | if breakpoint.filename == bp.filename && breakpoint.lineNumber == bp.lineNumber 37 | return breakpoint 38 | return null 39 | -------------------------------------------------------------------------------- /lib/breakpoint.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | class Breakpoint 3 | decoration: null 4 | constructor: (@filename, @lineNumber) -> 5 | addCommand: -> 6 | "b " + @filename + ":" + @lineNumber 7 | clearCommand: -> 8 | "cl " + @filename + ":" + @lineNumber 9 | -------------------------------------------------------------------------------- /lib/python-debugger-view.coffee: -------------------------------------------------------------------------------- 1 | {Point, Disposable, CompositeDisposable} = require "atom" 2 | {$, $$, View, TextEditorView} = require "atom-space-pen-views" 3 | Breakpoint = require "./breakpoint" 4 | BreakpointStore = require "./breakpoint-store" 5 | 6 | spawn = require("child_process").spawn 7 | path = require "path" 8 | fs = require "fs" 9 | 10 | module.exports = 11 | class PythonDebuggerView extends View 12 | debuggedFileName: null 13 | debuggedFileArgs: [] 14 | backendDebuggerPath: null 15 | backendDebuggerName: "atom_pdb.py" 16 | 17 | getCurrentFilePath: -> 18 | return "" unless editor = atom.workspace.getActivePaneItem() 19 | return "" unless buffer = editor.buffer 20 | return buffer.file?.path 21 | 22 | getDebuggerPath: -> 23 | pkgs = atom.packages.getPackageDirPaths()[0] 24 | debuggerPath = path.join(pkgs, "python-debugger", "resources") 25 | return debuggerPath 26 | 27 | @content: -> 28 | @div class: "pythonDebuggerView", => 29 | @subview "argsEntryView", new TextEditorView 30 | mini: true, 31 | placeholderText: "> Enter input arguments here" 32 | @subview "commandEntryView", new TextEditorView 33 | mini: true, 34 | placeholderText: "> Enter debugger commands here" 35 | @button outlet: "breakpointBtn", click: "toggleBreakpoint", class: "btn", => 36 | @span "breakpoint" 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: "clearBtn", click: "clearOutput", class: "btn", => 60 | @span "clear" 61 | @div class: "panel-body", outlet: "outputContainer", => 62 | @pre class: "command-output", outlet: "output" 63 | 64 | toggleBreakpoint: -> 65 | editor = atom.workspace.getActiveTextEditor() 66 | filename = @getCurrentFilePath() 67 | lineNumber = editor.getCursorBufferPosition().row + 1 68 | # add to or remove breakpoint from internal list 69 | cmd = @breakpointStore.toggle(new Breakpoint(filename, lineNumber)) 70 | debuggerCmd = cmd + "\n" 71 | @backendDebugger.stdin.write(debuggerCmd) if @backendDebugger 72 | @output.append(debuggerCmd) 73 | 74 | stepOverBtnPressed: -> 75 | @backendDebugger?.stdin.write("n\n") 76 | 77 | stepInBtnPressed: -> 78 | @backendDebugger?.stdin.write("s\n") 79 | 80 | continueBtnPressed: -> 81 | @backendDebugger?.stdin.write("c\n") 82 | 83 | returnBtnPressed: -> 84 | @backendDebugger?.stdin.write("r\n") 85 | 86 | loopOverBreakpoints: () -> 87 | n = @breakpointStore.breakpoints.length 88 | for i in [0..n-1] 89 | # always yield first element; it will be spliced out 90 | yield @breakpointStore.breakpoints[0] 91 | 92 | clearBreakpoints: () -> 93 | return unless @breakpointStore.breakpoints.length > 0 94 | # The naive `@toggle breakpoint for breakpoint in @breakpoints` 95 | # gives indexing errors because of the async loop. 96 | # Clear breakpoints sequentially. 97 | 98 | # for ... from will be supported in a future version of Atom 99 | # for breakpoint from @loopOverBreakpoints() 100 | # cmd = @toggle breakpoint 101 | # debuggerCmd = cmd + "\n" 102 | # @backendDebugger.stdin.write(debuggerCmd) if @backendDebugger 103 | # @output.append(debuggerCmd) 104 | ` 105 | for (let breakpoint of this.loopOverBreakpoints()) { 106 | cmd = this.breakpointStore.toggle(breakpoint) 107 | debuggerCmd = cmd + "\n" 108 | if (this.backendDebugger) { 109 | this.backendDebugger.stdin.write(debuggerCmd) 110 | this.output.append(debuggerCmd) 111 | } 112 | } 113 | ` 114 | return 115 | 116 | workspacePath: -> 117 | editor = atom.workspace.getActiveTextEditor() 118 | activePath = editor.getPath() 119 | relative = atom.project.relativizePath(activePath) 120 | pathToWorkspace = relative[0] || (path.dirname(activePath) if activePath?) 121 | pathToWorkspace 122 | 123 | runApp: -> 124 | @stopApp() if @backendDebugger 125 | @debuggedFileArgs = @getInputArguments() 126 | console.log @debuggedFileArgs 127 | if @pathsNotSet() 128 | @askForPaths() 129 | return 130 | @runBackendDebugger() 131 | 132 | varBtnPressed: -> 133 | @backendDebugger?.stdin.write("for (__k, __v) in [(__k, __v) for __k, __v in globals().items() if not __k.startswith('__')]: print __k, '=', __v\n") 134 | @backendDebugger?.stdin.write("print '-------------'\n") 135 | @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") 136 | @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") 137 | 138 | # Extract the file name and line number output by the debugger. 139 | processDebuggerOutput: (data) -> 140 | data_str = data.toString().trim() 141 | lineNumber = null 142 | fileName = null 143 | 144 | [data_str, tail] = data_str.split("line:: ") 145 | if tail 146 | [lineNumber, tail] = tail.split("\n") 147 | data_str = data_str + tail if tail 148 | 149 | [data_str, tail] = data_str.split("file:: ") 150 | if tail 151 | [fileName, tail] = tail.split("\n") 152 | data_str = data_str + tail if tail 153 | fileName = fileName.trim() if fileName 154 | fileName = null if fileName == "" 155 | 156 | @highlightLineInEditor fileName, lineNumber 157 | @addOutput(data_str.trim()) 158 | 159 | highlightLineInEditor: (fileName, lineNumber) -> 160 | return unless fileName && lineNumber 161 | lineNumber = parseInt(lineNumber) 162 | focusOnCmd = atom.config.get "python-debugger.focusOnCmd" 163 | options = { 164 | searchAllPanes: true, 165 | activateItem: true, 166 | activatePane: focusOnCmd, 167 | } 168 | atom.workspace.open(fileName, options).then (editor) -> 169 | position = Point(lineNumber - 1, 0) 170 | editor.setCursorBufferPosition(position) 171 | editor.unfoldBufferRow(lineNumber) 172 | editor.scrollToBufferPosition(position) 173 | # TODO: add decoration to current line? 174 | 175 | runBackendDebugger: -> 176 | args = [path.join(@backendDebuggerPath, @backendDebuggerName)] 177 | args.push(@debuggedFileName) 178 | args.push(arg) for arg in @debuggedFileArgs 179 | python = atom.config.get "python-debugger.pythonExecutable" 180 | console.log("python-debugger: using", python) 181 | @backendDebugger = spawn python, args 182 | 183 | for breakpoint in @breakpointStore.breakpoints 184 | @backendDebugger.stdin.write(breakpoint.addCommand() + "\n") 185 | 186 | # Move to first breakpoint or run program if there are none. 187 | @backendDebugger.stdin.write("c\n") 188 | 189 | @backendDebugger.stdout.on "data", (data) => 190 | @processDebuggerOutput(data) 191 | @backendDebugger.stderr.on "data", (data) => 192 | @processDebuggerOutput(data) 193 | @backendDebugger.on "exit", (code) => 194 | @addOutput("debugger exits with code: " + code.toString().trim()) if code? 195 | 196 | stopApp: -> 197 | console.log "backendDebugger is ", @backendDebugger 198 | @backendDebugger?.stdin.write("\nexit()\n") 199 | @backendDebugger = null 200 | @debuggedFileName = null 201 | @debuggedFileArgs = [] 202 | console.log "debugger stopped" 203 | 204 | clearOutput: -> 205 | @output.empty() 206 | 207 | createOutputNode: (text) -> 208 | node = $("").text(text) 209 | parent = $("").append(node) 210 | 211 | addOutput: (data) -> 212 | atBottom = @atBottomOfOutput() 213 | node = @createOutputNode(data) 214 | @output.append(node) 215 | @output.append("\n") 216 | if atBottom 217 | @scrollToBottomOfOutput() 218 | 219 | pathsNotSet: -> 220 | !@debuggedFileName 221 | 222 | askForPaths: -> 223 | @addOutput("To set or change the entry point, set file to debug using e=fileName") 224 | 225 | initialize: (breakpointStore) -> 226 | @breakpointStore = breakpointStore 227 | @debuggedFileName = @getCurrentFilePath() 228 | @backendDebuggerPath = @getDebuggerPath() 229 | @addOutput("Welcome to Python Debugger for Atom!") 230 | @addOutput("The file being debugged is: " + @debuggedFileName) 231 | @askForPaths() 232 | @subscriptions = atom.commands.add @element, 233 | "core:confirm": (event) => 234 | if @parseAndSetPaths() 235 | @clearInputText() 236 | else 237 | @confirmBackendDebuggerCommand() 238 | event.stopPropagation() 239 | "core:cancel": (event) => 240 | @cancelBackendDebuggerCommand() 241 | event.stopPropagation() 242 | 243 | parseAndSetPaths:() -> 244 | command = @getCommand() 245 | return false if !command 246 | if /e=(.*)/.test command 247 | match = /e=(.*)/.exec command 248 | # TODO: check that file exists 249 | if fs.existsSync match[1] 250 | @debuggedFileName = match[1] 251 | @addOutput("The file being debugged is: " + @debuggedFileName) 252 | return true 253 | else 254 | @addOutput("File #{match[1]} does not appear to exist") 255 | return false 256 | 257 | stringIsBlank: (str) -> 258 | !str or /^\s*$/.test str 259 | 260 | escapeString: (str) -> 261 | !str or str.replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0') 262 | 263 | getInputArguments: -> 264 | args = @argsEntryView.getModel().getText() 265 | return if !@stringIsBlank(args) then args.split(" ") else [] 266 | 267 | getCommand: -> 268 | command = @commandEntryView.getModel().getText() 269 | command if !@stringIsBlank(command) 270 | 271 | cancelBackendDebuggerCommand: -> 272 | @commandEntryView.getModel().setText("") 273 | 274 | confirmBackendDebuggerCommand: -> 275 | if !@backendDebugger 276 | @addOutput("Program not running") 277 | return 278 | command = @getCommand() 279 | if command 280 | @backendDebugger.stdin.write(command + "\n") 281 | @clearInputText() 282 | 283 | clearInputText: -> 284 | @commandEntryView.getModel().setText("") 285 | 286 | serialize: -> 287 | attached: @panel?.isVisible() 288 | 289 | destroy: -> 290 | @detach() 291 | 292 | toggle: -> 293 | if @panel?.isVisible() 294 | @detach() 295 | else 296 | @attach() 297 | 298 | atBottomOfOutput: -> 299 | @output[0].scrollHeight <= @output.scrollTop() + @output.outerHeight() 300 | 301 | scrollToBottomOfOutput: -> 302 | @output.scrollToBottom() 303 | 304 | attach: -> 305 | console.log "attached" 306 | @panel = atom.workspace.addBottomPanel(item: this) 307 | @panel.show() 308 | @scrollToBottomOfOutput() 309 | 310 | detach: -> 311 | console.log "detached" 312 | @panel.destroy() 313 | @panel = null 314 | -------------------------------------------------------------------------------- /lib/python-debugger.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require "atom" 2 | path = require "path" 3 | Breakpoint = require "./breakpoint" 4 | BreakpointStore = require "./breakpoint-store" 5 | 6 | module.exports = PythonDebugger = 7 | pythonDebuggerView: null 8 | subscriptions: null 9 | 10 | config: 11 | pythonExecutable: 12 | title: "Path to Python executable to use during debugging" 13 | type: "string" 14 | default: "python" 15 | focusOnCmd: 16 | title: "Focus editor on current line change" 17 | type: "boolean" 18 | default: false 19 | 20 | createDebuggerView: (backendDebugger) -> 21 | unless @pythonDebuggerView? 22 | PythonDebuggerView = require "./python-debugger-view" 23 | @pythonDebuggerView = new PythonDebuggerView(@breakpointStore) 24 | @pythonDebuggerView 25 | 26 | activate: ({attached}={}) -> 27 | 28 | @subscriptions = new CompositeDisposable 29 | @breakpointStore = new BreakpointStore() 30 | @createDebuggerView().toggle() if attached 31 | 32 | @subscriptions.add atom.commands.add "atom-workspace", 33 | "python-debugger:toggle": => @createDebuggerView().toggle() 34 | "python-debugger:breakpoint": => @pythonDebuggerView?.toggleBreakpoint() 35 | "python-debugger:clear-all-breakpoints": => @pythonDebuggerView?.clearBreakpoints() 36 | 37 | deactivate: -> 38 | @backendDebuggerInputView.destroy() 39 | @subscriptions.dispose() 40 | @pythonDebuggerView.destroy() 41 | 42 | serialize: -> 43 | pythonDebuggerViewState: @pythonDebuggerView?.serialize() 44 | 45 | activePath = editor?.getPath() 46 | relative = atom.project.relativizePath(activePath) 47 | themPaths = relative[0] || (path.dirname(activePath) if activePath?) 48 | -------------------------------------------------------------------------------- /menus/python-debugger.cson: -------------------------------------------------------------------------------- 1 | "context-menu": 2 | "atom-text-editor": [ 3 | { 4 | "label": "Toggle python-debugger" 5 | "command": "python-debugger:toggle" 6 | } 7 | ] 8 | "menu": [ 9 | { 10 | "label": "Packages" 11 | "submenu": [ 12 | "label": "python-debugger" 13 | "submenu": [ 14 | { 15 | "label": "Toggle" 16 | "command": "python-debugger:toggle" 17 | } 18 | ] 19 | ] 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "python-debugger", 3 | "main": "./lib/python-debugger", 4 | "version": "0.2.0", 5 | "description": "Develop and Debug python projects", 6 | "keywords": [ 7 | "python", 8 | "develop python", 9 | "debug python", 10 | "pdb" 11 | ], 12 | "activationCommands": { 13 | "atom-workspace": [ 14 | "python-debugger:toggle", 15 | "python-debugger:breakpoint" 16 | ] 17 | }, 18 | "repository": "https://github.com/dpo/atom-python-debugger", 19 | "license": "MIT", 20 | "engines": { 21 | "atom": ">=1.0.0 <2.0.0" 22 | }, 23 | "dependencies": { 24 | "atom-space-pen-views": "^2.0.0" 25 | }, 26 | "activationHooks": [ 27 | "language-python:grammar-used" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /resources/atom_pdb.py: -------------------------------------------------------------------------------- 1 | # This is a simple customized pdb that has no prompt and outputs the 2 | # name and line number of the file being debugged after each command. 3 | # It is appropriate for a IDE-like debugger package in a text editor, 4 | # such as Atom. 5 | # dominique.orban@gmail.com, 2016. 6 | 7 | import os 8 | import pdb 9 | import sys 10 | import traceback 11 | 12 | 13 | class Restart(Exception): 14 | """Causes a debugger to be restarted for the debugged python program.""" 15 | pass 16 | 17 | 18 | class AtomPDB(pdb.Pdb): 19 | 20 | def __init__(self, **kwargs): 21 | kwargs.pop("stdout", None) 22 | pdb.Pdb.__init__(self, stdout=sys.__stdout__, **kwargs) 23 | self.prompt = "" 24 | 25 | ver = sys.version_info 26 | if isinstance(ver, tuple): 27 | # We're using python <= 2.6 28 | py2 = True 29 | else: 30 | py2 = ver.major == 2 31 | if py2: 32 | def do_locate(self, arg): 33 | # An interface can grep the file and line number to follow along. 34 | frame, lineno = self.stack[self.curindex] 35 | filename = self.canonic(frame.f_code.co_filename) 36 | self.stdout.write("file:: %s\nline:: %s\n" % (filename, lineno)) 37 | 38 | else: 39 | def do_locate(self, arg): 40 | # An interface can grep the file and line number to follow along. 41 | frame, lineno = self.stack[self.curindex] 42 | filename = self.canonic(frame.f_code.co_filename) 43 | self.message("file:: %s\nline:: %s\n" % (filename, lineno)) 44 | 45 | def preloop(self): 46 | self.do_locate(1) 47 | 48 | def precmd(self, line): 49 | return line 50 | 51 | def postcmd(self, stop, line): 52 | return stop 53 | 54 | 55 | def main(): 56 | if not sys.argv[1:] or sys.argv[1] in ("--help", "-h"): 57 | sys.stdout.write("atom_pdb.py script [args...]\n") 58 | sys.exit(2) 59 | 60 | script = sys.argv[1] 61 | if not os.path.exists(script): 62 | sys.exit(1) 63 | del sys.argv[0] 64 | sys.path[0] = os.path.dirname(script) 65 | apdb = AtomPDB() 66 | while True: 67 | try: 68 | apdb._runscript(script) 69 | if apdb._user_requested_quit: 70 | break 71 | sys.stdout.write("The program finished and will be restarted\n") 72 | except Restart: 73 | sys.stdout.write("Restarting %s with arguments: " % script) 74 | sys.stdout.write(" ".join(sys.argv[1:]) + "\n") 75 | except SystemExit: 76 | sys.stdout.write("The program exited via sys.exit(). ") 77 | sys.stdout.write("Exit status: %s\n" % sys.exc_info()[1]) 78 | except Exception: 79 | inst = sys.exc_info()[1] 80 | traceback.print_exc() 81 | sys.stdout.write("Uncaught exception %s " % str(type(inst))) 82 | sys.stdout.write("... entering post-mortem debugging\n") 83 | sys.stdout.write("Continue or Step will restart the program\n") 84 | apdb.interaction(None, sys.exc_info()[2]) 85 | sys.stdout.write("Post-mortem debugging finished.") 86 | sys.stdout.write(" %s will be restarted.\n" % script) 87 | 88 | 89 | if __name__ == "__main__": 90 | 91 | import atom_pdb 92 | atom_pdb.main() 93 | -------------------------------------------------------------------------------- /screenshots/atom-python-debugger-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpo/atom-python-debugger/aa2c1fee2f4d934b66aca62055bac1ff2f514628/screenshots/atom-python-debugger-demo.gif -------------------------------------------------------------------------------- /spec/python-debugger-spec.coffee: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /styles/decoration.atom-text-editor.less: -------------------------------------------------------------------------------- 1 | @import "syntax-variables"; 2 | @red-color: @syntax-color-removed; 3 | @green-color: @syntax-color-added; 4 | @blue-color: @syntax-color-renamed; 5 | 6 | atom-text-editor.editor .gutter .line-number { 7 | &.line-number-green { 8 | color: #fff; 9 | background-color: @green-color; 10 | } 11 | 12 | &.line-number-blue { 13 | color: #fff; 14 | background-color: @blue-color; 15 | } 16 | 17 | &.line-number-red { 18 | color: #fff; 19 | background-color: @red-color; 20 | } 21 | } 22 | 23 | .line { 24 | &.line-green { 25 | background-color: @green-color; 26 | } 27 | 28 | &.line-blue { 29 | background-color: @blue-color; 30 | } 31 | 32 | &.line-red { 33 | background-color: @red-color; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /styles/python-debugger.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | @import "octicon-mixins"; 3 | 4 | .command-output { 5 | background-color: transparent; 6 | height: 100px; 7 | max-height: 100px; 8 | overflow-y: scroll; 9 | } 10 | --------------------------------------------------------------------------------