├── .flake8 ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── Context.sublime-menu ├── Default (OSX).sublime-keymap ├── Default.sublime-commands ├── Default.sublime-keymap ├── LICENSE ├── Main.sublime-menu ├── README.md ├── Xdebug.sublime-settings ├── Xdebug.tmLanguage ├── icons ├── breakpoint_current.png ├── breakpoint_disabled.png ├── breakpoint_enabled.png └── current_line.png ├── main.py ├── messages.json ├── messages └── 1.1.0.txt ├── tests ├── integration │ ├── test_breakpoint_exception.py │ ├── test_breakpoint_start.py │ ├── test_breakpoint_step.py │ └── test_debug_layout.py ├── php-server ├── php-xdebug │ └── Dockerfile └── server │ ├── logs │ └── .gitkeep │ └── public │ ├── breakpoint_exception.php │ ├── breakpoint_exception_parse_error.php │ ├── breakpoint_start.php │ ├── breakpoint_step.php │ ├── index.php │ └── version.php ├── unittesting.json └── xdebug ├── __init__.py ├── config.py ├── dbgp.py ├── elementtree ├── ElementInclude.py ├── ElementPath.py ├── ElementTree.py ├── HTMLTreeBuilder.py ├── SgmlopXMLTreeBuilder.py ├── SimpleXMLTreeBuilder.py ├── SimpleXMLWriter.py ├── TidyHTMLTreeBuilder.py ├── TidyTools.py ├── XMLTreeBuilder.py └── __init__.py ├── helper ├── __init__.py ├── helper.py ├── helper_26.py ├── helper_27.py └── ordereddict.py ├── load.py ├── log.py ├── protocol.py ├── session.py ├── settings.py ├── unittesting.py ├── util.py └── view.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | # E501: line too long (line-length > max-line-length characters) 4 | E501, 5 | # E722: do not use bare except 6 | E722, 7 | # F405: name may be undefined, or defined from star imports: module 8 | F405 9 | exclude = 10 | # No need to traverse our git directory 11 | .git, 12 | # External libraries/classes 13 | xdebug/elementtree, 14 | xdebug/helper/ordereddict.py 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | Provide here a description of the bug or feature request. 9 | More details the better, such as reproduction steps for a bug if possible. 10 | Please also try to fill in the following details when reporting a bug: 11 | 12 | ## Environment 13 | 14 | ### Sublime Text 15 | __Operating system:__ 16 | 17 | __Installed version/build:__ 18 | 19 | __Python version:__ 20 | 21 | 22 | ### Server 23 | __Operating system:__ 24 | 28 | __PHP/Xdebug version:__ 29 | 32 | 33 | ## Configuration 34 | 35 | __php.ini/xdebug.ini__ 36 | 37 | ```ini 38 | [xdebug] 39 | # ... 40 | ``` 41 | 42 | __Packages/User/Xdebug.sublime-settings__ 43 | 44 | ```json 45 | { 46 | 47 | } 48 | ``` 49 | __*.sublime-project__ 50 | 51 | ```json 52 | { 53 | 54 | } 55 | ``` 56 | 57 | ## Logs 58 | 59 | __Console output:__ 60 | 64 | ``` 65 | 66 | ``` 67 | __Packages/User/Xdebug.log:__ 68 | 69 | ``` 70 | 71 | ``` 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.tmLanguage.cache 3 | /tests/server/logs/* 4 | !/tests/server/logs/.gitkeep 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - PACKAGE="SublimeTextXdebug" 4 | - SUBLIME_TEXT_VERSION="3" 5 | - UNITTESTING_TAG="1.4.0" 6 | # Disable accessibility bridge to prevent following warning upon starting Sublime Text: 7 | # "Error retrieving accessibility bus address: org.freedesktop.DBus.Error.ServiceUnknown: The name org.a11y.Bus was not provided by any .service files" 8 | - NO_AT_BRIDGE=1 9 | language: python 10 | notifications: 11 | email: false 12 | stages: 13 | - coding style 14 | - integration tests 15 | jobs: 16 | exclude: 17 | - &python-27 18 | name: Python 2.7.16 19 | dist: xenial 20 | python: 2.7.16 21 | - &python-33 22 | name: Python 3.3.6 23 | dist: trusty 24 | python: 3.3.6 25 | - &python-38 26 | name: Python 3.8.0 27 | dist: xenial 28 | python: 3.8.0 29 | - &python-flake8 30 | stage: coding style 31 | env: 32 | - FLAKE8_VERSION="3.5.0" 33 | install: 34 | - pip install flake8==${FLAKE8_VERSION} 35 | - pip install flake8-quotes==2.1.1 36 | script: flake8 --verbose . 37 | - &sublime-text-unittesting 38 | stage: integration tests 39 | before_install: 40 | - curl -OL https://raw.githubusercontent.com/SublimeText/UnitTesting/${UNITTESTING_TAG}/sbin/travis.sh 41 | # Create log file in order for Xdebug to open remote debug file 42 | - export XDEBUG_REMOTE_LOG=${TRAVIS_BUILD_DIR}/tests/server/logs/xdebug_${XDEBUG_VERSION}_${PHP_VERSION}.log 43 | - touch $XDEBUG_REMOTE_LOG && chmod 666 $XDEBUG_REMOTE_LOG 44 | before_script: 45 | # Verify test server is running and using correct version before executing tests 46 | - | 47 | SERVER_VERSION=$(curl http://127.0.0.1:8090/version.php) 48 | if [ "${SERVER_VERSION}" != "${PHP_VERSION}_${XDEBUG_VERSION}" ] 49 | then 50 | >&2 echo "Test server is not running or using wrong version (${SERVER_VERSION})." 51 | exit 1 52 | else 53 | echo "Test server is running and using correct version (${SERVER_VERSION})." 54 | fi 55 | # Install Sublime Text and testing suite 56 | - sh travis.sh bootstrap 57 | script: 58 | - sh travis.sh run_tests 59 | - &sublime-text-unittesting-linux 60 | <<: *sublime-text-unittesting 61 | os: linux 62 | services: 63 | - docker 64 | - xvfb 65 | install: 66 | # Start test server with PHP/Xdebug available at http://127.0.0.1:8090/ 67 | - tests/php-server start 68 | # Wait couple seconds for Docker container to spin up before continuing 69 | - sleep 5 70 | after_failure: 71 | - docker ps -a 72 | - docker logs php-server --tail 1000 || echo "No logs available for test server." 73 | - cat $XDEBUG_REMOTE_LOG || echo "No logs available for Xdebug extension." 74 | - &sublime-text-unittesting-osx 75 | <<: *sublime-text-unittesting 76 | os: osx 77 | osx_image: xcode9.4 78 | language: generic 79 | addons: 80 | homebrew: 81 | update: true 82 | install: 83 | # Using Homebrew because Docker is not supported on macOS 84 | - brew install httpd php@${PHP_VERSION} 85 | # Install Xdebug extension 86 | - sudo $(brew --prefix php@${PHP_VERSION})/bin/pecl channel-update pecl.php.net 87 | - sudo $(brew --prefix php@${PHP_VERSION})/bin/pecl install xdebug-${XDEBUG_VERSION} 88 | # Set configuration for Xdebug extension 89 | - | 90 | XDEBUG_INI_FILE=/usr/local/etc/php/${PHP_VERSION}/conf.d/ext-xdebug.ini 91 | echo "xdebug.remote_enable=1" >> $XDEBUG_INI_FILE 92 | echo "xdebug.remote_autostart=1" >> $XDEBUG_INI_FILE 93 | echo "xdebug.remote_host=127.0.0.1" >> $XDEBUG_INI_FILE 94 | echo "xdebug.remote_port=9000" >> $XDEBUG_INI_FILE 95 | echo "xdebug.remote_handler=dbgp" >> $XDEBUG_INI_FILE 96 | echo "xdebug.remote_log=${XDEBUG_REMOTE_LOG}" >> $XDEBUG_INI_FILE 97 | # Configure Apache web server 98 | - | 99 | APACHE_CONFIGURATION_FILE=/usr/local/etc/httpd/httpd.conf 100 | NEWLINE=$'\n' 101 | # Change document root and host settings 102 | sed -i "" "s|/usr/local/var/www|${TRAVIS_BUILD_DIR}/tests/server/public|g" $APACHE_CONFIGURATION_FILE 103 | sed -i "" "s|Listen 8080|Listen 8090|g" $APACHE_CONFIGURATION_FILE 104 | # Enable rewrite and PHP modules 105 | sed -i "" -e "s|#LoadModule rewrite_module|LoadModule php7_module $(brew --prefix php@${PHP_VERSION})/lib/httpd/modules/libphp7.so\\${NEWLINE}LoadModule rewrite_module|g" $APACHE_CONFIGURATION_FILE 106 | sed -i "" "s|AllowOverride None|AllowOverride All|g" $APACHE_CONFIGURATION_FILE 107 | sed -i "" "s|DirectoryIndex index.html|DirectoryIndex index.html index.php|g" $APACHE_CONFIGURATION_FILE 108 | # Ensure PHP files are executed as code 109 | echo '' >> $APACHE_CONFIGURATION_FILE 110 | echo ' AddType application/x-httpd-php .php' >> $APACHE_CONFIGURATION_FILE 111 | echo '' >> $APACHE_CONFIGURATION_FILE 112 | echo '' >> $APACHE_CONFIGURATION_FILE 113 | echo ' SetHandler application/x-httpd-php' >> $APACHE_CONFIGURATION_FILE 114 | echo '' >> $APACHE_CONFIGURATION_FILE 115 | # Start test server with PHP/Xdebug available at http://127.0.0.1:8090/ 116 | - sudo apachectl -k start 117 | after_failure: 118 | - cat /usr/local/etc/httpd/httpd.conf || echo "Missing configuration for Apache installation." 119 | - $(brew --prefix php@${PHP_VERSION})/bin/php --ini || echo "Unable to show configuration file names for PHP." 120 | - $(brew --prefix php@${PHP_VERSION})/bin/php -m | grep -iq "xdebug" && echo "Xdebug extension is enabled." || echo "Xdebug extension is not enabled." 121 | - tail -n 1000 /usr/local/var/log/httpd/* || echo "No logs available for test server." 122 | - cat $XDEBUG_REMOTE_LOG || echo "No logs available for Xdebug extension." 123 | include: 124 | # 2.6.5, Sublime Text 2.0.2 Build 2221 (Windows) 125 | # 2.6.6, Sublime Text 2.0.2 Build 2221 (Linux) 126 | # 2.6.9, Sublime Text 2.0.2 Build 2221 (macOS Sierra) 127 | # 2.7.10, Sublime Text 2.0.2 Build 2221 (macOS High Sierra) 128 | # 2.7.16, Sublime Text 2.0.2 Build 2221 (macOS Mojave) 129 | - <<: *python-27 130 | <<: *python-flake8 131 | # 3.3.6, Sublime Text 3.0 Build 3143/3156 132 | # 3.3.6, Sublime Text 3.2.2 Build 3210/3211 133 | - <<: *python-33 134 | <<: *python-flake8 135 | - <<: *sublime-text-unittesting-linux 136 | name: Linux (Sublime Text 3, Xdebug 2.5.0, PHP 7.0) 137 | env: 138 | - PHP_VERSION="7.0" 139 | - XDEBUG_VERSION="2.5.0" 140 | - <<: *sublime-text-unittesting-linux 141 | name: Linux (Sublime Text 3, Xdebug 2.5.5, PHP 7.1) 142 | env: 143 | - PHP_VERSION="7.1" 144 | - XDEBUG_VERSION="2.5.5" 145 | - <<: *sublime-text-unittesting-linux 146 | name: Linux (Sublime Text 3, Xdebug 2.6.1, PHP 7.2) 147 | env: 148 | - PHP_VERSION="7.2" 149 | - XDEBUG_VERSION="2.6.1" 150 | - <<: *sublime-text-unittesting-linux 151 | name: Linux (Sublime Text 3, Xdebug 2.7.2, PHP 7.3) 152 | env: 153 | - PHP_VERSION="7.3" 154 | - XDEBUG_VERSION="2.7.2" 155 | - <<: *sublime-text-unittesting-linux 156 | name: Linux (Sublime Text 3, Xdebug 2.8.0, PHP 7.4) 157 | env: 158 | - PHP_VERSION="7.4" 159 | - XDEBUG_VERSION="2.8.0" 160 | # Keep macOS jobs to a minimum as Homebrew setup significantly increases total testing time 161 | - <<: *sublime-text-unittesting-osx 162 | name: macOS (Sublime Text 3, Xdebug 2.5.5, PHP 7.1) 163 | env: 164 | - PHP_VERSION="7.1" 165 | - XDEBUG_VERSION="2.5.5" 166 | - <<: *sublime-text-unittesting-osx 167 | name: macOS (Sublime Text 3, Xdebug 2.8.0, PHP 7.3) 168 | env: 169 | - PHP_VERSION="7.3" 170 | - XDEBUG_VERSION="2.8.0" 171 | # 3.8.0, Sublime Text 4 Build 4050/4057 172 | - <<: *python-38 173 | <<: *python-flake8 174 | env: 175 | - FLAKE8_VERSION="3.7.9" 176 | -------------------------------------------------------------------------------- /Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Xdebug", 4 | "children": 5 | [ 6 | { 7 | "caption": "Add/Remove Breakpoint", 8 | "command": "xdebug_breakpoint" 9 | }, 10 | { 11 | "caption": "Set Conditional Breakpoint", 12 | "command": "xdebug_conditional_breakpoint" 13 | }, 14 | { 15 | "caption": "Clear Breakpoints", 16 | "command": "xdebug_clear_breakpoints" 17 | }, 18 | { 19 | "caption": "Clear All Breakpoints", 20 | "command": "xdebug_clear_all_breakpoints" 21 | }, 22 | { 23 | "caption": "-" 24 | }, 25 | { 26 | "caption": "Set Watch Expression", 27 | "command": "xdebug_watch" 28 | }, 29 | { 30 | "caption": "Edit Watch Expression", 31 | "command": "xdebug_watch", 32 | "args" : {"edit" : true} 33 | }, 34 | { 35 | "caption": "Remove Watch Expression", 36 | "command": "xdebug_watch", 37 | "args" : {"remove" : true} 38 | }, 39 | { 40 | "caption": "Clear Watch Expressions", 41 | "command": "xdebug_watch", 42 | "args" : {"clear" : true} 43 | }, 44 | { 45 | "caption": "-" 46 | }, 47 | { 48 | "caption": "Run", 49 | "command": "xdebug_continue", 50 | "args" : {"command" : "run"} 51 | }, 52 | { 53 | "caption": "Run To Line", 54 | "command": "xdebug_run_to_line" 55 | }, 56 | { 57 | "caption": "Step Over", 58 | "command": "xdebug_continue", 59 | "args" : {"command" : "step_over"} 60 | }, 61 | { 62 | "caption": "Step Into", 63 | "command": "xdebug_continue", 64 | "args" : {"command" : "step_into"} 65 | }, 66 | { 67 | "caption": "Step Out", 68 | "command": "xdebug_continue", 69 | "args" : {"command" : "step_out"} 70 | }, 71 | { 72 | "caption": "Stop", 73 | "command": "xdebug_continue", 74 | "args" : {"command" : "stop"} 75 | }, 76 | { 77 | "caption": "Detach", 78 | "command": "xdebug_continue", 79 | "args" : {"command" : "detach"} 80 | } 81 | ] 82 | } 83 | ] -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | {"keys": ["super+f8"], "command": "xdebug_breakpoint"}, 3 | {"keys": ["shift+f8"], "command": "xdebug_conditional_breakpoint"}, 4 | {"keys": ["super+shift+f5"], "command": "xdebug_continue", "args": {"command": "run"}}, 5 | {"keys": ["super+shift+f6"], "command": "xdebug_continue", "args": {"command": "step_over"}}, 6 | {"keys": ["super+shift+f7"], "command": "xdebug_continue", "args": {"command": "step_into"}}, 7 | {"keys": ["super+shift+f8"], "command": "xdebug_continue", "args": {"command": "step_out"}}, 8 | {"keys": ["super+shift+f9"], "command": "xdebug_session_start"}, 9 | {"keys": ["super+shift+f10"], "command": "xdebug_session_stop"}, 10 | {"keys": ["super+shift+f11"], "command": "xdebug_layout", "args": {"keymap" : true}} 11 | ] -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Xdebug: Start Debugging", 4 | "command": "xdebug_session_start" 5 | }, 6 | { 7 | "caption": "Xdebug: Start Debugging (Launch Browser)", 8 | "command": "xdebug_session_start", 9 | "args" : {"launch_browser" : true} 10 | }, 11 | { 12 | "caption": "Xdebug: Restart Session", 13 | "command": "xdebug_session_restart" 14 | }, 15 | { 16 | "caption": "Xdebug: Stop Debugging", 17 | "command": "xdebug_session_stop" 18 | }, 19 | { 20 | "caption": "Xdebug: Stop Debugging (Launch Browser)", 21 | "command": "xdebug_session_stop", 22 | "args" : {"launch_browser" : true} 23 | }, 24 | { 25 | "caption": "Xdebug: Stop Debugging (Close Windows)", 26 | "command": "xdebug_session_stop", 27 | "args" : {"close_windows" : true} 28 | }, 29 | { 30 | "caption": "Xdebug: Add/Remove Breakpoint", 31 | "command": "xdebug_breakpoint" 32 | }, 33 | { 34 | "caption": "Xdebug: Set Conditional Breakpoint", 35 | "command": "xdebug_conditional_breakpoint" 36 | }, 37 | { 38 | "caption": "Xdebug: Clear Breakpoints", 39 | "command": "xdebug_clear_breakpoints" 40 | }, 41 | { 42 | "caption": "Xdebug: Clear All Breakpoints", 43 | "command": "xdebug_clear_all_breakpoints" 44 | }, 45 | { 46 | "caption": "Xdebug: Set Watch Expression", 47 | "command": "xdebug_watch" 48 | }, 49 | { 50 | "caption": "Xdebug: Edit Watch Expression", 51 | "command": "xdebug_watch", 52 | "args" : {"edit" : true} 53 | }, 54 | { 55 | "caption": "Xdebug: Remove Watch Expression", 56 | "command": "xdebug_watch", 57 | "args" : {"remove" : true} 58 | }, 59 | { 60 | "caption": "Xdebug: Clear Watch Expressions", 61 | "command": "xdebug_watch", 62 | "args" : {"clear" : true} 63 | }, 64 | { 65 | "caption": "Xdebug: Session - Evaluate", 66 | "command": "xdebug_evaluate" 67 | }, 68 | { 69 | "caption": "Xdebug: Session - Execute", 70 | "command": "xdebug_user_execute" 71 | }, 72 | { 73 | "caption": "Xdebug: Session - Status", 74 | "command": "xdebug_status" 75 | }, 76 | { 77 | "caption": "Xdebug: Breakpoint - Run", 78 | "command": "xdebug_continue", 79 | "args" : {"command" : "run"} 80 | }, 81 | { 82 | "caption": "Xdebug: Breakpoint - Run To Line", 83 | "command": "xdebug_run_to_line" 84 | }, 85 | { 86 | "caption": "Xdebug: Breakpoint - Step Over", 87 | "command": "xdebug_continue", 88 | "args" : {"command" : "step_over"} 89 | }, 90 | { 91 | "caption": "Xdebug: Breakpoint - Step Into", 92 | "command": "xdebug_continue", 93 | "args" : {"command" : "step_into"} 94 | }, 95 | { 96 | "caption": "Xdebug: Breakpoint - Step Out", 97 | "command": "xdebug_continue", 98 | "args" : {"command" : "step_out"} 99 | }, 100 | { 101 | "caption": "Xdebug: Breakpoint - Stop", 102 | "command": "xdebug_continue", 103 | "args" : {"command" : "stop"} 104 | }, 105 | { 106 | "caption": "Xdebug: Breakpoint - Detach", 107 | "command": "xdebug_continue", 108 | "args" : {"command" : "detach"} 109 | }, 110 | { 111 | "caption": "Xdebug: Close Windows", 112 | "command": "xdebug_layout", 113 | "args" : {"close_windows" : true} 114 | }, 115 | { 116 | "caption": "Xdebug: Restore Layout", 117 | "command": "xdebug_layout", 118 | "args" : {"restore" : true} 119 | }, 120 | { 121 | "caption": "Preferences: Xdebug Settings – Default", 122 | "command": "xdebug_settings" 123 | }, 124 | { 125 | "caption": "Preferences: Xdebug Settings – User", 126 | "command": "open_file", 127 | "args" : {"file" : "${packages}/User/Xdebug.sublime-settings"} 128 | } 129 | ] -------------------------------------------------------------------------------- /Default.sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | {"keys": ["ctrl+f8"], "command": "xdebug_breakpoint"}, 3 | {"keys": ["shift+f8"], "command": "xdebug_conditional_breakpoint"}, 4 | {"keys": ["ctrl+shift+f5"], "command": "xdebug_continue", "args": {"command": "run"}}, 5 | {"keys": ["ctrl+shift+f6"], "command": "xdebug_continue", "args": {"command": "step_over"}}, 6 | {"keys": ["ctrl+shift+f7"], "command": "xdebug_continue", "args": {"command": "step_into"}}, 7 | {"keys": ["ctrl+shift+f8"], "command": "xdebug_continue", "args": {"command": "step_out"}}, 8 | {"keys": ["ctrl+shift+f9"], "command": "xdebug_session_start"}, 9 | {"keys": ["ctrl+shift+f10"], "command": "xdebug_session_stop"}, 10 | {"keys": ["ctrl+shift+f11"], "command": "xdebug_layout", "args": {"keymap" : true}} 11 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2018 Martijn Dijkhuizen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "tools", 4 | "children": 5 | [ 6 | { 7 | "id": "end" 8 | }, 9 | { 10 | "caption": "Xdebug", 11 | "children": 12 | [ 13 | { 14 | "caption": "Start Debugging", 15 | "command": "xdebug_session_start" 16 | }, 17 | { 18 | "caption": "Start Debugging (Launch Browser)", 19 | "command": "xdebug_session_start", 20 | "args" : {"launch_browser" : true} 21 | }, 22 | { 23 | "caption": "Restart Session", 24 | "command": "xdebug_session_restart" 25 | }, 26 | { 27 | "caption": "Stop Debugging", 28 | "command": "xdebug_session_stop" 29 | }, 30 | { 31 | "caption": "Stop Debugging (Launch Browser)", 32 | "command": "xdebug_session_stop", 33 | "args" : {"launch_browser" : true} 34 | }, 35 | { 36 | "caption": "Stop Debugging (Close Windows)", 37 | "command": "xdebug_session_stop", 38 | "args" : {"close_windows" : true} 39 | }, 40 | { 41 | "caption": "-" 42 | }, 43 | { 44 | "caption": "Add/Remove Breakpoint", 45 | "command": "xdebug_breakpoint" 46 | }, 47 | { 48 | "caption": "Set Conditional Breakpoint", 49 | "command": "xdebug_conditional_breakpoint" 50 | }, 51 | { 52 | "caption": "Clear Breakpoints", 53 | "command": "xdebug_clear_breakpoints" 54 | }, 55 | { 56 | "caption": "Clear All Breakpoints", 57 | "command": "xdebug_clear_all_breakpoints" 58 | }, 59 | { 60 | "caption": "-" 61 | }, 62 | { 63 | "caption": "Set Watch Expression", 64 | "command": "xdebug_watch" 65 | }, 66 | { 67 | "caption": "Edit Watch Expression", 68 | "command": "xdebug_watch", 69 | "args" : {"edit" : true} 70 | }, 71 | { 72 | "caption": "Remove Watch Expression", 73 | "command": "xdebug_watch", 74 | "args" : {"remove" : true} 75 | }, 76 | { 77 | "caption": "Clear Watch Expressions", 78 | "command": "xdebug_watch", 79 | "args" : {"clear" : true} 80 | }, 81 | { 82 | "caption": "-" 83 | }, 84 | { 85 | "caption": "Evaluate", 86 | "command": "xdebug_evaluate" 87 | }, 88 | { 89 | "caption": "Execute", 90 | "command": "xdebug_user_execute" 91 | }, 92 | { 93 | "caption": "Status", 94 | "command": "xdebug_status" 95 | }, 96 | { 97 | "caption": "-" 98 | }, 99 | { 100 | "caption": "Run", 101 | "command": "xdebug_continue", 102 | "args" : {"command" : "run"} 103 | }, 104 | { 105 | "caption": "Run To Line", 106 | "command": "xdebug_run_to_line" 107 | }, 108 | { 109 | "caption": "Step Over", 110 | "command": "xdebug_continue", 111 | "args" : {"command" : "step_over"} 112 | }, 113 | { 114 | "caption": "Step Into", 115 | "command": "xdebug_continue", 116 | "args" : {"command" : "step_into"} 117 | }, 118 | { 119 | "caption": "Step Out", 120 | "command": "xdebug_continue", 121 | "args" : {"command" : "step_out"} 122 | }, 123 | { 124 | "caption": "Stop", 125 | "command": "xdebug_continue", 126 | "args" : {"command" : "stop"} 127 | }, 128 | { 129 | "caption": "Detach", 130 | "command": "xdebug_continue", 131 | "args" : {"command" : "detach"} 132 | }, 133 | { 134 | "caption": "-" 135 | }, 136 | { 137 | "caption": "Close Windows", 138 | "command": "xdebug_layout", 139 | "args" : {"close_windows" : true} 140 | }, 141 | { 142 | "caption": "Restore Layout", 143 | "command": "xdebug_layout", 144 | "args" : {"restore" : true} 145 | }, 146 | { 147 | "caption": "Settings - Default", 148 | "command": "xdebug_settings" 149 | }, 150 | { 151 | "caption": "Settings - User", 152 | "command": "open_file", 153 | "args" : {"file" : "${packages}/User/Xdebug.sublime-settings"} 154 | } 155 | ] 156 | } 157 | ] 158 | }, 159 | { 160 | "caption": "Preferences", 161 | "mnemonic": "n", 162 | "id": "preferences", 163 | "children": 164 | [ 165 | { 166 | "caption": "Package Settings", 167 | "mnemonic": "P", 168 | "id": "package-settings", 169 | "children": 170 | [ 171 | { 172 | "caption": "Xdebug", 173 | "children": 174 | [ 175 | { 176 | "caption": "Settings – Default", 177 | "command": "xdebug_settings" 178 | }, 179 | { 180 | "caption": "Settings – User", 181 | "command": "open_file", 182 | "args": {"file": "${packages}/User/Xdebug.sublime-settings"} 183 | } 184 | ] 185 | } 186 | ] 187 | } 188 | ] 189 | } 190 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SublimeTextXdebug 2 | Xdebug debugger client integration for Sublime Text. 3 | 4 | ![SublimeTextXdebug](http://i.imgur.com/2FGYW3P.png) 5 | 6 | Based on the Xdebug protocol functionality in [SublimeXdebug](https://github.com/Kindari/SublimeXdebug) package by [Kindari](https://github.com/Kindari). 7 | 8 | [![Build Status][travis-badge]][travis-link] [![Package Control][package-control-badge]][package-control-link] [![MIT License][license-badge]][license-link] 9 | 10 | ## Overview 11 | * [Features](#features) 12 | * [Commands](#commands) 13 | * [Installation](#installation) 14 | * [Xdebug](#xdebug) 15 | * [Configuration](#configuration) 16 | * [Troubleshoot](#troubleshoot) 17 | * [License](#license) 18 | 19 | ## Features 20 | * Remote debugging by configuring path mapping 21 | * Navigate on breakpoint hit to relevant file on specific line, when found on local drive 22 | * Customizable debugging layout for displaying stack history and context variables with syntax 23 | * Overview of breakpoints in all files and disable/enable breakpoints with simple click 24 | * Evaluate code within the current execution context, by setting watch expressions 25 | * Inspect (nested) context variables 26 | * Works on both Sublime Text 2 __and__ 3 27 | 28 | ## Commands 29 | Here is a complete list of commands you can find Command Palette under the `Xdebug` namespace or in the menu under `Tools / Xdebug`: 30 | 31 | #### Start/Stop debugging session 32 | * Start Debugging - Ctrl+Shift+F9 or ⌘+Shift+F9 33 | * Start Debugging (Launch Browser) 34 | * Restart Session 35 | * Stop Debugging - Ctrl+Shift+F10 or ⌘+Shift+F10 36 | * Stop Debugging (Launch Browser) 37 | * Stop Debugging (Close Windows) 38 | 39 | *__Launch Browser__ menu option will only show if you have an url configured within [settings](#configuration).* 40 | 41 | #### Breakpoints 42 | * Add/Remove Breakpoint - Ctrl+F8 or ⌘+F8 43 | * Set Conditional Breakpoint - Shift+F8 44 | * Clear Breakpoints 45 | * Clear All Breakpoints 46 | 47 | #### Watch expressions 48 | * Set Watch Expression 49 | * Edit Watch Expression 50 | * Remove Watch Expression 51 | * Clear Watch Expressions 52 | 53 | #### Session commands 54 | * Evaluate 55 | * Execute 56 | * Status 57 | 58 | #### Continuation commands 59 | * Run - Ctrl+Shift+F5 or ⌘+Shift+F5 60 | * Run To Line 61 | * Step Over - Ctrl+Shift+F6 or ⌘+Shift+F6 62 | * Step Into - Ctrl+Shift+F7 or ⌘+Shift+F7 63 | * Step Out - Ctrl+Shift+F8 or ⌘+Shift+F8 64 | * Stop 65 | * Detach 66 | 67 | #### Other 68 | * Restore Layout / Close Windows - Ctrl+Shift+F11 or ⌘+Shift+F11 69 | * Settings - Default 70 | * Settings - User 71 | 72 | ## Installation 73 | 74 | #### [Package Control](http://wbond.net/sublime_packages/package_control) 75 | Execute __"Package Control: Install Package"__ in the Command Palette to retrieve a list of available packages. 76 | Search in the list and install package `Xdebug Client`. 77 | 78 | #### Git 79 | Clone the repository by executing the following command in your Packages directory: 80 | 81 | ```bash 82 | git clone https://github.com/martomo/SublimeTextXdebug.git "Xdebug Client" 83 | ``` 84 | 85 | #### Download 86 | Get the latest [source from GitHub](https://github.com/martomo/SublimeTextXdebug/archive/master.zip) and extract the source into your Packages directory. 87 | 88 | 89 | *__Note:__ You can locate your Packages directory in the menu under* `Preferences / Browse Packages...` 90 | 91 | ## Xdebug 92 | In order to be able to debug your PHP scripts, you will need have Xdebug extension installed on your server. 93 | [See here for installation instructions](http://xdebug.org/docs/install) 94 | 95 | Below is a configuration template for php.ini/xdebug.ini, be warned if you are on a Live environment, __remote\_connect\_back__ allows every debug request from any source to be accepted. 96 | 97 | ```ini 98 | [xdebug] 99 | zend_extension = /absolute/path/to/your/xdebug-extension.so 100 | ;zend_extension = "C:\Program Files (x86)\PHP\ext\php_xdebug.dll" 101 | xdebug.remote_enable = 1 102 | xdebug.remote_host = "127.0.0.1" 103 | xdebug.remote_port = 9000 104 | xdebug.remote_handler = "dbgp" 105 | xdebug.remote_mode = req 106 | xdebug.remote_connect_back = 1 107 | ``` 108 | For details about all available settings for configuring Xdebug, see [here](http://xdebug.org/docs/all_settings). 109 | 110 | ## Configuration 111 | The following settings can be configured in Xdebug.sublime-settings or in \*.sublime-project files: 112 | 113 | *__path\_mapping__* 114 | For remote debugging to resolve the file locations it is required to configure the path mapping with the server path as key and local path as value. 115 | 116 | *__url__* 117 | Determine which URL to launch in the default web browser when starting/stopping a session. 118 | 119 | *__ide\_key__* 120 | An IDE key is used to identify with debugger engine when Sublime Text will start or stop a debugging session. 121 | 122 | _This package does not filter sessions by IDE key, it will accept any IDE key, also ones that do not match this configured IDE key. It is merely used when launching the default web browser with the configured URL._ 123 | 124 | *__host__* 125 | Host address of network interface which Sublime Text should listen to connect with debugger engine. 126 | 127 | _When specifying host address of network interface, be sure to specify an IPv4 address as Sublime Text will listen for connections through an IPv4 socket._ 128 | 129 | *__port__* 130 | Which port number Sublime Text should listen to connect with debugger engine. 131 | 132 | *__max\_children__* 133 | Maximum amount of array children and object's properties to return. 134 | 135 | *__max\_data__* 136 | Maximum amount of variable data to initially retrieve. 137 | 138 | *__max\_depth__* 139 | Maximum amount of nested levels to retrieve of array elements and object properties. 140 | 141 | *__break\_on\_start__* 142 | Break at first line on session start, when debugger engine has connected. 143 | 144 | *__break\_on\_exception__* 145 | Break on exceptions, suspend execution when the exception name matches an entry in this list value. 146 | 147 | *__close\_on\_stop__* 148 | Always close debug windows and restore layout on session stop. 149 | 150 | *__super\_globals__* 151 | Show information about super globals in context view. 152 | 153 | *__fullname\_property__* 154 | Display property by fullname in context view. 155 | 156 | *__hide\_password__* 157 | Do not show possible password values in context view. 158 | 159 | *__pretty\_output__* 160 | Render evaluated result as parsed output instead of raw XML. 161 | 162 | *__launch\_browser__* 163 | Always launch browser on session start/stop. 164 | 165 | *This will only work if you have the '__url__' setting configured.* 166 | 167 | *__browser\_no\_execute__* 168 | When launching browser on session stop do not execute script. 169 | By using parameter XDEBUG\_SESSION\_STOP\_NO\_EXEC instead of XDEBUG\_SESSION\_STOP. 170 | 171 | *__disable\_layout__* 172 | Do not use the debugging window layout. 173 | 174 | *__debug\_layout__* 175 | Window layout that is being used when debugging. 176 | 177 | *__breakpoint\_group__* 178 | *__breakpoint\_index__* 179 | *__context\_group__* 180 | *__context\_index__* 181 | *__stack\_group__* 182 | *__stack\_index__* 183 | *__watch\_group__* 184 | *__watch\_index__* 185 | Group and index positions for debug views. 186 | 187 | *__breakpoint\_enabled__* 188 | *__breakpoint\_disabled__* 189 | *__breakpoint\_current__* 190 | *__current\_line__* 191 | Custom gutter icons for indicating current line or enabled/disabled breakpoints. 192 | 193 | _Do not use same icon for above values, because Sublime Text is unable to use the same icon for different scopes, in case there are duplicate icons detected it will fall back to the corresponding icon in the package._ 194 | 195 | *__python\_path__* 196 | Path to Python installation on your system. 197 | Which is being used to load missing modules. 198 | 199 | *__debug__* 200 | Show detailed log information about communication between debugger engine and Sublime Text. 201 | 202 | --- 203 | 204 | Below are examples how to configure your Xdebug.sublime-settings and \*.sublime-project files. 205 | 206 | __Xdebug.sublime-settings__ 207 | 208 | ```json 209 | { 210 | "path_mapping": { 211 | "/absolute/path/to/file/on/server" : "/absolute/path/to/file/on/computer", 212 | "/var/www/htdocs/example/" : "C:/git/websites/example/" 213 | }, 214 | "url": "http://your.web.server/index.php", 215 | "super_globals": true, 216 | "close_on_stop": true 217 | } 218 | ``` 219 | 220 | __\*.sublime-project__ 221 | 222 | ```json 223 | { 224 | "folders": 225 | [ 226 | { 227 | "path": "..." 228 | } 229 | ], 230 | "settings": 231 | { 232 | "xdebug": { 233 | "path_mapping": { 234 | "/absolute/path/to/file/on/server" : "/absolute/path/to/file/on/computer", 235 | "/var/www/htdocs/example/" : "C:/git/websites/example/" 236 | }, 237 | "url": "http://your.web.server/index.php", 238 | "super_globals": true, 239 | "close_on_stop": true 240 | } 241 | } 242 | } 243 | ``` 244 | 245 | ## Troubleshoot 246 | 247 | #### Can I have both [SublimeTextXdebug](https://github.com/martomo/SublimeTextXdebug) and [SublimeXdebug](https://github.com/Kindari/SublimeXdebug) installed? 248 | No. Having installed both packages can cause conflicts, because they might both listen to the same port for a debugger engine response and have similar keymapping. 249 | 250 | However (project) settings from SublimeXdebug are compatible with SublimeTextXdebug. 251 | 252 | #### How can I start a debugging session? 253 | SublimeTextXdebug can [start or stop a debugging session](#startstop-debugging-session) by launching the default web browser with the configured URL and parameter `XDEBUG_SESSION_START` or `XDEBUG_SESSION_STOP` which uses the configured IDE key as value. By default the IDE key is `sublime.xdebug`. 254 | 255 | When you do not configure the URL, the plugin will still listen for debugging connections from Xdebug, but you will need to trigger Xdebug [for a remote session](http://xdebug.org/docs/remote#starting). 256 | 257 | If you want to run a start a debugging session from command line, before you run your script, you will need to set the environment variable __XDEBUG\_CONFIG__ with the IDE key. 258 | 259 | __Windows__ 260 | 261 | ```bash 262 | set XDEBUG_CONFIG="idekey=sublime.xdebug" 263 | php myscript.php 264 | ``` 265 | 266 | __UNIX__ 267 | 268 | ```bash 269 | export XDEBUG_CONFIG="idekey=sublime.xdebug" 270 | php myscript.php 271 | ``` 272 | 273 | Make sure before defining the environment variable you have switched to the proper user environment. 274 | As example you would set the environment variable as __guest__ and execute the script as __root__ _(sudo)_, then __root__ will not have the environment variable XDEBUG\_CONFIG that was defined by __guest__. 275 | 276 | #### How do I set a breakpoint and/or watch expression? 277 | With SublimeTextXdebug you can easily [add/remove breakpoints](#breakpoints), which are send on session start. 278 | Or [set/edit/remove watch expressions](#watch-expressions), that are evaluated in the current execution context. 279 | 280 | Setting a conditional breakpoints or watch expressions is quite easy, it is like an __if__ statement in PHP. 281 | 282 | As example you only want to stop on breakpoint when value of __$number__ is equal to __13__, then your __Breakpoint condition__ would be `$number==13`. 283 | Another example would be when you would like to know the value of __$item['image']__ on each break, then your __Watch expression__ would be `$item['image']`. 284 | 285 | Another way is to set the breakpoint in your PHP code with the following function [`xdebug_break()`](http://xdebug.org/docs/remote#xdebug_break). 286 | 287 | #### How to configure or disable breaking on exceptions? 288 | By default the execution of a debugging session is suspended on each of the following exception names: 289 | * __"Fatal error"__ \- E\_ERROR, E\_CORE\_ERROR, E\_COMPILE\_ERROR, E\_USER\_ERROR 290 | * __"Catchable fatal error"__ \- E\_RECOVERABLE\_ERROR (since PHP 5.2.0) 291 | * __"Warning"__ \- E\_WARNING, E\_CORE\_WARNING, E\_COMPILE\_WARNING, E\_USER\_WARNING 292 | * __"Parse error"__ \- E\_PARSE 293 | * __"Notice"__ \- E\_NOTICE, E\_USER\_NOTICE 294 | * __"Strict standards"__ \- E\_STRICT 295 | * __"Deprecated"__ \- E\_DEPRECATED, E\_USER\_DEPRECATED (since PHP 5.3.0) 296 | * __"Xdebug"__ 297 | * __"Unknown error"__ 298 | 299 | In order to specify which exception names to suspend the execution of a debugging session, configure the `break_on_exception` setting with a list of the specific exception names by choice from the list shown above. 300 | 301 | It is also possible to specify custom exceptions instead of __all__ exceptions (__"Fatal error"__). For example if you would configure __"MissingArgumentException"__ instead of __"Fatal error"__, it would not break on __"InvalidParameterException"__. 302 | 303 | To disable breaking on exceptions either configure an empty list `break_on_exception: []` or set as `break_on_exception: false`. 304 | 305 | #### How can I customize/disable the debugging layout? 306 | Re-adjust the layout in Sublime Text to your liking and then in console (Ctrl+\`) you type `window.get_layout()` and set that value as your `debug_layout`. 307 | 308 | Further customizing can be done by assigning the Xdebug views to a group/index with the `breakpoint_group`, `breakpoint_index`, `context_group`, `context_index`, `stack_group`, `stack_index`, `watch_group`, `watch_index` settings. 309 | 310 | Or you can disable the debugging layout by setting `disable_layout: true`, which will open all Xdebug views in current active group/window on session start and does not change your layout. 311 | 312 | #### How to solve `ImportError`? 313 | Older versions of Sublime Text do not come with Python bundled and rely on your Python system installation. 314 | Some systems do not include specific modules which are required by this package, such as __pyexpat__. 315 | 316 | Configure the `python_path` setting to the path of your Python installation on your system which is either newer or has all required modules. For example: `"python_path" : "/usr/lib/python2.7"`. 317 | 318 | #### What to do when you experience any issues? 319 | First check following _possible_ solutions that could resolve any issues: 320 | 321 | - Use __absolute__ paths in your `path_mapping` setting, Xdebug does not return symbolic links. 322 | - Breakpoint is on an empty line, Xdebug does not stop on empty lines. 323 | - Set `port` and [xdebug.remote_port](http://xdebug.org/docs/all_settings#remote_port) to different port *(9001)*, default port 9000 might already be used by an other application. 324 | - Add an exception for Sublime Text *(plugin_host.exe)* to your firewall, response from Xdebug could be blocked by firewall. 325 | - Lower the `max_data`/`max_depth`/`max_children` settings to increase response speed or prevent crashing, Xdebug could return to much data to process. 326 | - Change permissions for Sublime Text and it's packages on your filesystem, Sublime Text or package might not have valid rights. 327 | 328 | Do you still experience any issues, then [create an issue](https://github.com/martomo/SublimeTextXdebug/issues/new) including the following data: 329 | 330 | - What operating system(s) and version of Sublime Text are you using? 331 | - How did you [install](https://github.com/martomo/SublimeTextXdebug#installation) SublimeTextXdebug, Package Control, git clone or download? 332 | - Are you trying to debug the script remotely or locally, through browser or command line? 333 | - Which version of Xdebug extension do you have? 334 | - Can you post your [project/settings file](https://github.com/martomo/SublimeTextXdebug#configuration) and [Xdebug configuration](https://github.com/martomo/SublimeTextXdebug#xdebug) from the \*.ini located on your server. 335 | - Does the console window (Ctrl+\`) show any more information regarding the error? 336 | 337 | ## License 338 | 339 | SublimeTextXdebug is released under the [MIT License][license-link]. 340 | 341 | 342 | [travis-badge]: https://travis-ci.org/martomo/SublimeTextXdebug.svg?branch=master 343 | [travis-link]: https://travis-ci.org/martomo/SublimeTextXdebug 344 | [package-control-badge]: https://img.shields.io/packagecontrol/dt/Xdebug%20Client.svg 345 | [package-control-link]: https://packagecontrol.io/packages/Xdebug%20Client 346 | [license-badge]: https://img.shields.io/badge/license-MIT-007EC7.svg 347 | [license-link]: LICENSE 348 | -------------------------------------------------------------------------------- /Xdebug.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // For remote debugging to resolve the file locations 3 | // it is required to configure the path mapping 4 | // with the server path as key and local path as value. 5 | // 6 | // Make sure to use absolute path when defining server path, 7 | // because Xdebug debugger engine does not return symbolic links. 8 | // 9 | // Example: 10 | // "/absolute/path/to/file/on/server" : "/path/to/file/on/computer", 11 | // "/var/www/htdocs/example/" : "C:/git/websites/example/" 12 | "path_mapping": { 13 | 14 | }, 15 | 16 | // Determine which URL to launch in the default web browser 17 | // when starting/stopping a session. 18 | "url": "", 19 | 20 | // An IDE key is used to identify with debugger engine 21 | // when Sublime Text will start or stop a debugging session. 22 | // 23 | // This package does not filter sessions by IDE key, 24 | // it will accept any IDE key, also ones that do not match this configured IDE key. 25 | // It is merely used when launching the default web browser with the configured URL. 26 | "ide_key": "sublime.xdebug", 27 | 28 | // Host address of network interface which Sublime Text 29 | // should listen to connect with debugger engine. 30 | // 31 | // By specifying "" or "0.0.0.0" as host address, Sublime Text 32 | // will listen on all the configured network interfaces. 33 | // This is the desired configuration when debugging 34 | // a script that is located on a remote server. 35 | // 36 | // Otherwise it is recommended to use "127.0.0.1" or "localhost" 37 | // as configuration, when debugging a script on your local machine. 38 | // Due to the fact that by listening on all the configured 39 | // network interfaces raises a security concern as anyone 40 | // can access Sublime Text through the configured port. 41 | // 42 | // When specifying host address of network interface, 43 | // be sure to specify an IPv4 address as Sublime Text 44 | // will listen for connections through an IPv4 socket. 45 | "host": "", 46 | 47 | // Which port number Sublime Text should listen 48 | // to connect with debugger engine. 49 | "port": 9000, 50 | 51 | // Maximum amount of array children 52 | // and object's properties to return. 53 | "max_children": 32, 54 | 55 | // Maximum amount of 56 | // variable data to initially retrieve. 57 | "max_data": 1024, 58 | 59 | // Maximum amount of nested levels to retrieve 60 | // of array elements and object properties. 61 | "max_depth": 1, 62 | 63 | // Break at first line on session start, when debugger engine has connected. 64 | "break_on_start": false, 65 | 66 | // Break on exceptions, suspend execution 67 | // when the exception name matches an entry in this list value. 68 | "break_on_exception": [ 69 | // E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR 70 | "Fatal error", 71 | // E_RECOVERABLE_ERROR (since PHP 5.2.0) 72 | "Catchable fatal error", 73 | // E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING 74 | "Warning", 75 | // E_PARSE 76 | "Parse error", 77 | // E_NOTICE, E_USER_NOTICE 78 | "Notice", 79 | // E_STRICT 80 | "Strict standards", 81 | // E_DEPRECATED, E_USER_DEPRECATED (since PHP 5.3.0) 82 | "Deprecated", 83 | // 0 84 | "Xdebug", 85 | // default 86 | "Unknown error" 87 | ], 88 | 89 | // Always close debug windows and restore layout on session stop. 90 | "close_on_stop": false, 91 | 92 | // Show information about super globals in context view. 93 | "super_globals": true, 94 | 95 | // Display property by fullname in context view. 96 | "fullname_property": true, 97 | 98 | // Do not show possible password values in context view. 99 | "hide_password": false, 100 | 101 | // Render evaluated result as parsed output instead of raw XML. 102 | "pretty_output": false, 103 | 104 | // Always launch browser on session start/stop. 105 | // Note: This will only work if you have the 'url' setting configured. 106 | "launch_browser": false, 107 | 108 | // When launching browser on session stop do not execute script. 109 | // By using parameter XDEBUG_SESSION_STOP_NO_EXEC instead of XDEBUG_SESSION_STOP. 110 | "browser_no_execute": false, 111 | 112 | // Do not use the debugging window layout. 113 | "disable_layout": false, 114 | 115 | // Window layout that is being used when debugging. 116 | "debug_layout" : { 117 | "cols": [0.0, 0.5, 1.0], 118 | "rows": [0.0, 0.7, 1.0], 119 | "cells": [[0, 0, 2, 1], [0, 1, 1, 2], [1, 1, 2, 2]] 120 | }, 121 | 122 | // Group and index positions for debug views. 123 | "breakpoint_group": 2, 124 | "breakpoint_index": 1, 125 | "context_group": 1, 126 | "context_index": 0, 127 | "stack_group": 2, 128 | "stack_index": 0, 129 | "watch_group": 1, 130 | "watch_index": 1, 131 | 132 | // Custom gutter icons for indicating current line or enabled/disabled breakpoints. 133 | // 134 | // Do not use same icon for following values, because Sublime Text is unable 135 | // to use the same icon for different scopes, in case there are duplicate icons 136 | // detected it will fall back to the corresponding icon in the package. 137 | "breakpoint_enabled": "circle", 138 | "breakpoint_disabled": "dot", 139 | "breakpoint_current": "", 140 | "current_line": "bookmark", 141 | 142 | // Path to Python installation on your system. 143 | // Which is being used to load missing modules. 144 | // 145 | // It is recommended to configure your Python path for Sublime Text 2 146 | // especially on older UNIX systems, where some modules (xml.parsers.expat) 147 | // might be missing and could improve performance of package. 148 | // 149 | // Example: 150 | // "python_path" : "/usr/lib/python2.7" 151 | "python_path" : "", 152 | 153 | // Show detailed log information about communication 154 | // between debugger engine and Sublime Text. 155 | // Log can be found at Packages/User/Xdebug.log 156 | "debug": false 157 | } -------------------------------------------------------------------------------- /Xdebug.tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Xdebug 7 | patterns 8 | 9 | 10 | captures 11 | 12 | 1 13 | 14 | name 15 | variable.other.xdebug.entry.name 16 | 17 | 2 18 | 19 | name 20 | keyword.operator.xdebug.entry.scope.resolution 21 | 22 | 3 23 | 24 | name 25 | keyword.operator.xdebug.entry.object.member 26 | 27 | 4 28 | 29 | name 30 | keyword.operator.xdebug.entry.equals 31 | 32 | 5 33 | 34 | name 35 | support.type.xdebug.entry.type 36 | 37 | 6 38 | 39 | name 40 | variable.parameter.xdebug.entry.numchildren 41 | 42 | 7 43 | 44 | name 45 | support.type.xdebug.entry.type 46 | 47 | 8 48 | 49 | name 50 | string.quoted.xdebug.entry.value 51 | 52 | 9 53 | 54 | name 55 | comment.line.xdebug.entry.unknown 56 | 57 | 58 | match 59 | ^(?=\$|\s)\s*((?:.*(::))?(?:.*(->))?.*?)\s+(=)\s+(?:(\S*)(\[\d+\])|(\(.*?\))\s(.*)|(<.*>)) 60 | name 61 | meta.xdebug.entry.line 62 | 63 | 64 | captures 65 | 66 | 1 67 | 68 | name 69 | constant.other.xdebug.entry.ellipsis 70 | 71 | 72 | match 73 | ^\s*(\.{3,}) 74 | name 75 | meta.xdebug.entry.maximum.depth 76 | 77 | 78 | captures 79 | 80 | 1 81 | 82 | name 83 | keyword.operator.xdebug.stack.level 84 | 85 | 2 86 | 87 | name 88 | variable.other.xdebug.stack.filename 89 | 90 | 3 91 | 92 | name 93 | string.quoted.xdebug.stack.lineno 94 | 95 | 4 96 | 97 | name 98 | variable.parameter.xdebug.stack.where 99 | 100 | 101 | match 102 | ^(\[\d+\])\s(.*)(:\d+),\s(.*\(\)) 103 | name 104 | meta.xdebug.stack.line 105 | 106 | 107 | captures 108 | 109 | 1 110 | 111 | name 112 | support.type.xdebug.exception.name 113 | 114 | 2 115 | 116 | name 117 | comment.line.xdebug.exception.message 118 | 119 | 120 | match 121 | ^(\[\D+\])\s(.*) 122 | name 123 | meta.xdebug.stack.exception 124 | 125 | 126 | captures 127 | 128 | 1 129 | 130 | name 131 | support.type.xdebug.breakpoint.file.indent 132 | 133 | 2 134 | 135 | name 136 | comment.line.xdebug.breakpoint.file.name 137 | 138 | 139 | match 140 | ^\s*(=>)\s*(.*) 141 | name 142 | meta.xdebug.breakpoint.file 143 | 144 | 145 | captures 146 | 147 | 1 148 | 149 | name 150 | entity.other.attribute-name.anchor.xdebug.entry.enabled 151 | 152 | 2 153 | 154 | name 155 | keyword.operator.xdebug.entry.disabled 156 | 157 | 3 158 | 159 | name 160 | variable.other.xdebug.breakpoint.line.number 161 | 162 | 4 163 | 164 | name 165 | variable.parameter.xdebug.breakpoint.line.separator 166 | 167 | 5 168 | 169 | name 170 | string.quoted.xdebug.breakpoint.line.expression 171 | 172 | 173 | match 174 | ^\s*(?:(\|\+\|)|(\|-\|))\s*(\d+)\s*(?:(--)(.*)|.*) 175 | name 176 | meta.xdebug.breakpoint.line 177 | 178 | 179 | captures 180 | 181 | 1 182 | 183 | name 184 | entity.other.attribute-name.anchor.xdebug.entry.enabled 185 | 186 | 2 187 | 188 | name 189 | keyword.operator.xdebug.entry.disabled 190 | 191 | 3 192 | 193 | name 194 | variable.other.xdebug.entry.name.expression 195 | 196 | 4 197 | 198 | name 199 | keyword.operator.xdebug.entry.equals 200 | 201 | 5 202 | 203 | name 204 | support.type.xdebug.entry.type 205 | 206 | 6 207 | 208 | name 209 | string.quoted.xdebug.entry.value 210 | 211 | 7 212 | 213 | name 214 | support.type.xdebug.entry.type 215 | 216 | 8 217 | 218 | name 219 | variable.parameter.xdebug.entry.numchildren 220 | 221 | 9 222 | 223 | name 224 | comment.line.xdebug.entry.unknown 225 | 226 | 227 | match 228 | ^(?:(\|\+\|)|(\|-\|)|)\s+(\".*\")\s+(?:(=)\s+(?:(\(.*?\))\s(.*)|(\S*)(\[.*\])|(<.*>))|.*) 229 | name 230 | meta.xdebug.watch.line 231 | 232 | 233 | scopeName 234 | meta.xdebug 235 | uuid 236 | 924fce90-cf0a-4ef6-bf29-b98899924cad 237 | 238 | 239 | -------------------------------------------------------------------------------- /icons/breakpoint_current.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martomo/SublimeTextXdebug/7b62975aed85f4bc839d908d7a696d1ca2b794d9/icons/breakpoint_current.png -------------------------------------------------------------------------------- /icons/breakpoint_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martomo/SublimeTextXdebug/7b62975aed85f4bc839d908d7a696d1ca2b794d9/icons/breakpoint_disabled.png -------------------------------------------------------------------------------- /icons/breakpoint_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martomo/SublimeTextXdebug/7b62975aed85f4bc839d908d7a696d1ca2b794d9/icons/breakpoint_enabled.png -------------------------------------------------------------------------------- /icons/current_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martomo/SublimeTextXdebug/7b62975aed85f4bc839d908d7a696d1ca2b794d9/icons/current_line.png -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.1.0": "messages/1.1.0.txt" 3 | } -------------------------------------------------------------------------------- /messages/1.1.0.txt: -------------------------------------------------------------------------------- 1 | 1.1.0 2 | ----- 3 | 4 | Enhancement: 5 | - Display error dialog for connection issues upon debug session start 6 | - Reformat stack entry output to improve distinction between file/line and function 7 | - Improve navigation from Stack/Breakpoint view to file by breakpoint/entry 8 | - Highlight current breakpoint/line by selecting row in file 9 | - Use current value when editing an existing watch expression 10 | - Rename syntax scope definitions to be in accordance with recommendations 11 | 12 | Feature: 13 | - Specify which network interface to listen to with new 'host' setting 14 | - With 'fullname_property' setting toggle between short/full name representation of nested properties 15 | 16 | Fix: 17 | - Parsing of non-existent files which are expected to contain breakpoint/watch data 18 | - Duplicate entries in Watch view of static variables referencing itself 19 | - Opening of file upon (accidental) multiselection in Breakpoint/Stack view 20 | - Unable to toggle watch expressions by double click when no connection established 21 | - Losing focus of active view upon closing debugging windows 22 | 23 | Internal: 24 | - Enforcing of coding style by running flake8 with TravisCI 25 | - Added issue template to encourage detailed bug reports 26 | 27 | README 28 | - Introduce badges for displaying build status and total download count on Package Control 29 | - Added documentation for new 'host' and 'fullname_property' settings 30 | - Update documentation for 'super_globals', 'hide_password' and 'pretty_output' settings 31 | - Properly escape single asterisk character and fixed several typos 32 | -------------------------------------------------------------------------------- /tests/integration/test_breakpoint_exception.py: -------------------------------------------------------------------------------- 1 | import os 2 | try: 3 | from xdebug.unittesting import XdebugDeferrableTestCase 4 | except: 5 | from SublimeTextXdebug.xdebug.unittesting import XdebugDeferrableTestCase 6 | 7 | 8 | class TestBreakpointException(XdebugDeferrableTestCase): 9 | breakpoint_exception_file = 'breakpoint_exception.php' 10 | breakpoint_exception_file_local_path = os.path.join(XdebugDeferrableTestCase.local_path, breakpoint_exception_file) 11 | 12 | def test_break_on_deprecated(self): 13 | self.run_command('xdebug_session_start') 14 | yield self.window_has_debug_layout 15 | 16 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 17 | context_view = self.get_view_by_title('Xdebug Context') 18 | stack_view = self.get_view_by_title('Xdebug Stack') 19 | 20 | self.assertViewIsEmpty(breakpoint_view) 21 | self.assertViewIsEmpty(context_view) 22 | self.assertViewIsEmpty(stack_view) 23 | 24 | self.send_server_request(path=self.breakpoint_exception_file, params={'exception': 'Deprecated'}) 25 | 26 | def stack_has_content(): 27 | return not self.view_is_empty(stack_view) 28 | yield stack_has_content 29 | 30 | self.assertViewContains(stack_view, '[Deprecated] Non-static method Deprecated::get() should not be called statically') 31 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:16, {{main}}()'.format(remote_path=self.remote_path, file=self.breakpoint_exception_file)) 32 | 33 | def test_break_on_fatal_error(self): 34 | self.run_command('xdebug_session_start') 35 | yield self.window_has_debug_layout 36 | 37 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 38 | context_view = self.get_view_by_title('Xdebug Context') 39 | stack_view = self.get_view_by_title('Xdebug Stack') 40 | 41 | self.assertViewIsEmpty(breakpoint_view) 42 | self.assertViewIsEmpty(context_view) 43 | self.assertViewIsEmpty(stack_view) 44 | 45 | self.send_server_request(path=self.breakpoint_exception_file, params={'exception': 'Fatal error'}) 46 | 47 | def stack_has_content(): 48 | return not self.view_is_empty(stack_view) 49 | yield stack_has_content 50 | 51 | self.assertViewContains(stack_view, "[Fatal error] Uncaught Exception: I'm sorry Dave, I'm afraid I can't do that. in {remote_path}/{file}:28".format(remote_path=self.remote_path, file=self.breakpoint_exception_file)) 52 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:28, {{unknown}}()'.format(remote_path=self.remote_path, file=self.breakpoint_exception_file)) 53 | 54 | def test_break_on_notice(self): 55 | self.run_command('xdebug_session_start') 56 | yield self.window_has_debug_layout 57 | 58 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 59 | context_view = self.get_view_by_title('Xdebug Context') 60 | stack_view = self.get_view_by_title('Xdebug Stack') 61 | 62 | self.assertViewIsEmpty(breakpoint_view) 63 | self.assertViewIsEmpty(context_view) 64 | self.assertViewIsEmpty(stack_view) 65 | 66 | self.send_server_request(path=self.breakpoint_exception_file, params={'exception': 'Notice'}) 67 | 68 | def stack_has_content(): 69 | return not self.view_is_empty(stack_view) 70 | yield stack_has_content 71 | 72 | self.assertViewContains(stack_view, '[Notice] Undefined variable: notice') 73 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:19, {{main}}()'.format(remote_path=self.remote_path, file=self.breakpoint_exception_file)) 74 | 75 | def test_break_on_parse_error(self): 76 | self.run_command('xdebug_session_start') 77 | yield self.window_has_debug_layout 78 | 79 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 80 | context_view = self.get_view_by_title('Xdebug Context') 81 | stack_view = self.get_view_by_title('Xdebug Stack') 82 | 83 | self.assertViewIsEmpty(breakpoint_view) 84 | self.assertViewIsEmpty(context_view) 85 | self.assertViewIsEmpty(stack_view) 86 | 87 | self.send_server_request(path=self.breakpoint_exception_file, params={'exception': 'Parse error'}) 88 | 89 | def stack_has_content(): 90 | return not self.view_is_empty(stack_view) 91 | yield stack_has_content 92 | 93 | self.assertViewContains(stack_view, '[Parse error] syntax error, unexpected end of file') 94 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:4, {{unknown}}()'.format(remote_path=self.remote_path, file='breakpoint_exception_parse_error.php')) 95 | 96 | def test_break_on_warning(self): 97 | self.run_command('xdebug_session_start') 98 | yield self.window_has_debug_layout 99 | 100 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 101 | context_view = self.get_view_by_title('Xdebug Context') 102 | stack_view = self.get_view_by_title('Xdebug Stack') 103 | 104 | self.assertViewIsEmpty(breakpoint_view) 105 | self.assertViewIsEmpty(context_view) 106 | self.assertViewIsEmpty(stack_view) 107 | 108 | self.send_server_request(path=self.breakpoint_exception_file, params={'exception': 'Warning'}) 109 | 110 | def stack_has_content(): 111 | return not self.view_is_empty(stack_view) 112 | yield stack_has_content 113 | 114 | self.assertViewContains(stack_view, '[Warning] include(breakpoint_exception_warning.php): failed to open stream: No such file or directory') 115 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:25, {{main}}()'.format(remote_path=self.remote_path, file=self.breakpoint_exception_file)) 116 | -------------------------------------------------------------------------------- /tests/integration/test_breakpoint_start.py: -------------------------------------------------------------------------------- 1 | import os 2 | try: 3 | from xdebug.unittesting import XdebugDeferrableTestCase 4 | except: 5 | from SublimeTextXdebug.xdebug.unittesting import XdebugDeferrableTestCase 6 | 7 | 8 | class TestBreakpointStart(XdebugDeferrableTestCase): 9 | breakpoint_start_file = 'breakpoint_start.php' 10 | breakpoint_start_file_local_path = os.path.join(XdebugDeferrableTestCase.local_path, breakpoint_start_file) 11 | 12 | def test_break_on_start(self): 13 | self.set_xdebug_settings({ 14 | 'break_on_start': True 15 | }) 16 | yield self.window_has_xdebug_settings 17 | 18 | self.run_command('xdebug_session_start') 19 | yield self.window_has_debug_layout 20 | 21 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 22 | context_view = self.get_view_by_title('Xdebug Context') 23 | stack_view = self.get_view_by_title('Xdebug Stack') 24 | 25 | self.assertViewIsEmpty(breakpoint_view) 26 | self.assertViewIsEmpty(context_view) 27 | self.assertViewIsEmpty(stack_view) 28 | 29 | self.send_server_request(path=self.breakpoint_start_file) 30 | 31 | def stack_has_content(): 32 | return not self.view_is_empty(stack_view) 33 | yield stack_has_content 34 | 35 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}.{{main}}:1'.format(remote_path=self.remote_path, file=self.breakpoint_start_file)) 36 | 37 | def test_set_breakpoint(self): 38 | self.set_breakpoint(self.breakpoint_start_file_local_path, 3) 39 | 40 | self.run_command('xdebug_session_start') 41 | yield self.window_has_debug_layout 42 | 43 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 44 | context_view = self.get_view_by_title('Xdebug Context') 45 | stack_view = self.get_view_by_title('Xdebug Stack') 46 | 47 | self.assertViewContains(breakpoint_view, '=> {file_local_path}\n\t|+| 3'.format(file_local_path=self.breakpoint_start_file_local_path)) 48 | self.assertViewIsEmpty(context_view) 49 | self.assertViewIsEmpty(stack_view) 50 | 51 | self.send_server_request(path=self.breakpoint_start_file) 52 | 53 | def context_and_stack_have_content(): 54 | return not self.view_is_empty(context_view) and not self.view_is_empty(stack_view) 55 | yield context_and_stack_have_content 56 | 57 | self.assertViewContains(context_view, '$hello = ') 58 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:3, {{main}}()'.format(remote_path=self.remote_path, file=self.breakpoint_start_file)) 59 | -------------------------------------------------------------------------------- /tests/integration/test_breakpoint_step.py: -------------------------------------------------------------------------------- 1 | import os 2 | try: 3 | from xdebug.unittesting import XdebugDeferrableTestCase 4 | except: 5 | from SublimeTextXdebug.xdebug.unittesting import XdebugDeferrableTestCase 6 | 7 | 8 | class TestBreakpointStep(XdebugDeferrableTestCase): 9 | breakpoint_step_file = 'breakpoint_step.php' 10 | breakpoint_step_file_local_path = os.path.join(XdebugDeferrableTestCase.local_path, breakpoint_step_file) 11 | 12 | def test_step_into(self): 13 | self.set_breakpoint(self.breakpoint_step_file_local_path, 11) 14 | 15 | self.run_command('xdebug_session_start') 16 | yield self.window_has_debug_layout 17 | 18 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 19 | context_view = self.get_view_by_title('Xdebug Context') 20 | stack_view = self.get_view_by_title('Xdebug Stack') 21 | 22 | self.assertViewContains(breakpoint_view, '=> {file_local_path}\n\t|+| 11'.format(file_local_path=self.breakpoint_step_file_local_path)) 23 | self.assertViewIsEmpty(context_view) 24 | self.assertViewIsEmpty(stack_view) 25 | 26 | self.send_server_request(path=self.breakpoint_step_file) 27 | 28 | def context_and_stack_have_content(): 29 | return not self.view_is_empty(context_view) and not self.view_is_empty(stack_view) 30 | yield context_and_stack_have_content 31 | 32 | self.assertViewContains(context_view, '$greeting = ') 33 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:11, {{main}}()'.format(remote_path=self.remote_path, file=self.breakpoint_step_file)) 34 | 35 | context_view_contents = self.get_contents_of_view(context_view) 36 | stack_view_contents = self.get_contents_of_view(stack_view) 37 | 38 | def context_and_stack_have_different_content(): 39 | return self.get_contents_of_view(context_view) != context_view_contents and self.get_contents_of_view(stack_view) != stack_view_contents 40 | 41 | self.run_command('xdebug_execute', {'command': 'step_into'}) 42 | yield context_and_stack_have_different_content 43 | yield context_and_stack_have_content 44 | 45 | self.assertViewContains(context_view, '$greet = ') 46 | self.assertViewContains(context_view, '$name = (string) Stranger') 47 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:4, greet()'.format(remote_path=self.remote_path, file=self.breakpoint_step_file)) 48 | 49 | context_view_contents = self.get_contents_of_view(context_view) 50 | stack_view_contents = self.get_contents_of_view(stack_view) 51 | 52 | def context_and_stack_have_different_content(): 53 | return self.get_contents_of_view(context_view) != context_view_contents and self.get_contents_of_view(stack_view) != stack_view_contents 54 | 55 | self.run_command('xdebug_execute', {'command': 'step_into'}) 56 | yield context_and_stack_have_different_content 57 | yield context_and_stack_have_content 58 | 59 | self.assertViewContains(context_view, '$greet = (string) Hi') 60 | self.assertViewContains(context_view, '$name = (string) Stranger') 61 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:5, greet()'.format(remote_path=self.remote_path, file=self.breakpoint_step_file)) 62 | 63 | def test_step_out(self): 64 | self.set_breakpoint(self.breakpoint_step_file_local_path, 5) 65 | 66 | self.run_command('xdebug_session_start') 67 | yield self.window_has_debug_layout 68 | 69 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 70 | context_view = self.get_view_by_title('Xdebug Context') 71 | stack_view = self.get_view_by_title('Xdebug Stack') 72 | 73 | self.assertViewContains(breakpoint_view, '=> {file_local_path}\n\t|+| 5'.format(file_local_path=self.breakpoint_step_file_local_path)) 74 | self.assertViewIsEmpty(context_view) 75 | self.assertViewIsEmpty(stack_view) 76 | 77 | self.send_server_request(path=self.breakpoint_step_file) 78 | 79 | def context_and_stack_have_content(): 80 | return not self.view_is_empty(context_view) and not self.view_is_empty(stack_view) 81 | yield context_and_stack_have_content 82 | 83 | self.assertViewContains(context_view, '$greet = (string) Hi') 84 | self.assertViewContains(context_view, '$name = (string) Stranger') 85 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:5, greet()'.format(remote_path=self.remote_path, file=self.breakpoint_step_file)) 86 | 87 | context_view_contents = self.get_contents_of_view(context_view) 88 | stack_view_contents = self.get_contents_of_view(stack_view) 89 | 90 | def context_and_stack_have_different_content(): 91 | return self.get_contents_of_view(context_view) != context_view_contents and self.get_contents_of_view(stack_view) != stack_view_contents 92 | 93 | self.run_command('xdebug_execute', {'command': 'step_out'}) 94 | yield context_and_stack_have_different_content 95 | yield context_and_stack_have_content 96 | 97 | self.assertViewContains(context_view, '$greeting = (string) Hello Stranger!') 98 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:12, {{main}}()'.format(remote_path=self.remote_path, file=self.breakpoint_step_file)) 99 | 100 | def test_step_over(self): 101 | self.set_breakpoint(self.breakpoint_step_file_local_path, 11) 102 | 103 | self.run_command('xdebug_session_start') 104 | yield self.window_has_debug_layout 105 | 106 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 107 | context_view = self.get_view_by_title('Xdebug Context') 108 | stack_view = self.get_view_by_title('Xdebug Stack') 109 | 110 | self.assertViewContains(breakpoint_view, '=> {file_local_path}\n\t|+| 11'.format(file_local_path=self.breakpoint_step_file_local_path)) 111 | self.assertViewIsEmpty(context_view) 112 | self.assertViewIsEmpty(stack_view) 113 | 114 | self.send_server_request(path=self.breakpoint_step_file) 115 | 116 | def context_and_stack_have_content(): 117 | return not self.view_is_empty(context_view) and not self.view_is_empty(stack_view) 118 | yield context_and_stack_have_content 119 | 120 | self.assertViewContains(context_view, '$greeting = ') 121 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:11, {{main}}()'.format(remote_path=self.remote_path, file=self.breakpoint_step_file)) 122 | 123 | context_view_contents = self.get_contents_of_view(context_view) 124 | stack_view_contents = self.get_contents_of_view(stack_view) 125 | 126 | def context_and_stack_have_different_content(): 127 | return self.get_contents_of_view(context_view) != context_view_contents and self.get_contents_of_view(stack_view) != stack_view_contents 128 | 129 | self.run_command('xdebug_execute', {'command': 'step_over'}) 130 | yield context_and_stack_have_different_content 131 | yield context_and_stack_have_content 132 | 133 | self.assertViewContains(context_view, '$greeting = (string) Hello Stranger!') 134 | self.assertViewContains(stack_view, '[0] file://{remote_path}/{file}:12, {{main}}()'.format(remote_path=self.remote_path, file=self.breakpoint_step_file)) 135 | -------------------------------------------------------------------------------- /tests/integration/test_debug_layout.py: -------------------------------------------------------------------------------- 1 | try: 2 | from xdebug.unittesting import XdebugDeferrableTestCase 3 | except: 4 | from SublimeTextXdebug.xdebug.unittesting import XdebugDeferrableTestCase 5 | 6 | 7 | class TestDebugLayout(XdebugDeferrableTestCase): 8 | 9 | def window_does_not_have_debug_layout(self): 10 | breakpoint_view = self.get_view_by_title('Xdebug Breakpoint') 11 | context_view = self.get_view_by_title('Xdebug Context') 12 | stack_view = self.get_view_by_title('Xdebug Stack') 13 | watch_view = self.get_view_by_title('Xdebug Watch') 14 | return not breakpoint_view and not context_view and not stack_view and not watch_view 15 | 16 | def test_debug_layout_remains_open_on_session_stop(self): 17 | self.set_xdebug_settings({ 18 | 'break_on_start': True 19 | }) 20 | yield self.window_has_xdebug_settings 21 | 22 | self.run_command('xdebug_session_start') 23 | yield self.window_has_debug_layout 24 | 25 | stack_view = self.get_view_by_title('Xdebug Stack') 26 | self.send_server_request() 27 | 28 | def stack_has_content(): 29 | return not self.view_is_empty(stack_view) 30 | yield stack_has_content 31 | 32 | self.run_command('xdebug_session_stop') 33 | 34 | self.assertIsNotNone(self.get_view_by_title('Xdebug Breakpoint')) 35 | self.assertIsNotNone(self.get_view_by_title('Xdebug Context')) 36 | self.assertIsNotNone(self.get_view_by_title('Xdebug Stack')) 37 | self.assertIsNotNone(self.get_view_by_title('Xdebug Watch')) 38 | 39 | def test_debug_layout_is_restored_after_session_stop(self): 40 | self.set_xdebug_settings({ 41 | 'break_on_start': True 42 | }) 43 | yield self.window_has_xdebug_settings 44 | 45 | self.run_command('xdebug_session_start') 46 | yield self.window_has_debug_layout 47 | 48 | stack_view = self.get_view_by_title('Xdebug Stack') 49 | self.send_server_request() 50 | 51 | def stack_has_content(): 52 | return not self.view_is_empty(stack_view) 53 | yield stack_has_content 54 | 55 | self.run_command('xdebug_session_stop') 56 | 57 | self.assertIsNotNone(self.get_view_by_title('Xdebug Breakpoint')) 58 | self.assertIsNotNone(self.get_view_by_title('Xdebug Context')) 59 | self.assertIsNotNone(self.get_view_by_title('Xdebug Stack')) 60 | self.assertIsNotNone(self.get_view_by_title('Xdebug Watch')) 61 | 62 | self.run_command('xdebug_layout', {'restore': True}) 63 | yield self.window_does_not_have_debug_layout 64 | 65 | self.assertIsNone(self.get_view_by_title('Xdebug Breakpoint')) 66 | self.assertIsNone(self.get_view_by_title('Xdebug Context')) 67 | self.assertIsNone(self.get_view_by_title('Xdebug Stack')) 68 | self.assertIsNone(self.get_view_by_title('Xdebug Watch')) 69 | 70 | def test_debug_layout_is_closed_on_session_stop(self): 71 | self.set_xdebug_settings({ 72 | 'break_on_start': True, 73 | 'close_on_stop': True 74 | }) 75 | yield self.window_has_xdebug_settings 76 | 77 | self.run_command('xdebug_session_start') 78 | yield self.window_has_debug_layout 79 | 80 | stack_view = self.get_view_by_title('Xdebug Stack') 81 | self.send_server_request() 82 | 83 | def stack_has_content(): 84 | return not self.view_is_empty(stack_view) 85 | yield stack_has_content 86 | 87 | self.run_command('xdebug_session_stop') 88 | yield self.window_does_not_have_debug_layout 89 | 90 | self.assertIsNone(self.get_view_by_title('Xdebug Breakpoint')) 91 | self.assertIsNone(self.get_view_by_title('Xdebug Context')) 92 | self.assertIsNone(self.get_view_by_title('Xdebug Stack')) 93 | self.assertIsNone(self.get_view_by_title('Xdebug Watch')) 94 | -------------------------------------------------------------------------------- /tests/php-server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu -o pipefail 3 | 4 | # Use polyfill for unsupported -e/-f operator in OSX 5 | readlink-polyfill() { 6 | local target=$1 7 | [ -f "$target" ] || return 1 8 | 9 | cd $(dirname $target) 10 | target=$(basename $target) 11 | 12 | while [ -L "$target" ] 13 | do 14 | target=$(readlink $target) 15 | cd $(dirname $target) 16 | target=$(basename $target) 17 | done 18 | 19 | local physical_directory=$(pwd -P) 20 | echo $physical_directory/$target 21 | } 22 | 23 | usage() { 24 | cat </dev/null 2>&1 && echo "running" || echo "stopped" 54 | } 55 | 56 | stop() { 57 | docker rm --force $DOCKER_CONTAINER_NAME || true 58 | } 59 | 60 | start() { 61 | # Build image with specified PHP/Xdebug versions 62 | docker build -t $DOCKER_IMAGE_NAME \ 63 | --build-arg PHP_VERSION=${PHP_VERSION} \ 64 | --build-arg XDEBUG_VERSION=${XDEBUG_VERSION} \ 65 | ${PACKAGE_DIRECTORY}/tests/php-xdebug 66 | 67 | # Set remote host based on OS, considering Linux does not support 'host.docker.internal' (https://github.com/docker/for-linux/issues/264) 68 | [[ "$OSTYPE" == "darwin"* || "$OSTYPE" == "msys" ]] && XDEBUG_REMOTE_HOST="host.docker.internal" || XDEBUG_REMOTE_HOST="$(ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+')" 69 | 70 | # Remove any running containers to avoid container name conflict 71 | stop 72 | 73 | # Start container with PHP/Xdebug image 74 | docker run --detach --rm -p 8090:80 \ 75 | --name $DOCKER_CONTAINER_NAME \ 76 | --volume ${LOGS_DIRECTORY}:/var/www/logs \ 77 | --volume ${SOURCE_DIRECTORY}:/var/www/html \ 78 | --env XDEBUG_CONFIG="remote_log=/var/www/logs/${XDEBUG_LOGFILE} remote_host=${XDEBUG_REMOTE_HOST}" \ 79 | $DOCKER_IMAGE_NAME 80 | } 81 | 82 | restart() { 83 | start 84 | } 85 | 86 | COMMAND=${1:-} 87 | # Execute command or display usage when invalid command specified 88 | declare -F $COMMAND >/dev/null || usage 89 | $COMMAND 90 | -------------------------------------------------------------------------------- /tests/php-xdebug/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base of official Docker PHP images (https://hub.docker.com/_/php) 2 | ARG PHP_VERSION=7.3 3 | FROM php:${PHP_VERSION}-apache 4 | 5 | # Download and compile Xdebug extension (https://pecl.php.net/package/xdebug) 6 | ARG XDEBUG_VERSION=2.8.0 7 | RUN pecl channel-update pecl.php.net && \ 8 | pecl install xdebug-${XDEBUG_VERSION} && \ 9 | # Enable Xdebug extension 10 | docker-php-ext-enable xdebug && \ 11 | # Configure default Xdebug configuration for Docker image 12 | echo "xdebug.remote_enable=1" >> $PHP_INI_DIR/conf.d/ext-xdebug.ini && \ 13 | echo "xdebug.remote_connect_back=0" >> $PHP_INI_DIR/conf.d/ext-xdebug.ini && \ 14 | echo "xdebug.remote_host=host.docker.internal" >> $PHP_INI_DIR/conf.d/ext-xdebug.ini && \ 15 | echo "xdebug.remote_port=9000" >> $PHP_INI_DIR/conf.d/ext-xdebug.ini && \ 16 | echo "xdebug.remote_handler=dbgp" >> $PHP_INI_DIR/conf.d/ext-xdebug.ini 17 | -------------------------------------------------------------------------------- /tests/server/logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martomo/SublimeTextXdebug/7b62975aed85f4bc839d908d7a696d1ca2b794d9/tests/server/logs/.gitkeep -------------------------------------------------------------------------------- /tests/server/public/breakpoint_exception.php: -------------------------------------------------------------------------------- 1 | default_loader. 92 | # @throws FatalIncludeError If the function fails to include a given 93 | # resource, or if the tree contains malformed XInclude elements. 94 | # @throws IOError If the function fails to load a given resource. 95 | 96 | def include(elem, loader=None): 97 | if loader is None: 98 | loader = default_loader 99 | # look for xinclude elements 100 | i = 0 101 | while i < len(elem): 102 | e = elem[i] 103 | if e.tag == XINCLUDE_INCLUDE: 104 | # process xinclude directive 105 | href = e.get("href") 106 | parse = e.get("parse", "xml") 107 | if parse == "xml": 108 | node = loader(href, parse) 109 | if node is None: 110 | raise FatalIncludeError( 111 | "cannot load %r as %r" % (href, parse) 112 | ) 113 | node = copy.copy(node) 114 | if e.tail: 115 | node.tail = (node.tail or "") + e.tail 116 | elem[i] = node 117 | elif parse == "text": 118 | text = loader(href, parse, e.get("encoding")) 119 | if text is None: 120 | raise FatalIncludeError( 121 | "cannot load %r as %r" % (href, parse) 122 | ) 123 | if i: 124 | node = elem[i-1] 125 | node.tail = (node.tail or "") + text 126 | else: 127 | elem.text = (elem.text or "") + text + (e.tail or "") 128 | del elem[i] 129 | continue 130 | else: 131 | raise FatalIncludeError( 132 | "unknown parse type in xi:include tag (%r)" % parse 133 | ) 134 | elif e.tag == XINCLUDE_FALLBACK: 135 | raise FatalIncludeError( 136 | "xi:fallback tag must be child of xi:include (%r)" % e.tag 137 | ) 138 | else: 139 | include(e, loader) 140 | i = i + 1 141 | 142 | -------------------------------------------------------------------------------- /xdebug/elementtree/ElementPath.py: -------------------------------------------------------------------------------- 1 | # 2 | # ElementTree 3 | # $Id: ElementPath.py 1858 2004-06-17 21:31:41Z Fredrik $ 4 | # 5 | # limited xpath support for element trees 6 | # 7 | # history: 8 | # 2003-05-23 fl created 9 | # 2003-05-28 fl added support for // etc 10 | # 2003-08-27 fl fixed parsing of periods in element names 11 | # 12 | # Copyright (c) 2003-2004 by Fredrik Lundh. All rights reserved. 13 | # 14 | # fredrik@pythonware.com 15 | # http://www.pythonware.com 16 | # 17 | # -------------------------------------------------------------------- 18 | # The ElementTree toolkit is 19 | # 20 | # Copyright (c) 1999-2004 by Fredrik Lundh 21 | # 22 | # By obtaining, using, and/or copying this software and/or its 23 | # associated documentation, you agree that you have read, understood, 24 | # and will comply with the following terms and conditions: 25 | # 26 | # Permission to use, copy, modify, and distribute this software and 27 | # its associated documentation for any purpose and without fee is 28 | # hereby granted, provided that the above copyright notice appears in 29 | # all copies, and that both that copyright notice and this permission 30 | # notice appear in supporting documentation, and that the name of 31 | # Secret Labs AB or the author not be used in advertising or publicity 32 | # pertaining to distribution of the software without specific, written 33 | # prior permission. 34 | # 35 | # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 36 | # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- 37 | # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR 38 | # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 39 | # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 40 | # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 41 | # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 42 | # OF THIS SOFTWARE. 43 | # -------------------------------------------------------------------- 44 | 45 | ## 46 | # Implementation module for XPath support. There's usually no reason 47 | # to import this module directly; the ElementTree does this for 48 | # you, if needed. 49 | ## 50 | 51 | import re 52 | 53 | xpath_tokenizer = re.compile( 54 | "(::|\.\.|\(\)|[/.*:\[\]\(\)@=])|((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|\s+" 55 | ).findall 56 | 57 | class xpath_descendant_or_self: 58 | pass 59 | 60 | ## 61 | # Wrapper for a compiled XPath. 62 | 63 | class Path: 64 | 65 | ## 66 | # Create an Path instance from an XPath expression. 67 | 68 | def __init__(self, path): 69 | tokens = xpath_tokenizer(path) 70 | # the current version supports 'path/path'-style expressions only 71 | self.path = [] 72 | self.tag = None 73 | if tokens and tokens[0][0] == "/": 74 | raise SyntaxError("cannot use absolute path on element") 75 | while tokens: 76 | op, tag = tokens.pop(0) 77 | if tag or op == "*": 78 | self.path.append(tag or op) 79 | elif op == ".": 80 | pass 81 | elif op == "/": 82 | self.path.append(xpath_descendant_or_self()) 83 | continue 84 | else: 85 | raise SyntaxError("unsupported path syntax (%s)" % op) 86 | if tokens: 87 | op, tag = tokens.pop(0) 88 | if op != "/": 89 | raise SyntaxError( 90 | "expected path separator (%s)" % (op or tag) 91 | ) 92 | if self.path and isinstance(self.path[-1], xpath_descendant_or_self): 93 | raise SyntaxError("path cannot end with //") 94 | if len(self.path) == 1 and isinstance(self.path[0], type("")): 95 | self.tag = self.path[0] 96 | 97 | ## 98 | # Find first matching object. 99 | 100 | def find(self, element): 101 | tag = self.tag 102 | if tag is None: 103 | nodeset = self.findall(element) 104 | if not nodeset: 105 | return None 106 | return nodeset[0] 107 | for elem in element: 108 | if elem.tag == tag: 109 | return elem 110 | return None 111 | 112 | ## 113 | # Find text for first matching object. 114 | 115 | def findtext(self, element, default=None): 116 | tag = self.tag 117 | if tag is None: 118 | nodeset = self.findall(element) 119 | if not nodeset: 120 | return default 121 | return nodeset[0].text or "" 122 | for elem in element: 123 | if elem.tag == tag: 124 | return elem.text or "" 125 | return default 126 | 127 | ## 128 | # Find all matching objects. 129 | 130 | def findall(self, element): 131 | nodeset = [element] 132 | index = 0 133 | while 1: 134 | try: 135 | path = self.path[index] 136 | index = index + 1 137 | except IndexError: 138 | return nodeset 139 | set = [] 140 | if isinstance(path, xpath_descendant_or_self): 141 | try: 142 | tag = self.path[index] 143 | if not isinstance(tag, type("")): 144 | tag = None 145 | else: 146 | index = index + 1 147 | except IndexError: 148 | tag = None # invalid path 149 | for node in nodeset: 150 | new = list(node.getiterator(tag)) 151 | if new and new[0] is node: 152 | set.extend(new[1:]) 153 | else: 154 | set.extend(new) 155 | else: 156 | for node in nodeset: 157 | for node in node: 158 | if path == "*" or node.tag == path: 159 | set.append(node) 160 | if not set: 161 | return [] 162 | nodeset = set 163 | 164 | _cache = {} 165 | 166 | ## 167 | # (Internal) Compile path. 168 | 169 | def _compile(path): 170 | p = _cache.get(path) 171 | if p is not None: 172 | return p 173 | p = Path(path) 174 | if len(_cache) >= 100: 175 | _cache.clear() 176 | _cache[path] = p 177 | return p 178 | 179 | ## 180 | # Find first matching object. 181 | 182 | def find(element, path): 183 | return _compile(path).find(element) 184 | 185 | ## 186 | # Find text for first matching object. 187 | 188 | def findtext(element, path, default=None): 189 | return _compile(path).findtext(element, default) 190 | 191 | ## 192 | # Find all matching objects. 193 | 194 | def findall(element, path): 195 | return _compile(path).findall(element) 196 | 197 | -------------------------------------------------------------------------------- /xdebug/elementtree/HTMLTreeBuilder.py: -------------------------------------------------------------------------------- 1 | # 2 | # ElementTree 3 | # $Id: HTMLTreeBuilder.py 2325 2005-03-16 15:50:43Z fredrik $ 4 | # 5 | # a simple tree builder, for HTML input 6 | # 7 | # history: 8 | # 2002-04-06 fl created 9 | # 2002-04-07 fl ignore IMG and HR end tags 10 | # 2002-04-07 fl added support for 1.5.2 and later 11 | # 2003-04-13 fl added HTMLTreeBuilder alias 12 | # 2004-12-02 fl don't feed non-ASCII charrefs/entities as 8-bit strings 13 | # 2004-12-05 fl don't feed non-ASCII CDATA as 8-bit strings 14 | # 15 | # Copyright (c) 1999-2004 by Fredrik Lundh. All rights reserved. 16 | # 17 | # fredrik@pythonware.com 18 | # http://www.pythonware.com 19 | # 20 | # -------------------------------------------------------------------- 21 | # The ElementTree toolkit is 22 | # 23 | # Copyright (c) 1999-2004 by Fredrik Lundh 24 | # 25 | # By obtaining, using, and/or copying this software and/or its 26 | # associated documentation, you agree that you have read, understood, 27 | # and will comply with the following terms and conditions: 28 | # 29 | # Permission to use, copy, modify, and distribute this software and 30 | # its associated documentation for any purpose and without fee is 31 | # hereby granted, provided that the above copyright notice appears in 32 | # all copies, and that both that copyright notice and this permission 33 | # notice appear in supporting documentation, and that the name of 34 | # Secret Labs AB or the author not be used in advertising or publicity 35 | # pertaining to distribution of the software without specific, written 36 | # prior permission. 37 | # 38 | # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 39 | # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- 40 | # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR 41 | # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 42 | # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 43 | # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 44 | # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 45 | # OF THIS SOFTWARE. 46 | # -------------------------------------------------------------------- 47 | 48 | ## 49 | # Tools to build element trees from HTML files. 50 | ## 51 | 52 | import htmlentitydefs 53 | import re, string, sys 54 | import mimetools, StringIO 55 | 56 | import ElementTree 57 | 58 | AUTOCLOSE = "p", "li", "tr", "th", "td", "head", "body" 59 | IGNOREEND = "img", "hr", "meta", "link", "br" 60 | 61 | if sys.version[:3] == "1.5": 62 | is_not_ascii = re.compile(r"[\x80-\xff]").search # 1.5.2 63 | else: 64 | is_not_ascii = re.compile(eval(r'u"[\u0080-\uffff]"')).search 65 | 66 | try: 67 | from HTMLParser import HTMLParser 68 | except ImportError: 69 | from sgmllib import SGMLParser 70 | # hack to use sgmllib's SGMLParser to emulate 2.2's HTMLParser 71 | class HTMLParser(SGMLParser): 72 | # the following only works as long as this class doesn't 73 | # provide any do, start, or end handlers 74 | def unknown_starttag(self, tag, attrs): 75 | self.handle_starttag(tag, attrs) 76 | def unknown_endtag(self, tag): 77 | self.handle_endtag(tag) 78 | 79 | ## 80 | # ElementTree builder for HTML source code. This builder converts an 81 | # HTML document or fragment to an ElementTree. 82 | #

