2021-03-30 18:49:29 +01:00
const std = @import ( " std " ) ;
2021-10-07 12:34:14 +01:00
const builtin = @import ( " builtin " ) ;
2021-03-31 12:46:56 +01:00
const known_folders = @import ( " known-folders " ) ;
2021-03-30 18:49:29 +01:00
2022-06-05 18:14:08 +01:00
/// Caller must free memory.
pub fn askString ( allocator : std . mem . Allocator , prompt : [ ] const u8 , max_size : usize ) ! [ ] u8 {
const in = std . io . getStdIn ( ) . reader ( ) ;
const out = std . io . getStdOut ( ) . writer ( ) ;
try out . print ( " ? {s} " , . { prompt } ) ;
const result = try in . readUntilDelimiterAlloc ( allocator , '\n' , max_size ) ;
return if ( std . mem . endsWith ( u8 , result , " \r " ) ) result [ 0 . . ( result . len - 1 ) ] else result ;
}
/// Caller must free memory. Max size is recommended to be a high value, like 512.
pub fn askDirPath ( allocator : std . mem . Allocator , prompt : [ ] const u8 , max_size : usize ) ! [ ] u8 {
const out = std . io . getStdOut ( ) . writer ( ) ;
while ( true ) {
const path = try askString ( allocator , prompt , max_size ) ;
if ( ! std . fs . path . isAbsolute ( path ) ) {
try out . writeAll ( " Error: Invalid directory, please try again. \n \n " ) ;
allocator . free ( path ) ;
continue ;
}
var dir = std . fs . cwd ( ) . openDir ( path , std . fs . Dir . OpenDirOptions { } ) catch {
try out . writeAll ( " Error: Invalid directory, please try again. \n \n " ) ;
allocator . free ( path ) ;
continue ;
} ;
dir . close ( ) ;
return path ;
}
}
pub fn askBool ( prompt : [ ] const u8 ) ! bool {
const in = std . io . getStdIn ( ) . reader ( ) ;
const out = std . io . getStdOut ( ) . writer ( ) ;
var buffer : [ 1 ] u8 = undefined ;
while ( true ) {
try out . print ( " ? {s} (y/n) > " , . { prompt } ) ;
const read = in . read ( & buffer ) catch continue ;
try in . skipUntilDelimiterOrEof ( '\n' ) ;
if ( read = = 0 ) return error . EndOfStream ;
switch ( buffer [ 0 ] ) {
'y' = > return true ,
'n' = > return false ,
else = > continue ,
}
}
}
pub fn askSelectOne ( prompt : [ ] const u8 , comptime Options : type ) ! Options {
const in = std . io . getStdIn ( ) . reader ( ) ;
const out = std . io . getStdOut ( ) . writer ( ) ;
try out . print ( " ? {s} (select one) \n \n " , . { prompt } ) ;
comptime var max_size : usize = 0 ;
inline for ( @typeInfo ( Options ) . Enum . fields ) | option | {
try out . print ( " - {s} \n " , . { option . name } ) ;
if ( option . name . len > max_size ) max_size = option . name . len ;
}
while ( true ) {
var buffer : [ max_size + 1 ] u8 = undefined ;
try out . writeAll ( " \n > " ) ;
var result = ( in . readUntilDelimiterOrEof ( & buffer , '\n' ) catch {
try in . skipUntilDelimiterOrEof ( '\n' ) ;
try out . writeAll ( " Error: Invalid option, please try again. \n " ) ;
continue ;
} ) orelse return error . EndOfStream ;
result = if ( std . mem . endsWith ( u8 , result , " \r " ) ) result [ 0 . . ( result . len - 1 ) ] else result ;
inline for ( @typeInfo ( Options ) . Enum . fields ) | option |
if ( std . ascii . eqlIgnoreCase ( option . name , result ) )
return @intToEnum ( Options , option . value ) ;
try out . writeAll ( " Error: Invalid option, please try again. \n " ) ;
}
}
2021-03-31 15:54:27 +01:00
fn print ( comptime fmt : [ ] const u8 , args : anytype ) void {
const stdout = std . io . getStdOut ( ) . writer ( ) ;
stdout . print ( fmt , args ) catch @panic ( " Could not write to stdout " ) ;
}
fn write ( text : [ ] const u8 ) void {
const stdout = std . io . getStdOut ( ) . writer ( ) ;
stdout . writeAll ( text ) catch @panic ( " Could not write to stdout " ) ;
}
2021-12-02 05:16:15 +00:00
pub fn wizard ( allocator : std . mem . Allocator ) ! void {
2021-03-30 18:49:29 +01:00
@setEvalBranchQuota ( 2500 ) ;
2021-03-31 15:54:27 +01:00
write (
2021-03-30 18:49:29 +01:00
\\Welcome to the ZLS configuration wizard!
\\ *
\\ |\
\\ /* \
\\ | *\
\\ _/_*___|_ x
\\ | @ @ /
\\ @ \ /
\\ \__-/ /
\\
\\
2021-06-24 11:38:01 +01:00
) ;
2021-03-31 15:54:27 +01:00
var local_path = known_folders . getPath ( allocator , . local_configuration ) catch null ;
var global_path = known_folders . getPath ( allocator , . global_configuration ) catch null ;
defer if ( local_path ) | d | allocator . free ( d ) ;
defer if ( global_path ) | d | allocator . free ( d ) ;
if ( global_path = = null and local_path = = null ) {
write ( " Could not open a global or local config directory. \n " ) ;
return ;
}
var config_path : [ ] const u8 = undefined ;
2022-06-05 18:14:08 +01:00
if ( try askBool ( " Should this configuration be system-wide? " ) ) {
2021-03-31 15:54:27 +01:00
if ( global_path ) | p | {
config_path = p ;
} else {
write ( " Could not find a global config directory. \n " ) ;
return ;
}
} else {
if ( local_path ) | p | {
config_path = p ;
} else {
write ( " Could not find a local config directory. \n " ) ;
return ;
}
}
var dir = std . fs . cwd ( ) . openDir ( config_path , . { } ) catch | err | {
2021-06-24 11:38:01 +01:00
print ( " Could not open {s}: {}. \n " , . { config_path , err } ) ;
2021-03-31 15:54:27 +01:00
return ;
} ;
defer dir . close ( ) ;
var file = dir . createFile ( " zls.json " , . { } ) catch | err | {
2021-06-24 11:38:01 +01:00
print ( " Could not create {s}/zls.json: {}. \n " , . { config_path , err } ) ;
2021-03-31 15:54:27 +01:00
return ;
} ;
defer file . close ( ) ;
const out = file . writer ( ) ;
2021-03-30 18:49:29 +01:00
var zig_exe_path = try findZig ( allocator ) ;
2021-03-31 12:46:56 +01:00
defer if ( zig_exe_path ) | p | allocator . free ( p ) ;
2021-03-30 18:49:29 +01:00
if ( zig_exe_path ) | path | {
2021-03-31 15:54:27 +01:00
print ( " Found zig executable '{s}' in PATH. \n " , . { path } ) ;
2021-03-30 18:49:29 +01:00
} else {
2021-03-31 15:54:27 +01:00
write ( " Could not find 'zig' in PATH \n " ) ;
2022-06-05 18:14:08 +01:00
zig_exe_path = try askString ( allocator , if ( builtin . os . tag = = . windows )
2021-06-24 11:38:01 +01:00
\\What is the path to the 'zig' executable you would like to use?
\\Note that due to a bug in zig (https://github.com/ziglang/zig/issues/6044),
\\your zig directory cannot contain the '/' character.
else
" What is the path to the 'zig' executable you would like to use? " , std . fs . MAX_PATH_BYTES ) ;
2021-03-30 18:49:29 +01:00
}
2022-07-24 16:01:05 +01:00
const editor = try askSelectOne ( " Which code editor do you use? " , enum { VSCode , Sublime , Kate , Neovim , Vim8 , Emacs , Doom , Spacemacs , Helix , Other } ) ;
2022-06-05 18:14:08 +01:00
const snippets = try askBool ( " Do you want to enable snippets? " ) ;
2022-07-09 10:22:02 +01:00
const unused_variables = try askBool ( " Do you want to enable unused variable warnings? " ) ;
const ief_apc = try askBool ( " Do you want to enable @import/@embedFile argument path completion? " ) ;
2022-06-05 18:14:08 +01:00
const style = try askBool ( " Do you want to enable style warnings? " ) ;
const semantic_tokens = try askBool ( " Do you want to enable semantic highlighting? " ) ;
2022-07-24 12:38:13 +01:00
const inlay_hints = try askBool ( " Do you want to enable inlay hints? " ) ;
2022-06-05 18:14:08 +01:00
const operator_completions = try askBool ( " Do you want to enable .* and .? completions? " ) ;
2021-03-30 18:49:29 +01:00
const include_at_in_builtins = switch ( editor ) {
2022-07-24 12:58:26 +01:00
. Sublime = > ! try askBool ( " Are you using a Sublime Text version > 4000? " ) ,
2022-07-24 16:01:05 +01:00
. VSCode , . Kate , . Neovim , . Vim8 , . Emacs , . Doom , . Spacemacs , . Helix = > false ,
2022-06-05 18:14:08 +01:00
else = > try askBool ( " Should the @ sign be included in completions of builtin functions? \n Change this later if `@inc` completes to `include` or `@@include` " ) ,
2021-03-30 18:49:29 +01:00
} ;
const max_detail_length : usize = switch ( editor ) {
2021-03-31 12:46:56 +01:00
. Sublime = > 256 ,
else = > 1024 * 1024 ,
2021-03-30 18:49:29 +01:00
} ;
2021-11-30 18:25:36 +00:00
std . debug . print ( " Writing config to {s}/zls.json ... " , . { config_path } ) ;
2021-03-31 12:46:56 +01:00
2021-06-24 11:38:01 +01:00
try std . json . stringify ( . {
2021-03-30 18:49:29 +01:00
. zig_exe_path = zig_exe_path ,
. enable_snippets = snippets ,
2022-07-09 10:22:02 +01:00
. enable_unused_variable_warnings = unused_variables ,
. enable_import_embedfile_argument_completions = ief_apc ,
2021-03-30 18:49:29 +01:00
. warn_style = style ,
. enable_semantic_tokens = semantic_tokens ,
2022-07-24 12:38:13 +01:00
. enable_inlay_hints = inlay_hints ,
2021-03-30 18:49:29 +01:00
. operator_completions = operator_completions ,
. include_at_in_builtins = include_at_in_builtins ,
. max_detail_length = max_detail_length ,
2022-08-02 19:28:27 +01:00
} , . {
. whitespace = . { } ,
} , out ) ;
2021-03-31 15:54:27 +01:00
write ( " successful. \n \n \n \n " ) ;
2021-03-30 18:49:29 +01:00
// Keep synced with README.md
switch ( editor ) {
. VSCode = > {
2021-03-31 15:54:27 +01:00
write (
2021-03-30 18:49:29 +01:00
\\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.
2022-07-24 16:01:05 +01:00
\\ZLS will automatically be installed if it is not found in your PATH
2021-03-31 15:54:27 +01:00
) ;
2021-03-30 18:49:29 +01:00
} ,
. Sublime = > {
2021-03-31 15:54:27 +01:00
write (
2021-03-30 18:49:29 +01:00
\\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:
\\
2021-11-02 09:50:14 +00:00
\\For Sublime Text 3:
\\
2021-03-31 15:54:27 +01:00
\\{
\\ "clients": {
\\ "zig": {
2021-03-30 18:49:29 +01:00
\\ "command": ["zls"],
\\ "enabled": true,
\\ "languageId": "zig",
\\ "scopes": ["source.zig"],
2021-11-02 09:50:14 +00:00
\\ "syntaxes": ["Packages/Zig Language/Syntaxes/Zig.tmLanguage"]
\\ }
\\ }
\\}
\\
\\For Sublime Text 4:
\\
\\{
\\ "clients": {
\\ "zig": {
\\ "command": ["zls"],
\\ "enabled": true,
\\ "selector": "source.zig"
2021-03-31 15:54:27 +01:00
\\ }
\\ }
\\}
) ;
2021-03-30 18:49:29 +01:00
} ,
. Kate = > {
2021-03-31 15:54:27 +01:00
write (
2021-03-30 18:49:29 +01:00
\\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)
\\
2021-03-31 15:54:27 +01:00
\\{
\\ "servers": {
\\ "zig": {
2021-03-30 18:49:29 +01:00
\\ "command": ["zls"],
\\ "url": "https://github.com/zigtools/zls",
\\ "highlightingModeRegex": "^Zig$"
2021-03-31 15:54:27 +01:00
\\ }
\\ }
\\}
) ;
2021-03-30 18:49:29 +01:00
} ,
. Neovim , . Vim8 = > {
2021-03-31 15:54:27 +01:00
write (
2021-03-30 18:49:29 +01:00
\\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:
\\
2021-03-31 15:54:27 +01:00
\\{
\\ "languageserver": {
\\ "zls" : {
2021-03-30 18:49:29 +01:00
\\ "command": "command_or_path_to_zls",
\\ "filetypes": ["zig"]
2021-03-31 15:54:27 +01:00
\\ }
\\ }
\\}
) ;
2021-03-30 18:49:29 +01:00
} ,
. Emacs = > {
2021-03-31 15:54:27 +01:00
write (
2021-03-30 18:49:29 +01:00
\\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 "<path to zls>")
2021-03-31 15:54:27 +01:00
) ;
2021-03-30 18:49:29 +01:00
} ,
. Doom = > {
2021-03-31 15:54:27 +01:00
write (
2021-03-30 18:49:29 +01:00
\\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 "<path to zls>")
\\ :major-modes '(zig-mode)
\\ :server-id 'zls))))
2021-03-31 15:54:27 +01:00
) ;
2021-03-30 18:49:29 +01:00
} ,
2022-06-06 18:53:20 +01:00
. Spacemacs = > {
write (
\\To use ZLS in Spacemacs, add the `lsp` and `zig` layers
\\to `dotspacemacs-configuration-layers` in your .spacemacs file.
\\Then, if you don't have `zls` in your PATH, add the following to
\\`dotspacemacs/user-config` in your .spacemacs file:
\\
\\(setq lsp-zig-zls-executable "<path to zls>")
) ;
} ,
2022-07-24 16:01:05 +01:00
. Helix = > {
write (
\\Helix has out of the box support for ZLS
\\Make sure you have added ZLS to your PATH
\\run hx --health to check if helix has found it.
) ;
} ,
2021-03-30 18:49:29 +01:00
. Other = > {
2021-03-31 15:54:27 +01:00
write (
2021-03-30 18:49:29 +01:00
\\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!
2021-03-31 15:54:27 +01:00
) ;
2021-03-30 18:49:29 +01:00
} ,
}
2021-03-31 15:54:27 +01:00
write ( " \n \n Thank you for choosing ZLS! \n " ) ;
2021-03-30 18:49:29 +01:00
}
2021-12-02 05:16:15 +00:00
pub fn findZig ( allocator : std . mem . Allocator ) ! ? [ ] const u8 {
2021-03-30 18:49:29 +01:00
const env_path = std . process . getEnvVarOwned ( allocator , " PATH " ) catch | err | switch ( err ) {
error . EnvironmentVariableNotFound = > {
return null ;
} ,
else = > return err ,
} ;
defer allocator . free ( env_path ) ;
2021-10-07 12:34:14 +01:00
const exe_extension = builtin . target . exeFileExt ( ) ;
2021-03-30 18:49:29 +01:00
const zig_exe = try std . fmt . allocPrint ( allocator , " zig{s} " , . { exe_extension } ) ;
defer allocator . free ( zig_exe ) ;
2021-08-09 17:14:20 +01:00
var it = std . mem . tokenize ( u8 , env_path , & [ _ ] u8 { std . fs . path . delimiter } ) ;
2021-03-30 18:49:29 +01:00
while ( it . next ( ) ) | path | {
2021-10-07 12:34:14 +01:00
if ( builtin . os . tag = = . windows ) {
2021-06-24 11:38:01 +01:00
if ( std . mem . indexOfScalar ( u8 , path , '/' ) ! = null ) continue ;
2021-04-03 10:14:52 +01:00
}
2021-10-01 01:52:36 +01:00
const full_path = try std . fs . path . join ( allocator , & [ _ ] [ ] const u8 { path , zig_exe } ) ;
2021-03-30 18:49:29 +01:00
defer allocator . free ( full_path ) ;
if ( ! std . fs . path . isAbsolute ( full_path ) ) continue ;
2021-06-24 11:38:01 +01:00
2021-03-30 18:49:29 +01:00
const file = std . fs . openFileAbsolute ( full_path , . { } ) catch continue ;
defer file . close ( ) ;
const stat = file . stat ( ) catch continue ;
if ( stat . kind = = . Directory ) continue ;
2021-03-31 12:46:56 +01:00
2021-03-30 18:49:29 +01:00
return try allocator . dupe ( u8 , full_path ) ;
}
return null ;
}