โ† Back to DevBytes

Sublime Text Debugging: Complete Guide

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:

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:

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:

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:

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:

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:

Debugger Fails to Start

If the debugger will not start at all, try these steps:

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.

๐Ÿ›  Tools from DevBytes

Inventory Tracker Pro โ€” Excel inventory system, low-stock alerts ยท $19
AI Dev Kit for Mac โ€” local AI dev environment templates ยท $9.99
KeyMapper for Mac โ€” custom keyboard shortcut toolkit ยท $7.99

โ† Back to all articles