83 | # The parser is relatively picky, and requires balanced tags for most 84 | # elements. However, elements belonging to the following group are 85 | # automatically closed: P, LI, TR, TH, and TD. In addition, the 86 | # parser automatically inserts end tags immediately after the start 87 | # tag, and ignores any end tags for the following group: IMG, HR, 88 | # META, and LINK. 89 | # 90 | # @keyparam builder Optional builder object. If omitted, the parser 91 | # uses the standard elementtree builder. 92 | # @keyparam encoding Optional character encoding, if known. If omitted, 93 | # the parser looks for META tags inside the document. If no tags 94 | # are found, the parser defaults to ISO-8859-1. Note that if your 95 | # document uses a non-ASCII compatible encoding, you must decode 96 | # the document before parsing. 97 | # 98 | # @see elementtree.ElementTree 99 | 100 | class HTMLTreeBuilder(HTMLParser): 101 | 102 | # FIXME: shouldn't this class be named Parser, not Builder? 103 | 104 | def __init__(self, builder=None, encoding=None): 105 | self.__stack = [] 106 | if builder is None: 107 | builder = ElementTree.TreeBuilder() 108 | self.__builder = builder 109 | self.encoding = encoding or "iso-8859-1" 110 | HTMLParser.__init__(self) 111 | 112 | ## 113 | # Flushes parser buffers, and return the root element. 114 | # 115 | # @return An Element instance. 116 | 117 | def close(self): 118 | HTMLParser.close(self) 119 | return self.__builder.close() 120 | 121 | ## 122 | # (Internal) Handles start tags. 123 | 124 | def handle_starttag(self, tag, attrs): 125 | if tag == "meta": 126 | # look for encoding directives 127 | http_equiv = content = None 128 | for k, v in attrs: 129 | if k == "http-equiv": 130 | http_equiv = string.lower(v) 131 | elif k == "content": 132 | content = v 133 | if http_equiv == "content-type" and content: 134 | # use mimetools to parse the http header 135 | header = mimetools.Message( 136 | StringIO.StringIO("%s: %s\n\n" % (http_equiv, content)) 137 | ) 138 | encoding = header.getparam("charset") 139 | if encoding: 140 | self.encoding = encoding 141 | if tag in AUTOCLOSE: 142 | if self.__stack and self.__stack[-1] == tag: 143 | self.handle_endtag(tag) 144 | self.__stack.append(tag) 145 | attrib = {} 146 | if attrs: 147 | for k, v in attrs: 148 | attrib[string.lower(k)] = v 149 | self.__builder.start(tag, attrib) 150 | if tag in IGNOREEND: 151 | self.__stack.pop() 152 | self.__builder.end(tag) 153 | 154 | ## 155 | # (Internal) Handles end tags. 156 | 157 | def handle_endtag(self, tag): 158 | if tag in IGNOREEND: 159 | return 160 | lasttag = self.__stack.pop() 161 | if tag != lasttag and lasttag in AUTOCLOSE: 162 | self.handle_endtag(lasttag) 163 | self.__builder.end(tag) 164 | 165 | ## 166 | # (Internal) Handles character references. 167 | 168 | def handle_charref(self, char): 169 | if char[:1] == "x": 170 | char = int(char[1:], 16) 171 | else: 172 | char = int(char) 173 | if 0 <= char < 128: 174 | self.__builder.data(chr(char)) 175 | else: 176 | self.__builder.data(unichr(char)) 177 | 178 | ## 179 | # (Internal) Handles entity references. 180 | 181 | def handle_entityref(self, name): 182 | entity = htmlentitydefs.entitydefs.get(name) 183 | if entity: 184 | if len(entity) == 1: 185 | entity = ord(entity) 186 | else: 187 | entity = int(entity[2:-1]) 188 | if 0 <= entity < 128: 189 | self.__builder.data(chr(entity)) 190 | else: 191 | self.__builder.data(unichr(entity)) 192 | else: 193 | self.unknown_entityref(name) 194 | 195 | ## 196 | # (Internal) Handles character data. 197 | 198 | def handle_data(self, data): 199 | if isinstance(data, type('')) and is_not_ascii(data): 200 | # convert to unicode, but only if necessary 201 | data = unicode(data, self.encoding, "ignore") 202 | self.__builder.data(data) 203 | 204 | ## 205 | # (Hook) Handles unknown entity references. The default action 206 | # is to ignore unknown entities. 207 | 208 | def unknown_entityref(self, name): 209 | pass # ignore by default; override if necessary 210 | 211 | ## 212 | # An alias for the HTMLTreeBuilder class. 213 | 214 | TreeBuilder = HTMLTreeBuilder 215 | 216 | ## 217 | # Parse an HTML document or document fragment. 218 | # 219 | # @param source A filename or file object containing HTML data. 220 | # @param encoding Optional character encoding, if known. If omitted, 221 | # the parser looks for META tags inside the document. If no tags 222 | # are found, the parser defaults to ISO-8859-1. 223 | # @return An ElementTree instance 224 | 225 | def parse(source, encoding=None): 226 | return ElementTree.parse(source, HTMLTreeBuilder(encoding=encoding)) 227 | 228 | if __name__ == "__main__": 229 | import sys 230 | ElementTree.dump(parse(open(sys.argv[1]))) 231 | -------------------------------------------------------------------------------- /xdebug/elementtree/SgmlopXMLTreeBuilder.py: -------------------------------------------------------------------------------- 1 | # 2 | # ElementTree 3 | # $Id$ 4 | # 5 | # A simple XML tree builder, based on the sgmlop library. 6 | # 7 | # Note that this version does not support namespaces. This may be 8 | # changed in future versions. 9 | # 10 | # history: 11 | # 2004-03-28 fl created 12 | # 13 | # Copyright (c) 1999-2004 by Fredrik Lundh. All rights reserved. 14 | # 15 | # fredrik@pythonware.com 16 | # http://www.pythonware.com 17 | # 18 | # -------------------------------------------------------------------- 19 | # The ElementTree toolkit is 20 | # 21 | # Copyright (c) 1999-2004 by Fredrik Lundh 22 | # 23 | # By obtaining, using, and/or copying this software and/or its 24 | # associated documentation, you agree that you have read, understood, 25 | # and will comply with the following terms and conditions: 26 | # 27 | # Permission to use, copy, modify, and distribute this software and 28 | # its associated documentation for any purpose and without fee is 29 | # hereby granted, provided that the above copyright notice appears in 30 | # all copies, and that both that copyright notice and this permission 31 | # notice appear in supporting documentation, and that the name of 32 | # Secret Labs AB or the author not be used in advertising or publicity 33 | # pertaining to distribution of the software without specific, written 34 | # prior permission. 35 | # 36 | # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 37 | # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- 38 | # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR 39 | # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 40 | # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 41 | # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 42 | # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 43 | # OF THIS SOFTWARE. 44 | # -------------------------------------------------------------------- 45 | 46 | ## 47 | # Tools to build element trees from XML, based on the SGMLOP parser. 48 | #

