mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
windows: Add support for fetching shell environment in remote projects (#39831)
Closes #39216 Note that this affects all platforms, I'm just using the prefix to make auto-cherry-picking easier. Release Notes: - Fixed shell commands run by agents failing to find installed programs in some cases.
This commit is contained in:
parent
abc1e67221
commit
92e765b5d2
14 changed files with 180 additions and 59 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -13077,6 +13077,7 @@ dependencies = [
|
|||
"shellexpand 2.1.2",
|
||||
"smol",
|
||||
"sysinfo",
|
||||
"task",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.8.20",
|
||||
"unindent",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ use std::{
|
|||
cmp::Reverse,
|
||||
collections::HashSet,
|
||||
fmt::Write,
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
|
@ -328,17 +327,13 @@ impl ActivityIndicator {
|
|||
.flatten()
|
||||
}
|
||||
|
||||
fn pending_environment_errors<'a>(
|
||||
&'a self,
|
||||
cx: &'a App,
|
||||
) -> impl Iterator<Item = (&'a Arc<Path>, &'a EnvironmentErrorMessage)> {
|
||||
self.project.read(cx).shell_environment_errors(cx)
|
||||
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a EnvironmentErrorMessage> {
|
||||
self.project.read(cx).peek_environment_error(cx)
|
||||
}
|
||||
|
||||
fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {
|
||||
// Show if any direnv calls failed
|
||||
if let Some((abs_path, error)) = self.pending_environment_errors(cx).next() {
|
||||
let abs_path = abs_path.clone();
|
||||
if let Some(error) = self.pending_environment_error(cx) {
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Warning)
|
||||
|
|
@ -348,7 +343,7 @@ impl ActivityIndicator {
|
|||
message: error.0.clone(),
|
||||
on_click: Some(Arc::new(move |this, window, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
project.remove_environment_error(&abs_path, cx);
|
||||
project.pop_environment_error(cx);
|
||||
});
|
||||
window.dispatch_action(Box::new(workspace::OpenLog), cx);
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ use rpc::{AnyProtoClient, TypedEnvelope, proto};
|
|||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::SettingsStore;
|
||||
use task::Shell;
|
||||
use util::{ResultExt as _, debug_panic};
|
||||
|
||||
use crate::ProjectEnvironment;
|
||||
|
|
@ -850,7 +851,11 @@ impl ExternalAgentServer for LocalGemini {
|
|||
cx.spawn(async move |cx| {
|
||||
let mut env = project_environment
|
||||
.update(cx, |project_environment, cx| {
|
||||
project_environment.get_directory_environment(root_dir.clone(), cx)
|
||||
project_environment.get_local_directory_environment(
|
||||
&Shell::System,
|
||||
root_dir.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
|
@ -937,7 +942,11 @@ impl ExternalAgentServer for LocalClaudeCode {
|
|||
cx.spawn(async move |cx| {
|
||||
let mut env = project_environment
|
||||
.update(cx, |project_environment, cx| {
|
||||
project_environment.get_directory_environment(root_dir.clone(), cx)
|
||||
project_environment.get_local_directory_environment(
|
||||
&Shell::System,
|
||||
root_dir.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
|
@ -1023,7 +1032,11 @@ impl ExternalAgentServer for LocalCodex {
|
|||
cx.spawn(async move |cx| {
|
||||
let mut env = project_environment
|
||||
.update(cx, |project_environment, cx| {
|
||||
project_environment.get_directory_environment(root_dir.clone(), cx)
|
||||
project_environment.get_local_directory_environment(
|
||||
&Shell::System,
|
||||
root_dir.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
|
@ -1163,7 +1176,11 @@ impl ExternalAgentServer for LocalCustomAgent {
|
|||
cx.spawn(async move |cx| {
|
||||
let mut env = project_environment
|
||||
.update(cx, |project_environment, cx| {
|
||||
project_environment.get_directory_environment(root_dir.clone(), cx)
|
||||
project_environment.get_local_directory_environment(
|
||||
&Shell::System,
|
||||
root_dir.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::{Arc, Once},
|
||||
};
|
||||
use task::{DebugScenario, SpawnInTerminal, TaskContext, TaskTemplate};
|
||||
use task::{DebugScenario, Shell, SpawnInTerminal, TaskContext, TaskTemplate};
|
||||
use util::{ResultExt as _, rel_path::RelPath};
|
||||
use worktree::Worktree;
|
||||
|
||||
|
|
@ -279,7 +279,11 @@ impl DapStore {
|
|||
.unwrap()
|
||||
.environment
|
||||
.update(cx, |environment, cx| {
|
||||
environment.get_directory_environment(cwd, cx)
|
||||
environment.get_local_directory_environment(
|
||||
&Shell::System,
|
||||
cwd,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})?
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use futures::{FutureExt, future::Shared};
|
||||
use language::Buffer;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use remote::RemoteClient;
|
||||
use rpc::proto::{self, REMOTE_SERVER_PROJECT_ID};
|
||||
use std::{collections::VecDeque, path::Path, sync::Arc};
|
||||
use task::Shell;
|
||||
use util::ResultExt;
|
||||
use worktree::Worktree;
|
||||
|
|
@ -16,10 +18,9 @@ use crate::{
|
|||
|
||||
pub struct ProjectEnvironment {
|
||||
cli_environment: Option<HashMap<String, String>>,
|
||||
environments: HashMap<Arc<Path>, Shared<Task<Option<HashMap<String, String>>>>>,
|
||||
shell_based_environments:
|
||||
HashMap<(Shell, Arc<Path>), Shared<Task<Option<HashMap<String, String>>>>>,
|
||||
environment_error_messages: HashMap<Arc<Path>, EnvironmentErrorMessage>,
|
||||
local_environments: HashMap<(Shell, Arc<Path>), Shared<Task<Option<HashMap<String, String>>>>>,
|
||||
remote_environments: HashMap<(Shell, Arc<Path>), Shared<Task<Option<HashMap<String, String>>>>>,
|
||||
environment_error_messages: VecDeque<EnvironmentErrorMessage>,
|
||||
}
|
||||
|
||||
pub enum ProjectEnvironmentEvent {
|
||||
|
|
@ -32,8 +33,8 @@ impl ProjectEnvironment {
|
|||
pub fn new(cli_environment: Option<HashMap<String, String>>) -> Self {
|
||||
Self {
|
||||
cli_environment,
|
||||
environments: Default::default(),
|
||||
shell_based_environments: Default::default(),
|
||||
local_environments: Default::default(),
|
||||
remote_environments: Default::default(),
|
||||
environment_error_messages: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -48,19 +49,6 @@ impl ProjectEnvironment {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over all pairs `(abs_path, error_message)` of
|
||||
/// environment errors associated with this project environment.
|
||||
pub(crate) fn environment_errors(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (&Arc<Path>, &EnvironmentErrorMessage)> {
|
||||
self.environment_error_messages.iter()
|
||||
}
|
||||
|
||||
pub(crate) fn remove_environment_error(&mut self, abs_path: &Path, cx: &mut Context<Self>) {
|
||||
self.environment_error_messages.remove(abs_path);
|
||||
cx.emit(ProjectEnvironmentEvent::ErrorsUpdated);
|
||||
}
|
||||
|
||||
pub(crate) fn get_buffer_environment(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
|
|
@ -115,15 +103,16 @@ impl ProjectEnvironment {
|
|||
abs_path = parent.into();
|
||||
}
|
||||
|
||||
self.get_directory_environment(abs_path, cx)
|
||||
self.get_local_directory_environment(&Shell::System, abs_path, cx)
|
||||
}
|
||||
|
||||
/// Returns the project environment, if possible.
|
||||
/// If the project was opened from the CLI, then the inherited CLI environment is returned.
|
||||
/// If it wasn't opened from the CLI, and an absolute path is given, then a shell is spawned in
|
||||
/// that directory, to get environment variables as if the user has `cd`'d there.
|
||||
pub fn get_directory_environment(
|
||||
pub fn get_local_directory_environment(
|
||||
&mut self,
|
||||
shell: &Shell,
|
||||
abs_path: Arc<Path>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Shared<Task<Option<HashMap<String, String>>>> {
|
||||
|
|
@ -136,26 +125,53 @@ impl ProjectEnvironment {
|
|||
return Task::ready(Some(cli_environment)).shared();
|
||||
}
|
||||
|
||||
self.environments
|
||||
.entry(abs_path.clone())
|
||||
self.local_environments
|
||||
.entry((shell.clone(), abs_path.clone()))
|
||||
.or_insert_with(|| {
|
||||
get_directory_env_impl(&Shell::System, abs_path.clone(), cx).shared()
|
||||
get_local_directory_environment_impl(shell, abs_path.clone(), cx).shared()
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Returns the project environment, if possible, with the given shell.
|
||||
pub fn get_directory_environment_for_shell(
|
||||
pub fn get_remote_directory_environment(
|
||||
&mut self,
|
||||
shell: &Shell,
|
||||
abs_path: Arc<Path>,
|
||||
remote_client: Entity<RemoteClient>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Shared<Task<Option<HashMap<String, String>>>> {
|
||||
self.shell_based_environments
|
||||
if cfg!(any(test, feature = "test-support")) {
|
||||
return Task::ready(Some(HashMap::default())).shared();
|
||||
}
|
||||
|
||||
self.remote_environments
|
||||
.entry((shell.clone(), abs_path.clone()))
|
||||
.or_insert_with(|| get_directory_env_impl(shell, abs_path.clone(), cx).shared())
|
||||
.or_insert_with(|| {
|
||||
let response =
|
||||
remote_client
|
||||
.read(cx)
|
||||
.proto_client()
|
||||
.request(proto::GetDirectoryEnvironment {
|
||||
project_id: REMOTE_SERVER_PROJECT_ID,
|
||||
shell: Some(shell.clone().to_proto()),
|
||||
directory: abs_path.to_string_lossy().to_string(),
|
||||
});
|
||||
cx.spawn(async move |_, _| {
|
||||
let environment = response.await.log_err()?;
|
||||
Some(environment.environment.into_iter().collect())
|
||||
})
|
||||
.shared()
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn peek_environment_error(&self) -> Option<&EnvironmentErrorMessage> {
|
||||
self.environment_error_messages.front()
|
||||
}
|
||||
|
||||
pub fn pop_environment_error(&mut self) -> Option<EnvironmentErrorMessage> {
|
||||
self.environment_error_messages.pop_front()
|
||||
}
|
||||
}
|
||||
|
||||
fn set_origin_marker(env: &mut HashMap<String, String>, origin: EnvironmentOrigin) {
|
||||
|
|
@ -307,7 +323,7 @@ async fn load_shell_environment(
|
|||
}
|
||||
}
|
||||
|
||||
fn get_directory_env_impl(
|
||||
fn get_local_directory_environment_impl(
|
||||
shell: &Shell,
|
||||
abs_path: Arc<Path>,
|
||||
cx: &Context<ProjectEnvironment>,
|
||||
|
|
@ -341,8 +357,8 @@ fn get_directory_env_impl(
|
|||
|
||||
if let Some(error) = error_message {
|
||||
this.update(cx, |this, cx| {
|
||||
log::error!("{error}",);
|
||||
this.environment_error_messages.insert(abs_path, error);
|
||||
log::error!("{error}");
|
||||
this.environment_error_messages.push_back(error);
|
||||
cx.emit(ProjectEnvironmentEvent::ErrorsUpdated)
|
||||
})
|
||||
.log_err();
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ use std::{
|
|||
time::Instant,
|
||||
};
|
||||
use sum_tree::{Edit, SumTree, TreeSet};
|
||||
use task::Shell;
|
||||
use text::{Bias, BufferId};
|
||||
use util::{
|
||||
ResultExt, debug_panic,
|
||||
|
|
@ -4599,7 +4600,7 @@ impl Repository {
|
|||
.upgrade()
|
||||
.context("missing project environment")?
|
||||
.update(cx, |project_environment, cx| {
|
||||
project_environment.get_directory_environment(work_directory_abs_path.clone(), cx)
|
||||
project_environment.get_local_directory_environment(&Shell::System, work_directory_abs_path.clone(), cx)
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_else(|| {
|
||||
|
|
|
|||
|
|
@ -1912,20 +1912,24 @@ impl Project {
|
|||
cx: &mut App,
|
||||
) -> Shared<Task<Option<HashMap<String, String>>>> {
|
||||
self.environment.update(cx, |environment, cx| {
|
||||
environment.get_directory_environment_for_shell(shell, abs_path, cx)
|
||||
if let Some(remote_client) = self.remote_client.clone() {
|
||||
environment.get_remote_directory_environment(shell, abs_path, remote_client, cx)
|
||||
} else {
|
||||
environment.get_local_directory_environment(shell, abs_path, cx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn shell_environment_errors<'a>(
|
||||
pub fn peek_environment_error<'a>(
|
||||
&'a self,
|
||||
cx: &'a App,
|
||||
) -> impl Iterator<Item = (&'a Arc<Path>, &'a EnvironmentErrorMessage)> {
|
||||
self.environment.read(cx).environment_errors()
|
||||
) -> Option<&'a EnvironmentErrorMessage> {
|
||||
self.environment.read(cx).peek_environment_error()
|
||||
}
|
||||
|
||||
pub fn remove_environment_error(&mut self, abs_path: &Path, cx: &mut Context<Self>) {
|
||||
self.environment.update(cx, |environment, cx| {
|
||||
environment.remove_environment_error(abs_path, cx);
|
||||
pub fn pop_environment_error(&mut self, cx: &mut Context<Self>) {
|
||||
self.environment.update(cx, |environment, _| {
|
||||
environment.pop_environment_error();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use rpc::{
|
|||
},
|
||||
};
|
||||
use settings::WorktreeId;
|
||||
use task::Shell;
|
||||
use util::{ResultExt as _, rel_path::RelPath};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -521,7 +522,11 @@ impl LocalToolchainStore {
|
|||
|
||||
let project_env = environment
|
||||
.update(cx, |environment, cx| {
|
||||
environment.get_directory_environment(abs_path.as_path().into(), cx)
|
||||
environment.get_local_directory_environment(
|
||||
&Shell::System,
|
||||
abs_path.as_path().into(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
|
|
@ -574,7 +579,11 @@ impl LocalToolchainStore {
|
|||
|
||||
let project_env = environment
|
||||
.update(cx, |environment, cx| {
|
||||
environment.get_directory_environment(path.as_path().into(), cx)
|
||||
environment.get_local_directory_environment(
|
||||
&Shell::System,
|
||||
path.as_path().into(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
cx.background_spawn(async move { toolchain_lister.resolve(path, project_env).await })
|
||||
|
|
|
|||
|
|
@ -48,3 +48,13 @@ message SpawnInTerminal {
|
|||
map<string, string> env = 4;
|
||||
optional string cwd = 5;
|
||||
}
|
||||
|
||||
message GetDirectoryEnvironment {
|
||||
uint64 project_id = 1;
|
||||
Shell shell = 2;
|
||||
string directory = 3;
|
||||
}
|
||||
|
||||
message DirectoryEnvironment {
|
||||
map<string, string> environment = 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -418,7 +418,10 @@ message Envelope {
|
|||
|
||||
GitRenameBranch git_rename_branch = 380;
|
||||
|
||||
RemoteStarted remote_started = 381; // current max
|
||||
RemoteStarted remote_started = 381;
|
||||
|
||||
GetDirectoryEnvironment get_directory_environment = 382;
|
||||
DirectoryEnvironment directory_environment = 383; // current max
|
||||
}
|
||||
|
||||
reserved 87 to 88;
|
||||
|
|
|
|||
|
|
@ -319,6 +319,8 @@ messages!(
|
|||
(GitClone, Background),
|
||||
(GitCloneResponse, Background),
|
||||
(ToggleLspLogs, Background),
|
||||
(GetDirectoryEnvironment, Background),
|
||||
(DirectoryEnvironment, Background),
|
||||
(GetAgentServerCommand, Background),
|
||||
(AgentServerCommand, Background),
|
||||
(ExternalAgentsUpdated, Background),
|
||||
|
|
@ -497,6 +499,7 @@ request_messages!(
|
|||
(GetDefaultBranch, GetDefaultBranchResponse),
|
||||
(GitClone, GitCloneResponse),
|
||||
(ToggleLspLogs, Ack),
|
||||
(GetDirectoryEnvironment, DirectoryEnvironment),
|
||||
(GetProcesses, GetProcessesResponse),
|
||||
(GetAgentServerCommand, AgentServerCommand),
|
||||
(RemoteStarted, Ack),
|
||||
|
|
@ -634,6 +637,7 @@ entity_messages!(
|
|||
GitCheckoutFiles,
|
||||
SetIndexText,
|
||||
ToggleLspLogs,
|
||||
GetDirectoryEnvironment,
|
||||
|
||||
Push,
|
||||
Fetch,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ settings.workspace = true
|
|||
shellexpand.workspace = true
|
||||
smol.workspace = true
|
||||
sysinfo.workspace = true
|
||||
task.workspace = true
|
||||
util.workspace = true
|
||||
watch.workspace = true
|
||||
worktree.workspace = true
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ pub struct HeadlessProject {
|
|||
pub languages: Arc<LanguageRegistry>,
|
||||
pub extensions: Entity<HeadlessExtensionStore>,
|
||||
pub git_store: Entity<GitStore>,
|
||||
pub environment: Entity<ProjectEnvironment>,
|
||||
// Used mostly to keep alive the toolchain store for RPC handlers.
|
||||
// Local variant is used within LSP store, but that's a separate entity.
|
||||
pub _toolchain_store: Entity<ToolchainStore>,
|
||||
|
|
@ -199,7 +200,7 @@ impl HeadlessProject {
|
|||
let mut agent_server_store = AgentServerStore::local(
|
||||
node_runtime.clone(),
|
||||
fs.clone(),
|
||||
environment,
|
||||
environment.clone(),
|
||||
http_client.clone(),
|
||||
cx,
|
||||
);
|
||||
|
|
@ -255,6 +256,7 @@ impl HeadlessProject {
|
|||
session.add_entity_request_handler(Self::handle_open_new_buffer);
|
||||
session.add_entity_request_handler(Self::handle_find_search_candidates);
|
||||
session.add_entity_request_handler(Self::handle_open_server_settings);
|
||||
session.add_entity_request_handler(Self::handle_get_directory_environment);
|
||||
session.add_entity_message_handler(Self::handle_toggle_lsp_logs);
|
||||
|
||||
session.add_entity_request_handler(BufferStore::handle_update_buffer);
|
||||
|
|
@ -295,6 +297,7 @@ impl HeadlessProject {
|
|||
languages,
|
||||
extensions,
|
||||
git_store,
|
||||
environment,
|
||||
_toolchain_store: toolchain_store,
|
||||
}
|
||||
}
|
||||
|
|
@ -764,6 +767,26 @@ impl HeadlessProject {
|
|||
|
||||
Ok(proto::GetProcessesResponse { processes })
|
||||
}
|
||||
|
||||
async fn handle_get_directory_environment(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::GetDirectoryEnvironment>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::DirectoryEnvironment> {
|
||||
let shell = task::Shell::from_proto(envelope.payload.shell.context("missing shell")?)?;
|
||||
let directory = PathBuf::from(envelope.payload.directory);
|
||||
let environment = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.environment.update(cx, |environment, cx| {
|
||||
environment.get_local_directory_environment(&shell, directory.into(), cx)
|
||||
})
|
||||
})?
|
||||
.await
|
||||
.context("failed to get directory environment")?
|
||||
.into_iter()
|
||||
.collect();
|
||||
Ok(proto::DirectoryEnvironment { environment })
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_to_proto(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ mod task_template;
|
|||
mod vscode_debug_format;
|
||||
mod vscode_format;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use collections::{HashMap, HashSet, hash_map};
|
||||
use gpui::SharedString;
|
||||
use schemars::JsonSchema;
|
||||
|
|
@ -361,6 +362,38 @@ impl Shell {
|
|||
Shell::System => ShellKind::system(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_proto(proto: proto::Shell) -> anyhow::Result<Self> {
|
||||
let shell_type = proto.shell_type.context("invalid shell type")?;
|
||||
let shell = match shell_type {
|
||||
proto::shell::ShellType::System(_) => Self::System,
|
||||
proto::shell::ShellType::Program(program) => Self::Program(program),
|
||||
proto::shell::ShellType::WithArguments(program) => Self::WithArguments {
|
||||
program: program.program,
|
||||
args: program.args,
|
||||
title_override: None,
|
||||
},
|
||||
};
|
||||
Ok(shell)
|
||||
}
|
||||
|
||||
pub fn to_proto(self) -> proto::Shell {
|
||||
let shell_type = match self {
|
||||
Shell::System => proto::shell::ShellType::System(proto::System {}),
|
||||
Shell::Program(program) => proto::shell::ShellType::Program(program),
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: _,
|
||||
} => proto::shell::ShellType::WithArguments(proto::shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
}),
|
||||
};
|
||||
proto::Shell {
|
||||
shell_type: Some(shell_type),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type VsCodeEnvVariable = String;
|
||||
|
|
|
|||
Loading…
Reference in a new issue