Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c839994
feat(lsp): add Content-Length framing and JSON-RPC codec
guysenpai May 20, 2026
12d2df9
feat(lsp): add URI <-> path helpers cross-platform
guysenpai May 20, 2026
97543d6
feat(util): add stderr logger with level via env
guysenpai May 20, 2026
e839f08
feat(lsp): add LSP types needed for initialize handshake
guysenpai May 20, 2026
a1f295b
chore(build): add test step that aggregates inline tests
guysenpai May 20, 2026
6e877cb
feat(lsp): add ZLS client with subprocess spawn and request registry
guysenpai May 20, 2026
608f373
feat(lsp): implement initialize + initialized + shutdown handshake
guysenpai May 20, 2026
7ceb5df
feat(main): switch entry point to Juicy Main pattern
guysenpai May 20, 2026
6afd56b
test(lsp): add integration test running real ZLS handshake
guysenpai May 20, 2026
0aefcc0
style(probe): apply zig fmt to probe/probe.zig
guysenpai May 20, 2026
004be69
chore(build): add CI matrix with ZLS install
guysenpai May 20, 2026
b13220a
fix(lsp): join reader/stderr threads before nulling self.child
guysenpai May 20, 2026
9f14b67
fix(lsp): consolidate spawn() error path through terminate()
guysenpai May 20, 2026
cf9bde4
fix(lsp): guarantee slot is freed on every return path
guysenpai May 20, 2026
eca52e5
fix(lsp): guard response id cast against negative / out-of-range values
guysenpai May 20, 2026
57948d4
docs(lsp): annotate double-parse cost and pathToUri backslash limitation
guysenpai May 20, 2026
45a4159
fix(build): bump mlugg/setup-zig to v2 for Zig 0.16.0 download
guysenpai May 21, 2026
594f8ce
fix(build): force LF line endings via .gitattributes
guysenpai May 21, 2026
8d71cbc
chore(build): switch to weldengine/setup-zig@v0.1.0
guysenpai May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Force LF line endings on checkout, on all platforms.
# Zig's formatter normalizes to LF; without this, Windows runners with
# `core.autocrlf=true` (the default) check out CRLF and `zig fmt --check`
# flags every file as needing reformatting.
* text=auto eol=lf
60 changes: 60 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- name: Install Zig 0.16.0
uses: weldengine/setup-zig@v0.1.0
with:
version: 0.16.0

- name: Install ZLS 0.16.0 (Linux)
if: runner.os == 'Linux'
run: |
curl -L https://github.com/zigtools/zls/releases/download/0.16.0/zls-x86_64-linux.tar.xz | tar xJ
sudo mv zls /usr/local/bin/
zls --version

- name: Install ZLS 0.16.0 (macOS)
if: runner.os == 'macOS'
run: |
curl -L https://github.com/zigtools/zls/releases/download/0.16.0/zls-aarch64-macos.tar.xz | tar xJ
sudo mv zls /usr/local/bin/
zls --version