49 | # The current version does not support XML namespaces. 50 | #

51 | # This tree builder requires the sgmlop extension module 52 | # (available from 53 | # http://effbot.org/downloads). 54 | ## 55 | 56 | import ElementTree 57 | 58 | ## 59 | # ElementTree builder for XML source data, based on the SGMLOP parser. 60 | # 61 | # @see elementtree.ElementTree 62 | 63 | class TreeBuilder: 64 | 65 | def __init__(self, html=0): 66 | try: 67 | import sgmlop 68 | except ImportError: 69 | raise RuntimeError("sgmlop parser not available") 70 | self.__builder = ElementTree.TreeBuilder() 71 | if html: 72 | import htmlentitydefs 73 | self.entitydefs.update(htmlentitydefs.entitydefs) 74 | self.__parser = sgmlop.XMLParser() 75 | self.__parser.register(self) 76 | 77 | ## 78 | # Feeds data to the parser. 79 | # 80 | # @param data Encoded data. 81 | 82 | def feed(self, data): 83 | self.__parser.feed(data) 84 | 85 | ## 86 | # Finishes feeding data to the parser. 87 | # 88 | # @return An element structure. 89 | # @defreturn Element 90 | 91 | def close(self): 92 | self.__parser.close() 93 | self.__parser = None 94 | return self.__builder.close() 95 | 96 | def finish_starttag(self, tag, attrib): 97 | self.__builder.start(tag, attrib) 98 | 99 | def finish_endtag(self, tag): 100 | self.__builder.end(tag) 101 | 102 | def handle_data(self, data): 103 | self.__builder.data(data) 104 | -------------------------------------------------------------------------------- /xdebug/elementtree/SimpleXMLTreeBuilder.py: -------------------------------------------------------------------------------- 1 | # 2 | # ElementTree 3 | # $Id: SimpleXMLTreeBuilder.py 1862 2004-06-18 07:31:02Z Fredrik $ 4 | # 5 | # A simple XML tree builder, based on Python's xmllib 6 | # 7 | # Note that due to bugs in xmllib, this builder does not fully support 8 | # namespaces (unqualified attributes are put in the default namespace, 9 | # instead of being left as is). Run this module as a script to find 10 | # out if this affects your Python version. 11 | # 12 | # history: 13 | # 2001-10-20 fl created 14 | # 2002-05-01 fl added namespace support for xmllib 15 | # 2002-08-17 fl added xmllib sanity test 16 | # 17 | # Copyright (c) 1999-2004 by Fredrik Lundh. All rights reserved. 18 | # 19 | # fredrik@pythonware.com 20 | # http://www.pythonware.com 21 | # 22 | # -------------------------------------------------------------------- 23 | # The ElementTree toolkit is 24 | # 25 | # Copyright (c) 1999-2004 by Fredrik Lundh 26 | # 27 | # By obtaining, using, and/or copying this software and/or its 28 | # associated documentation, you agree that you have read, understood, 29 | # and will comply with the following terms and conditions: 30 | # 31 | # Permission to use, copy, modify, and distribute this software and 32 | # its associated documentation for any purpose and without fee is 33 | # hereby granted, provided that the above copyright notice appears in 34 | # all copies, and that both that copyright notice and this permission 35 | # notice appear in supporting documentation, and that the name of 36 | # Secret Labs AB or the author not be used in advertising or publicity 37 | # pertaining to distribution of the software without specific, written 38 | # prior permission. 39 | # 40 | # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 41 | # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- 42 | # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR 43 | # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 44 | # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 45 | # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 46 | # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 47 | # OF THIS SOFTWARE. 48 | # -------------------------------------------------------------------- 49 | 50 | ## 51 | # Tools to build element trees from XML files, using xmllib. 52 | # This module can be used instead of the standard tree builder, for 53 | # Python versions where "expat" is not available (such as 1.5.2). 54 | #

