Neovim setup for Zig
How to setup Neovim for Zig development in Windows including the Language Server and Debugger
Zig is an open-source, statically-typed programming language designed with a focus on simplicity, performance, and safety. Created by Andrew Kelley, Zig aims to provide a modern alternative to existing programming languages, offering low-level control without sacrificing developer convenience.
I first learned about Zig just a few months ago. However, with the recent surge of languages attempting to replace C/C++, such as Rust, Carbon, Nim, and others, I initially held a degree of skepticism toward yet another contender. It seemed like another attempt to replace the longstanding C/C++. Nevertheless, as I observed Zig gaining traction in the indie game development scene, my curiosity was piqued, prompting me to explore it further. The more I delve into its features, the more I find myself drawn to it. This growing affinity led me to take the step of setting up a development environment on Windows, eager to give Zig a chance and test how it feels to program in this language.
Zig installation
First, we are going to install manually Zig by downloading a release for Windows from the official website:
Download the release for Windows (e.g.: zig-windows-x86_64-0.12.0-dev.1819+5c1428ea9.zip)
Unzip the file in the folder that you want (e.g.: C:\Users\<username>\zig)
After that, we need to add the path of the unzipped folder (i.e.: C:\Users\<username>\zig\zig-windows-x86_64-0.12.0-dev.1819+5c1428ea9) to the PATH environment variable, so that we can run Zig from any location.
Test installation
Now that we have Zig installed, we can check if it works properly. The zig init command sets up the basic structure and configuration files needed for a Zig project. Open a Terminal and type the following commands:
mkdir zig_hello_world
cd zig_hello_world
zig init
This command creates an example program containing a basic main function that prints a message, and a unit test.
const std = @import("std");
pub fn main() !void {
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
// stdout is for the actual output of your application, for example if you
// are implementing gzip, then only the compressed bytes should be sent to
// stdout, not any debugging messages.
const stdout_file = std.io.getStdOut().writer();
var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer();
try stdout.print("Run `zig build test` to run the tests.\n", .{});
try bw.flush(); // don't forget to flush!
}
test "simple test" {
var list = std.ArrayList(i32).init(std.testing.allocator);
defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
try list.append(42);
try std.testing.expectEqual(@as(i32, 42), list.pop());
}
We can execute the main function by running the command zig build run:
zig build run
All your codebase are belong to us.
Run `zig build test` to run the tests.
To run the unit test, we just have to run the command zig build test, which will not print anything. But we can check that it works properly by modifying the value to be expected in the unit test.
zig build test
Neovim
Language Server
To install the Zig Language Server we can download the Windows version from the following website:
After downloading it, we have to copy the zls.exe file to the path where we have installed Zig (i.e.: C:\Users\<username>\zig\zig-windows-x86_64-0.12.0-dev.1819+5c1428ea9).
Then, we have to configure the language server in our Neovim configuration file. I prefer to use a local configuration file (i.e.: .nvim.lua), instead of the global Neovim configuration files as I explain in my book about Neovim. There I also explain how to configure the main actions of lspconfig in Neovim.
require 'lspconfig'.zls.setup{}
We can check that it works properly by opening the main.zig function, and showing the details of a function.
Debugger
LLVM
To debug Zig code we will use LLDB, which is included in the LLVM installation. We can download the LLVM version for Windows (e.g.: LLVM-16.0.6-win64.exe) from the following website:
Python
Once we have LLVM installed, we need to install the Python version required by that LLVM version. We can check that by running LLDB from a terminal. It will show an error dialog complaining of the missing Python DLL (e.g.: python310.dll not found). For the LLVM-16.0.6 version, we need to install the Python version 3.10.x (e.g.: python-3.10.10-amd64.exe). We can download it from the website:
Finally, we need to define the following environment variable:
LLDB_USE_NATIVE_PDB_READER="yes"
After that, we can check lldb from a terminal and debug the executable coming out from the example program:
PS C:\Users\<username>\zig_hello_world> lldb.exe .\zig-out\bin\zig_hello_world.exe
(lldb) target create ".\\zig-out\\bin\\hello_world.exe"
Current executable set to 'C:\Users\<username>\zig_hello_world\zig-out\bin\zig_hello_world.exe' (x86_64).
(lldb) (lldb) b main
Breakpoint 1: where = zig_hello_world.exe`main + 26 at main.zig:5, address = 0x00000001400013fa
(lldb) r
(lldb) Process 28096 launched: 'C:\Users\<username>\zig_hello_world\zig-out\bin\zig_hello_world.exe' (x86_64)
Process 28096 stopped
* thread #1, stop reason = breakpoint 1.1
frame #0: 0x00007ff7d46b13fa zig_hello_world.exe`main at main.zig:5
2
3 pub fn main() !void {
4 // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
-> 5 std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
6
7 // stdout is for the actual output of your application, for example if you
8 // are implementing gzip, then only the compressed bytes should be sent to
(lldb)
As Neovim supports the Debug Adapter Protocol (DAP), we can debug directly from Neovim. To configure Zig debugging in Neovim we can use the nvim-dap plugin. The required configuration for Zig that we need to add to our local Neovim configuration files (i.e.: .nvim.lua) is the following:
local dap = require('dap')
dap.adapters.lldb = {
type = 'executable',
command = 'C:\\Program Files\\LLVM\\bin\\lldb-vscode.exe', -- adjust as needed, must be absolute path
name = 'lldb'
}
dap.configurations.zig = {
{
name = 'Launch',
type = 'lldb',
request = 'launch',
program = '${workspaceFolder}/zig-out/bin/zig_hello_world.exe',
cwd = '${workspaceFolder}',
stopOnEntry = false,
args = {},
},
}
Then, you can start a debugging session from Neovim, and move around the source code while inspecting the execution flow of the program.
Bonus: VScode setup
If you don't feel comfortable working with a text editor like Neovim, I'm going to give you the instructions to configure VScode to work with Zig as VScode is one of the most commonly used IDEs nowadays. To add support for Zig, you just need to install the Zig extension from the VScode marketplace.
Now that we have the Zig extension installed, we can open the folder of the example project that we created previously (i.e.: zig_hello_world) from VScode. When we open the main.zig file, VScode will ask us to install Zig and the Zig Language Server. It will install them under the local VScode directory (e.g.: c:\Users\<username>\AppData\Roaming\Code\User\globalStorage\ziglang.vscode-zig\zig_install\zig.exe).
Once we have the Zig Language Server installed, we can test it by opening the main.zig file and placing the cursor over one of the functions (e.g.: std.debug.print). It will pop up a dialog showing details about the selected function.
Lastly, we can configure the debugger for Zig by creating the following launch.json file:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/zig-out/bin/zig_hello_world.exe",
"cwd": "${workspaceFolder}",
},
]
}
After that, if we place a breakpoint on one of the lines of the main.zig file and press F5 to start a debugging session, we will see how the program execution stops at the breakpoint:
And that's all. I hope this post helps you set up a development environment for Zig so that you can give this language a try. It is promising, especially for game development, where I've seen several indie games and engines already implemented using Zig. In the case of Linux, the setup is pretty similar to this one. You just need to pick the versions corresponding to Linux for each product, but the steps would be pretty much the same.
See you in the next post!