Compare commits
7 Commits
failing-pr
...
static-rel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6946ad4e8 | ||
|
|
c9972c2972 | ||
|
|
afdc53fdb7 | ||
|
|
d2e5947cf3 | ||
|
|
b02b130b7c | ||
|
|
41ac6a8764 | ||
|
|
963204c99d |
5
.github/actions/run_tests/action.yml
vendored
5
.github/actions/run_tests/action.yml
vendored
@@ -18,9 +18,6 @@ runs:
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
# FIXME
|
||||
- name: Run tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
env:
|
||||
RUST_LOG: project=debug,project_panel=debug
|
||||
run: cargo nextest run -p project_panel --no-fail-fast --failure-output immediate-final --no-capture
|
||||
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -808,7 +808,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"log",
|
||||
"net",
|
||||
"proto",
|
||||
"smol",
|
||||
"tempfile",
|
||||
"windows 0.61.1",
|
||||
@@ -12078,7 +12077,6 @@ dependencies = [
|
||||
"criterion",
|
||||
"db",
|
||||
"editor",
|
||||
"env_logger 0.11.8",
|
||||
"file_icons",
|
||||
"git",
|
||||
"git_ui",
|
||||
|
||||
@@ -38,7 +38,7 @@ use std::{
|
||||
cell::{Ref, RefCell},
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
sync::{Arc, LazyLock, Mutex},
|
||||
};
|
||||
use util::{ResultExt as _, rel_path::RelPath};
|
||||
|
||||
@@ -74,17 +74,19 @@ impl Column for DataType {
|
||||
}
|
||||
}
|
||||
|
||||
const RULES_FILE_NAMES: [&str; 9] = [
|
||||
".rules",
|
||||
".cursorrules",
|
||||
".windsurfrules",
|
||||
".clinerules",
|
||||
".github/copilot-instructions.md",
|
||||
"CLAUDE.md",
|
||||
"AGENT.md",
|
||||
"AGENTS.md",
|
||||
"GEMINI.md",
|
||||
];
|
||||
static RULES_FILE_NAMES: LazyLock<[&RelPath; 9]> = LazyLock::new(|| {
|
||||
[
|
||||
RelPath::unix(".rules").unwrap(),
|
||||
RelPath::unix(".cursorrules").unwrap(),
|
||||
RelPath::unix(".windsurfrules").unwrap(),
|
||||
RelPath::unix(".clinerules").unwrap(),
|
||||
RelPath::unix(".github/copilot-instructions.md").unwrap(),
|
||||
RelPath::unix("CLAUDE.md").unwrap(),
|
||||
RelPath::unix("AGENT.md").unwrap(),
|
||||
RelPath::unix("AGENTS.md").unwrap(),
|
||||
RelPath::unix("GEMINI.md").unwrap(),
|
||||
]
|
||||
});
|
||||
|
||||
pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
ThreadsDatabase::init(fs, cx);
|
||||
@@ -232,11 +234,10 @@ impl ThreadStore {
|
||||
self.enqueue_system_prompt_reload();
|
||||
}
|
||||
project::Event::WorktreeUpdatedEntries(_, items) => {
|
||||
if items.iter().any(|(path, _, _)| {
|
||||
RULES_FILE_NAMES
|
||||
.iter()
|
||||
.any(|name| path.as_ref() == RelPath::unix(name).unwrap())
|
||||
}) {
|
||||
if items
|
||||
.iter()
|
||||
.any(|(path, _, _)| RULES_FILE_NAMES.iter().any(|name| path.as_ref() == *name))
|
||||
{
|
||||
self.enqueue_system_prompt_reload();
|
||||
}
|
||||
}
|
||||
@@ -368,7 +369,7 @@ impl ThreadStore {
|
||||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
worktree
|
||||
.entry_for_path(RelPath::unix(name).unwrap())
|
||||
.entry_for_path(name)
|
||||
.filter(|entry| entry.is_file())
|
||||
.map(|entry| entry.path.clone())
|
||||
})
|
||||
|
||||
@@ -25,21 +25,23 @@ use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use util::ResultExt;
|
||||
use util::rel_path::RelPath;
|
||||
|
||||
const RULES_FILE_NAMES: [&str; 9] = [
|
||||
".rules",
|
||||
".cursorrules",
|
||||
".windsurfrules",
|
||||
".clinerules",
|
||||
".github/copilot-instructions.md",
|
||||
"CLAUDE.md",
|
||||
"AGENT.md",
|
||||
"AGENTS.md",
|
||||
"GEMINI.md",
|
||||
];
|
||||
static RULES_FILE_NAMES: LazyLock<[&RelPath; 9]> = LazyLock::new(|| {
|
||||
[
|
||||
RelPath::unix(".rules").unwrap(),
|
||||
RelPath::unix(".cursorrules").unwrap(),
|
||||
RelPath::unix(".windsurfrules").unwrap(),
|
||||
RelPath::unix(".clinerules").unwrap(),
|
||||
RelPath::unix(".github/copilot-instructions.md").unwrap(),
|
||||
RelPath::unix("CLAUDE.md").unwrap(),
|
||||
RelPath::unix("AGENT.md").unwrap(),
|
||||
RelPath::unix("AGENTS.md").unwrap(),
|
||||
RelPath::unix("GEMINI.md").unwrap(),
|
||||
]
|
||||
});
|
||||
|
||||
pub struct RulesLoadingError {
|
||||
pub message: SharedString,
|
||||
@@ -475,7 +477,7 @@ impl NativeAgent {
|
||||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
worktree
|
||||
.entry_for_path(RelPath::unix(name).unwrap())
|
||||
.entry_for_path(name)
|
||||
.filter(|entry| entry.is_file())
|
||||
.map(|entry| entry.path.clone())
|
||||
})
|
||||
@@ -556,11 +558,10 @@ impl NativeAgent {
|
||||
self.project_context_needs_refresh.send(()).ok();
|
||||
}
|
||||
project::Event::WorktreeUpdatedEntries(_, items) => {
|
||||
if items.iter().any(|(path, _, _)| {
|
||||
RULES_FILE_NAMES
|
||||
.iter()
|
||||
.any(|name| path.as_ref() == RelPath::unix(name).unwrap())
|
||||
}) {
|
||||
if items
|
||||
.iter()
|
||||
.any(|(path, _, _)| RULES_FILE_NAMES.iter().any(|name| path.as_ref() == *name))
|
||||
{
|
||||
self.project_context_needs_refresh.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
net.workspace = true
|
||||
proto.workspace = true
|
||||
smol.workspace = true
|
||||
log.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
mod encrypted_password;
|
||||
|
||||
pub use encrypted_password::{EncryptedPassword, ProcessExt};
|
||||
use util::paths::PathExt;
|
||||
pub use encrypted_password::{EncryptedPassword, IKnowWhatIAmDoingAndIHaveReadTheDocs};
|
||||
|
||||
use net::async_net::UnixListener;
|
||||
use smol::lock::Mutex;
|
||||
use util::fs::make_file_executable;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::ops::ControlFlow;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::{ffi::OsStr, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
@@ -14,9 +20,7 @@ use futures::{
|
||||
};
|
||||
use gpui::{AsyncApp, BackgroundExecutor, Task};
|
||||
use smol::fs;
|
||||
use util::{ResultExt as _, debug_panic};
|
||||
|
||||
use crate::encrypted_password::decrypt;
|
||||
use util::{ResultExt as _, debug_panic, maybe, paths::PathExt};
|
||||
|
||||
/// Path to the program used for askpass
|
||||
///
|
||||
@@ -32,6 +36,7 @@ pub enum AskPassResult {
|
||||
|
||||
pub struct AskPassDelegate {
|
||||
tx: mpsc::UnboundedSender<(String, oneshot::Sender<EncryptedPassword>)>,
|
||||
executor: BackgroundExecutor,
|
||||
_task: Task<()>,
|
||||
}
|
||||
|
||||
@@ -49,24 +54,27 @@ impl AskPassDelegate {
|
||||
password_prompt(prompt, channel, cx);
|
||||
}
|
||||
});
|
||||
Self { tx, _task: task }
|
||||
Self {
|
||||
tx,
|
||||
_task: task,
|
||||
executor: cx.background_executor().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ask_password(&mut self, prompt: String) -> Option<EncryptedPassword> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.tx.send((prompt, tx)).await.ok()?;
|
||||
rx.await.ok()
|
||||
pub fn ask_password(&mut self, prompt: String) -> Task<Option<EncryptedPassword>> {
|
||||
let mut this_tx = self.tx.clone();
|
||||
self.executor.spawn(async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
this_tx.send((prompt, tx)).await.ok()?;
|
||||
rx.await.ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AskPassSession {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
script_path: std::path::PathBuf,
|
||||
#[cfg(target_os = "windows")]
|
||||
askpass_helper: String,
|
||||
#[cfg(target_os = "windows")]
|
||||
secret: std::sync::Arc<OnceLock<EncryptedPassword>>,
|
||||
_askpass_task: Task<()>,
|
||||
askpass_task: PasswordProxy,
|
||||
askpass_opened_rx: Option<oneshot::Receiver<()>>,
|
||||
askpass_kill_master_rx: Option<oneshot::Receiver<()>>,
|
||||
}
|
||||
@@ -81,103 +89,57 @@ impl AskPassSession {
|
||||
/// You must retain this session until the master process exits.
|
||||
#[must_use]
|
||||
pub async fn new(executor: &BackgroundExecutor, mut delegate: AskPassDelegate) -> Result<Self> {
|
||||
use net::async_net::UnixListener;
|
||||
use util::fs::make_file_executable;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let secret = std::sync::Arc::new(OnceLock::new());
|
||||
let temp_dir = tempfile::Builder::new().prefix("zed-askpass").tempdir()?;
|
||||
let askpass_socket = temp_dir.path().join("askpass.sock");
|
||||
let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME);
|
||||
let (askpass_opened_tx, askpass_opened_rx) = oneshot::channel::<()>();
|
||||
let listener = UnixListener::bind(&askpass_socket).context("creating askpass socket")?;
|
||||
|
||||
let current_exec =
|
||||
std::env::current_exe().context("Failed to determine current zed executable path.")?;
|
||||
|
||||
let askpass_program = ASKPASS_PROGRAM
|
||||
.get_or_init(|| current_exec)
|
||||
.try_shell_safe()
|
||||
.context("Failed to shell-escape Askpass program path.")?
|
||||
.to_string();
|
||||
let askpass_opened_tx = Arc::new(Mutex::new(Some(askpass_opened_tx)));
|
||||
|
||||
let (askpass_kill_master_tx, askpass_kill_master_rx) = oneshot::channel::<()>();
|
||||
let mut kill_tx = Some(askpass_kill_master_tx);
|
||||
let kill_tx = Arc::new(Mutex::new(Some(askpass_kill_master_tx)));
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_secret = secret.clone();
|
||||
let askpass_task = executor.spawn(async move {
|
||||
let mut askpass_opened_tx = Some(askpass_opened_tx);
|
||||
let get_password = {
|
||||
let executor = executor.clone();
|
||||
|
||||
while let Ok((mut stream, _)) = listener.accept().await {
|
||||
if let Some(askpass_opened_tx) = askpass_opened_tx.take() {
|
||||
askpass_opened_tx.send(()).ok();
|
||||
}
|
||||
let mut buffer = Vec::new();
|
||||
let mut reader = BufReader::new(&mut stream);
|
||||
if reader.read_until(b'\0', &mut buffer).await.is_err() {
|
||||
buffer.clear();
|
||||
}
|
||||
let prompt = String::from_utf8_lossy(&buffer);
|
||||
if let Some(password) = delegate.ask_password(prompt.into_owned()).await {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
askpass_secret.get_or_init(|| password.clone());
|
||||
move |prompt| {
|
||||
let prompt = delegate.ask_password(prompt);
|
||||
let kill_tx = kill_tx.clone();
|
||||
let askpass_opened_tx = askpass_opened_tx.clone();
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_secret = askpass_secret.clone();
|
||||
executor.spawn(async move {
|
||||
if let Some(askpass_opened_tx) = askpass_opened_tx.lock().await.take() {
|
||||
askpass_opened_tx.send(()).ok();
|
||||
}
|
||||
if let Ok(decrypted) = decrypt(password) {
|
||||
stream.write_all(decrypted.as_bytes()).await.log_err();
|
||||
if let Some(password) = prompt.await {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
_ = askpass_secret.set(password.clone());
|
||||
}
|
||||
ControlFlow::Continue(Ok(password))
|
||||
} else {
|
||||
if let Some(kill_tx) = kill_tx.lock().await.take() {
|
||||
kill_tx.send(()).log_err();
|
||||
}
|
||||
ControlFlow::Break(())
|
||||
}
|
||||
} else {
|
||||
if let Some(kill_tx) = kill_tx.take() {
|
||||
kill_tx.send(()).log_err();
|
||||
}
|
||||
// note: we expect the caller to drop this task when it's done.
|
||||
// We need to keep the stream open until the caller is done to avoid
|
||||
// spurious errors from ssh.
|
||||
std::future::pending::<()>().await;
|
||||
drop(stream);
|
||||
}
|
||||
})
|
||||
}
|
||||
drop(temp_dir)
|
||||
});
|
||||
|
||||
// Create an askpass script that communicates back to this process.
|
||||
let askpass_script = generate_askpass_script(&askpass_program, &askpass_socket);
|
||||
fs::write(&askpass_script_path, askpass_script)
|
||||
.await
|
||||
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
|
||||
make_file_executable(&askpass_script_path).await?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_helper = format!(
|
||||
"powershell.exe -ExecutionPolicy Bypass -File {}",
|
||||
askpass_script_path.display()
|
||||
);
|
||||
};
|
||||
let askpass_task = PasswordProxy::new(get_password, executor.clone()).await?;
|
||||
|
||||
Ok(Self {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
script_path: askpass_script_path,
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
secret,
|
||||
#[cfg(target_os = "windows")]
|
||||
askpass_helper,
|
||||
|
||||
_askpass_task: askpass_task,
|
||||
askpass_task,
|
||||
askpass_kill_master_rx: Some(askpass_kill_master_rx),
|
||||
askpass_opened_rx: Some(askpass_opened_rx),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn script_path(&self) -> impl AsRef<OsStr> {
|
||||
&self.script_path
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn script_path(&self) -> impl AsRef<OsStr> {
|
||||
&self.askpass_helper
|
||||
}
|
||||
|
||||
// This will run the askpass task forever, resolving as many authentication requests as needed.
|
||||
// The caller is responsible for examining the result of their own commands and cancelling this
|
||||
// future when this is no longer needed. Note that this can only be called once, but due to the
|
||||
@@ -209,8 +171,109 @@ impl AskPassSession {
|
||||
pub fn get_password(&self) -> Option<EncryptedPassword> {
|
||||
self.secret.get().cloned()
|
||||
}
|
||||
|
||||
pub fn script_path(&self) -> impl AsRef<OsStr> {
|
||||
self.askpass_task.script_path()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PasswordProxy {
|
||||
_task: Task<()>,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
askpass_script_path: std::path::PathBuf,
|
||||
#[cfg(target_os = "windows")]
|
||||
askpass_helper: String,
|
||||
}
|
||||
|
||||
impl PasswordProxy {
|
||||
pub async fn new(
|
||||
mut get_password: impl FnMut(String) -> Task<ControlFlow<(), Result<EncryptedPassword>>>
|
||||
+ 'static
|
||||
+ Send
|
||||
+ Sync,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Result<Self> {
|
||||
let temp_dir = tempfile::Builder::new().prefix("zed-askpass").tempdir()?;
|
||||
let askpass_socket = temp_dir.path().join("askpass.sock");
|
||||
let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME);
|
||||
let current_exec =
|
||||
std::env::current_exe().context("Failed to determine current zed executable path.")?;
|
||||
|
||||
let askpass_program = ASKPASS_PROGRAM
|
||||
.get_or_init(|| current_exec)
|
||||
.try_shell_safe()
|
||||
.context("Failed to shell-escape Askpass program path.")?
|
||||
.to_string();
|
||||
// Create an askpass script that communicates back to this process.
|
||||
let askpass_script = generate_askpass_script(&askpass_program, &askpass_socket);
|
||||
let _task = executor.spawn(async move {
|
||||
maybe!(async move {
|
||||
let listener =
|
||||
UnixListener::bind(&askpass_socket).context("creating askpass socket")?;
|
||||
|
||||
while let Ok((mut stream, _)) = listener.accept().await {
|
||||
let mut buffer = Vec::new();
|
||||
let mut reader = BufReader::new(&mut stream);
|
||||
if reader.read_until(b'\0', &mut buffer).await.is_err() {
|
||||
buffer.clear();
|
||||
}
|
||||
let prompt = String::from_utf8_lossy(&buffer).into_owned();
|
||||
let password = get_password(prompt).await;
|
||||
match password {
|
||||
ControlFlow::Continue(password) => {
|
||||
if let Ok(password) = password
|
||||
&& let Ok(decrypted) =
|
||||
password.decrypt(IKnowWhatIAmDoingAndIHaveReadTheDocs)
|
||||
{
|
||||
stream.write_all(decrypted.as_bytes()).await.log_err();
|
||||
}
|
||||
}
|
||||
ControlFlow::Break(()) => {
|
||||
// note: we expect the caller to drop this task when it's done.
|
||||
// We need to keep the stream open until the caller is done to avoid
|
||||
// spurious errors from ssh.
|
||||
std::future::pending::<()>().await;
|
||||
drop(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(temp_dir);
|
||||
Result::<_, anyhow::Error>::Ok(())
|
||||
})
|
||||
.await
|
||||
.log_err();
|
||||
});
|
||||
|
||||
fs::write(&askpass_script_path, askpass_script)
|
||||
.await
|
||||
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
|
||||
make_file_executable(&askpass_script_path).await?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_helper = format!(
|
||||
"powershell.exe -ExecutionPolicy Bypass -File {}",
|
||||
askpass_script_path.display()
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
_task,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
askpass_script_path,
|
||||
#[cfg(target_os = "windows")]
|
||||
askpass_helper,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn script_path(&self) -> impl AsRef<OsStr> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
&self.askpass_script_path
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
&self.askpass_helper
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The main function for when Zed is running in netcat mode for use in askpass.
|
||||
/// Called from both the remote server binary and the zed binary in their respective main functions.
|
||||
pub fn main(socket: &str) {
|
||||
|
||||
@@ -21,27 +21,6 @@ type LengthWithoutPadding = u32;
|
||||
#[derive(Clone)]
|
||||
pub struct EncryptedPassword(Vec<u8>, LengthWithoutPadding);
|
||||
|
||||
pub trait ProcessExt {
|
||||
fn encrypted_env(&mut self, name: &str, value: EncryptedPassword) -> &mut Self;
|
||||
}
|
||||
|
||||
impl ProcessExt for smol::process::Command {
|
||||
fn encrypted_env(&mut self, name: &str, value: EncryptedPassword) -> &mut Self {
|
||||
if let Ok(password) = decrypt(value) {
|
||||
self.env(name, password);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<EncryptedPassword> for proto::AskPassResponse {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(pw: EncryptedPassword) -> Result<Self, Self::Error> {
|
||||
let pw = decrypt(pw)?;
|
||||
Ok(Self { response: pw })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EncryptedPassword {
|
||||
fn drop(&mut self) {
|
||||
self.0.zeroize();
|
||||
@@ -79,38 +58,45 @@ impl TryFrom<&str> for EncryptedPassword {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decrypt(mut password: EncryptedPassword) -> Result<String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use anyhow::Context;
|
||||
use windows::Win32::Security::Cryptography::{
|
||||
CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS, CryptUnprotectMemory,
|
||||
};
|
||||
assert_eq!(
|
||||
password.0.len() % CRYPTPROTECTMEMORY_BLOCK_SIZE as usize,
|
||||
0,
|
||||
"Violated pre-condition (buffer size <{}> must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE <{}>) for CryptUnprotectMemory.",
|
||||
password.0.len(),
|
||||
CRYPTPROTECTMEMORY_BLOCK_SIZE
|
||||
);
|
||||
if password.1 != 0 {
|
||||
unsafe {
|
||||
CryptUnprotectMemory(
|
||||
password.0.as_mut_ptr() as _,
|
||||
password.0.len().try_into()?,
|
||||
CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
)
|
||||
.context("while decrypting a SSH password")?
|
||||
/// Read the docs for [EncryptedPassword]; please take care of not storing the plaintext string in memory for extended
|
||||
/// periods of time.
|
||||
pub struct IKnowWhatIAmDoingAndIHaveReadTheDocs;
|
||||
|
||||
impl EncryptedPassword {
|
||||
pub fn decrypt(mut self, _: IKnowWhatIAmDoingAndIHaveReadTheDocs) -> Result<String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use anyhow::Context;
|
||||
use windows::Win32::Security::Cryptography::{
|
||||
CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
CryptUnprotectMemory,
|
||||
};
|
||||
assert_eq!(
|
||||
self.0.len() % CRYPTPROTECTMEMORY_BLOCK_SIZE as usize,
|
||||
0,
|
||||
"Violated pre-condition (buffer size <{}> must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE <{}>) for CryptUnprotectMemory.",
|
||||
self.0.len(),
|
||||
CRYPTPROTECTMEMORY_BLOCK_SIZE
|
||||
);
|
||||
if self.1 != 0 {
|
||||
unsafe {
|
||||
CryptUnprotectMemory(
|
||||
self.0.as_mut_ptr() as _,
|
||||
self.0.len().try_into()?,
|
||||
CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
)
|
||||
.context("while decrypting a SSH password")?
|
||||
};
|
||||
|
||||
{
|
||||
// Remove padding
|
||||
_ = password.0.drain(password.1 as usize..);
|
||||
{
|
||||
// Remove padding
|
||||
_ = self.0.drain(self.1 as usize..);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(std::mem::take(&mut password.0))?)
|
||||
Ok(String::from_utf8(std::mem::take(&mut self.0))?)
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
Ok(String::from_utf8(std::mem::take(&mut self.0))?)
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
Ok(String::from_utf8(std::mem::take(&mut password.0))?)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ pub trait DapDelegate: Send + Sync + 'static {
|
||||
async fn which(&self, command: &OsStr) -> Option<PathBuf>;
|
||||
async fn read_text_file(&self, path: &RelPath) -> Result<String>;
|
||||
async fn shell_env(&self) -> collections::HashMap<String, String>;
|
||||
fn is_headless(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
||||
@@ -120,13 +120,6 @@ impl JsDebugAdapter {
|
||||
configuration
|
||||
.entry("sourceMapRenames")
|
||||
.or_insert(true.into());
|
||||
|
||||
// Set up remote browser debugging
|
||||
if delegate.is_headless() {
|
||||
configuration
|
||||
.entry("browserLaunchLocation")
|
||||
.or_insert("ui".into());
|
||||
}
|
||||
}
|
||||
|
||||
let adapter_path = if let Some(user_installed_path) = user_installed_path {
|
||||
|
||||
@@ -350,7 +350,6 @@ pub fn init(cx: &mut App) {
|
||||
|
||||
cx.set_global(GlobalBlameRenderer(Arc::new(())));
|
||||
|
||||
dbg!();
|
||||
workspace::register_project_item::<Editor>(cx);
|
||||
workspace::FollowableViewRegistry::register::<Editor>(cx);
|
||||
workspace::register_serializable_item::<Editor>(cx);
|
||||
|
||||
@@ -1503,39 +1503,28 @@ impl Render for ExtensionsPage {
|
||||
})),
|
||||
)
|
||||
.child(self.render_feature_upsells(cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.pl_4()
|
||||
.pr_6()
|
||||
.size_full()
|
||||
.overflow_y_hidden()
|
||||
.map(|this| {
|
||||
let mut count = self.filtered_remote_extension_indices.len();
|
||||
if self.filter.include_dev_extensions() {
|
||||
count += self.dev_extension_entries.len();
|
||||
}
|
||||
.child(v_flex().px_4().size_full().overflow_y_hidden().map(|this| {
|
||||
let mut count = self.filtered_remote_extension_indices.len();
|
||||
if self.filter.include_dev_extensions() {
|
||||
count += self.dev_extension_entries.len();
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
this.py_4()
|
||||
.child(self.render_empty_state(cx))
|
||||
.into_any_element()
|
||||
} else {
|
||||
let scroll_handle = self.list.clone();
|
||||
this.child(
|
||||
uniform_list(
|
||||
"entries",
|
||||
count,
|
||||
cx.processor(Self::render_extensions),
|
||||
)
|
||||
.flex_grow()
|
||||
.pb_4()
|
||||
.track_scroll(scroll_handle.clone()),
|
||||
)
|
||||
.vertical_scrollbar_for(scroll_handle, window, cx)
|
||||
.into_any_element()
|
||||
}
|
||||
}),
|
||||
)
|
||||
if count == 0 {
|
||||
this.py_4()
|
||||
.child(self.render_empty_state(cx))
|
||||
.into_any_element()
|
||||
} else {
|
||||
let scroll_handle = self.list.clone();
|
||||
this.child(
|
||||
uniform_list("entries", count, cx.processor(Self::render_extensions))
|
||||
.flex_grow()
|
||||
.pb_4()
|
||||
.track_scroll(scroll_handle.clone()),
|
||||
)
|
||||
.vertical_scrollbar_for(scroll_handle, window, cx)
|
||||
.into_any_element()
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::{LazyLock, OnceLock};
|
||||
|
||||
pub use util::paths::home_dir;
|
||||
use util::rel_path::RelPath;
|
||||
@@ -31,12 +31,16 @@ static CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
/// Returns the relative path to the zed_server directory on the ssh host.
|
||||
pub fn remote_server_dir_relative() -> &'static RelPath {
|
||||
RelPath::unix(".zed_server").unwrap()
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".zed_server").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
/// Returns the relative path to the zed_wsl_server directory on the wsl host.
|
||||
pub fn remote_wsl_server_dir_relative() -> &'static RelPath {
|
||||
RelPath::unix(".zed_wsl_server").unwrap()
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".zed_wsl_server").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
/// Sets a custom directory for all user data, overriding the default data directory.
|
||||
@@ -410,17 +414,23 @@ pub fn local_vscode_folder_name() -> &'static str {
|
||||
|
||||
/// Returns the relative path to a `settings.json` file within a project.
|
||||
pub fn local_settings_file_relative_path() -> &'static RelPath {
|
||||
RelPath::unix(".zed/settings.json").unwrap()
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".zed/settings.json").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
/// Returns the relative path to a `tasks.json` file within a project.
|
||||
pub fn local_tasks_file_relative_path() -> &'static RelPath {
|
||||
RelPath::unix(".zed/tasks.json").unwrap()
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".zed/tasks.json").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
/// Returns the relative path to a `.vscode/tasks.json` file within a project.
|
||||
pub fn local_vscode_tasks_file_relative_path() -> &'static RelPath {
|
||||
RelPath::unix(".vscode/tasks.json").unwrap()
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".vscode/tasks.json").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
pub fn debug_task_file_name() -> &'static str {
|
||||
@@ -434,12 +444,16 @@ pub fn task_file_name() -> &'static str {
|
||||
/// Returns the relative path to a `debug.json` file within a project.
|
||||
/// .zed/debug.json
|
||||
pub fn local_debug_file_relative_path() -> &'static RelPath {
|
||||
RelPath::unix(".zed/debug.json").unwrap()
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".zed/debug.json").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
/// Returns the relative path to a `.vscode/launch.json` file within a project.
|
||||
pub fn local_vscode_launch_file_relative_path() -> &'static RelPath {
|
||||
RelPath::unix(".vscode/launch.json").unwrap()
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".vscode/launch.json").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
pub fn user_ssh_config_file() -> PathBuf {
|
||||
|
||||
@@ -164,7 +164,6 @@ pub struct BreakpointStore {
|
||||
|
||||
impl BreakpointStore {
|
||||
pub fn init(client: &AnyProtoClient) {
|
||||
log::error!("breakpoint store init");
|
||||
client.add_entity_request_handler(Self::handle_toggle_breakpoint);
|
||||
client.add_entity_message_handler(Self::handle_breakpoints_for_file);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ use dap::{
|
||||
inline_value::VariableLookupKind,
|
||||
messages::Message,
|
||||
};
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
StreamExt, TryStreamExt as _,
|
||||
StreamExt,
|
||||
channel::mpsc::{self, UnboundedSender},
|
||||
future::{Shared, join_all},
|
||||
};
|
||||
@@ -78,15 +78,12 @@ pub struct LocalDapStore {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
environment: Entity<ProjectEnvironment>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
is_headless: bool,
|
||||
}
|
||||
|
||||
pub struct RemoteDapStore {
|
||||
remote_client: Entity<RemoteClient>,
|
||||
upstream_client: AnyProtoClient,
|
||||
upstream_project_id: u64,
|
||||
node_runtime: NodeRuntime,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
}
|
||||
|
||||
pub struct DapStore {
|
||||
@@ -137,19 +134,17 @@ impl DapStore {
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
is_headless: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let mode = DapStoreMode::Local(LocalDapStore {
|
||||
fs: fs.clone(),
|
||||
fs,
|
||||
environment,
|
||||
http_client,
|
||||
node_runtime,
|
||||
toolchain_store,
|
||||
is_headless,
|
||||
});
|
||||
|
||||
Self::new(mode, breakpoint_store, worktree_store, fs, cx)
|
||||
Self::new(mode, breakpoint_store, worktree_store, cx)
|
||||
}
|
||||
|
||||
pub fn new_remote(
|
||||
@@ -157,20 +152,15 @@ impl DapStore {
|
||||
remote_client: Entity<RemoteClient>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
node_runtime: NodeRuntime,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let mode = DapStoreMode::Remote(RemoteDapStore {
|
||||
upstream_client: remote_client.read(cx).proto_client(),
|
||||
remote_client,
|
||||
upstream_project_id: project_id,
|
||||
node_runtime,
|
||||
http_client,
|
||||
});
|
||||
|
||||
Self::new(mode, breakpoint_store, worktree_store, fs, cx)
|
||||
Self::new(mode, breakpoint_store, worktree_store, cx)
|
||||
}
|
||||
|
||||
pub fn new_collab(
|
||||
@@ -178,55 +168,17 @@ impl DapStore {
|
||||
_upstream_client: AnyProtoClient,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
DapStoreMode::Collab,
|
||||
breakpoint_store,
|
||||
worktree_store,
|
||||
fs,
|
||||
cx,
|
||||
)
|
||||
Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx)
|
||||
}
|
||||
|
||||
fn new(
|
||||
mode: DapStoreMode,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut Context<Self>,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
cx.background_spawn(async move {
|
||||
let dir = paths::debug_adapters_dir().join("js-debug-companion");
|
||||
|
||||
let mut children = fs.read_dir(&dir).await?.try_collect::<Vec<_>>().await?;
|
||||
children.sort_by_key(|child| semver::Version::parse(child.file_name()?.to_str()?).ok());
|
||||
|
||||
if let Some(child) = children.last()
|
||||
&& let Some(name) = child.file_name()
|
||||
&& let Some(name) = name.to_str()
|
||||
&& semver::Version::parse(name).is_ok()
|
||||
{
|
||||
children.pop();
|
||||
}
|
||||
|
||||
for child in children {
|
||||
fs.remove_dir(
|
||||
&child,
|
||||
RemoveOptions {
|
||||
recursive: true,
|
||||
ignore_if_not_exists: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
mode,
|
||||
next_session_id: 0,
|
||||
@@ -449,15 +401,6 @@ impl DapStore {
|
||||
});
|
||||
}
|
||||
|
||||
let (remote_client, node_runtime, http_client) = match &self.mode {
|
||||
DapStoreMode::Local(_) => (None, None, None),
|
||||
DapStoreMode::Remote(remote_dap_store) => (
|
||||
Some(remote_dap_store.remote_client.clone()),
|
||||
Some(remote_dap_store.node_runtime.clone()),
|
||||
Some(remote_dap_store.http_client.clone()),
|
||||
),
|
||||
DapStoreMode::Collab => (None, None, None),
|
||||
};
|
||||
let session = Session::new(
|
||||
self.breakpoint_store.clone(),
|
||||
session_id,
|
||||
@@ -466,9 +409,6 @@ impl DapStore {
|
||||
adapter,
|
||||
task_context,
|
||||
quirks,
|
||||
remote_client,
|
||||
node_runtime,
|
||||
http_client,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -598,7 +538,6 @@ impl DapStore {
|
||||
local_store.environment.update(cx, |env, cx| {
|
||||
env.get_worktree_environment(worktree.clone(), cx)
|
||||
}),
|
||||
local_store.is_headless,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -931,7 +870,6 @@ pub struct DapAdapterDelegate {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
|
||||
is_headless: bool,
|
||||
}
|
||||
|
||||
impl DapAdapterDelegate {
|
||||
@@ -943,7 +881,6 @@ impl DapAdapterDelegate {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
|
||||
is_headless: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
fs,
|
||||
@@ -953,7 +890,6 @@ impl DapAdapterDelegate {
|
||||
node_runtime,
|
||||
toolchain_store,
|
||||
load_shell_env_task,
|
||||
is_headless,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1017,8 +953,4 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate {
|
||||
|
||||
self.fs.load(&abs_path).await
|
||||
}
|
||||
|
||||
fn is_headless(&self) -> bool {
|
||||
self.is_headless
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,28 +31,21 @@ use dap::{
|
||||
RunInTerminalRequestArguments, StackFramePresentationHint, StartDebuggingRequestArguments,
|
||||
StartDebuggingRequestArgumentsRequest, VariablePresentationHint, WriteMemoryArguments,
|
||||
};
|
||||
use futures::SinkExt;
|
||||
use futures::channel::mpsc::UnboundedSender;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::io::BufReader;
|
||||
use futures::{AsyncBufReadExt as _, SinkExt, StreamExt, TryStreamExt};
|
||||
use futures::{FutureExt, future::Shared};
|
||||
use gpui::{
|
||||
App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString,
|
||||
Task, WeakEntity,
|
||||
};
|
||||
use http_client::HttpClient;
|
||||
|
||||
use node_runtime::NodeRuntime;
|
||||
use remote::RemoteClient;
|
||||
use rpc::ErrorExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::net::TcpListener;
|
||||
use smol::stream::StreamExt;
|
||||
use std::any::TypeId;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
use std::u64;
|
||||
use std::{
|
||||
any::Any,
|
||||
@@ -63,7 +56,6 @@ use std::{
|
||||
};
|
||||
use task::TaskContext;
|
||||
use text::{PointUtf16, ToPointUtf16};
|
||||
use util::command::new_smol_command;
|
||||
use util::{ResultExt, debug_panic, maybe};
|
||||
use worktree::Worktree;
|
||||
|
||||
@@ -704,10 +696,6 @@ pub struct Session {
|
||||
task_context: TaskContext,
|
||||
memory: memory::Memory,
|
||||
quirks: SessionQuirks,
|
||||
remote_client: Option<Entity<RemoteClient>>,
|
||||
node_runtime: Option<NodeRuntime>,
|
||||
http_client: Option<Arc<dyn HttpClient>>,
|
||||
companion_port: Option<u16>,
|
||||
}
|
||||
|
||||
trait CacheableCommand: Any + Send + Sync {
|
||||
@@ -824,9 +812,6 @@ impl Session {
|
||||
adapter: DebugAdapterName,
|
||||
task_context: TaskContext,
|
||||
quirks: SessionQuirks,
|
||||
remote_client: Option<Entity<RemoteClient>>,
|
||||
node_runtime: Option<NodeRuntime>,
|
||||
http_client: Option<Arc<dyn HttpClient>>,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
cx.new::<Self>(|cx| {
|
||||
@@ -882,10 +867,6 @@ impl Session {
|
||||
task_context,
|
||||
memory: memory::Memory::new(),
|
||||
quirks,
|
||||
remote_client,
|
||||
node_runtime,
|
||||
http_client,
|
||||
companion_port: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1576,21 +1557,7 @@ impl Session {
|
||||
Events::ProgressStart(_) => {}
|
||||
Events::ProgressUpdate(_) => {}
|
||||
Events::Invalidated(_) => {}
|
||||
Events::Other(event) => {
|
||||
if event.event == "launchBrowserInCompanion" {
|
||||
let Some(request) = serde_json::from_value(event.body).ok() else {
|
||||
log::error!("failed to deserialize launchBrowserInCompanion event");
|
||||
return;
|
||||
};
|
||||
self.launch_browser_for_remote_server(request, cx);
|
||||
} else if event.event == "killCompanionBrowser" {
|
||||
let Some(request) = serde_json::from_value(event.body).ok() else {
|
||||
log::error!("failed to deserialize killCompanionBrowser event");
|
||||
return;
|
||||
};
|
||||
self.kill_browser(request, cx);
|
||||
}
|
||||
}
|
||||
Events::Other(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2749,304 +2716,4 @@ impl Session {
|
||||
pub fn quirks(&self) -> SessionQuirks {
|
||||
self.quirks
|
||||
}
|
||||
|
||||
fn launch_browser_for_remote_server(
|
||||
&mut self,
|
||||
mut request: LaunchBrowserInCompanionParams,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(remote_client) = self.remote_client.clone() else {
|
||||
log::error!("can't launch browser in companion for non-remote project");
|
||||
return;
|
||||
};
|
||||
let Some(http_client) = self.http_client.clone() else {
|
||||
return;
|
||||
};
|
||||
let Some(node_runtime) = self.node_runtime.clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut console_output = self.console_output(cx);
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
let (dap_port, _child) =
|
||||
if remote_client.read_with(cx, |client, _| client.shares_network_interface())? {
|
||||
(request.server_port, None)
|
||||
} else {
|
||||
let port = {
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.context("getting port for DAP")?;
|
||||
listener.local_addr()?.port()
|
||||
};
|
||||
let child = remote_client.update(cx, |client, _| {
|
||||
let command = client.build_forward_port_command(
|
||||
port,
|
||||
"localhost".into(),
|
||||
request.server_port,
|
||||
)?;
|
||||
let child = new_smol_command(command.program)
|
||||
.args(command.args)
|
||||
.envs(command.env)
|
||||
.spawn()
|
||||
.context("spawning port forwarding process")?;
|
||||
anyhow::Ok(child)
|
||||
})??;
|
||||
(port, Some(child))
|
||||
};
|
||||
|
||||
let mut companion_process = None;
|
||||
let companion_port =
|
||||
if let Some(companion_port) = this.read_with(cx, |this, _| this.companion_port)? {
|
||||
companion_port
|
||||
} else {
|
||||
let task = cx.spawn(async move |cx| spawn_companion(node_runtime, cx).await);
|
||||
match task.await {
|
||||
Ok((port, child)) => {
|
||||
companion_process = Some(child);
|
||||
port
|
||||
}
|
||||
Err(e) => {
|
||||
console_output
|
||||
.send(format!("Failed to launch browser companion process: {e}"))
|
||||
.await
|
||||
.ok();
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.update(cx, |this, cx| {
|
||||
this.companion_port = Some(companion_port);
|
||||
let Some(mut child) = companion_process else {
|
||||
return;
|
||||
};
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
let mut console_output = console_output.clone();
|
||||
this.background_tasks.push(cx.spawn(async move |_, _| {
|
||||
let mut stderr = BufReader::new(stderr);
|
||||
let mut line = String::new();
|
||||
while let Ok(n) = stderr.read_line(&mut line).await
|
||||
&& n > 0
|
||||
{
|
||||
console_output
|
||||
.send(format!("companion stderr: {line}"))
|
||||
.await
|
||||
.ok();
|
||||
line.clear();
|
||||
}
|
||||
}));
|
||||
}
|
||||
this.background_tasks.push(cx.spawn({
|
||||
let mut console_output = console_output.clone();
|
||||
async move |_, _| match child.status().await {
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
console_output
|
||||
.send("Companion process exited normally".into())
|
||||
.await
|
||||
.ok();
|
||||
} else {
|
||||
console_output
|
||||
.send(format!(
|
||||
"Companion process exited abnormally with {status:?}"
|
||||
))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
console_output
|
||||
.send(format!("Failed to join companion process: {e}"))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}))
|
||||
})?;
|
||||
|
||||
request
|
||||
.other
|
||||
.insert("proxyUri".into(), format!("127.0.0.1:{dap_port}").into());
|
||||
// TODO pass wslInfo as needed
|
||||
|
||||
let response = http_client
|
||||
.post_json(
|
||||
&format!("http://127.0.0.1:{companion_port}/launch-and-attach"),
|
||||
serde_json::to_string(&request)
|
||||
.context("serializing request")?
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
match response {
|
||||
Ok(response) => {
|
||||
if !response.status().is_success() {
|
||||
console_output
|
||||
.send("Launch request to companion failed".into())
|
||||
.await
|
||||
.ok();
|
||||
return Err(anyhow!("launch request failed"));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
console_output
|
||||
.send("Failed to read response from companion".into())
|
||||
.await
|
||||
.ok();
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
});
|
||||
self.background_tasks.push(cx.spawn(async move |_, _| {
|
||||
task.await.log_err();
|
||||
}));
|
||||
}
|
||||
|
||||
fn kill_browser(&self, request: KillCompanionBrowserParams, cx: &mut App) {
|
||||
let Some(companion_port) = self.companion_port else {
|
||||
log::error!("received killCompanionBrowser but js-debug-companion is not running");
|
||||
return;
|
||||
};
|
||||
let Some(http_client) = self.http_client.clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
cx.spawn(async move |_| {
|
||||
http_client
|
||||
.post_json(
|
||||
&format!("http://127.0.0.1:{companion_port}/kill"),
|
||||
serde_json::to_string(&request)
|
||||
.context("serializing request")?
|
||||
.into(),
|
||||
)
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct LaunchBrowserInCompanionParams {
|
||||
server_port: u16,
|
||||
#[serde(flatten)]
|
||||
other: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct KillCompanionBrowserParams {
|
||||
launch_id: u64,
|
||||
}
|
||||
|
||||
async fn spawn_companion(
|
||||
node_runtime: NodeRuntime,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<(u16, smol::process::Child)> {
|
||||
let binary_path = node_runtime
|
||||
.binary_path()
|
||||
.await
|
||||
.context("getting node path")?;
|
||||
let path = cx
|
||||
.spawn(async move |cx| get_or_install_companion(node_runtime, cx).await)
|
||||
.await?;
|
||||
log::info!("will launch js-debug-companion version {path:?}");
|
||||
|
||||
let port = {
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.context("getting port for companion")?;
|
||||
listener.local_addr()?.port()
|
||||
};
|
||||
|
||||
let dir = paths::data_dir()
|
||||
.join("js_debug_companion_state")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let child = new_smol_command(binary_path)
|
||||
.arg(path)
|
||||
.args([
|
||||
format!("--listen=127.0.0.1:{port}"),
|
||||
format!("--state={dir}"),
|
||||
])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.context("spawning companion child process")?;
|
||||
|
||||
Ok((port, child))
|
||||
}
|
||||
|
||||
async fn get_or_install_companion(node: NodeRuntime, cx: &mut AsyncApp) -> Result<PathBuf> {
|
||||
const PACKAGE_NAME: &str = "@zed-industries/js-debug-companion-cli";
|
||||
|
||||
async fn install_latest_version(dir: PathBuf, node: NodeRuntime) -> Result<PathBuf> {
|
||||
let temp_dir = tempfile::tempdir().context("creating temporary directory")?;
|
||||
node.npm_install_packages(temp_dir.path(), &[(PACKAGE_NAME, "latest")])
|
||||
.await
|
||||
.context("installing latest companion package")?;
|
||||
let version = node
|
||||
.npm_package_installed_version(temp_dir.path(), PACKAGE_NAME)
|
||||
.await
|
||||
.context("getting installed companion version")?
|
||||
.context("companion was not installed")?;
|
||||
smol::fs::rename(temp_dir.path(), dir.join(&version))
|
||||
.await
|
||||
.context("moving companion package into place")?;
|
||||
Ok(dir.join(version))
|
||||
}
|
||||
|
||||
let dir = paths::debug_adapters_dir().join("js-debug-companion");
|
||||
let (latest_installed_version, latest_version) = cx
|
||||
.background_spawn({
|
||||
let dir = dir.clone();
|
||||
let node = node.clone();
|
||||
async move {
|
||||
smol::fs::create_dir_all(&dir)
|
||||
.await
|
||||
.context("creating companion installation directory")?;
|
||||
|
||||
let mut children = smol::fs::read_dir(&dir)
|
||||
.await
|
||||
.context("reading companion installation directory")?
|
||||
.try_collect::<Vec<_>>()
|
||||
.await
|
||||
.context("reading companion installation directory entries")?;
|
||||
children
|
||||
.sort_by_key(|child| semver::Version::parse(child.file_name().to_str()?).ok());
|
||||
|
||||
let latest_installed_version = children.last().and_then(|child| {
|
||||
let version = child.file_name().into_string().ok()?;
|
||||
Some((child.path(), version))
|
||||
});
|
||||
let latest_version = node
|
||||
.npm_package_latest_version(PACKAGE_NAME)
|
||||
.await
|
||||
.log_err();
|
||||
anyhow::Ok((latest_installed_version, latest_version))
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
let path = if let Some((installed_path, installed_version)) = latest_installed_version {
|
||||
if let Some(latest_version) = latest_version
|
||||
&& latest_version != installed_version
|
||||
{
|
||||
cx.background_spawn(install_latest_version(dir.clone(), node.clone()))
|
||||
.detach();
|
||||
}
|
||||
Ok(installed_path)
|
||||
} else {
|
||||
cx.background_spawn(install_latest_version(dir.clone(), node.clone()))
|
||||
.await
|
||||
};
|
||||
|
||||
Ok(path?
|
||||
.join("node_modules")
|
||||
.join(PACKAGE_NAME)
|
||||
.join("out")
|
||||
.join("cli.js"))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
worktree_store::{WorktreeStore, WorktreeStoreEvent},
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use askpass::{AskPassDelegate, EncryptedPassword};
|
||||
use askpass::{AskPassDelegate, EncryptedPassword, IKnowWhatIAmDoingAndIHaveReadTheDocs};
|
||||
use buffer_diff::{BufferDiff, BufferDiffEvent};
|
||||
use client::ProjectId;
|
||||
use collections::HashMap;
|
||||
@@ -2120,7 +2120,10 @@ impl GitStore {
|
||||
.lock()
|
||||
.insert(envelope.payload.askpass_id, askpass);
|
||||
|
||||
response.try_into()
|
||||
// In fact, we don't quite know what we're doing here, as we're sending askpass password unencrypted, but..
|
||||
Ok(proto::AskPassResponse {
|
||||
response: response.decrypt(IKnowWhatIAmDoingAndIHaveReadTheDocs)?,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_check_for_pushed_commits(
|
||||
|
||||
@@ -1084,7 +1084,6 @@ impl Project {
|
||||
toolchain_store.read(cx).as_language_toolchain_store(),
|
||||
worktree_store.clone(),
|
||||
breakpoint_store.clone(),
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1307,9 +1306,6 @@ impl Project {
|
||||
remote.clone(),
|
||||
breakpoint_store.clone(),
|
||||
worktree_store.clone(),
|
||||
node.clone(),
|
||||
client.http_client(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1507,7 +1503,6 @@ impl Project {
|
||||
client.clone().into(),
|
||||
breakpoint_store.clone(),
|
||||
worktree_store.clone(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -51,7 +51,6 @@ workspace-hack.workspace = true
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
criterion.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -102,7 +102,7 @@ impl State {
|
||||
max_width_item_index: None,
|
||||
edit_state: old.edit_state.clone(),
|
||||
unfolded_dir_ids: old.unfolded_dir_ids.clone(),
|
||||
selection: dbg!(old.selection),
|
||||
selection: old.selection,
|
||||
expanded_dir_ids: old.expanded_dir_ids.clone(),
|
||||
}
|
||||
}
|
||||
@@ -398,7 +398,6 @@ pub fn init(cx: &mut App) {
|
||||
if let Some(first_marked) = panel.marked_entries.first() {
|
||||
let first_marked = *first_marked;
|
||||
panel.marked_entries.clear();
|
||||
dbg!();
|
||||
panel.state.selection = Some(first_marked);
|
||||
}
|
||||
panel.rename(action, window, cx);
|
||||
@@ -730,10 +729,8 @@ impl ProjectPanel {
|
||||
focus_opened_item,
|
||||
allow_preview,
|
||||
} => {
|
||||
dbg!();
|
||||
if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx)
|
||||
&& let Some(entry) = worktree.read(cx).entry_for_id(entry_id) {
|
||||
dbg!();
|
||||
let file_path = entry.path.clone();
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
let entry_id = entry.id;
|
||||
@@ -770,13 +767,12 @@ impl ProjectPanel {
|
||||
});
|
||||
|
||||
if let Some(project_panel) = project_panel.upgrade() {
|
||||
dbg!();
|
||||
// Always select and mark the entry, regardless of whether it is opened or not.
|
||||
project_panel.update(cx, |project_panel, _| {
|
||||
let entry = SelectedEntry { worktree_id, entry_id };
|
||||
project_panel.marked_entries.clear();
|
||||
project_panel.marked_entries.push(entry);
|
||||
project_panel.state.selection = Some(dbg!(entry));
|
||||
project_panel.state.selection = Some(entry);
|
||||
});
|
||||
if !focus_opened_item {
|
||||
let focus_handle = project_panel.read(cx).focus_handle.clone();
|
||||
@@ -961,7 +957,6 @@ impl ProjectPanel {
|
||||
return;
|
||||
};
|
||||
|
||||
dbg!();
|
||||
self.state.selection = Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id,
|
||||
@@ -1403,7 +1398,6 @@ impl ProjectPanel {
|
||||
worktree_id: *worktree_id,
|
||||
entry_id: entries[entry_ix].id,
|
||||
};
|
||||
dbg!();
|
||||
self.state.selection = Some(selection);
|
||||
if window.modifiers().shift {
|
||||
self.marked_entries.push(selection);
|
||||
@@ -1581,7 +1575,6 @@ impl ProjectPanel {
|
||||
let edit_task;
|
||||
let edited_entry_id;
|
||||
if is_new_entry {
|
||||
dbg!();
|
||||
self.state.selection = Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: NEW_ENTRY_ID,
|
||||
@@ -1636,7 +1629,6 @@ impl ProjectPanel {
|
||||
project_panel.update_in( cx, |project_panel, window, cx| {
|
||||
if let Some(selection) = &mut project_panel.state.selection
|
||||
&& selection.entry_id == edited_entry_id {
|
||||
dbg!();
|
||||
selection.worktree_id = worktree_id;
|
||||
selection.entry_id = new_entry.id;
|
||||
project_panel.marked_entries.clear();
|
||||
@@ -1697,7 +1689,6 @@ impl ProjectPanel {
|
||||
if let Some(previously_focused) =
|
||||
previous_edit_state.and_then(|edit_state| edit_state.previously_focused)
|
||||
{
|
||||
dbg!();
|
||||
self.state.selection = Some(previously_focused);
|
||||
self.autoscroll(cx);
|
||||
}
|
||||
@@ -1758,7 +1749,6 @@ impl ProjectPanel {
|
||||
.read(cx)
|
||||
.id();
|
||||
|
||||
dbg!();
|
||||
self.state.selection = Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id,
|
||||
@@ -2263,7 +2253,6 @@ impl ProjectPanel {
|
||||
worktree_id: *worktree_id,
|
||||
entry_id: entry.id,
|
||||
};
|
||||
dbg!();
|
||||
self.state.selection = Some(selection);
|
||||
if window.modifiers().shift {
|
||||
self.marked_entries.push(selection);
|
||||
@@ -2303,7 +2292,6 @@ impl ProjectPanel {
|
||||
);
|
||||
|
||||
if let Some(selection) = selection {
|
||||
dbg!();
|
||||
self.state.selection = Some(selection);
|
||||
self.expand_entry(selection.worktree_id, selection.entry_id, cx);
|
||||
self.update_visible_entries(
|
||||
@@ -2343,7 +2331,6 @@ impl ProjectPanel {
|
||||
);
|
||||
|
||||
if let Some(selection) = selection {
|
||||
dbg!();
|
||||
self.state.selection = Some(selection);
|
||||
self.expand_entry(selection.worktree_id, selection.entry_id, cx);
|
||||
self.update_visible_entries(
|
||||
@@ -2382,7 +2369,6 @@ impl ProjectPanel {
|
||||
);
|
||||
|
||||
if let Some(selection) = selection {
|
||||
dbg!();
|
||||
self.state.selection = Some(selection);
|
||||
self.expand_entry(selection.worktree_id, selection.entry_id, cx);
|
||||
self.update_visible_entries(
|
||||
@@ -2418,7 +2404,6 @@ impl ProjectPanel {
|
||||
);
|
||||
|
||||
if let Some(selection) = selection {
|
||||
dbg!();
|
||||
self.state.selection = Some(selection);
|
||||
self.autoscroll(cx);
|
||||
cx.notify();
|
||||
@@ -2447,7 +2432,6 @@ impl ProjectPanel {
|
||||
);
|
||||
|
||||
if let Some(selection) = selection {
|
||||
dbg!();
|
||||
self.state.selection = Some(selection);
|
||||
self.autoscroll(cx);
|
||||
cx.notify();
|
||||
@@ -2477,7 +2461,6 @@ impl ProjectPanel {
|
||||
);
|
||||
|
||||
if let Some(selection) = selection {
|
||||
dbg!();
|
||||
self.state.selection = Some(selection);
|
||||
self.expand_entry(selection.worktree_id, selection.entry_id, cx);
|
||||
self.update_visible_entries(
|
||||
@@ -2496,7 +2479,6 @@ impl ProjectPanel {
|
||||
if let Some(parent) = entry.path.parent() {
|
||||
let worktree = worktree.read(cx);
|
||||
if let Some(parent_entry) = worktree.entry_for_path(parent) {
|
||||
dbg!();
|
||||
self.state.selection = Some(SelectedEntry {
|
||||
worktree_id: worktree.id(),
|
||||
entry_id: parent_entry.id,
|
||||
@@ -2522,7 +2504,6 @@ impl ProjectPanel {
|
||||
worktree_id: *worktree_id,
|
||||
entry_id: entry.id,
|
||||
};
|
||||
dbg!();
|
||||
self.state.selection = Some(selection);
|
||||
if window.modifiers().shift {
|
||||
self.marked_entries.push(selection);
|
||||
@@ -2547,7 +2528,6 @@ impl ProjectPanel {
|
||||
worktree_id: *worktree_id,
|
||||
entry_id: entry.id,
|
||||
};
|
||||
dbg!();
|
||||
self.state.selection = Some(selection);
|
||||
self.autoscroll(cx);
|
||||
cx.notify();
|
||||
@@ -2699,19 +2679,16 @@ impl ProjectPanel {
|
||||
}
|
||||
// update selection
|
||||
if let Some(entry) = last_succeed {
|
||||
dbg!();
|
||||
project_panel
|
||||
.update_in(cx, |project_panel, window, cx| {
|
||||
dbg!();
|
||||
project_panel.state.selection = Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: dbg!(entry.id),
|
||||
entry_id: entry.id,
|
||||
});
|
||||
|
||||
if item_count == 1 {
|
||||
// open entry if not dir, and only focus if rename is not pending
|
||||
if !dbg!(entry.is_dir()) {
|
||||
dbg!();
|
||||
if !entry.is_dir() {
|
||||
project_panel.open_entry(
|
||||
entry.id,
|
||||
disambiguation_range.is_none(),
|
||||
@@ -2722,7 +2699,6 @@ impl ProjectPanel {
|
||||
|
||||
// if only one entry was pasted and it was disambiguated, open the rename editor
|
||||
if disambiguation_range.is_some() {
|
||||
dbg!();
|
||||
cx.defer_in(window, |this, window, cx| {
|
||||
this.rename_impl(disambiguation_range, window, cx);
|
||||
});
|
||||
@@ -3216,7 +3192,6 @@ impl ProjectPanel {
|
||||
|
||||
let old_ancestors = self.state.ancestors.clone();
|
||||
let mut new_state = State::derive(&self.state);
|
||||
dbg!(&new_state.selection);
|
||||
new_state.last_worktree_root_id = project
|
||||
.visible_worktrees(cx)
|
||||
.next_back()
|
||||
@@ -3444,17 +3419,15 @@ impl ProjectPanel {
|
||||
}
|
||||
}
|
||||
if let Some((worktree_id, entry_id)) = new_selected_entry {
|
||||
dbg!();
|
||||
new_state.selection = Some(dbg!(SelectedEntry {
|
||||
new_state.selection = Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id,
|
||||
}));
|
||||
});
|
||||
}
|
||||
new_state
|
||||
})
|
||||
.await;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
dbg!();
|
||||
this.state = new_state;
|
||||
let elapsed = now.elapsed();
|
||||
if this.last_reported_update.elapsed() > Duration::from_secs(3600) {
|
||||
@@ -3666,7 +3639,6 @@ impl ProjectPanel {
|
||||
if let Some(entry_id) = last_succeed {
|
||||
project_panel
|
||||
.update_in(cx, |project_panel, window, cx| {
|
||||
dbg!();
|
||||
project_panel.state.selection = Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id,
|
||||
@@ -4621,7 +4593,6 @@ impl ProjectPanel {
|
||||
}
|
||||
}
|
||||
|
||||
dbg!();
|
||||
project_panel.state.selection = Some(clicked_entry);
|
||||
if !project_panel.marked_entries.contains(&clicked_entry) {
|
||||
project_panel.marked_entries.push(clicked_entry);
|
||||
@@ -4631,7 +4602,6 @@ impl ProjectPanel {
|
||||
if event.click_count() > 1 {
|
||||
project_panel.split_entry(entry_id, false, None, cx);
|
||||
} else {
|
||||
dbg!();
|
||||
project_panel.state.selection = Some(selection);
|
||||
if let Some(position) = project_panel.marked_entries.iter().position(|e| *e == selection) {
|
||||
project_panel.marked_entries.remove(position);
|
||||
@@ -5018,9 +4988,6 @@ impl ProjectPanel {
|
||||
};
|
||||
let is_marked = self.marked_entries.contains(&selection);
|
||||
let is_selected = self.state.selection == Some(selection);
|
||||
if is_selected {
|
||||
dbg!(&entry.path, &self.state.selection);
|
||||
}
|
||||
|
||||
let diagnostic_severity = self
|
||||
.diagnostics
|
||||
@@ -5485,7 +5452,6 @@ impl Render for ProjectPanel {
|
||||
return;
|
||||
};
|
||||
|
||||
dbg!();
|
||||
this.state.selection = Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id,
|
||||
|
||||
@@ -1445,10 +1445,6 @@ async fn test_cut_paste_between_different_worktrees(cx: &mut gpui::TestAppContex
|
||||
async fn test_copy_paste_between_different_worktrees(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
cx.update(|cx| {
|
||||
register_project_item::<TestProjectItemView>(cx);
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/root1",
|
||||
@@ -1493,16 +1489,9 @@ async fn test_copy_paste_between_different_worktrees(cx: &mut gpui::TestAppConte
|
||||
select_path(&panel, "root2/one.txt", cx);
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_next(&Default::default(), window, cx);
|
||||
dbg!();
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
dbg!(visible_entries_as_strings(&panel, 0..50, cx));
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.paste(&Default::default(), window, cx);
|
||||
dbg!();
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
dbg!();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..50, cx),
|
||||
&[
|
||||
@@ -3114,7 +3103,6 @@ async fn test_rename_with_hide_root(cx: &mut gpui::TestAppContext) {
|
||||
let project = panel.project.read(cx);
|
||||
let worktree = project.visible_worktrees(cx).next().unwrap();
|
||||
let root_entry = worktree.read(cx).root_entry().unwrap();
|
||||
dbg!();
|
||||
panel.state.selection = Some(SelectedEntry {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
entry_id: root_entry.id,
|
||||
@@ -6695,7 +6683,7 @@ fn select_path(panel: &Entity<ProjectPanel>, path: &str, cx: &mut VisualTestCont
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
for worktree in panel.project.read(cx).worktrees(cx).collect::<Vec<_>>() {
|
||||
let worktree = worktree.read(cx);
|
||||
if let Ok(relative_path) = path.strip_prefix(dbg!(worktree.root_name())) {
|
||||
if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
|
||||
let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
|
||||
panel.update_visible_entries(
|
||||
Some((worktree.id(), entry_id)),
|
||||
@@ -6704,7 +6692,6 @@ fn select_path(panel: &Entity<ProjectPanel>, path: &str, cx: &mut VisualTestCont
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
dbg!(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -6727,7 +6714,6 @@ fn select_path_with_mark(panel: &Entity<ProjectPanel>, path: &str, cx: &mut Visu
|
||||
if !panel.marked_entries.contains(&entry) {
|
||||
panel.marked_entries.push(entry);
|
||||
}
|
||||
dbg!();
|
||||
panel.state.selection = Some(entry);
|
||||
return;
|
||||
}
|
||||
@@ -6813,7 +6799,6 @@ fn visible_entries_as_strings(
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
env_logger::try_init().ok();
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
init_settings(cx);
|
||||
|
||||
@@ -836,18 +836,6 @@ impl RemoteClient {
|
||||
connection.build_command(program, args, env, working_dir, port_forward)
|
||||
}
|
||||
|
||||
pub fn build_forward_port_command(
|
||||
&self,
|
||||
local_port: u16,
|
||||
host: String,
|
||||
remote_port: u16,
|
||||
) -> Result<CommandTemplate> {
|
||||
let Some(connection) = self.remote_connection() else {
|
||||
return Err(anyhow!("no ssh connection"));
|
||||
};
|
||||
connection.build_forward_port_command(local_port, host, remote_port)
|
||||
}
|
||||
|
||||
pub fn upload_directory(
|
||||
&self,
|
||||
src_path: PathBuf,
|
||||
@@ -1116,12 +1104,6 @@ pub(crate) trait RemoteConnection: Send + Sync {
|
||||
working_dir: Option<String>,
|
||||
port_forward: Option<(u16, String, u16)>,
|
||||
) -> Result<CommandTemplate>;
|
||||
fn build_forward_port_command(
|
||||
&self,
|
||||
local_port: u16,
|
||||
remote: String,
|
||||
remote_port: u16,
|
||||
) -> Result<CommandTemplate>;
|
||||
fn connection_options(&self) -> RemoteConnectionOptions;
|
||||
fn path_style(&self) -> PathStyle;
|
||||
fn shell(&self) -> String;
|
||||
@@ -1551,23 +1533,6 @@ mod fake {
|
||||
})
|
||||
}
|
||||
|
||||
fn build_forward_port_command(
|
||||
&self,
|
||||
local_port: u16,
|
||||
host: String,
|
||||
remote_port: u16,
|
||||
) -> anyhow::Result<CommandTemplate> {
|
||||
Ok(CommandTemplate {
|
||||
program: "ssh".into(),
|
||||
args: vec![
|
||||
"-N".into(),
|
||||
"-L".into(),
|
||||
format!("{local_port}:{host}:{remote_port}"),
|
||||
],
|
||||
env: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn upload_directory(
|
||||
&self,
|
||||
_src_path: PathBuf,
|
||||
|
||||
@@ -70,14 +70,13 @@ impl From<settings::SshConnection> for SshConnectionOptions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SshSocket {
|
||||
connection_options: SshConnectionOptions,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
socket_path: PathBuf,
|
||||
socket_path: std::path::PathBuf,
|
||||
envs: HashMap<String, String>,
|
||||
#[cfg(target_os = "windows")]
|
||||
password: askpass::EncryptedPassword,
|
||||
_proxy: askpass::PasswordProxy,
|
||||
}
|
||||
|
||||
macro_rules! shell_script {
|
||||
@@ -146,23 +145,6 @@ impl RemoteConnection for SshRemoteConnection {
|
||||
)
|
||||
}
|
||||
|
||||
fn build_forward_port_command(
|
||||
&self,
|
||||
local_port: u16,
|
||||
host: String,
|
||||
remote_port: u16,
|
||||
) -> Result<CommandTemplate> {
|
||||
Ok(CommandTemplate {
|
||||
program: "ssh".into(),
|
||||
args: vec![
|
||||
"-N".into(),
|
||||
"-L".into(),
|
||||
format!("{local_port}:{host}:{remote_port}"),
|
||||
],
|
||||
env: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn upload_directory(
|
||||
&self,
|
||||
src_path: PathBuf,
|
||||
@@ -360,16 +342,17 @@ impl SshRemoteConnection {
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let socket = SshSocket::new(connection_options, socket_path)?;
|
||||
let socket = SshSocket::new(connection_options, socket_path).await?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let socket = SshSocket::new(
|
||||
connection_options,
|
||||
&temp_dir,
|
||||
askpass
|
||||
.get_password()
|
||||
.or_else(|| askpass::EncryptedPassword::try_from("").ok())
|
||||
.context("Failed to fetch askpass password")?,
|
||||
)?;
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await?;
|
||||
drop(askpass);
|
||||
|
||||
let ssh_platform = socket.platform().await?;
|
||||
@@ -676,7 +659,7 @@ impl SshRemoteConnection {
|
||||
|
||||
impl SshSocket {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn new(options: SshConnectionOptions, socket_path: PathBuf) -> Result<Self> {
|
||||
async fn new(options: SshConnectionOptions, socket_path: PathBuf) -> Result<Self> {
|
||||
Ok(Self {
|
||||
connection_options: options,
|
||||
envs: HashMap::default(),
|
||||
@@ -685,21 +668,26 @@ impl SshSocket {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn new(
|
||||
async fn new(
|
||||
options: SshConnectionOptions,
|
||||
temp_dir: &TempDir,
|
||||
password: askpass::EncryptedPassword,
|
||||
executor: gpui::BackgroundExecutor,
|
||||
) -> Result<Self> {
|
||||
let askpass_script = temp_dir.path().join("askpass.bat");
|
||||
std::fs::write(&askpass_script, "@ECHO OFF\necho %ZED_SSH_ASKPASS%")?;
|
||||
let mut envs = HashMap::default();
|
||||
let get_password =
|
||||
move |_| Task::ready(std::ops::ControlFlow::Continue(Ok(password.clone())));
|
||||
|
||||
let _proxy = askpass::PasswordProxy::new(get_password, executor).await?;
|
||||
envs.insert("SSH_ASKPASS_REQUIRE".into(), "force".into());
|
||||
envs.insert("SSH_ASKPASS".into(), askpass_script.display().to_string());
|
||||
envs.insert(
|
||||
"SSH_ASKPASS".into(),
|
||||
_proxy.script_path().as_ref().display().to_string(),
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
connection_options: options,
|
||||
envs,
|
||||
password,
|
||||
_proxy,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -753,14 +741,12 @@ impl SshSocket {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn ssh_options<'a>(&self, command: &'a mut process::Command) -> &'a mut process::Command {
|
||||
use askpass::ProcessExt;
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.args(self.connection_options.additional_args())
|
||||
.envs(self.envs.clone())
|
||||
.encrypted_env("ZED_SSH_ASKPASS", self.password.clone())
|
||||
}
|
||||
|
||||
// On Windows, we need to use `SSH_ASKPASS` to provide the password to ssh.
|
||||
|
||||
@@ -433,15 +433,6 @@ impl RemoteConnection for WslRemoteConnection {
|
||||
})
|
||||
}
|
||||
|
||||
fn build_forward_port_command(
|
||||
&self,
|
||||
_: u16,
|
||||
_: String,
|
||||
_: u16,
|
||||
) -> anyhow::Result<CommandTemplate> {
|
||||
Err(anyhow!("WSL shares a network interface with the host"))
|
||||
}
|
||||
|
||||
fn connection_options(&self) -> RemoteConnectionOptions {
|
||||
RemoteConnectionOptions::Wsl(self.connection_options.clone())
|
||||
}
|
||||
|
||||
@@ -123,7 +123,6 @@ impl HeadlessProject {
|
||||
toolchain_store.read(cx).as_language_toolchain_store(),
|
||||
worktree_store.clone(),
|
||||
breakpoint_store.clone(),
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
dap_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
|
||||
|
||||
@@ -397,7 +397,19 @@ pub enum SteppingGranularity {
|
||||
Instruction,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
MergeFrom,
|
||||
PartialEq,
|
||||
Eq,
|
||||
strum::VariantArray,
|
||||
strum::VariantNames,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DockPosition {
|
||||
Left,
|
||||
@@ -571,7 +583,18 @@ pub struct FileFinderSettingsContent {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, MergeFrom,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
MergeFrom,
|
||||
strum::VariantArray,
|
||||
strum::VariantNames,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum FileFinderWidthContent {
|
||||
@@ -733,7 +756,19 @@ pub enum DockSide {
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema, MergeFrom)]
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
JsonSchema,
|
||||
MergeFrom,
|
||||
strum::VariantArray,
|
||||
strum::VariantNames,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ShowIndentGuides {
|
||||
Always,
|
||||
|
||||
@@ -541,7 +541,18 @@ pub struct ProjectPanelSettingsContent {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq,
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
MergeFrom,
|
||||
PartialEq,
|
||||
Eq,
|
||||
strum::VariantArray,
|
||||
strum::VariantNames,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProjectPanelEntrySpacing {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -278,6 +278,9 @@ fn init_renderers(cx: &mut App) {
|
||||
.add_renderer::<settings::TerminalDockPosition>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<settings::DockPosition>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<settings::GitGutterSetting>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
@@ -309,6 +312,11 @@ fn init_renderers(cx: &mut App) {
|
||||
.add_renderer::<settings::ShowCloseButton>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<settings::ProjectPanelEntrySpacing>(
|
||||
|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
},
|
||||
)
|
||||
.add_renderer::<settings::RewrapBehavior>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
@@ -323,6 +331,12 @@ fn init_renderers(cx: &mut App) {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
},
|
||||
)
|
||||
.add_renderer::<settings::FileFinderWidthContent>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<settings::ShowDiagnostics>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<settings::WordsCompletionMode>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
|
||||
@@ -624,14 +624,12 @@ impl ProjectItemRegistry {
|
||||
let project_path = project_path.clone();
|
||||
let is_file = project
|
||||
.read(cx)
|
||||
.entry_for_path(dbg!(&project_path), cx)
|
||||
.entry_for_path(&project_path, cx)
|
||||
.is_some_and(|entry| entry.is_file());
|
||||
let entry_abs_path = project.read(cx).absolute_path(&project_path, cx);
|
||||
dbg!(&entry_abs_path);
|
||||
let is_local = project.read(cx).is_local();
|
||||
let project_item =
|
||||
<T::Item as project::ProjectItem>::try_open(project, &project_path, cx)?;
|
||||
dbg!();
|
||||
let project = project.clone();
|
||||
Some(window.spawn(cx, async move |cx| {
|
||||
match project_item.await.with_context(|| {
|
||||
@@ -695,7 +693,6 @@ impl ProjectItemRegistry {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
|
||||
dbg!(self.build_project_item_for_path_fns.len());
|
||||
let Some(open_project_item) = self
|
||||
.build_project_item_for_path_fns
|
||||
.iter()
|
||||
|
||||
@@ -1350,7 +1350,7 @@ mod tests {
|
||||
|
||||
let (res_tx, res_rx) = oneshot::channel();
|
||||
req_tx.unbounded_send((req, res_tx)).unwrap();
|
||||
serde_json::to_string(&res_rx.await?).unwrap()
|
||||
serde_json::to_string(&res_rx.await.unwrap()).unwrap()
|
||||
}
|
||||
_ => {
|
||||
panic!("Unexpected path: {}", uri)
|
||||
|
||||
Reference in New Issue
Block a user