55 | # Note that due to bugs in xmllib, the namespace support is 56 | # not reliable (you can run the module as a script to find out exactly 57 | # how unreliable it is on your Python version). 58 | ## 59 | 60 | import xmllib, string 61 | 62 | import ElementTree 63 | 64 | ## 65 | # ElementTree builder for XML source data. 66 | # 67 | # @see elementtree.ElementTree 68 | 69 | class TreeBuilder(xmllib.XMLParser): 70 | 71 | def __init__(self, html=0): 72 | self.__builder = ElementTree.TreeBuilder() 73 | if html: 74 | import htmlentitydefs 75 | self.entitydefs.update(htmlentitydefs.entitydefs) 76 | xmllib.XMLParser.__init__(self) 77 | 78 | ## 79 | # Feeds data to the parser. 80 | # 81 | # @param data Encoded data. 82 | 83 | def feed(self, data): 84 | xmllib.XMLParser.feed(self, data) 85 | 86 | ## 87 | # Finishes feeding data to the parser. 88 | # 89 | # @return An element structure. 90 | # @defreturn Element 91 | 92 | def close(self): 93 | xmllib.XMLParser.close(self) 94 | return self.__builder.close() 95 | 96 | def handle_data(self, data): 97 | self.__builder.data(data) 98 | 99 | handle_cdata = handle_data 100 | 101 | def unknown_starttag(self, tag, attrs): 102 | attrib = {} 103 | for key, value in attrs.items(): 104 | attrib[fixname(key)] = value 105 | self.__builder.start(fixname(tag), attrib) 106 | 107 | def unknown_endtag(self, tag): 108 | self.__builder.end(fixname(tag)) 109 | 110 | 111 | def fixname(name, split=string.split): 112 | # xmllib in 2.0 and later provides limited (and slightly broken) 113 | # support for XML namespaces. 114 | if " " not in name: 115 | return name 116 | return "{%s}%s" % tuple(split(name, " ", 1)) 117 | 118 | 119 | if __name__ == "__main__": 120 | import sys 121 | # sanity check: look for known namespace bugs in xmllib 122 | p = TreeBuilder() 123 | text = """\ 124 | 125 | 126 | 127 | """ 128 | p.feed(text) 129 | tree = p.close() 130 | status = [] 131 | # check for bugs in the xmllib implementation 132 | tag = tree.find("{default}tag") 133 | if tag is None: 134 | status.append("namespaces not supported") 135 | if tag is not None and tag.get("{default}attribute"): 136 | status.append("default namespace applied to unqualified attribute") 137 | # report bugs 138 | if status: 139 | print "xmllib doesn't work properly in this Python version:" 140 | for bug in status: 141 | print "-", bug 142 | else: 143 | print "congratulations; no problems found in xmllib" 144 | 145 | -------------------------------------------------------------------------------- /xdebug/elementtree/SimpleXMLWriter.py: -------------------------------------------------------------------------------- 1 | # 2 | # SimpleXMLWriter 3 | # $Id: SimpleXMLWriter.py 2312 2005-03-02 18:13:39Z fredrik $ 4 | # 5 | # a simple XML writer 6 | # 7 | # history: 8 | # 2001-12-28 fl created 9 | # 2002-11-25 fl fixed attribute encoding 10 | # 2002-12-02 fl minor fixes for 1.5.2 11 | # 2004-06-17 fl added pythondoc markup 12 | # 2004-07-23 fl added flush method (from Jay Graves) 13 | # 2004-10-03 fl added declaration method 14 | # 15 | # Copyright (c) 2001-2004 by Fredrik Lundh 16 | # 17 | # fredrik@pythonware.com 18 | # http://www.pythonware.com 19 | # 20 | # -------------------------------------------------------------------- 21 | # The SimpleXMLWriter module is 22 | # 23 | # Copyright (c) 2001-2004 by Fredrik Lundh 24 | # 25 | # By obtaining, using, and/or copying this software and/or its 26 | # associated documentation, you agree that you have read, understood, 27 | # and will comply with the following terms and conditions: 28 | # 29 | # Permission to use, copy, modify, and distribute this software and 30 | # its associated documentation for any purpose and without fee is 31 | # hereby granted, provided that the above copyright notice appears in 32 | # all copies, and that both that copyright notice and this permission 33 | # notice appear in supporting documentation, and that the name of 34 | # Secret Labs AB or the author not be used in advertising or publicity 35 | # pertaining to distribution of the software without specific, written 36 | # prior permission. 37 | # 38 | # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 39 | # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- 40 | # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR 41 | # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 42 | # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 43 | # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 44 | # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 45 | # OF THIS SOFTWARE. 46 | # -------------------------------------------------------------------- 47 | 48 | ## 49 | # Tools to write XML files, without having to deal with encoding 50 | # issues, well-formedness, etc. 51 | #

