diff --git a/README.md b/README.md index 9722afd..39c7712 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ Zig Language Server, or `zls`, is a language server for Zig. The Zig wiki states ## Installation -Installing `zls` is pretty simple. You will need [a build of Zig master](https://ziglang.org/download/) (or >0.6) to build ZLS. +Installing `zls` is pretty simple. You will need [a build of Zig master](https://ziglang.org/download/) to build zls. ```bash git clone --recurse-submodules https://github.com/zigtools/zls cd zls zig build -# To configure ZLS: +# To configure zls: zig build config ``` @@ -36,7 +36,9 @@ zig build config | Option | Type | Default Value | What it Does | | --- | --- | --- | --- | -| `-Ddata_version` | `string` (master or 0.6.0) | 0.6.0 | The data file version. This selects the files in the `src/data` folder that correspond to the Zig version being served. +| `-Ddata_version` | `string` (master or 0.6.0) | 0.6.0 | The data file version. This selects the files in the `src/data` folder that correspond to the Zig version being served.| +| `-Dallocation_info` | `bool` | `false` | Enable the use of the debug allocator that will print out information in debug mode and track memory leaks.| +| `-Dmax_bytes_allocated` | `usize` | `0` | When `allocation_info` is true, enables a maximum allowed allocation size (excluding stacktraces) before the program panics.| Then, you can use the `zls` executable in an editor of your choice that has a Zig language server client! @@ -57,15 +59,29 @@ The following options are currently available. | `zig_exe_path` | `?[]const u8` | `null` | zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided. | | `warn_style` | `bool` | `false` | Enables warnings for style *guideline* mismatches | | `build_runner_path` | `?[]const u8` | `null` | Path to the build_runner.zig file provided by zls. This option must be present in one of the global configuration files to have any effect. `null` is equivalent to `${executable_directory}/build_runner.zig` | -| `enable_semantic_tokens` | `bool` | false | Enables semantic token support when the client also supports it. | +| `enable_semantic_tokens` | `bool` | `false` | Enables semantic token support when the client also supports it. | +| `operator_completions` | `bool` | `true` | Enables `*` and `?` operators in completion lists. | -## Usage +## Features -`zls` will supercharge your Zig programming experience with autocomplete, function documentation, and more! Follow the instructions for your specific editor below: +`zls` supports most language features, including simple type function support, usingnamespace, payload capture type resolution, custom packages and others. +Notable language features that are not currently implemented include `@cImport` as well as most forms of compile time evaluation. + +The following LSP features are supported: +- Completions +- Hover +- Goto definition/declaration +- Document symbols +- Find references +- Rename symbol +- Formatting using `zig fmt` +- Semantic token highlighting (LSP 3.16 proposed feature, implemented by a few clients including VSCode, kak and emacs lsp-mode) + +You can install `zls` using the instuctions for your text editor below: ### VSCode -Install the `zls-vscode` extension from [here](https://github.com/zigtools/zls-vscode/releases). +Install the `zls-vscode` extension from [here](https://github.com/zigtools/zls-vscode/releases) and provide a path to the build `zls` executable. ### Sublime Text 3 diff --git a/src/analysis.zig b/src/analysis.zig index c3f58b6..60afa56 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1012,13 +1012,18 @@ pub const NodeWithHandle = struct { handle: *DocumentStore.Handle, }; +pub const FieldAccessReturn = struct { + original: TypeWithHandle, + unwrapped: ?TypeWithHandle = null, +}; + pub fn getFieldAccessType( store: *DocumentStore, arena: *std.heap.ArenaAllocator, handle: *DocumentStore.Handle, source_index: usize, tokenizer: *std.zig.Tokenizer, -) !?TypeWithHandle { +) !?FieldAccessReturn { var current_type = TypeWithHandle.typeVal(.{ .node = &handle.tree.root_node.base, .handle = handle, @@ -1030,7 +1035,10 @@ pub fn getFieldAccessType( while (true) { const tok = tokenizer.next(); switch (tok.id) { - .Eof => return try resolveFieldAccessLhsType(store, arena, current_type, &bound_type_params), + .Eof => return FieldAccessReturn{ + .original = current_type, + .unwrapped = try resolveDerefType(store, arena, current_type, &bound_type_params), + }, .Identifier => { if (try lookupSymbolGlobal(store, arena, current_type.handle, tokenizer.buffer[tok.loc.start..tok.loc.end], source_index)) |child| { current_type = (try child.resolveType(store, arena, &bound_type_params)) orelse return null; @@ -1039,10 +1047,17 @@ pub fn getFieldAccessType( .Period => { const after_period = tokenizer.next(); switch (after_period.id) { - .Eof => return try resolveFieldAccessLhsType(store, arena, current_type, &bound_type_params), + .Eof => return FieldAccessReturn{ + .original = current_type, + .unwrapped = try resolveDerefType(store, arena, current_type, &bound_type_params), + }, .Identifier => { - if (after_period.loc.end == tokenizer.buffer.len) - return try resolveFieldAccessLhsType(store, arena, current_type, &bound_type_params); + if (after_period.loc.end == tokenizer.buffer.len) { + return FieldAccessReturn{ + .original = current_type, + .unwrapped = try resolveDerefType(store, arena, current_type, &bound_type_params), + }; + } current_type = try resolveFieldAccessLhsType(store, arena, current_type, &bound_type_params); const current_type_node = switch (current_type.type.data) { @@ -1121,7 +1136,10 @@ pub fn getFieldAccessType( } } - return try resolveFieldAccessLhsType(store, arena, current_type, &bound_type_params); + return FieldAccessReturn{ + .original = current_type, + .unwrapped = try resolveDerefType(store, arena, current_type, &bound_type_params), + }; } pub fn isNodePublic(tree: *ast.Tree, node: *ast.Node) bool { @@ -1428,7 +1446,7 @@ fn getDocumentSymbolsInternal(allocator: *std.mem.Allocator, tree: *ast.Tree, no pub fn getDocumentSymbols(allocator: *std.mem.Allocator, tree: *ast.Tree, encoding: offsets.Encoding) ![]types.DocumentSymbol { var symbols = try std.ArrayList(types.DocumentSymbol).initCapacity(allocator, tree.root_node.decls_len); - var context = GetDocumentSymbolsContext { + var context = GetDocumentSymbolsContext{ .symbols = &symbols, .encoding = encoding, }; diff --git a/src/config.zig b/src/config.zig index efb28f1..be85f99 100644 --- a/src/config.zig +++ b/src/config.zig @@ -20,3 +20,6 @@ build_runner_path: ?[]const u8 = null, /// Semantic token support enable_semantic_tokens: bool = false, + +/// Whether to enable `*` and `?` operators in completion lists +operator_completions: bool = true, diff --git a/src/main.zig b/src/main.zig index 861a809..5424191 100644 --- a/src/main.zig +++ b/src/main.zig @@ -262,10 +262,11 @@ fn publishDiagnostics(arena: *std.heap.ArenaAllocator, handle: DocumentStore.Han fn typeToCompletion( arena: *std.heap.ArenaAllocator, list: *std.ArrayList(types.CompletionItem), - type_handle: analysis.TypeWithHandle, + field_access: analysis.FieldAccessReturn, orig_handle: *DocumentStore.Handle, config: Config, ) error{OutOfMemory}!void { + const type_handle = field_access.original; switch (type_handle.type.data) { .slice => { if (!type_handle.type.is_type_val) { @@ -284,6 +285,7 @@ fn typeToCompletion( arena, list, .{ .node = n, .handle = type_handle.handle }, + field_access.unwrapped, orig_handle, type_handle.type.is_type_val, config, @@ -296,6 +298,7 @@ fn nodeToCompletion( arena: *std.heap.ArenaAllocator, list: *std.ArrayList(types.CompletionItem), node_handle: analysis.NodeWithHandle, + unwrapped: ?analysis.TypeWithHandle, orig_handle: *DocumentStore.Handle, is_type_val: bool, config: Config, @@ -417,15 +420,40 @@ fn nodeToCompletion( }, .PrefixOp => { const prefix_op = node.cast(std.zig.ast.Node.PrefixOp).?; + switch (prefix_op.op) { .ArrayType, .SliceType => {}, .PtrType => { + if (config.operator_completions) { + try list.append(.{ + .label = "*", + .kind = .Operator, + }); + } + if (prefix_op.rhs.cast(std.zig.ast.Node.PrefixOp)) |child_pop| { switch (child_pop.op) { - .ArrayType => {}, - else => return, + .ArrayType => { + try list.append(.{ + .label = "len", + .kind = .Field, + }); + }, + else => {}, } - } else return; + } else if (unwrapped) |actual_type| { + try typeToCompletion(arena, list, .{ .original = actual_type }, orig_handle, config); + } + return; + }, + .OptionalType => { + if (config.operator_completions) { + try list.append(.{ + .label = "?", + .kind = .Operator, + }); + } + return; }, else => return, } @@ -638,7 +666,8 @@ fn getSymbolFieldAccess( if (name.len == 0) return null; var tokenizer = std.zig.Tokenizer.init(position.line[range.start..range.end]); - if (try analysis.getFieldAccessType(&document_store, arena, handle, position.absolute_index, &tokenizer)) |container_handle| { + if (try analysis.getFieldAccessType(&document_store, arena, handle, position.absolute_index, &tokenizer)) |result| { + const container_handle = result.original; const container_handle_node = switch (container_handle.type.data) { .other => |n| n, else => return null, @@ -799,7 +828,7 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.Decl const tree = decl_handle.handle.tree; switch (decl_handle.decl.*) { - .ast_node => |node| try nodeToCompletion(context.arena, context.completions, .{ .node = node, .handle = decl_handle.handle }, context.orig_handle, false, context.config.*), + .ast_node => |node| try nodeToCompletion(context.arena, context.completions, .{ .node = node, .handle = decl_handle.handle }, null, context.orig_handle, false, context.config.*), .param_decl => |param| { const doc_kind: types.MarkupKind = if (client_capabilities.completion_doc_supports_md) .Markdown else .PlainText; const doc = if (param.doc_comments) |doc_comments| @@ -898,8 +927,8 @@ fn completeFieldAccess( ) !void { var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator); var tokenizer = std.zig.Tokenizer.init(position.line[range.start..range.end]); - if (try analysis.getFieldAccessType(&document_store, arena, handle, position.absolute_index, &tokenizer)) |node| { - try typeToCompletion(arena, &completions, node, handle, config); + if (try analysis.getFieldAccessType(&document_store, arena, handle, position.absolute_index, &tokenizer)) |result| { + try typeToCompletion(arena, &completions, result, handle, config); } try send(arena, types.Response{