Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
2c5cc2b48f
@ -1,4 +1,4 @@
|
||||
![Zig Language Server](https://raw.githubusercontent.com/SuperAuguste/zls/master/.assets/zls.svg)
|
||||
![Zig Language Server](https://raw.githubusercontent.com/zigtools/zls/master/.assets/zls.svg)
|
||||
|
||||
![CI](https://github.com/zigtools/zls/workflows/CI/badge.svg)
|
||||
![Zig Tools](https://img.shields.io/static/v1?label=zigtools&message=for%20all%20of%20ziguanity&color=F7A41D&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAEDklEQVR4nOzYbUwbZRwA8Oe53vXuSltaa2lxc+KKBGcyBhLDgH3SiSMZ++TQRT8uJH4x8S0myL6YmUjUbIkfZvZtX3TJlAwjiYImxuBwa1hEtxAcQ8GFQrtBWXvXu17vTH1ux1lv99IeLcn6//Rw9/D0l+ft/28xsC2jyrISVZaV2KYsfCsGxSDYs5OIhPCAB0tlpFt3hF//yqYyUsVYrQ3Eaz2ew0/Tta7/rENOlCZnuTMTqZHLrJlxoF2ggAf7+FVff2eNfrf+U/HRaMZwNHtmqzGMf/NucNfDxqNFQqY+0QZWYxifGKoL1TrQnzlRGrvKXphio/M8ANLEUKjeL7+aW86e+5EpB4vEwRevBxTTtSX++Gd3rv6ZBQCEfdi3g3VqU8/J1dspsRysd454n3rUidq//MH1Dcc3WEkxNdUTalNsXTYFPNgr3TULcWE0qn0CStryXhoufPqIi8wfusWE0DEYW0sbm9Rvj52Oj1zROAElXacvd7mQCQAwdH4dmdwUNGkCAAwc9GiOXBKrp4VGjcWEcGFKXo6B59wmTQCA7mbSTWmsWEmstsflXfXdTEa8d4e375YfMpx46AM9EwDAgcGWXYSdLAyCkE8+Zdf/5pXnqxs51HCR2Pv9PgxqmJbXckr/HQGHnSx1cNnN9tnvU5msPHXHumvODjy0w194AvqGV5X+bkrDUDxLlPI3J2rXujb3x+9LwoufxNWymY/qC3Ybw22m7cTdnJ0sAMD8ioAaHU+Q6ucTv3FqmXJalRPQHnEqnW/GBJtZk7Mcajy/l/bSUEdWcCqP7pczejItXr+lwSr+lg/7sK5meZIoJ2x5jPhpli+QHTixcvxZd73fcfkGd2Y8hUqu1gbihX0U6vP1NCNqlWFF3vL/v8c7BmMsb/yPXhr+cKJOyVed78VQAi2IYhZRM7eYMflr4MjbQcV0/ue0pqkYln6+o53wwJNkwT5Dl9zR/fTUyXBnk7zuiwnhzXPr9/sUa3vLZA7OZKXxGfbSHJ9kRIqAe3YSB/dS6iIxsZHrG47rFDkW9pb5ukA/ri3xL52+fUPrXlDC7GzZYmI48dTY3eGLG5weyTTLkmluOTs5y3U1k5EQ7vg3I64kc9F5fnwm8/lkGhWJhmHMsmpSvy06DE5iRUwGrEqZ9FgYBF++EayISY91pJ1qu1dnltmkx+ptlev0JCOW2aTH8rvlWvbKPFdmkx5rNSkXjZ1NZGMYL6dJL/kc2kd99VYQtRlOvDTHt0ecys9DW2rKfyO634ubK0J3M9kQzM8TgcPdIZwiYHlMeiwJgNEo+0yjE8mUmF7gD38Y31KTcQWBQdDbSvW20XVex1paHJtmL0ZZzTL3gYht+ktzlWUlqiwrUWVZiX8CAAD//7jyYLmjqPd4AAAAAElFTkSuQmCC)
|
||||
@ -20,7 +20,7 @@ Zig Language Server, or `zls`, is a language server for Zig. The Zig wiki states
|
||||
Installing `zls` is pretty simple;
|
||||
|
||||
```bash
|
||||
git clone https://github.com/SuperAuguste/zls
|
||||
git clone https://github.com/zigtools/zls
|
||||
cd zls
|
||||
zig build
|
||||
```
|
||||
@ -33,6 +33,8 @@ zig build
|
||||
|
||||
Then, you can use the `zls` executable in an editor of your choice that has a Zig language server client!
|
||||
|
||||
*Note:`zls` itself must be built using the master branch of zig currently due to a bug in `std.json` which was [fixed](https://github.com/ziglang/zig/pull/5167) after 0.6.0 was released.*
|
||||
|
||||
### Configuration Options
|
||||
|
||||
You can configure zls by providing a zls.json file in the same directory as the executable.
|
||||
@ -42,6 +44,7 @@ The following options are currently available.
|
||||
| --- | --- | --- | --- |
|
||||
| `enable_snippets` | `bool` | `true` | Enables snippet completion, set to false for compatibility with language clients that do not support snippets (such as ale). |
|
||||
| `zig_lib_path` | `?[]const u8` | `null` | zig library path, used to analyze std library imports. |
|
||||
| `warn_style` | `bool` | `false` | Enables warnings for style *guideline* mismatches |
|
||||
|
||||
## Usage
|
||||
|
||||
|
206
src/analysis.zig
206
src/analysis.zig
@ -79,6 +79,7 @@ pub fn getFunctionSignature(tree: *ast.Tree, func: *ast.Node.FnProto) []const u8
|
||||
const start = tree.tokens.at(func.firstToken()).start;
|
||||
const end = tree.tokens.at(switch (func.return_type) {
|
||||
.Explicit, .InferErrorSet => |node| node.lastToken(),
|
||||
.Invalid => |r_paren| r_paren,
|
||||
}).end;
|
||||
return tree.source[start..end];
|
||||
}
|
||||
@ -118,21 +119,22 @@ pub fn getFunctionSnippet(allocator: *std.mem.Allocator, tree: *ast.Tree, func:
|
||||
try buffer.appendSlice(": ");
|
||||
}
|
||||
|
||||
if (param_decl.var_args_token) |_| {
|
||||
try buffer.appendSlice("...");
|
||||
continue;
|
||||
}
|
||||
switch (param_decl.param_type) {
|
||||
.var_args => try buffer.appendSlice("..."),
|
||||
.var_type => try buffer.appendSlice("var"),
|
||||
.type_expr => |type_expr| {
|
||||
var curr_tok = type_expr.firstToken();
|
||||
var end_tok = type_expr.lastToken();
|
||||
while (curr_tok <= end_tok) : (curr_tok += 1) {
|
||||
const id = tree.tokens.at(curr_tok).id;
|
||||
const is_comma = tree.tokens.at(curr_tok).id == .Comma;
|
||||
|
||||
var curr_tok = param_decl.type_node.firstToken();
|
||||
var end_tok = param_decl.type_node.lastToken();
|
||||
while (curr_tok <= end_tok) : (curr_tok += 1) {
|
||||
const id = tree.tokens.at(curr_tok).id;
|
||||
const is_comma = tree.tokens.at(curr_tok).id == .Comma;
|
||||
if (curr_tok == end_tok and is_comma) continue;
|
||||
|
||||
if (curr_tok == end_tok and is_comma) continue;
|
||||
|
||||
try buffer.appendSlice(tree.tokenSlice(curr_tok));
|
||||
if (is_comma or id == .Keyword_const) try buffer.append(' ');
|
||||
try buffer.appendSlice(tree.tokenSlice(curr_tok));
|
||||
if (is_comma or id == .Keyword_const) try buffer.append(' ');
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
try buffer.append('}');
|
||||
@ -146,12 +148,26 @@ pub fn getFunctionSnippet(allocator: *std.mem.Allocator, tree: *ast.Tree, func:
|
||||
pub fn getVariableSignature(tree: *ast.Tree, var_decl: *ast.Node.VarDecl) []const u8 {
|
||||
const start = tree.tokens.at(var_decl.firstToken()).start;
|
||||
const end = tree.tokens.at(var_decl.semicolon_token).start;
|
||||
// var end =
|
||||
// if (var_decl.init_n) |body| tree.tokens.at(body.firstToken()).start
|
||||
// else tree.tokens.at(var_decl.name_token).end;
|
||||
return tree.source[start..end];
|
||||
}
|
||||
|
||||
/// Gets a param signature
|
||||
pub fn getParamSignature(tree: *ast.Tree, param: *ast.Node.ParamDecl) []const u8 {
|
||||
const start = tree.tokens.at(param.firstToken()).start;
|
||||
const end = tree.tokens.at(param.lastToken()).end;
|
||||
return tree.source[start..end];
|
||||
}
|
||||
|
||||
pub fn isTypeFunction(tree: *ast.Tree, func: *ast.Node.FnProto) bool {
|
||||
switch (func.return_type) {
|
||||
.Explicit => |node| return if (node.cast(std.zig.ast.Node.Identifier)) |ident|
|
||||
std.mem.eql(u8, tree.tokenSlice(ident.token), "type")
|
||||
else
|
||||
false,
|
||||
.InferErrorSet, .Invalid => return false,
|
||||
}
|
||||
}
|
||||
|
||||
// STYLE
|
||||
|
||||
pub fn isCamelCase(name: []const u8) bool {
|
||||
@ -188,20 +204,59 @@ pub fn getChild(tree: *ast.Tree, node: *ast.Node, name: []const u8) ?*ast.Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Gets the child of slice
|
||||
pub fn getChildOfSlice(tree: *ast.Tree, nodes: []*ast.Node, name: []const u8) ?*ast.Node {
|
||||
// var index: usize = 0;
|
||||
for (nodes) |child| {
|
||||
switch (child.id) {
|
||||
.VarDecl => {
|
||||
const vari = child.cast(ast.Node.VarDecl).?;
|
||||
if (std.mem.eql(u8, tree.tokenSlice(vari.name_token), name)) return child;
|
||||
},
|
||||
.ParamDecl => {
|
||||
const decl = child.cast(ast.Node.ParamDecl).?;
|
||||
if (decl.name_token != null and std.mem.eql(u8, tree.tokenSlice(decl.name_token.?), name)) return child;
|
||||
},
|
||||
.FnProto => {
|
||||
const func = child.cast(ast.Node.FnProto).?;
|
||||
if (func.name_token != null and std.mem.eql(u8, tree.tokenSlice(func.name_token.?), name)) return child;
|
||||
},
|
||||
.ContainerField => {
|
||||
const field = child.cast(ast.Node.ContainerField).?;
|
||||
if (std.mem.eql(u8, tree.tokenSlice(field.name_token), name)) return child;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
// index += 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Resolves the type of a node
|
||||
pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.Node {
|
||||
std.debug.warn("Resolving node of type {}\n", .{node.id});
|
||||
std.debug.warn("NODE {}\n", .{node});
|
||||
switch (node.id) {
|
||||
.VarDecl => {
|
||||
const vari = node.cast(ast.Node.VarDecl).?;
|
||||
|
||||
return resolveTypeOfNode(analysis_ctx, vari.type_node orelse vari.init_node.?) orelse null;
|
||||
},
|
||||
.ParamDecl => {
|
||||
const decl = node.cast(ast.Node.ParamDecl).?;
|
||||
switch (decl.param_type) {
|
||||
.var_type, .type_expr => |var_type| {
|
||||
return resolveTypeOfNode(analysis_ctx, var_type) orelse null;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.FnProto => {
|
||||
return node;
|
||||
},
|
||||
.Identifier => {
|
||||
if (getChild(analysis_ctx.tree, &analysis_ctx.tree.root_node.base, analysis_ctx.tree.getNodeSource(node))) |child| {
|
||||
// std.debug.warn("IDENTIFIER {}\n", .{analysis_ctx.tree.getNodeSource(node)});
|
||||
if (getChildOfSlice(analysis_ctx.tree, analysis_ctx.scope_nodes, analysis_ctx.tree.getNodeSource(node))) |child| {
|
||||
// std.debug.warn("CHILD {}\n", .{child});
|
||||
return resolveTypeOfNode(analysis_ctx, child);
|
||||
} else return null;
|
||||
},
|
||||
@ -218,13 +273,14 @@ pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.
|
||||
.SuffixOp => {
|
||||
const suffix_op = node.cast(ast.Node.SuffixOp).?;
|
||||
switch (suffix_op.op) {
|
||||
.Call => {
|
||||
const func_decl = resolveTypeOfNode(analysis_ctx, suffix_op.lhs.node) orelse return null;
|
||||
.Call, .StructInitializer => {
|
||||
const func_decl = resolveTypeOfNode(analysis_ctx, suffix_op.lhs.node) orelse return null;
|
||||
|
||||
if (func_decl.id == .FnProto) {
|
||||
const func = node.cast(ast.Node.FnProto).?;
|
||||
switch (func.return_type) {
|
||||
.Explicit, .InferErrorSet => |return_type| return resolveTypeOfNode(analysis_ctx, return_type),
|
||||
.Invalid => {},
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -246,15 +302,7 @@ pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.
|
||||
// Use the analysis context temporary arena to store the rhs string.
|
||||
rhs_str = std.mem.dupe(&analysis_ctx.arena.allocator, u8, rhs_str) catch return null;
|
||||
const left = resolveTypeOfNode(analysis_ctx, infix_op.lhs) orelse return null;
|
||||
std.debug.warn("InfixOp left = {}\n", .{left});
|
||||
const child = getChild(analysis_ctx.tree, left, rhs_str) orelse return null;
|
||||
std.debug.warn("InfixOp child = {}\n", .{child});
|
||||
|
||||
const right_type = resolveTypeOfNode(analysis_ctx, child);
|
||||
|
||||
std.debug.warn("InfixOp rightType = {}\n", .{right_type});
|
||||
|
||||
return right_type;
|
||||
return resolveTypeOfNode(analysis_ctx, getChild(analysis_ctx.tree, left, rhs_str) orelse return null);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@ -262,8 +310,14 @@ pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.
|
||||
.PrefixOp => {
|
||||
const prefix_op = node.cast(ast.Node.PrefixOp).?;
|
||||
switch (prefix_op.op) {
|
||||
.SliceType, .ArrayType => return node,
|
||||
.PtrType => {
|
||||
return resolveTypeOfNode(analysis_ctx, prefix_op.rhs);
|
||||
const op_token = analysis_ctx.tree.tokens.at(prefix_op.op_token);
|
||||
switch (op_token.id) {
|
||||
.Asterisk => return resolveTypeOfNode(analysis_ctx, prefix_op.rhs),
|
||||
.LBracket, .AsteriskAsterisk => return null,
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@ -279,10 +333,13 @@ pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.
|
||||
|
||||
const import_str = analysis_ctx.tree.tokenSlice(import_param.cast(ast.Node.StringLiteral).?.token);
|
||||
return analysis_ctx.onImport(import_str[1 .. import_str.len - 1]) catch |err| block: {
|
||||
std.debug.warn("Error {} while proessing import {}\n", .{ err, import_str });
|
||||
std.debug.warn("Error {} while processing import {}\n", .{ err, import_str });
|
||||
break :block null;
|
||||
};
|
||||
},
|
||||
.MultilineStringLiteral, .StringLiteral => {
|
||||
return node;
|
||||
},
|
||||
else => {
|
||||
std.debug.warn("Type resolution case not implemented; {}\n", .{node.id});
|
||||
},
|
||||
@ -347,7 +404,7 @@ pub fn getFieldAccessTypeNode(analysis_ctx: *AnalysisContext, tokenizer: *std.zi
|
||||
.Identifier => {
|
||||
// var root = current_node.cast(ast.Node.Root).?;
|
||||
// current_node.
|
||||
if (getChild(analysis_ctx.tree, current_node, tokenizer.buffer[next.start..next.end])) |child| {
|
||||
if (getChildOfSlice(analysis_ctx.tree, analysis_ctx.scope_nodes, tokenizer.buffer[next.start..next.end])) |child| {
|
||||
if (resolveTypeOfNode(analysis_ctx, child)) |node_type| {
|
||||
current_node = node_type;
|
||||
} else return null;
|
||||
@ -378,25 +435,14 @@ pub fn isNodePublic(tree: *ast.Tree, node: *ast.Node) bool {
|
||||
switch (node.id) {
|
||||
.VarDecl => {
|
||||
const var_decl = node.cast(ast.Node.VarDecl).?;
|
||||
if (var_decl.visib_token) |visib_token| {
|
||||
return std.mem.eql(u8, tree.tokenSlice(visib_token), "pub");
|
||||
} else return false;
|
||||
return var_decl.visib_token != null;
|
||||
},
|
||||
.FnProto => {
|
||||
const func = node.cast(ast.Node.FnProto).?;
|
||||
if (func.visib_token) |visib_token| {
|
||||
return std.mem.eql(u8, tree.tokenSlice(visib_token), "pub");
|
||||
} else return false;
|
||||
},
|
||||
.ContainerField => {
|
||||
return true;
|
||||
},
|
||||
else => {
|
||||
return false;
|
||||
return func.visib_token != null;
|
||||
},
|
||||
else => return true,
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn nodeToString(tree: *ast.Tree, node: *ast.Node) ?[]const u8 {
|
||||
@ -426,3 +472,71 @@ pub fn nodeToString(tree: *ast.Tree, node: *ast.Node) ?[]const u8 {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn declsFromIndexInternal(allocator: *std.mem.Allocator, tree: *ast.Tree, node: *ast.Node, nodes: *std.ArrayList(*ast.Node)) anyerror!void {
|
||||
switch (node.id) {
|
||||
.FnProto => {
|
||||
const func = node.cast(ast.Node.FnProto).?;
|
||||
|
||||
var param_index: usize = 0;
|
||||
while (param_index < func.params.len) : (param_index += 1)
|
||||
try declsFromIndexInternal(allocator, tree, func.params.at(param_index).*, nodes);
|
||||
|
||||
if (func.body_node) |body_node|
|
||||
try declsFromIndexInternal(allocator, tree, body_node, nodes);
|
||||
},
|
||||
.Block => {
|
||||
var index: usize = 0;
|
||||
|
||||
while (node.iterate(index)) |inode| {
|
||||
try declsFromIndexInternal(allocator, tree, inode, nodes);
|
||||
index += 1;
|
||||
}
|
||||
},
|
||||
.VarDecl => {
|
||||
try nodes.append(node);
|
||||
},
|
||||
.ParamDecl => {
|
||||
try nodes.append(node);
|
||||
},
|
||||
else => {
|
||||
try nodes.appendSlice(try getCompletionsFromNode(allocator, tree, node));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getCompletionsFromNode(allocator: *std.mem.Allocator, tree: *ast.Tree, node: *ast.Node) ![]*ast.Node {
|
||||
var nodes = std.ArrayList(*ast.Node).init(allocator);
|
||||
|
||||
var index: usize = 0;
|
||||
while (node.iterate(index)) |child_node| {
|
||||
try nodes.append(child_node);
|
||||
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return nodes.items;
|
||||
}
|
||||
|
||||
pub fn declsFromIndex(allocator: *std.mem.Allocator, tree: *ast.Tree, index: usize) ![]*ast.Node {
|
||||
var iindex: usize = 0;
|
||||
|
||||
var node = &tree.root_node.base;
|
||||
var nodes = std.ArrayList(*ast.Node).init(allocator);
|
||||
|
||||
try nodes.appendSlice(try getCompletionsFromNode(allocator, tree, node));
|
||||
|
||||
while (node.iterate(iindex)) |inode| {
|
||||
if (tree.tokens.at(inode.firstToken()).start < index and index < tree.tokens.at(inode.lastToken()).start) {
|
||||
try declsFromIndexInternal(allocator, tree, inode, &nodes);
|
||||
}
|
||||
|
||||
iindex += 1;
|
||||
}
|
||||
|
||||
if (tree.tokens.at(node.firstToken()).start < index and index < tree.tokens.at(node.lastToken()).start) {
|
||||
return nodes.items;
|
||||
}
|
||||
|
||||
return nodes.items;
|
||||
}
|
||||
|
@ -5,3 +5,7 @@ enable_snippets: bool = true,
|
||||
|
||||
/// zig library path
|
||||
zig_lib_path: ?[]const u8 = null,
|
||||
|
||||
/// Whether to pay attention to style issues. This is opt-in since the style
|
||||
/// guide explicitly states that the style info provided is a guideline only.
|
||||
warn_style: bool = false,
|
||||
|
124
src/debug_allocator.zig
Normal file
124
src/debug_allocator.zig
Normal file
@ -0,0 +1,124 @@
|
||||
//! This allocator collects information about allocation sizes
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const DebugAllocator = @This();
|
||||
|
||||
fn toMB(value: var) f64 {
|
||||
return switch (@TypeOf(value)) {
|
||||
f64 => value / (1024 * 1024),
|
||||
else => @intToFloat(f64, value) / (1024 * 1024),
|
||||
};
|
||||
}
|
||||
|
||||
const Stats = struct {
|
||||
mean: f64 = 0,
|
||||
mean_of_squares: f64 = 0,
|
||||
total: usize = 0,
|
||||
count: usize = 0,
|
||||
|
||||
fn addSample(self: *Stats, value: usize) void {
|
||||
const count_f64 = @intToFloat(f64, self.count);
|
||||
self.mean = (self.mean * count_f64 + @intToFloat(f64, value)) / (count_f64 + 1);
|
||||
self.mean_of_squares = (self.mean_of_squares * count_f64 + @intToFloat(f64, value * value)) / (count_f64 + 1);
|
||||
self.total += value;
|
||||
self.count += 1;
|
||||
}
|
||||
|
||||
fn stdDev(self: Stats) f64 {
|
||||
return std.math.sqrt(self.mean_of_squares - self.mean * self.mean);
|
||||
}
|
||||
};
|
||||
|
||||
pub const AllocationInfo = struct {
|
||||
allocation_stats: Stats = Stats{},
|
||||
deallocation_count: usize = 0,
|
||||
deallocation_total: usize = 0,
|
||||
|
||||
reallocation_stats: Stats = Stats{},
|
||||
shrink_stats: Stats = Stats{},
|
||||
|
||||
pub fn format(
|
||||
self: AllocationInfo,
|
||||
comptime fmt: []const u8,
|
||||
options: std.fmt.FormatOptions,
|
||||
out_stream: var,
|
||||
) !void {
|
||||
@setEvalBranchQuota(2000);
|
||||
|
||||
// TODO: Make these behave like {Bi}, which doesnt work on floating point numbers.
|
||||
return std.fmt.format(
|
||||
out_stream,
|
||||
\\------------------------------------------ Allocation info ------------------------------------------
|
||||
\\{} total allocations (total: {d:.2} MB, mean: {d:.2} MB, std. dev: {d:.2} MB), {} deallocations
|
||||
\\{} current allocations ({d:.2} MB)
|
||||
\\{} reallocations (total: {d:.2} MB, mean: {d:.2} MB, std. dev: {d:.2} MB)
|
||||
\\{} shrinks (total: {d:.2} MB, mean: {d:.2} MB, std. dev: {d:.2} MB)
|
||||
\\-----------------------------------------------------------------------------------------------------
|
||||
,
|
||||
.{
|
||||
self.allocation_stats.count,
|
||||
toMB(self.allocation_stats.total),
|
||||
toMB(self.allocation_stats.mean),
|
||||
toMB(self.allocation_stats.stdDev()),
|
||||
self.deallocation_count,
|
||||
self.allocation_stats.count - self.deallocation_count,
|
||||
toMB(self.allocation_stats.total + self.reallocation_stats.total - self.deallocation_total - self.shrink_stats.total),
|
||||
self.reallocation_stats.count,
|
||||
toMB(self.reallocation_stats.total),
|
||||
toMB(self.reallocation_stats.mean),
|
||||
toMB(self.reallocation_stats.stdDev()),
|
||||
self.shrink_stats.count,
|
||||
toMB(self.shrink_stats.total),
|
||||
toMB(self.shrink_stats.mean),
|
||||
toMB(self.shrink_stats.stdDev()),
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
base_allocator: *std.mem.Allocator,
|
||||
info: AllocationInfo,
|
||||
|
||||
// Interface implementation
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn init(base_allocator: *std.mem.Allocator) DebugAllocator {
|
||||
return .{
|
||||
.base_allocator = base_allocator,
|
||||
.info = .{},
|
||||
.allocator = .{
|
||||
.reallocFn = realloc,
|
||||
.shrinkFn = shrink,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn realloc(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
|
||||
const self = @fieldParentPtr(DebugAllocator, "allocator", allocator);
|
||||
var data = try self.base_allocator.reallocFn(self.base_allocator, old_mem, old_align, new_size, new_align);
|
||||
if (old_mem.len == 0) {
|
||||
self.info.allocation_stats.addSample(new_size);
|
||||
} else if (new_size > old_mem.len) {
|
||||
self.info.reallocation_stats.addSample(new_size - old_mem.len);
|
||||
} else if (new_size < old_mem.len) {
|
||||
self.info.shrink_stats.addSample(old_mem.len - new_size);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
fn shrink(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
|
||||
const self = @fieldParentPtr(DebugAllocator, "allocator", allocator);
|
||||
if (new_size == 0) {
|
||||
if (self.info.allocation_stats.count == self.info.deallocation_count) {
|
||||
@panic("error - too many calls to free, most likely double free");
|
||||
}
|
||||
self.info.deallocation_total += old_mem.len;
|
||||
self.info.deallocation_count += 1;
|
||||
} else if (new_size < old_mem.len) {
|
||||
self.info.shrink_stats.addSample(old_mem.len - new_size);
|
||||
} else if (new_size > old_mem.len) {
|
||||
@panic("error - trying to shrink to a bigger size");
|
||||
}
|
||||
return self.base_allocator.shrinkFn(self.base_allocator, old_mem, old_align, new_size, new_align);
|
||||
}
|
@ -14,24 +14,10 @@ pub const Handle = struct {
|
||||
return handle.document.uri;
|
||||
}
|
||||
|
||||
/// Returns the zig AST resulting from parsing the document's text, even
|
||||
/// if it contains errors.
|
||||
pub fn dirtyTree(handle: Handle, allocator: *std.mem.Allocator) !*std.zig.ast.Tree {
|
||||
/// Returns a zig AST, with all its errors.
|
||||
pub fn tree(handle: Handle, allocator: *std.mem.Allocator) !*std.zig.ast.Tree {
|
||||
return try std.zig.parse(allocator, handle.document.text);
|
||||
}
|
||||
|
||||
/// Returns a zig AST with no errors, either from the current text or
|
||||
/// the stored sane text, null if no such ast exists.
|
||||
pub fn saneTree(handle: Handle, allocator: *std.mem.Allocator) !?*std.zig.ast.Tree {
|
||||
var tree = try std.zig.parse(allocator, handle.document.text);
|
||||
if (tree.errors.len == 0) return tree;
|
||||
|
||||
tree.deinit();
|
||||
if (handle.document.sane_text) |sane| {
|
||||
return try std.zig.parse(allocator, sane);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
allocator: *std.mem.Allocator,
|
||||
@ -61,32 +47,23 @@ pub fn init(self: *DocumentStore, allocator: *std.mem.Allocator, zig_lib_path: ?
|
||||
}
|
||||
}
|
||||
|
||||
/// This function assersts the document is not open yet and takes owneship
|
||||
/// This function asserts the document is not open yet and takes ownership
|
||||
/// of the uri and text passed in.
|
||||
fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) !*Handle {
|
||||
std.debug.warn("Opened document: {}\n", .{uri});
|
||||
|
||||
errdefer {
|
||||
self.allocator.free(uri);
|
||||
self.allocator.free(text);
|
||||
}
|
||||
|
||||
var handle = try self.allocator.create(Handle);
|
||||
errdefer self.allocator.destroy(handle);
|
||||
|
||||
handle.* = Handle{
|
||||
var handle = Handle{
|
||||
.count = 1,
|
||||
.import_uris = std.ArrayList([]const u8).init(self.allocator),
|
||||
.document = .{
|
||||
.uri = uri,
|
||||
.text = text,
|
||||
.mem = text,
|
||||
.sane_text = null,
|
||||
},
|
||||
};
|
||||
try self.checkSanity(handle);
|
||||
try self.handles.putNoClobber(uri, handle);
|
||||
return (self.handles.get(uri) orelse unreachable).value;
|
||||
try self.checkSanity(&handle);
|
||||
const kv = try self.handles.getOrPutValue(uri, handle);
|
||||
return &kv.value;
|
||||
}
|
||||
|
||||
pub fn openDocument(self: *DocumentStore, uri: []const u8, text: []const u8) !*Handle {
|
||||
@ -102,7 +79,7 @@ pub fn openDocument(self: *DocumentStore, uri: []const u8, text: []const u8) !*H
|
||||
const duped_uri = try std.mem.dupe(self.allocator, u8, uri);
|
||||
errdefer self.allocator.free(duped_uri);
|
||||
|
||||
return self.newDocument(duped_uri, duped_text);
|
||||
return try self.newDocument(duped_uri, duped_text);
|
||||
}
|
||||
|
||||
fn decrementCount(self: *DocumentStore, uri: []const u8) void {
|
||||
@ -113,9 +90,6 @@ fn decrementCount(self: *DocumentStore, uri: []const u8) void {
|
||||
|
||||
std.debug.warn("Freeing document: {}\n", .{uri});
|
||||
self.allocator.free(entry.value.document.mem);
|
||||
if (entry.value.document.sane_text) |sane| {
|
||||
self.allocator.free(sane);
|
||||
}
|
||||
|
||||
for (entry.value.import_uris.items) |import_uri| {
|
||||
self.decrementCount(import_uri);
|
||||
@ -145,18 +119,10 @@ pub fn getHandle(self: *DocumentStore, uri: []const u8) ?*Handle {
|
||||
|
||||
// Check if the document text is now sane, move it to sane_text if so.
|
||||
fn checkSanity(self: *DocumentStore, handle: *Handle) !void {
|
||||
const dirty_tree = try handle.dirtyTree(self.allocator);
|
||||
defer dirty_tree.deinit();
|
||||
|
||||
if (dirty_tree.errors.len > 0) return;
|
||||
|
||||
std.debug.warn("New sane text for document {}\n", .{handle.uri()});
|
||||
if (handle.document.sane_text) |sane| {
|
||||
self.allocator.free(sane);
|
||||
}
|
||||
|
||||
handle.document.sane_text = try std.mem.dupe(self.allocator, u8, handle.document.text);
|
||||
const tree = try handle.tree(self.allocator);
|
||||
defer tree.deinit();
|
||||
|
||||
std.debug.warn("New text for document {}\n", .{handle.uri()});
|
||||
// TODO: Better algorithm or data structure?
|
||||
// Removing the imports is costly since they live in an array list
|
||||
// Perhaps we should use an AutoHashMap([]const u8, {}) ?
|
||||
@ -164,7 +130,7 @@ fn checkSanity(self: *DocumentStore, handle: *Handle) !void {
|
||||
// Try to detect removed imports and decrement their counts.
|
||||
if (handle.import_uris.items.len == 0) return;
|
||||
|
||||
const import_strs = try analysis.collectImports(self.allocator, dirty_tree);
|
||||
const import_strs = try analysis.collectImports(self.allocator, tree);
|
||||
defer self.allocator.free(import_strs);
|
||||
|
||||
const still_exist = try self.allocator.alloc(bool, handle.import_uris.items.len);
|
||||
@ -175,7 +141,7 @@ fn checkSanity(self: *DocumentStore, handle: *Handle) !void {
|
||||
}
|
||||
|
||||
for (import_strs) |str| {
|
||||
const uri = (try uriFromImportStr(self, handle, str)) orelse continue;
|
||||
const uri = (try uriFromImportStr(self, handle.*, str)) orelse continue;
|
||||
defer self.allocator.free(uri);
|
||||
|
||||
var idx: usize = 0;
|
||||
@ -205,7 +171,7 @@ fn checkSanity(self: *DocumentStore, handle: *Handle) !void {
|
||||
}
|
||||
|
||||
pub fn applyChanges(self: *DocumentStore, handle: *Handle, content_changes: std.json.Array) !void {
|
||||
var document = &handle.document;
|
||||
const document = &handle.document;
|
||||
|
||||
for (content_changes.items) |change| {
|
||||
if (change.Object.getValue("range")) |range| {
|
||||
@ -260,7 +226,7 @@ pub fn applyChanges(self: *DocumentStore, handle: *Handle, content_changes: std.
|
||||
try self.checkSanity(handle);
|
||||
}
|
||||
|
||||
fn uriFromImportStr(store: *DocumentStore, handle: *Handle, import_str: []const u8) !?[]const u8 {
|
||||
fn uriFromImportStr(store: *DocumentStore, handle: Handle, import_str: []const u8) !?[]const u8 {
|
||||
return if (std.mem.eql(u8, import_str, "std"))
|
||||
if (store.std_uri) |std_root_uri| try std.mem.dupe(store.allocator, u8, std_root_uri) else {
|
||||
std.debug.warn("Cannot resolve std library import, path is null.\n", .{});
|
||||
@ -289,10 +255,11 @@ pub const AnalysisContext = struct {
|
||||
// not for the tree allocations.
|
||||
arena: *std.heap.ArenaAllocator,
|
||||
tree: *std.zig.ast.Tree,
|
||||
scope_nodes: []*std.zig.ast.Node,
|
||||
|
||||
pub fn onImport(self: *AnalysisContext, import_str: []const u8) !?*std.zig.ast.Node {
|
||||
const allocator = self.store.allocator;
|
||||
const final_uri = (try uriFromImportStr(self.store, self.handle, import_str)) orelse return null;
|
||||
const final_uri = (try uriFromImportStr(self.store, self.handle.*, import_str)) orelse return null;
|
||||
|
||||
std.debug.warn("Import final URI: {}\n", .{final_uri});
|
||||
var consumed_final_uri = false;
|
||||
@ -305,11 +272,8 @@ pub const AnalysisContext = struct {
|
||||
self.handle = self.store.getHandle(final_uri) orelse return null;
|
||||
|
||||
self.tree.deinit();
|
||||
if (try self.handle.saneTree(allocator)) |tree| {
|
||||
self.tree = tree;
|
||||
return &self.tree.root_node.base;
|
||||
}
|
||||
return null;
|
||||
self.tree = try self.handle.tree(allocator);
|
||||
return &self.tree.root_node.base;
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,11 +285,8 @@ pub const AnalysisContext = struct {
|
||||
self.handle = new_handle;
|
||||
|
||||
self.tree.deinit();
|
||||
if (try self.handle.saneTree(allocator)) |tree| {
|
||||
self.tree = tree;
|
||||
return &self.tree.root_node.base;
|
||||
}
|
||||
return null;
|
||||
self.tree = try self.handle.tree(allocator);
|
||||
return &self.tree.root_node.base;
|
||||
}
|
||||
|
||||
// New document, read the file then call into openDocument.
|
||||
@ -355,17 +316,16 @@ pub const AnalysisContext = struct {
|
||||
|
||||
// Swap handles and get new tree.
|
||||
// This takes ownership of the passed uri and text.
|
||||
self.handle = try newDocument(self.store, try std.mem.dupe(allocator, u8, final_uri), file_contents);
|
||||
const duped_final_uri = try std.mem.dupe(allocator, u8, final_uri);
|
||||
errdefer allocator.free(duped_final_uri);
|
||||
self.handle = try newDocument(self.store, duped_final_uri, file_contents);
|
||||
}
|
||||
|
||||
// Free old tree, add new one if it exists.
|
||||
// If we return null, no one should access the tree.
|
||||
self.tree.deinit();
|
||||
if (try self.handle.saneTree(allocator)) |tree| {
|
||||
self.tree = tree;
|
||||
return &self.tree.root_node.base;
|
||||
}
|
||||
return null;
|
||||
self.tree = try self.handle.tree(allocator);
|
||||
return &self.tree.root_node.base;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *AnalysisContext) void {
|
||||
@ -373,14 +333,14 @@ pub const AnalysisContext = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn analysisContext(self: *DocumentStore, handle: *Handle, arena: *std.heap.ArenaAllocator) !?AnalysisContext {
|
||||
const tree = (try handle.saneTree(self.allocator)) orelse return null;
|
||||
|
||||
pub fn analysisContext(self: *DocumentStore, handle: *Handle, arena: *std.heap.ArenaAllocator, position: types.Position) !AnalysisContext {
|
||||
const tree = try handle.tree(self.allocator);
|
||||
return AnalysisContext{
|
||||
.store = self,
|
||||
.handle = handle,
|
||||
.arena = arena,
|
||||
.tree = tree,
|
||||
.scope_nodes = try analysis.declsFromIndex(&arena.allocator, tree, try handle.document.positionToIndex(position))
|
||||
};
|
||||
}
|
||||
|
||||
@ -388,9 +348,6 @@ pub fn deinit(self: *DocumentStore) void {
|
||||
var entry_iterator = self.handles.iterator();
|
||||
while (entry_iterator.next()) |entry| {
|
||||
self.allocator.free(entry.value.document.mem);
|
||||
if (entry.value.document.sane_text) |sane| {
|
||||
self.allocator.free(sane);
|
||||
}
|
||||
|
||||
for (entry.value.import_uris.items) |uri| {
|
||||
self.allocator.free(uri);
|
||||
|
44
src/header.zig
Normal file
44
src/header.zig
Normal file
@ -0,0 +1,44 @@
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
|
||||
const RequestHeader = struct {
|
||||
content_length: usize,
|
||||
|
||||
/// null implies "application/vscode-jsonrpc; charset=utf-8"
|
||||
content_type: ?[]const u8,
|
||||
|
||||
pub fn deinit(self: @This(), allocator: *mem.Allocator) void {
|
||||
if (self.content_type) |ct| allocator.free(ct);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn readRequestHeader(allocator: *mem.Allocator, instream: var) !RequestHeader {
|
||||
var r = RequestHeader{
|
||||
.content_length = undefined,
|
||||
.content_type = null,
|
||||
};
|
||||
errdefer r.deinit(allocator);
|
||||
|
||||
var has_content_length = false;
|
||||
while (true) {
|
||||
const header = try instream.readUntilDelimiterAlloc(allocator, '\n', 0x100);
|
||||
defer allocator.free(header);
|
||||
if (header.len == 0 or header[header.len - 1] != '\r') return error.MissingCarriageReturn;
|
||||
if (header.len == 1) break;
|
||||
|
||||
const header_name = header[0..mem.indexOf(u8, header, ": ") orelse return error.MissingColon];
|
||||
const header_value = header[header_name.len + 2..header.len-1];
|
||||
if (mem.eql(u8, header_name, "Content-Length")) {
|
||||
if (header_value.len == 0) return error.MissingHeaderValue;
|
||||
r.content_length = std.fmt.parseInt(usize, header_value, 10) catch return error.InvalidContentLength;
|
||||
has_content_length = true;
|
||||
} else if (mem.eql(u8, header_name, "Content-Type")) {
|
||||
r.content_type = try mem.dupe(allocator, u8, header_value);
|
||||
} else {
|
||||
return error.UnknownHeader;
|
||||
}
|
||||
}
|
||||
if (!has_content_length) return error.MissingContentLength;
|
||||
|
||||
return r;
|
||||
}
|
275
src/main.zig
275
src/main.zig
@ -3,6 +3,8 @@ const build_options = @import("build_options");
|
||||
|
||||
const Config = @import("config.zig");
|
||||
const DocumentStore = @import("document_store.zig");
|
||||
const DebugAllocator = @import("debug_allocator.zig");
|
||||
const readRequestHeader = @import("header.zig").readRequestHeader;
|
||||
const data = @import("data/" ++ build_options.data_version ++ ".zig");
|
||||
const types = @import("types.zig");
|
||||
const analysis = @import("analysis.zig");
|
||||
@ -99,7 +101,7 @@ fn astLocationToRange(loc: std.zig.ast.Tree.Location) types.Range {
|
||||
}
|
||||
|
||||
fn publishDiagnostics(handle: DocumentStore.Handle, config: Config) !void {
|
||||
const tree = try handle.dirtyTree(allocator);
|
||||
const tree = try handle.tree(allocator);
|
||||
defer tree.deinit();
|
||||
|
||||
// Use an arena for our local memory allocations.
|
||||
@ -137,34 +139,30 @@ fn publishDiagnostics(handle: DocumentStore.Handle, config: Config) !void {
|
||||
if (is_extern)
|
||||
break :blk;
|
||||
|
||||
if (func.name_token) |name_token| {
|
||||
const loc = tree.tokenLocation(0, name_token);
|
||||
if (config.warn_style) {
|
||||
if (func.name_token) |name_token| {
|
||||
const loc = tree.tokenLocation(0, name_token);
|
||||
|
||||
const is_type_function = switch (func.return_type) {
|
||||
.Explicit => |node| if (node.cast(std.zig.ast.Node.Identifier)) |ident|
|
||||
std.mem.eql(u8, tree.tokenSlice(ident.token), "type")
|
||||
else
|
||||
false,
|
||||
.InferErrorSet => false,
|
||||
};
|
||||
const is_type_function = analysis.isTypeFunction(tree, func);
|
||||
|
||||
const func_name = tree.tokenSlice(name_token);
|
||||
if (!is_type_function and !analysis.isCamelCase(func_name)) {
|
||||
try diagnostics.append(.{
|
||||
.range = astLocationToRange(loc),
|
||||
.severity = .Information,
|
||||
.code = "BadStyle",
|
||||
.source = "zls",
|
||||
.message = "Functions should be camelCase",
|
||||
});
|
||||
} else if (is_type_function and !analysis.isPascalCase(func_name)) {
|
||||
try diagnostics.append(.{
|
||||
.range = astLocationToRange(loc),
|
||||
.severity = .Information,
|
||||
.code = "BadStyle",
|
||||
.source = "zls",
|
||||
.message = "Type functions should be PascalCase",
|
||||
});
|
||||
const func_name = tree.tokenSlice(name_token);
|
||||
if (!is_type_function and !analysis.isCamelCase(func_name)) {
|
||||
try diagnostics.append(.{
|
||||
.range = astLocationToRange(loc),
|
||||
.severity = .Information,
|
||||
.code = "BadStyle",
|
||||
.source = "zls",
|
||||
.message = "Functions should be camelCase",
|
||||
});
|
||||
} else if (is_type_function and !analysis.isPascalCase(func_name)) {
|
||||
try diagnostics.append(.{
|
||||
.range = astLocationToRange(loc),
|
||||
.severity = .Information,
|
||||
.code = "BadStyle",
|
||||
.source = "zls",
|
||||
.message = "Type functions should be PascalCase",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -184,8 +182,17 @@ fn publishDiagnostics(handle: DocumentStore.Handle, config: Config) !void {
|
||||
});
|
||||
}
|
||||
|
||||
fn nodeToCompletion(alloc: *std.mem.Allocator, tree: *std.zig.ast.Tree, decl: *std.zig.ast.Node, config: Config) !?types.CompletionItem {
|
||||
var doc = if (try analysis.getDocComments(alloc, tree, decl)) |doc_comments|
|
||||
fn containerToCompletion(list: *std.ArrayList(types.CompletionItem), tree: *std.zig.ast.Tree, container: *std.zig.ast.Node, config: Config) !void {
|
||||
var index: usize = 0;
|
||||
while (container.iterate(index)) |child_node| : (index+=1) {
|
||||
if (analysis.isNodePublic(tree, child_node)) {
|
||||
try nodeToCompletion(list, tree, child_node, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn nodeToCompletion(list: *std.ArrayList(types.CompletionItem), tree: *std.zig.ast.Tree, node: *std.zig.ast.Node, config: Config) error{OutOfMemory}!void {
|
||||
var doc = if (try analysis.getDocComments(list.allocator, tree, node)) |doc_comments|
|
||||
types.MarkupContent{
|
||||
.kind = .Markdown,
|
||||
.value = doc_comments,
|
||||
@ -193,48 +200,78 @@ fn nodeToCompletion(alloc: *std.mem.Allocator, tree: *std.zig.ast.Tree, decl: *s
|
||||
else
|
||||
null;
|
||||
|
||||
switch (decl.id) {
|
||||
switch (node.id) {
|
||||
.ErrorSetDecl, .Root, .ContainerDecl => {
|
||||
try containerToCompletion(list, tree, node, config);
|
||||
},
|
||||
.FnProto => {
|
||||
const func = decl.cast(std.zig.ast.Node.FnProto).?;
|
||||
const func = node.cast(std.zig.ast.Node.FnProto).?;
|
||||
if (func.name_token) |name_token| {
|
||||
const insert_text = if (config.enable_snippets)
|
||||
try analysis.getFunctionSnippet(alloc, tree, func)
|
||||
try analysis.getFunctionSnippet(list.allocator, tree, func)
|
||||
else
|
||||
null;
|
||||
|
||||
return types.CompletionItem{
|
||||
const is_type_function = analysis.isTypeFunction(tree, func);
|
||||
|
||||
try list.append(.{
|
||||
.label = tree.tokenSlice(name_token),
|
||||
.kind = .Function,
|
||||
.kind = if (is_type_function) .Struct else .Function,
|
||||
.documentation = doc,
|
||||
.detail = analysis.getFunctionSignature(tree, func),
|
||||
.insertText = insert_text,
|
||||
.insertTextFormat = if (config.enable_snippets) .Snippet else .PlainText,
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
.VarDecl => {
|
||||
const var_decl = decl.cast(std.zig.ast.Node.VarDecl).?;
|
||||
return types.CompletionItem{
|
||||
const var_decl = node.cast(std.zig.ast.Node.VarDecl).?;
|
||||
const is_const = tree.tokens.at(var_decl.mut_token).id == .Keyword_const;
|
||||
try list.append(.{
|
||||
.label = tree.tokenSlice(var_decl.name_token),
|
||||
.kind = .Variable,
|
||||
.kind = if (is_const) .Constant else .Variable,
|
||||
.documentation = doc,
|
||||
.detail = analysis.getVariableSignature(tree, var_decl),
|
||||
};
|
||||
});
|
||||
},
|
||||
else => if (analysis.nodeToString(tree, decl)) |string| {
|
||||
return types.CompletionItem{
|
||||
.ParamDecl => {
|
||||
const param = node.cast(std.zig.ast.Node.ParamDecl).?;
|
||||
if (param.name_token) |name_token|
|
||||
try list.append(.{
|
||||
.label = tree.tokenSlice(name_token),
|
||||
.kind = .Constant,
|
||||
.documentation = doc,
|
||||
.detail = analysis.getParamSignature(tree, param),
|
||||
});
|
||||
},
|
||||
.PrefixOp => {
|
||||
try list.append(.{
|
||||
.label = "len",
|
||||
.kind = .Field,
|
||||
});
|
||||
try list.append(.{
|
||||
.label = "ptr",
|
||||
.kind = .Field,
|
||||
});
|
||||
},
|
||||
.StringLiteral => {
|
||||
try list.append(.{
|
||||
.label = "len",
|
||||
.kind = .Field,
|
||||
});
|
||||
},
|
||||
else => if (analysis.nodeToString(tree, node)) |string| {
|
||||
try list.append(.{
|
||||
.label = string,
|
||||
.kind = .Field,
|
||||
.documentation = doc,
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn completeGlobal(id: i64, handle: DocumentStore.Handle, config: Config) !void {
|
||||
var tree = (try handle.saneTree(allocator)) orelse return respondGeneric(id, no_completions_response);
|
||||
fn completeGlobal(id: i64, pos_index: usize, handle: DocumentStore.Handle, config: Config) !void {
|
||||
var tree = try handle.tree(allocator);
|
||||
defer tree.deinit();
|
||||
|
||||
// We use a local arena allocator to deallocate all temporary data without iterating
|
||||
@ -243,12 +280,11 @@ fn completeGlobal(id: i64, handle: DocumentStore.Handle, config: Config) !void {
|
||||
// Deallocate all temporary data.
|
||||
defer arena.deinit();
|
||||
|
||||
var decls = tree.root_node.decls.iterator(0);
|
||||
while (decls.next()) |decl_ptr| {
|
||||
// var decls = tree.root_node.decls.iterator(0);
|
||||
var decls = try analysis.declsFromIndex(&arena.allocator, tree, pos_index);
|
||||
for (decls) |decl_ptr| {
|
||||
var decl = decl_ptr.*;
|
||||
if (try nodeToCompletion(&arena.allocator, tree, decl, config)) |completion| {
|
||||
try completions.append(completion);
|
||||
}
|
||||
try nodeToCompletion(&completions, tree, decl_ptr, config);
|
||||
}
|
||||
|
||||
try send(types.Response{
|
||||
@ -266,26 +302,15 @@ fn completeFieldAccess(id: i64, handle: *DocumentStore.Handle, position: types.P
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
var analysis_ctx = (try document_store.analysisContext(handle, &arena)) orelse {
|
||||
return send(types.Response{
|
||||
.id = .{ .Integer = id },
|
||||
.result = .{
|
||||
.CompletionList = .{
|
||||
.isIncomplete = false,
|
||||
.items = &[_]types.CompletionItem{},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
var analysis_ctx = try document_store.analysisContext(handle, &arena, position);
|
||||
defer analysis_ctx.deinit();
|
||||
|
||||
var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator);
|
||||
|
||||
var line = try handle.document.getLine(@intCast(usize, position.line));
|
||||
// handle pointer could change from underneath us, so let's copy the line
|
||||
var line_copy = try std.mem.dupe(&arena.allocator, u8, line[line_start_idx..]);
|
||||
var tokenizer = std.zig.Tokenizer.init(line_copy);
|
||||
const line = try handle.document.getLine(@intCast(usize, position.line));
|
||||
var tokenizer = std.zig.Tokenizer.init(line[line_start_idx..]);
|
||||
|
||||
// var decls = try analysis.declsFromIndex(&arena.allocator, analysis_ctx.tree, try handle.document.positionToIndex(position));
|
||||
if (analysis.getFieldAccessTypeNode(&analysis_ctx, &tokenizer)) |node| {
|
||||
var index: usize = 0;
|
||||
while (node.iterate(index)) |child_node| {
|
||||
@ -579,7 +604,7 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v
|
||||
},
|
||||
},
|
||||
}),
|
||||
.var_access, .empty => try completeGlobal(id, handle.*, config),
|
||||
.var_access, .empty => try completeGlobal(id, pos_index, handle.*, config),
|
||||
.field_access => |start_idx| try completeFieldAccess(id, handle, pos, start_idx, config),
|
||||
else => try respondGeneric(id, no_completions_response),
|
||||
}
|
||||
@ -607,20 +632,20 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v
|
||||
}
|
||||
}
|
||||
|
||||
var debug_alloc_state: std.testing.LeakCountAllocator = undefined;
|
||||
var debug_alloc_state: DebugAllocator = undefined;
|
||||
// We can now use if(leak_count_alloc) |alloc| { ... } as a comptime check.
|
||||
const debug_alloc: ?*std.testing.LeakCountAllocator = if (build_options.allocation_info) &debug_alloc_state else null;
|
||||
const debug_alloc: ?*DebugAllocator = if (build_options.allocation_info) &debug_alloc_state else null;
|
||||
|
||||
pub fn main() anyerror!void {
|
||||
// TODO: Use a better purpose general allocator once std has one.
|
||||
// Probably after the generic composable allocators PR?
|
||||
// This is not too bad for now since most allocations happen in local areans.
|
||||
// This is not too bad for now since most allocations happen in local arenas.
|
||||
allocator = std.heap.page_allocator;
|
||||
|
||||
if (build_options.allocation_info) {
|
||||
// TODO: Use a better debugging allocator, track size in bytes, memory reserved etc..
|
||||
// Initialize the leak counting allocator.
|
||||
debug_alloc_state = std.testing.LeakCountAllocator.init(allocator);
|
||||
debug_alloc_state = DebugAllocator.init(allocator);
|
||||
allocator = &debug_alloc_state.allocator;
|
||||
}
|
||||
|
||||
@ -636,9 +661,10 @@ pub fn main() anyerror!void {
|
||||
const stdin = std.io.getStdIn().inStream();
|
||||
stdout = std.io.getStdOut().outStream();
|
||||
|
||||
// Read he configuration, if any.
|
||||
var config = Config{};
|
||||
// Read the configuration, if any.
|
||||
const config_parse_options = std.json.ParseOptions{ .allocator = allocator };
|
||||
var config = Config{};
|
||||
defer std.json.parseFree(Config, config, config_parse_options);
|
||||
|
||||
// TODO: Investigate using std.fs.Watch to detect writes to the config and reload it.
|
||||
config_read: {
|
||||
@ -648,30 +674,26 @@ pub fn main() anyerror!void {
|
||||
var exec_dir = std.fs.cwd().openDir(exec_dir_path, .{}) catch break :config_read;
|
||||
defer exec_dir.close();
|
||||
|
||||
var conf_file = exec_dir.openFile("zls.json", .{}) catch break :config_read;
|
||||
const conf_file = exec_dir.openFile("zls.json", .{}) catch break :config_read;
|
||||
defer conf_file.close();
|
||||
|
||||
const conf_file_stat = conf_file.stat() catch break :config_read;
|
||||
|
||||
// Allocate enough memory for the whole file.
|
||||
var file_buf = try allocator.alloc(u8, conf_file_stat.size);
|
||||
// Max 1MB
|
||||
const file_buf = conf_file.inStream().readAllAlloc(allocator, 0x1000000) catch break :config_read;
|
||||
defer allocator.free(file_buf);
|
||||
|
||||
const bytes_read = conf_file.readAll(file_buf) catch break :config_read;
|
||||
if (bytes_read != conf_file_stat.size) break :config_read;
|
||||
|
||||
// TODO: Better errors? Doesnt seem like std.json can provide us positions or context.
|
||||
// TODO: Better errors? Doesn't seem like std.json can provide us positions or context.
|
||||
config = std.json.parse(Config, &std.json.TokenStream.init(file_buf), config_parse_options) catch |err| {
|
||||
std.debug.warn("Error while parsing configuration file: {}\nUsing default config.\n", .{err});
|
||||
break :config_read;
|
||||
};
|
||||
}
|
||||
defer std.json.parseFree(Config, config, config_parse_options);
|
||||
|
||||
if (config.zig_lib_path != null and !std.fs.path.isAbsolute(config.zig_lib_path.?)) {
|
||||
std.debug.warn("zig library path is not absolute, defaulting to null.\n", .{});
|
||||
allocator.free(config.zig_lib_path.?);
|
||||
config.zig_lib_path = null;
|
||||
if (config.zig_lib_path) |zig_lib_path| {
|
||||
if (!std.fs.path.isAbsolute(zig_lib_path)) {
|
||||
std.debug.warn("zig library path is not absolute, defaulting to null.\n", .{});
|
||||
allocator.free(zig_lib_path);
|
||||
config.zig_lib_path = null;
|
||||
}
|
||||
}
|
||||
|
||||
try document_store.init(allocator, config.zig_lib_path);
|
||||
@ -681,77 +703,20 @@ pub fn main() anyerror!void {
|
||||
var json_parser = std.json.Parser.init(allocator, false);
|
||||
defer json_parser.deinit();
|
||||
|
||||
var offset: usize = 0;
|
||||
var bytes_read: usize = 0;
|
||||
|
||||
var index: usize = 0;
|
||||
var content_len: usize = 0;
|
||||
|
||||
stdin_poll: while (true) {
|
||||
if (offset >= 16 and std.mem.startsWith(u8, buffer.items, "Content-Length: ")) {
|
||||
index = 16;
|
||||
while (index <= offset + 10) : (index += 1) {
|
||||
const c = buffer.items[index];
|
||||
if (c >= '0' and c <= '9') {
|
||||
content_len = content_len * 10 + (c - '0');
|
||||
}
|
||||
if (c == '\r' and buffer.items[index + 1] == '\n') {
|
||||
index += 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.items[index] == '\r') {
|
||||
index += 2;
|
||||
if (buffer.items.len < index + content_len) {
|
||||
try buffer.resize(index + content_len);
|
||||
}
|
||||
|
||||
body_poll: while (offset < content_len + index) {
|
||||
bytes_read = try stdin.readAll(buffer.items[offset .. index + content_len]);
|
||||
if (bytes_read == 0) {
|
||||
try log("0 bytes read; exiting!", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
offset += bytes_read;
|
||||
}
|
||||
|
||||
try processJsonRpc(&json_parser, buffer.items[index .. index + content_len], config);
|
||||
json_parser.reset();
|
||||
|
||||
offset = 0;
|
||||
content_len = 0;
|
||||
} else {
|
||||
try log("\\r not found", .{});
|
||||
}
|
||||
} else if (offset >= 16) {
|
||||
try log("Offset is greater than 16!", .{});
|
||||
while (true) {
|
||||
const headers = readRequestHeader(allocator, stdin) catch |err| {
|
||||
try log("{}; exiting!", .{@errorName(err)});
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset < 16) {
|
||||
bytes_read = try stdin.readAll(buffer.items[offset..25]);
|
||||
} else {
|
||||
if (offset == buffer.items.len) {
|
||||
try buffer.resize(buffer.items.len * 2);
|
||||
}
|
||||
if (index + content_len > buffer.items.len) {
|
||||
bytes_read = try stdin.readAll(buffer.items[offset..buffer.items.len]);
|
||||
} else {
|
||||
bytes_read = try stdin.readAll(buffer.items[offset .. index + content_len]);
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_read == 0) {
|
||||
try log("0 bytes read; exiting!", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
offset += bytes_read;
|
||||
};
|
||||
defer headers.deinit(allocator);
|
||||
const buf = try allocator.alloc(u8, headers.content_length);
|
||||
defer allocator.free(buf);
|
||||
try stdin.readNoEof(buf);
|
||||
try processJsonRpc(&json_parser, buf, config);
|
||||
json_parser.reset();
|
||||
|
||||
if (debug_alloc) |dbg| {
|
||||
try log("Allocations alive: {}", .{dbg.count});
|
||||
try log("{}", .{dbg.info});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,7 +140,6 @@ pub const TextDocument = struct {
|
||||
text: String,
|
||||
// This holds the memory that we have actually allocated.
|
||||
mem: []u8,
|
||||
sane_text: ?String = null,
|
||||
|
||||
pub fn positionToIndex(self: TextDocument, position: Position) !usize {
|
||||
var split_iterator = std.mem.split(self.text, "\n");
|
||||
@ -186,11 +185,11 @@ pub const MarkupKind = enum(u1) {
|
||||
options: json.StringifyOptions,
|
||||
out_stream: var,
|
||||
) !void {
|
||||
if (@enumToInt(value) == 0) {
|
||||
try json.stringify("plaintext", options, out_stream);
|
||||
} else {
|
||||
try json.stringify("markdown", options, out_stream);
|
||||
}
|
||||
const str = switch (value) {
|
||||
.PlainText => "plaintext",
|
||||
.Markdown => "markdown",
|
||||
};
|
||||
try json.stringify(str, options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -14,7 +14,7 @@ pub fn fromPath(allocator: *std.mem.Allocator, path: []const u8) ![]const u8 {
|
||||
var buf = std.ArrayList(u8).init(allocator);
|
||||
try buf.appendSlice(prefix);
|
||||
|
||||
var out_stream = buf.outStream();
|
||||
const out_stream = buf.outStream();
|
||||
|
||||
for (path) |char| {
|
||||
if (char == std.fs.path.sep) {
|
||||
@ -55,17 +55,16 @@ fn parseHex(c: u8) !u8 {
|
||||
pub fn parse(allocator: *std.mem.Allocator, str: []const u8) ![]u8 {
|
||||
if (str.len < 7 or !std.mem.eql(u8, "file://", str[0..7])) return error.UriBadScheme;
|
||||
|
||||
var uri = try allocator.alloc(u8, str.len - (if (std.fs.path.sep == '\\') 8 else 7));
|
||||
const uri = try allocator.alloc(u8, str.len - (if (std.fs.path.sep == '\\') 8 else 7));
|
||||
errdefer allocator.free(uri);
|
||||
|
||||
const path = if (std.fs.path.sep == '\\') str[8..] else str[7..];
|
||||
|
||||
var i: usize = 0;
|
||||
var j: usize = 0;
|
||||
var e: usize = path.len;
|
||||
while (j < e) : (i += 1) {
|
||||
while (j < path.len) : (i += 1) {
|
||||
if (path[j] == '%') {
|
||||
if (j + 2 >= e) return error.UriBadEscape;
|
||||
if (j + 2 >= path.len) return error.UriBadEscape;
|
||||
const upper = try parseHex(path[j + 1]);
|
||||
const lower = try parseHex(path[j + 2]);
|
||||
uri[i] = (upper << 4) + lower;
|
||||
|
Loading…
Reference in New Issue
Block a user