├── .gitignore ├── .travis.php.ini ├── .travis.yml ├── CHANGELOG ├── Gemfile ├── LICENCE ├── README.md ├── Rakefile ├── VERSION ├── Vagrantfile ├── bootstrap.sh ├── build.sh ├── doc └── Vdebug.txt ├── features ├── correct_window_setup.feature ├── eval.feature ├── step_definitions │ ├── debugger_command_steps.rb │ ├── evaluate_steps.rb │ ├── hooks.rb │ ├── interface_steps.rb │ ├── php_steps.rb │ ├── source_file_steps.rb │ ├── stack_window_steps.rb │ ├── status_window_steps.rb │ ├── trace_window_steps.rb │ └── watch_window_steps.rb ├── support │ ├── env.rb │ └── helpers.rb ├── trace.feature ├── unicode_in_source_buffer.feature └── valid_file_names.feature ├── plugin └── vdebug.vim ├── python3 └── vdebug │ ├── __init__.py │ ├── breakpoint.py │ ├── connection.py │ ├── dbgp.py │ ├── debugger_interface.py │ ├── error.py │ ├── event.py │ ├── listener.py │ ├── log.py │ ├── opts.py │ ├── session.py │ ├── ui │ ├── __init__.py │ ├── interface.py │ └── vimui.py │ └── util.py ├── rubylib └── vdebug.rb ├── spec ├── options_command_spec.rb ├── spec_helper.rb ├── startup_spec.rb └── vdebug_spec.rb ├── syntax ├── debugger_breakpoint.vim ├── debugger_log.vim ├── debugger_stack.vim ├── debugger_status.vim └── debugger_watch.vim └── tests ├── __init__.py ├── test_breakpoint_breakpoint.py ├── test_connection.py ├── test_dbgp_api.py ├── test_dbgp_context_property.py ├── test_dbgp_eval_property.py ├── test_dbgp_response.py ├── test_log.py ├── test_opts_options.py ├── test_util_environment.py ├── test_util_filepath.py └── vim.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | doc/tags 3 | .coverage 4 | htmlcov/ 5 | .*.tags 6 | .vagrant/ 7 | .bundle/ 8 | vendor/ 9 | .bash_history 10 | Gemfile.lock 11 | .gem/ 12 | -------------------------------------------------------------------------------- /.travis.php.ini: -------------------------------------------------------------------------------- 1 | xdebug.remote_enable=1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | irc: irc.freenode.org#vdebug 3 | slack: 4 | secure: BksygPSsoRfvre27w6XQN9ooKB9C1n83Bw96YurvyDHJ6BYM2Gv9fW1Sj6NHpB8MQkMaJ+9gRiBSflkP+l80t3EXAdStuI/8EM/wZu1ov6bmYT/hYQ8HnULmmPNj8uVzsX13VGEUC/YY4osWuJFM1wh61dStoF+SKrpslqWCV4w= 5 | language: python 6 | env: 7 | matrix: 8 | - DISTRO: 'ubuntu:16.04' 9 | - DISTRO: 'debian:9' 10 | before_install: 11 | - docker run --rm -d --name vdebug-distro-test -v $(pwd):/travis $DISTRO tail -f /dev/null 12 | - docker ps 13 | install: 14 | - |- 15 | docker exec -t vdebug-distro-test bash -c "apt-get update && apt-get install -y sudo vim-gtk3 xvfb python3 python3-pip ruby-dev gcc make automake libtool php-cli php-xdebug locales && gem install bundler && pip3 install coverage && echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && locale-gen && echo 'LANG=en_US.UTF-8' > /etc/default/locale && useradd -u $(id -u) -M -s /bin/bash -d /travis travis && echo '%travis ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/travis" 16 | - docker exec -t vdebug-distro-test su - travis -c "bundle install" 17 | script: 18 | - docker exec -t vdebug-distro-test su - travis -c "coverage run -m unittest discover && coverage report -m --include='python3/vdebug/*'" 19 | - docker exec -t vdebug-distro-test su - travis -c "xvfb-run bundle exec rake spec features" 20 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | === Vdebug 2.0.0 / 2018-04-30 2 | Features: 3 | * async behaviour (@joonty finished python2 based version) 4 | * python3 based 5 | * allow debugging of phar files 6 | 7 | Big thanks to @Garethp, @lucc and @markkimsal for the python 3 related works. 8 | 9 | Bug fixes: 10 | * hide numbers and make debug windows non editable 11 | * all sorts of issues triggered by the pyton3 move 12 | * fix window positioning in neovim 13 | 14 | Other changes, vdebug moved into its own organisation on github now. 15 | 16 | === Vdebug 1.5.1 / 2015-09-07 17 | Bug fixes: 18 | * Fix buffers not being wiped out after ending a session (thanks @BlackIkeEagle, #226) 19 | 20 | Features: 21 | * Add :VdebugEval! command, which remembers the eval expression when stepping 22 | through, as an alternative to :VdebugTrace (thanks @benjifisher, #193) 23 | 24 | === Vdebug 1.5.0 / 2015-08-28 25 | Bug fixes: 26 | * Fix for remote and local file paths with a windows/unix setup (thanks @brothert, #130) 27 | * Sort path maps by remote path length (thanks @ragol and @adambiggs, #153, #201) 28 | * Default "stop" behaviour (default ) actually stops rather than detaches (issue #166) 29 | * Various documentation fixes (issue #172, issue #223, thanks @loonies for #210) 30 | * Add documentation to show that komodo-debug doesn't work with nodejs > 0.10 (#202, #223) 31 | * Don't override previously defined breakpoint highlights (#208) 32 | 33 | Features: 34 | * Add support for Python 2.4 (thanks @mbarbon, #189) 35 | * Add Vagrantfile and bootstrap for easier local development 36 | * Add new VdebugTrace command for tracking individual variables and expressions (thanks @escher9, #178) 37 | * Show ellipses for truncated arrays in watch window (#177) 38 | * Bind to all available interfaces by default, instead of just localhost (#209, thanks @zolem) 39 | 40 | Misc: 41 | * Changed HISTORY to CHANGELOG 42 | 43 | === Vdebug 1.4.2 / 2014-05-15 44 | 45 | Bug fixes: 46 | * Allow overriding of options dictionary after sourcing vdebug (issues #112, #113) 47 | * Show local file path in stack window if using path mapping (issue #118) 48 | * Change function names for stricter rules introduced in Vim v.7.4.260 (thanks @BlackIkeEagle, #158 and #155) 49 | 50 | Documentation: 51 | * Various fixes 52 | * Mention vim-nox package for Debian (thanks @henningda, #138) 53 | * Add Python installation for Windows information (issue #135) 54 | 55 | Features: 56 | * Add completion to :Breakpoint command (thanks @tommcdo, #124) 57 | * Add VdebugStart command (thanks @blueyed, #132) 58 | * Sleep for 100ms in wait loop, don't consume as much CPU (thanks @blueyed, #133) 59 | 60 | === Vdebug 1.4.2a / 2013-11-11 61 | 62 | Bug fixes: 63 | * Add compatibility fix for Python debugging (due to incorrect "file:/" prefix) (issue #68, #102) 64 | * Fix remote path slash replacement (issue #94) 65 | * Fix rendering of unicode in watch window (issue #95) 66 | * Stop errors being thrown on Vim 7.4 (issue #98, #96, #99) 67 | * Fix error message on plugin reload, from function definition (thanks @glittershark, #106) 68 | 69 | Documentation: 70 | * Correct minor spelling and grammar errors in help doc (thanks @dkinzer, #108) 71 | * Change the description for setting up the python debugger in the docs 72 | 73 | Other: 74 | * Add cucumber for integration tests, and add lots of features 75 | 76 | === Vdebug 1.4.1 / 2013-06-09 77 | 78 | Bug fixes: 79 | * Fix folds being forgotten after debugging (issue #56) 80 | * Don't overwrite features array if it exists (issue #59) 81 | * Mapping of single modes (thanks @xbot, #66) 82 | * Stop empty buffers from being created (issue #70) 83 | * Fix ElementTree deprecation warning (thanks @InFog, #89) 84 | * Fix minheight error for windows (thanks @zhaocai, #73, also #84) 85 | * Fix filename handling on windows (thanks @Chronial, #74) 86 | * Use :mkexrc for restoring keymapping (thanks @Chronial and @qstrahl, #75) 87 | * Fix window destruction (thanks @Chronial, #76) 88 | * Inspect PHP constants with cursor eval (issue #80) 89 | * Don't cut final character when inspecting variables at end of line (issue #85) 90 | 91 | Documentation: 92 | * Test suite requirements (thanks @michaeltwofish, #61) 93 | * Add helptag instructions to README (issue #67) 94 | * Add 'eval_visual' to documentation (issue #88) 95 | 96 | === Vdebug 1.4.0 / 2013-03-06 97 | 98 | Features: 99 | * Set debugger features with g:vdebug_features dictionary (issue #55) 100 | * Allow use of '~' in log files (issue #53) 101 | 102 | Bug fixes: 103 | * Print error message if trying to start debugger with unsaved buffers (issue #57) 104 | * Don't allow visual eval outside of debugging session (issue #52) 105 | * Show updated key mappings in status window (issue #51) 106 | * Fix problem with stuck breakpoints after debugging (issue #48, #43) 107 | * Swap back to original tab after debugging session closes (issue #23) 108 | 109 | === Vdebug 1.3.2 / 2013-01-28 110 | 111 | Features: 112 | * Don't allow breakpoints to be set on empty lines (issue #27) 113 | * Add :VdebugOpt command for easy setting and getting of options (issue #38) 114 | 115 | Bug fixes: 116 | * Small fixes for documentation (thanks: https://github.com/grota, #31) 117 | * Parse continuous mode option as an integer (thanks: https://github.com/grota, #32) 118 | * Stop Xdebug hanging when using the detach command (issue #33) 119 | * Don't use recursive Vim mapping (issue #34) 120 | * Track movement of line breaks with Vim signs (issue #35) 121 | * Add "default" keyword to highlights, to allow custom colours (issue #36) 122 | * Fix check for multibyte support (issue #37) 123 | * Fix deprecation warning in Python 2.6, catching string exceptions (issue #39) 124 | * Stop source code panel opening in incorrect position (issue #40) 125 | 126 | === Vdebug 1.3.1 / 2012-12-21 127 | 128 | Bug fixes: 129 | * Filepath problems with Windows version 130 | 131 | Other: 132 | * Packaged tests as an easy-to-run suite 133 | * Set up on Travis CI 134 | 135 | === Vdebug 1.3.0 / 2012-12-19 136 | 137 | Features: 138 | * Added minor version number to versioning, for bug fixes 139 | * Support and compatibility fixes for debugging Tcl, plus help file updates 140 | * Non-multibyte Vim installations now use ASCII watch window marker characters as a fallback (thanks: https://github.com/satiani, #21) 141 | * Watch window marker characters are fully customizable 142 | * Added breakpoint list function to Api class 143 | * Satiani added multiple file path mappings, for remote debugging (thanks: https://github.com/satiani, #20) 144 | * Vdebug re-maps previous key mappings after shutting down (issue #16) 145 | * Added 'watch_window_style' option that allows for a compact or (default) expanded watch window (thanks: https://github.com/georgjaehnig, #13) 146 | * Simple continuous mode added, where vdebug starts listening after a connection closes, to allow for debugging to start again immediately (issue #19) 147 | 148 | Bug fixes: 149 | * Fix for watch window characters on Windows versions of Vim (issue #29) 150 | * Fix for watch window variable expansion on Windows versions of Vim (issue #28) 151 | * Stopped creating empty buffers every time Vdebug started (issue #25) 152 | * Fixed bug with file URI quoting 153 | * Fixed escaping of characters when calling eval (issue #15) 154 | 155 | 156 | === Vdebug 1.2 / 2012-10-05 157 | 158 | * Support for nodejs debugging using the komodo-debug package in NPM 159 | * Keyboard interrupt while Vim is waiting for a connection (thanks: http://github.com/artnez, #6) 160 | * Fixed overwriting dir function with badly named variable (thanks: http://github.com/csomme, #8) 161 | * Function keys for debugger are only mapped when the session starts, and are unmapped when it's closed (issue #1) 162 | * Removed the help tags file from the git repo (issue #10) 163 | 164 | === Vdebug 1.1 / 2012-08-30 165 | 166 | * Fix for Windows file paths (thanks: http://github.com/beatle, #4) 167 | * Watch window trees can be closed as well as opened (issue #5) 168 | 169 | === Vdebug 1.0 / 2012-08-14 170 | 171 | * First release! 172 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake', '~> 10.0.4' 4 | gem 'rspec', '~> 2.13.0' 5 | gem 'vimrunner', '~> 0.3.0' 6 | gem 'cucumber' 7 | gem 'travis' 8 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright © 2012 Jon Cairns 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | The Software is provided "as is", without warranty of any kind, express or 14 | implied, including but not limited to the warranties of merchantability, 15 | fitness for a particular purpose and noninfringement. In no event shall the 16 | authors or copyright holders be liable for any claim, damages or other liability, 17 | whether in an action of contract, tort or otherwise, arising from, out of or in 18 | connection with the software or the use or other dealings in the Software. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vdebug 2 | 3 | [![Build Status](https://travis-ci.org/vim-vdebug/vdebug.png?branch=master)](https://travis-ci.org/vim-vdebug/vdebug) 4 | 5 | ## Introduction 6 | 7 | Vdebug is a new, fast, powerful debugger client for Vim. It's multi-language, 8 | and has been tested with PHP, Python, Ruby, Perl, Tcl and NodeJS. It interfaces 9 | with **any** debugger that faithfully uses the DBGP protocol, such as Xdebug 10 | for PHP. There are step-by-step instructions for setting up debugging with all 11 | of the aforementioned languages in the Vim help file that comes with Vdebug. 12 | 13 | It builds on the experience gained through the legacy of the Xdebug Vim script 14 | originally created by Seung Woo Shin and extended by so many others, but it's a 15 | total rebuild to allow for a nicer interface and support of new features. 16 | 17 | It's written in Python, and has an object-oriented interface that is easy to 18 | extend and can even be used from the command-line. It even has unit tests 19 | covering some of the more critical parts of the code. 20 | 21 | ## Installation 22 | 23 | **Requirements**: 24 | 25 | * Vim compiled with Python 3 support, tabs and signs (for Debian/Ubuntu this is 26 | provided in the vim-nox package) 27 | * A programming language that has a DBGP debugger, e.g. PHP, Python, Ruby, 28 | Perl, NodeJS, Tcl... 29 | 30 | The actual installation is no different than for any other Vim plugin, you can 31 | 32 | * install manually: Clone or download a tarball of the plugin and move its 33 | content in your `~/.vim/` directory. You should call `:helptags ~/.vim/doc` 34 | to generate the necessary help tags afterwards. 35 | * use Pathogen: Clone this repository to your `~/.vim/bundle` directory, run 36 | `:execute pathogen#infect()` and `:call pathogen#helptags()` afterwards. 37 | 38 | * use your favorite plugin manager: Put the respective instruction in your init 39 | file and update your plugins afterwards. For Vundle this would be `Plugin 40 | 'vim-vdebug/vdebug'` and `:PluginInstall`. 41 | 42 | ### Python 2 43 | 44 | When you are stuck on a machine with only `+python` (Python 2) support you can 45 | use the latest [1.5][5] release. 46 | 47 | ## Usage 48 | 49 | There is *extensive* help provided in the form of a Vim help file. This goes 50 | through absolutely everything, from installation to configuration, setting up 51 | debuggers for various languages, explanation of the interface, options, remote 52 | server debugging and more. 53 | 54 | To get this help, type: 55 | 56 | ``` 57 | :help Vdebug 58 | ``` 59 | 60 | ### Quick guide 61 | 62 | Set up any DBGP protocol debugger, e.g. Xdebug. (See :help VdebugSetUp). Start 63 | Vdebug with ``, which will make it wait for an incoming connection. Run the 64 | script you want to debug, with the debugging engine enabled. A new tab will 65 | open with the debugging interface. 66 | 67 | Once in debugging mode, the following default mappings are available: 68 | 69 | * ``: start/run (to next breakpoint/end of script) 70 | * ``: step over 71 | * ``: step into 72 | * ``: step out 73 | * ``: stop debugging (kills script) 74 | * ``: detach script from debugger 75 | * ``: run to cursor 76 | * ``: toggle line breakpoint 77 | * ``: show context variables (e.g. after "eval") 78 | * ``: evaluate variable under cursor 79 | * `:Breakpoint `: set a breakpoint of any type (see :help 80 | VdebugBreakpoints) 81 | * `:VdebugEval `: evaluate some code and display the result 82 | * `e`: evaluate the expression under visual highlight and display the 83 | result 84 | 85 | To stop debugging, press ``. Press it again to close the debugger 86 | interface. 87 | 88 | If you can't get a connection, then chances are you need to spend a bit of time 89 | setting up your environment. Type `:help Vdebug` for more information. 90 | 91 | ## Getting help 92 | 93 | If you're having trouble with Vdebug in any way, here are the steps you can 94 | take to get help (in the right order): 95 | 96 | 1. [Check the issues][3] to see whether it's already come up. 97 | 2. Visit the **#vdebug** irc channel on freenode, someone is normally there. 98 | 3. [Open a new issue.][4] 99 | 100 | ## Debugging 101 | 102 | If you have a problem, and would like to see what's going on under the hood or 103 | raise an issue, it's best to create a log file. You can do this by setting 104 | these options before you start debugging: 105 | 106 | ```vim 107 | :VdebugOpt debug_file ~/vdebug.log 108 | :VdebugOpt debug_file_level 2 109 | ``` 110 | 111 | Then start debugging, and you can follow what's added to the log file as you 112 | go. It shows the communication between the debugging engine and Vdebug. 113 | 114 | If you're creating an issue then it's probably best to upload a log as a Gist, 115 | as it can be pretty large. 116 | 117 | ## Contributing 118 | 119 | I gladly accept contributions to the code. Just fork the repository, make your 120 | changes and open a pull request with detail about your changes. There are a 121 | couple of conditions: 122 | 123 | * The tests must pass (see below) 124 | * Your commit messages should follow the [rules outlined here][2] 125 | 126 | ## Tests 127 | 128 | The tests use `unittest` and `mock`, which are both part of the stdlib in 129 | Python 3. To run the tests, run `python3 -m unittest discover` in the top 130 | directory of the plugin 131 | 132 | ## Licence 133 | 134 | This plugin is released under the [MIT License][1]. 135 | 136 | [1]: https://raw.github.com/vim-vdebug/vdebug/master/LICENCE 137 | [2]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 138 | [3]: https://github.com/vim-vdebug/vdebug/issues/ 139 | [4]: https://github.com/vim-vdebug/vdebug/issues/new 140 | [5]: https://github.com/vim-vdebug/vdebug/releases/tag/v1.5.2 141 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | require 'cucumber' 3 | require 'cucumber/rake/task' 4 | 5 | namespace :test do 6 | desc "Run all tests (unit and integration/specs)" 7 | task :all do 8 | puts "Running unit tests" 9 | Rake::Task["test:unit"].execute 10 | puts "Running integration (spec) tests" 11 | Rake::Task[:spec].execute 12 | puts "Running cucumber features" 13 | Rake::Task[:features].execute 14 | end 15 | 16 | desc "Run unit tests" 17 | task :unit do 18 | if ENV["COVERAGE"] 19 | puts "Running unit tests with coverage (view output at ./htmlcov/index.html)" 20 | cmd = "coverage run -m unittest discover && coverage report -m --include='python3/vdebug/*'" 21 | else 22 | cmd = "python -m unittest discover" 23 | end 24 | puts cmd 25 | system cmd 26 | end 27 | 28 | desc "Run integration tests (alias for `spec`)" 29 | task :integration do 30 | Rake::Task[:spec] 31 | end 32 | end 33 | 34 | RSpec::Core::RakeTask.new(:spec) 35 | Cucumber::Rake::Task.new(:features) do |t| 36 | t.cucumber_opts = "features --format pretty" 37 | end 38 | 39 | task :default => "test:all" 40 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.0.0 2 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VAGRANTFILE_API_VERSION = "2" 5 | 6 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 7 | # Use Debian 7 64-bit as our operating system, same as production 8 | config.vm.box = "debian/wheezy7.6" 9 | config.vm.box_url = "https://github.com/jose-lpa/packer-debian_7.6.0/releases/download/1.0/packer_virtualbox-iso_virtualbox.box" 10 | 11 | # Configure the virtual machine to use 1GB of RAM 12 | config.vm.provider :virtualbox do |vb| 13 | vb.customize ["modifyvm", :id, "--memory", "1024"] 14 | end 15 | 16 | # Forward the vdebug server port to the host 17 | config.vm.network :forwarded_port, guest: 9000, host: 9000 18 | config.vm.network :forwarded_port, guest: 5900, host: 5901 19 | 20 | # Use a shell script to provision 21 | config.vm.provision :shell, path: "bootstrap.sh" 22 | end 23 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash -e 2 | # Hostname 3 | hostname vdebug 4 | echo vdebug > /etc/hostname 5 | 6 | # Set password to 'vdebug' 7 | echo "vagrant:vdebug" | chpasswd 8 | 9 | # Allow passwordless sudo 10 | if grep "vagrant" /etc/sudoers 11 | then 12 | echo "vagrant group already a sudoer" 13 | else 14 | echo "%vagrant ALL=(ALL) NOPASSWD:ALL" 15 | fi 16 | 17 | # Install packages 18 | apt-get update 19 | apt-get install locales-all git-core vim vim-gtk xvfb php5 php5-cli php5-xdebug -y 20 | 21 | # Fix locale 22 | /usr/sbin/update-locale LANG=en_US LC_ALL=en_US 23 | 24 | # Install ruby (http://blog.packager.io/post/101342252191/one-liner-to-get-a-precompiled-ruby-on-your-own) 25 | curl -s https://s3.amazonaws.com/pkgr-buildpack-ruby/current/debian-7/ruby-2.1.5.tgz -o - | sudo tar xzf - -C /usr/local 26 | gem install bundler 27 | 28 | cat <<'EOF' >> /etc/php5/conf.d/*-xdebug.ini 29 | xdebug.remote_enable=on 30 | xdebug.remote_handler=dbgp 31 | xdebug.remote_host=localhost 32 | xdebug.remote_port=9000 33 | EOF 34 | 35 | cat <<'EOF' > /usr/local/bin/php-xdebug 36 | #!/bin/bash 37 | export XDEBUG_CONFIG="idekey=vdebug" 38 | /usr/bin/env php "$@" 39 | EOF 40 | 41 | cat <<'EOF' > /etc/init.d/xvfb 42 | XVFB=/usr/bin/Xvfb 43 | XVFBARGS=":0 -screen 0 1280x1024x24 -ac +extension GLX +render -noreset" 44 | PIDFILE=/var/run/xvfb.pid 45 | case "$1" in 46 | start) 47 | echo -n "Starting virtual X frame buffer: Xvfb" 48 | /sbin/start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile --background --exec $XVFB -- $XVFBARGS 49 | echo "." 50 | ;; 51 | stop) 52 | echo -n "Stopping virtual X frame buffer: Xvfb" 53 | /sbin/start-stop-daemon --stop --quiet --pidfile $PIDFILE 54 | echo "." 55 | ;; 56 | restart) 57 | $0 stop 58 | $0 start 59 | ;; 60 | *) 61 | echo "Usage: /etc/init.d/xvfb {start|stop|restart}" 62 | exit 1 63 | esac 64 | 65 | exit 0 66 | EOF 67 | 68 | chmod +x /etc/init.d/xvfb 69 | update-rc.d xvfb defaults 70 | /etc/init.d/xvfb start 71 | 72 | chmod +x /usr/local/bin/php-xdebug 73 | 74 | cat < /home/vagrant/.vimrc 75 | set nocompatible 76 | filetype off 77 | 78 | " key is , 79 | let mapleader="," 80 | 81 | " Vundle init 82 | set rtp+=~/.vim/bundle/Vundle.vim/ 83 | 84 | " Require Vundle 85 | try 86 | call vundle#begin() 87 | catch 88 | echohl Error | echo "Vundle is not installed." | echohl None 89 | finish 90 | endtry 91 | 92 | Plugin 'gmarik/Vundle.vim' 93 | Plugin 'vim-vdebug/vdebug.git' 94 | 95 | call vundle#end() 96 | 97 | filetype plugin indent on 98 | syntax enable 99 | 100 | "{{{ Settings 101 | set ttyscroll=0 102 | set hidden 103 | set history=1000 104 | set ruler 105 | set ignorecase 106 | set smartcase 107 | set title 108 | set scrolloff=3 109 | set backupdir=~/.vim-tmp,/tmp 110 | set directory=~/.vim-tmp,/tmp 111 | set wrapscan 112 | set visualbell 113 | set backspace=indent,eol,start 114 | "Status line coolness 115 | set laststatus=2 116 | set showcmd 117 | " Search things 118 | set hlsearch 119 | set incsearch " ...dynamically as they are typed. 120 | " Folds 121 | set foldmethod=marker 122 | set wildmenu 123 | set wildmode=list:longest,full 124 | set nohidden 125 | set shortmess+=filmnrxoOt 126 | set viewoptions=folds,options,cursor,unix,slash 127 | set virtualedit=onemore 128 | set shell=bash\ --login 129 | set nocursorcolumn 130 | set nocursorline 131 | syntax sync minlines=256 132 | "Spaces, not tabs 133 | set shiftwidth=4 134 | set tabstop=4 135 | set expandtab 136 | " Line numbers 137 | set relativenumber 138 | "}}} 139 | EOF 140 | 141 | chown vagrant:vagrant /home/vagrant/.vimrc 142 | pip install mock 143 | 144 | # Do things as the vagrant user 145 | sudo -u vagrant bash << EOF 146 | echo "export LANG=en_US.UTF-8" >> /home/vagrant/.bashrc 147 | echo "export DISPLAY=:0" >> /home/vagrant/.bashrc 148 | mkdir -p /home/vagrant/.vim-tmp /home/vagrant/.vim/bundle 149 | git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim 150 | git clone https://github.com/vim-vdebug/vdebug.git ~/.vim/bundle/vdebug 151 | cd /vagrant 152 | bundle install 153 | EOF 154 | 155 | ### Neovim installation 156 | cat < /home/vagrant/neovim.sh 157 | ### This script compiles and builds neovim 158 | # Install required build tools 159 | sudo apt-get install pkg-config build-essential libtool automake software-properties-common python-dev -y 160 | 161 | # install latest cmake 162 | # Taken from https://askubuntu.com/questions/355565/how-to-install-latest-cmake-version-in-linux-ubuntu-from-command-line 163 | # Uninstall the default version provided by Ubuntu's package manager: 164 | sudo apt-get purge cmake -y 165 | # Go to the official CMake webpage, then download and extract the latest version. 166 | cd /tmp 167 | wget https://cmake.org/files/v3.9/cmake-3.9.4.tar.gz 168 | tar -xzvf cmake-3.9.4.tar.gz 169 | cd cmake-3.9.4/ 170 | # Install the extracted source by running: 171 | ./bootstrap 172 | make -j4 173 | sudo make install 174 | 175 | # Install the neovim itself 176 | cd ~ && git clone https://github.com/neovim/neovim.git && cd neovim 177 | make CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX=$(echo ~vagrant)/neovim" 178 | make install 179 | 180 | # install neovim-python client 181 | sudo pip install neovim 182 | 183 | # Set ide key 184 | echo PATH="$(echo ~vagrant)/neovim/build/bin:$PATH" | sudo tee -a /etc/profile 185 | alias nvim="nvim -u ~/.vimrc" 186 | EOF 187 | 188 | chown vagrant:vagrant /home/vagrant/neovim.sh 189 | chmod +x /home/vagrant/neovim.sh 190 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | version=`cat VERSION` 3 | echo "Building vdebug version $version" 4 | tar -cvzf vdebug-$version.tar.gz doc plugin python3 syntax CHANGELOG LICENCE README.md VERSION 5 | -------------------------------------------------------------------------------- /features/correct_window_setup.feature: -------------------------------------------------------------------------------- 1 | Feature: Correct window setup 2 | In order to use Vdebug with all window panels 3 | As a user 4 | I want to see correct watch, stack and status information 5 | 6 | Scenario: The status window 7 | Given I have a file example.php containing 8 | """ 9 | 12 | """ 13 | When I start the debugger with the PHP script example.php 14 | Then the status should be break 15 | And the status window should contain :9000 16 | 17 | Scenario: The watch window 18 | Given I have a file example.php containing 19 | """ 20 | 24 | """ 25 | And I start the debugger with the PHP script example.php 26 | When I step over 27 | Then the watch window should show $var1 28 | And the watch window should show $var2 29 | And the watch window variable $var1 should be (int) 1 30 | And the watch window variable $var2 should be (uninitialized) 31 | 32 | Scenario: The stack window 33 | Given I have a file example.php containing 34 | """ 35 | 39 | """ 40 | And I start the debugger with the PHP script example.php 41 | When I step over 42 | Then the first item on the stack should show the file example.php 43 | And the first item on the stack should show line 3 44 | 45 | Scenario: Reading the stack window with multiple files 46 | Given I have a file example.php containing 47 | """ 48 | 51 | """ 52 | And I have a file example2.php containing 53 | """ 54 | 58 | """ 59 | And I start the debugger with the PHP script example.php 60 | When I step in 61 | Then item 1 on the stack should show the file example2.php 62 | And item 1 on the stack should show line 2 63 | And item 2 on the stack should show the file example.php 64 | And item 2 on the stack should show line 2 65 | -------------------------------------------------------------------------------- /features/eval.feature: -------------------------------------------------------------------------------- 1 | Feature: Evaluating expressions 2 | In order to evaluate variables in Vdebug 3 | As a user 4 | I want to see the evaluated variable in the watch window 5 | 6 | Scenario: Evaluating a PHP expression 7 | Given I have a file example.php containing 8 | """ 9 | 12 | """ 13 | And I start the debugger with the PHP script example.php 14 | When I evaluate "$var1" 15 | Then the watch window should show Eval of: '$var1' 16 | 17 | Scenario: Evaluating a PHP expression with VdebugEval! 18 | Given I have a file example.php containing 19 | """ 20 | 24 | """ 25 | And I start the debugger with the PHP script example.php 26 | When I evaluate "$var1" with VdebugEval! 27 | Then the watch window should show Eval of: '$var1' 28 | When I step over 29 | Then the watch window should show Eval of: '$var1' 30 | 31 | Scenario: Evaluating a PHP expression and resetting 32 | Given I have a file example.php containing 33 | """ 34 | 38 | """ 39 | And I start the debugger with the PHP script example.php 40 | When I evaluate "$var1" with VdebugEval! 41 | Then the watch window should show Eval of: '$var1' 42 | When I run VdebugEval without any arguments 43 | Then the watch window should show Locals 44 | -------------------------------------------------------------------------------- /features/step_definitions/debugger_command_steps.rb: -------------------------------------------------------------------------------- 1 | When "I step over" do 2 | vdebug.step_over 3 | vdebug.running?.should be(true), "Vdebug is not running: #{vdebug.messages}" 4 | end 5 | 6 | When "I step in" do 7 | vdebug.step_in 8 | vdebug.running?.should be(true), "Vdebug is not running: #{vdebug.messages}" 9 | end 10 | -------------------------------------------------------------------------------- /features/step_definitions/evaluate_steps.rb: -------------------------------------------------------------------------------- 1 | When(/^I evaluate "(.*)"$/) do |expr| 2 | vdebug.evaluate expr 3 | end 4 | 5 | When(/^I evaluate "(.*)" with VdebugEval!$/) do |expr| 6 | vdebug.evaluate! expr 7 | end 8 | 9 | When "I run VdebugEval without any arguments" do 10 | vdebug.evaluate 11 | end 12 | -------------------------------------------------------------------------------- /features/step_definitions/hooks.rb: -------------------------------------------------------------------------------- 1 | Before('@broken') do 2 | pending 3 | end 4 | -------------------------------------------------------------------------------- /features/step_definitions/interface_steps.rb: -------------------------------------------------------------------------------- 1 | Then "the last message should be $message" do |message| 2 | sleep 2 3 | vdebug.last_error.should eq(message) 4 | end 5 | -------------------------------------------------------------------------------- /features/step_definitions/php_steps.rb: -------------------------------------------------------------------------------- 1 | Given "I start the debugger with the PHP script $script" do |script| 2 | vdebug.start_listening 3 | full_script_path = Dir.getwd + "/" + script 4 | run_php_script full_script_path 5 | vdebug.running?.should be(true), "Error, vdebug is not running\nVIM messages: #{vdebug.messages}" 6 | end 7 | -------------------------------------------------------------------------------- /features/step_definitions/source_file_steps.rb: -------------------------------------------------------------------------------- 1 | Given "I have a file $file containing" do |file, content| 2 | dir = File.dirname(file) 3 | unless dir == '.' 4 | FileUtils.mkdir_p(dir) 5 | end 6 | File.open file, "w" do |f| 7 | f.write content 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /features/step_definitions/stack_window_steps.rb: -------------------------------------------------------------------------------- 1 | Then "the first item on the stack should show the file $file" do |file| 2 | vdebug.stack.first[:file].should include file 3 | end 4 | 5 | Then "the first item on the stack should show line $line" do |line| 6 | vdebug.stack.first[:line].should == line 7 | end 8 | 9 | Then "item $item on the stack should show the file $file" do |idx, file| 10 | idx = idx.to_i 11 | stack_length = vdebug.stack.length 12 | stack_length.should be >= idx, "There are only #{stack_length} items on the stack" 13 | vdebug.stack[idx-1][:file].should include file 14 | end 15 | 16 | Then "item $item on the stack should show line $line" do |idx, line| 17 | idx = idx.to_i 18 | stack_length = vdebug.stack.length 19 | stack_length.should be >= idx, "There are only #{stack_length} items on the stack" 20 | vdebug.stack[idx-1][:line].should == line 21 | end 22 | -------------------------------------------------------------------------------- /features/step_definitions/status_window_steps.rb: -------------------------------------------------------------------------------- 1 | Then "the status should be $status" do |status| 2 | vdebug.status.should == status 3 | end 4 | 5 | Then "the status window should contain $string" do |string| 6 | vdebug.status_window_content.should include string 7 | end 8 | -------------------------------------------------------------------------------- /features/step_definitions/trace_window_steps.rb: -------------------------------------------------------------------------------- 1 | When(/^I trace "(.*)"$/) do |expr| 2 | vdebug.trace expr 3 | sleep 1 4 | end 5 | 6 | Then "the trace window should show $text" do |text| 7 | vdebug.clear_buffer_cache! 8 | vdebug.trace_window_content.should include text 9 | end 10 | 11 | -------------------------------------------------------------------------------- /features/step_definitions/watch_window_steps.rb: -------------------------------------------------------------------------------- 1 | Then "the watch window should show $text" do |text| 2 | vdebug.watch_window_content.should include text 3 | end 4 | 5 | Then "the watch window variable $var should be $value" do |var, value| 6 | vdebug.watch_vars[var].should == value 7 | end 8 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'vimrunner' 2 | require 'securerandom' 3 | require_relative "../../rubylib/vdebug" 4 | 5 | begin 6 | TMP_DIR = "tmpspace_#{SecureRandom::hex(3)}" 7 | PHP_INI = File.expand_path('../../../.travis.php.ini', __FILE__) 8 | 9 | Before do 10 | Dir.mkdir TMP_DIR unless Dir.exists? TMP_DIR 11 | Dir.chdir TMP_DIR 12 | 13 | # Setup plugin in the Vim instance 14 | plugin_path = File.expand_path('../../..', __FILE__) 15 | vim.add_plugin(plugin_path, 'plugin/vdebug.vim') 16 | end 17 | 18 | After do 19 | kill_vim 20 | Dir.chdir '..' 21 | system "rm -r #{TMP_DIR}" 22 | end 23 | 24 | rescue Exception 25 | system "rm -r #{TMP_DIR}" 26 | raise 27 | end 28 | -------------------------------------------------------------------------------- /features/support/helpers.rb: -------------------------------------------------------------------------------- 1 | require 'shellwords' 2 | 3 | module VdebugHelper 4 | def vdebug 5 | @vdebug ||= Vdebug.new vim 6 | end 7 | 8 | def vim 9 | @vim ||= Vimrunner.start 10 | end 11 | 12 | def kill_vim 13 | if @vim 14 | @vim.kill 15 | @vim = nil 16 | if @vdebug 17 | @vdebug.remove_lock_file! 18 | @vdebug = nil 19 | end 20 | end 21 | end 22 | end 23 | 24 | module ScriptRunner 25 | STDERR_FILE = "error.out" 26 | 27 | def run_php_script(path) 28 | fork_and_run "php -c #{PHP_INI}", Shellwords.escape(path) 29 | end 30 | 31 | def stderr_contents 32 | File.read(STDERR_FILE) 33 | end 34 | 35 | def fork_and_run(bin, argstr) 36 | fork do 37 | exec %Q{XDEBUG_CONFIG="idekey=something" /usr/bin/env #{bin} #{argstr} 2>#{STDERR_FILE}} 38 | exit! 39 | end 40 | sleep 1 41 | end 42 | end 43 | 44 | World(VdebugHelper) 45 | World(ScriptRunner) 46 | -------------------------------------------------------------------------------- /features/trace.feature: -------------------------------------------------------------------------------- 1 | Feature: Tracing expressions 2 | In order to trace variables in Vdebug 3 | As a user 4 | I want to see the evaluated expression in the trace window 5 | 6 | Scenario: Tracing a PHP expression 7 | Given I have a file example.php containing 8 | """ 9 | 13 | """ 14 | And I start the debugger with the PHP script example.php 15 | When I trace "$var1" 16 | And I step over 17 | Then the trace window should show Trace of: '$var1' 18 | And the trace window should show $var1 = (int) 1 19 | When I step over 20 | Then the trace window should show Trace of: '$var1' 21 | And the trace window should show $var1 = (int) 1 22 | -------------------------------------------------------------------------------- /features/unicode_in_source_buffer.feature: -------------------------------------------------------------------------------- 1 | Feature: Unicode in source buffer 2 | In order to use Vdebug with all character sets 3 | As a user 4 | I want to debug code that uses Unicode characters 5 | 6 | # FIXME this is a known failure but we could not figure out the reason. 7 | @broken 8 | Scenario: Debug a PHP file containing Unicode characters 9 | Given I have a file test.php containing 10 | """ 11 | '中文'); 13 | ?> 14 | """ 15 | And I start the debugger with the PHP script test.php 16 | When I step over 17 | Then the watch window should show $arr 18 | -------------------------------------------------------------------------------- /features/valid_file_names.feature: -------------------------------------------------------------------------------- 1 | Feature: Valid file names 2 | In order to use Vdebug with all file names 3 | As a user 4 | I want to debug a file with any valid system characters 5 | 6 | Scenario: Debug a PHP file containing spaces 7 | Given I have a file My File.php containing 8 | """ 9 | 12 | """ 13 | And I start the debugger with the PHP script My File.php 14 | When I step over 15 | Then the watch window should show $var 16 | 17 | Scenario: Debug a PHP file with a space in the directory 18 | Given I have a file A Path/myfile.php containing 19 | """ 20 | 23 | """ 24 | And I start the debugger with the PHP script A Path/myfile.php 25 | When I step over 26 | Then the watch window should show $var 27 | -------------------------------------------------------------------------------- /plugin/vdebug.vim: -------------------------------------------------------------------------------- 1 | " Vdebug: Powerful, fast, multi-language debugger client for Vim. 2 | " 3 | " Script Info {{{ 4 | "============================================================================= 5 | " Copyright: Copyright (C) 2012 Jon Cairns 6 | " Licence: The MIT Licence (see LICENCE file) 7 | " Name Of File: vdebug.vim 8 | " Description: Multi-language debugger client for Vim (PHP, Ruby, Python, 9 | " Perl, NodeJS) 10 | " Maintainer: Jon Cairns 11 | " Version: 2.0.0 12 | " Inspired by the Xdebug plugin, which was originally written by 13 | " Seung Woo Shin sayclub.com> and extended by many 14 | " others. 15 | " Usage: Use :help Vdebug for information on how to configure and use 16 | " this script, or visit the Github page 17 | " https://github.com/vim-vdebug/vdebug. 18 | " 19 | "============================================================================= 20 | " }}} 21 | 22 | " avoid double loading of vdebug 23 | if exists('g:is_vdebug_loaded') 24 | finish 25 | endif 26 | 27 | " Set a special flag used only by this plugin for preventing doubly 28 | " loading the script. 29 | let g:is_vdebug_loaded = 1 30 | 31 | " Do not source this script when python is not compiled in. 32 | if !has('python3') 33 | echomsg ':python3 is not available, vdebug will not be loaded.' 34 | finish 35 | endif 36 | 37 | if !exists('g:vdebug_force_ascii') 38 | " Nice characters get screwed up on windows 39 | if has('win32') || has('win64') 40 | let g:vdebug_force_ascii = 1 41 | elseif has('multi_byte') == 0 42 | let g:vdebug_force_ascii = 1 43 | else 44 | let g:vdebug_force_ascii = 0 45 | end 46 | endif 47 | 48 | if !exists('g:vdebug_options') 49 | let g:vdebug_options = {} 50 | endif 51 | 52 | if !exists('g:vdebug_keymap') 53 | let g:vdebug_keymap = {} 54 | endif 55 | 56 | if !exists('g:vdebug_features') 57 | let g:vdebug_features = {} 58 | endif 59 | 60 | if !exists('g:vdebug_leader_key') 61 | let g:vdebug_leader_key = '' 62 | endif 63 | 64 | let g:vdebug_keymap_defaults = { 65 | \ 'run' : '', 66 | \ 'run_to_cursor' : '', 67 | \ 'step_over' : '', 68 | \ 'step_into' : '', 69 | \ 'step_out' : '', 70 | \ 'close' : '', 71 | \ 'detach' : '', 72 | \ 'set_breakpoint' : '', 73 | \ 'get_context' : '', 74 | \ 'eval_under_cursor' : '', 75 | \ 'eval_visual' : 'e' 76 | \} 77 | 78 | let g:vdebug_options_defaults = { 79 | \ 'port' : 9000, 80 | \ 'timeout' : 20, 81 | \ 'server' : '', 82 | \ "proxy_host" : '', 83 | \ "proxy_port" : 9001, 84 | \ 'on_close' : 'stop', 85 | \ 'break_on_open' : 1, 86 | \ 'ide_key' : '', 87 | \ 'debug_window_level' : 0, 88 | \ 'debug_file_level' : 0, 89 | \ 'debug_file' : '', 90 | \ 'path_maps' : {}, 91 | \ 'watch_window_style' : 'expanded', 92 | \ 'marker_default' : '⬦', 93 | \ 'marker_closed_tree' : '▸', 94 | \ 'marker_open_tree' : '▾', 95 | \ 'sign_breakpoint' : '▷', 96 | \ 'sign_current' : '▶', 97 | \ 'sign_current_stack_position' : '▶', 98 | \ 'sign_disabled': '▌▌', 99 | \ 'continuous_mode' : 1, 100 | \ 'background_listener' : 1, 101 | \ 'auto_start' : 1, 102 | \ 'simplified_status': 1, 103 | \ 'layout': 'vertical', 104 | \} 105 | 106 | " Different symbols for non unicode Vims 107 | if g:vdebug_force_ascii == 1 108 | let g:vdebug_options_defaults['marker_default'] = '*' 109 | let g:vdebug_options_defaults['marker_closed_tree'] = '+' 110 | let g:vdebug_options_defaults['marker_open_tree'] = '-' 111 | let g:vdebug_options_defaults['sign_breakpoint'] = 'B>' 112 | let g:vdebug_options_defaults['sign_current'] = '->' 113 | let g:vdebug_options_defaults['sign_current_stack_position'] = '->' 114 | let g:vdebug_options_defaults['sign_disabled'] = 'B|' 115 | endif 116 | 117 | " Create the top dog 118 | python3 import vdebug.debugger_interface 119 | python3 debugger = vdebug.debugger_interface.DebuggerInterface() 120 | 121 | " Commands 122 | command! -nargs=? VdebugChangeStack python3 debugger.change_stack() 123 | command! -nargs=? -complete=customlist,s:BreakpointTypes Breakpoint python3 debugger.cycle_breakpoint() 124 | command! -nargs=? -complete=customlist,s:BreakpointTypes SetBreakpoint python3 debugger.set_breakpoint() 125 | command! VdebugStart python3 debugger.run() 126 | command! -nargs=? BreakpointRemove python3 debugger.remove_breakpoint() 127 | command! -nargs=? BreakpointToggle python3 debugger.toggle_breakpoint() 128 | command! BreakpointWindow python3 debugger.toggle_breakpoint_window() 129 | command! -nargs=? -bang VdebugEval python3 debugger.handle_eval('', ) 130 | command! -nargs=+ -complete=customlist,s:OptionNames VdebugOpt :call Vdebug_set_option() 131 | command! -nargs=+ VdebugPathMap :call Vdebug_path_map() 132 | command! -nargs=+ VdebugAddPathMap :call Vdebug_add_path_map() 133 | command! -nargs=? VdebugTrace python3 debugger.handle_trace() 134 | command! -nargs=? BreakpointStatus python3 debugger.breakpoint_status() 135 | 136 | if hlexists('DbgCurrentLine') == 0 137 | hi default DbgCurrentLine term=reverse ctermfg=White ctermbg=Red guifg=#ffffff guibg=#ff0000 138 | end 139 | if hlexists('DbgCurrentSign') == 0 140 | hi default DbgCurrentSign term=reverse ctermfg=White ctermbg=Red guifg=#ffffff guibg=#ff0000 141 | end 142 | if hlexists('DbgBreakptLine') == 0 143 | hi default DbgBreakptLine term=reverse ctermfg=White ctermbg=Green guifg=#ffffff guibg=#00ff00 144 | end 145 | if hlexists('DbgBreakptSign') == 0 146 | hi default DbgBreakptSign term=reverse ctermfg=White ctermbg=Green guifg=#ffffff guibg=#00ff00 147 | end 148 | if hlexists('DbgDisabledLine') == 0 149 | highlight DbgDisabledLine term=reverse ctermbg=White ctermfg=Cyan guibg=#b4ee9a guifg=#888888 150 | endif 151 | if hlexists('DbgDisabledSign') == 0 152 | highlight DbgDisabledSign term=reverse ctermbg=White ctermfg=Cyan guibg=#b4ee9a guifg=#888888 153 | endif 154 | if hlexists('DbgCurrentStackPositionSign') == 0 155 | highlight DbgCurrentStackPositionSign term=reverse ctermbg=White ctermfg=Cyan guibg=#e17e67 guifg=#888888 156 | endif 157 | if hlexists('DbgCurrentStackPositionLine') == 0 158 | highlight DbgCurrentStackPositionLine term=reverse ctermbg=White ctermfg=Cyan guibg=#e17e67 guifg=#888888 159 | endif 160 | 161 | " Signs and highlighted lines for breakpoints, etc. 162 | function! s:DefineSigns() 163 | exe 'sign define breakpt text=' . g:vdebug_options['sign_breakpoint'] . ' texthl=DbgBreakptSign linehl=DbgBreakptLine' 164 | exe 'sign define current text=' . g:vdebug_options['sign_current'] . ' texthl=DbgCurrentSign linehl=DbgCurrentLine' 165 | exe 'sign define breakpt_dis text=' . g:vdebug_options['sign_disabled'] . ' texthl=DbgDisabledSign linehl=DbgDisabledLine' 166 | exe 'sign define current_stack_position text=' . g:vdebug_options['sign_current_stack_position'] . ' texthl=DbgCurrentStackPositionSign linehl=DbgCurrentStackPositionLine' 167 | endfunction 168 | 169 | function! s:BreakpointTypes(A,L,P) 170 | let arg_to_cursor = strpart(a:L,11,a:P) 171 | let space_idx = stridx(arg_to_cursor,' ') 172 | if space_idx == -1 173 | return filter(['conditional ','exception ','return ','call ','watch '],'v:val =~ "^".a:A.".*"') 174 | else 175 | return [] 176 | endif 177 | endfunction 178 | 179 | function! s:HandleEval(bang,code) 180 | let code = escape(a:code,'"') 181 | if strlen(a:bang) 182 | execute 'python3 debugger.save_eval("'.code.'")' 183 | endif 184 | if strlen(a:code) 185 | execute 'python3 debugger.handle_eval("'.code.'")' 186 | endif 187 | endfunction 188 | 189 | " Reload options dictionary, by merging with default options. 190 | " 191 | " This should be called if you want to update the options after vdebug has 192 | " been loaded. 193 | function! Vdebug_load_options(options) 194 | " Merge options with defaults 195 | let g:vdebug_options = extend(g:vdebug_options_defaults, a:options) 196 | 197 | " Override with single defined params ie. g:vdebug_options_port 198 | let single_defined_params = s:Vdebug_get_options() 199 | let g:vdebug_options = extend(g:vdebug_options, single_defined_params) 200 | 201 | call s:DefineSigns() 202 | python3 debugger.reload_options() 203 | endfunction 204 | 205 | " Get options defined outside of the vdebug_options dictionary 206 | " 207 | " This helps for when users might want to define a single option by itself 208 | " without needing the dictionary ie. vdebug_options_port = 9000 209 | function! s:Vdebug_get_options() 210 | let param_namespace = 'g:vdebug_options_' 211 | let param_namespace_len = strlen(param_namespace) 212 | 213 | " Get the paramter names and concat the g:vdebug_options namespace 214 | let parameters = map(keys(g:vdebug_options_defaults), 'param_namespace.v:val') 215 | 216 | " Only use the defined parameters 217 | let existing_params = filter(parameters, 'exists(v:val)') 218 | 219 | " put into a dictionary for use with extend() 220 | let params = {} 221 | for name in existing_params 222 | let val = eval(name) 223 | 224 | " Remove g:vdebug_options namespace from param 225 | let name = strpart(name, param_namespace_len) 226 | let params[name] = val 227 | endfor 228 | if !empty(params) 229 | echoerr 'Deprication Warning: The options g:vdebug_options_* are depricated. Please use the g:vdebug_options dictionary.' 230 | endif 231 | return params 232 | endfunction 233 | 234 | " Assign keymappings, and merge with defaults. 235 | " 236 | " This should be called if you want to update the keymappings after vdebug has 237 | " been loaded. 238 | function! Vdebug_load_keymaps(keymaps) 239 | " Unmap existing keys, if needed 240 | " the keys should in theory exist because they are part of the defaults 241 | if has_key(g:vdebug_keymap, 'run') 242 | exe 'silent! nunmap '.g:vdebug_keymap['run'] 243 | endif 244 | if has_key(g:vdebug_keymap, 'close') 245 | exe 'silent! nunmap '.g:vdebug_keymap['close'] 246 | endif 247 | if has_key(g:vdebug_keymap, 'set_breakpoint') 248 | exe 'silent! nunmap '.g:vdebug_keymap['set_breakpoint'] 249 | endif 250 | if has_key(g:vdebug_keymap, 'eval_visual') 251 | exe 'silent! vunmap '.g:vdebug_keymap['eval_visual'] 252 | endif 253 | 254 | " Merge keymaps with defaults 255 | let g:vdebug_keymap = extend(g:vdebug_keymap_defaults, a:keymaps) 256 | 257 | " Mappings allowed in non-debug mode 258 | " XXX: don't use keymaps not found in g:vdebug_keymap_defaults 259 | exe 'noremap '.g:vdebug_keymap['run'].' :python3 debugger.run()' 260 | exe 'noremap '.g:vdebug_keymap['close'].' :python3 debugger.close()' 261 | exe 'noremap '.g:vdebug_keymap['set_breakpoint'].' :python3 debugger.set_breakpoint()' 262 | 263 | " Exceptional case for visual evaluation 264 | exe 'vnoremap '.g:vdebug_keymap['eval_visual'].' :python3 debugger.handle_visual_eval()' 265 | python3 debugger.reload_keymappings() 266 | endfunction 267 | 268 | function! s:OptionNames(A,L,P) 269 | let arg_to_cursor = strpart(a:L,10,a:P) 270 | let space_idx = stridx(arg_to_cursor,' ') 271 | if space_idx == -1 272 | return filter(keys(g:vdebug_options_defaults),'v:val =~ a:A') 273 | else 274 | let opt_name = strpart(arg_to_cursor,0,space_idx) 275 | if has_key(g:vdebug_options,opt_name) 276 | return [g:vdebug_options[opt_name]] 277 | else 278 | return [] 279 | endif 280 | endif 281 | endfunction 282 | 283 | function! Vdebug_set_option(option, ...) 284 | if ! a:0 285 | let g:vdebug_options[a:option] 286 | return 287 | endif 288 | if a:option == 'path_maps' 289 | echomsg 'use :VdebugAddPathMap to add extra or :VdebugPathMap to set new' 290 | return 291 | elseif a:option == 'window_commands' 292 | echomsg 'update window_commands in your vimrc please' 293 | return 294 | elseif a:option == 'window_arrangement' 295 | echomsg 'update window_arrangement in your vimrc please' 296 | return 297 | endif 298 | echomsg 'Setting vdebug option "' . a:option . '" to: ' . a:1 299 | let g:vdebug_options[a:option] = a:1 300 | call s:DefineSigns() 301 | python3 debugger.reload_options() 302 | endfunction 303 | 304 | function! Vdebug_add_path_map(from, to) 305 | echomsg 'Adding vdebug path map "{' . a:from . ':' . a:to . '}"' 306 | let g:vdebug_options['path_maps'] = extend(g:vdebug_options['path_maps'], {a:from: a:to}) 307 | python3 debugger.reload_options() 308 | endfunction 309 | 310 | function! Vdebug_path_map(from, to) 311 | echomsg 'Setting vdebug path maps to "{' . a:from . ':' . a:to . '}"' 312 | let g:vdebug_options['path_maps'] = {a:from: a:to} 313 | python3 debugger.reload_options() 314 | endfunction 315 | 316 | function! Vdebug_get_visual_selection() 317 | let [lnum1, col1] = getpos("'<")[1:2] 318 | let [lnum2, col2] = getpos("'>")[1:2] 319 | let lines = getline(lnum1, lnum2) 320 | let lines[-1] = lines[-1][: col2 - 1] 321 | let lines[0] = lines[0][col1 - 1:] 322 | return join(lines, "\n") 323 | endfunction 324 | 325 | function! Vdebug_edit(filename) 326 | try 327 | execute 'buffer' fnameescape(a:filename) 328 | catch /^Vim\%((\a\+)\)\=:E94/ 329 | execute 'silent view' fnameescape(a:filename) 330 | endtry 331 | endfunction 332 | 333 | function! Vdebug_statusline() 334 | return pyeval('debugger.status_for_statusline()') 335 | endfunction 336 | 337 | augroup Vdebug 338 | augroup END 339 | augroup VdebugOut 340 | autocmd VimLeavePre * python3 debugger.quit() 341 | augroup END 342 | 343 | call Vdebug_load_options(g:vdebug_options) 344 | call Vdebug_load_keymaps(g:vdebug_keymap) 345 | -------------------------------------------------------------------------------- /python3/vdebug/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-vdebug/vdebug/66517871178779ac54e19ed0f34d66805b820664/python3/vdebug/__init__.py -------------------------------------------------------------------------------- /python3/vdebug/breakpoint.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | from . import error 4 | from . import log 5 | 6 | 7 | class Store: 8 | 9 | def __init__(self): 10 | self.breakpoints = {} 11 | self.api = None 12 | 13 | def link_api(self, api): 14 | self.api = api 15 | num_bps = len(self.breakpoints) 16 | if num_bps > 0: 17 | log.Log("Registering %i breakpoints with the debugger" % num_bps) 18 | for bp in self.breakpoints.values(): 19 | res = self.api.breakpoint_set(bp.get_cmd()) 20 | bp.set_debugger_id(res.get_id()) 21 | 22 | # Update line-based breakpoints with a dict of IDs and lines 23 | def update_lines(self, lines): 24 | for id, line in lines.items(): 25 | try: 26 | self.breakpoints[id].set_line(line) 27 | log.Log("Updated line number of breakpoint %s to %s" % (id, 28 | line)) 29 | except ValueError: 30 | pass 31 | # No method set_line, not a line breakpoint 32 | 33 | def unlink_api(self): 34 | self.api = None 35 | 36 | def add_breakpoint(self, breakpoint): 37 | log.Log("Adding " + str(breakpoint)) 38 | self.breakpoints[str(breakpoint.get_id())] = breakpoint 39 | breakpoint.on_add() 40 | if self.api is not None: 41 | res = self.api.breakpoint_set(breakpoint.get_cmd()) 42 | breakpoint.set_debugger_id(res.get_id()) 43 | 44 | def toggle_breakpoint_by_id(self, id): 45 | id = str(id) 46 | if id not in self.breakpoints: 47 | raise error.BreakpointError("No breakpoint matching ID %s" % id) 48 | if self.breakpoints[id].enabled: 49 | self.disable_breakpoint_by_id(id) 50 | else: 51 | self.enable_breakpoint_by_id(id) 52 | 53 | def enable_breakpoint_by_id(self, id): 54 | id = str(id) 55 | if id not in self.breakpoints: 56 | raise error.BreakpointError("No breakpoint matching ID %s" % id) 57 | dbg_id = self.breakpoints[id].get_debugger_id() 58 | if dbg_id is not None: 59 | self.api.breakpoint_enable(dbg_id) 60 | self.breakpoints[id].on_enable() 61 | 62 | def disable_breakpoint_by_id(self, id): 63 | id = str(id) 64 | if id not in self.breakpoints: 65 | raise error.BreakpointError("No breakpoint matching ID %s" % id) 66 | dbg_id = self.breakpoints[id].get_debugger_id() 67 | if dbg_id is not None: 68 | self.api.breakpoint_disable(dbg_id) 69 | self.breakpoints[id].on_disable() 70 | 71 | def remove_breakpoint(self, breakpoint): 72 | self.remove_breakpoint_by_id(breakpoint.get_id()) 73 | 74 | def remove_breakpoint_by_id(self, id): 75 | id = str(id) 76 | if id not in self.breakpoints: 77 | raise error.BreakpointError("No breakpoint matching ID %s" % id) 78 | log.Log("Removing breakpoint id %s" % id) 79 | if self.api is not None: 80 | dbg_id = self.breakpoints[id].get_debugger_id() 81 | if dbg_id is not None: 82 | self.api.breakpoint_remove(dbg_id) 83 | self.breakpoints[id].on_remove() 84 | del self.breakpoints[id] 85 | 86 | def clear_breakpoints(self): 87 | # We need to copy the keys to a new list, otherwise the for loop 88 | # complains when items are removed. 89 | for id in list(self.breakpoints.keys()): 90 | self.remove_breakpoint_by_id(id) 91 | self.breakpoints = {} 92 | 93 | def get_breakpoint_by_id(self, id): 94 | id = str(id) 95 | if id not in list(self.breakpoints.keys()): 96 | return None 97 | 98 | return self.breakpoints[id] 99 | 100 | def find_breakpoint(self, file, line): 101 | found = None 102 | for bp in self.breakpoints.values(): 103 | if bp.type == "line": 104 | if bp.get_file() == file and\ 105 | bp.get_line() == line: 106 | found = bp.get_id() 107 | break 108 | return found 109 | 110 | 111 | class Breakpoint: 112 | """ Abstract factory for creating a breakpoint object. 113 | 114 | Use the class method parse to create a concrete subclass 115 | of a specific type. 116 | """ 117 | type = None 118 | id = 11000 119 | dbg_id = None 120 | 121 | def __init__(self, ui): 122 | self.id = Breakpoint.id 123 | Breakpoint.id += 1 124 | self.ui = ui 125 | self.enabled = True 126 | 127 | def get_id(self): 128 | return self.id 129 | 130 | def set_debugger_id(self, dbg_id): 131 | self.dbg_id = dbg_id 132 | 133 | def get_debugger_id(self): 134 | return self.dbg_id 135 | 136 | def on_add(self): 137 | self.ui.register_breakpoint(self) 138 | 139 | def on_enable(self): 140 | self.enabled = True 141 | self.ui.enable_breakpoint(self) 142 | 143 | def on_disable(self): 144 | self.enabled = False 145 | self.ui.disable_breakpoint(self) 146 | 147 | def on_remove(self): 148 | self.ui.remove_breakpoint(self) 149 | 150 | @staticmethod 151 | def parse(ui, args): 152 | if args is None: 153 | args = "" 154 | args = args.strip() 155 | if not args: 156 | """ Line breakpoint """ 157 | row = ui.get_current_row() 158 | try: 159 | file = ui.get_current_file() 160 | line = ui.get_current_line() 161 | except error.FilePathError: 162 | raise error.BreakpointError('No file, cannot set breakpoint') 163 | if not line.strip(): 164 | raise error.BreakpointError( 165 | 'Cannot set a breakpoint on an empty line') 166 | return LineBreakpoint(ui, file, row) 167 | arg_parts = args.split(' ') 168 | type = arg_parts.pop(0) 169 | type.lower() 170 | if type == 'conditional': 171 | row = ui.get_current_row() 172 | file = ui.get_current_file() 173 | if not arg_parts: 174 | raise error.BreakpointError( 175 | "Conditional breakpoints require a condition to be " 176 | "specified") 177 | cond = " ".join(arg_parts) 178 | return ConditionalBreakpoint(ui, file, row, cond) 179 | elif type == 'watch': 180 | if not arg_parts: 181 | raise error.BreakpointError( 182 | "Watch breakpoints require a condition to be specified") 183 | expr = " ".join(arg_parts) 184 | log.Log("Expression: %s" % expr) 185 | return WatchBreakpoint(ui, expr) 186 | elif type == 'exception': 187 | if not arg_parts: 188 | raise error.BreakpointError( 189 | "Exception breakpoints require an exception name to be " 190 | "specified") 191 | return ExceptionBreakpoint(ui, arg_parts[0]) 192 | elif type == 'return': 193 | l = len(arg_parts) 194 | if l == 0: 195 | raise error.BreakpointError( 196 | "Return breakpoints require a function name to be " 197 | "specified") 198 | return ReturnBreakpoint(ui, arg_parts[0]) 199 | elif type == 'call': 200 | l = len(arg_parts) 201 | if l == 0: 202 | raise error.BreakpointError( 203 | "Call breakpoints require a function name to be specified") 204 | return CallBreakpoint(ui, arg_parts[0]) 205 | raise error.BreakpointError( 206 | "Unknown breakpoint type, please choose one of: conditional, " 207 | "exception, call or return") 208 | 209 | def get_cmd(self): 210 | pass 211 | 212 | def __str__(self): 213 | return "%s breakpoint, id %i" % (self.type, self.id) 214 | 215 | 216 | class LineBreakpoint(Breakpoint): 217 | type = "line" 218 | 219 | def __init__(self, ui, file, line): 220 | Breakpoint.__init__(self, ui) 221 | self.file = file 222 | self.line = line 223 | 224 | def get_line(self): 225 | return self.line 226 | 227 | def set_line(self, line): 228 | self.line = int(line) 229 | 230 | def get_file(self): 231 | return self.file 232 | 233 | def get_cmd(self): 234 | return '-t {} -f "{}" -n {} -s {}'.format( 235 | self.type, self.file.as_remote(), self.line, "enabled" if self.enabled else "disabled") 236 | 237 | 238 | class TemporaryLineBreakpoint(LineBreakpoint): 239 | def on_add(self): 240 | pass 241 | 242 | def on_remove(self): 243 | pass 244 | 245 | def get_cmd(self): 246 | cmd = LineBreakpoint.get_cmd(self) 247 | return cmd + " -r 1" 248 | 249 | 250 | class ConditionalBreakpoint(LineBreakpoint): 251 | type = "conditional" 252 | 253 | def __init__(self, ui, file, line, condition): 254 | LineBreakpoint.__init__(self, ui, file, line) 255 | self.condition = condition 256 | 257 | def get_cmd(self): 258 | cmd = LineBreakpoint.get_cmd(self) 259 | cmd += " -- " + base64.encodebytes( 260 | self.condition.encode("UTF-8")).decode("UTF-8") 261 | return cmd 262 | 263 | 264 | class WatchBreakpoint(Breakpoint): 265 | type = "watch" 266 | 267 | def __init__(self, ui, expr): 268 | Breakpoint.__init__(self, ui) 269 | self.expr = expr 270 | 271 | def get_cmd(self): 272 | cmd = "-t " + self.type 273 | cmd += " -- " + base64.encodebytes(self.expr) 274 | return cmd 275 | 276 | 277 | class ExceptionBreakpoint(Breakpoint): 278 | type = "exception" 279 | 280 | def __init__(self, ui, exception): 281 | Breakpoint.__init__(self, ui) 282 | self.exception = exception 283 | 284 | def get_cmd(self): 285 | return "-t {} -x {} -s enabled".format(self.type, self.exception) 286 | 287 | 288 | class CallBreakpoint(Breakpoint): 289 | type = "call" 290 | 291 | def __init__(self, ui, function): 292 | Breakpoint.__init__(self, ui) 293 | self.function = function 294 | 295 | def get_cmd(self): 296 | return "-t {} -m {} -s enabled".format(self.type, self.function) 297 | 298 | 299 | class ReturnBreakpoint(CallBreakpoint): 300 | type = "return" 301 | -------------------------------------------------------------------------------- /python3/vdebug/connection.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import queue 3 | import socket 4 | import sys 5 | import threading 6 | import time 7 | import asyncio 8 | import xml.etree.ElementTree as ET 9 | 10 | from . import log 11 | 12 | 13 | class ConnectionHandler: 14 | """Handles read and write operations to a given socket.""" 15 | 16 | def __init__(self, socket, address): 17 | """Accept the socket used for reading and writing. 18 | 19 | socket -- the network socket 20 | """ 21 | self.sock = socket 22 | self.address = address 23 | 24 | def __del__(self): 25 | """Make sure the connection is closed.""" 26 | self.close() 27 | 28 | def isconnected(self): 29 | return 1 30 | 31 | def close(self): 32 | """Close the connection.""" 33 | log.Log("Closing the socket", log.Logger.DEBUG) 34 | self.sock.close() 35 | 36 | def __recv_length(self): 37 | """Get the length of the proceeding message.""" 38 | length = [] 39 | while 1: 40 | c = self.sock.recv(1) 41 | if c == b'': 42 | self.close() 43 | raise EOFError('Socket Closed') 44 | if c == b'\x00': 45 | return int(b''.join(length)) 46 | if c.isdigit(): 47 | length.append(c) 48 | 49 | def __recv_null(self): 50 | """Receive a null byte.""" 51 | while 1: 52 | c = self.sock.recv(1) 53 | if c == b'': 54 | self.close() 55 | raise EOFError('Socket Closed') 56 | if c == b'\x00': 57 | return 58 | 59 | def __recv_body(self, to_recv): 60 | body = [] 61 | while to_recv > 0: 62 | buf = self.sock.recv(to_recv) 63 | if buf == b'': 64 | self.close() 65 | raise EOFError('Socket Closed') 66 | to_recv -= len(buf) 67 | body.append(buf.decode("utf-8")) 68 | return ''.join(body) 69 | 70 | def recv_msg(self): 71 | """Receive a message from the debugger. 72 | 73 | Returns a string, which is expected to be XML. 74 | """ 75 | length = self.__recv_length() 76 | body = self.__recv_body(length) 77 | self.__recv_null() 78 | return body 79 | 80 | def send_msg(self, cmd): 81 | """Send a message to the debugger. 82 | 83 | cmd -- command to send 84 | """ 85 | # self.sock.send(cmd + '\0') 86 | MSGLEN = len(cmd) 87 | totalsent = 0 88 | while totalsent < MSGLEN: 89 | sent = self.sock.send(bytes(cmd[totalsent:].encode())) 90 | if sent == 0: 91 | raise RuntimeError("socket connection broken") 92 | totalsent = totalsent + sent 93 | sent = self.sock.send(b'\x00') 94 | 95 | 96 | class SocketCreator: 97 | 98 | def __init__(self, input_stream=None): 99 | """Create a new Connection. 100 | 101 | The connection is not established until open() is called. 102 | 103 | input_stream -- object for checking input stream and user interrupts (default None) 104 | """ 105 | self.__sock = None 106 | self.input_stream = input_stream 107 | self.proxy_success = False 108 | 109 | def start(self, host='', proxy_host = '', proxy_port = 9001, idekey = None, port=9000, timeout=30): 110 | """Listen for a connection from the debugger. Listening for the actual 111 | connection is handled by self.listen() 112 | 113 | host -- host name where debugger is running (default '') 114 | port -- port number which debugger is listening on (default 9000) 115 | proxy_host -- If using a DBGp Proxy, host name where the proxy is running (default None to disable) 116 | proxy_port -- If using a DBGp Proxy, port where the proxy is listening for debugger connections (default 9001) 117 | idekey -- The idekey that our Api() wrapper is expecting. Only required if using a proxy 118 | timeout -- time in seconds to wait for a debugger connection before giving up (default 30) 119 | """ 120 | print('Waiting for a connection (Ctrl-C to cancel, this message will ' 121 | 'self-destruct in ', timeout, ' seconds...)') 122 | serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 123 | try: 124 | serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 125 | serv.setblocking(1) 126 | serv.bind((host, port)) 127 | serv.listen(5) 128 | if proxy_host and proxy_port: 129 | # Register ourselves with the proxy server 130 | self.proxyinit(proxy_host, proxy_port, port, idekey) 131 | self.__sock = self.accept(serv, timeout) 132 | except socket.timeout: 133 | raise TimeoutError("Timeout waiting for connection") 134 | finally: 135 | self.proxystop(proxy_host, proxy_port, idekey) 136 | serv.close() 137 | 138 | def accept(self, serv, timeout): 139 | """Non-blocking listener. Provides support for keyboard interrupts from 140 | the user. Although it's non-blocking, the user interface will still 141 | block until the timeout is reached. 142 | 143 | serv -- Socket server to listen to. 144 | timeout -- Seconds before timeout. 145 | """ 146 | start = time.time() 147 | while True: 148 | if (time.time() - start) > timeout: 149 | raise socket.timeout 150 | try: 151 | """Check for user interrupts""" 152 | if self.input_stream is not None: 153 | self.input_stream.probe() 154 | return serv.accept() 155 | except socket.error: 156 | pass 157 | 158 | def clear(self): 159 | self.__sock = None 160 | 161 | def socket(self): 162 | return self.__sock 163 | 164 | def has_socket(self): 165 | return self.__sock is not None 166 | 167 | def proxyinit(self, proxy_host, proxy_port, port, idekey): 168 | """Register ourselves with the proxy.""" 169 | if not proxy_host or not proxy_port: 170 | return 171 | 172 | self.log("Connecting to DBGp proxy [%s:%d]" % (proxy_host, proxy_port)) 173 | proxy_conn = socket.create_connection((proxy_host, proxy_port), 30) 174 | 175 | self.log("Sending proxyinit command") 176 | msg = 'proxyinit -p %d -k %s -m 0' % (port, idekey) 177 | proxy_conn.send(msg.encode()) 178 | proxy_conn.shutdown(socket.SHUT_WR) 179 | 180 | # Parse proxy response 181 | response = proxy_conn.recv(8192) 182 | proxy_conn.close() 183 | response = ET.fromstring(response) 184 | self.proxy_success = bool(response.get("success")) 185 | 186 | def proxystop(self, proxy_host, proxy_port, idekey): 187 | """De-register ourselves from the proxy.""" 188 | if not self.proxy_success: 189 | return 190 | 191 | proxy_conn = socket.create_connection((proxy_host, proxy_port), 30) 192 | 193 | self.log("Sending proxystop command") 194 | msg = 'proxystop -k %s' % str(idekey) 195 | proxy_conn.send(msg.encode()) 196 | proxy_conn.close() 197 | self.proxy_success = False 198 | 199 | 200 | 201 | class BackgroundSocketCreator(threading.Thread): 202 | 203 | def __init__(self, host, port, proxy_host, proxy_port, idekey, output_q): 204 | self.__output_q = output_q 205 | self.__host = host 206 | self.__port = port 207 | self.__proxy_host = proxy_host 208 | self.__proxy_port = proxy_port 209 | self.__idekey = idekey 210 | self.proxy_success = False 211 | self.__socket_task = None 212 | self.__loop = None 213 | threading.Thread.__init__(self) 214 | 215 | @staticmethod 216 | def log(message): 217 | log.Log(message, log.Logger.DEBUG) 218 | 219 | def run(self): 220 | # needed for python 3.5 221 | self.__loop = asyncio.new_event_loop() 222 | asyncio.set_event_loop(self.__loop) 223 | self.__loop.run_until_complete(self.run_async()) 224 | 225 | async def run_async(self): 226 | self.log("Started") 227 | self.log("Listening on port %s" % self.__port) 228 | try: 229 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 230 | s.setblocking(False) 231 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 232 | s.bind((self.__host, self.__port)) 233 | s.listen(5) 234 | while 1: 235 | try: 236 | # using ensure_future here since before 3.7, this is not a coroutine, but returns a future 237 | self.__socket_task = asyncio.ensure_future(self.__loop.sock_accept(s)) 238 | if self.__proxy_host and self.__proxy_port: 239 | # Register ourselves with the proxy server 240 | await self.proxyinit() 241 | client, address = await self.__socket_task 242 | # set resulting socket to blocking 243 | client.setblocking(True) 244 | 245 | self.log("Found client, %s" % str(address)) 246 | self.__output_q.put((client, address)) 247 | break 248 | except socket.error: 249 | await self.proxystop() 250 | # No connection 251 | pass 252 | except socket.error as socket_error: 253 | self.log("Error: %s" % str(sys.exc_info())) 254 | self.log("Stopping server") 255 | 256 | if socket_error.errno == errno.EADDRINUSE: 257 | self.log("Address already in use") 258 | print("Socket is already in use") 259 | except asyncio.CancelledError as e: 260 | self.log("Stopping server") 261 | self.__socket_task = None 262 | except Exception as e: 263 | print("Exception caught") 264 | self.log("Error: %s" % str(sys.exc_info())) 265 | self.log("Stopping server") 266 | finally: 267 | await self.proxystop() 268 | self.log("Finishing socket server") 269 | s.close() 270 | 271 | async def proxyinit(self): 272 | """Register ourselves with the proxy.""" 273 | if not self.__proxy_host or not self.__proxy_port: 274 | return 275 | 276 | self.log("Connecting to DBGp proxy [%s:%d]" % (self.__proxy_host, self.__proxy_port)) 277 | proxy_conn = socket.create_connection((self.__proxy_host, self.__proxy_port), 30) 278 | 279 | self.log("Sending proxyinit command") 280 | msg = 'proxyinit -p %d -k %s -m 0' % (self.__port, self.__idekey) 281 | proxy_conn.send(msg.encode()) 282 | proxy_conn.shutdown(socket.SHUT_WR) 283 | 284 | # Parse proxy response 285 | response = proxy_conn.recv(8192) 286 | proxy_conn.close() 287 | response = ET.fromstring(response) 288 | self.proxy_success = bool(response.get("success")) 289 | 290 | async def proxystop(self): 291 | """De-register ourselves from the proxy.""" 292 | if not self.proxy_success: 293 | return 294 | 295 | proxy_conn = socket.create_connection((self.__proxy_host, self.__proxy_port), 30) 296 | 297 | self.log("Sending proxystop command") 298 | msg = 'proxystop -k %s' % str(self.__idekey) 299 | proxy_conn.send(msg.encode()) 300 | proxy_conn.close() 301 | self.proxy_success = False 302 | 303 | 304 | 305 | def _exit(self): 306 | if self.__socket_task: 307 | # this will raise asyncio.CancelledError 308 | self.__socket_task.cancel() 309 | 310 | # called from outside of the thread 311 | def exit(self): 312 | self.__loop.call_soon_threadsafe(self._exit) 313 | 314 | 315 | class SocketServer: 316 | 317 | def __init__(self): 318 | self.__socket_q = queue.Queue(1) 319 | self.__thread = None 320 | 321 | def __del__(self): 322 | self.stop() 323 | 324 | def start(self, host, port, proxy_host, proxy_port, ide_key): 325 | if not self.is_alive(): 326 | self.__thread = BackgroundSocketCreator( 327 | host, port, proxy_host, proxy_port, ide_key, self.__socket_q) 328 | self.__thread.start() 329 | 330 | def is_alive(self): 331 | return self.__thread and self.__thread.is_alive() 332 | 333 | def has_socket(self): 334 | return self.__socket_q.full() 335 | 336 | def socket(self): 337 | return self.__socket_q.get_nowait() 338 | 339 | def stop(self): 340 | if self.is_alive(): 341 | self.__thread.exit() 342 | self.__thread.join(3000) 343 | if self.has_socket(): 344 | self.socket()[0].close() 345 | -------------------------------------------------------------------------------- /python3/vdebug/dbgp.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import xml.etree.ElementTree as ET 3 | 4 | from . import log 5 | 6 | 7 | class Response: 8 | """ Response objects for the DBGP module. 9 | 10 | Contains response data from a command made to the debugger. 11 | """ 12 | 13 | ns = '{urn:debugger_protocol_v1}' 14 | 15 | def __init__(self, response, cmd, cmd_args, api): 16 | self.response = response 17 | self.cmd = cmd 18 | self.cmd_args = cmd_args 19 | self.xml = None 20 | self.api = api 21 | if " 0 481 | self.children = [] 482 | 483 | def __init_children(self, node): 484 | if self.has_children: 485 | idx = 0 486 | tagname = '%sproperty' % self.ns 487 | children = list(node) 488 | if children is not None: 489 | for c in children: 490 | if c.tag == tagname: 491 | idx += 1 492 | p = self._create_child(c, self, self.depth + 1) 493 | self.children.append(p) 494 | if idx == self.num_declared_children: 495 | p.mark_as_last_child() 496 | 497 | def _create_child(self, node, parent, depth): 498 | return ContextProperty(node, parent, depth) 499 | 500 | def mark_as_last_child(self): 501 | self.is_last_child = True 502 | 503 | def is_uninitialized(self): 504 | return self.type == 'uninitialized' 505 | 506 | def child_count(self): 507 | return len(self.children) 508 | 509 | def type_and_size(self): 510 | size = None 511 | if self.has_children: 512 | size = self.num_declared_children 513 | elif self.size is not None: 514 | size = self.size 515 | 516 | if size is None: 517 | return self.type 518 | return "%s [%s]" % (self.type, size) 519 | 520 | 521 | class EvalProperty(ContextProperty): 522 | def __init__(self, node, code, language, parent=None, depth=0): 523 | self.code = code 524 | self.language = language.lower() 525 | self.is_parent = parent is None 526 | ContextProperty.__init__(self, node, parent, depth) 527 | 528 | def _create_child(self, node, parent, depth): 529 | return EvalProperty(node, self.code, self.language, parent, depth) 530 | 531 | def _determine_displayname(self, node): 532 | if self.is_parent: 533 | self.display_name = self.code 534 | else: 535 | if self.language == 'php': 536 | if self.parent.type == 'array': 537 | if node.get('name').isdigit(): 538 | self.display_name = self.parent.display_name + \ 539 | "[%s]" % node.get('name') 540 | else: 541 | self.display_name = self.parent.display_name + \ 542 | "['%s']" % node.get('name') 543 | else: 544 | self.display_name = self.parent.display_name + \ 545 | "->" + node.get('name') 546 | elif self.language == 'perl': 547 | self.display_name = node.get('fullname') 548 | else: 549 | name = node.get('name') 550 | if name is None: 551 | name = "?" 552 | name = self._get_enc_node_text(node, 'name', '?') 553 | if self.parent.type == 'list': 554 | self.display_name = self.parent.display_name + name 555 | else: 556 | self.display_name = self.parent.display_name + \ 557 | "." + name 558 | 559 | 560 | # Errors/Exceptions 561 | class TimeoutError(Exception): 562 | pass 563 | 564 | 565 | class DBGPError(Exception): 566 | """Raised when the debugger returns an error message.""" 567 | pass 568 | 569 | 570 | class CmdNotImplementedError(Exception): 571 | """Raised when the debugger returns an error message.""" 572 | pass 573 | 574 | 575 | class EvalError(Exception): 576 | """Raised when some evaluated code is invalid.""" 577 | pass 578 | 579 | 580 | class ResponseError(Exception): 581 | """An error caused by an unexpected response from the 582 | debugger (e.g. invalid format XML).""" 583 | pass 584 | 585 | 586 | class TraceError(Exception): 587 | """Raised when trace is out of domain.""" 588 | pass 589 | -------------------------------------------------------------------------------- /python3/vdebug/debugger_interface.py: -------------------------------------------------------------------------------- 1 | import vim 2 | 3 | from . import breakpoint 4 | from . import event 5 | from . import opts 6 | from . import session 7 | from . import util 8 | from .ui import vimui 9 | 10 | 11 | class DebuggerInterface: 12 | """Provides all methods used to control the debugger.""" 13 | def __init__(self): 14 | self.breakpoints = breakpoint.Store() 15 | self.ui = vimui.Ui() 16 | 17 | self.session_handler = session.SessionHandler(self.ui, 18 | self.breakpoints) 19 | self.event_dispatcher = event.Dispatcher(self.session_handler) 20 | 21 | def __del__(self): 22 | self.session_handler.close() 23 | self.session_handler = None 24 | 25 | def change_stack(self, args=None): 26 | self.session_handler.dispatch_event("change_stack", args) 27 | 28 | @staticmethod 29 | def reload_options(): 30 | util.Environment.reload() 31 | 32 | def reload_keymappings(self): 33 | self.session_handler.dispatch_event("reload_keymappings") 34 | 35 | def status(self): 36 | return self.session_handler.status() 37 | 38 | def status_for_statusline(self): 39 | return self.session_handler.status_for_statusline() 40 | 41 | def start_if_ready(self): 42 | self.session_handler.start_if_ready() 43 | 44 | def listen(self): 45 | self.session_handler.listen() 46 | 47 | def run(self): 48 | """Tell the debugger to run, until the next breakpoint or end of script. 49 | """ 50 | self.session_handler.run() 51 | 52 | def run_to_cursor(self): 53 | """Run to the current VIM cursor position. 54 | """ 55 | self.session_handler.dispatch_event("run_to_cursor") 56 | 57 | def step_over(self): 58 | """Step over to the next statement. 59 | """ 60 | self.session_handler.dispatch_event("step_over") 61 | 62 | def step_into(self): 63 | """Step into a statement on the current line. 64 | """ 65 | self.session_handler.dispatch_event("step_into") 66 | 67 | def step_out(self): 68 | """Step out of the current statement. 69 | """ 70 | self.session_handler.dispatch_event("step_out") 71 | 72 | def handle_return_keypress(self): 73 | """React to a keypress event. 74 | """ 75 | return self.event_dispatcher.by_position(self.session_handler) 76 | 77 | def handle_delete_line_keypress(self): 78 | """React to a
keypress event. 79 | """ 80 | return self.event_dispatcher.delete_line(self.session_handler) 81 | 82 | def handle_delete_visual_keypress(self): 83 | """React to a vunmap keypress event. 84 | """ 85 | return self.event_dispatcher.event_visual( 86 | self.session_handler, 87 | 'delete' 88 | ) 89 | 90 | def handle_double_click(self): 91 | """React to a mouse double click event. 92 | """ 93 | return self.event_dispatcher.by_position(self.session_handler) 94 | 95 | def handle_visual_eval(self): 96 | """React to eval during visual selection. 97 | """ 98 | return self.event_dispatcher.visual_eval(self.session_handler) 99 | 100 | def handle_eval(self, bang, args): 101 | """Evaluate a code snippet specified by args. 102 | """ 103 | return self.session_handler.dispatch_event("set_eval_expression", 104 | len(bang) > 0, args) 105 | 106 | def handle_trace(self, args=None): 107 | """Trace a code snippet specified by args. 108 | """ 109 | return self.session_handler.dispatch_event("trace", args) 110 | 111 | def eval_under_cursor(self): 112 | """Evaluate the property under the cursor. 113 | """ 114 | return self.event_dispatcher.eval_under_cursor(self.session_handler) 115 | 116 | def mark_window_as_closed(self, window): 117 | self.session_handler.ui().mark_window_as_closed(window) 118 | 119 | def toggle_window(self, name): 120 | self.session_handler.ui().toggle_window(name) 121 | 122 | def toggle_breakpoint_window(self): 123 | self.session_handler.ui().toggle_window("DebuggerBreakpoints") 124 | 125 | def get_last_error(self): 126 | return self.session_handler.ui().get_last_error() 127 | 128 | def set_breakpoint(self, args=None): 129 | """Set a breakpoint, specified by args. 130 | """ 131 | self.session_handler.dispatch_event("set_breakpoint", args) 132 | 133 | def cycle_breakpoint(self, args=None): 134 | """Cycle a breakpoint between Enabled, Disabled and Removed 135 | """ 136 | self.session_handler.dispatch_event("cycle_breakpoint", args) 137 | 138 | def toggle_breakpoint(self, args=None): 139 | """Toggle a breakpoint, specified by args 140 | """ 141 | self.session_handler.dispatch_event("toggle_breakpoint", args) 142 | 143 | def enable_breakpoint(self, args=None): 144 | """Enable a breakpoint, specified by args 145 | """ 146 | self.session_handler.dispatch_event("enable_breakpoint", args) 147 | 148 | def disable_breakpoint(self, args=None): 149 | """Disable a breakpoint, specified by args 150 | """ 151 | self.session_handler.dispatch_event("disable_breakpoint", args) 152 | 153 | def breakpoint_status(self, args=None): 154 | """Either gets the status of a breakpoint or changes it 155 | """ 156 | self.session_handler.dispatch_event("breakpoint_status", args) 157 | 158 | def remove_breakpoint(self, args=None): 159 | """Remove one or more breakpoints, specified by args. 160 | """ 161 | self.session_handler.dispatch_event("remove_breakpoint", args) 162 | 163 | def jump_breakpoint(self, args=None): 164 | """Jump to a breakpoint in the source window from the breakpoint window 165 | """ 166 | self.session_handler.dispatch_event("breakpoint_jump", args) 167 | 168 | def get_context(self): 169 | """Get all the variables in the default context 170 | """ 171 | self.session_handler.dispatch_event("get_context", 0) 172 | 173 | def detach(self): 174 | """Detach the debugger, so the script runs to the end. 175 | """ 176 | self.session_handler.dispatch_event("detach") 177 | 178 | def close(self): 179 | """Close the connection, or the UI if already closed. 180 | """ 181 | self.session_handler.stop() 182 | 183 | def quit(self): 184 | """Close the connection, or the UI if already closed. On Exit 185 | """ 186 | self.session_handler.stop(quiet=True) 187 | -------------------------------------------------------------------------------- /python3/vdebug/error.py: -------------------------------------------------------------------------------- 1 | """Exception classes for Vdebug.""" 2 | 3 | 4 | class BreakpointError(Exception): 5 | pass 6 | 7 | 8 | class UserInterrupt(Exception): 9 | """Raised when a user interrupts connection wait.""" 10 | pass 11 | 12 | 13 | class FilePathError(Exception): 14 | pass 15 | 16 | 17 | class EventError(Exception): 18 | pass 19 | 20 | 21 | class LogError(Exception): 22 | pass 23 | 24 | 25 | class ModifiedBufferError(Exception): 26 | pass 27 | 28 | 29 | class NoConnectionError(Exception): 30 | pass 31 | -------------------------------------------------------------------------------- /python3/vdebug/listener.py: -------------------------------------------------------------------------------- 1 | import vim 2 | 3 | from . import connection 4 | from . import opts 5 | from . import util 6 | 7 | 8 | class Listener: 9 | 10 | @classmethod 11 | def create(cls): 12 | if opts.Options.get('background_listener', int): 13 | return BackgroundListener() 14 | return ForegroundListener() 15 | 16 | 17 | class ForegroundListener: 18 | 19 | def __init__(self): 20 | self.__server = connection.SocketCreator(util.InputStream()) 21 | 22 | def start(self): 23 | self.__server.start(opts.Options.get('server'), 24 | opts.Options.get('port', int), 25 | opts.Options.get('proxy_host'), 26 | opts.Options.get('proxy_port', int), 27 | opts.Options.get('ide_key'), 28 | opts.Options.get('timeout', int)) 29 | 30 | def stop(self): 31 | self.__server.clear() 32 | 33 | def is_listening(self): 34 | return False 35 | 36 | def is_ready(self): 37 | return self.__server.has_socket() 38 | 39 | def status(self): 40 | return "inactive" 41 | 42 | def create_connection(self): 43 | handler = connection.ConnectionHandler(*self.__server.socket()) 44 | self.stop() 45 | return handler 46 | 47 | 48 | class BackgroundListener: 49 | 50 | def __init__(self): 51 | self.__server = connection.SocketServer() 52 | 53 | def start(self): 54 | if opts.Options.get("auto_start", int): 55 | vim.command('autocmd Vdebug CursorHold,CursorHoldI,CursorMoved,CursorMovedI,FocusGained,FocusLost * python3 debugger.start_if_ready()') 56 | self.__server.start(opts.Options.get('server'), 57 | opts.Options.get('port', int), 58 | opts.Options.get('proxy_host'), 59 | opts.Options.get('proxy_port', int), 60 | opts.Options.get('ide_key')) 61 | 62 | def stop(self): 63 | if opts.Options.get("auto_start", bool): 64 | vim.command('augroup Vdebug') 65 | vim.command('autocmd!') 66 | vim.command('augroup END') 67 | self.__server.stop() 68 | 69 | def status(self): 70 | if not self.__server: 71 | return "inactive" 72 | if self.is_ready(): 73 | return "ready" 74 | if self.__server.is_alive(): 75 | return "listening" 76 | return "inactive" 77 | 78 | def is_ready(self): 79 | return self.__server.has_socket() 80 | 81 | def is_listening(self): 82 | return not self.is_ready() and self.__server.is_alive() 83 | 84 | def create_connection(self): 85 | handler = connection.ConnectionHandler(*self.__server.socket()) 86 | self.stop() 87 | return handler 88 | -------------------------------------------------------------------------------- /python3/vdebug/log.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | from . import error 6 | 7 | 8 | class Logger: 9 | """ Abstract class for all logger implementations. 10 | 11 | Concrete classes will log messages using various methods, 12 | e.g. write to a file. 13 | """ 14 | 15 | (ERROR, INFO, DEBUG) = (0, 1, 2) 16 | TYPES = ("ERROR", "Info", "Debug") 17 | debug_level = ERROR 18 | 19 | def __init__(self, debug_level): 20 | self.debug_level = int(debug_level) 21 | 22 | def log(self, string, level): 23 | """ Log a message """ 24 | if level > self.debug_level: 25 | return 26 | self._actual_log(string, level) 27 | 28 | def _actual_log(self, string, level): 29 | """ Actually perform the logging (to be implemented in subclasses) """ 30 | pass 31 | 32 | def shutdown(self): 33 | """ Action to perform when closing the logger """ 34 | pass 35 | 36 | @staticmethod 37 | def time(): 38 | """ Get a nicely formatted time string """ 39 | return time.strftime("%a %d %Y %H:%M:%S", time.localtime()) 40 | 41 | def format(self, string, level): 42 | """ Format the error message in a standard way """ 43 | display_level = self.TYPES[level] 44 | return "- [%s] {%s} %s" % (display_level, self.time(), string) 45 | 46 | 47 | class WindowLogger(Logger): 48 | 49 | """ Log messages to a window. 50 | 51 | The window object is passed in on construction, but 52 | only created if a message is written. 53 | """ 54 | 55 | def __init__(self, debug_level, window): 56 | self.window = window 57 | super(WindowLogger, self).__init__(debug_level) 58 | 59 | def shutdown(self): 60 | if self.window is not None: 61 | self.window.is_open = False 62 | 63 | def _actual_log(self, string, level): 64 | if not self.window.is_open: 65 | self.window.create("rightbelow 6new") 66 | self.window.write(self.format(string, level)+"\n") 67 | 68 | 69 | class FileLogger(Logger): 70 | 71 | """ Log messages to a window. 72 | 73 | The window object is passed in on construction, but 74 | only created if a message is written. 75 | """ 76 | 77 | def __init__(self, debug_level, filename): 78 | self.filename = os.path.expanduser(filename) 79 | self.f = None 80 | super(FileLogger, self).__init__(debug_level) 81 | 82 | def __open(self): 83 | try: 84 | self.f = open(self.filename, 'w', encoding='utf-8') 85 | except IOError as e: 86 | raise error.LogError("Invalid file name '%s' for log file: %s" 87 | % (self.filename, e)) 88 | except: 89 | raise error.LogError("Error using file '%s' as a log file: %s" 90 | % (self.filename, sys.exc_info()[0])) 91 | 92 | def shutdown(self): 93 | if self.f is not None: 94 | self.f.close() 95 | 96 | def _actual_log(self, string, level): 97 | if self.f is None: 98 | self.__open() 99 | self.f.write(self.format(string, level)+"\n") 100 | self.f.flush() 101 | 102 | 103 | class Log: 104 | 105 | loggers = {} 106 | 107 | 108 | 109 | def __init__(self, string, level=Logger.INFO): 110 | Log.log(string, level) 111 | 112 | @classmethod 113 | def log(cls, string, level=Logger.INFO): 114 | for logger in cls.loggers.values(): 115 | logger.log(string, level) 116 | 117 | @classmethod 118 | def set_logger(cls, logger): 119 | k = logger.__class__.__name__ 120 | if k in cls.loggers: 121 | cls.loggers[k].shutdown() 122 | cls.loggers[k] = logger 123 | 124 | @classmethod 125 | def remove_logger(cls, type): 126 | if type in cls.loggers: 127 | cls.loggers[type].shutdown() 128 | return True 129 | print("Failed to find logger %s in list of loggers" % type) 130 | return False 131 | 132 | @classmethod 133 | def shutdown(cls): 134 | for logger in cls.loggers.values(): 135 | logger.shutdown() 136 | cls.loggers = {} 137 | -------------------------------------------------------------------------------- /python3/vdebug/opts.py: -------------------------------------------------------------------------------- 1 | 2 | class Options: 3 | instance = None 4 | 5 | def __init__(self, options): 6 | self.options = options 7 | 8 | @classmethod 9 | def set(cls, options): 10 | """Create an Options instance with the provided dictionary of 11 | options""" 12 | cls.instance = Options(options) 13 | 14 | @classmethod 15 | def inst(cls): 16 | """Get the Options instance. 17 | """ 18 | if cls.instance is None: 19 | raise OptionsError("No options have been set") 20 | return cls.instance 21 | 22 | @classmethod 23 | def get(cls, name, as_type=str): 24 | """Get an option by name. 25 | 26 | Raises an OptionsError if the option doesn't exist. 27 | """ 28 | inst = cls.inst() 29 | if name in inst.options: 30 | return as_type(inst.options[name]) 31 | raise OptionsError("No option with key '%s'" % name) 32 | 33 | @classmethod 34 | def get_for_print(cls, name): 35 | """Get an option by name and for human readable output. 36 | 37 | Raises an OptionsError if the option doesn't exist. 38 | """ 39 | option = cls.get(name) 40 | if not option: 41 | return "" 42 | return option 43 | 44 | @classmethod 45 | def overwrite(cls, name, value): 46 | inst = cls.inst() 47 | inst.options[name] = value 48 | 49 | @classmethod 50 | def isset(cls, name): 51 | """Checks whether the option exists and is set. 52 | 53 | By set, it means whether the option has length. All the option 54 | values are strings. 55 | """ 56 | inst = cls.inst() 57 | return name in inst.options and len(inst.options[name]) > 0 58 | 59 | 60 | class OptionsError(Exception): 61 | pass 62 | -------------------------------------------------------------------------------- /python3/vdebug/session.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | import vim 4 | 5 | from . import dbgp 6 | from . import error 7 | from . import event 8 | from . import listener 9 | from . import log 10 | from . import opts 11 | from . import util 12 | 13 | 14 | class SessionHandler: 15 | 16 | def __init__(self, ui, breakpoints): 17 | self.__ui = ui 18 | self.__breakpoints = breakpoints 19 | self.__ex_handler = util.ExceptionHandler(self) 20 | self.__session = None 21 | self.listener = None 22 | 23 | def dispatch_event(self, name, *args): 24 | event.Dispatcher(self).dispatch_event(name, *args) 25 | 26 | def ui(self): 27 | return self.__ui 28 | 29 | def breakpoints(self): 30 | return self.__breakpoints 31 | 32 | def session(self): 33 | return self.__session 34 | 35 | def listen(self): 36 | if self.listener and self.listener.is_listening(): 37 | print("Waiting for a connection: none found so far") 38 | elif self.listener and self.listener.is_ready(): 39 | print("Found connection, starting debugger") 40 | log.Log("Got connection, starting", log.Logger.DEBUG) 41 | self.__new_session() 42 | else: 43 | self.start_listener() 44 | 45 | def start_listener(self): 46 | self.listener = listener.Listener.create() 47 | print("Vdebug will wait for a connection") 48 | util.Environment.reload() 49 | if self.is_open(): 50 | self.ui().set_status("listening") 51 | self.listener.start() 52 | self.start_if_ready() 53 | 54 | def stop_listening(self): 55 | if self.listener: 56 | self.listener.stop() 57 | self.ui().say("Vdebug stopped waiting for a connection") 58 | 59 | if self.__session: 60 | self.__session.close_connection() 61 | 62 | def run(self): 63 | if self.is_connected(): 64 | self.dispatch_event("run") 65 | else: 66 | self.listen() 67 | 68 | def stop(self, quiet=False): 69 | if self.is_connected(): 70 | self.__session.close_connection() 71 | elif self.is_listening(): 72 | self.stop_listening() 73 | elif self.is_open(): 74 | self.__ui.close() 75 | else: 76 | if False is quiet: 77 | self.__ui.say("Vdebug is not running") 78 | 79 | def close(self): 80 | self.stop_listening() 81 | if self.is_connected(): 82 | self.__session.close_connection() 83 | if self.is_open(): 84 | self.__ui.close() 85 | 86 | def is_connected(self): 87 | return self.__session and self.__session.is_connected() 88 | 89 | def is_listening(self): 90 | return self.listener and self.listener.is_listening() 91 | 92 | def is_open(self): 93 | return self.__ui.is_open 94 | 95 | def status(self): 96 | if self.is_connected(): 97 | return "running" 98 | return self.listener.status() 99 | 100 | def status_for_statusline(self): 101 | return "vdebug(%s)" % self.status() 102 | 103 | def start_if_ready(self): 104 | try: 105 | if self.listener.is_ready(): 106 | print("Found connection, starting debugger") 107 | log.Log("Got connection, starting", log.Logger.DEBUG) 108 | self.__new_session() 109 | return True 110 | return False 111 | except Exception as e: 112 | print("Error starting Vdebug: %s" % 113 | self.__ex_handler.exception_to_string(e)) 114 | 115 | def __new_session(self): 116 | log.Log("create session", log.Logger.DEBUG) 117 | self.__session = Session(self.__ui, self.__breakpoints, 118 | util.Keymapper()) 119 | 120 | log.Log("start session", log.Logger.DEBUG) 121 | status = self.__session.start(self.listener.create_connection()) 122 | log.Log("refresh event", log.Logger.DEBUG) 123 | self.dispatch_event("refresh", status) 124 | 125 | 126 | class Session: 127 | 128 | def __init__(self, ui, breakpoints, keymapper): 129 | self.__ui = ui 130 | self.__breakpoints = breakpoints 131 | self.__keymapper = keymapper 132 | self.__api = None 133 | self.cur_file = None 134 | self.cur_lineno = None 135 | self.context_names = None 136 | 137 | def api(self): 138 | return self.__api 139 | 140 | def keymapper(self): 141 | return self.__keymapper 142 | 143 | def is_connected(self): 144 | return self.__api is not None 145 | 146 | def is_open(self): 147 | return self.__ui.is_open 148 | 149 | def ui(self): 150 | return self.__ui 151 | 152 | def close(self): 153 | """ Close both the connection and vdebug.ui. 154 | """ 155 | self.close_connection() 156 | self.__ui.close() 157 | self.__keymapper.unmap() 158 | 159 | def close_connection(self, stop=True): 160 | """ Close the connection to the debugger. 161 | """ 162 | self.__ui.mark_as_stopped() 163 | try: 164 | if self.is_connected(): 165 | log.Log("Closing the connection") 166 | if stop: 167 | if opts.Options.get('on_close') == 'detach': 168 | try: 169 | self.__api.detach() 170 | except dbgp.CmdNotImplementedError: 171 | self.__ui.error('Detach is not supported by the ' 172 | 'debugger, stopping instead') 173 | opts.Options.overwrite('on_close', 'stop') 174 | self.__api.stop() 175 | else: 176 | self.__api.stop() 177 | self.__api.conn.close() 178 | self.__api = None 179 | self.__breakpoints.unlink_api() 180 | else: 181 | self.__api = None 182 | self.__breakpoints.unlink_api() 183 | except EOFError: 184 | self.__api = None 185 | self.__ui.say("Connection has been closed") 186 | except socket.error: 187 | self.__api = None 188 | self.__ui.say("Connection has been closed") 189 | 190 | def start(self, connection): 191 | util.Environment.reload() 192 | if self.__ui.is_modified(): 193 | raise error.ModifiedBufferError("Modified buffers must be saved " 194 | "before debugging") 195 | 196 | try: 197 | self.__api = dbgp.Api(connection) 198 | if not self.is_open(): 199 | self.__ui.open() 200 | self.__keymapper.map() 201 | 202 | self.__ui.set_listener_details(opts.Options.get('server'), 203 | opts.Options.get('port'), 204 | opts.Options.get('ide_key')) 205 | 206 | addr = self.__api.conn.address 207 | log.Log("Found connection from %s" % str(addr), log.Logger.INFO) 208 | self.__ui.set_conn_details(addr[0], addr[1]) 209 | 210 | self.__collect_context_names() 211 | self.__check_features() # only for debugging at the moment 212 | self.__set_default_features() # features we try by default 213 | self.__set_features() # user defined features 214 | self.__initialize_breakpoints() 215 | 216 | if opts.Options.get('break_on_open', int) == 1: 217 | log.Log('starting with step_into (break_on_open = 1)', log.Logger.DEBUG) 218 | status = self.__api.step_into() 219 | else: 220 | log.Log('starting with run (break_on_open = 0)', log.Logger.DEBUG) 221 | status = self.__api.run() 222 | return status 223 | except Exception: 224 | self.close() 225 | raise 226 | 227 | def detach(self): 228 | """Detach the debugger engine, and allow it to continue execution. 229 | """ 230 | if self.is_connected(): 231 | self.__ui.say("Detaching the debugger") 232 | self.__api.detach() 233 | 234 | self.close_connection(False) 235 | 236 | def __check_features(self): 237 | must_features = [ 238 | 'language_supports_threads', 239 | 'language_name', 240 | 'language_version', 241 | 'encoding', # has set 242 | 'protocol_version', 243 | 'supports_async', 244 | 'data_encoding', 245 | 'breakpoint_languages', 246 | 'breakpoint_types', 247 | 'resolved_breakpoints', 248 | 'multiple_sessions', # has set 249 | 'max_children', # has set 250 | 'max_data', # has set 251 | 'max_depth', # has set 252 | 'extended_properties', # has set 253 | ] 254 | maybe_features = [ 255 | 'supported_encodings', 256 | 'supports_postmortem', 257 | 'show_hidden', # has set 258 | 'notify_ok', # has set 259 | ] 260 | for feature in must_features: 261 | try: 262 | feature_value = self.__api.feature_get(feature) 263 | log.Log( 264 | "Must Feature: %s = %s" % (feature, str(feature_value)), 265 | log.Logger.DEBUG 266 | ) 267 | except dbgp.DBGPError: 268 | error_str = "Failed to get feature %s" % feature 269 | log.Log(error_str, log.Logger.DEBUG) 270 | 271 | for feature in maybe_features: 272 | try: 273 | feature_value = self.__api.feature_get(feature) 274 | log.Log( 275 | "Maybe Feature: %s = %s" % (feature, str(feature_value)), 276 | log.Logger.DEBUG 277 | ) 278 | except dbgp.DBGPError: 279 | error_str = "Failed to get feature %s" % feature 280 | log.Log(error_str, log.Logger.DEBUG) 281 | 282 | def __set_default_features(self): 283 | features = { 284 | 'multiple_sessions': 0, # explicitly disable multiple sessions atm 285 | 'extended_properties': 1, 286 | } 287 | for name, value in features.items(): 288 | try: 289 | self.__api.feature_set(name, value) 290 | except dbgp.DBGPError as e: 291 | error_str = "Failed to set feature %s: %s" % (name, e.args[0]) 292 | log.Log(error_str, log.Logger.DEBUG) 293 | 294 | def __set_features(self): 295 | """Evaluate vim dictionary of features and pass to debugger. 296 | 297 | Errors are caught if the debugger doesn't like the feature name or 298 | value. This doesn't break the loop, so multiple features can be set 299 | even in the case of an error.""" 300 | features = vim.eval('g:vdebug_features') 301 | for name, value in features.items(): 302 | try: 303 | self.__api.feature_set(name, value) 304 | except dbgp.DBGPError as e: 305 | error_str = "Failed to set feature %s: %s" % (name, e.args[0]) 306 | self.__ui.error(error_str) 307 | 308 | def __initialize_breakpoints(self): 309 | self.__breakpoints.update_lines( 310 | self.__ui.get_breakpoint_sign_positions()) 311 | self.__breakpoints.link_api(self.__api) 312 | 313 | def __collect_context_names(self): 314 | cn_res = self.__api.context_names() 315 | self.context_names = cn_res.names() 316 | log.Log("Available context names: %s" % self.context_names, 317 | log.Logger.DEBUG) 318 | -------------------------------------------------------------------------------- /python3/vdebug/ui/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["interface", "vimui"] 2 | -------------------------------------------------------------------------------- /python3/vdebug/ui/interface.py: -------------------------------------------------------------------------------- 1 | class Ui: 2 | """Abstract for the UI, used by the debugger 3 | """ 4 | watchwin = None 5 | stackwin = None 6 | statuswin = None 7 | logwin = None 8 | sourcewin = None 9 | tracewin = None 10 | 11 | def __init__(self): 12 | self.is_open = False 13 | 14 | def __del__(self): 15 | self.close() 16 | 17 | def open(self): 18 | pass 19 | 20 | def say(self, string): 21 | pass 22 | 23 | def close(self): 24 | pass 25 | 26 | def log(self): 27 | pass 28 | 29 | 30 | class Window: 31 | """Abstract for UI windows 32 | """ 33 | name = "WINDOW" 34 | is_open = False 35 | 36 | def __del__(self): 37 | self.destroy() 38 | 39 | def on_create(self): 40 | """ Callback for after the window is created """ 41 | pass 42 | 43 | def on_destroy(self): 44 | """ Callback for after the window is destroyed """ 45 | pass 46 | 47 | def create(self): 48 | """ Create the window """ 49 | pass 50 | 51 | def write(self, msg): 52 | """ Write string in the window """ 53 | pass 54 | 55 | def insert(self, msg, position=None): 56 | """ Insert a string somewhere in the window """ 57 | pass 58 | 59 | def destroy(self): 60 | """ Close window """ 61 | pass 62 | 63 | def clean(self): 64 | """ clean all data in buffer """ 65 | pass 66 | -------------------------------------------------------------------------------- /python3/vdebug/util.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import socket 4 | import sys 5 | import time 6 | import traceback 7 | import urllib.parse as urllib 8 | 9 | import vim 10 | 11 | from . import dbgp 12 | from . import error 13 | from . import log 14 | from . import opts 15 | 16 | 17 | class ExceptionHandler: 18 | 19 | """ Exception handlers """ 20 | 21 | def __init__(self, session_handler): 22 | self._session_handler = session_handler 23 | self.readable_errors = (error.EventError, error.BreakpointError, 24 | error.LogError, error.NoConnectionError, 25 | error.ModifiedBufferError) 26 | 27 | def exception_to_string(self, e): 28 | if isinstance(e, self.readable_errors): 29 | return str(e) 30 | return str(sys.exc_info()[0]) 31 | 32 | def handle_timeout(self): 33 | """Handle a timeout, which is pretty normal. 34 | """ 35 | self._session_handler.stop() 36 | self._session_handler.ui().say("No connection was made") 37 | 38 | def handle_interrupt(self): 39 | """Handle a user interrupt, which is pretty normal. 40 | """ 41 | self._session_handler.stop() 42 | self._session_handler.ui().say("Connection cancelled") 43 | 44 | def handle_socket_end(self): 45 | """Handle a socket closing, which is pretty normal. 46 | """ 47 | self._session_handler.ui().say( 48 | "Connection to the debugger has been closed" 49 | ) 50 | self._session_handler.stop() 51 | 52 | def handle_vim_error(self, e): 53 | """Handle a VIM error. 54 | 55 | This should NOT occur under normal circumstances. 56 | """ 57 | self._session_handler.ui().error("A Vim error occured: %s\n%s" % 58 | (e, traceback.format_exc())) 59 | 60 | def handle_readable_error(self, e): 61 | """Simply print an error, since it is human readable enough. 62 | """ 63 | self._session_handler.ui().error(str(e)) 64 | 65 | def handle_dbgp_error(self, e): 66 | """Simply print an error, since it is human readable enough. 67 | """ 68 | self._session_handler.ui().error(str(e.args[0])) 69 | 70 | def handle_general_exception(self): 71 | """Handle an unknown error of any kind. 72 | """ 73 | self._session_handler.ui().error("An error occured: %s\n%s" % ( 74 | sys.exc_info()[0], traceback.format_exc(5))) 75 | 76 | def handle(self, e): 77 | """Switch on the exception type to work out how to handle it. 78 | """ 79 | if isinstance(e, dbgp.TimeoutError): 80 | self.handle_timeout() 81 | elif isinstance(e, error.UserInterrupt): 82 | try: 83 | self.handle_interrupt() 84 | except Exception as e: 85 | pass 86 | elif isinstance(e, self.readable_errors): 87 | self.handle_readable_error(e) 88 | elif isinstance(e, dbgp.DBGPError): 89 | self.handle_dbgp_error(e) 90 | elif isinstance(e, (EOFError, socket.error)): 91 | self.handle_socket_end() 92 | elif isinstance(e, KeyboardInterrupt): 93 | print("Keyboard interrupt - debugging session cancelled") 94 | try: 95 | self._session_handler.stop() 96 | except Exception as e: 97 | pass 98 | else: 99 | self.handle_general_exception() 100 | 101 | 102 | class Keymapper: 103 | 104 | """Map and unmap key commands for the Vim user interface. 105 | """ 106 | 107 | exclude = ["run", "close", "set_breakpoint", "enable_breakpoint", "disable_breakpoint", 108 | "toggle_breakpoint", "eval_visual"] 109 | 110 | def __init__(self): 111 | self.is_mapped = False 112 | self._reload_keys() 113 | self.existing = [] 114 | 115 | def run_key(self): 116 | return self.keymaps['run'] 117 | 118 | def close_key(self): 119 | return self.keymaps['close'] 120 | 121 | def map(self): 122 | log.Log("keymapper: map", log.Logger.DEBUG) 123 | if self.is_mapped: 124 | return 125 | self._store_old_map() 126 | self._reload_keys() 127 | for func in self.keymaps: 128 | if func not in self.exclude: 129 | key = self.keymaps[func] 130 | map_cmd = "noremap %s%s :python3 debugger.%s()" % ( 131 | self.leader, key, func) 132 | vim.command(map_cmd) 133 | self.is_mapped = True 134 | 135 | def reload(self): 136 | log.Log("keymapper: reload", log.Logger.DEBUG) 137 | self.is_mapped = False 138 | self.map() 139 | 140 | def _reload_keys(self): 141 | log.Log("keymapper: reload_keys", log.Logger.DEBUG) 142 | self.keymaps = vim.eval("g:vdebug_keymap") 143 | self.leader = vim.eval("g:vdebug_leader_key") 144 | 145 | def _store_old_map(self): 146 | log.Log("keymapper: store_old_map", log.Logger.DEBUG) 147 | vim.command('let tempfile=tempname()') 148 | tempfile = vim.eval("tempfile") 149 | vim.command('mkexrc! %s' % (tempfile)) 150 | regex = re.compile(r'^([nvxsoilc]|)(nore)?map!?') 151 | split_regex = re.compile(r'\s+') 152 | keys = {v for k, v in self.keymaps.items() if k not in self.exclude} 153 | special = {"", "", "", "