- name: Install ZLS 0.16.0 (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
Invoke-WebRequest -Uri https://github.com/zigtools/zls/releases/download/0.16.0/zls-x86_64-windows.zip -OutFile zls.zip
Expand-Archive zls.zip -DestinationPath C:\zls
echo "C:\zls" >> $env:GITHUB_PATH
C:\zls\zls.exe --version

- name: zig version
run: zig version

- name: Build
run: zig build

- name: Test
run: zig build test

- name: Check formatting
run: zig fmt --check src/ tests/ probe/ build.zig
28 changes: 28 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,32 @@ pub fn build(b: *std.Build) void {
if (b.args) |args| run_probe.addArgs(args);
const probe_step = b.step("probe", "Run the ZLS capability probe");
probe_step.dependOn(&run_probe.step);

// ---- `zig build test` ----
//
// src/root.zig serves two purposes:
// 1. As the test root, its comptime imports ensure every inline
// `test "..."` block under src/ runs.
// 2. As the public root, it re-exports the library so external
// test targets (tests/) can `@import("zlsmcp")`.
const test_step = b.step("test", "Run all unit and integration tests");

const root_mod = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
const src_test = b.addTest(.{ .root_module = root_mod });
test_step.dependOn(&b.addRunArtifact(src_test).step);

const integ_mod = b.createModule(.{
.root_source_file = b.path("tests/integration_test.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "zlsmcp", .module = root_mod },
},
});
const integ_test = b.addTest(.{ .root_module = integ_mod });
test_step.dependOn(&b.addRunArtifact(integ_test).step);
}
30 changes: 13 additions & 17 deletions probe/probe.zig
Original file line number Diff line number Diff line change
Expand Up @@ -515,9 +515,7 @@ fn writeReport(
{
const ch_advertised = !std.mem.eql(u8, caps.call_hierarchy_provider, "false") and !std.mem.eql(u8, caps.call_hierarchy_provider, "absent");
const refs_advertised = !std.mem.eql(u8, caps.references_provider, "false") and !std.mem.eql(u8, caps.references_provider, "absent");
const verdict = if (ch_advertised and prep_ok and incoming_ok) "OK avec callHierarchy"
else if (refs_advertised) "fallback references"
else "KO";
const verdict = if (ch_advertised and prep_ok and incoming_ok) "OK avec callHierarchy" else if (refs_advertised) "fallback references" else "KO";
try w.print("- `zig_callers_of` / `zig_callees_of` : **{s}** (prepareCallHierarchy={s}, incomingCalls={s})\n", .{
verdict,
if (prep_ok) "OK" else "no",
Expand All @@ -534,10 +532,7 @@ fn writeReport(
// dans le workspace ?".
const one_level = o_ok; // Physics(f32).World
const two_levels = q1_ok; // Outer(u32).Inner (composition à un niveau de plus)
const verdict = if (one_level and two_levels) "OK — comptime résolu pour types retournés (Foo(T).Member)"
else if (one_level and !two_levels) "Best-effort — fonctionne pour un niveau de composition (Foo(T).X), échoue au-delà"
else if (!one_level and def_call_ok) "Partiel — fonctionne pour les méthodes via call-site definition, échoue pour les types retournés"
else "KO";
const verdict = if (one_level and two_levels) "OK — comptime résolu pour types retournés (Foo(T).Member)" else if (one_level and !two_levels) "Best-effort — fonctionne pour un niveau de composition (Foo(T).X), échoue au-delà" else if (!one_level and def_call_ok) "Partiel — fonctionne pour les méthodes via call-site definition, échoue pour les types retournés" else "KO";
try w.print("- `zig_instantiation_members` : **{s}** (o={s}, p={s}, q1={s}, q2={s}, q3={s})\n", .{
verdict,
if (o_ok) "OK" else "no",
Expand Down Expand Up @@ -850,16 +845,17 @@ pub fn main() !void {

const caps = if (init_resp_owned) |ir|
try extractCaps(gpa, ir)
else Caps{
.position_encoding = try gpa.dupe(u8, "<no init response>"),
.workspace_symbol_provider = try gpa.dupe(u8, "<no init response>"),
.call_hierarchy_provider = try gpa.dupe(u8, "<no init response>"),
.references_provider = try gpa.dupe(u8, "<no init response>"),
.document_symbol_provider = try gpa.dupe(u8, "<no init response>"),
.hover_provider = try gpa.dupe(u8, "<no init response>"),
.definition_provider = try gpa.dupe(u8, "<no init response>"),
.diagnostic_provider = try gpa.dupe(u8, "<no init response>"),
};
else
Caps{
.position_encoding = try gpa.dupe(u8, "<no init response>"),
.workspace_symbol_provider = try gpa.dupe(u8, "<no init response>"),
.call_hierarchy_provider = try gpa.dupe(u8, "<no init response>"),
.references_provider = try gpa.dupe(u8, "<no init response>"),
.document_symbol_provider = try gpa.dupe(u8, "<no init response>"),
.hover_provider = try gpa.dupe(u8, "<no init response>"),
.definition_provider = try gpa.dupe(u8, "<no init response>"),
.diagnostic_provider = try gpa.dupe(u8, "<no init response>"),
};
defer {
gpa.free(caps.position_encoding);
gpa.free(caps.workspace_symbol_provider);
Expand Down
Loading
Loading