A simple Lua debugger which requires no additional dependencies.
This repository represents the "Third" Generation of the Local Lua Debugger. It consolidates years of community developments into a single, modernized codebase.
This fork merges the original work by tomblind with significant improvements from Ismoh and several other key contributors.
Key Updates:
- Infrastructure: Upgraded to TypeScript 5.x and TSTL 1.34+.
- Language Support: Full compatibility with Lua 5.5 (including 64-bit integers and new operators) while maintaining support for Lua 5.1, 5.2, 5.3, 5.4, and LuaJIT.
- Zero-Config Injection: The debugger is now globally available via
_G.lldebuggerwhen launched through VS Code. Manualrequireis no longer strictly necessary. - Bug Fixes: Resolved the critical
unpackerror in TSTL-generated code and improved Windows path handling.
Depending on your workflow, you can either let the debugger handle everything automatically or manually trigger it within your code.
Simply start your script via the VS Code Debugger. The debugger will automatically inject itself. You can check for its presence in your Lua code:
if lldebugger then
-- Debugger is active and injected
endIf you prefer manual control or need to start the debugger at a specific point in your execution, use the following snippet:
local hasModule, lld = pcall(require, "lldebugger")
if hasModule then
lld.start()
endThe following sources have been integrated into this release:
tomblind/local-lua-debugger-vscode
|-- jan6055/local-lua-debugger-vscode (+1) Merged (Lua 5.5 Support)
|-- Dobby233Liu/local-lua-debugger-vscode (+6) Errors-not-merged
|-- Ismoh/local-lua-debugger-vscode (+57) Merged
| |-- jurmerlo/local-lua-debugger-vscode (+58) Merged
| |-- eduardomezencio/local-lua-debugger-vscode (+49) Merged
| |-- Vanglant/local-lua-debugger-vscode (+43) Merged
| |-- jaymo1011/local-lua-debugger-vscode (+33) Merged
| |-- ogsadmin/local-lua-debugger-vscode (+20) Merged
| |__ Zorbn/local-lua-debugger-vscode (+19) Merged
|-- EdMUK/local-lua-debugger-vscode (+7) Merged
|-- rewqazxv/local-lua-debugger-vscode (+1) Errors-not-merged
|-- sewbacca/local-lua-debugger-vscode (+2) Merged
|-- tarantool/tarantool-lua-debugger-vscode (+8) Errors-not-merged
| |-- mivallion/tarantool-lua-debugger-vscode (+8) Errors-not-merged
| |__ tsafin/tarantool-local-lua-debugger-vscode (+3) Errors-not-merged
|__ B-head/local-lua-debugger-vscode (+16) Errors-not-mergedImportant
The author of this fork makes no claim to the original work. Instead, all contributions from previous authors are fully acknowledged. This fork is solely an attempt to consolidate various improvements and unaddressed pull requests into a functional, up-to-date codebase. You can compare the changes between the original and this fork here. The remainder of this document remains in its original state.
Forked from tomblind/local-lua-debugger-vscode to maintain the extension, until the main repo will be maintained again.
Disclaimer: I'm not the original author of this extension, I'm just trying to maintaining it.
You can compare the changes between the original and this fork here.
Beginning in version 0.3.0, projects which use sourcemaps to debug code transpiled from another language (such as TypescriptToLua), must specify the scriptFiles launch configuration option in order to use breakpoints in the original source files. This allows these to be resolved at startup instead of at runtime which allows for a significant performance increase.
- Debug Lua using stand-alone interpretor or a custom executable
- Supports Lua versions 5.1, 5.2, 5.3 and LuaJIT
- Basic debugging features (stepping, inspecting, breakpoints, etc...)
- Conditional breakpoints
- Debug coroutines as separate threads
- Basic support for source maps, such as those generated by TypescriptToLua
To debug a Lua program using a stand-alone interpreter, set lua-local.interpreter in your user or workspace settings:
Alternatively, you can set the interpreter and file to run in launch.json:
{
"configurations": [
{
"type": "lua-local",
"request": "launch",
"name": "Debug",
"program": {
"lua": "lua5.1",
"file": "main.lua"
}
}
]
}To debug using a custom Lua executable, you must set up your launch.json with the name/path of the executable and any additional arguments that may be needed.
{
"configurations": [
{
"type": "lua-local",
"request": "launch",
"name": "Debug Custom Executable",
"program": {
"command": "executable"
},
"args": [
"${workspaceFolder}"
]
}
]
}You must then manually start the debugger in your Lua code:
require("lldebugger").start()Note that the path to lldebugger will automatically be appended to the LUA_PATH environment variable, so it can be found by Lua.
- The Lua environment must support communication via either stdio or pipes (named pipes on Windows, fifos on Linux).
- Some enviroments may require command line options to support stdio communication (ex. Solar2D requires
/no-consoleflag) - Use of
io.reador other calls that require user input will cause problems in stdio mode. Setprogram.communicationtopipeto work around this.
- Some enviroments may require command line options to support stdio communication (ex. Solar2D requires
- The Lua environment must be built with the
debuglibrary, and no other code should attempt to set debug hooks. - You cannot manually pause debugging while the program is running.
- In Lua 5.1 and LuaJIT, the main thread cannot be accessed while stopped inside of a coroutine.
-
For convenience, a global reference to the debugger is always stored as
lldebugger. -
You can detect that the debugger extension is attached by inspecting the environment variable
LOCAL_LUA_DEBUGGER_VSCODE. This is useful for conditionally starting the debugger in custom environments.if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then require("lldebugger").start() end
-
Some custom environments will not break on uncaught runtime errors. To catch a runtime error, you can wrap the code with
lldebugger.call():lldebugger.call(function() --code causing runtime error end)
-
Some environments will not load required files from the standard filesystem. In these cases, you may be able to load the debugger manually using the file path stored in
LOCAL_LUA_DEBUGGER_FILEPATH:package.loaded["lldebugger"] = assert(loadfile(os.getenv("LOCAL_LUA_DEBUGGER_FILEPATH")))() require("lldebugger").start()
A list of alternate paths to find Lua scripts. This is useful for environments like LÖVE, which use custom resolvers to find scripts in other locations than what is in package.config.
A list of glob patterns identifying where to find Lua scripts in the workspace when debugging. This is required for placing breakpoints in sourcemapped files (ex. 'ts' scripts when using TypescriptToLua), as the source files must be looked up ahead of time so that breakpoints can be resolved.
Example: scriptFiles: ["**/*.lua"]
A list of Lua patterns that specifies files to skip when stepping through code.
Example: ignorePatterns: ["^/usr"]
Step into Lua when stepping through source-mapped code and no mapping is available for the current line.
Break into the debugger when errors occur inside coroutines.
- Coroutines created with
coroutine.wrapwill always break, regardless of this option. - In Lua 5.1, this will break where the coroutine was resumed and the message will contain the actual location of the error.
Automatically break on first line after debug hook is set.
Specify working directory to launch executable in. Default is the project directory.
List of arguments to pass to Lua script or custom environment when launching.
Specify environment variables to set when launching executable.
Specifies how the extension communicates with the debugger.
Possible values:
stdio(default): Messages are embeded in stdin and stdout.pipe: Pipes are created for passing messages (named pipes on Windows, fifos on Linux). Use this if your environment has issues with stdio communication.
Enable verbose output from debugger. Only useful when trying to identify problems with the debugger itself.
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Love",
"type": "lua-local",
"request": "launch",
"program": {
"command": "love"
},
"args": [
"game"
],
"scriptRoots": [
"game"
]
}
]
}if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
require("lldebugger").start()
end
function love.load()
...Note that console must be set to false (the default value) in conf.lua, or the debugger will not be able to communicate with the running program.
game/conf.lua
function love.conf(t)
t.console = false
endNote that even when using busted via a lua interpreter, it must be set up as a custom environment to work correctly.
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Busted CLI",
"type": "lua-local",
"request": "launch",
"program": {
"command": "busted"
},
"args": [
"test/start-cli.lua"
],
"ignorePatterns": "^/usr"
},
{
"name": "Debug Busted via Lua Interpreter",
"type": "lua-local",
"request": "launch",
"program": {
"command": "lua"
},
"args": [
"test/start-interpreter.lua"
],
"ignorePatterns": "^/usr"
}
]
}test/start-cli.lua
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
require("lldebugger").start()
end
describe("a test", function()
...
end)test/start-interpreter.lua
--busted should be required before hooking debugger to avoid double-hooking
require("busted.runner")()
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
require("lldebugger").start()
end
describe("a test", function()
...
end){
"version": "0.2.0",
"configurations": [
{
"name": "Debug",
"type": "lua-local",
"request": "launch",
"program": {
"command": "dmengine"
},
"args": ["./build/default/game.projectc"],
"scriptRoots": ["."] // Required for debugger to find scripts
}
]
}if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
local lldebugger = loadfile(os.getenv("LOCAL_LUA_DEBUGGER_FILEPATH"))()
lldebugger.start()
end
function init(self)
...
endInformation on downloading dmengine for your platform can be found here.
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug",
"type": "lua-local",
"request": "launch",
"windows": {
"program": {
"command": "C:\\Program Files (x86)\\Corona Labs\\Corona\\Corona Simulator.exe",
},
"args": [
"/no-console",
"/debug",
"${workspaceFolder}\\main.lua"
]
},
"osx": {
"program": {
"command": "/Applications/Corona/CoronaSimulator.app/Contents/MacOS/CoronaSimulator",
},
"args": [
"-no-console"
"YES"
"-debug"
"1"
"-project"
"${workspaceFolder}/main.lua"
]
}
}
]
}if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
local lldebugger = loadfile(os.getenv("LOCAL_LUA_DEBUGGER_FILEPATH"))()
lldebugger.start()
end
...{
"version": "0.2.0",
"configurations": [
{
"name": "Debug TSTL",
"type": "lua-local",
"request": "launch",
"program": {
"command": "my_custom_environment"
},
"args": [
...
],
"scriptFiles": ["**/*.lua"] // Required for breakpoints in ts files to work
}
]
}if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
require("lldebugger").start()
end
...tsconfig.json
{
"compilerOptions": {
"sourceMap": true,
...
},
"tstl": {
"noResolvePaths": ["lldebugger"] // Required so TSTL ignores the missing dependency
}
}