Merge branch 'main' of https://github.com/jacobtread/zed into feat-git-commit-list
This commit is contained in:
commit
c79046effc
490 changed files with 21892 additions and 14140 deletions
|
@ -414,6 +414,7 @@ impl GitStore {
|
|||
pub fn init(client: &AnyProtoClient) {
|
||||
client.add_entity_request_handler(Self::handle_get_remotes);
|
||||
client.add_entity_request_handler(Self::handle_get_branches);
|
||||
client.add_entity_request_handler(Self::handle_get_default_branch);
|
||||
client.add_entity_request_handler(Self::handle_change_branch);
|
||||
client.add_entity_request_handler(Self::handle_create_branch);
|
||||
client.add_entity_request_handler(Self::handle_git_init);
|
||||
|
@ -441,6 +442,7 @@ impl GitStore {
|
|||
client.add_entity_request_handler(Self::handle_blame_buffer);
|
||||
client.add_entity_message_handler(Self::handle_update_repository);
|
||||
client.add_entity_message_handler(Self::handle_remove_repository);
|
||||
client.add_entity_request_handler(Self::handle_git_clone);
|
||||
}
|
||||
|
||||
pub fn is_local(&self) -> bool {
|
||||
|
@ -1464,6 +1466,45 @@ impl GitStore {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn git_clone(
|
||||
&self,
|
||||
repo: String,
|
||||
path: impl Into<Arc<std::path::Path>>,
|
||||
cx: &App,
|
||||
) -> Task<Result<()>> {
|
||||
let path = path.into();
|
||||
match &self.state {
|
||||
GitStoreState::Local { fs, .. } => {
|
||||
let fs = fs.clone();
|
||||
cx.background_executor()
|
||||
.spawn(async move { fs.git_clone(&repo, &path).await })
|
||||
}
|
||||
GitStoreState::Ssh {
|
||||
upstream_client,
|
||||
upstream_project_id,
|
||||
..
|
||||
} => {
|
||||
let request = upstream_client.request(proto::GitClone {
|
||||
project_id: upstream_project_id.0,
|
||||
abs_path: path.to_string_lossy().to_string(),
|
||||
remote_repo: repo,
|
||||
});
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let result = request.await?;
|
||||
|
||||
match result.success {
|
||||
true => Ok(()),
|
||||
false => Err(anyhow!("Git Clone failed")),
|
||||
}
|
||||
})
|
||||
}
|
||||
GitStoreState::Remote { .. } => {
|
||||
Task::ready(Err(anyhow!("Git Clone isn't supported for remote users")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_update_repository(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::UpdateRepository>,
|
||||
|
@ -1550,6 +1591,22 @@ impl GitStore {
|
|||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_git_clone(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::GitClone>,
|
||||
cx: AsyncApp,
|
||||
) -> Result<proto::GitCloneResponse> {
|
||||
let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
|
||||
let repo_name = envelope.payload.remote_repo;
|
||||
let result = cx
|
||||
.update(|cx| this.read(cx).git_clone(repo_name, path, cx))?
|
||||
.await;
|
||||
|
||||
Ok(proto::GitCloneResponse {
|
||||
success: result.is_ok(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_fetch(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Fetch>,
|
||||
|
@ -1838,6 +1895,23 @@ impl GitStore {
|
|||
.collect::<Vec<_>>(),
|
||||
})
|
||||
}
|
||||
async fn handle_get_default_branch(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::GetDefaultBranch>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::GetDefaultBranchResponse> {
|
||||
let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
|
||||
let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
|
||||
|
||||
let branch = repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.default_branch()
|
||||
})?
|
||||
.await??
|
||||
.map(Into::into);
|
||||
|
||||
Ok(proto::GetDefaultBranchResponse { branch })
|
||||
}
|
||||
async fn handle_create_branch(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::GitCreateBranch>,
|
||||
|
@ -2275,7 +2349,7 @@ impl GitStore {
|
|||
return None;
|
||||
};
|
||||
|
||||
let mut paths = vec![];
|
||||
let mut paths = Vec::new();
|
||||
// All paths prefixed by a given repo will constitute a continuous range.
|
||||
while let Some(path) = entries.get(ix)
|
||||
&& let Some(repo_path) =
|
||||
|
@ -2284,7 +2358,11 @@ impl GitStore {
|
|||
paths.push((repo_path, ix));
|
||||
ix += 1;
|
||||
}
|
||||
Some((repo, paths))
|
||||
if paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((repo, paths))
|
||||
}
|
||||
});
|
||||
tasks.push_back(task);
|
||||
}
|
||||
|
@ -4299,7 +4377,8 @@ impl Repository {
|
|||
bail!("not a local repository")
|
||||
};
|
||||
let (snapshot, events) = this
|
||||
.read_with(&mut cx, |this, _| {
|
||||
.update(&mut cx, |this, _| {
|
||||
this.paths_needing_status_update.clear();
|
||||
compute_snapshot(
|
||||
this.id,
|
||||
this.work_directory_abs_path.clone(),
|
||||
|
@ -4529,6 +4608,9 @@ impl Repository {
|
|||
};
|
||||
|
||||
let paths = changed_paths.iter().cloned().collect::<Vec<_>>();
|
||||
if paths.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let statuses = backend.status(&paths).await?;
|
||||
|
||||
let changed_path_statuses = cx
|
||||
|
|
|
@ -390,13 +390,17 @@ impl LocalLspStore {
|
|||
delegate.update_status(
|
||||
adapter.name(),
|
||||
BinaryStatus::Failed {
|
||||
error: format!("{err}\n-- stderr--\n{log}"),
|
||||
error: if log.is_empty() {
|
||||
format!("{err:#}")
|
||||
} else {
|
||||
format!("{err:#}\n-- stderr --\n{log}")
|
||||
},
|
||||
},
|
||||
);
|
||||
let message =
|
||||
format!("Failed to start language server {server_name:?}: {err:#?}");
|
||||
log::error!("{message}");
|
||||
log::error!("server stderr: {log}");
|
||||
log::error!("Failed to start language server {server_name:?}: {err:?}");
|
||||
if !log.is_empty() {
|
||||
log::error!("server stderr: {log}");
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -638,139 +642,27 @@ impl LocalLspStore {
|
|||
|
||||
language_server
|
||||
.on_request::<lsp::request::RegisterCapability, _, _>({
|
||||
let this = this.clone();
|
||||
let lsp_store = this.clone();
|
||||
move |params, cx| {
|
||||
let lsp_store = this.clone();
|
||||
let lsp_store = lsp_store.clone();
|
||||
let mut cx = cx.clone();
|
||||
async move {
|
||||
for reg in params.registrations {
|
||||
match reg.method.as_str() {
|
||||
"workspace/didChangeWatchedFiles" => {
|
||||
if let Some(options) = reg.register_options {
|
||||
let options = serde_json::from_value(options)?;
|
||||
lsp_store.update(&mut cx, |this, cx| {
|
||||
this.as_local_mut()?.on_lsp_did_change_watched_files(
|
||||
server_id, ®.id, options, cx,
|
||||
lsp_store
|
||||
.update(&mut cx, |lsp_store, cx| {
|
||||
if lsp_store.as_local().is_some() {
|
||||
match lsp_store
|
||||
.register_server_capabilities(server_id, params, cx)
|
||||
{
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to register server capabilities: {e:#}"
|
||||
);
|
||||
Some(())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
"textDocument/rangeFormatting" => {
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
if let Some(server) =
|
||||
lsp_store.language_server_for_id(server_id)
|
||||
{
|
||||
let options = reg
|
||||
.register_options
|
||||
.map(|options| {
|
||||
serde_json::from_value::<
|
||||
lsp::DocumentRangeFormattingOptions,
|
||||
>(
|
||||
options
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let provider = match options {
|
||||
None => OneOf::Left(true),
|
||||
Some(options) => OneOf::Right(options),
|
||||
};
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_range_formatting_provider =
|
||||
Some(provider);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
};
|
||||
}
|
||||
"textDocument/onTypeFormatting" => {
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
if let Some(server) =
|
||||
lsp_store.language_server_for_id(server_id)
|
||||
{
|
||||
let options = reg
|
||||
.register_options
|
||||
.map(|options| {
|
||||
serde_json::from_value::<
|
||||
lsp::DocumentOnTypeFormattingOptions,
|
||||
>(
|
||||
options
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
if let Some(options) = options {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities
|
||||
.document_on_type_formatting_provider =
|
||||
Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
}
|
||||
"textDocument/formatting" => {
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
if let Some(server) =
|
||||
lsp_store.language_server_for_id(server_id)
|
||||
{
|
||||
let options = reg
|
||||
.register_options
|
||||
.map(|options| {
|
||||
serde_json::from_value::<
|
||||
lsp::DocumentFormattingOptions,
|
||||
>(
|
||||
options
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let provider = match options {
|
||||
None => OneOf::Left(true),
|
||||
Some(options) => OneOf::Right(options),
|
||||
};
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_formatting_provider =
|
||||
Some(provider);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
}
|
||||
"workspace/didChangeConfiguration" => {
|
||||
// Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings.
|
||||
}
|
||||
"textDocument/rename" => {
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
if let Some(server) =
|
||||
lsp_store.language_server_for_id(server_id)
|
||||
{
|
||||
let options = reg
|
||||
.register_options
|
||||
.map(|options| {
|
||||
serde_json::from_value::<lsp::RenameOptions>(
|
||||
options,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let options = match options {
|
||||
None => OneOf::Left(true),
|
||||
Some(options) => OneOf::Right(options),
|
||||
};
|
||||
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.rename_provider = Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
}
|
||||
_ => log::warn!("unhandled capability registration: {reg:?}"),
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -779,79 +671,27 @@ impl LocalLspStore {
|
|||
|
||||
language_server
|
||||
.on_request::<lsp::request::UnregisterCapability, _, _>({
|
||||
let this = this.clone();
|
||||
let lsp_store = this.clone();
|
||||
move |params, cx| {
|
||||
let lsp_store = this.clone();
|
||||
let lsp_store = lsp_store.clone();
|
||||
let mut cx = cx.clone();
|
||||
async move {
|
||||
for unreg in params.unregisterations.iter() {
|
||||
match unreg.method.as_str() {
|
||||
"workspace/didChangeWatchedFiles" => {
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.as_local_mut()?
|
||||
.on_lsp_unregister_did_change_watched_files(
|
||||
server_id, &unreg.id, cx,
|
||||
lsp_store
|
||||
.update(&mut cx, |lsp_store, cx| {
|
||||
if lsp_store.as_local().is_some() {
|
||||
match lsp_store
|
||||
.unregister_server_capabilities(server_id, params, cx)
|
||||
{
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to unregister server capabilities: {e:#}"
|
||||
);
|
||||
Some(())
|
||||
})?;
|
||||
}
|
||||
"workspace/didChangeConfiguration" => {
|
||||
// Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings.
|
||||
}
|
||||
"textDocument/rename" => {
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
if let Some(server) =
|
||||
lsp_store.language_server_for_id(server_id)
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.rename_provider = None
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
}
|
||||
"textDocument/rangeFormatting" => {
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
if let Some(server) =
|
||||
lsp_store.language_server_for_id(server_id)
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_range_formatting_provider =
|
||||
None
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
"textDocument/onTypeFormatting" => {
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
if let Some(server) =
|
||||
lsp_store.language_server_for_id(server_id)
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_on_type_formatting_provider =
|
||||
None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
"textDocument/formatting" => {
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
if let Some(server) =
|
||||
lsp_store.language_server_for_id(server_id)
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_formatting_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
_ => log::warn!("unhandled capability unregistration: {unreg:?}"),
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -3519,6 +3359,16 @@ impl LocalLspStore {
|
|||
|
||||
Ok(workspace_config)
|
||||
}
|
||||
|
||||
fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
|
||||
if let Some(LanguageServerState::Running { server, .. }) = self.language_servers.get(&id) {
|
||||
Some(server.clone())
|
||||
} else if let Some((_, server)) = self.supplementary_language_servers.get(&id) {
|
||||
Some(Arc::clone(server))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_server_capabilities_updated(server: &LanguageServer, cx: &mut Context<LspStore>) {
|
||||
|
@ -9434,16 +9284,7 @@ impl LspStore {
|
|||
}
|
||||
|
||||
pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
|
||||
let local_lsp_store = self.as_local()?;
|
||||
if let Some(LanguageServerState::Running { server, .. }) =
|
||||
local_lsp_store.language_servers.get(&id)
|
||||
{
|
||||
Some(server.clone())
|
||||
} else if let Some((_, server)) = local_lsp_store.supplementary_language_servers.get(&id) {
|
||||
Some(Arc::clone(server))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.as_local()?.language_server_for_id(id)
|
||||
}
|
||||
|
||||
fn on_lsp_progress(
|
||||
|
@ -11808,6 +11649,375 @@ impl LspStore {
|
|||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
fn register_server_capabilities(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
params: lsp::RegistrationParams,
|
||||
cx: &mut Context<Self>,
|
||||
) -> anyhow::Result<()> {
|
||||
let server = self
|
||||
.language_server_for_id(server_id)
|
||||
.with_context(|| format!("no server {server_id} found"))?;
|
||||
for reg in params.registrations {
|
||||
match reg.method.as_str() {
|
||||
"workspace/didChangeWatchedFiles" => {
|
||||
if let Some(options) = reg.register_options {
|
||||
let notify = if let Some(local_lsp_store) = self.as_local_mut() {
|
||||
let caps = serde_json::from_value(options)?;
|
||||
local_lsp_store
|
||||
.on_lsp_did_change_watched_files(server_id, ®.id, caps, cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if notify {
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
"workspace/didChangeConfiguration" => {
|
||||
// Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings.
|
||||
}
|
||||
"workspace/symbol" => {
|
||||
if let Some(options) = parse_register_capabilities(reg)? {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.workspace_symbol_provider = Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"workspace/fileOperations" => {
|
||||
if let Some(options) = reg.register_options {
|
||||
let caps = serde_json::from_value(options)?;
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities
|
||||
.workspace
|
||||
.get_or_insert_default()
|
||||
.file_operations = Some(caps);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"workspace/executeCommand" => {
|
||||
if let Some(options) = reg.register_options {
|
||||
let options = serde_json::from_value(options)?;
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.execute_command_provider = Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/rangeFormatting" => {
|
||||
if let Some(options) = parse_register_capabilities(reg)? {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_range_formatting_provider = Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/onTypeFormatting" => {
|
||||
if let Some(options) = reg
|
||||
.register_options
|
||||
.map(serde_json::from_value)
|
||||
.transpose()?
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_on_type_formatting_provider = Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/formatting" => {
|
||||
if let Some(options) = parse_register_capabilities(reg)? {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_formatting_provider = Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/rename" => {
|
||||
if let Some(options) = parse_register_capabilities(reg)? {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.rename_provider = Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/inlayHint" => {
|
||||
if let Some(options) = parse_register_capabilities(reg)? {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.inlay_hint_provider = Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/documentSymbol" => {
|
||||
if let Some(options) = parse_register_capabilities(reg)? {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_symbol_provider = Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/codeAction" => {
|
||||
if let Some(options) = reg
|
||||
.register_options
|
||||
.map(serde_json::from_value)
|
||||
.transpose()?
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.code_action_provider =
|
||||
Some(lsp::CodeActionProviderCapability::Options(options));
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/definition" => {
|
||||
if let Some(options) = parse_register_capabilities(reg)? {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.definition_provider = Some(options);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/completion" => {
|
||||
if let Some(caps) = reg
|
||||
.register_options
|
||||
.map(serde_json::from_value)
|
||||
.transpose()?
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.completion_provider = Some(caps);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/hover" => {
|
||||
if let Some(caps) = reg
|
||||
.register_options
|
||||
.map(serde_json::from_value)
|
||||
.transpose()?
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.hover_provider = Some(caps);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/signatureHelp" => {
|
||||
if let Some(caps) = reg
|
||||
.register_options
|
||||
.map(serde_json::from_value)
|
||||
.transpose()?
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.signature_help_provider = Some(caps);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/didChange" => {
|
||||
if let Some(sync_kind) = reg
|
||||
.register_options
|
||||
.and_then(|opts| opts.get("syncKind").cloned())
|
||||
.map(serde_json::from_value::<lsp::TextDocumentSyncKind>)
|
||||
.transpose()?
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.text_document_sync =
|
||||
Some(lsp::TextDocumentSyncCapability::Kind(sync_kind));
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/codeLens" => {
|
||||
if let Some(caps) = reg
|
||||
.register_options
|
||||
.map(serde_json::from_value)
|
||||
.transpose()?
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.code_lens_provider = Some(caps);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/diagnostic" => {
|
||||
if let Some(caps) = reg
|
||||
.register_options
|
||||
.map(serde_json::from_value)
|
||||
.transpose()?
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.diagnostic_provider = Some(caps);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"textDocument/colorProvider" => {
|
||||
if let Some(caps) = reg
|
||||
.register_options
|
||||
.map(serde_json::from_value)
|
||||
.transpose()?
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.color_provider = Some(caps);
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
_ => log::warn!("unhandled capability registration: {reg:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unregister_server_capabilities(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
params: lsp::UnregistrationParams,
|
||||
cx: &mut Context<Self>,
|
||||
) -> anyhow::Result<()> {
|
||||
let server = self
|
||||
.language_server_for_id(server_id)
|
||||
.with_context(|| format!("no server {server_id} found"))?;
|
||||
for unreg in params.unregisterations.iter() {
|
||||
match unreg.method.as_str() {
|
||||
"workspace/didChangeWatchedFiles" => {
|
||||
let notify = if let Some(local_lsp_store) = self.as_local_mut() {
|
||||
local_lsp_store
|
||||
.on_lsp_unregister_did_change_watched_files(server_id, &unreg.id, cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if notify {
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
}
|
||||
"workspace/didChangeConfiguration" => {
|
||||
// Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings.
|
||||
}
|
||||
"workspace/symbol" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.workspace_symbol_provider = None
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"workspace/fileOperations" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities
|
||||
.workspace
|
||||
.get_or_insert_with(|| lsp::WorkspaceServerCapabilities {
|
||||
workspace_folders: None,
|
||||
file_operations: None,
|
||||
})
|
||||
.file_operations = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"workspace/executeCommand" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.execute_command_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/rangeFormatting" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_range_formatting_provider = None
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/onTypeFormatting" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_on_type_formatting_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/formatting" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_formatting_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/rename" => {
|
||||
server.update_capabilities(|capabilities| capabilities.rename_provider = None);
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/codeAction" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.code_action_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/definition" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.definition_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/completion" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.completion_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/hover" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.hover_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/signatureHelp" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.signature_help_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/didChange" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.text_document_sync = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/codeLens" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.code_lens_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/diagnostic" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.diagnostic_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/colorProvider" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.color_provider = None;
|
||||
});
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
_ => log::warn!("unhandled capability unregistration: {unreg:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Registration with empty capabilities should be ignored.
|
||||
// https://github.com/microsoft/vscode-languageserver-node/blob/d90a87f9557a0df9142cfb33e251cfa6fe27d970/client/src/common/formatting.ts#L67-L70
|
||||
fn parse_register_capabilities<T: serde::de::DeserializeOwned>(
|
||||
reg: lsp::Registration,
|
||||
) -> anyhow::Result<Option<OneOf<bool, T>>> {
|
||||
Ok(reg
|
||||
.register_options
|
||||
.map(|options| serde_json::from_value::<T>(options))
|
||||
.transpose()?
|
||||
.map(OneOf::Right))
|
||||
}
|
||||
|
||||
fn subscribe_to_binary_statuses(
|
||||
|
|
|
@ -962,14 +962,19 @@ impl settings::Settings for DisableAiSettings {
|
|||
type FileContent = Option<bool>;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
Ok(Self {
|
||||
disable_ai: sources
|
||||
.user
|
||||
.or(sources.server)
|
||||
.copied()
|
||||
.flatten()
|
||||
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
|
||||
})
|
||||
// For security reasons, settings can only make AI restrictions MORE strict, not less.
|
||||
// (For example, if someone is working on a project that contractually
|
||||
// requires no AI use, that should override the user's setting which
|
||||
// permits AI use.)
|
||||
// This also prevents an attacker from using project or server settings to enable AI when it should be disabled.
|
||||
let disable_ai = sources
|
||||
.project
|
||||
.iter()
|
||||
.chain(sources.user.iter())
|
||||
.chain(sources.server.iter())
|
||||
.any(|disabled| **disabled == Some(true));
|
||||
|
||||
Ok(Self { disable_ai })
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
|
@ -5508,3 +5513,153 @@ fn provide_inline_values(
|
|||
|
||||
variables
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod disable_ai_settings_tests {
|
||||
use super::*;
|
||||
use gpui::TestAppContext;
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_disable_ai_settings_security(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
// Test 1: Default is false (AI enabled)
|
||||
let sources = SettingsSources {
|
||||
default: &Some(false),
|
||||
global: None,
|
||||
extensions: None,
|
||||
user: None,
|
||||
release_channel: None,
|
||||
operating_system: None,
|
||||
profile: None,
|
||||
server: None,
|
||||
project: &[],
|
||||
};
|
||||
let settings = DisableAiSettings::load(sources, cx).unwrap();
|
||||
assert_eq!(settings.disable_ai, false, "Default should allow AI");
|
||||
|
||||
// Test 2: Global true, local false -> still disabled (local cannot re-enable)
|
||||
let global_true = Some(true);
|
||||
let local_false = Some(false);
|
||||
let sources = SettingsSources {
|
||||
default: &Some(false),
|
||||
global: None,
|
||||
extensions: None,
|
||||
user: Some(&global_true),
|
||||
release_channel: None,
|
||||
operating_system: None,
|
||||
profile: None,
|
||||
server: None,
|
||||
project: &[&local_false],
|
||||
};
|
||||
let settings = DisableAiSettings::load(sources, cx).unwrap();
|
||||
assert_eq!(
|
||||
settings.disable_ai, true,
|
||||
"Local false cannot override global true"
|
||||
);
|
||||
|
||||
// Test 3: Global false, local true -> disabled (local can make more restrictive)
|
||||
let global_false = Some(false);
|
||||
let local_true = Some(true);
|
||||
let sources = SettingsSources {
|
||||
default: &Some(false),
|
||||
global: None,
|
||||
extensions: None,
|
||||
user: Some(&global_false),
|
||||
release_channel: None,
|
||||
operating_system: None,
|
||||
profile: None,
|
||||
server: None,
|
||||
project: &[&local_true],
|
||||
};
|
||||
let settings = DisableAiSettings::load(sources, cx).unwrap();
|
||||
assert_eq!(
|
||||
settings.disable_ai, true,
|
||||
"Local true can override global false"
|
||||
);
|
||||
|
||||
// Test 4: Server can only make more restrictive (set to true)
|
||||
let user_false = Some(false);
|
||||
let server_true = Some(true);
|
||||
let sources = SettingsSources {
|
||||
default: &Some(false),
|
||||
global: None,
|
||||
extensions: None,
|
||||
user: Some(&user_false),
|
||||
release_channel: None,
|
||||
operating_system: None,
|
||||
profile: None,
|
||||
server: Some(&server_true),
|
||||
project: &[],
|
||||
};
|
||||
let settings = DisableAiSettings::load(sources, cx).unwrap();
|
||||
assert_eq!(
|
||||
settings.disable_ai, true,
|
||||
"Server can set to true even if user is false"
|
||||
);
|
||||
|
||||
// Test 5: Server false cannot override user true
|
||||
let user_true = Some(true);
|
||||
let server_false = Some(false);
|
||||
let sources = SettingsSources {
|
||||
default: &Some(false),
|
||||
global: None,
|
||||
extensions: None,
|
||||
user: Some(&user_true),
|
||||
release_channel: None,
|
||||
operating_system: None,
|
||||
profile: None,
|
||||
server: Some(&server_false),
|
||||
project: &[],
|
||||
};
|
||||
let settings = DisableAiSettings::load(sources, cx).unwrap();
|
||||
assert_eq!(
|
||||
settings.disable_ai, true,
|
||||
"Server false cannot override user true"
|
||||
);
|
||||
|
||||
// Test 6: Multiple local settings, any true disables AI
|
||||
let global_false = Some(false);
|
||||
let local_false3 = Some(false);
|
||||
let local_true2 = Some(true);
|
||||
let local_false4 = Some(false);
|
||||
let sources = SettingsSources {
|
||||
default: &Some(false),
|
||||
global: None,
|
||||
extensions: None,
|
||||
user: Some(&global_false),
|
||||
release_channel: None,
|
||||
operating_system: None,
|
||||
profile: None,
|
||||
server: None,
|
||||
project: &[&local_false3, &local_true2, &local_false4],
|
||||
};
|
||||
let settings = DisableAiSettings::load(sources, cx).unwrap();
|
||||
assert_eq!(
|
||||
settings.disable_ai, true,
|
||||
"Any local true should disable AI"
|
||||
);
|
||||
|
||||
// Test 7: All three sources can independently disable AI
|
||||
let user_false2 = Some(false);
|
||||
let server_false2 = Some(false);
|
||||
let local_true3 = Some(true);
|
||||
let sources = SettingsSources {
|
||||
default: &Some(false),
|
||||
global: None,
|
||||
extensions: None,
|
||||
user: Some(&user_false2),
|
||||
release_channel: None,
|
||||
operating_system: None,
|
||||
profile: None,
|
||||
server: Some(&server_false2),
|
||||
project: &[&local_true3],
|
||||
};
|
||||
let settings = DisableAiSettings::load(sources, cx).unwrap();
|
||||
assert_eq!(
|
||||
settings.disable_ai, true,
|
||||
"Local can disable even if user and server are false"
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -256,7 +256,7 @@ impl Project {
|
|||
|
||||
let local_path = if is_ssh_terminal { None } else { path.clone() };
|
||||
|
||||
let mut python_venv_activate_command = None;
|
||||
let mut python_venv_activate_command = Task::ready(None);
|
||||
|
||||
let (spawn_task, shell) = match kind {
|
||||
TerminalKind::Shell(_) => {
|
||||
|
@ -265,6 +265,7 @@ impl Project {
|
|||
python_venv_directory,
|
||||
&settings.detect_venv,
|
||||
&settings.shell,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -419,9 +420,12 @@ impl Project {
|
|||
})
|
||||
.detach();
|
||||
|
||||
if let Some(activate_command) = python_venv_activate_command {
|
||||
this.activate_python_virtual_environment(activate_command, &terminal_handle, cx);
|
||||
}
|
||||
this.activate_python_virtual_environment(
|
||||
python_venv_activate_command,
|
||||
&terminal_handle,
|
||||
cx,
|
||||
);
|
||||
|
||||
terminal_handle
|
||||
})
|
||||
}
|
||||
|
@ -539,12 +543,15 @@ impl Project {
|
|||
venv_base_directory: &Path,
|
||||
venv_settings: &VenvSettings,
|
||||
shell: &Shell,
|
||||
) -> Option<String> {
|
||||
let venv_settings = venv_settings.as_option()?;
|
||||
cx: &mut App,
|
||||
) -> Task<Option<String>> {
|
||||
let Some(venv_settings) = venv_settings.as_option() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let activate_keyword = match venv_settings.activate_script {
|
||||
terminal_settings::ActivateScript::Default => match std::env::consts::OS {
|
||||
"windows" => ".",
|
||||
_ => "source",
|
||||
_ => ".",
|
||||
},
|
||||
terminal_settings::ActivateScript::Nushell => "overlay use",
|
||||
terminal_settings::ActivateScript::PowerShell => ".",
|
||||
|
@ -589,30 +596,44 @@ impl Project {
|
|||
.join(activate_script_name)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let quoted = shlex::try_quote(&path).ok()?;
|
||||
smol::block_on(self.fs.metadata(path.as_ref()))
|
||||
.ok()
|
||||
.flatten()?;
|
||||
|
||||
Some(format!(
|
||||
"{} {} ; clear{}",
|
||||
activate_keyword, quoted, line_ending
|
||||
))
|
||||
let is_valid_path = self.resolve_abs_path(path.as_ref(), cx);
|
||||
cx.background_spawn(async move {
|
||||
let quoted = shlex::try_quote(&path).ok()?;
|
||||
if is_valid_path.await.is_some_and(|meta| meta.is_file()) {
|
||||
Some(format!(
|
||||
"{} {} ; clear{}",
|
||||
activate_keyword, quoted, line_ending
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Some(format!(
|
||||
Task::ready(Some(format!(
|
||||
"{activate_keyword} {activate_script_name} {name}; clear{line_ending}",
|
||||
name = venv_settings.venv_name
|
||||
))
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_python_virtual_environment(
|
||||
&self,
|
||||
command: String,
|
||||
command: Task<Option<String>>,
|
||||
terminal_handle: &Entity<Terminal>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
terminal_handle.update(cx, |terminal, _| terminal.input(command.into_bytes()));
|
||||
terminal_handle.update(cx, |_, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
if let Some(command) = command.await {
|
||||
this.update(cx, |this, _| {
|
||||
this.input(command.into_bytes());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn local_terminal_handles(&self) -> &Vec<WeakEntity<terminal::Terminal>> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue