Skip to content

Add VS Code create new project command#22103

Open
joshka wants to merge 2 commits intorust-lang:masterfrom
joshka:joshka/new-project-command
Open

Add VS Code create new project command#22103
joshka wants to merge 2 commits intorust-lang:masterfrom
joshka:joshka/new-project-command

Conversation

@joshka
Copy link
Copy Markdown
Contributor

@joshka joshka commented Apr 20, 2026

Fixes #19338.

Summary

Add a VS Code-side rust-analyzer: Create New Project... command that creates a new Cargo
binary or library package from the editor.

This adds:

  • the rust-analyzer.newProject command
  • a rust-analyzer.projectCreation.openAfterCreate setting for post-create behavior
  • command palette and Explorer empty-state entry points
  • a walkthrough step
  • unit tests for the local validation/action-selection/argument-building logic

Behavior

The command flow is:

  1. resolve and validate Cargo
  2. choose Binary Application or Library
  3. choose the parent folder
  4. enter the project name
  5. run cargo new
  6. choose whether to open the project, open it in a new window, or add it to the workspace

The implementation stays entirely in the VS Code extension and does not add any rust-analyzer
server/LSP changes.

Notes and tradeoffs

  • Local validation is intentionally narrow. It rejects obviously invalid directory names and lets
    cargo new remain the source of truth for package-name-specific validation.
  • The command now uses rust-analyzer's effective environment (server.extraEnv) both to resolve
    and to run Cargo. While implementing this, I found a broader extension-side inconsistency where
    some tool-launch paths already pass an env through but cargoPath(env) still resolved against
    the VS Code host process environment. This patch makes cargoPath(env) honor the supplied env's
    CARGO, PATH, and CARGO_HOME view.
  • Command-failure logging was adjusted to avoid dumping the merged environment. Environment
    variables may contain secrets (API keys, tokens, credentials), so failures log only the command,
    cwd, exit status, and stdout/stderr/error details.
  • The project-name prompt now shows the selected base path (for example ~/code) to make it clear
    that the folder picker selects the parent directory and the project will be created underneath it.

Manual smoke tests

Tested manually for:

  • Empty VS Code window -> create binary project -> choose Open.
  • Empty VS Code window -> create library project -> choose Open in New Window.
  • Existing single-folder workspace -> create project -> choose Add to Workspace and confirm RA discovers it without window reload.
  • Existing multi-root workspace -> create project -> choose each open mode and confirm behavior matches the prompt.
  • Failure path with missing/broken cargo -> clear error message.
  • Failure path with Cargo-rejected package name -> Cargo error is shown rather than silently swallowed.
  • Explorer empty-state entry launches the same flow as the command palette command.

Follow-up work

  • Activating the extension from an empty window for rust-analyzer.newProject currently still
    flips the inRustProject context to true because activation unconditionally sets that context
    today. That is a broader context-management issue rather than part of the project-creation flow
    itself, and should be handled as a follow-up so newProject remains available in empty windows
    without exposing unrelated Rust-project-only UI.

AI disclosure

I used Codex/AI tooling to research similar editor implementations, refine the implementation plan,
review parts of the code structure, and draft/refine portions of this change. I reviewed the
resulting code and notes before opening this PR.

Comment on lines +919 to +924
/**
* Entry point for the `rust-analyzer: Create New Project...` command.
*
* This function keeps the user-visible workflow in one place: resolve Cargo, gather the project
* inputs, run `cargo new`, and then apply the requested post-create workspace action.
*/
Copy link
Copy Markdown
Contributor Author

@joshka joshka Apr 20, 2026

Choose a reason for hiding this comment

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

I intentionally steered codex to produce docs as the intent helps make the code readable IMO. Intention revealing names are good, but they don't convey tradeoffs / rationale / etc. This might be a bit noisier than most places in RA, but this is an intentional choice to improve such things.

View changes since the review

