Added *, ? completions, improved readme

This commit is contained in:
Alexandros Naskos 2020-07-07 23:26:12 +03:00
parent f55d5a25aa
commit 5454883d2d
4 changed files with 88 additions and 22 deletions

View File

@ -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

View File

@ -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,
};

View File

@ -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,

View File

@ -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{