Introduction to Sublime Text Debugging
Sublime Text is one of the most beloved code editors among developers, prized for its speed, minimalism, and extensibility. While it is not a full-fledged IDE like Visual Studio Code or IntelliJ IDEA, Sublime Text can be transformed into a powerful debugging environment with the right plugins and configurations. This tutorial will walk you through everything you need to know about debugging in Sublime Text, from basic concepts to advanced techniques.
What Is Sublime Text Debugging?
Sublime Text debugging refers to the process of identifying, analyzing, and fixing errors in your code while working within the Sublime Text editor. Because Sublime Text is fundamentally a lightweight text editor, debugging capabilities are added through community-built packages and integrations with external debugging tools. These packages allow you to set breakpoints, step through code, inspect variables, and view call stacks without leaving the editor.
Debugging in Sublime Text typically involves connecting the editor to a debugger backend such as the Python debugger (pdb), Xdebug for PHP, or the Debug Adapter Protocol (DAP) used by many modern languages. The editor acts as the frontend interface, while the actual debugging logic runs in the backend tool.
Why Debugging in Sublime Text Matters
You might wonder why you should bother setting up debugging in Sublime Text when dedicated IDEs come with debugging built in. There are several compelling reasons:
- Lightweight performance: Sublime Text consumes far fewer system resources than most IDEs, making it ideal for developers working on older machines or running multiple applications simultaneously.
- Unified workflow: If you already use Sublime Text as your primary editor, adding debugging capabilities means you do not need to switch contexts between different applications.
- Customizability: Sublime Text's plugin ecosystem allows you to tailor the debugging experience to your exact needs, without the bloat of features you will never use.
- Multi-language support: With the right packages, you can debug Python, JavaScript, PHP, Go, and more, all from the same editor.
- Speed: The near-instant startup time of Sublime Text means you can jump into debugging sessions faster than waiting for a heavy IDE to load.
Prerequisites and Setup
Installing Package Control
Before you can install any debugging packages, you need Package Control, the package manager for Sublime Text. If you do not already have it installed, open the Sublime Text console by pressing Ctrl+` (or Cmd+` on macOS) and paste the following Python code:
import urllib.request,os,hashlib;
h = '6f4c264a24d933ce70df5dedcf1dcaee' + 'ebe013ee18cced0ef93d5f746d80ef60';
pf = 'Package Control.sublime-package';
ipp = sublime.installed_packages_path();
urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));
by = urllib.request.urlopen('https://packagecontrol.io/' + pf.replace(' ', '%20')).read();
dh = hashlib.sha256(by).hexdigest();
print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else None;
open(os.path.join(ipp, pf), 'wb').write(by)
After the installation completes, restart Sublime Text. You can now access Package Control by pressing Ctrl+Shift+P (or Cmd+Shift+P on macOS) and typing "Package Control: Install Package."
Essential Debugging Packages
Several packages form the core of a debugging setup in Sublime Text. The most important ones include:
- Sublime Debugger: A modern DAP-based debugger that supports Python, JavaScript, Go, PHP, and more.
- SublimeREPL: Allows you to run interactive language shells inside Sublime Text, useful for quick debugging sessions.
- Terminus: Provides a terminal emulator within Sublime Text, helpful for running command-line debuggers.
- Python Debugger (pdb): Built into Python itself, can be integrated with Sublime Text through various packages.
Setting Up Sublime Debugger
Installation
Sublime Debugger is currently the most powerful and actively maintained debugging package for Sublime Text. To install it, follow these steps:
- Open the Command Palette with
Ctrl+Shift+P. - Type "Package Control: Install Package" and press Enter.
- Search for "Debugger" and select the package by Microsoft (the one with DAP support).
- Wait for the installation to complete.
Configuring a Debug Session
Sublime Debugger uses configuration files similar to those found in Visual Studio Code. These files define how the debugger should launch or attach to your application. Create a file named .sublime/debugger.config in your project root, or use the built-in configuration system. Here is an example configuration for a Python project:
{
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
},
{
"name": "Python: Attach to Process",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
}
},
{
"name": "Python: Flask",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app.py",
"FLASK_DEBUG": "1"
},
"args": ["run", "--no-debugger", "--no-reload"],
"jinja": true
}
]
}
Each configuration object defines a different way to start a debugging session. The name field is what you will see in the debugger UI. The type field tells the debugger which debug adapter to use. The request field determines whether the debugger should launch a new process or attach to an existing one.
Configuring JavaScript and Node.js Debugging
For JavaScript and Node.js projects, you will need a slightly different configuration. First, ensure you have Node.js installed on your system. Then create a configuration like the following:
{
"configurations": [
{
"name": "Node.js: Current File",
"type": "node",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
},
{
"name": "Node.js: Attach to Process",
"type": "node",
"request": "attach",
"port": 9229,
"address": "localhost"
},
{
"name": "Node.js: Mocha Tests",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"--inspect-brk",
"--reporter",
"spec",
"${workspaceFolder}/test/**/*.js"
]
}
]
}
Using the Debugger Interface
Starting a Debug Session
Once your configuration is in place, starting a debug session is straightforward. Open the Command Palette and type "Debugger: Open," or use the keyboard shortcut Ctrl+Shift+D (you may need to configure this in your key bindings). A debugger panel will appear at the bottom or side of your editor, showing the available configurations. Click the play button next to the configuration you want to use, and the debugger will launch your program.
Setting Breakpoints
Breakpoints are the foundation of any debugging workflow. They allow you to pause execution at a specific line of code so you can inspect the program state. To set a breakpoint in Sublime Text with the Debugger package installed, simply click in the gutter (the area to the left of the line numbers) next to the line where you want execution to pause. A red dot will appear, indicating an active breakpoint. Click the dot again to remove the breakpoint.
You can also set breakpoints using the keyboard. Add the following to your key bindings file (Preferences > Key Bindings):
[
{
"keys": ["f9"],
"command": "debugger_toggle_breakpoint"
},
{
"keys": ["ctrl+f9"],
"command": "debugger_toggle_conditional_breakpoint"
},
{
"keys": ["f5"],
"command": "debugger_continue"
},
{
"keys": ["f10"],
"command": "debugger_step_over"
},
{
"keys": ["f11"],
"command": "debugger_step_into"
},
{
"keys": ["shift+f11"],
"command": "debugger_step_out"
}
]
Conditional Breakpoints
Sometimes you only want to pause execution when a certain condition is met, such as inside a loop that runs thousands of times. Conditional breakpoints solve this problem. To set one, use the Ctrl+F9 shortcut (or whatever you configured above) and enter a condition expression. The debugger will only pause at that breakpoint when the expression evaluates to true. For example:
i == 500
This condition would cause the breakpoint to trigger only when the variable i equals 500, which is invaluable for debugging loop-related issues.
Stepping Through Code
Once execution is paused at a breakpoint, you can control how the program proceeds using the step commands:
- Continue (F5): Resumes execution until the next breakpoint is hit or the program ends.
- Step Over (F10): Executes the current line and moves to the next line. If the current line contains a function call, the entire function is executed without stepping into it.
- Step Into (F11): If the current line contains a function call, this command moves execution into the first line of that function.
- Step Out (Shift+F11): Executes the rest of the current function and pauses at the line where the function was called.
Inspecting Variables
When execution is paused, the debugger panel displays all variables in the current scope. You can expand objects and arrays to inspect their contents. To watch a specific variable throughout your debugging session, you can add it to the Watch panel. Right-click on a variable in the Variables panel and select "Add to Watch," or manually enter an expression in the Watch panel.
You can also evaluate arbitrary expressions in the debugger console. This is extremely useful for testing hypotheses about what your code is doing. For example, if you are debugging a Python script, you can type expressions like:
len(my_list)
type(my_object)
my_dict.get('missing_key', 'default_value')
[x for x in range(10) if x % 2 == 0]
Examining the Call Stack
The call stack shows the chain of function calls that led to the current point of execution. Each entry in the call stack represents a frame, and clicking on a frame navigates you to that location in the source code. This is invaluable for understanding how your program arrived at its current state, especially when debugging errors that originate deep in a call chain.
Debugging Python with pdb Integration
Using pdb Directly
Sometimes you may not want the overhead of a full debug adapter setup. Python's built-in debugger, pdb, can be used directly within Sublime Text through the Terminus package. First, install Terminus via Package Control. Then, you can insert a breakpoint directly into your Python code:
import pdb
def calculate_total(items):
total = 0
for item in items:
pdb.set_trace() # Execution will pause here
total += item['price'] * item['quantity']
return total
items = [
{'name': 'Widget', 'price': 10.99, 'quantity': 3},
{'name': 'Gadget', 'price': 24.99, 'quantity': 1},
{'name': 'Gizmo', 'price': 5.50, 'quantity': 5},
]
result = calculate_total(items)
print(f"Total: ${result:.2f}")
When you run this script in a Terminus terminal within Sublime Text, execution will pause at the pdb.set_trace() line, and you will get an interactive pdb prompt. Common pdb commands include:
n(next): Step to the next line, stepping over function calls.s(step): Step into a function call.c(continue): Continue execution until the next breakpoint.p variable_name(print): Print the value of a variable.l(list): Show the surrounding source code.w(where): Print the call stack.q(quit): Exit the debugger and terminate the program.
Using breakpoint() in Modern Python
Python 3.7 and later provide a built-in breakpoint() function that automatically invokes the default debugger. This is cleaner than importing pdb explicitly:
def process_data(data):
cleaned = []
for entry in data:
if entry is None:
breakpoint() # Investigate why entry is None
continue
cleaned.append(entry.strip().lower())
return cleaned
raw_data = [" Hello ", " World ", None, " Python "]
result = process_data(raw_data)
Debugging PHP with Xdebug
PHP developers can use Xdebug in combination with Sublime Debugger to step through PHP applications. First, install Xdebug on your system and configure it in your php.ini file:
[xdebug]
zend_extension=xdebug
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=127.0.0.1
xdebug.client_port=9003
xdebug.log=/tmp/xdebug.log
Then, create a debugger configuration for PHP:
{
"configurations": [
{
"name": "PHP: Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
}
},
{
"name": "PHP: Current Script",
"type": "php",
"request": "launch",
"program": "${file}",
"port": 9003
}
]
}
With the "Listen for Xdebug" configuration active, the debugger will wait for incoming connections from Xdebug. When you load a PHP page in your browser (with a browser extension like Xdebug Helper), the debugger will pause execution at your breakpoints.
Debugging JavaScript in the Browser
For front-end JavaScript debugging, you can use the Chrome Debug Adapter. This allows you to debug JavaScript running in Chrome directly from Sublime Text. Install the Chrome extension for remote debugging, then use this configuration:
{
"configurations": [
{
"name": "Chrome: Launch",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/src"
},
{
"name": "Chrome: Attach",
"type": "chrome",
"request": "attach",
"port": 9222,
"webRoot": "${workspaceFolder}/src"
}
]
}
Make sure Chrome is started with remote debugging enabled for the attach configuration:
google-chrome --remote-debugging-port=9222
Using SublimeREPL for Interactive Debugging
SublimeREPL provides an interactive language shell within Sublime Text, which is excellent for exploratory debugging. After installing SublimeREPL via Package Control, you can open a REPL for your language of choice through the Command Palette. For Python, you can send code from your file to the REPL for immediate evaluation.
Here is a practical workflow using SublimeREPL for debugging a Python function:
# In your Python file, select this code and send it to the REPL
def fibonacci(n):
if n <= 0:
return []
elif n == 1:
return [0]
elif n == 2:
return [0, 1]
sequence = [0, 1]
for i in range(2, n):
next_val = sequence[-1] + sequence[-2]
sequence.append(next_val)
return sequence
# Now test it interactively in the REPL
# >>> fibonacci(10)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# >>> fibonacci(0)
# []
# >>> len(fibonacci(100))
# 100
Remote Debugging
Attaching to a Remote Process
Debugging code running on a remote server is a common requirement, especially for web applications deployed in Docker containers or on remote servers. The attach mode of Sublime Debugger makes this possible. For Python, you need to set up the remote process to listen for debugger connections:
# On the remote server, install debugpy
# pip install debugpy
# Add this to your remote application
import debugpy
# Listen for incoming debugger connections
debugpy.listen(('0.0.0.0', 5678))
print("Waiting for debugger to attach...")
debugpy.wait_for_client()
# Your application code continues here
def main():
data = load_configuration()
process_records(data)
generate_report()
if __name__ == '__main__':
main()
Then, in your local Sublime Text, use an attach configuration:
{
"configurations": [
{
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"connect": {
"host": "remote-server.example.com",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app"
}
]
}
]
}
The pathMappings entry is critical: it tells the debugger how to map file paths on the remote server to your local file system so that breakpoints work correctly.
Debugging Docker Containers
For Docker-based development, you can combine remote debugging with Docker port forwarding. Here is an example docker-compose.yml that exposes a debug port:
version: '3.8'
services:
app:
build: .
ports:
- "8000:8000"
- "5678:5678"
environment:
- FLASK_ENV=development
- FLASK_APP=app.py
volumes:
- ./src:/app
command: python -m debugpy --listen 0.0.0.0:5678 --wait-for-client app.py
With this setup, you can attach your local Sublime Text debugger to localhost:5678 and debug the code running inside the container as if it were running locally.
Logging as a Debugging Strategy
While interactive debugging is powerful, sometimes logging is the most practical approach, especially for issues that only occur in production or under specific conditions. Sublime Text does not have built-in log viewing, but you can use packages like Terminality or Terminus to tail log files in real time. Here is a Python logging setup that provides rich debugging information:
import logging
import sys
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('debug.log')
]
)
logger = logging.getLogger(__name__)
def divide_numbers(a, b):
logger.debug(f"Entering divide_numbers(a={a}, b={b})")
try:
result = a / b
logger.debug(f"Result: {result}")
return result
except ZeroDivisionError as e:
logger.error(f"Division by zero: {e}", exc_info=True)
raise
finally:
logger.debug("Exiting divide_numbers")
# Test the function
divide_numbers(10, 2)
divide_numbers(10, 0)
You can then open debug.log in Sublime Text and use the built-in search and highlighting features to analyze the output. The WordHighlight package can help you highlight related log entries, and Filter Lines can show only lines matching a pattern.
Best Practices for Sublime Text Debugging
Use Project-Specific Configurations
Always store your debugger configurations in your project directory rather than in global settings. This ensures that configurations are version-controlled and shared across your team. Sublime Text project files (.sublime-project) can include debugger settings:
{
"folders": [
{
"path": "."
}
],
"settings": {
"debugger.configurations": [
{
"name": "Python: Run Tests",
"type": "python",
"request": "launch",
"module": "pytest",
"args": ["-v", "--no-header"],
"console": "integratedTerminal"
}
]
}
}
Learn Keyboard Shortcuts
Efficient debugging requires muscle memory. Invest time in learning and customizing keyboard shortcuts for the most common debugging actions. The default key bindings may conflict with other packages, so review and customize them to fit your workflow. Here is an extended set of useful bindings:
[
{
"keys": ["f5"],
"command": "debugger_continue",
"caption": "Debugger: Continue"
},
{
"keys": ["ctrl+f5"],
"command": "debugger_start",
"caption": "Debugger: Start"
},
{
"keys": ["shift+f5"],
"command": "debugger_stop",
"caption": "Debugger: Stop"
},
{
"keys": ["f9"],
"command": "debugger_toggle_breakpoint"
},
{
"keys": ["ctrl+shift+f9"],
"command": "debugger_clear_breakpoints"
},
{
"keys": ["f10"],
"command": "debugger_step_over"
},
{
"keys": ["f11"],
"command": "debugger_step_into"
},
{
"keys": ["shift+f11"],
"command": "debugger_step_out"
},
{
"keys": ["ctrl+shift+d"],
"command": "debugger_open"
}
]
Combine Logging with Interactive Debugging
The most effective debugging strategy often combines logging with interactive debugging. Use logging to narrow down where a problem occurs, then use interactive breakpoints to inspect the exact state when the problem happens. This two-pronged approach is far more efficient than relying on either method alone.
Use Conditional Breakpoints Sparingly
While conditional breakpoints are powerful, they can slow down execution significantly if the condition is evaluated on every iteration of a tight loop. If you find yourself needing a conditional breakpoint in a loop that runs millions of times, consider restructuring your code to isolate the problematic case instead.
Keep Your Debug Adapters Updated
Debug adapters are actively developed and frequently updated. Outdated adapters can cause mysterious failures or missing features. Periodically check for updates to your debug adapters through Package Control, and review the changelog for new features and bug fixes.
Leverage the Watch Panel
The Watch panel is one of the most underused features in debugging. Instead of repeatedly inspecting variables after each step, add them to the Watch panel once and monitor how they change. This is especially useful for tracking state changes across multiple function calls.
Troubleshooting Common Issues
Breakpoints Not Being Hit
If your breakpoints are not being triggered, check the following:
- Ensure the file path in your configuration matches the actual file being executed. Path mapping issues are the most common cause of non-working breakpoints.
- Verify that the debug adapter is properly installed and compatible with your language version.
- Check that the code at the breakpoint location is actually being executed. Dead code or conditional branches that are not taken will never trigger a breakpoint.
- Make sure you are not running an optimized or compiled version of your code that strips out debugging information.
Debugger Fails to Start
If the debugger will not start at all, try these steps:
- Check the Sublime Text console (
Ctrl+`) for error messages from the debugger package. - Verify that all dependencies are installed. For Python debugging, you need the
debugpypackage. For Node.js, you need Node.js installed and accessible from your PATH. - Ensure no other process is using the debug port you configured.
- Try simplifying your configuration to the bare minimum and adding complexity back incrementally.
Variable Values Not Displaying
If the Variables panel shows incomplete or missing information, it may be due to optimization settings or language-specific limitations. For Python, make sure justMyCode is set appropriately. For compiled languages, ensure debug symbols are included in your build.
Conclusion
Sublime Text may not be a full IDE out of the box, but with the right packages and configurations, it becomes a capable and efficient debugging environment. By leveraging tools like Sublime Debugger, SublimeREPL, Terminus, and language-specific debug adapters, you can set breakpoints, step through code, inspect variables, and trace call stacks without ever leaving your favorite editor. The key to success lies in understanding the configuration system, mastering keyboard shortcuts, and combining interactive debugging with strategic logging. Whether you are debugging a simple Python script, a complex PHP web application, or a containerized microservice, the techniques covered in this guide will help you find and fix bugs faster while enjoying the speed and simplicity that make Sublime Text such a popular choice among developers.