Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added project-specific Zed IDE settings #127793

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Session.vim
.idea
*.iml
.vscode
.zed
.project
.favorites.json
.settings/
Expand Down
129 changes: 118 additions & 11 deletions src/bootstrap/src/core/build_steps/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ pub enum Profile {

static PROFILE_DIR: &str = "src/bootstrap/defaults";

/// A list of historical hashes of `src/etc/rust_analyzer_settings.json`.
/// New entries should be appended whenever this is updated so we can detect
/// A list of historical SHA-256 hashes of `src/etc/vscode_settings.json`. New
/// entries should be appended whenever this is updated so we can detect
/// outdated vs. user-modified settings files.
static SETTINGS_HASHES: &[&str] = &[
static VSCODE_SETTINGS_HASHES: &[&str] = &[
"ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8",
"56e7bf011c71c5d81e0bf42e84938111847a810eee69d906bba494ea90b51922",
"af1b5efe196aed007577899db9dae15d6dbc923d6fa42fa0934e68617ba9bbe0",
Expand All @@ -46,7 +46,14 @@ static SETTINGS_HASHES: &[&str] = &[
"b526bd58d0262dd4dda2bff5bc5515b705fb668a46235ace3e057f807963a11a",
"828666b021d837a33e78d870b56d34c88a5e2c85de58b693607ec574f0c27000",
];
static RUST_ANALYZER_SETTINGS: &str = include_str!("../../../../etc/rust_analyzer_settings.json");
static VSCODE_SETTINGS: &str = include_str!("../../../../etc/vscode_settings.json");

/// A list of historical SHA-256 hashes of `src/etc/zed_settings.json`. New
/// entries should be appended whenever this is updated so we can detect
/// outdated vs. user-modified settings files.
static ZED_SETTINGS_HASHES: &[&str] =
&["08bb47a0a93947284102eece102cb2e21a91ff952bf21798df92590e31240d98"];
static ZED_SETTINGS: &str = include_str!("../../../../etc/zed_settings.json");

impl Profile {
fn include_path(&self, src_path: &Path) -> PathBuf {
Expand Down Expand Up @@ -527,11 +534,11 @@ undesirable, simply delete the `pre-push` file from .git/hooks."
Ok(())
}

/// Sets up or displays `src/etc/rust_analyzer_settings.json`
/// Sets up or displays `src/etc/vscode_settings.json`
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Vscode;
pub struct VsCode;

impl Step for Vscode {
impl Step for VsCode {
type Output = ();
const DEFAULT: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
Expand All @@ -543,7 +550,7 @@ impl Step for Vscode {
}
if let [cmd] = &run.paths[..] {
if cmd.assert_single_path().path.as_path().as_os_str() == "vscode" {
run.builder.ensure(Vscode);
run.builder.ensure(VsCode);
}
}
}
Expand All @@ -559,7 +566,7 @@ impl Step for Vscode {
/// Create a `.vscode/settings.json` file for rustc development, or just print it
/// If this method should be re-called, it returns `false`.
fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> {
let (current_hash, historical_hashes) = SETTINGS_HASHES.split_last().unwrap();
let (current_hash, historical_hashes) = VSCODE_SETTINGS_HASHES.split_last().unwrap();
let vscode_settings = config.src.join(".vscode").join("settings.json");
// If None, no settings.json exists
// If Some(true), is a previous version of settings.json
Expand Down Expand Up @@ -619,10 +626,110 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> {
}
_ => "Created",
};
fs::write(&vscode_settings, RUST_ANALYZER_SETTINGS)?;
fs::write(&vscode_settings, VSCODE_SETTINGS)?;
println!("{verb} `.vscode/settings.json`");
} else {
println!("\n{RUST_ANALYZER_SETTINGS}");
println!("\n{VSCODE_SETTINGS}");
}
Ok(should_create)
}

/// Sets up or displays `src/etc/zed_settings.json`
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Zed;

impl Step for Zed {
type Output = ();
const DEFAULT: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.alias("zed")
}
fn make_run(run: RunConfig<'_>) {
if run.builder.config.dry_run() {
return;
}
if let [cmd] = &run.paths[..] {
if cmd.assert_single_path().path.as_path().as_os_str() == "zed" {
run.builder.ensure(Zed);
}
}
}
fn run(self, builder: &Builder<'_>) -> Self::Output {
let config = &builder.config;
if config.dry_run() {
return;
}
while !t!(create_zed_settings_maybe(config)) {}
}
}
Comment on lines +641 to +664
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This makes it more readable

Suggested change
impl Step for Zed {
type Output = ();
const DEFAULT: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.alias("zed")
}
fn make_run(run: RunConfig<'_>) {
if run.builder.config.dry_run() {
return;
}
if let [cmd] = &run.paths[..] {
if cmd.assert_single_path().path.as_path().as_os_str() == "zed" {
run.builder.ensure(Zed);
}
}
}
fn run(self, builder: &Builder<'_>) -> Self::Output {
let config = &builder.config;
if config.dry_run() {
return;
}
while !t!(create_zed_settings_maybe(config)) {}
}
}
impl Step for Zed {
type Output = ();
const DEFAULT: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.alias("zed")
}
fn make_run(run: RunConfig<'_>) {
if run.builder.config.dry_run() {
return;
}
if let [cmd] = &run.paths[..] {
if cmd.assert_single_path().path.as_path().as_os_str() == "zed" {
run.builder.ensure(Zed);
}
}
}
fn run(self, builder: &Builder<'_>) -> Self::Output {
let config = &builder.config;
if config.dry_run() {
return;
}
while !t!(create_zed_settings_maybe(config)) {}
}
}


/// Create a `.zed/settings.json` file for rustc development, or just print it
/// If this method should be re-called, it returns `false`.
fn create_zed_settings_maybe(config: &Config) -> io::Result<bool> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this function be deduplicated with the vscode version?

let (current_hash, historical_hashes) = ZED_SETTINGS_HASHES.split_last().unwrap();
let zed_settings = config.src.join(".zed").join("settings.json");
// If None, no settings.json exists
// If Some(true), is a previous version of settings.json
// If Some(false), is not a previous version (i.e. user modified)
// If it's up to date we can just skip this
let mut mismatched_settings = None;
if let Ok(current) = fs::read_to_string(&zed_settings) {
let mut hasher = sha2::Sha256::new();
hasher.update(&current);
let hash = hex_encode(hasher.finalize().as_slice());
if hash == *current_hash {
return Ok(true);
} else if historical_hashes.contains(&hash.as_str()) {
mismatched_settings = Some(true);
} else {
mismatched_settings = Some(false);
}
}
println!(
"\nx.py can automatically install the recommended `.zed/settings.json` file for rustc development"
);
match mismatched_settings {
Some(true) => eprintln!(
"WARNING: existing `.zed/settings.json` is out of date, x.py will update it"
),
Some(false) => eprintln!(
"WARNING: existing `.zed/settings.json` has been modified by user, x.py will back it up and replace it"
),
_ => (),
}
let should_create = match prompt_user(
"Would you like to create/update settings.json? (Press 'p' to preview values): [y/N]",
)? {
Some(PromptResult::Yes) => true,
Some(PromptResult::Print) => false,
_ => {
println!("Ok, skipping settings!");
return Ok(true);
}
};
if should_create {
let path = config.src.join(".zed");
if !path.exists() {
fs::create_dir(&path)?;
}
let verb = match mismatched_settings {
// exists but outdated, we can replace this
Some(true) => "Updated",
// exists but user modified, back it up
Some(false) => {
// exists and is not current version or outdated, so back it up
let mut backup = zed_settings.clone();
backup.set_extension("json.bak");
eprintln!("WARNING: copying `settings.json` to `settings.json.bak`");
fs::copy(&zed_settings, &backup)?;
"Updated"
}
_ => "Created",
};
fs::write(&zed_settings, ZED_SETTINGS)?;
println!("{verb} `.zed/settings.json`");
} else {
println!("\n{ZED_SETTINGS}");
}
Ok(should_create)
}
2 changes: 1 addition & 1 deletion src/bootstrap/src/core/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,7 @@ impl<'a> Builder<'a> {
run::GenerateWindowsSys,
run::GenerateCompletions,
),
Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode),
Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::VsCode, setup::Zed),
Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std),
Kind::Vendor => describe!(vendor::Vendor),
// special-cased in Build::build()
Expand Down
5 changes: 3 additions & 2 deletions src/bootstrap/src/core/config/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,14 +443,15 @@ Arguments:
The profile is optional and you will be prompted interactively if it is not given.
The following profiles are available:
{}
To only set up the git hook, VS Code config or toolchain link, you may use
To only set up the git hook, VS Code config, Zed config, or toolchain link, you may use
./x.py setup hook
./x.py setup vscode
./x.py setup zed
./x.py setup link", Profile::all_for_help(" ").trim_end()))]
Setup {
/// Either the profile for `config.toml` or another setup action.
/// May be omitted to set up interactively
#[arg(value_name = "<PROFILE>|hook|vscode|link")]
#[arg(value_name = "<PROFILE>|hook|vscode|zed|link")]
profile: Option<PathBuf>,
},
/// Suggest a subset of tests to run, based on modified files
Expand Down
2 changes: 1 addition & 1 deletion src/etc/completions/x.py.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1642,7 +1642,7 @@ _x.py() {
return 0
;;
x.py__setup)
opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [<PROFILE>|hook|vscode|link] [PATHS]... [ARGS]..."
opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [<PROFILE>|hook|vscode|zed|link] [PATHS]... [ARGS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down
File renamed without changes.
48 changes: 48 additions & 0 deletions src/etc/zed_settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
// The following commented-out VS Code settings are not supported by Zed yet.
// "git.detectSubmodulesLimit": 20
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option was introduced for vscode in #120138. There is a fair chance that zed won't suffer from the same issue once it has a git ui anyway (they may not even add a hard limit on the amount of auto detected git submodules at all). I don't think there is any value in keeping this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be nicer to still keep one file for RA settings, then just delete this key via bootstrap (serde_json is already a dependency). So if there are future updates that work for both, they only need to happen in one file.

Copy link
Contributor Author

@ChaiTRex ChaiTRex Jul 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to do this properly, I think we'll need three files: one shared file for rust-analyzer settings and one file each for the non-rust-analyzer IDE-specific settings.

Even IDE settings that do the same thing are different between the two IDEs. For example, to turn on format-on-save, VS Code wants "editor.formatOnSave": true, while Zed wants "format_on_save": "on",.

There are also JSON structure requirements that differ between the IDEs. For example, VS Code wants the rust-analyzer settings to use the standard dotted notation at the root of the JSON tree, like:

{
    "rust-analyzer.rustc.source": "discover"
}

Zed wants the rust-analyzer settings to live under a path through the JSON tree of "lsp", then "rust-analyzer", then "initialization_options", then the setting with the original dots signifying an increase in tree depth:

{
  "lsp": {
    "rust-analyzer": {
      "initialization_options": {
        "rustc": {
          "source": "discover"
        }
      }
    }
  }
}

So this means that we need to do JSON parsing of our three files, transformation of the structure, and JSON generation of the resulting .vscode/settings.json and .zed/settings.json files.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also just have one rust source file with everything in json!, and build it dynamically

use serde_json::{json, Value};

fn base_settings() -> Value {
    json! {{
         // common settings 
    }}
}

fn vscode_settings() -> Value {
    let vscode_specific = json! {{ anything new}} 

    let mut inner = base_settings();
    inner.as_object_mut().unwrap().extend(vscode_specific);

    json! {{
         "lsp": {
             "rust_analyzer": /* other nesting */ inner,
         }
    }}
}

fn zed_settings() -> Value {
    // ...
}

Which would also be kind of nice because it's easy to cover any config file schema or format. E.g. I would appreciate a Helix config at some point, but that uses toml (easy to convert from serde_json to toml, both are already dependencies of bootstrap).

But 🤷, that may be overkill.

"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"invocationLocation": "root",
"invocationStrategy": "once",
"overrideCommand": ["python3", "x.py", "check", "--json-output"]
},
"linkedProjects": [
"Cargo.toml",
"src/tools/x/Cargo.toml",
"src/bootstrap/Cargo.toml",
"src/tools/rust-analyzer/Cargo.toml",
"compiler/rustc_codegen_cranelift/Cargo.toml",
"compiler/rustc_codegen_gcc/Cargo.toml"
],
"rustfmt": {
"overrideCommand": [
"${workspaceFolder}/build/host/rustfmt/bin/rustfmt",
"--edition=2021"
]
},
"procMacro": {
"server": "${workspaceFolder}/build/host/stage0/libexec/rust-analyzer-proc-macro-srv",
"enable": true
},
"cargo": {
"buildScripts": {
"enable": true,
"invocationLocation": "root",
"invocationStrategy": "once",
"overrideCommand": ["python3", "x.py", "check", "--json-output"]
},
"sysrootSrc": "./library",
"extraEnv": {
"RUSTC_BOOTSTRAP": "1"
}
},
"rustc": {
"source": "./Cargo.toml"
}
}
}
}
}
Loading