346 lines
12 KiB
EmacsLisp
346 lines
12 KiB
EmacsLisp
|
;;; lsp-zig.el --- lsp-mode Zig integration -*- lexical-binding: t; -*-
|
||
|
|
||
|
;; Copyright (C) 2021 Riccardo Binetti
|
||
|
|
||
|
;; Author: Riccardo Binetti <rbino@gmx.com>
|
||
|
;; Keywords: languages,tools
|
||
|
|
||
|
;; This program is free software; you can redistribute it and/or modify
|
||
|
;; it under the terms of the GNU General Public License as published by
|
||
|
;; the Free Software Foundation, either version 3 of the License, or
|
||
|
;; (at your option) any later version.
|
||
|
|
||
|
;; This program is distributed in the hope that it will be useful,
|
||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
;; GNU General Public License for more details.
|
||
|
|
||
|
;; You should have received a copy of the GNU General Public License
|
||
|
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
|
||
|
;;; Commentary:
|
||
|
|
||
|
;; client for zls, the Zig language server
|
||
|
|
||
|
;;; Code:
|
||
|
|
||
|
(require 'lsp-mode)
|
||
|
|
||
|
(defgroup lsp-zig nil
|
||
|
"LSP support for Zig via zls."
|
||
|
:group 'lsp-mode
|
||
|
:link '(url-link "https://github.com/zigtools/zls"))
|
||
|
|
||
|
(defcustom lsp-zig-zls-executable "zls"
|
||
|
"The zls executable to use.
|
||
|
|
||
|
Leave as just the executable name to use the default behavior of finding the
|
||
|
executable with variable `exec-path'."
|
||
|
:group 'lsp-zig
|
||
|
:type 'string)
|
||
|
|
||
|
(defcustom lsp-zig-trace-server "off"
|
||
|
"Traces the communication between Emacs and the language server."
|
||
|
:group 'lsp-zig
|
||
|
:type '(choice (const "off")
|
||
|
(const "messages")
|
||
|
(const "verbose")))
|
||
|
|
||
|
(defcustom lsp-zls-enable-snippets t
|
||
|
"Enables snippet completions when the client also supports them."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-enable-argument-placeholders t
|
||
|
"Whether to enable function argument placeholder completions."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-enable-build-on-save nil
|
||
|
"Whether to enable build-on-save diagnostics."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-build-on-save-step "install"
|
||
|
"Select which step should be executed on build-on-save."
|
||
|
:group 'lsp-zig
|
||
|
:type 'string)
|
||
|
|
||
|
(defcustom lsp-zig-enable-autofix nil
|
||
|
"Whether to automatically fix errors on save.
|
||
|
Currently supports adding and removing discards."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-semantic-tokens "partial"
|
||
|
"Traces the communication between Emacs and the language server."
|
||
|
:group 'lsp-zig
|
||
|
:type '(choice (const "off")
|
||
|
(const "messages")
|
||
|
(const "verbose")))
|
||
|
|
||
|
(defcustom lsp-zig-enable-inlay-hints t
|
||
|
"Enables inlay hint support when the client also supports it."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-inlay-hints-show-variable-type-hints t
|
||
|
"Enable inlay hints for variable type."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-inlay-hints-show-parameter-name t
|
||
|
"Enable inlay hints for parameter names."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-inlay-hints-exclude-single-argument t
|
||
|
"Don't show inlay hints for single argument calls."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-inlay-hints-show-builtin t
|
||
|
"Don't show inlay hints for single argument calls."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-inlay-hints-hide-redundant-param-names nil
|
||
|
"Hides inlay hints when parameter name matches the identifier (e.g. foo: foo)."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-inlay-hints-hide-redundant-param-names-last-token nil
|
||
|
"Hides inlay hints when parameter name matches the last token of a parameter
|
||
|
node (e.g. foo: bar.foo, foo: &foo)."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-warn-style nil
|
||
|
"Enables warnings for style guideline mismatches."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-highlight-global-var-declarations nil
|
||
|
"Whether to highlight global var declarations."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-dangerous-comptime-experiments-do-not-enable nil
|
||
|
"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."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-skip-std-references nil
|
||
|
"hen 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."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-prefer-ast-check-as-child-process t
|
||
|
"Favor using `zig ast-check` instead of ZLS's fork."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-record-session nil
|
||
|
"When true, zls will record all request is receives and write in into
|
||
|
`record_session_path`, so that they can replayed with `zls replay`."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
(defcustom lsp-zig-record-session-path ""
|
||
|
"Output file path when `record_session` is set.
|
||
|
The recommended file extension *.zlsreplay."
|
||
|
:group 'lsp-zig
|
||
|
:type 'string)
|
||
|
|
||
|
(defcustom lsp-zig-replay-session-path ""
|
||
|
"Used when calling `zls replay` for specifying the replay file.
|
||
|
If no extra argument is given `record_session_path` is used as the default path."
|
||
|
:group 'lsp-zig
|
||
|
:type 'string)
|
||
|
|
||
|
(defcustom lsp-zig-builtin-path ""
|
||
|
"Path to `builtin'; useful for debugging, automatically set if let null."
|
||
|
:group 'lsp-zig
|
||
|
:type 'string)
|
||
|
|
||
|
(defcustom lsp-zig-zig-lib-path ""
|
||
|
"Zig library path.
|
||
|
e.g. `/path/to/zig/lib/zig`, used to analyze std library imports."
|
||
|
:group 'lsp-zig
|
||
|
:type 'string)
|
||
|
|
||
|
(defcustom lsp-zig-zig-exe-path ""
|
||
|
" 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."
|
||
|
:group 'lsp-zig
|
||
|
:type 'string)
|
||
|
|
||
|
(defcustom lsp-zig-build-runner-path ""
|
||
|
"Path to the `build_runner.zig` file provided by zls.
|
||
|
null is equivalent to `${executable_directory}/build_runner.zig`."
|
||
|
:group 'lsp-zig
|
||
|
:type 'string)
|
||
|
|
||
|
(defcustom lsp-zig-global-cache-path ""
|
||
|
"Path to a directory that will be used as zig's cache.
|
||
|
null is equivalent to `${KnownFolders.Cache}/zls`."
|
||
|
:group 'lsp-zig
|
||
|
:type 'string)
|
||
|
|
||
|
(defcustom lsp-zig-build-runner-global-cache-path ""
|
||
|
"Path to a directory that will be used as the global cache path when executing
|
||
|
a projects build.zig. null is equivalent to the path shown by `zig env`."
|
||
|
:group 'lsp-zig
|
||
|
:type 'string)
|
||
|
|
||
|
(defcustom lsp-zig-completions-with-replace nil
|
||
|
"Completions confirm behavior.
|
||
|
If `true', replace the text after the cursor."
|
||
|
:group 'lsp-zig
|
||
|
:type 'boolean)
|
||
|
|
||
|
;;
|
||
|
;;; Util
|
||
|
|
||
|
(defmacro lsp-zig--mute-apply (&rest body)
|
||
|
"Execute BODY without message."
|
||
|
(declare (indent 0) (debug t))
|
||
|
`(let (message-log-max)
|
||
|
(with-temp-message (or (current-message) nil)
|
||
|
(let ((inhibit-message t)) ,@body))))
|
||
|
|
||
|
(defun lsp-zig--execute (cmd &rest args)
|
||
|
"Return non-nil if CMD executed succesfully with ARGS."
|
||
|
(save-window-excursion
|
||
|
(lsp-zig--mute-apply
|
||
|
(= 0 (shell-command (concat cmd " "
|
||
|
(mapconcat #'shell-quote-argument
|
||
|
(cl-remove-if #'null args)
|
||
|
" ")))))))
|
||
|
|
||
|
;;
|
||
|
;;; Installation
|
||
|
|
||
|
(defcustom lsp-zig-server-store-path
|
||
|
(expand-file-name "zig/" lsp-server-install-dir)
|
||
|
"The path to the file in which zls will be stored."
|
||
|
:type 'file
|
||
|
:group 'lsp-zig)
|
||
|
|
||
|
(defcustom lsp-zig-server-version "0.11.0"
|
||
|
"The zls version to install."
|
||
|
:type 'file
|
||
|
:group 'lsp-zig)
|
||
|
|
||
|
(defconst lsp-zig-download-url-format
|
||
|
"https://github.com/zigtools/zls/releases/download/%s/zls-%s-%s.%s"
|
||
|
"Format to the download url link.")
|
||
|
|
||
|
(defun lsp-zig--zls-url ()
|
||
|
"Return Url points to the zls' zip/tar file."
|
||
|
(let* ((x86 (string-prefix-p "x86_64" system-configuration))
|
||
|
(arch (if x86 "x86_64" "aarch64")))
|
||
|
(cl-case system-type
|
||
|
((cygwin windows-nt ms-dos)
|
||
|
(format lsp-zig-download-url-format
|
||
|
lsp-zig-server-version arch "windows" "zip"))
|
||
|
(darwin
|
||
|
(format lsp-zig-download-url-format
|
||
|
lsp-zig-server-version arch "macos" "tar.gz"))
|
||
|
(gnu/linux
|
||
|
(format lsp-zig-download-url-format
|
||
|
lsp-zig-server-version arch "linux" "tar.gz")))))
|
||
|
|
||
|
(defvar lsp-zig--server-download-url (lsp-zig--zls-url)
|
||
|
"The actual url used to download language server.")
|
||
|
|
||
|
(defvar lsp-zig--downloaded-file (f-join lsp-zig-server-store-path "temp.tar")
|
||
|
"The full file path after downloading the server zipped file.")
|
||
|
|
||
|
(defun lsp-zig--stored-zls-executable ()
|
||
|
"Return the stored zls executable.
|
||
|
|
||
|
This is differ from the variable `lsp-zig-zls-executable'; this is local storage
|
||
|
and not the global storage."
|
||
|
(executable-find (f-join lsp-zig-server-store-path "bin/zls")))
|
||
|
|
||
|
(defun lsp-zig--extract-compressed-file (callback)
|
||
|
"Install zls."
|
||
|
(cond ((file-exists-p lsp-zig--downloaded-file)
|
||
|
;; Suprisingly, you can just use `tar' to unzip a zip file on Windows.
|
||
|
;; Therefore, just use the same command.
|
||
|
(lsp-zig--execute "tar" "-xvzf" lsp-zig--downloaded-file "-C" lsp-zig-server-store-path)
|
||
|
;; Delete the zip file.
|
||
|
(ignore-errors (delete-file lsp-zig--downloaded-file)))
|
||
|
(t
|
||
|
(error "Can't extract the downloaded file: %s" lsp-zig--downloaded-file)))
|
||
|
(funcall callback))
|
||
|
|
||
|
(lsp-dependency
|
||
|
'zls
|
||
|
'(:system "zls")
|
||
|
`(:download :url ,lsp-zig--server-download-url
|
||
|
:store-path ,lsp-zig--downloaded-file))
|
||
|
|
||
|
;;
|
||
|
;;; Core
|
||
|
|
||
|
(lsp-register-custom-settings
|
||
|
'(("zls.enable_snippets" lsp-zls-enable-snippets t)
|
||
|
("zls.enable_argument_placeholders" lsp-zig-enable-argument-placeholders t)
|
||
|
("zls.enable_build_on_save" lsp-zig-enable-build-on-save t)
|
||
|
("zls.build_on_save_step" lsp-zig-build-on-save-step)
|
||
|
("zls.enable_autofix" lsp-zig-enable-autofix t)
|
||
|
("zls.semantic_tokens" lsp-zig-semantic-tokens)
|
||
|
("zls.enable_inlay_hints" lsp-zig-enable-inlay-hints t)
|
||
|
("zls.inlay_hints_show_variable_type_hints" lsp-zig-inlay-hints-show-variable-type-hints t)
|
||
|
("zls.inlay_hints_show_parameter_name" lsp-zig-inlay-hints-show-parameter-name t)
|
||
|
("zls.inlay_hints_show_builtin" lsp-zig-inlay-hints-show-builtin t)
|
||
|
("zls.inlay_hints_exclude_single_argument" lsp-zig-inlay-hints-exclude-single-argument t)
|
||
|
("zls.inlay_hints_hide_redundant_param_names" lsp-zig-inlay-hints-hide-redundant-param-names t)
|
||
|
("zls.inlay_hints_hide_redundant_param_names_last_token" lsp-zig-inlay-hints-hide-redundant-param-names-last-token t)
|
||
|
("zls.warn_style" lsp-zig-warn-style t)
|
||
|
("zls.highlight_global_var_declarations" lsp-zig-highlight-global-var-declarations t)
|
||
|
("zls.dangerous_comptime_experiments_do_not_enable" lsp-zig-dangerous-comptime-experiments-do-not-enable t)
|
||
|
("zls.skip_std_references" lsp-zig-skip-std-references t)
|
||
|
("zls.prefer_ast_check_as_child_process" lsp-zig-prefer-ast-check-as-child-process t)
|
||
|
("zls.record_session" lsp-zig-record-session t)
|
||
|
("zls.record_session_path" lsp-zig-record-session-path)
|
||
|
("zls.replay_session_path" lsp-zig-replay-session-path)
|
||
|
("zls.builtin_path" lsp-zig-builtin-path)
|
||
|
("zls.zig_lib_path" lsp-zig-zig-lib-path)
|
||
|
("zls.zig_exe_path" lsp-zig-zig-exe-path)
|
||
|
("zls.build_runner_path" lsp-zig-build-runner-path)
|
||
|
("zls.global_cache_path" lsp-zig-global-cache-path)
|
||
|
("zls.build_runner_global_cache_path" lsp-zig-build-runner-global-cache-path)
|
||
|
("zls.completion_label_details" lsp-zig-completions-with-replace t)))
|
||
|
|
||
|
(lsp-register-client
|
||
|
(make-lsp-client
|
||
|
:new-connection (lsp-stdio-connection
|
||
|
(lambda () (or (executable-find lsp-zig-zls-executable)
|
||
|
(lsp-zig--stored-zls-executable)))
|
||
|
(lambda ()
|
||
|
(or (executable-find lsp-zig-zls-executable)
|
||
|
(file-executable-p (lsp-zig--stored-zls-executable)))))
|
||
|
:activation-fn (lsp-activate-on "zig")
|
||
|
:priority -1
|
||
|
:server-id 'zls
|
||
|
:download-server-fn
|
||
|
(lambda (_client callback error-callback _update?)
|
||
|
(lsp-package-ensure 'zls
|
||
|
(lambda (&rest _)
|
||
|
(lsp-zig--extract-compressed-file callback))
|
||
|
error-callback))))
|
||
|
|
||
|
(lsp-consistency-check lsp-zig)
|
||
|
|
||
|
(provide 'lsp-zig)
|
||
|
;;; lsp-zig.el ends here
|