nvim-lsp: Add settings for rust-analyzer (#113)

* nvim-lsp: Add settings for rust-analyzer

Because the settings are quite complex they are auto-generated. This
commit adds the script creating it & the generated options.

* Update flake.nix

Co-authored-by: Pedro Alves <pta2002@users.noreply.github.com>
This commit is contained in:
traxys 2023-01-21 18:15:46 +01:00 committed by GitHub
parent 5b84bbb513
commit 2f2f724f6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 1846 additions and 2 deletions

View file

@ -35,10 +35,49 @@
(system:
let
pkgs = import nixpkgs { inherit system; };
extractRustAnalyzer = { stdenv, pkgs }: stdenv.mkDerivation {
pname = "extract_rust_analyzer";
version = "master";
dontUnpack = true;
dontBuild = true;
buildInputs = [ pkgs.python3 ];
installPhase = ''
ls -la
mkdir -p $out/bin
cp ${./helpers/extract_rust_analyzer.py} $out/bin/extract_rust_analyzer.py
'';
};
extractRustAnalyzerPkg = pkgs.callPackage extractRustAnalyzer { };
in
{
packages.docs = pkgs.callPackage (import ./docs.nix) {
modules = nixvimModules;
packages = {
docs = pkgs.callPackage (import ./docs.nix) {
modules = nixvimModules;
};
runUpdates = pkgs.callPackage
({ pkgs, stdenv }: stdenv.mkDerivation {
pname = "run-updates";
version = pkgs.rust-analyzer.version;
src = pkgs.rust-analyzer.src;
nativeBuildInputs = with pkgs; [extractRustAnalyzerPkg alejandra nixpkgs-fmt];
buildPhase = ''
extract_rust_analyzer.py editors/code/package.json |
alejandra --quiet |
nixpkgs-fmt > rust-analyzer-config.nix
'';
installPhase = ''
mkdir -p $out/share
cp rust-analyzer-config.nix $out/share
'';
})
{ };
};
legacyPackages = rec {
makeNixvimWithModule = import ./wrappers/standalone.nix pkgs modules;

229
helpers/extract_rust_analyzer.py Executable file
View file

@ -0,0 +1,229 @@
#!/usr/bin/env python3
import sys
import json
ra_package_json = sys.argv[1]
with open(ra_package_json) as f:
ra_package = json.load(f)
config = ra_package["contributes"]["configuration"]["properties"]
config_dict = {}
in_common_block = False
def py_to_nix(obj):
if obj is None:
return "null"
if obj is False:
return "false"
if obj is True:
return "true"
if isinstance(obj, str):
s = f'"{obj}"'
if "${" in s:
s = s.replace("${", "$\\{")
return s
if isinstance(obj, int):
return f"{obj}"
if isinstance(obj, dict):
val = "{"
for key in obj:
key_val = py_to_nix(obj[key])
val += f'"{key}" = {key_val};\n'
val += "}"
return val
if isinstance(obj, list):
return "[" + " ".join(py_to_nix(val) for val in obj) + "]"
print(f"Unhandled value: {obj}")
sys.exit(1)
def ty_to_nix(ty):
if ty == "boolean":
return "types.bool"
if ty == "string":
return "types.str"
# This is an object without any additional properties
if ty == "object":
return "types.attrsOf types.anything"
if isinstance(ty, list) and ty[0] == "null":
if len(ty) > 2:
print("Unhandled type", ty)
sys.exit()
nullable_ty = ty_to_nix(ty[1])
return f"types.nullOr ({nullable_ty})"
if isinstance(ty, list):
either_types = (ty_to_nix(t) for t in ty)
either_types = " ".join(f"({t})" for t in either_types)
return f"types.oneOf ([{either_types}])"
print(f"Unhandled type: {ty}")
sys.exit(1)
def prop_ty_to_nix(prop_info):
if "type" in prop_info:
if "enum" in prop_info:
enum = "[" + " ".join(f'"{member}"' for member in prop_info["enum"]) + "]"
if prop_info["type"] == "string":
return f"types.enum {enum}"
print("TODO: with unknown enum type", prop_info["type"])
sys.exit()
if "additionalProperties" in prop_info or "properties" in prop_info:
print("TODO: with (additional)Properties", prop_info)
sys.exit()
if "minimum" in prop_info or "maximum" in prop_info:
can_be_null = False
if "null" in prop_info["type"]:
can_be_null = True
if len(prop_info["type"]) > 2:
print("Unhandled int type", prop_info["type"])
sys.exit()
prop_info["type"] = prop_info["type"][1]
if prop_info["type"] == "number":
int_ty = "types.number"
elif prop_info["type"] == "integer":
int_ty = "types.int"
else:
print("Unhandled int type", prop_info["type"])
sys.exit()
if "minimum" in prop_info and "maximum" in prop_info:
min = prop_info["minimum"]
max = prop_info["maximum"]
int_ty = f"{int_ty}s.between {min} {max}"
elif "minimum" in prop_info:
min = prop_info["minimum"]
int_ty = f"types.addCheck {int_ty} (x: x >= {min})"
else:
print("TODO: max number", prop_info)
sys.exit()
if can_be_null:
return f"types.nullOr ({int_ty})"
else:
return int_ty
if "array" in prop_info["type"] or prop_info["type"] == "array":
if "items" not in prop_info:
print("Array without items")
sys.exit()
items_ty = prop_ty_to_nix(prop_info["items"])
array_ty = f"types.listOf ({items_ty})"
if prop_info["type"] == "array":
return array_ty
elif prop_info["type"] == ["null", "array"]:
return f"types.nullOr ({array_ty})"
else:
print("Unhandled array type", prop_info)
sys.exit()
return ty_to_nix(prop_info["type"])
elif "anyOf" in prop_info:
can_be_null = False
if {"type": "null"} in prop_info["anyOf"]:
can_be_null = True
prop_info["anyOf"].remove({"type": "null"})
types = (prop_ty_to_nix(prop) for prop in prop_info["anyOf"])
one_of = " ".join(f"({ty})" for ty in types)
one_of_ty = f"types.oneOf ([{one_of}])"
if can_be_null:
return f"types.nullOr ({one_of_ty})"
else:
return one_of_ty
else:
print("TODO: no *type*", prop_info)
sys.exit()
for opt in config:
if opt.startswith("$"):
in_common_block = True
continue
if not in_common_block:
continue
opt_path = opt.split(".")
if opt_path[0] != "rust-analyzer":
print("ERROR: expected all options to start with 'rust-analyzer'")
sys.exit(1)
path = opt_path[1:-1]
option = opt_path[-1]
top_dict = config_dict
for p in path:
if not p in top_dict:
top_dict[p] = {}
top_dict = top_dict[p]
prop_info = config[opt]
is_optional = False
ty = prop_ty_to_nix(prop_info)
default = py_to_nix(prop_info["default"])
if "markdownDescription" in prop_info:
desc = prop_info["markdownDescription"]
else:
desc = prop_info["description"]
desc += f"\n\ndefault value is: \n```nix\n {default}\n```"
top_dict[
option
] = """
mkOption {{
type = types.nullOr ({ty});
default = null;
description = ''
{desc}
'';
}}
""".format(
ty=ty, default=default, desc=desc
)
def print_dict(d):
print("{")
for key in d:
print(f'"{key}" = ')
if isinstance(d[key], str):
print(d[key])
else:
print_dict(d[key])
print(";")
print("}")
print("# THIS FILE IS AUTOGENERATED DO NOT EDIT")
print("lib: with lib;")
print_dict(config_dict)

View file

@ -197,6 +197,9 @@ let
name = "rust-analyzer";
description = "Enable rust-analyzer, for Rust.";
serverName = "rust_analyzer";
extraOptions = import ./rust-analyzer-config.nix lib;
settings = cfg: { rust-analyzer = cfg; };
}
{
name = "sumneko-lua";

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
# Updating rust-analyzer options
Because there a large number of rust-analyzer options it's difficult to handle them by hand.
The options can be fetched from the [rust-analyzer package.json](https://github.com/rust-lang/rust-analyzer/blob/master/editors/code/package.json).
There is a derivation on the top-level flake that allows to build it easily, you just have to run:
```bash
nix build .#rustAnalyzerOptions
```
You can then copy the `result/share/rust-analyzer-config.nix` to the correct location.