* inputs, run `cargo new`, and then apply the requested post-create workspace action.
*/
export function newProject(ctx: Ctx): Cmd {
return async () => {
Copy link
Copy Markdown
Contributor Author

@joshka joshka Apr 20, 2026

Choose a reason for hiding this comment

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

I split this method up from a longer one that had each of the pieces in a single method. It was difficult to follow / understand. This feels a bit table of contents and similarly problematic - there's probably a good balance, but I feel this is closer to the right size than the opposite (all the helper methods inlined here).

View changes since the review

Comment on lines +983 to +985
// Use the same effective environment rust-analyzer uses elsewhere so project creation sees
// toolchain wrappers, PATH overrides, and CARGO_HOME changes from configuration.
const cargoEnv = { ...process.env, ...ctx.config.serverExtraEnv };
Copy link
Copy Markdown
Contributor Author

@joshka joshka Apr 20, 2026

Choose a reason for hiding this comment

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

This is the part I'm least familiar with being correct choices. I think it's reasonably correct based on my understanding of what is going on here, but would value a second opinion.

View changes since the review

return async () => ctx.client.sendRequest(ra.reloadWorkspace);
}

type NewProjectKind = "bin" | "lib";
Copy link
Copy Markdown
Contributor Author

@joshka joshka Apr 20, 2026

Choose a reason for hiding this comment

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

This is a bunch of code to dump in an existing module. I'd tend to split additive code like this into a new separate module as it cuts down the cognitive load a bit, but went with keeping with the existing convention here rather than doing that. Ideally this code probably belongs in newProject.ts

View changes since the review

// variables may contain secrets such as API keys, tokens, and credentials, so failure logs
// must not dump the merged env here.
const commandLine = [logContext.cargo, ...logContext.args].join(" ");
log.error(message);
Copy link
Copy Markdown
Contributor Author

@joshka joshka Apr 20, 2026

Choose a reason for hiding this comment

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

Not sure if there's an existing better logging approach here. The initial implementation just showed a notification, but I figure that there's a bunch of failure cases where the details of what's going on in cargo invocation is likely a bit more detailed for whatever reason. Maybe it's unlikely to hit this point. This should probably be extracted to a common function somewhere. Codex didn't find an obvious existing pattern / code to reuse for this.

View changes since the review

if (env?.["RUSTC_TOOLCHAIN"]) {
return Promise.resolve("cargo");
}
if (env) {
Copy link
Copy Markdown
Contributor Author

@joshka joshka Apr 20, 2026

Choose a reason for hiding this comment

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

I'm not sure I 100% understand this part of the change.

View changes since the review

Comment on lines +163 to +165
if (env) {
return getPathForExecutableWithEnv("cargo", env);
}
Copy link
Copy Markdown
Contributor Author

@joshka joshka Apr 20, 2026

Choose a reason for hiding this comment

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

The new-project command builds an effective Cargo environment from rust-analyzer configuration (server.extraEnv) before running cargo new. Resolve cargo from that same env here so the command does not discover one Cargo binary and then execute another.

View changes since the review

Implement a rust-analyzer VS Code command for creating new Cargo
projects from the editor. Add command registration, UI entry points,
configuration for post-create behavior, and tests for the command
flow.

AI tools were used to research comparable editor behaviors and help
refine the implementation plan; the resulting changes were reviewed
before submission.
@joshka joshka force-pushed the joshka/new-project-command branch from d7ff960 to 28f842e Compare April 20, 2026 02:43
@joshka
Copy link
Copy Markdown
Contributor Author

joshka commented Apr 20, 2026

image image image image image image image image

@joshka joshka marked this pull request as ready for review April 20, 2026 02:52
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 20, 2026
@ChayimFriedman2
Copy link
Copy Markdown
Contributor

This is a large wall of code, and I'm not even sure this feature is in scope for r-a.

@joshka
Copy link
Copy Markdown
Contributor Author

joshka commented Apr 20, 2026

This is a large wall of code, and I'm not even sure this feature is in scope for r-a.

It's working, self-reviewed, tested manually and with unit tests, with a firm spec in the linked issue that I spent time iterating on prior to throwing the tooling at it, plus refactoring and improving the generated code.

It exists because other extensions do this already, and it allows me to live in vscode rather than a terminal in order to create rust applications / libraries. This seems like a solidly good way to validate whether it's scope.

r-a is the place where rust projects live in vscode. From that perspective creating a new rust project in vscode should live in r-a.

Comment thread editors/code/src/commands.ts Outdated
function displayProjectBasePath(parentFolder: vscode.Uri): string {
const fsPath = parentFolder.fsPath;
const home = os.homedir();
if (fsPath === home) {
Copy link
Copy Markdown

@stefnotch stefnotch Apr 22, 2026

Choose a reason for hiding this comment

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

This won't ever be true on Windows, because the drive letter won't match. In general, I tend to be extremely suspicious of any path manipulation, because it's so easy to get it wrong.

Will handle UNC paths and normalize windows drive letters to lower-case
https://code.visualstudio.com/api/references/vscode-api#Uri

View changes since the review

Copy link
Copy Markdown
Contributor Author

@joshka joshka Apr 22, 2026

Choose a reason for hiding this comment

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

Removed this home-relative ~ shortening for now to simplify the PR. We can re-add it later if it proves useful or necessary.


// Keep local validation intentionally narrow: reject obviously invalid folder names up front,
// then let `cargo new` remain the source of truth for package-name-specific rules.
export function validateNewProjectName(
Copy link
Copy Markdown

@stefnotch stefnotch Apr 22, 2026

Choose a reason for hiding this comment

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

Is there any upside in doing this validation?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes - you get immediate results in the ui where it prompts for a name rather than after you hit return.

image

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field

Then why not harden the check to be like cargo itself? It seems like a pretty straightforward regex.

const openNewWindow = "Open in New Window";
const choices = [open, openNewWindow];

const addToWorkspace = "Add to Workspace";
Copy link
Copy Markdown

@stefnotch stefnotch Apr 22, 2026

Choose a reason for hiding this comment

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

What does "workspace" mean in this context? Is it about adding it to the cargo workspace or about the vscode workspace?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

vscode workspace. This is about running cargo new. That cannot add a crate to a workspace.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I see. Would it be possible to change the wording?
If I used this cargo new command in a sub-folder of a cargo workspace, then the project would be automatically added to the cargo workspace.

At which point I'd be confused about why the extension is asking about a workspace. Until I figure out that it is asking about a vscode workspace.

@stefnotch
Copy link
Copy Markdown

stefnotch commented Apr 22, 2026

As a general question, which other extensions do this? I don't have any extensions installed with a similar feature, except for the Typst one. Though I don't fully understand the Typst one. I think Typst does it, because it doesn't rely on a CLI?

@joshka
Copy link
Copy Markdown
Contributor Author

joshka commented Apr 22, 2026

As a general question, which other extensions do this? I don't have any extensions installed with a similar feature, except for the Typst one. Though I don't fully understand the Typst one. I think Typst does it, because it doesn't rely on a CLI?

(From the issue plan details) #19338 (comment):
I also checked several other extensions to ground the expected behavior and tradeoffs here,
including Swift, Java, Python, Go, and .NET.

Remove home-relative path shortening from the new-project prompt to
avoid adding path-normalization logic for a cosmetic display detail.

AI-assisted-by: OpenAI Codex
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create a new rust project from VSCode (cargo new / cargo init)

4 participants