From 2fc9506fdccaecae5ff557c7fac3b0e9ea1d18f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20H=C3=A4hne?= Date: Tue, 30 Mar 2021 19:49:29 +0200 Subject: [PATCH] Refactor config wizard Deduplicated zig finder and added `zls config` subcommand --- README.md | 2 + build.zig | 194 +------------------------------------------- src/main.zig | 50 +++++------- src/setup.zig | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 244 insertions(+), 223 deletions(-) create mode 100644 src/setup.zig diff --git a/README.md b/README.md index 8f57a79..8d59a30 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,8 @@ The following options are currently available. | `enable_semantic_tokens` | `bool` | `true` | Enables semantic token support when the client also supports it. | | `operator_completions` | `bool` | `true` | Enables `*` and `?` operators in completion lists. | | `skip_std_references` | `bool` | `false` | When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is. +| `include_at_in_builtins` | `bool` | `false` | Most editors (except Sublime Text, it seems) generate a duplicate @ if a completion starts with it. +| `max_detail_length` | `usize` | `1048576` | Completion detail fields are truncated to this length. Decrease if previews lag your editor. ## Features diff --git a/build.zig b/build.zig index 89523cd..502d4bb 100644 --- a/build.zig +++ b/build.zig @@ -2,202 +2,12 @@ const std = @import("std"); const builtin = @import("builtin"); // const build_options = @import("build_options") -const zinput = @import("src/zinput/src/main.zig"); +const setup = @import("src/setup.zig"); var builder: *std.build.Builder = undefined; pub fn config(step: *std.build.Step) anyerror!void { - @setEvalBranchQuota(2500); - std.debug.warn("Welcome to the ZLS configuration wizard! (insert mage emoji here)\n", .{}); - - var zig_exe_path: ?[]const u8 = null; - std.debug.print("Looking for 'zig' in PATH...\n", .{}); - find_zig: { - const allocator = builder.allocator; - const env_path = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) { - error.EnvironmentVariableNotFound => { - break :find_zig; - }, - else => return err, - }; - defer allocator.free(env_path); - - const exe_extension = @as(std.zig.CrossTarget, .{}).exeFileExt(); - const zig_exe = try std.fmt.allocPrint(allocator, "zig{s}", .{exe_extension}); - defer allocator.free(zig_exe); - - var it = std.mem.tokenize(env_path, &[_]u8{std.fs.path.delimiter}); - while (it.next()) |path| { - const resolved_path = try std.fs.path.resolve(allocator, &[_][]const u8{path}); - defer allocator.free(resolved_path); - const full_path = try std.fs.path.join(allocator, &[_][]const u8{ - resolved_path, - zig_exe, - }); - defer allocator.free(full_path); - - if (!std.fs.path.isAbsolute(full_path)) continue; - // Skip folders named zig - const file = std.fs.openFileAbsolute(full_path, .{}) catch continue; - const stat = file.stat() catch continue; - const is_dir = stat.kind == .Directory; - if (is_dir) continue; - - var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - zig_exe_path = try std.mem.dupe(allocator, u8, std.os.realpath(full_path, &buf) catch continue); - break :find_zig; - } - } - - if (zig_exe_path == null) { - std.debug.print("Could not find 'zig' in PATH\n", .{}); - zig_exe_path = try zinput.askString(builder.allocator, "What is the path to the 'zig' executable you would like to use?", 512); - } else { - std.debug.print("Found zig executable '{s}'\n", .{zig_exe_path.?}); - } - - const editor = try zinput.askSelectOne("Which code editor do you use?", enum { VSCode, Sublime, Kate, Neovim, Vim8, Emacs, Doom, Other }); - const snippets = try zinput.askBool("Do you want to enable snippets?"); - const style = try zinput.askBool("Do you want to enable style warnings?"); - const semantic_tokens = try zinput.askBool("Do you want to enable semantic highlighting?"); - const operator_completions = try zinput.askBool("Do you want to enable .* and .? completions?"); - const include_at_in_builtins = switch (editor) { - .Sublime => - true, - .VSCode, - .Kate, - .Neovim, - .Vim8, - .Emacs, - .Doom => - false, - else => - try zinput.askBool("Should the @ sign be included in completions of builtin functions?\nChange this later if `@inc` completes to `include` or `@@include`") - }; - const max_detail_length: usize = switch (editor) { - .Sublime => - 256, - else => - 1024 * 1024 - }; - - var dir = try std.fs.cwd().openDir(builder.exe_dir, .{}); - defer dir.close(); - - var file = try dir.createFile("zls.json", .{}); - defer file.close(); - - const out = file.writer(); - - std.debug.warn("Writing to config...\n", .{}); - - const content = std.json.stringify(.{ - .zig_exe_path = zig_exe_path, - .enable_snippets = snippets, - .warn_style = style, - .enable_semantic_tokens = semantic_tokens, - .operator_completions = operator_completions, - .include_at_in_builtins = include_at_in_builtins, - .max_detail_length = max_detail_length, - }, std.json.StringifyOptions{}, out); - - std.debug.warn("Successfully saved configuration options!\n", .{}); - std.debug.warn("\n", .{}); - - switch (editor) { - .VSCode => { - std.debug.warn( - \\To use ZLS in Visual Studio Code, install the 'ZLS for VSCode' extension. - \\Then, open VSCode's 'settings.json' file, and add `"zigLanguageClient.path": "[command_or_path_to_zls]"`. - , .{}); - }, - .Sublime => { - std.debug.warn( - \\To use ZLS in Sublime, install the `LSP` package from `https://github.com/sublimelsp/LSP/releases` or via Package Control. - \\Then, add the following snippet to `LSP`'s user settings: - \\ - \\{{ - \\ "clients": {{ - \\ "zig": {{ - \\ "command": ["zls"], - \\ "enabled": true, - \\ "languageId": "zig", - \\ "scopes": ["source.zig"], - \\ "syntaxes": ["Packages/Zig/Syntaxes/Zig.tmLanguage"] - \\ }} - \\ }} - \\}} - , .{}); - }, - .Kate => { - std.debug.warn( - \\To use ZLS in Kate, enable `LSP client` plugin in Kate settings. - \\Then, add the following snippet to `LSP client's` user settings: - \\(or paste it in `LSP client's` GUI settings) - \\ - \\{{ - \\ "servers": {{ - \\ "zig": {{ - \\ "command": ["zls"], - \\ "url": "https://github.com/zigtools/zls", - \\ "highlightingModeRegex": "^Zig$" - \\ }} - \\ }} - \\}} - , .{}); - }, - .Neovim, .Vim8 => { - std.debug.warn( - \\To use ZLS in Neovim/Vim8, we recommend using CoC engine. You can get it from 'https://github.com/neoclide/coc.nvim'. - \\Then, simply issue cmd from Neovim/Vim8 `:CocConfig`, and add this to your CoC config: - \\ - \\{{ - \\ "languageserver": {{ - \\ "zls" : {{ - \\ "command": "command_or_path_to_zls", - \\ "filetypes": ["zig"] - \\ }} - \\ }} - \\}} - , .{}); - }, - .Emacs => { - std.debug.warn( - \\To use ZLS in Emacs, install lsp-mode (https://github.com/emacs-lsp/lsp-mode) from melpa. - \\Zig mode (https://github.com/ziglang/zig-mode) is also useful! - \\Then, add the following to your emacs config: - \\ - \\(require 'lsp-mode) - \\(setq lsp-zig-zls-executable "") - , .{}); - }, - .Doom => { - std.debug.warn( - \\To use ZLS in Doom Emacs, enable the lsp module - \\And install the `zig-mode` (https://github.com/ziglang/zig-mode) package by adding `(package! zig-mode)` to your packages.el file. - \\ - \\(use-package! zig-mode - \\ :hook ((zig-mode . lsp-deferred)) - \\ :custom (zig-format-on-save nil) - \\ :config - \\ (after! lsp-mode - \\ (add-to-list 'lsp-language-id-configuration '(zig-mode . "zig")) - \\ (lsp-register-client - \\ (make-lsp-client - \\ :new-connection (lsp-stdio-connection "") - \\ :major-modes '(zig-mode) - \\ :server-id 'zls)))) - , .{}); - }, - .Other => { - std.debug.warn( - \\We might not *officially* support your editor, but you can definitely still use ZLS! - \\Simply configure your editor for use with language servers and point it to the ZLS executable! - , .{}); - }, - } - - std.debug.warn("\nYou can find the ZLS executable in the \"zig-cache/bin\" by default.\nNOTE: Make sure that if you move the ZLS executable, you move the `zls.json` config file with it as well!\n\nAnd finally: Thanks for choosing ZLS!\n\n", .{}); + try setup.wizard(builder.allocator, builder.exe_dir); } pub fn build(b: *std.build.Builder) !void { diff --git a/src/main.zig b/src/main.zig index 1e3f594..14d6315 100644 --- a/src/main.zig +++ b/src/main.zig @@ -12,6 +12,7 @@ const URI = @import("uri.zig"); const references = @import("references.zig"); const rename = @import("rename.zig"); const offsets = @import("offsets.zig"); +const setup = @import("setup.zig"); const semantic_tokens = @import("semantic_tokens.zig"); const known_folders = @import("known-folders"); @@ -356,6 +357,8 @@ fn nodeToCompletion( const node_tags = tree.nodes.items(.tag); const datas = tree.nodes.items(.data); const token_tags = tree.tokens.items(.tag); + if (tree.errors.len > 0) + return; const doc_kind: types.MarkupContent.Kind = if (client_capabilities.completion_doc_supports_md) .Markdown @@ -1678,10 +1681,17 @@ fn processJsonRpc(arena: *std.heap.ArenaAllocator, parser: *std.json.Parser, jso logger.debug("Method without return value not implemented: {s}", .{method}); } -const stack_frames = switch (std.builtin.mode) { - .Debug => 10, - else => 0, -}; + +fn launchWizard() !void { + const dest = + (try known_folders.getPath(allocator, .local_configuration)) + orelse (try known_folders.getPath(allocator, .executable_dir)) + orelse return error.NoConfigPathFound; + defer allocator.free(dest); + try setup.wizard(allocator, dest); +} + +const stack_frames = switch (std.builtin.mode) { .Debug => 10, else => 0 }; var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_frames }){}; pub fn main() anyerror!void { @@ -1699,6 +1709,10 @@ pub fn main() anyerror!void { if (std.mem.eql(u8, arg, "--debug-log")) { actual_log_level = .debug; std.debug.print("Enabled debug logging\n", .{}); + } else if (std.mem.eql(u8, arg, "config")) { + try launchWizard(); + args_it.deinit(); + return; } else { std.debug.print("Unrecognized argument {s}\n", .{arg}); std.os.exit(1); @@ -1746,33 +1760,7 @@ pub fn main() anyerror!void { logger.debug("zig path `{s}` is not absolute, will look in path", .{exe_path}); allocator.free(exe_path); } - - const env_path = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) { - error.EnvironmentVariableNotFound => { - logger.warn("Could not get PATH environmental variable", .{}); - break :find_zig; - }, - else => return err, - }; - defer allocator.free(env_path); - - const exe_extension = std.Target.current.exeFileExt(); - const zig_exe = try std.fmt.allocPrint(allocator, "zig{s}", .{exe_extension}); - defer allocator.free(zig_exe); - - var it = std.mem.tokenize(env_path, &[_]u8{std.fs.path.delimiter}); - while (it.next()) |path| { - const full_path = try std.fs.path.join(allocator, &[_][]const u8{ - path, - zig_exe, - }); - defer allocator.free(full_path); - - var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - config.zig_exe_path = try std.mem.dupe(allocator, u8, std.os.realpath(full_path, &buf) catch continue); - logger.info("Found zig in PATH: {s}", .{config.zig_exe_path}); - break :find_zig; - } + config.zig_exe_path = try setup.findZig(allocator); } if (config.zig_exe_path) |exe_path| { diff --git a/src/setup.zig b/src/setup.zig new file mode 100644 index 0000000..d97ceda --- /dev/null +++ b/src/setup.zig @@ -0,0 +1,221 @@ +const std = @import("std"); +const zinput = @import("zinput/src/main.zig"); + +pub fn wizard(allocator: *std.mem.Allocator, exe_dir: []const u8) !void { + @setEvalBranchQuota(2500); + std.debug.warn( + \\Welcome to the ZLS configuration wizard! + \\ * + \\ |\ + \\ /* \ + \\ | *\ + \\ _/_*___|_ x + \\ | @ @ / + \\ @ \ / + \\ \__-/ / + \\ + \\ + , .{}); + + var zig_exe_path = try findZig(allocator); + defer if(zig_exe_path) |p| allocator.free(p); + + if (zig_exe_path) |path| { + std.debug.print("Found zig executable '{s}' in PATH.\n", .{path}); + } else { + std.debug.print("Could not find 'zig' in PATH\n", .{}); + zig_exe_path = try zinput.askString(allocator, "What is the path to the 'zig' executable you would like to use?", std.fs.MAX_PATH_BYTES); + } + + const editor = try zinput.askSelectOne("Which code editor do you use?", enum { VSCode, Sublime, Kate, Neovim, Vim8, Emacs, Doom, Other }); + const snippets = try zinput.askBool("Do you want to enable snippets?"); + const style = try zinput.askBool("Do you want to enable style warnings?"); + const semantic_tokens = try zinput.askBool("Do you want to enable semantic highlighting?"); + const operator_completions = try zinput.askBool("Do you want to enable .* and .? completions?"); + const include_at_in_builtins = switch (editor) { + .Sublime => + true, + .VSCode, + .Kate, + .Neovim, + .Vim8, + .Emacs, + .Doom => + false, + else => + try zinput.askBool("Should the @ sign be included in completions of builtin functions?\nChange this later if `@inc` completes to `include` or `@@include`") + }; + const max_detail_length: usize = switch (editor) { + .Sublime => + 256, + else => + 1024 * 1024 + }; + + var dir = try std.fs.cwd().openDir(exe_dir, .{}); + defer dir.close(); + + var file = try dir.createFile("zls.json", .{}); + defer file.close(); + + const out = file.writer(); + + std.debug.warn("Writing config to {s}/zls.json ... ", .{exe_dir}); + + const content = std.json.stringify(.{ + .zig_exe_path = zig_exe_path, + .enable_snippets = snippets, + .warn_style = style, + .enable_semantic_tokens = semantic_tokens, + .operator_completions = operator_completions, + .include_at_in_builtins = include_at_in_builtins, + .max_detail_length = max_detail_length, + }, std.json.StringifyOptions{}, out); + + std.debug.warn("successful.\n\n\n\n", .{}); + + + // Keep synced with README.md + switch (editor) { + .VSCode => { + std.debug.warn( + \\To use ZLS in Visual Studio Code, install the 'ZLS for VSCode' extension from + \\'https://github.com/zigtools/zls-vscode/releases' or via the extensions menu. + \\Then, open VSCode's 'settings.json' file, and add: + \\ + \\"zigLanguageClient.path": "[command_or_path_to_zls]" + , .{}); + }, + .Sublime => { + std.debug.warn( + \\To use ZLS in Sublime, install the `LSP` package from + \\https://github.com/sublimelsp/LSP/releases or via Package Control. + \\Then, add the following snippet to LSP's user settings: + \\ + \\{{ + \\ "clients": {{ + \\ "zig": {{ + \\ "command": ["zls"], + \\ "enabled": true, + \\ "languageId": "zig", + \\ "scopes": ["source.zig"], + \\ "syntaxes": ["Packages/Zig/Syntaxes/Zig.tmLanguage"] + \\ }} + \\ }} + \\}} + , .{}); + }, + .Kate => { + std.debug.warn( + \\To use ZLS in Kate, enable `LSP client` plugin in Kate settings. + \\Then, add the following snippet to `LSP client's` user settings: + \\(or paste it in `LSP client's` GUI settings) + \\ + \\{{ + \\ "servers": {{ + \\ "zig": {{ + \\ "command": ["zls"], + \\ "url": "https://github.com/zigtools/zls", + \\ "highlightingModeRegex": "^Zig$" + \\ }} + \\ }} + \\}} + , .{}); + }, + .Neovim, .Vim8 => { + std.debug.warn( + \\To use ZLS in Neovim/Vim8, we recommend using CoC engine. + \\You can get it from https://github.com/neoclide/coc.nvim. + \\Then, simply issue cmd from Neovim/Vim8 `:CocConfig`, and add this to your CoC config: + \\ + \\{{ + \\ "languageserver": {{ + \\ "zls" : {{ + \\ "command": "command_or_path_to_zls", + \\ "filetypes": ["zig"] + \\ }} + \\ }} + \\}} + , .{}); + }, + .Emacs => { + std.debug.warn( + \\To use ZLS in Emacs, install lsp-mode (https://github.com/emacs-lsp/lsp-mode) from melpa. + \\Zig mode (https://github.com/ziglang/zig-mode) is also useful! + \\Then, add the following to your emacs config: + \\ + \\(require 'lsp-mode) + \\(setq lsp-zig-zls-executable "") + , .{}); + }, + .Doom => { + std.debug.warn( + \\To use ZLS in Doom Emacs, enable the lsp module + \\And install the `zig-mode` (https://github.com/ziglang/zig-mode) + \\package by adding `(package! zig-mode)` to your packages.el file. + \\ + \\(use-package! zig-mode + \\ :hook ((zig-mode . lsp-deferred)) + \\ :custom (zig-format-on-save nil) + \\ :config + \\ (after! lsp-mode + \\ (add-to-list 'lsp-language-id-configuration '(zig-mode . "zig")) + \\ (lsp-register-client + \\ (make-lsp-client + \\ :new-connection (lsp-stdio-connection "") + \\ :major-modes '(zig-mode) + \\ :server-id 'zls)))) + , .{}); + }, + .Other => { + std.debug.warn( + \\We might not *officially* support your editor, but you can definitely still use ZLS! + \\Simply configure your editor for use with language servers and point it to the ZLS executable! + , .{}); + }, + } + + std.debug.warn( + \\ + \\You can find the ZLS executable in the "zig-cache/bin" by default. + \\NOTE: Make sure that if you move the ZLS executable, you move the `zls.json` config file with it as well! + \\And finally: Thanks for choosing ZLS! + \\ + \\ + , .{}); +} + + +pub fn findZig(allocator: *std.mem.Allocator) !?[]const u8 { + const env_path = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) { + error.EnvironmentVariableNotFound => { + return null; + }, + else => return err, + }; + defer allocator.free(env_path); + + const exe_extension = std.Target.current.exeFileExt(); + const zig_exe = try std.fmt.allocPrint(allocator, "zig{s}", .{exe_extension}); + defer allocator.free(zig_exe); + + var it = std.mem.tokenize(env_path, &[_]u8{std.fs.path.delimiter}); + while (it.next()) |path| { + const full_path = try std.fs.path.join(allocator, &[_][]const u8{ + path, + zig_exe, + }); + defer allocator.free(full_path); + + if (!std.fs.path.isAbsolute(full_path)) continue; + + // Skip folders named zig + const file = std.fs.openFileAbsolute(full_path, .{}) catch continue; + defer file.close(); + const stat = file.stat() catch continue; + if (stat.kind == .Directory) continue; + + return try allocator.dupe(u8, full_path); + } + return null; +}