import c
This commit is contained in:
@@ -4,6 +4,7 @@ const sx = struct {
|
||||
pub const parser = @import("../parser.zig");
|
||||
pub const sema = @import("../sema.zig");
|
||||
pub const imports = @import("../imports.zig");
|
||||
pub const c_import = @import("../c_import.zig");
|
||||
};
|
||||
|
||||
pub const Import = struct {
|
||||
@@ -28,6 +29,10 @@ pub const Document = struct {
|
||||
last_good_sema: ?sx.sema.SemaResult = null,
|
||||
/// Import declarations parsed from this file.
|
||||
imports: []const Import,
|
||||
/// Last successful imports (preserved across parse failures for completions).
|
||||
last_good_imports: []const Import = &.{},
|
||||
/// Source locations for C import functions (name → file:line for go-to-definition).
|
||||
c_source_locations: std.StringHashMap(sx.c_import.CSourceLocation),
|
||||
/// True while this document is being analyzed (circular import guard).
|
||||
is_analyzing: bool = false,
|
||||
|
||||
@@ -110,6 +115,7 @@ pub const DocumentStore = struct {
|
||||
.root = null,
|
||||
.sema = null,
|
||||
.imports = &.{},
|
||||
.c_source_locations = std.StringHashMap(sx.c_import.CSourceLocation).init(self.allocator),
|
||||
};
|
||||
try self.by_path.put(path_owned, doc);
|
||||
return doc;
|
||||
@@ -126,6 +132,43 @@ pub const DocumentStore = struct {
|
||||
var p = sx.parser.Parser.init(self.allocator, doc.source);
|
||||
doc.root = p.parse() catch return;
|
||||
}
|
||||
|
||||
// Expand root with synthetic fn_decls from #import c { ... } declarations.
|
||||
// This makes C functions visible to sema, completions, and hover.
|
||||
doc.c_source_locations = std.StringHashMap(sx.c_import.CSourceLocation).init(self.allocator);
|
||||
if (doc.root) |parsed_root| {
|
||||
if (parsed_root.data == .root) {
|
||||
var expanded = std.ArrayList(*sx.ast.Node).empty;
|
||||
for (parsed_root.data.root.decls) |decl| {
|
||||
if (decl.data == .c_import_decl) {
|
||||
const ci = decl.data.c_import_decl;
|
||||
if (sx.c_import.processCImport(
|
||||
self.allocator,
|
||||
ci.includes,
|
||||
ci.defines,
|
||||
ci.flags,
|
||||
)) |result| {
|
||||
for (result.fn_decls, result.locations) |fd, loc| {
|
||||
try expanded.append(self.allocator, fd);
|
||||
if (fd.data == .fn_decl) {
|
||||
try doc.c_source_locations.put(fd.data.fn_decl.name, loc);
|
||||
}
|
||||
}
|
||||
} else |_| {}
|
||||
}
|
||||
try expanded.append(self.allocator, decl);
|
||||
}
|
||||
if (expanded.items.len != parsed_root.data.root.decls.len) {
|
||||
const new_root = try self.allocator.create(sx.ast.Node);
|
||||
new_root.* = .{
|
||||
.span = parsed_root.span,
|
||||
.data = .{ .root = .{ .decls = try expanded.toOwnedSlice(self.allocator) } },
|
||||
};
|
||||
doc.root = new_root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const root = doc.root orelse return;
|
||||
|
||||
// Extract imports from AST
|
||||
@@ -146,6 +189,7 @@ pub const DocumentStore = struct {
|
||||
}
|
||||
}
|
||||
doc.imports = try import_list.toOwnedSlice(self.allocator);
|
||||
doc.last_good_imports = doc.imports;
|
||||
|
||||
// Recursively analyze imported documents and pre-register their symbols
|
||||
var analyzer = sx.sema.Analyzer.init(self.allocator);
|
||||
|
||||
@@ -8,6 +8,7 @@ const sx = struct {
|
||||
pub const sema = @import("../sema.zig");
|
||||
pub const errors = @import("../errors.zig");
|
||||
pub const imports = @import("../imports.zig");
|
||||
pub const c_import = @import("../c_import.zig");
|
||||
};
|
||||
const lsp = @import("types.zig");
|
||||
const doc_mod = @import("document.zig");
|
||||
@@ -22,6 +23,7 @@ pub const Server = struct {
|
||||
transport: *Transport,
|
||||
io: std.Io,
|
||||
shutdown_requested: bool = false,
|
||||
root_path: []const u8 = "",
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, transport: *Transport, io: std.Io) Server {
|
||||
return .{
|
||||
@@ -44,7 +46,7 @@ pub const Server = struct {
|
||||
const params = jsonGet(root, "params");
|
||||
|
||||
if (std.mem.eql(u8, method, "initialize")) {
|
||||
self.handleInitialize(id) catch |e| self.logError(method, e);
|
||||
self.handleInitialize(id, params) catch |e| self.logError(method, e);
|
||||
} else if (std.mem.eql(u8, method, "initialized")) {
|
||||
// Nothing to do
|
||||
} else if (std.mem.eql(u8, method, "shutdown")) {
|
||||
@@ -115,7 +117,19 @@ pub const Server = struct {
|
||||
try self.transport.writeMessage(resp);
|
||||
}
|
||||
|
||||
fn handleInitialize(self: *Server, id: ?std.json.Value) !void {
|
||||
fn handleInitialize(self: *Server, id: ?std.json.Value, params: ?std.json.Value) !void {
|
||||
// chdir to workspace root so relative paths in #import c work
|
||||
chdir: {
|
||||
const p = params orelse break :chdir;
|
||||
const root_uri_val = jsonGet(p, "rootUri") orelse break :chdir;
|
||||
const root_uri = jsonStr(root_uri_val) orelse break :chdir;
|
||||
const prefix = "file://";
|
||||
if (!std.mem.startsWith(u8, root_uri, prefix)) break :chdir;
|
||||
const root_path = root_uri[prefix.len..];
|
||||
self.root_path = self.allocator.dupe(u8, root_path) catch break :chdir;
|
||||
const path_z = self.allocator.dupeZ(u8, root_path) catch break :chdir;
|
||||
_ = std.c.chdir(path_z.ptr);
|
||||
}
|
||||
const req_id = id orelse return;
|
||||
const id_json = try lsp.valueToJson(self.allocator, req_id);
|
||||
const result_json = try lsp.initializeResultJson(self.allocator);
|
||||
@@ -178,6 +192,10 @@ pub const Server = struct {
|
||||
// Namespace import member
|
||||
if (self.findImportByNs(doc, qn.ns)) |imp| {
|
||||
if (self.documents.get(imp.path)) |imp_doc| {
|
||||
// C import source location: jump to the C header
|
||||
if (imp_doc.c_source_locations.get(qn.member)) |cloc| {
|
||||
if (try self.sendCSourceLocation(id_json, cloc, qn_origin, doc.source)) return;
|
||||
}
|
||||
// Single-file import
|
||||
if (imp_doc.sema) |imp_sema| {
|
||||
if (findSymbolByName(imp_sema.symbols, qn.member)) |si| {
|
||||
@@ -1029,6 +1047,10 @@ pub const Server = struct {
|
||||
.hash_foreign,
|
||||
.hash_library,
|
||||
.hash_using,
|
||||
.hash_include,
|
||||
.hash_source,
|
||||
.hash_define,
|
||||
.hash_flags,
|
||||
=> ST.keyword,
|
||||
|
||||
.kw_f32, .kw_f64, .kw_Type => ST.type_,
|
||||
@@ -1312,6 +1334,30 @@ pub const Server = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a go-to-definition response pointing to a C header source location.
|
||||
fn sendCSourceLocation(self: *Server, id_json: []const u8, cloc: sx.c_import.CSourceLocation, origin_span: ?sx.ast.Span, origin_source: [:0]const u8) !bool {
|
||||
// Resolve to absolute path if relative
|
||||
const abs_path = if (cloc.file.len > 0 and cloc.file[0] != '/')
|
||||
try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ self.root_path, cloc.file })
|
||||
else
|
||||
cloc.file;
|
||||
const target_uri = try std.fmt.allocPrint(self.allocator, "file://{s}", .{abs_path});
|
||||
const line: u32 = if (cloc.line > 0) cloc.line - 1 else 0; // LSP lines are 0-based
|
||||
const target_range = lsp.Range{
|
||||
.start = .{ .line = line, .character = 0 },
|
||||
.end = .{ .line = line, .character = 0 },
|
||||
};
|
||||
if (origin_span) |os| {
|
||||
const src_range = spanToRange(origin_source, os);
|
||||
const loc_json = try lsp.locationLinkJson(self.allocator, target_uri, target_range, src_range);
|
||||
try self.sendResponse(id_json, loc_json);
|
||||
} else {
|
||||
const loc_json = try lsp.locationJson(self.allocator, target_uri, target_range);
|
||||
try self.sendResponse(id_json, loc_json);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Resolve which document a symbol belongs to (for hover/source lookup).
|
||||
fn resolveSymbolDoc(self: *Server, doc: *const Document, sym: sx.sema.Symbol) *const Document {
|
||||
if (sym.origin) |origin_path| {
|
||||
@@ -1320,11 +1366,14 @@ pub const Server = struct {
|
||||
return doc;
|
||||
}
|
||||
|
||||
/// Find an import by namespace name.
|
||||
/// Find an import by namespace name (falls back to last good imports).
|
||||
fn findImportByNs(_: *Server, doc: *const Document, ns_name: []const u8) ?doc_mod.Import {
|
||||
for (doc.imports) |imp| {
|
||||
if (imp.ns) |ns| {
|
||||
if (std.mem.eql(u8, ns, ns_name)) return imp;
|
||||
const imports_lists = [_][]const doc_mod.Import{ doc.imports, doc.last_good_imports };
|
||||
for (&imports_lists) |imports| {
|
||||
for (imports) |imp| {
|
||||
if (imp.ns) |ns| {
|
||||
if (std.mem.eql(u8, ns, ns_name)) return imp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user