52 | # The current version does not provide built-in support for 53 | # namespaces. To create files using namespaces, you have to provide 54 | # "xmlns" attributes and explicitly add prefixes to tags and 55 | # attributes. 56 | # 57 | #

Patterns

58 | # 59 | # The following example generates a small XHTML document. 60 | #
 61 | #
 62 | # from elementtree.SimpleXMLWriter import XMLWriter
 63 | # import sys
 64 | #
 65 | # w = XMLWriter(sys.stdout)
 66 | #
 67 | # html = w.start("html")
 68 | #
 69 | # w.start("head")
 70 | # w.element("title", "my document")
 71 | # w.element("meta", name="generator", value="my application 1.0")
 72 | # w.end()
 73 | #
 74 | # w.start("body")
 75 | # w.element("h1", "this is a heading")
 76 | # w.element("p", "this is a paragraph")
 77 | #
 78 | # w.start("p")
 79 | # w.data("this is ")
 80 | # w.element("b", "bold")
 81 | # w.data(" and ")
 82 | # w.element("i", "italic")
 83 | # w.data(".")
 84 | # w.end("p")
 85 | #
 86 | # w.close(html)
 87 | # 
88 | ## 89 | 90 | import re, sys, string 91 | 92 | try: 93 | unicode("") 94 | except NameError: 95 | def encode(s, encoding): 96 | # 1.5.2: application must use the right encoding 97 | return s 98 | _escape = re.compile(r"[&<>\"\x80-\xff]+") # 1.5.2 99 | else: 100 | def encode(s, encoding): 101 | return s.encode(encoding) 102 | _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"')) 103 | 104 | def encode_entity(text, pattern=_escape): 105 | # map reserved and non-ascii characters to numerical entities 106 | def escape_entities(m): 107 | out = [] 108 | for char in m.group(): 109 | out.append("&#%d;" % ord(char)) 110 | return string.join(out, "") 111 | return encode(pattern.sub(escape_entities, text), "ascii") 112 | 113 | del _escape 114 | 115 | # 116 | # the following functions assume an ascii-compatible encoding 117 | # (or "utf-16") 118 | 119 | def escape_cdata(s, encoding=None, replace=string.replace): 120 | s = replace(s, "&", "&") 121 | s = replace(s, "<", "<") 122 | s = replace(s, ">", ">") 123 | if encoding: 124 | try: 125 | return encode(s, encoding) 126 | except UnicodeError: 127 | return encode_entity(s) 128 | return s 129 | 130 | def escape_attrib(s, encoding=None, replace=string.replace): 131 | s = replace(s, "&", "&") 132 | s = replace(s, "'", "'") 133 | s = replace(s, "\"", """) 134 | s = replace(s, "<", "<") 135 | s = replace(s, ">", ">") 136 | if encoding: 137 | try: 138 | return encode(s, encoding) 139 | except UnicodeError: 140 | return encode_entity(s) 141 | return s 142 | 143 | ## 144 | # XML writer class. 145 | # 146 | # @param file A file or file-like object. This object must implement 147 | # a write method that takes an 8-bit string. 148 | # @param encoding Optional encoding. 149 | 150 | class XMLWriter: 151 | 152 | def __init__(self, file, encoding="us-ascii"): 153 | if not hasattr(file, "write"): 154 | file = open(file, "w") 155 | self.__write = file.write 156 | if hasattr(file, "flush"): 157 | self.flush = file.flush 158 | self.__open = 0 # true if start tag is open 159 | self.__tags = [] 160 | self.__data = [] 161 | self.__encoding = encoding 162 | 163 | def __flush(self): 164 | # flush internal buffers 165 | if self.__open: 166 | self.__write(">") 167 | self.__open = 0 168 | if self.__data: 169 | data = string.join(self.__data, "") 170 | self.__write(escape_cdata(data, self.__encoding)) 171 | self.__data = [] 172 | 173 | ## 174 | # Writes an XML declaration. 175 | 176 | def declaration(self): 177 | encoding = self.__encoding 178 | if encoding == "us-ascii" or encoding == "utf-8": 179 | self.__write("\n") 180 | else: 181 | self.__write("\n" % encoding) 182 | 183 | ## 184 | # Opens a new element. Attributes can be given as keyword 185 | # arguments, or as a string/string dictionary. You can pass in 186 | # 8-bit strings or Unicode strings; the former are assumed to use 187 | # the encoding passed to the constructor. The method returns an 188 | # opaque identifier that can be passed to the close method, 189 | # to close all open elements up to and including this one. 190 | # 191 | # @param tag Element tag. 192 | # @param attrib Attribute dictionary. Alternatively, attributes 193 | # can be given as keyword arguments. 194 | # @return An element identifier. 195 | 196 | def start(self, tag, attrib={}, **extra): 197 | self.__flush() 198 | tag = escape_cdata(tag, self.__encoding) 199 | self.__data = [] 200 | self.__tags.append(tag) 201 | self.__write("<%s" % tag) 202 | if attrib or extra: 203 | attrib = attrib.copy() 204 | attrib.update(extra) 205 | attrib = attrib.items() 206 | attrib.sort() 207 | for k, v in attrib: 208 | k = escape_cdata(k, self.__encoding) 209 | v = escape_attrib(v, self.__encoding) 210 | self.__write(" %s=\"%s\"" % (k, v)) 211 | self.__open = 1 212 | return len(self.__tags)-1 213 | 214 | ## 215 | # Adds a comment to the output stream. 216 | # 217 | # @param comment Comment text, as an 8-bit string or Unicode string. 218 | 219 | def comment(self, comment): 220 | self.__flush() 221 | self.__write("\n" % escape_cdata(comment, self.__encoding)) 222 | 223 | ## 224 | # Adds character data to the output stream. 225 | # 226 | # @param text Character data, as an 8-bit string or Unicode string. 227 | 228 | def data(self, text): 229 | self.__data.append(text) 230 | 231 | ## 232 | # Closes the current element (opened by the most recent call to 233 | # start). 234 | # 235 | # @param tag Element tag. If given, the tag must match the start 236 | # tag. If omitted, the current element is closed. 237 | 238 | def end(self, tag=None): 239 | if tag: 240 | assert self.__tags, "unbalanced end(%s)" % tag 241 | assert escape_cdata(tag, self.__encoding) == self.__tags[-1],\ 242 | "expected end(%s), got %s" % (self.__tags[-1], tag) 243 | else: 244 | assert self.__tags, "unbalanced end()" 245 | tag = self.__tags.pop() 246 | if self.__data: 247 | self.__flush() 248 | elif self.__open: 249 | self.__open = 0 250 | self.__write(" />") 251 | return 252 | self.__write("" % tag) 253 | 254 | ## 255 | # Closes open elements, up to (and including) the element identified 256 | # by the given identifier. 257 | # 258 | # @param id Element identifier, as returned by the start method. 259 | 260 | def close(self, id): 261 | while len(self.__tags) > id: 262 | self.end() 263 | 264 | ## 265 | # Adds an entire element. This is the same as calling start, 266 | # data, and end in sequence. The text argument 267 | # can be omitted. 268 | 269 | def element(self, tag, text=None, attrib={}, **extra): 270 | apply(self.start, (tag, attrib), extra) 271 | if text: 272 | self.data(text) 273 | self.end() 274 | 275 | ## 276 | # Flushes the output stream. 277 | 278 | def flush(self): 279 | pass # replaced by the constructor 280 | -------------------------------------------------------------------------------- /xdebug/elementtree/TidyHTMLTreeBuilder.py: -------------------------------------------------------------------------------- 1 | # 2 | # ElementTree 3 | # $Id: TidyHTMLTreeBuilder.py 2304 2005-03-01 17:42:41Z fredrik $ 4 | # 5 | 6 | from elementtidy.TidyHTMLTreeBuilder import * 7 | -------------------------------------------------------------------------------- /xdebug/elementtree/TidyTools.py: -------------------------------------------------------------------------------- 1 | # 2 | # ElementTree 3 | # $Id: TidyTools.py 1862 2004-06-18 07:31:02Z Fredrik $ 4 | # 5 | # tools to run the "tidy" command on an HTML or XHTML file, and return 6 | # the contents as an XHTML element tree. 7 | # 8 | # history: 9 | # 2002-10-19 fl added to ElementTree library; added getzonebody function 10 | # 11 | # Copyright (c) 1999-2004 by Fredrik Lundh. All rights reserved. 12 | # 13 | # fredrik@pythonware.com 14 | # http://www.pythonware.com 15 | # 16 | 17 | ## 18 | # Tools to build element trees from HTML, using the external tidy 19 | # utility. 20 | ## 21 | 22 | import glob, string, os, sys 23 | 24 | from ElementTree import ElementTree, Element 25 | 26 | NS_XHTML = "{http://www.w3.org/1999/xhtml}" 27 | 28 | ## 29 | # Convert an HTML or HTML-like file to XHTML, using the tidy 30 | # command line utility. 31 | # 32 | # @param file Filename. 33 | # @param new_inline_tags An optional list of valid but non-standard 34 | # inline tags. 35 | # @return An element tree, or None if not successful. 36 | 37 | def tidy(file, new_inline_tags=None): 38 | 39 | command = ["tidy", "-qn", "-asxml"] 40 | 41 | if new_inline_tags: 42 | command.append("--new-inline-tags") 43 | command.append(string.join(new_inline_tags, ",")) 44 | 45 | # FIXME: support more tidy options! 46 | 47 | # convert 48 | os.system( 49 | "%s %s >%s.out 2>%s.err" % (string.join(command), file, file, file) 50 | ) 51 | # check that the result is valid XML 52 | try: 53 | tree = ElementTree() 54 | tree.parse(file + ".out") 55 | except: 56 | print "*** %s:%s" % sys.exc_info()[:2] 57 | print ("*** %s is not valid XML " 58 | "(check %s.err for info)" % (file, file)) 59 | tree = None 60 | else: 61 | if os.path.isfile(file + ".out"): 62 | os.remove(file + ".out") 63 | if os.path.isfile(file + ".err"): 64 | os.remove(file + ".err") 65 | 66 | return tree 67 | 68 | ## 69 | # Get document body from a an HTML or HTML-like file. This function 70 | # uses the tidy function to convert HTML to XHTML, and cleans 71 | # up the resulting XML tree. 72 | # 73 | # @param file Filename. 74 | # @return A body element, or None if not successful. 75 | 76 | def getbody(file, **options): 77 | # get clean body from text file 78 | 79 | # get xhtml tree 80 | try: 81 | tree = apply(tidy, (file,), options) 82 | if tree is None: 83 | return 84 | except IOError, v: 85 | print "***", v 86 | return None 87 | 88 | NS = NS_XHTML 89 | 90 | # remove namespace uris 91 | for node in tree.getiterator(): 92 | if node.tag.startswith(NS): 93 | node.tag = node.tag[len(NS):] 94 | 95 | body = tree.getroot().find("body") 96 | 97 | return body 98 | 99 | ## 100 | # Same as getbody, but turns plain text at the start of the 101 | # document into an H1 tag. This function can be used to parse zone 102 | # documents. 103 | # 104 | # @param file Filename. 105 | # @return A body element, or None if not successful. 106 | 107 | def getzonebody(file, **options): 108 | 109 | body = getbody(file, **options) 110 | if body is None: 111 | return 112 | 113 | if body.text and string.strip(body.text): 114 | title = Element("h1") 115 | title.text = string.strip(body.text) 116 | title.tail = "\n\n" 117 | body.insert(0, title) 118 | 119 | body.text = None 120 | 121 | return body 122 | 123 | if __name__ == "__main__": 124 | 125 | import sys 126 | for arg in sys.argv[1:]: 127 | for file in glob.glob(arg): 128 | print file, "...", tidy(file) 129 | -------------------------------------------------------------------------------- /xdebug/elementtree/XMLTreeBuilder.py: -------------------------------------------------------------------------------- 1 | # 2 | # ElementTree 3 | # $Id: XMLTreeBuilder.py 2305 2005-03-01 17:43:09Z fredrik $ 4 | # 5 | # an XML tree builder 6 | # 7 | # history: 8 | # 2001-10-20 fl created 9 | # 2002-05-01 fl added namespace support for xmllib 10 | # 2002-07-27 fl require expat (1.5.2 code can use SimpleXMLTreeBuilder) 11 | # 2002-08-17 fl use tag/attribute name memo cache 12 | # 2002-12-04 fl moved XMLTreeBuilder to the ElementTree module 13 | # 14 | # Copyright (c) 1999-2004 by Fredrik Lundh. All rights reserved. 15 | # 16 | # fredrik@pythonware.com 17 | # http://www.pythonware.com 18 | # 19 | # -------------------------------------------------------------------- 20 | # The ElementTree toolkit is 21 | # 22 | # Copyright (c) 1999-2004 by Fredrik Lundh 23 | # 24 | # By obtaining, using, and/or copying this software and/or its 25 | # associated documentation, you agree that you have read, understood, 26 | # and will comply with the following terms and conditions: 27 | # 28 | # Permission to use, copy, modify, and distribute this software and 29 | # its associated documentation for any purpose and without fee is 30 | # hereby granted, provided that the above copyright notice appears in 31 | # all copies, and that both that copyright notice and this permission 32 | # notice appear in supporting documentation, and that the name of 33 | # Secret Labs AB or the author not be used in advertising or publicity 34 | # pertaining to distribution of the software without specific, written 35 | # prior permission. 36 | # 37 | # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 38 | # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- 39 | # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR 40 | # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 41 | # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 42 | # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 43 | # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 44 | # OF THIS SOFTWARE. 45 | # -------------------------------------------------------------------- 46 | 47 | ## 48 | # Tools to build element trees from XML files. 49 | ## 50 | 51 | import ElementTree 52 | 53 | ## 54 | # (obsolete) ElementTree builder for XML source data, based on the 55 | # expat parser. 56 | #

