Full AL language support for Neovim — LSP, debugging, build commands, and multi-project workspaces for Microsoft Dynamics 365 Business Central.
- LSP integration — code completion, diagnostics, go-to-definition, inlay hints, and semantic folding via the AL Language Server (from the VS Code AL extension)
- Multi-project workspaces — open
.code-workspacefiles containing multiple AL projects with full project-reference closure support (requires code-workspace.nvim) - Debugging — nvim-dap adapter with a cross-platform Go proxy that solves stdio handle issues with AL EditorServices
- Build system —
:AL build,:AL downloadSymbols,:AL definition - Treesitter — syntax highlighting via tree-sitter-al
- Snippets — AL snippets via LuaSnip
- Progress notifications — real-time feedback during project loading and builds
- Neovim >= 0.10
- Microsoft AL Language Extension for VS Code (provides the language server binary)
Using lazy.nvim:
{
"abonckus/al.nvim",
ft = "al",
dependencies = {
"nvim-neotest/nvim-nio",
},
opts = {},
}{
"abonckus/al.nvim",
ft = "al",
dependencies = {
"nvim-neotest/nvim-nio",
"mfussenegger/nvim-dap",
"rcarriga/nvim-dap-ui",
"theHamsta/nvim-dap-virtual-text",
},
opts = {},
}{
"abonckus/al.nvim",
ft = "al",
dependencies = {
"nvim-neotest/nvim-nio",
"abonckus/code-workspace.nvim",
},
opts = {},
}All options with their defaults:
require("al").setup({
-- Path to VS Code extensions directory.
-- The plugin scans this for the AL Language extension.
vscodeExtensionsPath = "~\\.vscode\\extensions\\",
integrations = {
luasnip = true,
},
lsp = {
telemetryLevel = "all", -- "none" | "crash" | "error" | "all"
browser = "SystemDefault", -- "SystemDefault" | "Chrome" | "Firefox"
-- | "Edge" | "EdgeBeta"
inlayHintsParameterNames = true,
inlayHintsFunctionReturnTypes = true,
semanticFolding = true,
extendGoToSymbolInWorkspace = true,
extendGoToSymbolInWorkspaceResultLimit = 100,
extendGoToSymbolInWorkspaceIncludeSymbolFiles = true,
log = {
path = "",
level = "Normal", -- "Debug" | "Verbose" | "Normal"
-- | "Warning" | "Error"
},
},
workspace = {
alResourceConfigurationSettings = {
assemblyProbingPaths = { "./.netpackages" },
codeAnalyzers = {
"${CodeCop}",
"${analyzerFolder}BusinessCentral.LinterCop.dll",
},
enableCodeAnalysis = true,
backgroundCodeAnalysis = true,
packageCachePaths = { "./.alpackages" },
ruleSetPath = ".vscode/ruleset.json",
enableCodeActions = true,
incrementalBuild = false,
outputAnalyzerStatistics = false,
enableExternalRulesets = true,
},
},
-- Multi-project workspace settings (used with code-workspace.nvim)
multiproject = {
-- Relative path to per-project settings file (read for
-- alResourceConfigurationSettings overrides per folder)
settings_path = ".vscode/settings.json",
-- Maximum time (ms) to wait for a project closure to load
closure_timeout_ms = 300000,
},
})| Command | Description |
|---|---|
:AL config [name] |
Set the active launch configuration for the session |
:AL build |
Build the current AL package |
:AL publish |
Authenticate, build, and publish to Business Central |
:AL downloadSymbols |
Download symbols for a launch configuration |
:AL refreshSymbols |
Refresh symbol references (lightweight) |
:AL downloadSource |
Download source for a symbol |
:AL authenticate |
Authenticate for a launch configuration |
:AL clearCredentialsCache |
Clear cached credentials |
:AL definition |
Go to AL definition (uses al/gotodefinition) |
:AL runObject [Type] [Id] |
Run a Business Central object |
:AL openInBrowser |
Open Business Central web client |
:AL restartLsp |
Restart the AL language server |
:AL eventPublishers |
List event publishers in quickfix |
:AL symbolSearch [query] |
Search AL symbols |
:AL dependencies |
Show package dependencies |
:AL generatePermissionSet |
Generate a permission set AL object |
:AL lsp |
Show AL LSP client info and settings |
al.nvim exposes a statusline API for displaying AL project state:
local status = require("al.state").statusline()
-- status.config -- active launch config name (string|nil)
-- status.lsp -- AL language server attached (boolean)
-- status.closure_loaded -- project closure loaded (boolean)
-- status.project -- current project name in multi-project mode (string|nil)Example lualine component:
{
function()
local s = require("al.state").statusline()
if not s.lsp then return "" end
local parts = { "AL" }
if s.project then parts[#parts+1] = s.project end
if s.config then parts[#parts+1] = s.config end
if not s.closure_loaded then parts[#parts+1] = "loading..." end
return table.concat(parts, " | ")
end,
cond = function() return vim.bo.filetype == "al" end,
}al.nvim supports multi-project workspaces through integration with
code-workspace.nvim. When you open a .code-workspace file, al.nvim:
- Starts a single AL Language Server instance for all projects
- Sends
al/loadManifestfor each project folder - Computes project-reference closures from
app.jsondependencies - Switches the active workspace on
BufEnter(100ms debounce) - Sends
workspace/didChangeConfigurationfor dependency folders - Shows server progress notifications during closure loading
Each workspace folder with an app.json is treated as an AL project. When you
open a file in a project, al.nvim computes which other workspace projects are
dependencies (by matching app.json dependency IDs) and sends the full closure
to the server via al/setActiveWorkspace.
The server's rootPath is set to the first AL project folder in the workspace
(alphabetically), matching VS Code's behavior.
Each project can override workspace-level alResourceConfigurationSettings by
placing them in a settings file (default .vscode/settings.json) under the key
al.alResourceConfigurationSettings. Global defaults are merged with
per-project settings, with per-project values taking precedence.
my-workspace/
├── my-workspace.code-workspace
├── Cloud/
│ ├── app.json
│ ├── .vscode/settings.json
│ └── src/
├── Test/
│ ├── app.json (depends on Cloud)
│ ├── .vscode/settings.json
│ └── src/
└── DemoApp/
├── app.json (depends on Cloud)
└── src/
Opening a file in Test/ sends a closure of [Test, Cloud] to the server.
Opening a file in Cloud/ sends a closure of [Cloud] only.
al.nvim routes DAP traffic through a Go proxy binary to solve stdio handle
crashes in AL EditorServices. The proxy is bundled in bin/ for all platforms:
| Platform | Binary |
|---|---|
| Windows | al-debug-proxy.exe |
| Linux | al-debug-proxy |
| macOS (Intel) | al-debug-proxy-darwin |
| macOS (ARM) | al-debug-proxy-darwin-arm64 |
Debug configurations are read from .vscode/launch.json. Set "type": "al" in
your launch configuration.
:DapToggleBreakpoint " Set breakpoints
:DapContinue " Start debuggingFor technical details on the proxy, see DEBUGGING.md.
Requires Go 1.21+:
cd proxy-src && ./build.sh # Unix
cd proxy-src && build.bat # Windowsyour-al-project/
├── app.json AL app manifest
├── .alpackages/ Symbol packages
├── .vscode/
│ ├── launch.json Debug configurations
│ ├── settings.json Per-project settings (multi-project)
│ └── ruleset.json Code analysis rules
└── src/
└── *.al
| Plugin | Purpose | Required |
|---|---|---|
| nvim-nio | Async I/O for multi-project and debugging | Yes |
| code-workspace.nvim | Multi-project workspace detection | For multi-project |
| nvim-dap | Debug Adapter Protocol | For debugging |
| nvim-dap-ui | Debug UI | For debugging |
| nvim-dap-virtual-text | Inline debug values | For debugging |
| nvim-treesitter + tree-sitter-al | Syntax highlighting | Optional |
| LuaSnip | AL snippets | Optional |
| lsp-output.nvim | LSP server log viewer | Optional |
See LICENSE.