57 | # This class is an alias for ElementTree.XMLTreeBuilder. New code 58 | # should use that version instead. 59 | # 60 | # @see elementtree.ElementTree 61 | 62 | class TreeBuilder(ElementTree.XMLTreeBuilder): 63 | pass 64 | 65 | ## 66 | # (experimental) An alternate builder that supports manipulation of 67 | # new elements. 68 | 69 | class FancyTreeBuilder(TreeBuilder): 70 | 71 | def __init__(self, html=0): 72 | TreeBuilder.__init__(self, html) 73 | self._parser.StartNamespaceDeclHandler = self._start_ns 74 | self._parser.EndNamespaceDeclHandler = self._end_ns 75 | self.namespaces = [] 76 | 77 | def _start(self, tag, attrib_in): 78 | elem = TreeBuilder._start(self, tag, attrib_in) 79 | self.start(elem) 80 | 81 | def _start_list(self, tag, attrib_in): 82 | elem = TreeBuilder._start_list(self, tag, attrib_in) 83 | self.start(elem) 84 | 85 | def _end(self, tag): 86 | elem = TreeBuilder._end(self, tag) 87 | self.end(elem) 88 | 89 | def _start_ns(self, prefix, value): 90 | self.namespaces.insert(0, (prefix, value)) 91 | 92 | def _end_ns(self, prefix): 93 | assert self.namespaces.pop(0)[0] == prefix, "implementation confused" 94 | 95 | ## 96 | # Hook method that's called when a new element has been opened. 97 | # May access the namespaces attribute. 98 | # 99 | # @param element The new element. The tag name and attributes are, 100 | # set, but it has no children, and the text and tail attributes 101 | # are still empty. 102 | 103 | def start(self, element): 104 | pass 105 | 106 | ## 107 | # Hook method that's called when a new element has been closed. 108 | # May access the namespaces attribute. 109 | # 110 | # @param element The new element. 111 | 112 | def end(self, element): 113 | pass 114 | -------------------------------------------------------------------------------- /xdebug/elementtree/__init__.py: -------------------------------------------------------------------------------- 1 | # $Id: __init__.py 1821 2004-06-03 16:57:49Z fredrik $ 2 | # elementtree package 3 | 4 | # -------------------------------------------------------------------- 5 | # The ElementTree toolkit is 6 | # 7 | # Copyright (c) 1999-2004 by Fredrik Lundh 8 | # 9 | # By obtaining, using, and/or copying this software and/or its 10 | # associated documentation, you agree that you have read, understood, 11 | # and will comply with the following terms and conditions: 12 | # 13 | # Permission to use, copy, modify, and distribute this software and 14 | # its associated documentation for any purpose and without fee is 15 | # hereby granted, provided that the above copyright notice appears in 16 | # all copies, and that both that copyright notice and this permission 17 | # notice appear in supporting documentation, and that the name of 18 | # Secret Labs AB or the author not be used in advertising or publicity 19 | # pertaining to distribution of the software without specific, written 20 | # prior permission. 21 | # 22 | # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 23 | # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- 24 | # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR 25 | # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 26 | # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 27 | # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 28 | # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 29 | # OF THIS SOFTWARE. 30 | # -------------------------------------------------------------------- 31 | -------------------------------------------------------------------------------- /xdebug/helper/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # Get python version 4 | python_version = sys.version_info[:2] 5 | 6 | 7 | # Define helper class according to python version 8 | if (python_version <= (2, 6)): 9 | # Version 2.6 and below 10 | import helper_26 as H 11 | elif (python_version == (2, 7)): 12 | # Version 2.7 13 | from . import helper_27 as H 14 | else: 15 | # Version 3+ 16 | from . import helper as H 17 | 18 | 19 | # Modules to be imported from package when using * 20 | __all__ = ['H'] 21 | -------------------------------------------------------------------------------- /xdebug/helper/helper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper module for Python version 3.0 and above 3 | - Ordered dictionaries 4 | - Encoding/decoding urls 5 | - Unicode/Bytes (for sending/receiving data from/to socket, base64) 6 | - Exception handling (except Exception as e) 7 | """ 8 | 9 | import base64 10 | from urllib.parse import unquote, quote 11 | from collections import OrderedDict 12 | 13 | 14 | def modulename(): 15 | return 'Helper module for Python version 3.0 and above' 16 | 17 | 18 | def url_decode(uri): 19 | return unquote(uri) 20 | 21 | 22 | def url_encode(uri): 23 | return quote(uri) 24 | 25 | 26 | def new_dictionary(): 27 | return OrderedDict() 28 | 29 | 30 | def dictionary_keys(dictionary): 31 | return list(dictionary.keys()) 32 | 33 | 34 | def dictionary_values(dictionary): 35 | return list(dictionary.values()) 36 | 37 | 38 | def data_read(data): 39 | # Convert bytes to string 40 | return data.decode('utf8') 41 | 42 | 43 | def data_write(data): 44 | # Convert string to bytes 45 | return bytes(data, 'utf8') 46 | 47 | 48 | def base64_decode(data): 49 | # Base64 returns decoded byte string, decode to convert to UTF8 string 50 | return base64.b64decode(data).decode('utf8') 51 | 52 | 53 | def base64_encode(data): 54 | # Base64 needs ascii input to encode, which returns Base64 byte string, decode to convert to UTF8 string 55 | return base64.b64encode(data.encode('ascii')).decode('utf8') 56 | 57 | 58 | def unicode_chr(code): 59 | return chr(code) 60 | 61 | 62 | def unicode_string(string): 63 | # Python 3.* uses unicode by default 64 | return string 65 | 66 | 67 | def is_digit(string): 68 | # Check if string is digit 69 | return isinstance(string, str) and string.isdigit() 70 | 71 | 72 | def is_number(value): 73 | return isinstance(value, int) 74 | -------------------------------------------------------------------------------- /xdebug/helper/helper_26.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper module for Python version 2.6 and below 3 | - Ordered dictionaries 4 | - Encoding/decoding urls 5 | - Unicode 6 | - Exception handling (except Exception, e) 7 | """ 8 | 9 | import base64 10 | from urllib import unquote, quote 11 | try: 12 | from ordereddict import OrderedDict 13 | except: 14 | pass 15 | 16 | 17 | def modulename(): 18 | return 'Helper module for Python version 2.6 and below' 19 | 20 | 21 | def url_decode(uri): 22 | return unquote(uri) 23 | 24 | 25 | def url_encode(uri): 26 | return quote(uri) 27 | 28 | 29 | def new_dictionary(): 30 | try: 31 | return OrderedDict() 32 | except: 33 | return {} 34 | 35 | 36 | def dictionary_keys(dictionary): 37 | return dictionary.keys() 38 | 39 | 40 | def dictionary_values(dictionary): 41 | return dictionary.values() 42 | 43 | 44 | def data_read(data): 45 | # Data for reading/receiving already a string in version 2.* 46 | return data 47 | 48 | 49 | def data_write(data): 50 | # Using string in version 2.* for sending/writing data 51 | return data 52 | 53 | 54 | def base64_decode(data): 55 | return base64.b64decode(data) 56 | 57 | 58 | def base64_encode(data): 59 | return base64.b64encode(data) 60 | 61 | 62 | def unicode_chr(code): 63 | return unichr(code) # noqa: F821 64 | 65 | 66 | def unicode_string(string): 67 | if isinstance(string, unicode): # noqa: F821 68 | return string 69 | return string.decode('utf8', 'replace') 70 | 71 | 72 | def is_digit(string): 73 | # Check if basestring (str, unicode) is digit 74 | return isinstance(string, basestring) and string.isdigit() # noqa: F821 75 | 76 | 77 | def is_number(value): 78 | return isinstance(value, (int, long)) # noqa: F821 79 | -------------------------------------------------------------------------------- /xdebug/helper/helper_27.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper module for Python version 2.7 3 | - Ordered dictionaries 4 | - Encoding/decoding urls 5 | - Unicode 6 | - Exception handling (except Exception as e) 7 | """ 8 | 9 | import base64 10 | from urllib import unquote, quote 11 | from collections import OrderedDict 12 | 13 | 14 | def modulename(): 15 | return 'Helper module for Python version 2.7' 16 | 17 | 18 | def url_decode(uri): 19 | return unquote(uri) 20 | 21 | 22 | def url_encode(uri): 23 | return quote(uri) 24 | 25 | 26 | def new_dictionary(): 27 | return OrderedDict() 28 | 29 | 30 | def dictionary_keys(dictionary): 31 | return list(dictionary.keys()) 32 | 33 | 34 | def dictionary_values(dictionary): 35 | return list(dictionary.values()) 36 | 37 | 38 | def data_read(data): 39 | # Data for reading/receiving already a string in version 2.* 40 | return data 41 | 42 | 43 | def data_write(data): 44 | # Using string in version 2.* for sending/writing data 45 | return data 46 | 47 | 48 | def base64_decode(data): 49 | return base64.b64decode(data) 50 | 51 | 52 | def base64_encode(data): 53 | return base64.b64encode(data) 54 | 55 | 56 | def unicode_chr(code): 57 | return unichr(code) # noqa: F821 58 | 59 | 60 | def unicode_string(string): 61 | if isinstance(string, unicode): # noqa: F821 62 | return string 63 | return string.decode('utf8', 'replace') 64 | 65 | 66 | def is_digit(string): 67 | # Check if basestring (str, unicode) is digit 68 | return isinstance(string, basestring) and string.isdigit() # noqa: F821 69 | 70 | 71 | def is_number(value): 72 | return isinstance(value, (int, long)) # noqa: F821 73 | -------------------------------------------------------------------------------- /xdebug/helper/ordereddict.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009 Raymond Hettinger 2 | # 3 | # Permission is hereby granted, free of charge, to any person 4 | # obtaining a copy of this software and associated documentation files 5 | # (the "Software"), to deal in the Software without restriction, 6 | # including without limitation the rights to use, copy, modify, merge, 7 | # publish, distribute, sublicense, and/or sell copies of the Software, 8 | # and to permit persons to whom the Software is furnished to do so, 9 | # subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 18 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 21 | # OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | from UserDict import DictMixin 24 | 25 | class OrderedDict(dict, DictMixin): 26 | 27 | def __init__(self, *args, **kwds): 28 | if len(args) > 1: 29 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 30 | try: 31 | self.__end 32 | except AttributeError: 33 | self.clear() 34 | self.update(*args, **kwds) 35 | 36 | def clear(self): 37 | self.__end = end = [] 38 | end += [None, end, end] # sentinel node for doubly linked list 39 | self.__map = {} # key --> [key, prev, next] 40 | dict.clear(self) 41 | 42 | def __setitem__(self, key, value): 43 | if key not in self: 44 | end = self.__end 45 | curr = end[1] 46 | curr[2] = end[1] = self.__map[key] = [key, curr, end] 47 | dict.__setitem__(self, key, value) 48 | 49 | def __delitem__(self, key): 50 | dict.__delitem__(self, key) 51 | key, prev, next = self.__map.pop(key) 52 | prev[2] = next 53 | next[1] = prev 54 | 55 | def __iter__(self): 56 | end = self.__end 57 | curr = end[2] 58 | while curr is not end: 59 | yield curr[0] 60 | curr = curr[2] 61 | 62 | def __reversed__(self): 63 | end = self.__end 64 | curr = end[1] 65 | while curr is not end: 66 | yield curr[0] 67 | curr = curr[1] 68 | 69 | def popitem(self, last=True): 70 | if not self: 71 | raise KeyError('dictionary is empty') 72 | if last: 73 | key = reversed(self).next() 74 | else: 75 | key = iter(self).next() 76 | value = self.pop(key) 77 | return key, value 78 | 79 | def __reduce__(self): 80 | items = [[k, self[k]] for k in self] 81 | tmp = self.__map, self.__end 82 | del self.__map, self.__end 83 | inst_dict = vars(self).copy() 84 | self.__map, self.__end = tmp 85 | if inst_dict: 86 | return (self.__class__, (items,), inst_dict) 87 | return self.__class__, (items,) 88 | 89 | def keys(self): 90 | return list(self) 91 | 92 | setdefault = DictMixin.setdefault 93 | update = DictMixin.update 94 | pop = DictMixin.pop 95 | values = DictMixin.values 96 | items = DictMixin.items 97 | iterkeys = DictMixin.iterkeys 98 | itervalues = DictMixin.itervalues 99 | iteritems = DictMixin.iteritems 100 | 101 | def __repr__(self): 102 | if not self: 103 | return '%s()' % (self.__class__.__name__,) 104 | return '%s(%r)' % (self.__class__.__name__, self.items()) 105 | 106 | def copy(self): 107 | return self.__class__(self) 108 | 109 | @classmethod 110 | def fromkeys(cls, iterable, value=None): 111 | d = cls() 112 | for key in iterable: 113 | d[key] = value 114 | return d 115 | 116 | def __eq__(self, other): 117 | if isinstance(other, OrderedDict): 118 | if len(self) != len(other): 119 | return False 120 | for p, q in zip(self.items(), other.items()): 121 | if p != q: 122 | return False 123 | return True 124 | return dict.__eq__(self, other) 125 | 126 | def __ne__(self, other): 127 | return not self == other 128 | -------------------------------------------------------------------------------- /xdebug/load.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | import os 4 | 5 | # Settings variables 6 | try: 7 | from . import settings as S 8 | except: 9 | import settings as S 10 | 11 | # Load modules 12 | from .view import DATA_BREAKPOINT, DATA_CONTEXT, DATA_STACK, DATA_WATCH, TITLE_WINDOW_BREAKPOINT, TITLE_WINDOW_CONTEXT, TITLE_WINDOW_STACK, TITLE_WINDOW_WATCH, has_debug_view, render_regions, show_content 13 | from .util import load_breakpoint_data, load_watch_data 14 | from .log import clear_output, debug, info 15 | from .config import get_window_value, set_window_value, load_package_values, load_project_values 16 | 17 | 18 | def xdebug(): 19 | # Clear log file 20 | clear_output() 21 | if not S.PACKAGE_FOLDER: 22 | info('Unable to resolve current path for package.') 23 | info('==== Loading "%s" package ====' % S.PACKAGE_FOLDER) 24 | 25 | # Load config in package/project configuration 26 | load_package_values() 27 | load_project_values() 28 | 29 | # Load breakpoint data 30 | try: 31 | load_breakpoint_data() 32 | finally: 33 | # Render breakpoint markers 34 | render_regions() 35 | 36 | # Load watch data 37 | load_watch_data() 38 | 39 | # Clear/Reset content in debug windows 40 | if has_debug_view(TITLE_WINDOW_BREAKPOINT): 41 | show_content(DATA_BREAKPOINT) 42 | if has_debug_view(TITLE_WINDOW_CONTEXT): 43 | show_content(DATA_CONTEXT) 44 | if has_debug_view(TITLE_WINDOW_STACK): 45 | show_content(DATA_STACK) 46 | if has_debug_view(TITLE_WINDOW_WATCH): 47 | show_content(DATA_WATCH) 48 | 49 | # Check for conflicting packages 50 | if S.PACKAGE_FOLDER: 51 | # Get package list from Package Control 52 | packages = None 53 | try: 54 | packages = sublime.load_settings('Package Control.sublime-settings').get('installed_packages', []) 55 | except: 56 | pass 57 | # Make sure it is a list 58 | if not isinstance(packages, list): 59 | packages = [] 60 | # Get packages inside Package directory 61 | for package_name in os.listdir(sublime.packages_path()): 62 | if package_name not in packages: 63 | packages.append(package_name) 64 | # Strip .sublime-package of package name for comparison 65 | package_extension = '.sublime-package' 66 | current_package = S.PACKAGE_FOLDER 67 | if current_package.endswith(package_extension): 68 | current_package = current_package[:-len(package_extension)] 69 | # Search for other conflicting packages 70 | conflict = [] 71 | for package in packages: 72 | if package.endswith(package_extension): 73 | package = package[:-len(package_extension)] 74 | if (package.lower().count('xdebug') or package.lower().count('moai')) and package != current_package: 75 | conflict.append(package) 76 | # Show message if conflicting packages have been found 77 | if conflict: 78 | info('Conflicting packages detected.') 79 | debug(conflict) 80 | if not get_window_value('hide_conflict', False): 81 | sublime.error_message('The following package(s) could cause conflicts with "{package}":\n\n{other}\n\nPlease consider removing the package(s) above when experiencing any complications.' 82 | .format(package=S.PACKAGE_FOLDER, other='\n'.join(conflict))) 83 | set_window_value('hide_conflict', True) 84 | else: 85 | set_window_value('hide_conflict', False) 86 | -------------------------------------------------------------------------------- /xdebug/log.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | import logging 4 | import os 5 | 6 | # Settings variables 7 | try: 8 | from . import settings as S 9 | except: 10 | import settings as S 11 | 12 | # Config module 13 | from .config import get_value 14 | 15 | 16 | def clear_output(): 17 | # Clear previous output file and configure logging module 18 | output_file = os.path.join(sublime.packages_path(), 'User', S.FILE_LOG_OUTPUT) 19 | logging.basicConfig(filename=output_file, filemode='w', level=logging.DEBUG, format='[%(asctime)s] %(levelname)s - %(message)s', datefmt='%m/%d/%Y %I:%M:%S%p') 20 | 21 | 22 | def debug(message=None): 23 | if not get_value(S.KEY_DEBUG) or message is None: 24 | return 25 | # Write message to output file 26 | logging.debug(message) 27 | 28 | 29 | def info(message=None): 30 | if message is None: 31 | return 32 | # Write message to output file 33 | logging.info(message) 34 | -------------------------------------------------------------------------------- /xdebug/protocol.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import re 3 | import socket 4 | import sys 5 | 6 | # Helper module 7 | try: 8 | from .helper import H 9 | except: 10 | from helper import H 11 | 12 | # Settings variables 13 | try: 14 | from . import settings as S 15 | except: 16 | import settings as S 17 | 18 | # Config module 19 | from .config import get_value 20 | 21 | # Log module 22 | from .log import debug 23 | 24 | # HTML entities 25 | try: 26 | from html.entities import name2codepoint 27 | except ImportError: 28 | from htmlentitydefs import name2codepoint 29 | 30 | # XML parser 31 | try: 32 | from xml.etree import cElementTree as ET 33 | except ImportError: 34 | try: 35 | from xml.etree import ElementTree as ET 36 | except ImportError: 37 | from .elementtree import ElementTree as ET 38 | try: 39 | from xml.parsers import expat # noqa: F401 40 | UNESCAPE_RESPONSE_DATA = True 41 | except ImportError: 42 | # Module xml.parsers.expat missing, using SimpleXMLTreeBuilder 43 | from .elementtree import SimpleXMLTreeBuilder 44 | ET.XMLTreeBuilder = SimpleXMLTreeBuilder.TreeBuilder 45 | UNESCAPE_RESPONSE_DATA = False 46 | 47 | 48 | ILLEGAL_XML_UNICODE_CHARACTERS = [ 49 | (0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84), 50 | (0x86, 0x9F), (0xD800, 0xDFFF), (0xFDD0, 0xFDDF), 51 | (0xFFFE, 0xFFFF), 52 | (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF), 53 | (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF), 54 | (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF), 55 | (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF), 56 | (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF), 57 | (0x10FFFE, 0x10FFFF)] 58 | 59 | ILLEGAL_XML_RANGES = [ 60 | '%s-%s' % (H.unicode_chr(low), H.unicode_chr(high)) 61 | for (low, high) in ILLEGAL_XML_UNICODE_CHARACTERS 62 | if low < sys.maxunicode 63 | ] 64 | 65 | ILLEGAL_XML_RE = re.compile(H.unicode_string('[%s]') % H.unicode_string('').join(ILLEGAL_XML_RANGES)) 66 | 67 | 68 | class Protocol(object): 69 | """ 70 | Class for connecting with debugger engine which uses DBGp protocol. 71 | """ 72 | 73 | # Maximum amount of data to be received at once by socket 74 | read_size = 1024 75 | 76 | def __init__(self): 77 | # Set host address to listen for response 78 | self.host = get_value(S.KEY_HOST, S.DEFAULT_HOST) 79 | # Set port number to listen for response 80 | self.port = get_value(S.KEY_PORT, S.DEFAULT_PORT) 81 | self.clear() 82 | 83 | def transaction_id(): 84 | """ 85 | Standard argument for sending commands, an unique numerical ID. 86 | """ 87 | def fget(self): 88 | self._transaction_id += 1 89 | return self._transaction_id 90 | 91 | def fset(self, value): 92 | self._transaction_id = value 93 | 94 | def fdel(self): 95 | self._transaction_id = 0 96 | return locals() 97 | 98 | # Transaction ID property 99 | transaction_id = property(**transaction_id()) 100 | 101 | def clear(self): 102 | """ 103 | Clear variables, reset transaction_id, close socket connection. 104 | """ 105 | self.buffer = '' 106 | self.connected = False 107 | self.listening = False 108 | del self.transaction_id 109 | try: 110 | self.socket.close() 111 | except: 112 | pass 113 | self.socket = None 114 | 115 | def unescape(self, string): 116 | """ 117 | Convert HTML entities and character references to ordinary characters. 118 | """ 119 | def convert(matches): 120 | text = matches.group(0) 121 | # Character reference 122 | if text[:2] == '&#': 123 | try: 124 | if text[:3] == '&#x': 125 | return H.unicode_chr(int(text[3:-1], 16)) 126 | else: 127 | return H.unicode_chr(int(text[2:-1])) 128 | except ValueError: 129 | pass 130 | # Named entity 131 | else: 132 | try: 133 | # Following are not needed to be converted for XML 134 | if text[1:-1] in ('amp', 'apos', 'gt', 'lt', 'quot'): 135 | pass 136 | else: 137 | text = H.unicode_chr(name2codepoint[text[1:-1]]) 138 | except KeyError: 139 | pass 140 | return text 141 | return re.sub(r'&#?\w+;', convert, string) 142 | 143 | def read_until_null(self): 144 | """ 145 | Get response data from debugger engine. 146 | """ 147 | # Check socket connection 148 | if self.connected: 149 | # Get result data from debugger engine 150 | try: 151 | while '\x00' not in self.buffer: 152 | self.buffer += H.data_read(self.socket.recv(self.read_size)) 153 | data, self.buffer = self.buffer.split('\x00', 1) 154 | return data 155 | except: 156 | e = sys.exc_info()[1] 157 | raise ProtocolConnectionException(e) 158 | else: 159 | raise ProtocolConnectionException('Xdebug is not connected') 160 | 161 | def read_data(self): 162 | """ 163 | Get response data from debugger engine and verify length of response. 164 | """ 165 | # Verify length of response data 166 | length = self.read_until_null() 167 | message = self.read_until_null() 168 | if int(length) == len(message): 169 | return message 170 | else: 171 | raise ProtocolException('Length mismatch encountered while reading the Xdebug message') 172 | 173 | def read(self, return_string=False): 174 | """ 175 | Get response from debugger engine as XML document object. 176 | """ 177 | # Get result data from debugger engine and verify length of response 178 | data = self.read_data() 179 | 180 | # Show debug output 181 | debug('[Response data] %s' % data) 182 | 183 | # Return data string 184 | if return_string: 185 | return data 186 | 187 | # Remove special character quoting 188 | if UNESCAPE_RESPONSE_DATA: 189 | data = self.unescape(data) 190 | 191 | # Replace invalid XML characters 192 | data = ILLEGAL_XML_RE.sub('?', data) 193 | 194 | # Create XML document object 195 | document = ET.fromstring(data) 196 | return document 197 | 198 | def send(self, command, *args, **kwargs): 199 | """ 200 | Send command to the debugger engine according to DBGp protocol. 201 | """ 202 | # Expression is used for conditional and watch type breakpoints 203 | expression = None 204 | 205 | # Separate 'expression' from kwargs 206 | if 'expression' in kwargs: 207 | expression = kwargs['expression'] 208 | del kwargs['expression'] 209 | 210 | # Generate unique Transaction ID 211 | transaction_id = self.transaction_id 212 | 213 | # Append command/arguments to build list 214 | build_command = [command, '-i %i' % transaction_id] 215 | if args: 216 | build_command.extend(args) 217 | if kwargs: 218 | build_command.extend(['-%s %s' % pair for pair in kwargs.items()]) 219 | 220 | # Remove leading/trailing spaces and build command string 221 | build_command = [part.strip() for part in build_command if part.strip()] 222 | command = ' '.join(build_command) 223 | if expression: 224 | command += ' -- ' + H.base64_encode(expression) 225 | 226 | # Show debug output 227 | debug('[Send command] %s' % command) 228 | 229 | # Send command to debugger engine 230 | try: 231 | self.socket.send(H.data_write(command + '\x00')) 232 | except: 233 | e = sys.exc_info()[1] 234 | raise ProtocolConnectionException(e) 235 | 236 | def listen(self): 237 | """ 238 | Create socket server which listens for connection on configured port. 239 | """ 240 | # Create socket server 241 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 242 | 243 | if server: 244 | # Configure socket server 245 | try: 246 | server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 247 | server.settimeout(1) 248 | server.bind((self.host, self.port)) 249 | server.listen(1) 250 | self.listening = True 251 | self.socket = None 252 | except: 253 | e = sys.exc_info()[1] 254 | debug('Failed to create socket: %s' % e) 255 | # Substitute exception with readable (custom) message 256 | if isinstance(e, TypeError) and not H.is_number(self.port): 257 | e = 'Configured port is not a valid integer.' 258 | elif isinstance(e, socket.gaierror) and self.host != '': 259 | e = 'Hostname (%s) is not specified in hosts file or is an IPv6 address.' % self.host 260 | elif hasattr(e, 'errno'): 261 | address_or_port = 'address (%s:%d)' % (self.host, self.port) if self.host != '' else 'port (%d)' % self.port 262 | if e.errno == errno.EADDRINUSE: 263 | e = 'Another application is already listening on configured %s.' % address_or_port 264 | elif e.errno == errno.EADDRNOTAVAIL: 265 | e = 'Configured %s is not accessible.' % address_or_port 266 | raise ProtocolListenException(e) 267 | 268 | # Accept incoming connection on configured port 269 | while self.listening: 270 | try: 271 | self.socket, address = server.accept() 272 | self.listening = False 273 | except socket.timeout: 274 | pass 275 | 276 | # Check if a connection has been made 277 | if self.socket: 278 | self.connected = True 279 | self.socket.settimeout(None) 280 | else: 281 | self.connected = False 282 | self.listening = False 283 | 284 | # Close socket server 285 | try: 286 | server.close() 287 | except: 288 | pass 289 | server = None 290 | 291 | # Return socket connection 292 | return self.socket 293 | else: 294 | raise ProtocolListenException('Could not create socket server.') 295 | 296 | 297 | class ProtocolException(Exception): 298 | pass 299 | 300 | 301 | class ProtocolConnectionException(ProtocolException): 302 | pass 303 | 304 | 305 | class ProtocolListenException(ProtocolException): 306 | pass 307 | -------------------------------------------------------------------------------- /xdebug/session.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | import sys 4 | import threading 5 | 6 | # Helper module 7 | try: 8 | from .helper import H 9 | except: 10 | from helper import H 11 | 12 | # Settings variables 13 | try: 14 | from . import settings as S 15 | except: 16 | import settings as S 17 | 18 | # DBGp protocol constants 19 | try: 20 | from . import dbgp 21 | except: 22 | import dbgp 23 | 24 | # Config module 25 | from .config import get_value 26 | 27 | # Log module 28 | from .log import debug, info 29 | 30 | # Protocol module 31 | from .protocol import ProtocolConnectionException 32 | 33 | # Util module 34 | from .util import get_real_path 35 | 36 | # View module 37 | from .view import DATA_CONTEXT, DATA_STACK, DATA_WATCH, TITLE_WINDOW_WATCH, generate_context_output, generate_stack_output, get_response_properties, has_debug_view, render_regions, show_content, show_file, show_panel_content 38 | 39 | 40 | ACTION_EVALUATE = 'action_evaluate' 41 | ACTION_EXECUTE = 'action_execute' 42 | ACTION_INIT = 'action_init' 43 | ACTION_REMOVE_BREAKPOINT = 'action_remove_breakpoint' 44 | ACTION_SET_BREAKPOINT = 'action_set_breakpoint' 45 | ACTION_STATUS = 'action_status' 46 | ACTION_USER_EXECUTE = 'action_user_execute' 47 | ACTION_WATCH = 'action_watch' 48 | 49 | 50 | def is_connected(show_status=False): 51 | """ 52 | Check if client is connected to debugger engine. 53 | 54 | Keyword arguments: 55 | show_status -- Show message why client is not connected in status bar. 56 | """ 57 | if S.SESSION and S.SESSION.connected: 58 | return True 59 | elif S.SESSION and show_status: 60 | sublime.status_message('Xdebug: Waiting for response from debugger engine.') 61 | elif show_status: 62 | sublime.status_message('Xdebug: No Xdebug session running.') 63 | return False 64 | 65 | 66 | def connection_error(message): 67 | """ 68 | Template for showing error message on connection error/loss. 69 | 70 | Keyword arguments: 71 | message -- Exception/reason of connection error/loss. 72 | """ 73 | sublime.error_message('Please restart Xdebug debugging session.\nDisconnected from Xdebug debugger engine.\n' + message) 74 | info('Connection lost with debugger engine.') 75 | debug(message) 76 | # Reset connection 77 | try: 78 | S.SESSION.clear() 79 | except: 80 | pass 81 | finally: 82 | S.SESSION = None 83 | S.SESSION_BUSY = False 84 | S.BREAKPOINT_EXCEPTION = None 85 | S.BREAKPOINT_ROW = None 86 | S.BREAKPOINT_RUN = None 87 | S.CONTEXT_DATA.clear() 88 | async_session = SocketHandler(ACTION_WATCH) 89 | async_session.start() 90 | # Reset layout 91 | sublime.active_window().run_command('xdebug_layout') 92 | # Render breakpoint markers 93 | render_regions() 94 | 95 | 96 | class SocketHandler(threading.Thread): 97 | def __init__(self, action, **options): 98 | threading.Thread.__init__(self) 99 | self.action = action 100 | self.options = options 101 | 102 | def get_option(self, option, default_value=None): 103 | if option in self.options.keys(): 104 | return self.options[option] 105 | return default_value 106 | 107 | def run_command(self, command, args=None): 108 | if not isinstance(args, dict): 109 | args = {} 110 | self.timeout(lambda: self._run_command(command, args)) 111 | 112 | def _run_command(self, command, args=None): 113 | try: 114 | sublime.active_window().run_command(command, args) 115 | except: 116 | # In case active_window() is not available 117 | pass 118 | 119 | def run_view_command(self, command, args=None): 120 | if not isinstance(args, dict): 121 | args = {} 122 | self.timeout(lambda: self._run_view_command) 123 | 124 | def _run_view_command(self, command, args=None): 125 | try: 126 | sublime.active_window().active_view().run_command(command, args) 127 | except: 128 | # In case there is no active_view() available 129 | pass 130 | 131 | def status_message(self, message): 132 | sublime.set_timeout(lambda: sublime.status_message(message), 100) 133 | 134 | def timeout(self, function): 135 | sublime.set_timeout(function, 0) 136 | 137 | def run(self): 138 | # Make sure an action is defined 139 | if not self.action: 140 | return 141 | try: 142 | S.SESSION_BUSY = True 143 | # Evaluate 144 | if self.action == ACTION_EVALUATE: 145 | self.evaluate(self.get_option('expression')) 146 | # Execute 147 | elif self.action == ACTION_EXECUTE: 148 | self.execute(self.get_option('command')) 149 | # Init 150 | elif self.action == ACTION_INIT: 151 | self.init() 152 | # Remove breakpoint 153 | elif self.action == ACTION_REMOVE_BREAKPOINT: 154 | self.remove_breakpoint(self.get_option('breakpoint_id')) 155 | # Set breakpoint 156 | elif self.action == ACTION_SET_BREAKPOINT: 157 | self.set_breakpoint(self.get_option('filename'), self.get_option('lineno'), self.get_option('expression')) 158 | # Status 159 | elif self.action == ACTION_STATUS: 160 | self.status() 161 | # User defined execute 162 | elif self.action == ACTION_USER_EXECUTE: 163 | self.user_execute(self.get_option('command'), self.get_option('args')) 164 | # Watch expression 165 | elif self.action == ACTION_WATCH: 166 | self.watch_expression() 167 | # Show dialog on connection error 168 | except ProtocolConnectionException: 169 | e = sys.exc_info()[1] 170 | self.timeout(lambda: connection_error('%s' % e)) 171 | finally: 172 | S.SESSION_BUSY = False 173 | 174 | def evaluate(self, expression): 175 | if not expression or not is_connected(): 176 | return 177 | # Send 'eval' command to debugger engine with code to evaluate 178 | S.SESSION.send(dbgp.EVAL, expression=expression) 179 | if get_value(S.KEY_PRETTY_OUTPUT): 180 | response = S.SESSION.read() 181 | properties = get_response_properties(response, expression) 182 | response = generate_context_output(properties) 183 | else: 184 | response = S.SESSION.read(return_string=True) 185 | 186 | # Show response data in output panel 187 | self.timeout(lambda: show_panel_content(response)) 188 | 189 | def execute(self, command): 190 | # Do not execute if no command is set 191 | if not command or not is_connected(): 192 | return 193 | 194 | # Send command to debugger engine 195 | S.SESSION.send(command) 196 | response = S.SESSION.read() 197 | 198 | # Reset previous breakpoint values 199 | S.BREAKPOINT_EXCEPTION = None 200 | S.BREAKPOINT_ROW = None 201 | S.CONTEXT_DATA.clear() 202 | self.watch_expression() 203 | # Set debug layout 204 | self.run_command('xdebug_layout') 205 | 206 | # Handle breakpoint hit 207 | for child in response: 208 | if child.tag == dbgp.ELEMENT_BREAKPOINT or child.tag == dbgp.ELEMENT_PATH_BREAKPOINT or child.tag == dbgp.ELEMENT_PATH_SECURE_BREAKPOINT: 209 | # Get breakpoint attribute values 210 | fileuri = child.get(dbgp.BREAKPOINT_FILENAME) 211 | lineno = child.get(dbgp.BREAKPOINT_LINENO) 212 | exception = child.get(dbgp.BREAKPOINT_EXCEPTION) 213 | filename = get_real_path(fileuri) 214 | if (exception): 215 | info(exception + ': ' + child.text) 216 | # Remember Exception name and first line of message 217 | S.BREAKPOINT_EXCEPTION = {'name': exception, 'message': child.text.split('\n')[0], 'filename': fileuri, 'lineno': lineno} 218 | 219 | # Check if temporary breakpoint is set and hit 220 | if S.BREAKPOINT_RUN is not None and S.BREAKPOINT_RUN['filename'] == filename and S.BREAKPOINT_RUN['lineno'] == lineno: 221 | # Remove temporary breakpoint 222 | if S.BREAKPOINT_RUN['filename'] in S.BREAKPOINT and S.BREAKPOINT_RUN['lineno'] in S.BREAKPOINT[S.BREAKPOINT_RUN['filename']]: 223 | self.run_view_command('xdebug_breakpoint', {'rows': [S.BREAKPOINT_RUN['lineno']], 'filename': S.BREAKPOINT_RUN['filename']}) 224 | S.BREAKPOINT_RUN = None 225 | # Skip if temporary breakpoint was not hit 226 | if S.BREAKPOINT_RUN is not None and (S.BREAKPOINT_RUN['filename'] != filename or S.BREAKPOINT_RUN['lineno'] != lineno): 227 | self.run_command('xdebug_execute', {'command': 'run'}) 228 | return 229 | # Show debug/status output 230 | self.status_message('Xdebug: Breakpoint') 231 | info('Break: ' + filename + ':' + lineno) 232 | # Store line number of breakpoint for displaying region marker 233 | S.BREAKPOINT_ROW = {'filename': filename, 'lineno': lineno} 234 | # Focus/Open file window view 235 | self.timeout(lambda: show_file(filename, lineno)) 236 | 237 | # On breakpoint get context variables and stack history 238 | if response.get(dbgp.ATTRIBUTE_STATUS) == dbgp.STATUS_BREAK: 239 | # Context variables 240 | context = self.get_context_values() 241 | self.timeout(lambda: show_content(DATA_CONTEXT, context)) 242 | 243 | # Stack history 244 | stack = self.get_stack_values() 245 | self.timeout(lambda: show_content(DATA_STACK, stack)) 246 | 247 | # Watch expressions 248 | self.watch_expression() 249 | 250 | # Reload session when session stopped, by reaching end of file or interruption 251 | if response.get(dbgp.ATTRIBUTE_STATUS) == dbgp.STATUS_STOPPING or response.get(dbgp.ATTRIBUTE_STATUS) == dbgp.STATUS_STOPPED: 252 | self.run_command('xdebug_session_stop', {'restart': True}) 253 | self.run_command('xdebug_session_start', {'restart': True}) 254 | self.status_message('Xdebug: Finished executing file on server. Reload page to continue debugging.') 255 | 256 | # Render breakpoint markers 257 | self.timeout(lambda: render_regions()) 258 | 259 | def get_context_values(self): 260 | """ 261 | Get variables in current context. 262 | """ 263 | if not is_connected(): 264 | return 265 | 266 | context = H.new_dictionary() 267 | try: 268 | # Super global variables 269 | if get_value(S.KEY_SUPER_GLOBALS): 270 | S.SESSION.send(dbgp.CONTEXT_GET, c=dbgp.CONTEXT_ID_SUPERGLOBALS) 271 | response = S.SESSION.read() 272 | context.update(get_response_properties(response)) 273 | 274 | # Local variables 275 | S.SESSION.send(dbgp.CONTEXT_GET) 276 | response = S.SESSION.read() 277 | context.update(get_response_properties(response)) 278 | except ProtocolConnectionException: 279 | e = sys.exc_info()[1] 280 | self.timeout(lambda: connection_error('%s' % e)) 281 | 282 | # Store context variables in session 283 | S.CONTEXT_DATA = context 284 | 285 | return generate_context_output(context) 286 | 287 | def get_stack_values(self): 288 | """ 289 | Get stack information for current context. 290 | """ 291 | response = None 292 | if is_connected(): 293 | try: 294 | # Get stack information 295 | S.SESSION.send(dbgp.STACK_GET) 296 | response = S.SESSION.read() 297 | except ProtocolConnectionException: 298 | e = sys.exc_info()[1] 299 | self.timeout(lambda: connection_error('%s' % e)) 300 | return generate_stack_output(response) 301 | 302 | def get_watch_values(self): 303 | """ 304 | Evaluate all watch expressions in current context. 305 | """ 306 | for index, item in enumerate(S.WATCH): 307 | # Reset value for watch expression 308 | S.WATCH[index]['value'] = None 309 | 310 | # Evaluate watch expression when connected to debugger engine 311 | if is_connected(): 312 | if item['enabled']: 313 | watch_value = None 314 | try: 315 | S.SESSION.send(dbgp.EVAL, expression=item['expression']) 316 | response = S.SESSION.read() 317 | 318 | watch_value = get_response_properties(response, item['expression']) 319 | except ProtocolConnectionException: 320 | pass 321 | 322 | S.WATCH[index]['value'] = watch_value 323 | 324 | def init(self): 325 | if not is_connected(): 326 | return 327 | 328 | # Connection initialization 329 | init = S.SESSION.read() 330 | 331 | # More detailed internal information on properties 332 | S.SESSION.send(dbgp.FEATURE_SET, n='show_hidden', v=1) 333 | S.SESSION.read() 334 | 335 | # Set max children limit 336 | max_children = get_value(S.KEY_MAX_CHILDREN) 337 | if max_children is not False and max_children is not True and (H.is_number(max_children) or H.is_digit(max_children)): 338 | S.SESSION.send(dbgp.FEATURE_SET, n=dbgp.FEATURE_NAME_MAX_CHILDREN, v=max_children) 339 | S.SESSION.read() 340 | 341 | # Set max data limit 342 | max_data = get_value(S.KEY_MAX_DATA) 343 | if max_data is not False and max_data is not True and (H.is_number(max_data) or H.is_digit(max_data)): 344 | S.SESSION.send(dbgp.FEATURE_SET, n=dbgp.FEATURE_NAME_MAX_DATA, v=max_data) 345 | S.SESSION.read() 346 | 347 | # Set max depth limit 348 | max_depth = get_value(S.KEY_MAX_DEPTH) 349 | if max_depth is not False and max_depth is not True and (H.is_number(max_depth) or H.is_digit(max_depth)): 350 | S.SESSION.send(dbgp.FEATURE_SET, n=dbgp.FEATURE_NAME_MAX_DEPTH, v=max_depth) 351 | S.SESSION.read() 352 | 353 | # Set breakpoints for files 354 | for filename, breakpoint_data in S.BREAKPOINT.items(): 355 | if breakpoint_data: 356 | for lineno, bp in breakpoint_data.items(): 357 | if bp['enabled']: 358 | self.set_breakpoint(filename, lineno, bp['expression']) 359 | debug('breakpoint_set: ' + filename + ':' + lineno) 360 | 361 | # Set breakpoints for exceptions 362 | break_on_exception = get_value(S.KEY_BREAK_ON_EXCEPTION) 363 | if isinstance(break_on_exception, list): 364 | for exception_name in break_on_exception: 365 | self.set_exception(exception_name) 366 | 367 | # Determine if client should break at first line on connect 368 | if get_value(S.KEY_BREAK_ON_START): 369 | # Get init attribute values 370 | fileuri = init.get(dbgp.INIT_FILEURI) 371 | filename = get_real_path(fileuri) 372 | # Show debug/status output 373 | self.status_message('Xdebug: Break on start') 374 | info('Break on start: ' + filename) 375 | # Store line number of breakpoint for displaying region marker 376 | S.BREAKPOINT_ROW = {'filename': filename, 'lineno': 1} 377 | # Focus/Open file window view 378 | self.timeout(lambda: show_file(filename, 1)) 379 | 380 | # Context variables 381 | context = self.get_context_values() 382 | self.timeout(lambda: show_content(DATA_CONTEXT, context)) 383 | 384 | # Stack history 385 | stack = self.get_stack_values() 386 | if not stack: 387 | stack = H.unicode_string('[{level}] {filename}.{where}:{lineno}\n' 388 | .format(level=0, where='{main}', lineno=1, filename=fileuri)) 389 | self.timeout(lambda: show_content(DATA_STACK, stack)) 390 | 391 | # Watch expressions 392 | self.watch_expression() 393 | else: 394 | # Tell script to run it's process 395 | self.run_command('xdebug_execute', {'command': 'run'}) 396 | 397 | def remove_breakpoint(self, breakpoint_id): 398 | if not breakpoint_id or not is_connected(): 399 | return 400 | 401 | S.SESSION.send(dbgp.BREAKPOINT_REMOVE, d=breakpoint_id) 402 | S.SESSION.read() 403 | 404 | def set_breakpoint(self, filename, lineno, expression=None): 405 | if not filename or not lineno or not is_connected(): 406 | return 407 | 408 | # Get path of file on server 409 | fileuri = get_real_path(filename, True) 410 | # Set breakpoint 411 | S.SESSION.send(dbgp.BREAKPOINT_SET, t='line', f=fileuri, n=lineno, expression=expression) 412 | response = S.SESSION.read() 413 | # Update breakpoint id 414 | breakpoint_id = response.get(dbgp.ATTRIBUTE_BREAKPOINT_ID) 415 | if breakpoint_id: 416 | S.BREAKPOINT[filename][lineno]['id'] = breakpoint_id 417 | 418 | def set_exception(self, exception): 419 | if not is_connected(): 420 | return 421 | 422 | S.SESSION.send(dbgp.BREAKPOINT_SET, t='exception', x='"%s"' % exception) 423 | S.SESSION.read() 424 | 425 | def status(self): 426 | if not is_connected(): 427 | return 428 | 429 | # Send 'status' command to debugger engine 430 | S.SESSION.send(dbgp.STATUS) 431 | response = S.SESSION.read() 432 | # Show response in status bar 433 | self.status_message('Xdebug status: ' + response.get(dbgp.ATTRIBUTE_REASON) + ' - ' + response.get(dbgp.ATTRIBUTE_STATUS)) 434 | 435 | def user_execute(self, command, args=None): 436 | if not command or not is_connected(): 437 | return 438 | 439 | # Send command to debugger engine 440 | S.SESSION.send(command, args) 441 | response = S.SESSION.read(return_string=True) 442 | 443 | # Show response data in output panel 444 | self.timeout(lambda: show_panel_content(response)) 445 | 446 | def watch_expression(self): 447 | # Evaluate watch expressions 448 | self.get_watch_values() 449 | # Show watch expression 450 | self.timeout(lambda: self._watch_expression(self.get_option('check_watch_view', False))) 451 | 452 | def _watch_expression(self, check_watch_view): 453 | # Do not show if we only want to show content when Watch view is not available 454 | if check_watch_view and not has_debug_view(TITLE_WINDOW_WATCH): 455 | return 456 | 457 | show_content(DATA_WATCH) 458 | -------------------------------------------------------------------------------- /xdebug/settings.py: -------------------------------------------------------------------------------- 1 | DEFAULT_HOST = '' 2 | DEFAULT_PORT = 9000 3 | DEFAULT_IDE_KEY = 'sublime.xdebug' 4 | 5 | PACKAGE_PATH = None 6 | PACKAGE_FOLDER = None 7 | 8 | FILE_LOG_OUTPUT = 'Xdebug.log' 9 | FILE_BREAKPOINT_DATA = 'Xdebug.breakpoints' 10 | FILE_PACKAGE_SETTINGS = 'Xdebug.sublime-settings' 11 | FILE_WATCH_DATA = 'Xdebug.expressions' 12 | 13 | KEY_SETTINGS = 'settings' 14 | KEY_XDEBUG = 'xdebug' 15 | 16 | KEY_PATH_MAPPING = 'path_mapping' 17 | KEY_URL = 'url' 18 | KEY_IDE_KEY = 'ide_key' 19 | KEY_HOST = 'host' 20 | KEY_PORT = 'port' 21 | KEY_MAX_CHILDREN = 'max_children' 22 | KEY_MAX_DATA = 'max_data' 23 | KEY_MAX_DEPTH = 'max_depth' 24 | KEY_BREAK_ON_START = 'break_on_start' 25 | KEY_BREAK_ON_EXCEPTION = 'break_on_exception' 26 | KEY_CLOSE_ON_STOP = 'close_on_stop' 27 | KEY_SUPER_GLOBALS = 'super_globals' 28 | KEY_FULLNAME_PROPERTY = 'fullname_property' 29 | KEY_HIDE_PASSWORD = 'hide_password' 30 | KEY_PRETTY_OUTPUT = 'pretty_output' 31 | KEY_LAUNCH_BROWSER = 'launch_browser' 32 | KEY_BROWSER_NO_EXECUTE = 'browser_no_execute' 33 | KEY_DISABLE_LAYOUT = 'disable_layout' 34 | KEY_DEBUG_LAYOUT = 'debug_layout' 35 | 36 | KEY_BREAKPOINT_GROUP = 'breakpoint_group' 37 | KEY_BREAKPOINT_INDEX = 'breakpoint_index' 38 | KEY_CONTEXT_GROUP = 'context_group' 39 | KEY_CONTEXT_INDEX = 'context_index' 40 | KEY_STACK_GROUP = 'stack_group' 41 | KEY_STACK_INDEX = 'stack_index' 42 | KEY_WATCH_GROUP = 'watch_group' 43 | KEY_WATCH_INDEX = 'watch_index' 44 | 45 | KEY_BREAKPOINT_CURRENT = 'breakpoint_current' 46 | KEY_BREAKPOINT_DISABLED = 'breakpoint_disabled' 47 | KEY_BREAKPOINT_ENABLED = 'breakpoint_enabled' 48 | KEY_CURRENT_LINE = 'current_line' 49 | 50 | KEY_PYTHON_PATH = 'python_path' 51 | KEY_DEBUG = 'debug' 52 | 53 | # Region scope sources 54 | REGION_KEY_BREAKPOINT = 'xdebug_breakpoint' 55 | REGION_KEY_CURRENT = 'xdebug_current' 56 | REGION_KEY_DISABLED = 'xdebug_disabled' 57 | REGION_SCOPE_BREAKPOINT = 'comment.line.xdebug.gutter.breakpoint' 58 | REGION_SCOPE_CURRENT = 'string.quoted.xdebug.gutter.current' 59 | 60 | # Window layout for debugging output 61 | LAYOUT_DEBUG = { 62 | 'cols': [0.0, 0.5, 1.0], 63 | 'rows': [0.0, 0.7, 1.0], 64 | 'cells': [ 65 | [0, 0, 2, 1], 66 | [0, 1, 1, 2], 67 | [1, 1, 2, 2] 68 | ] 69 | } 70 | # Default single layout (similar to Alt+Shift+1) 71 | LAYOUT_NORMAL = { 72 | 'cols': [0.0, 1.0], 73 | 'rows': [0.0, 1.0], 74 | 'cells': [ 75 | [0, 0, 1, 1] 76 | ] 77 | } 78 | 79 | RESTORE_LAYOUT = None 80 | RESTORE_INDEX = None 81 | 82 | SESSION_BUSY = False 83 | 84 | SESSION = None 85 | BREAKPOINT = {} 86 | CONTEXT_DATA = {} 87 | WATCH = [] 88 | 89 | BREAKPOINT_EXCEPTION = None 90 | # Breakpoint line number in script being debugged 91 | BREAKPOINT_ROW = None 92 | # Placeholder for temporary breakpoint filename and line number 93 | BREAKPOINT_RUN = None 94 | # Will hold breakpoint line number to show for file which is being loaded 95 | SHOW_ROW_ONLOAD = {} 96 | 97 | CONFIG_PROJECT = None 98 | CONFIG_PACKAGE = None 99 | CONFIG_KEYS = [ 100 | KEY_PATH_MAPPING, 101 | KEY_URL, 102 | KEY_IDE_KEY, 103 | KEY_HOST, 104 | KEY_PORT, 105 | KEY_MAX_CHILDREN, 106 | KEY_MAX_DATA, 107 | KEY_MAX_DEPTH, 108 | KEY_BREAK_ON_START, 109 | KEY_BREAK_ON_EXCEPTION, 110 | KEY_CLOSE_ON_STOP, 111 | KEY_SUPER_GLOBALS, 112 | KEY_FULLNAME_PROPERTY, 113 | KEY_HIDE_PASSWORD, 114 | KEY_PRETTY_OUTPUT, 115 | KEY_LAUNCH_BROWSER, 116 | KEY_BROWSER_NO_EXECUTE, 117 | KEY_DISABLE_LAYOUT, 118 | KEY_DEBUG_LAYOUT, 119 | KEY_BREAKPOINT_GROUP, 120 | KEY_BREAKPOINT_INDEX, 121 | KEY_CONTEXT_GROUP, 122 | KEY_CONTEXT_INDEX, 123 | KEY_STACK_GROUP, 124 | KEY_STACK_INDEX, 125 | KEY_WATCH_GROUP, 126 | KEY_WATCH_INDEX, 127 | KEY_BREAKPOINT_CURRENT, 128 | KEY_BREAKPOINT_DISABLED, 129 | KEY_BREAKPOINT_ENABLED, 130 | KEY_CURRENT_LINE, 131 | KEY_PYTHON_PATH, 132 | KEY_DEBUG 133 | ] 134 | -------------------------------------------------------------------------------- /xdebug/unittesting.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sublime 3 | import threading 4 | from urllib import request 5 | from urllib.parse import urlencode 6 | from unittesting import DeferrableTestCase 7 | 8 | 9 | class XdebugDeferrableTestCase(DeferrableTestCase): 10 | # Using realpath in order to resolve any symbolic links otherwise path mapping will not work 11 | package_path = os.environ.get('TRAVIS_BUILD_DIR', os.path.realpath(os.path.join(sublime.packages_path(), 'SublimeTextXdebug'))) 12 | platform = os.environ.get('TRAVIS_OS_NAME', 'docker') 13 | 14 | local_path = os.path.join(package_path, 'tests', 'server', 'public') 15 | remote_path = local_path if platform == 'osx' else '/var/www/html' 16 | server_host = '127.0.0.1:8090' 17 | 18 | project_data = { 19 | 'folders': [ 20 | {'path': local_path} 21 | ], 22 | 'settings': { 23 | # Ensure Sublime Text window remains opened at all times 24 | 'close_windows_when_empty': False, 25 | # Force tabs in order to properly assert content 26 | 'translate_tabs_to_spaces': False, 27 | # Specify default settings in case of running tests locally, 28 | # to prevent failure with defined (conflicting) user settings 29 | 'xdebug': { 30 | 'path_mapping': {}, 31 | 'url': '', 32 | 'ide_key': 'sublime.xdebug', 33 | 'host': '', 34 | 'port': 9000, 35 | 'max_children': 32, 36 | 'max_data': 1024, 37 | 'max_depth': 1, 38 | 'break_on_start': False, 39 | 'break_on_exception': [ 40 | 'Fatal error', 41 | 'Catchable fatal error', 42 | 'Warning', 43 | 'Parse error', 44 | 'Notice', 45 | 'Strict standards', 46 | 'Deprecated', 47 | 'Xdebug', 48 | 'Unknown error' 49 | ], 50 | 'close_on_stop': False, 51 | 'super_globals': True, 52 | 'fullname_property': True, 53 | 'hide_password': False, 54 | 'pretty_output': False, 55 | 'launch_browser': False, 56 | 'browser_no_execute': False, 57 | 'disable_layout': False, 58 | 'debug_layout': { 59 | 'cols': [0.0, 0.5, 1.0], 60 | 'rows': [0.0, 0.7, 1.0], 61 | 'cells': [[0, 0, 2, 1], [0, 1, 1, 2], [1, 1, 2, 2]] 62 | }, 63 | 'breakpoint_group': 2, 64 | 'breakpoint_index': 1, 65 | 'context_group': 1, 66 | 'context_index': 0, 67 | 'stack_group': 2, 68 | 'stack_index': 0, 69 | 'watch_group': 1, 70 | 'watch_index': 1, 71 | 'breakpoint_enabled': 'circle', 72 | 'breakpoint_disabled': 'dot', 73 | 'breakpoint_current': '', 74 | 'current_line': 'bookmark', 75 | 'python_path': '', 76 | 'debug': False 77 | } 78 | } 79 | } 80 | 81 | # Path mapping is only required when server is on a remote machine. 82 | # Most cases test server is started in Docker (using php-server), 83 | # with the exception of macOS on Travis CI and will be running locally. 84 | if remote_path != local_path: 85 | project_data['settings']['xdebug']['path_mapping'] = { 86 | remote_path: local_path 87 | } 88 | 89 | def setUp(self): 90 | self._xdebug_settings = {} 91 | 92 | project_data = self.project_data.copy() 93 | project_data['settings']['xdebug']['_testMethodName'] = self._testMethodName 94 | sublime.active_window().set_project_data(project_data) 95 | 96 | def has_loaded_project_data(): 97 | return self.get_xdebug_setting('_testMethodName') == self._testMethodName 98 | yield has_loaded_project_data 99 | 100 | def tearDown(self): 101 | # Stop active session to prevent multiple test cases listening to port 9000 102 | self.run_command('xdebug_session_stop') 103 | # Remove any breakpoints to ensure a clean slate 104 | self.run_command('xdebug_clear_all_breakpoints') 105 | # Restore layout and close any files opened during session 106 | self.run_command('xdebug_layout', {'restore': True}) 107 | self.run_command('close_all') 108 | 109 | def assertViewContains(self, view, content): 110 | if not self.view_contains_content(view, content): 111 | title = 'View' 112 | if isinstance(view, sublime.View): 113 | title = view.name() if view.name() else view.file_name() 114 | self.fail(title + ' does not contain "' + content + '".') 115 | 116 | def assertViewIsEmpty(self, view): 117 | if not self.view_is_empty(view): 118 | title = 'View' 119 | if isinstance(view, sublime.View): 120 | title = view.name() if view.name() else view.file_name() 121 | self.fail(title + ' is not empty.') 122 | 123 | def get_contents_of_view(self, view): 124 | if view: 125 | return view.substr(sublime.Region(0, view.size())) 126 | return '' 127 | 128 | def get_view_by_title(self, title): 129 | for view in sublime.active_window().views(): 130 | if view.name() == title or view.file_name() == title: 131 | return view 132 | return None 133 | 134 | def get_xdebug_setting(self, key): 135 | settings = sublime.active_window().active_view().settings().get('xdebug') 136 | if isinstance(settings, dict) and key in settings: 137 | return settings[key] 138 | return None 139 | 140 | def run_command(self, *args, **kwargs): 141 | sublime.active_window().run_command(*args, **kwargs) 142 | 143 | def send_server_request(self, path='', params=''): 144 | if isinstance(params, dict): 145 | params = urlencode(params) 146 | query_string = '?' + params if len(params) else '' 147 | url = 'http://{host}/{path}{query_string}'.format(host=self.server_host, path=path, query_string=query_string) 148 | # Send request to server in separate thread to prevent blocking of test execution 149 | threading.Thread(target=request.urlopen, args=(url,)).start() 150 | print('Request send to {url}'.format(url=url)) 151 | 152 | def set_breakpoint(self, filename, lineno, enabled=True): 153 | self.run_command('xdebug_breakpoint', {'enabled': enabled, 'filename': filename, 'rows': [str(lineno)]}) 154 | 155 | def set_xdebug_settings(self, settings): 156 | project_data = sublime.active_window().project_data() 157 | for key, value in settings.items(): 158 | if value is not None: 159 | project_data['settings']['xdebug'][key] = value 160 | elif key in project_data['settings']['xdebug'].keys(): 161 | del project_data['settings']['xdebug'][key] 162 | # Remember any user defined settings for Xdebug plugin, 163 | # which are to be validated in 'window_has_xdebug_settings' 164 | self._xdebug_settings[key] = value 165 | sublime.active_window().set_project_data(project_data) 166 | 167 | def view_contains_content(self, view, content): 168 | view_contents = self.get_contents_of_view(view) 169 | return content in view_contents 170 | 171 | def view_is_empty(self, view): 172 | view_contents = self.get_contents_of_view(view) 173 | return not view_contents 174 | 175 | def window_has_debug_layout(self): 176 | for view in sublime.active_window().views(): 177 | # Watch view is last to initialize 178 | if view.name() == 'Xdebug Watch': 179 | return True 180 | return False 181 | 182 | def window_has_xdebug_settings(self): 183 | for key, value in self._xdebug_settings.items(): 184 | if self.get_xdebug_setting(key) != value: 185 | return False 186 | return True 187 | -------------------------------------------------------------------------------- /xdebug/util.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | import json 4 | import os 5 | import re 6 | import sys 7 | import webbrowser 8 | 9 | # Helper module 10 | try: 11 | from .helper import H 12 | except: 13 | from helper import H 14 | 15 | # Settings variables 16 | try: 17 | from . import settings as S 18 | except: 19 | import settings as S 20 | 21 | # Config module 22 | from .config import get_value 23 | 24 | # Log module 25 | from .log import debug, info 26 | 27 | 28 | def get_real_path(uri, server=False): 29 | """ 30 | Get real path 31 | 32 | Keyword arguments: 33 | uri -- Uri of file that needs to be mapped and located 34 | server -- Map local path to server path 35 | 36 | TODO: Fix mapping for root (/) and drive letters (P:/) 37 | """ 38 | if uri is None: 39 | return uri 40 | 41 | # URLdecode uri 42 | uri = H.url_decode(uri) 43 | 44 | # Split scheme from uri to get absolute path 45 | try: 46 | # scheme:///path/file => scheme, /path/file 47 | # scheme:///C:/path/file => scheme, C:/path/file 48 | transport, filename = uri.split(':///', 1) 49 | except: 50 | filename = uri 51 | 52 | # Normalize path for comparison and remove duplicate/trailing slashes 53 | uri = os.path.normpath(filename) 54 | 55 | # Pattern for checking if uri is a windows path 56 | drive_pattern = re.compile(r'^[a-zA-Z]:[\\/]') 57 | 58 | # Append leading slash if filesystem is not Windows 59 | if not drive_pattern.match(uri) and not os.path.isabs(uri): 60 | uri = os.path.normpath('/' + uri) 61 | 62 | path_mapping = get_value(S.KEY_PATH_MAPPING) 63 | if isinstance(path_mapping, dict): 64 | # Go through path mappings 65 | for server_path, local_path in path_mapping.items(): 66 | server_path = os.path.normpath(server_path) 67 | local_path = os.path.normpath(local_path) 68 | # Replace path if mapping available 69 | if server: 70 | # Map local path to server path 71 | if local_path in uri: 72 | uri = uri.replace(local_path, server_path) 73 | break 74 | else: 75 | # Map server path to local path 76 | if server_path in uri: 77 | uri = uri.replace(server_path, local_path) 78 | break 79 | else: 80 | sublime.set_timeout(lambda: sublime.status_message('Xdebug: No path mapping defined, returning given path.'), 100) 81 | 82 | # Replace slashes 83 | if not drive_pattern.match(uri): 84 | uri = uri.replace('\\', '/') 85 | 86 | # Append scheme 87 | if server: 88 | return H.url_encode('file://' + uri) 89 | 90 | return uri 91 | 92 | 93 | def get_region_icon(icon): 94 | # Default icons for color schemes from default theme 95 | default_current = 'bookmark' 96 | default_disabled = 'dot' 97 | default_enabled = 'circle' 98 | 99 | # Package icons (without .png extension) 100 | package_breakpoint_current = 'breakpoint_current' 101 | package_breakpoint_disabled = 'breakpoint_disabled' 102 | package_breakpoint_enabled = 'breakpoint_enabled' 103 | package_current_line = 'current_line' 104 | 105 | # List to check for duplicate icon entries 106 | icon_list = [default_current, default_disabled, default_enabled] 107 | 108 | # Determine icon path 109 | icon_path = None 110 | if S.PACKAGE_FOLDER is not None: 111 | # Strip .sublime-package of package name for comparison 112 | package_extension = '.sublime-package' 113 | current_package = S.PACKAGE_FOLDER 114 | if current_package.endswith(package_extension): 115 | current_package = current_package[:-len(package_extension)] 116 | if sublime.version() == '' or int(sublime.version()) > 3000: 117 | # ST3: Packages/Xdebug Client/icons/breakpoint_enabled.png 118 | icon_path = 'Packages/' + current_package + '/icons/{0}.png' 119 | else: 120 | # ST2: ../Xdebug Client/icons/breakpoint_enabled 121 | icon_path = '../' + current_package + '/icons/{0}' 122 | # Append icon path to package icons 123 | package_breakpoint_current = icon_path.format(package_breakpoint_current) 124 | package_breakpoint_disabled = icon_path.format(package_breakpoint_disabled) 125 | package_breakpoint_enabled = icon_path.format(package_breakpoint_enabled) 126 | package_current_line = icon_path.format(package_current_line) 127 | # Add to duplicate list 128 | icon_list.append(icon_path.format(package_breakpoint_current)) 129 | icon_list.append(icon_path.format(package_breakpoint_disabled)) 130 | icon_list.append(icon_path.format(package_breakpoint_enabled)) 131 | icon_list.append(icon_path.format(package_current_line)) 132 | 133 | # Get user defined icons from settings 134 | breakpoint_current = get_value(S.KEY_BREAKPOINT_CURRENT) 135 | breakpoint_disabled = get_value(S.KEY_BREAKPOINT_DISABLED) 136 | breakpoint_enabled = get_value(S.KEY_BREAKPOINT_ENABLED) 137 | current_line = get_value(S.KEY_CURRENT_LINE) 138 | 139 | # Duplicate check, enabled breakpoint 140 | if breakpoint_enabled not in icon_list: 141 | icon_list.append(breakpoint_enabled) 142 | else: 143 | breakpoint_enabled = None 144 | # Duplicate check, disabled breakpoint 145 | if breakpoint_disabled not in icon_list: 146 | icon_list.append(breakpoint_disabled) 147 | else: 148 | breakpoint_disabled = None 149 | # Duplicate check, current line 150 | if current_line not in icon_list: 151 | icon_list.append(current_line) 152 | else: 153 | current_line = None 154 | # Duplicate check, current breakpoint 155 | if breakpoint_current not in icon_list: 156 | icon_list.append(breakpoint_current) 157 | else: 158 | breakpoint_current = None 159 | 160 | # Use default/package icon if no user defined or duplicate detected 161 | if not breakpoint_current and icon_path is not None: 162 | breakpoint_current = package_breakpoint_current 163 | if not breakpoint_disabled: 164 | breakpoint_disabled = default_disabled if icon_path is None else package_breakpoint_disabled 165 | if not breakpoint_enabled: 166 | breakpoint_enabled = default_enabled if icon_path is None else package_breakpoint_enabled 167 | if not current_line: 168 | current_line = default_current if icon_path is None else package_current_line 169 | 170 | # Return icon for icon name 171 | if icon == S.KEY_CURRENT_LINE: 172 | return current_line 173 | elif icon == S.KEY_BREAKPOINT_CURRENT: 174 | return breakpoint_current 175 | elif icon == S.KEY_BREAKPOINT_DISABLED: 176 | return breakpoint_disabled 177 | elif icon == S.KEY_BREAKPOINT_ENABLED: 178 | return breakpoint_enabled 179 | else: 180 | info('Invalid icon name. (' + icon + ')') 181 | return 182 | 183 | 184 | def launch_browser(): 185 | url = get_value(S.KEY_URL) 186 | if not url: 187 | sublime.set_timeout(lambda: sublime.status_message('Xdebug: No URL defined in (project) settings file.'), 100) 188 | return 189 | ide_key = get_value(S.KEY_IDE_KEY, S.DEFAULT_IDE_KEY) 190 | operator = '?' 191 | 192 | # Check if url already has query string 193 | if url.count('?'): 194 | operator = '&' 195 | 196 | # Start debug session 197 | if S.SESSION and (S.SESSION.listening or not S.SESSION.connected): 198 | webbrowser.open(url + operator + 'XDEBUG_SESSION_START=' + ide_key) 199 | # Stop debug session 200 | else: 201 | # Check if we should execute script 202 | if get_value(S.KEY_BROWSER_NO_EXECUTE): 203 | # Without executing script 204 | webbrowser.open(url + operator + 'XDEBUG_SESSION_STOP_NO_EXEC=' + ide_key) 205 | else: 206 | # Run script normally 207 | webbrowser.open(url + operator + 'XDEBUG_SESSION_STOP=' + ide_key) 208 | 209 | 210 | def load_breakpoint_data(): 211 | data_path = os.path.join(sublime.packages_path(), 'User', S.FILE_BREAKPOINT_DATA) 212 | data = {} 213 | 214 | if os.path.isfile(data_path): 215 | try: 216 | data_file = open(data_path, 'rb') 217 | data = json.loads(H.data_read(data_file.read())) 218 | except: 219 | info('Failed to open/parse %s.' % data_path) 220 | debug(sys.exc_info()[1]) 221 | 222 | # Do not use deleted files or entries without breakpoints 223 | if data: 224 | for filename, breakpoint_data in data.copy().items(): 225 | if not breakpoint_data or not os.path.isfile(filename): 226 | del data[filename] 227 | 228 | if not isinstance(S.BREAKPOINT, dict): 229 | S.BREAKPOINT = {} 230 | 231 | # Set breakpoint data 232 | S.BREAKPOINT.update(data) 233 | 234 | 235 | def load_watch_data(): 236 | data_path = os.path.join(sublime.packages_path(), 'User', S.FILE_WATCH_DATA) 237 | data = [] 238 | 239 | if os.path.isfile(data_path): 240 | try: 241 | data_file = open(data_path, 'rb') 242 | data = json.loads(H.data_read(data_file.read())) 243 | except: 244 | info('Failed to open/parse %s.' % data_path) 245 | debug(sys.exc_info()[1]) 246 | 247 | # Check if expression is not already defined 248 | duplicates = [] 249 | for index, entry in enumerate(data): 250 | matches = [x for x in S.WATCH if x['expression'] == entry['expression']] 251 | if matches: 252 | duplicates.append(entry) 253 | else: 254 | # Unset any previous value 255 | data[index]['value'] = None 256 | for duplicate in duplicates: 257 | data.remove(duplicate) 258 | 259 | if not isinstance(S.WATCH, list): 260 | S.WATCH = [] 261 | 262 | # Set watch data 263 | S.WATCH.extend(data) 264 | 265 | 266 | def save_breakpoint_data(): 267 | data_path = os.path.join(sublime.packages_path(), 'User', S.FILE_BREAKPOINT_DATA) 268 | with open(data_path, 'wb') as data: 269 | data.write(H.data_write(json.dumps(S.BREAKPOINT))) 270 | 271 | 272 | def save_watch_data(): 273 | data_path = os.path.join(sublime.packages_path(), 'User', S.FILE_WATCH_DATA) 274 | with open(data_path, 'wb') as data: 275 | data.write(H.data_write(json.dumps(S.WATCH))) 276 | --------------------------------------------------------------------------------