From 92b93850bb19f8d0f08aa4fbb15a922cf9a552ef Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Sun, 14 Jul 2024 15:35:37 -0400 Subject: [PATCH 01/22] Add support for DAP to use std for communication --- crates/dap/src/client.rs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index d8ae216d06103..caaa11b126f67 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -2,6 +2,7 @@ use crate::transport::{self, Events, Payload, Request, Transport}; use anyhow::{anyhow, Context, Result}; use dap_types::{ + events::Process, requests::{ Attach, ConfigurationDone, Continue, Initialize, Launch, Next, Pause, SetBreakpoints, StepBack, StepIn, StepOut, @@ -130,14 +131,31 @@ impl DebugAdapterClient { } async fn create_stdio_client( - _id: DebugAdapterClientId, - _config: DebugAdapterConfig, - _command: &str, - _args: Vec<&str>, - _project_path: PathBuf, - _cx: &mut AsyncAppContext, + id: DebugAdapterClientId, + config: DebugAdapterConfig, + command: &str, + args: Vec<&str>, + project_path: PathBuf, + cx: &mut AsyncAppContext, ) -> Result { - todo!("not implemented") + let mut command = process::Command::new(command); + command + .current_dir(project_path) + .args(args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .kill_on_drop(true); + + let mut process = command + .spawn() + .with_context(|| "failed to spawn command.")?; + + let stdin = Box::new(process.stdin.take().unwrap()); + let stdout = Box::new(BufReader::new(process.stdout.take().unwrap())); + let stderr = Box::new(BufReader::new(process.stderr.take().unwrap())); + + Self::handle_transport(id, config, stdout, stdin, Some(stderr), Some(process), cx) } pub fn handle_transport( From 3e1aa65b20aecdfc84b9be21944fe1305d185422 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Sun, 14 Jul 2024 19:16:08 -0400 Subject: [PATCH 02/22] Add more descriptive error logs for DAP --- crates/dap/src/client.rs | 25 +++++++++++++++++++++---- crates/dap/src/transport.rs | 4 ++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index caaa11b126f67..c5d223443008b 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -2,7 +2,6 @@ use crate::transport::{self, Events, Payload, Request, Transport}; use anyhow::{anyhow, Context, Result}; use dap_types::{ - events::Process, requests::{ Attach, ConfigurationDone, Continue, Initialize, Launch, Next, Pause, SetBreakpoints, StepBack, StepIn, StepOut, @@ -151,9 +150,27 @@ impl DebugAdapterClient { .spawn() .with_context(|| "failed to spawn command.")?; - let stdin = Box::new(process.stdin.take().unwrap()); - let stdout = Box::new(BufReader::new(process.stdout.take().unwrap())); - let stderr = Box::new(BufReader::new(process.stderr.take().unwrap())); + // give the adapter some time to start std + cx.background_executor() + .timer(Duration::from_millis(1000)) + .await; + + let stdin = process + .stdin + .take() + .ok_or_else(|| anyhow!("Failed to open stdin"))?; + let stdout = process + .stdout + .take() + .ok_or_else(|| anyhow!("Failed to open stdout"))?; + let stderr = process + .stderr + .take() + .ok_or_else(|| anyhow!("Failed to open stderr"))?; + + let stdin = Box::new(stdin); + let stdout = Box::new(BufReader::new(stdout)); + let stderr = Box::new(BufReader::new(stderr)); Self::handle_transport(id, config, stdout, stdin, Some(stderr), Some(process), cx) } diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs index 2f314ab4d3a99..24fa67bfbcd9b 100644 --- a/crates/dap/src/transport.rs +++ b/crates/dap/src/transport.rs @@ -106,7 +106,7 @@ impl Transport { loop { buffer.truncate(0); if reader.read_line(buffer).await? == 0 { - return Err(anyhow!("stream closed")); + return Err(anyhow!("reader stream closed")); }; if buffer == "\r\n" { @@ -140,7 +140,7 @@ impl Transport { ) -> Result<()> { buffer.truncate(0); if err.read_line(buffer).await? == 0 { - return Err(anyhow!("stream closed")); + return Err(anyhow!("error stream closed")); }; Ok(()) From 73ef7718750425c50b6390e3610b345e6b024002 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Sun, 14 Jul 2024 22:36:54 -0400 Subject: [PATCH 03/22] Implement handler for continued event --- crates/debugger_ui/src/debugger_panel.rs | 26 ++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 203f58b159618..b1d03b7ec813d 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -1,10 +1,10 @@ use anyhow::Result; -use dap::client::{DebugAdapterClientId, ThreadState, ThreadStatus}; +use dap::client::{self, DebugAdapterClientId, ThreadState, ThreadStatus}; use dap::requests::{Disconnect, Scopes, StackTrace, Variables}; use dap::{client::DebugAdapterClient, transport::Events}; use dap::{ - DisconnectArguments, Scope, ScopesArguments, StackFrame, StackTraceArguments, StoppedEvent, - TerminatedEvent, ThreadEvent, ThreadEventReason, Variable, VariablesArguments, + ContinuedEvent, DisconnectArguments, Scope, ScopesArguments, StackFrame, StackTraceArguments, + StoppedEvent, TerminatedEvent, ThreadEvent, ThreadEventReason, Variable, VariablesArguments, }; use editor::Editor; use futures::future::try_join_all; @@ -134,7 +134,7 @@ impl DebugPanel { .detach_and_log_err(cx); } Events::Stopped(event) => Self::handle_stopped_event(this, client_id, event, cx), - Events::Continued(_) => {} + Events::Continued(event) => Self::handle_continued_event(this, client_id, event, cx), Events::Exited(_) => {} Events::Terminated(event) => Self::handle_terminated_event(this, client_id, event, cx), Events::Thread(event) => Self::handle_thread_event(this, client_id, event, cx), @@ -272,6 +272,24 @@ impl DebugPanel { }) } + fn handle_continued_event( + this: &mut Self, + client_id: &DebugAdapterClientId, + event: &ContinuedEvent, + cx: &mut ViewContext, + ) { + let all_threads = event.all_threads_continued.unwrap_or(false); + let client = this.debug_client_by_id(client_id.clone(), cx); + + if all_threads { + for thread in client.thread_states().values_mut() { + thread.status = ThreadStatus::Running; + } + } else { + client.update_thread_state_status(event.thread_id, ThreadStatus::Running); + } + } + fn handle_stopped_event( this: &mut Self, client_id: &DebugAdapterClientId, From 8d7ec33183da53f2aae24a6259328cfc209092f7 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Mon, 15 Jul 2024 11:43:10 -0400 Subject: [PATCH 04/22] Add PR feedback to handle_continued_event function --- crates/debugger_ui/src/debugger_panel.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index b1d03b7ec813d..31e9e3f4ed7f7 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -279,7 +279,7 @@ impl DebugPanel { cx: &mut ViewContext, ) { let all_threads = event.all_threads_continued.unwrap_or(false); - let client = this.debug_client_by_id(client_id.clone(), cx); + let client = this.debug_client_by_id(*client_id, cx); if all_threads { for thread in client.thread_states().values_mut() { @@ -288,6 +288,8 @@ impl DebugPanel { } else { client.update_thread_state_status(event.thread_id, ThreadStatus::Running); } + + cx.notify(); } fn handle_stopped_event( From 7c9771b8d72dea94f2d1665196a276d5136e24c0 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 18 Jul 2024 14:20:01 -0400 Subject: [PATCH 05/22] Updated debugger --- crates/dap/src/transport.rs | 2 ++ crates/debugger_ui/src/debugger_panel.rs | 16 ++++++++++++++-- crates/project/src/project.rs | 8 ++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs index 24fa67bfbcd9b..49a18f6117fd9 100644 --- a/crates/dap/src/transport.rs +++ b/crates/dap/src/transport.rs @@ -45,6 +45,8 @@ pub enum Events { ProgressEnd(ProgressEndEvent), Invalidated(InvalidatedEvent), Memory(MemoryEvent), + #[serde(untagged)] + Other(HashMap) } #[derive(Debug, Deserialize, Serialize)] diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 31e9e3f4ed7f7..b4024f1fa7e31 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -129,7 +129,18 @@ impl DebugPanel { task.await?; - client.configuration_done().await + // client.is_server_ready = true; + client.configuration_done().await?; + + let request_args = client.config().request_args.map(|a| a.args); + + // send correct request based on adapter config + match client.config().request { + DebugRequestType::Launch => client.launch(request_args).await?, + DebugRequestType::Attach => client.attach(request_args).await?, + }; + + anyhow::Ok(()) }) .detach_and_log_err(cx); } @@ -148,7 +159,8 @@ impl DebugPanel { Events::ProgressEnd(_) => {} Events::ProgressStart(_) => {} Events::ProgressUpdate(_) => {} - Events::Invalidated(_) => {} + Events::Invalidated(_) => {}, + Events::Other(_) => {}, } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4d3638409387f..1c3d90d8b72b9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1166,10 +1166,10 @@ impl Project { client.initialize().await.log_err()?; // send correct request based on adapter config - match adapter_config.request { - DebugRequestType::Launch => client.launch(request_args).await.log_err()?, - DebugRequestType::Attach => client.attach(request_args).await.log_err()?, - }; + // match adapter_config.request { + // DebugRequestType::Launch => client.launch(request_args).await.log_err()?, + // DebugRequestType::Attach => client.attach(request_args).await.log_err()?, + // }; let client = Arc::new(client); From 15d518639902b395a8fed214ba98aad142588565 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Tue, 23 Jul 2024 01:56:54 -0400 Subject: [PATCH 06/22] Fix race condition when using late case debug adapters The main thing this commit does is fix race conditions between a dap client's event handler and sending a launch/attach request. Some adapters would only respond to a starting request after the client handled an init event, which could never happen because the client used to start it's handler after it sent a launch request. This commit also ignores undefined errors instead of crashing. This allows the client to work with adapters that send custom events. Finially, I added some more descriptive error messages and change client's starting request seq from 0 to 1 to be more in line with the specs. --- crates/dap/src/client.rs | 2 +- crates/dap/src/transport.rs | 32 ++++++++++++++++++------ crates/debugger_ui/src/debugger_panel.rs | 6 ++--- crates/editor/src/editor.rs | 6 ++--- crates/project/src/project.rs | 13 +++++----- 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index 1fd4ada7cf634..741f840f9fe02 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -216,7 +216,7 @@ impl DebugAdapterClient { _process: process, capabilities: None, server_tx: server_tx.clone(), - request_count: AtomicU64::new(0), + request_count: AtomicU64::new(1), thread_states: Arc::new(Mutex::new(HashMap::new())), }; diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs index 3f2c58291c230..cff57c496fb01 100644 --- a/crates/dap/src/transport.rs +++ b/crates/dap/src/transport.rs @@ -46,7 +46,7 @@ pub enum Events { Invalidated(InvalidatedEvent), Memory(MemoryEvent), #[serde(untagged)] - Other(HashMap) + Other(HashMap), } #[derive(Debug, Deserialize, Serialize)] @@ -120,7 +120,13 @@ impl Transport { let mut content_length = None; loop { buffer.truncate(0); - if reader.read_line(buffer).await? == 0 { + + if reader + .read_line(buffer) + .await + .with_context(|| "reading a message from server")? + == 0 + { return Err(anyhow!("reader stream closed")); }; @@ -141,10 +147,12 @@ impl Transport { let content_length = content_length.context("missing content length")?; let mut content = vec![0; content_length]; - reader.read_exact(&mut content).await?; - let msg = std::str::from_utf8(&content).context("invalid utf8 from server")?; + reader + .read_exact(&mut content) + .await + .with_context(|| "reading after a loop")?; - dbg!(msg); + let msg = std::str::from_utf8(&content).context("invalid utf8 from server")?; Ok(serde_json::from_str::(msg)?) } @@ -187,7 +195,6 @@ impl Transport { server_stdin.write_all(request.as_bytes()).await?; server_stdin.flush().await?; - Ok(()) } @@ -212,11 +219,19 @@ impl Transport { }; if let Some(mut tx) = pending_request { - tx.send(Self::process_response(res)).await?; + if !tx.is_closed() { + tx.send(Self::process_response(res)).await?; + } else { + log::warn!( + "Response stream associated with request seq: {} is closed", + &res.request_seq + ); // TODO: Fix this case so it never happens + } } else { client_tx.send(Payload::Response(res)).await?; }; } + Payload::Request(_) => { client_tx.send(payload).await?; } @@ -239,7 +254,8 @@ impl Transport { &client_tx, Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await?, ) - .await?; + .await + .context("Process server message failed in transport::receive")?; } } diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index d8290912c2e33..0c80576c67310 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use dap::client::{self, DebugAdapterClientId, ThreadState, ThreadStatus}; +use dap::client::{DebugAdapterClientId, ThreadState, ThreadStatus}; use dap::requests::{Disconnect, Scopes, StackTrace, Variables}; use dap::{client::DebugAdapterClient, transport::Events}; use dap::{ @@ -132,8 +132,8 @@ impl DebugPanel { Events::ProgressEnd(_) => {} Events::ProgressStart(_) => {} Events::ProgressUpdate(_) => {} - Events::Invalidated(_) => {}, - Events::Other(_) => {}, + Events::Invalidated(_) => {} + Events::Other(_) => {} } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a1427cc8ec6cd..538736a256ffd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -450,7 +450,7 @@ struct ResolvedTasks { #[derive(Clone, Debug)] struct Breakpoint { row: MultiBufferRow, - line: BufferRow, + _line: BufferRow, } #[derive(Copy, Clone, Debug)] @@ -5942,11 +5942,11 @@ impl Editor { key, Breakpoint { row: MultiBufferRow(row), - line: row, + _line: row, }, ); } - + project.update(cx, |project, cx| { project.update_breakpoint(buffer, row + 1, cx); }); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 1c3d90d8b72b9..9206c308b695a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1163,13 +1163,7 @@ impl Project { .log_err()?; // initialize request - client.initialize().await.log_err()?; - - // send correct request based on adapter config - // match adapter_config.request { - // DebugRequestType::Launch => client.launch(request_args).await.log_err()?, - // DebugRequestType::Attach => client.attach(request_args).await.log_err()?, - // }; + let _capabilities = client.initialize().await.log_err()?; let client = Arc::new(client); @@ -1208,6 +1202,11 @@ impl Project { }) .log_err(); + match adapter_config.request { + DebugRequestType::Launch => client.launch(request_args).await.log_err()?, + DebugRequestType::Attach => client.attach(request_args).await.log_err()?, + }; + Some(client) }); From 0b8c4ded9ee6afdd9fbe262aae87150dc0dfd9bf Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Tue, 23 Jul 2024 02:12:09 -0400 Subject: [PATCH 07/22] Get clippy to run successfully --- crates/debugger_ui/src/debugger_panel.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 0c80576c67310..2afe749548234 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -313,10 +313,9 @@ impl DebugPanel { return; }; - let client_id = client_id.clone(); - let client = this.debug_client_by_id(client_id.clone(), cx); + let client_id = *client_id; + let client = this.debug_client_by_id(client_id, cx); - let client_id = client_id.clone(); cx.spawn({ let event = event.clone(); |this, mut cx| async move { @@ -333,11 +332,11 @@ impl DebugPanel { stack_trace_response.stack_frames.first().unwrap().clone(); let mut scope_tasks = Vec::new(); for stack_frame in stack_trace_response.stack_frames.clone().into_iter() { - let frame_id = stack_frame.id.clone(); + let frame_id = stack_frame.id; let client = client.clone(); scope_tasks.push(async move { anyhow::Ok(( - frame_id.clone(), + frame_id, client .request::(ScopesArguments { frame_id }) .await?, @@ -353,11 +352,11 @@ impl DebugPanel { scopes.insert(thread_id, response.scopes.clone()); for scope in response.scopes { - let scope_reference = scope.variables_reference.clone(); + let scope_reference = scope.variables_reference; let client = client.clone(); variable_tasks.push(async move { anyhow::Ok(( - scope_reference.clone(), + scope_reference, client .request::(VariablesArguments { variables_reference: scope_reference, @@ -383,7 +382,7 @@ impl DebugPanel { .or_insert(ThreadState::default()); thread_state.current_stack_frame_id = Some(current_stack_frame.clone().id); - thread_state.stack_frames = stack_trace_response.stack_frames.clone(); + thread_state.stack_frames = stack_trace_response.stack_frames; thread_state.scopes = scopes; thread_state.variables = variables; thread_state.status = ThreadStatus::Stopped; @@ -453,7 +452,7 @@ impl DebugPanel { .thread_states() .insert(thread_id, ThreadState::default()); } else { - client.update_thread_state_status(thread_id.clone(), ThreadStatus::Ended); + client.update_thread_state_status(thread_id, ThreadStatus::Ended); // TODO: we want to figure out for witch clients/threads we should remove the highlights cx.spawn({ From 780bfafedf7a1f775602a6d4642d9d4d4ac18f57 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Tue, 23 Jul 2024 02:56:20 -0400 Subject: [PATCH 08/22] Add some function docs to dap client --- crates/dap/src/client.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index 741f840f9fe02..e8607c317b8bf 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -72,6 +72,15 @@ pub struct DebugAdapterClient { } impl DebugAdapterClient { + /// Creates & returns a new debug adapter client + /// + /// # Parameters + /// - `id`: The id that [`Project`](project::Project) uses to keep track of specific clients + /// - `config`: The adapter specific configurations from debugger task that is starting + /// - `command`: The command that starts the debugger + /// - `args`: Arguments of the command that starts the debugger + /// - `project_path`: The absolute path of the project that is being debugged + /// - `cx`: The context that the new client belongs too pub async fn new( id: DebugAdapterClientId, config: DebugAdapterConfig, @@ -90,6 +99,17 @@ impl DebugAdapterClient { } } + /// Creates a debug client that connects to an adapter through tcp + /// + /// TCP clients don't have an error communication stream with an adapter + /// + /// # Parameters + /// - `id`: The id that [`Project`](project::Project) uses to keep track of specific clients + /// - `config`: The adapter specific configurations from debugger task that is starting + /// - `command`: The command that starts the debugger + /// - `args`: Arguments of the command that starts the debugger + /// - `project_path`: The absolute path of the project that is being debugged + /// - `cx`: The context that the new client belongs too async fn create_tcp_client( id: DebugAdapterClientId, config: DebugAdapterConfig, @@ -143,6 +163,7 @@ impl DebugAdapterClient { ) } + /// Get an open port to use with the tcp client when not supplied by debug config async fn get_port() -> Option { Some( TcpListener::bind(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 0)) @@ -154,6 +175,15 @@ impl DebugAdapterClient { ) } + /// Creates a debug client that connects to an adapter through std input/output + /// + /// # Parameters + /// - `id`: The id that [`Project`](project::Project) uses to keep track of specific clients + /// - `config`: The adapter specific configurations from debugger task that is starting + /// - `command`: The command that starts the debugger + /// - `args`: Arguments of the command that starts the debugger + /// - `project_path`: The absolute path of the project that is being debugged + /// - `cx`: The context that the new client belongs too async fn create_stdio_client( id: DebugAdapterClientId, config: DebugAdapterConfig, @@ -226,6 +256,14 @@ impl DebugAdapterClient { Ok(client) } + /// Set's up a client's event handler. + /// + /// This function should only be called once or else errors will arise + /// # Parameters + /// `client`: A pointer to the client to pass the event handler too + /// `event_handler`: The function that is called to handle events + /// should be DebugPanel::handle_debug_client_events + /// `cx`: The context that this task will run in pub async fn handle_events( client: Arc, mut event_handler: F, @@ -258,6 +296,8 @@ impl DebugAdapterClient { } } + /// Send a request to an adapter and get a response back + /// Note: This function will block until a response is sent back from the adapter pub async fn request( &self, arguments: R::Arguments, From c7f7d18681a11d62f778a77b727fc865f6f81aea Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 25 Jul 2024 00:06:51 -0400 Subject: [PATCH 09/22] Fix debugger's highlight when stepping through code Merging with the main zed branch removed a function that the debugger panel relied on. I added the function back and changed it to work with the updated internals of zed. --- crates/debugger_ui/src/debugger_panel.rs | 4 +-- crates/project/src/project.rs | 31 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 14734c88a0f2e..174b1bba67f53 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -172,7 +172,7 @@ impl DebugPanel { let task = workspace.update(&mut cx, |workspace, cx| { let project_path = workspace.project().read_with(cx, |project, cx| { - project.project_path_for_full_path(&Path::new(&path), cx) + project.project_path_for_absolute_path(&Path::new(&path), cx) }); if let Some(project_path) = project_path { @@ -255,7 +255,7 @@ impl DebugPanel { let task = workspace.update(&mut cx, |workspace, cx| { let project_path = workspace.project().read_with(cx, |project, cx| { - project.project_path_for_full_path(&Path::new(&path), cx) + project.project_path_for_absolute_path(&Path::new(&path), cx) }); if let Some(project_path) = project_path { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2f2f4878f3b59..ca4a5fc12c0ac 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8609,6 +8609,37 @@ impl Project { }) } + pub fn project_path_for_absolute_path( + &self, + abs_path: &Path, + cx: &AppContext, + ) -> Option { + self.find_local_worktree(abs_path, cx) + .map(|(worktree, relative_path)| ProjectPath { + worktree_id: worktree.read(cx).id(), + path: relative_path.into(), + }) + } + + pub fn find_local_worktree( + &self, + abs_path: &Path, + cx: &AppContext, + ) -> Option<(Model, PathBuf)> { + let trees = self.worktrees(cx); + + for tree in trees { + if let Some(relative_path) = tree + .read(cx) + .as_local() + .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok()) + { + return Some((tree.clone(), relative_path.into())); + } + } + None + } + pub fn get_workspace_root( &self, project_path: &ProjectPath, From 7f1bd3b1d92b35846473ae016b8d66e217f6e67f Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 25 Jul 2024 00:20:29 -0400 Subject: [PATCH 10/22] Get clippy to pass & add an error log instead of using dbg!() --- crates/dap/src/client.rs | 5 +++-- crates/debugger_ui/src/debugger_panel.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index 29c880e241edd..95751a46d351a 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -130,6 +130,7 @@ impl DebugAdapterClient { /// - `args`: Arguments of the command that starts the debugger /// - `project_path`: The absolute path of the project that is being debugged /// - `cx`: The context that the new client belongs too + #[allow(clippy::too_many_arguments)] async fn create_tcp_client( id: DebugAdapterClientId, config: DebugAdapterConfig, @@ -323,8 +324,8 @@ impl DebugAdapterClient { while let Ok(payload) = client_rx.recv().await { cx.update(|cx| match payload { Payload::Event(event) => event_handler(*event, cx), - e => { - dbg!(&e); + err => { + log::error!("Invalid Event: {:#?}", err); } })?; } diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 174b1bba67f53..3cb8b84398afc 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -325,7 +325,7 @@ impl DebugPanel { return; }; - let client_id = client.id().clone(); + let client_id = client.id(); cx.spawn({ let event = event.clone(); |this, mut cx| async move { From 0975ca844cf2498e15bd4b70ee36ab5b927beb67 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 25 Jul 2024 01:13:05 -0400 Subject: [PATCH 11/22] Get sequence id to be incremented after getting respond and event --- crates/dap/src/client.rs | 14 +++++++++----- crates/debugger_ui/src/debugger_panel.rs | 3 +++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index 95751a46d351a..57d664b3f259c 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -62,7 +62,7 @@ pub struct DebugAdapterClient { id: DebugAdapterClientId, _process: Option, server_tx: Sender, - request_count: AtomicU64, + sequence_count: AtomicU64, capabilities: Arc>>, config: DebugAdapterConfig, thread_states: Arc>>, // thread_id -> thread_state @@ -286,7 +286,7 @@ impl DebugAdapterClient { config, server_tx, _process: process, - request_count: AtomicU64::new(1), + sequence_count: AtomicU64::new(1), capabilities: Default::default(), thread_states: Arc::new(Mutex::new(HashMap::new())), }; @@ -357,7 +357,7 @@ impl DebugAdapterClient { let request = Request { back_ch: Some(callback_tx), - seq: self.next_request_id(), + seq: self.next_sequence_id(), command: R::COMMAND.to_string(), arguments: Some(serialized_arguments), }; @@ -365,6 +365,7 @@ impl DebugAdapterClient { self.server_tx.send(Payload::Request(request)).await?; let response = callback_rx.recv().await??; + let _ = self.next_sequence_id(); match response.success { true => Ok(serde_json::from_value(response.body.unwrap_or_default())?), @@ -388,8 +389,11 @@ impl DebugAdapterClient { self.capabilities.lock().clone().unwrap_or_default() } - pub fn next_request_id(&self) -> u64 { - self.request_count.fetch_add(1, Ordering::Relaxed) + /// Get the next sequence id to be used in a request + /// # Side Effect + /// This function also increment's client's sequence count by one + pub fn next_sequence_id(&self) -> u64 { + self.sequence_count.fetch_add(1, Ordering::Relaxed) } pub fn update_thread_state_status(&self, thread_id: u64, status: ThreadStatus) { diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 3cb8b84398afc..1e6218c8e126e 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -133,6 +133,9 @@ impl DebugPanel { event: &Events, cx: &mut ViewContext, ) { + // Increment the sequence id because an event is being processed + let _ = client.next_sequence_id(); + match event { Events::Initialized(event) => Self::handle_initialized_event(client, event, cx), Events::Stopped(event) => Self::handle_stopped_event(client, event, cx), From 916150a8e0e6787211ac1348b276ba0b6b515b05 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 25 Jul 2024 11:00:28 -0400 Subject: [PATCH 12/22] Get breakpoints to use anchors instead Warning: Project is not being sent breakpoints right now --- crates/editor/src/editor.rs | 44 +++++++++++++++++++++++++----------- crates/editor/src/element.rs | 16 ++++++++----- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9ca552937bcb8..e51865ab93f78 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -452,8 +452,7 @@ struct ResolvedTasks { #[derive(Clone, Debug)] struct Breakpoint { - row: MultiBufferRow, - _line: BufferRow, + position: Anchor, } #[derive(Copy, Clone, Debug)] @@ -575,7 +574,7 @@ pub struct Editor { expect_bounds_change: Option>, tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>, tasks_update_task: Option>, - breakpoints: BTreeMap<(BufferId, BufferRow), Breakpoint>, + breakpoints: BTreeMap<(BufferId, ExcerptId), Breakpoint>, previous_search_ranges: Option]>>, file_header_size: u8, breadcrumb_header: Option, @@ -5141,14 +5140,19 @@ impl Editor { } } - fn render_breakpoint(&self, row: DisplayRow, cx: &mut ViewContext) -> IconButton { + fn render_breakpoint( + &self, + position: Anchor, + row: DisplayRow, + cx: &mut ViewContext, + ) -> IconButton { IconButton::new(("breakpoint_indicator", row.0 as usize), ui::IconName::Play) .icon_size(IconSize::XSmall) .size(ui::ButtonSize::None) .icon_color(Color::Error) .on_click(cx.listener(move |editor, _e, cx| { editor.focus(cx); - editor.toggle_breakpoint_at_row(row.0, cx) //TODO handle folded + editor.toggle_breakpoint_at_row(position, cx) //TODO handle folded })) } @@ -5972,10 +5976,21 @@ impl Editor { pub fn toggle_breakpoint(&mut self, _: &ToggleBreakpoint, cx: &mut ViewContext) { let cursor_position: Point = self.selections.newest(cx).head(); - self.toggle_breakpoint_at_row(cursor_position.row, cx); + + let breakpoint_position = self + .snapshot(cx) + .display_snapshot + .buffer_snapshot + .anchor_before(cursor_position); + + self.toggle_breakpoint_at_row(breakpoint_position, cx); } - pub fn toggle_breakpoint_at_row(&mut self, row: u32, cx: &mut ViewContext) { + pub fn toggle_breakpoint_at_row( + &mut self, + breakpoint_position: Anchor, + cx: &mut ViewContext, + ) { let Some(project) = &self.project else { return; }; @@ -5984,21 +5999,24 @@ impl Editor { }; let buffer_id = buffer.read(cx).remote_id(); - let key = (buffer_id, row); + let key = (buffer_id, breakpoint_position.excerpt_id); if self.breakpoints.remove(&key).is_none() { self.breakpoints.insert( key, Breakpoint { - row: MultiBufferRow(row), - _line: row, + position: breakpoint_position, }, ); } + // let row = breakpoint_position + // .to_point(&(self.snapshot(cx).display_snapshot.buffer_snapshot)) + // .row + // + 1; - project.update(cx, |project, cx| { - project.update_breakpoint(buffer, row + 1, cx); - }); + // project.update(cx, |project, cx| { + // project.update_breakpoint(buffer, row, cx); + // }); cx.notify(); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 73ae70abdb13b..498aee55c21b9 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1570,17 +1570,21 @@ impl EditorElement { .breakpoints .iter() .filter_map(|(_, breakpoint)| { - if snapshot.is_line_folded(breakpoint.row) { + let point = breakpoint + .position + .to_display_point(&snapshot.display_snapshot); + + let row = MultiBufferRow { 0: point.row().0 }; + + if snapshot.is_line_folded(row) { return None; } - let display_row = Point::new(breakpoint.row.0, 0) - .to_display_point(snapshot) - .row(); - let button = editor.render_breakpoint(display_row, cx); + + let button = editor.render_breakpoint(breakpoint.position, point.row(), cx); let button = prepaint_gutter_button( button, - display_row, + point.row(), line_height, gutter_dimensions, scroll_pixel_position, From f0a5775204acc79308d4b49c6b4031771dc1d5b5 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 25 Jul 2024 13:33:05 -0400 Subject: [PATCH 13/22] Fix toggle breakpoints having a max of one bp per buffer Breakpoints are now stored in a BTreeMap> I did this becauase we need to constant check if a breakpoint exists in a buffer whenever we toggle one. --- crates/editor/src/editor.rs | 20 ++++++++++---------- crates/editor/src/element.rs | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e51865ab93f78..2f75d770d5130 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -450,7 +450,7 @@ struct ResolvedTasks { position: Anchor, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] struct Breakpoint { position: Anchor, } @@ -574,7 +574,7 @@ pub struct Editor { expect_bounds_change: Option>, tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>, tasks_update_task: Option>, - breakpoints: BTreeMap<(BufferId, ExcerptId), Breakpoint>, + breakpoints: BTreeMap>, previous_search_ranges: Option]>>, file_header_size: u8, breadcrumb_header: Option, @@ -5999,15 +5999,15 @@ impl Editor { }; let buffer_id = buffer.read(cx).remote_id(); - let key = (buffer_id, breakpoint_position.excerpt_id); + // let key = (buffer_id, breakpoint_position); + let breakpoint = Breakpoint { + position: breakpoint_position, + }; - if self.breakpoints.remove(&key).is_none() { - self.breakpoints.insert( - key, - Breakpoint { - position: breakpoint_position, - }, - ); + let breakpoint_set = self.breakpoints.entry(buffer_id).or_default(); + + if !breakpoint_set.remove(&breakpoint) { + breakpoint_set.insert(breakpoint); } // let row = breakpoint_position // .to_point(&(self.snapshot(cx).display_snapshot.buffer_snapshot)) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 498aee55c21b9..7f3b23ce9fb0a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1569,7 +1569,8 @@ impl EditorElement { editor .breakpoints .iter() - .filter_map(|(_, breakpoint)| { + .flat_map(|(_buffer_id, breakpoint_set)| breakpoint_set.iter()) + .filter_map(|breakpoint| { let point = breakpoint .position .to_display_point(&snapshot.display_snapshot); From 4373e479f773fab281e5565e5b88430297e9abee Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Sat, 27 Jul 2024 13:46:11 -0400 Subject: [PATCH 14/22] Get Editor & Project to share breakpoints through RWLock Previously editor would send breakpoints to project whenever a new breakpoint was deleted or added. Now editor shares a shared pointer to a breakpoint datastructure. This behavior is more efficient because it doesn't have to repeatly send breakpoints everytime one is updated. Still have to handle cases when a breakpoint is added/removed during a debugging phase. I also have to figure out how to share the breakpoints pointer only once per project and editor. --- Cargo.lock | 2 + crates/dap/Cargo.toml | 1 + crates/dap/src/client.rs | 5 ++ crates/editor/Cargo.toml | 1 + crates/editor/src/editor.rs | 29 ++++++--- crates/editor/src/element.rs | 1 + crates/project/src/project.rs | 117 +++++++++++++++++++--------------- crates/text/src/text.rs | 2 +- 8 files changed, 97 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d22c2dbf47af..aa960cb70c2f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3234,6 +3234,7 @@ dependencies = [ "futures 0.3.28", "gpui", "log", + "multi_buffer", "parking_lot", "postage", "release_channel", @@ -3593,6 +3594,7 @@ dependencies = [ "collections", "convert_case 0.6.0", "ctor", + "dap", "db", "emojis", "env_logger", diff --git a/crates/dap/Cargo.toml b/crates/dap/Cargo.toml index 462cf97cc972c..107c118cdde9e 100644 --- a/crates/dap/Cargo.toml +++ b/crates/dap/Cargo.toml @@ -14,6 +14,7 @@ async-std = "1.12.0" dap-types = { git = "https://github.com/zed-industries/dap-types" } futures.workspace = true gpui.workspace = true +multi_buffer.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index 57d664b3f259c..707c857f4c527 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -555,3 +555,8 @@ impl DebugAdapterClient { .await } } + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct Breakpoint { + pub position: multi_buffer::Anchor, +} diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 8fb4e3be32559..b0761667509cf 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -37,6 +37,7 @@ clock.workspace = true collections.workspace = true convert_case = "0.6.0" db.workspace = true +dap.workspace = true emojis.workspace = true file_icons.workspace = true futures.workspace = true diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2f75d770d5130..1a2f4689ee59c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -97,6 +97,7 @@ use language::{point_to_lsp, BufferRow, Runnable, RunnableRange}; use linked_editing_ranges::refresh_linked_ranges; use task::{ResolvedTask, TaskTemplate, TaskVariables}; +use dap::client::Breakpoint; use hover_links::{HoverLink, HoveredLinkState, InlayHighlight}; pub use lsp::CompletionContext; use lsp::{ @@ -450,11 +451,6 @@ struct ResolvedTasks { position: Anchor, } -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -struct Breakpoint { - position: Anchor, -} - #[derive(Copy, Clone, Debug)] struct MultiBufferOffset(usize); #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] @@ -574,7 +570,7 @@ pub struct Editor { expect_bounds_change: Option>, tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>, tasks_update_task: Option>, - breakpoints: BTreeMap>, + breakpoints: Arc>>>, previous_search_ranges: Option]>>, file_header_size: u8, breadcrumb_header: Option, @@ -1788,6 +1784,15 @@ impl Editor { None }; + let breakpoints: Arc>>> = Default::default(); + // TODO: Figure out why the code below doesn't work + // if let Some(project) = project.as_ref() { + // dbg!("Setting breakpoints from editor to project"); + // project.update(cx, |project, _cx| { + // project.breakpoints = breakpoints.clone(); + // }) + // } + let mut this = Self { focus_handle, show_cursor_when_unfocused: false, @@ -1890,7 +1895,7 @@ impl Editor { blame_subscription: None, file_header_size, tasks: Default::default(), - breakpoints: Default::default(), + breakpoints, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), cx.subscribe(&buffer, Self::on_buffer_event), @@ -5994,6 +5999,12 @@ impl Editor { let Some(project) = &self.project else { return; }; + + // TODO: Figure out how to only clone breakpoints pointer once per editor + project.update(cx, |project, _cx| { + project.breakpoints = self.breakpoints.clone(); + }); + let Some(buffer) = self.buffer.read(cx).as_singleton() else { return; }; @@ -6004,7 +6015,9 @@ impl Editor { position: breakpoint_position, }; - let breakpoint_set = self.breakpoints.entry(buffer_id).or_default(); + let mut write_guard = self.breakpoints.write(); + + let breakpoint_set = write_guard.entry(buffer_id).or_default(); if !breakpoint_set.remove(&breakpoint) { breakpoint_set.insert(breakpoint); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7f3b23ce9fb0a..6a08b2df07c00 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1568,6 +1568,7 @@ impl EditorElement { self.editor.update(cx, |editor, cx| { editor .breakpoints + .read() .iter() .flat_map(|(_buffer_id, breakpoint_set)| breakpoint_set.iter()) .filter_map(|breakpoint| { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index dfca5defbb7a9..a0f1bf2c54aae 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -25,7 +25,7 @@ use client::{ use clock::ReplicaId; use collections::{btree_map, BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}; use dap::{ - client::{DebugAdapterClient, DebugAdapterClientId}, + client::{Breakpoint, DebugAdapterClient, DebugAdapterClientId}, transport::Events, SourceBreakpoint, }; @@ -119,7 +119,7 @@ use task::{ HideStrategy, RevealStrategy, Shell, TaskContext, TaskTemplate, TaskVariables, VariableName, }; use terminals::Terminals; -use text::{Anchor, BufferId, LineEnding}; +use text::{Anchor, BufferId, LineEnding, Point}; use unicase::UniCase; use util::{ debug_panic, defer, maybe, merge_json_value_into, parse_env_output, post_inc, @@ -182,7 +182,7 @@ pub struct Project { language_servers: HashMap, language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>, debug_adapters: HashMap, - breakpoints: HashMap>, + pub breakpoints: Arc>>>, language_server_statuses: BTreeMap, last_formatting_failure: Option, last_workspace_edits_by_language_server: HashMap, @@ -314,10 +314,6 @@ impl PartialEq for LanguageServerPromptRequest { } } -struct Breakpoint { - row: BufferRow, -} - #[derive(Clone, Debug, PartialEq)] pub enum Event { LanguageServerAdded(LanguageServerId), @@ -1157,11 +1153,19 @@ impl Project { let task = project.update(&mut cx, |project, cx| { let mut tasks = Vec::new(); - for (buffer_id, breakpoints) in project.breakpoints.iter() { - let res = maybe!({ + for (buffer_id, breakpoints) in project.breakpoints.read().iter() { + let buffer = maybe!({ let buffer = project.buffer_for_id(*buffer_id, cx)?; + Some(buffer.read(cx)) + }); + + if buffer.is_none() { + continue; + } + let buffer = buffer.as_ref().unwrap(); - let project_path = buffer.read(cx).project_path(cx)?; + let res = maybe!({ + let project_path = buffer.project_path(cx)?; let worktree = project.worktree_for_id(project_path.worktree_id, cx)?; let path = worktree.read(cx).absolutize(&project_path.path).ok()?; @@ -1175,13 +1179,21 @@ impl Project { Some( breakpoints .iter() - .map(|b| SourceBreakpoint { - line: b.row as u64, - condition: None, - hit_condition: None, - log_message: None, - column: None, - mode: None, + .map(|b| { + dbg!(SourceBreakpoint { + line: (buffer + .summary_for_anchor::( + &b.position.text_anchor, + ) + .row + + 1) + as u64, + condition: None, + hit_condition: None, + log_message: None, + column: None, + mode: None, + }) }) .collect::>(), ), @@ -1204,6 +1216,7 @@ impl Project { debug_task: task::ResolvedTask, cx: &mut ModelContext, ) { + dbg!(&self.breakpoints); let id = DebugAdapterClientId(1); let debug_template = debug_task.original_task(); let cwd = debug_template @@ -1268,41 +1281,41 @@ impl Project { row: BufferRow, cx: &mut ModelContext, ) { - let breakpoints_for_buffer = self - .breakpoints - .entry(buffer.read(cx).remote_id()) - .or_insert(Vec::new()); - - if let Some(ix) = breakpoints_for_buffer - .iter() - .position(|breakpoint| breakpoint.row == row) - { - breakpoints_for_buffer.remove(ix); - } else { - breakpoints_for_buffer.push(Breakpoint { row }); - } - - let clients = self - .debug_adapters - .iter() - .filter_map(|(_, state)| match state { - DebugAdapterClientState::Starting(_) => None, - DebugAdapterClientState::Running(client) => Some(client.clone()), - }) - .collect::>(); - - let mut tasks = Vec::new(); - for client in clients { - tasks.push(self.send_breakpoints(client, cx)); - } - - cx.background_executor() - .spawn(async move { - try_join_all(tasks).await?; - - anyhow::Ok(()) - }) - .detach_and_log_err(cx) + // let breakpoints_for_buffer = self + // .breakpoints + // .entry(buffer.read(cx).remote_id()) + // .or_insert(Vec::new()); + + // if let Some(ix) = breakpoints_for_buffer + // .iter() + // .position(|breakpoint| breakpoint.row == row) + // { + // breakpoints_for_buffer.remove(ix); + // } else { + // breakpoints_for_buffer.push(Breakpoint { row }); + // } + + // let clients = self + // .debug_adapters + // .iter() + // .filter_map(|(_, state)| match state { + // DebugAdapterClientState::Starting(_) => None, + // DebugAdapterClientState::Running(client) => Some(client.clone()), + // }) + // .collect::>(); + + // let mut tasks = Vec::new(); + // for client in clients { + // tasks.push(self.send_breakpoints(client, cx)); + // } + + // cx.background_executor() + // .spawn(async move { + // try_join_all(tasks).await?; + + // anyhow::Ok(()) + // }) + // .detach_and_log_err(cx) } fn shutdown_language_servers( diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index a70d1b769238f..a40c0dedf28ee 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2155,7 +2155,7 @@ impl BufferSnapshot { }) } - fn summary_for_anchor(&self, anchor: &Anchor) -> D + pub fn summary_for_anchor(&self, anchor: &Anchor) -> D where D: TextDimension, { From c39c0a55f5394956cebdf31f0c12a821c457ed5b Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Sat, 27 Jul 2024 15:44:46 -0400 Subject: [PATCH 15/22] Have project share breakpoints pointer with editor Instead of editor sharing with project each time a breakpoint is toggled. An editor will clone project's breakpoints if a project is passed into the editors new function --- crates/editor/src/editor.rs | 32 ++++++++++---------------------- crates/editor/src/element.rs | 7 +++++-- crates/project/src/project.rs | 29 +++++++++++++---------------- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1a2f4689ee59c..b07060f38e875 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -570,7 +570,7 @@ pub struct Editor { expect_bounds_change: Option>, tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>, tasks_update_task: Option>, - breakpoints: Arc>>>, + breakpoints: Option>>>>, previous_search_ranges: Option]>>, file_header_size: u8, breadcrumb_header: Option, @@ -1784,14 +1784,11 @@ impl Editor { None }; - let breakpoints: Arc>>> = Default::default(); - // TODO: Figure out why the code below doesn't work - // if let Some(project) = project.as_ref() { - // dbg!("Setting breakpoints from editor to project"); - // project.update(cx, |project, _cx| { - // project.breakpoints = breakpoints.clone(); - // }) - // } + let breakpoints = if let Some(project) = project.as_ref() { + Some(project.update(cx, |project, _cx| project.breakpoints.clone())) + } else { + None + }; let mut this = Self { focus_handle, @@ -6000,10 +5997,9 @@ impl Editor { return; }; - // TODO: Figure out how to only clone breakpoints pointer once per editor - project.update(cx, |project, _cx| { - project.breakpoints = self.breakpoints.clone(); - }); + let Some(breakpoints) = &self.breakpoints else { + return; + }; let Some(buffer) = self.buffer.read(cx).as_singleton() else { return; @@ -6015,21 +6011,13 @@ impl Editor { position: breakpoint_position, }; - let mut write_guard = self.breakpoints.write(); + let mut write_guard = breakpoints.write(); let breakpoint_set = write_guard.entry(buffer_id).or_default(); if !breakpoint_set.remove(&breakpoint) { breakpoint_set.insert(breakpoint); } - // let row = breakpoint_position - // .to_point(&(self.snapshot(cx).display_snapshot.buffer_snapshot)) - // .row - // + 1; - - // project.update(cx, |project, cx| { - // project.update_breakpoint(buffer, row, cx); - // }); cx.notify(); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6a08b2df07c00..4c5ca7c680251 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1566,8 +1566,11 @@ impl EditorElement { cx: &mut WindowContext, ) -> Vec { self.editor.update(cx, |editor, cx| { - editor - .breakpoints + let Some(breakpoints) = &editor.breakpoints else { + return vec![]; + }; + + breakpoints .read() .iter() .flat_map(|(_buffer_id, breakpoint_set)| breakpoint_set.iter()) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a0f1bf2c54aae..10009b9544025 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1179,21 +1179,19 @@ impl Project { Some( breakpoints .iter() - .map(|b| { - dbg!(SourceBreakpoint { - line: (buffer - .summary_for_anchor::( - &b.position.text_anchor, - ) - .row - + 1) - as u64, - condition: None, - hit_condition: None, - log_message: None, - column: None, - mode: None, - }) + .map(|b| SourceBreakpoint { + line: (buffer + .summary_for_anchor::( + &b.position.text_anchor, + ) + .row + + 1) + as u64, + condition: None, + hit_condition: None, + log_message: None, + column: None, + mode: None, }) .collect::>(), ), @@ -1216,7 +1214,6 @@ impl Project { debug_task: task::ResolvedTask, cx: &mut ModelContext, ) { - dbg!(&self.breakpoints); let id = DebugAdapterClientId(1); let debug_template = debug_task.original_task(); let cwd = debug_template From 08dbf365bbf11020976fc2f123a31a4a9db525c1 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Sat, 27 Jul 2024 17:53:44 -0400 Subject: [PATCH 16/22] Start work on getting breakpoint indicator to show in gutter The end goal of this commit is to allow a user to set a breakpoint by hovering over a line in the editor's gutter. Currently breakpoints only show on line 6 when the mouse is on a gutter. --- crates/editor/src/editor.rs | 7 ++++++ crates/editor/src/element.rs | 43 ++++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b07060f38e875..04dc10c4a859a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -570,7 +570,13 @@ pub struct Editor { expect_bounds_change: Option>, tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>, tasks_update_task: Option>, + /// All the breakpoints that are active within a project + /// Is shared with editor's active project breakpoints: Option>>>>, + /// Allow's a user to create a breakpoint by selecting this indicator + /// It should be None while a user is not hovering over the gutter + /// Otherwise it represents the point that the breakpoint will be shown + pub gutter_breakpoint_indicator: Option, previous_search_ranges: Option]>>, file_header_size: u8, breadcrumb_header: Option, @@ -1893,6 +1899,7 @@ impl Editor { file_header_size, tasks: Default::default(), breakpoints, + gutter_breakpoint_indicator: None, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), cx.subscribe(&buffer, Self::on_buffer_event), diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4c5ca7c680251..cce39ebf92f61 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -702,6 +702,13 @@ impl EditorElement { let gutter_hovered = gutter_hitbox.is_hovered(cx); editor.set_gutter_hovered(gutter_hovered, cx); + if gutter_hovered { + // TODO: Show breakpoint indicator on line closest to mouse + editor.gutter_breakpoint_indicator = Some(Point::new(5, 0)); + } else { + editor.gutter_breakpoint_indicator = None; + } + // Don't trigger hover popover if mouse is hovering over context menu if text_hitbox.is_hovered(cx) { let point_for_position = @@ -1570,7 +1577,7 @@ impl EditorElement { return vec![]; }; - breakpoints + let mut breakpoints_to_render = breakpoints .read() .iter() .flat_map(|(_buffer_id, breakpoint_set)| breakpoint_set.iter()) @@ -1599,7 +1606,39 @@ impl EditorElement { ); Some(button) }) - .collect_vec() + .collect_vec(); + + // See if a user is hovered over a gutter line & if they are display + // a breakpoint indicator that they can click to add a breakpoint + if let Some(gutter_breakpoint) = &editor.gutter_breakpoint_indicator { + let button = IconButton::new( + ( + "gutter_breakpoint_indicator", + gutter_breakpoint.row as usize, + ), + ui::IconName::Play, + ) + .icon_size(IconSize::XSmall) + .size(ui::ButtonSize::None) + .icon_color(Color::Hint); + + let button = prepaint_gutter_button( + button, + gutter_breakpoint + .to_display_point(&snapshot.display_snapshot) + .row(), + line_height, + gutter_dimensions, + scroll_pixel_position, + gutter_hitbox, + rows_with_hunk_bounds, + cx, + ); + + breakpoints_to_render.push(button); + } + + breakpoints_to_render }) } From 4bb8ec96fd69226a0d171393a125ddc3025c26d5 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Sat, 27 Jul 2024 18:10:12 -0400 Subject: [PATCH 17/22] Get gutter breakpoint hint to display at correct point and set breakpoint The breakpoints that are added through clicking on the gutter can't be affect by the toggle breakpoint command. This is a bug --- crates/editor/src/editor.rs | 2 +- crates/editor/src/element.rs | 33 ++++++++++++++++++--------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 04dc10c4a859a..8227ee7b007ce 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -576,7 +576,7 @@ pub struct Editor { /// Allow's a user to create a breakpoint by selecting this indicator /// It should be None while a user is not hovering over the gutter /// Otherwise it represents the point that the breakpoint will be shown - pub gutter_breakpoint_indicator: Option, + pub gutter_breakpoint_indicator: Option, previous_search_ranges: Option]>>, file_header_size: u8, breadcrumb_header: Option, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index cce39ebf92f61..2c5c233d34629 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -704,7 +704,10 @@ impl EditorElement { if gutter_hovered { // TODO: Show breakpoint indicator on line closest to mouse - editor.gutter_breakpoint_indicator = Some(Point::new(5, 0)); + let point_for_position = + position_map.point_for_position(text_hitbox.bounds, event.position); + let position = point_for_position.previous_valid; + editor.gutter_breakpoint_indicator = Some(position); } else { editor.gutter_breakpoint_indicator = None; } @@ -1610,23 +1613,23 @@ impl EditorElement { // See if a user is hovered over a gutter line & if they are display // a breakpoint indicator that they can click to add a breakpoint - if let Some(gutter_breakpoint) = &editor.gutter_breakpoint_indicator { - let button = IconButton::new( - ( - "gutter_breakpoint_indicator", - gutter_breakpoint.row as usize, - ), - ui::IconName::Play, - ) - .icon_size(IconSize::XSmall) - .size(ui::ButtonSize::None) - .icon_color(Color::Hint); + // TODO: We should figure out a way to display this side by side with + // the code action button. They currently overlap + if let Some(gutter_breakpoint) = editor.gutter_breakpoint_indicator { + let gutter_anchor = snapshot.display_point_to_anchor(gutter_breakpoint, Bias::Left); + + let button = IconButton::new("gutter_breakpoint_indicator", ui::IconName::Play) + .icon_size(IconSize::XSmall) + .size(ui::ButtonSize::None) + .icon_color(Color::Hint) + .on_click(cx.listener(move |editor, _e, cx| { + editor.focus(cx); + editor.toggle_breakpoint_at_row(gutter_anchor, cx) //TODO handle folded + })); let button = prepaint_gutter_button( button, - gutter_breakpoint - .to_display_point(&snapshot.display_snapshot) - .row(), + gutter_breakpoint.row(), line_height, gutter_dimensions, scroll_pixel_position, From a54540053494177de31fdded3cd61c2b5837101b Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Sun, 28 Jul 2024 13:51:28 -0400 Subject: [PATCH 18/22] Set up breakpoint toggle to send breakpoints to dap when necessary --- Cargo.lock | 2 + crates/dap/Cargo.toml | 2 + crates/dap/src/client.rs | 18 +++++ crates/editor/src/editor.rs | 9 +++ crates/project/src/project.rs | 119 +++++++++++++++++----------------- 5 files changed, 90 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa960cb70c2f6..4bb714aa3b970 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3233,6 +3233,7 @@ dependencies = [ "dap-types", "futures 0.3.28", "gpui", + "language", "log", "multi_buffer", "parking_lot", @@ -3244,6 +3245,7 @@ dependencies = [ "serde_json_lenient", "smol", "task", + "text", "util", ] diff --git a/crates/dap/Cargo.toml b/crates/dap/Cargo.toml index 107c118cdde9e..c6a3aa0894bca 100644 --- a/crates/dap/Cargo.toml +++ b/crates/dap/Cargo.toml @@ -15,6 +15,7 @@ dap-types = { git = "https://github.com/zed-industries/dap-types" } futures.workspace = true gpui.workspace = true multi_buffer.workspace = true +language.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true @@ -25,4 +26,5 @@ serde_json.workspace = true serde_json_lenient.workspace = true smol.workspace = true task.workspace = true +text.workspace = true util.workspace = true diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index e8bf1f8738611..91b5981b33c5c 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -15,6 +15,7 @@ use dap_types::{ }; use futures::{AsyncBufRead, AsyncReadExt, AsyncWrite}; use gpui::{AppContext, AsyncAppContext}; +use language::Buffer; use parking_lot::{Mutex, MutexGuard}; use serde_json::Value; use smol::{ @@ -35,6 +36,7 @@ use std::{ time::Duration, }; use task::{DebugAdapterConfig, DebugConnectionType, DebugRequestType, TCPHost}; +use text::Point; use util::ResultExt; #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -652,3 +654,19 @@ impl DebugAdapterClient { pub struct Breakpoint { pub position: multi_buffer::Anchor, } + +impl Breakpoint { + pub fn to_source_breakpoint(&self, buffer: &Buffer) -> SourceBreakpoint { + SourceBreakpoint { + line: (buffer + .summary_for_anchor::(&self.position.text_anchor) + .row + + 1) as u64, + condition: None, + hit_condition: None, + log_message: None, + column: None, + mode: None, + } + } +} diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8227ee7b007ce..3ad8c003b2d1f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -578,6 +578,7 @@ pub struct Editor { /// Otherwise it represents the point that the breakpoint will be shown pub gutter_breakpoint_indicator: Option, previous_search_ranges: Option]>>, + active_debuggers: bool, file_header_size: u8, breadcrumb_header: Option, focused_block: Option, @@ -1922,6 +1923,7 @@ impl Editor { tasks_update_task: None, linked_edit_ranges: Default::default(), previous_search_ranges: None, + active_debuggers: false, breadcrumb_header: None, focused_block: None, }; @@ -6025,6 +6027,13 @@ impl Editor { if !breakpoint_set.remove(&breakpoint) { breakpoint_set.insert(breakpoint); } + + if self.active_debuggers { + project.update(cx, |project, cx| { + project.update_file_breakpoints(buffer_id, cx) + }); + } + cx.notify(); } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4e47282193a55..e068665a8dd17 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -27,7 +27,6 @@ use collections::{btree_map, BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}; use dap::{ client::{Breakpoint, DebugAdapterClient, DebugAdapterClientId}, transport::Events, - SourceBreakpoint, }; use debounced_delay::DebouncedDelay; use futures::{ @@ -56,8 +55,8 @@ use language::{ deserialize_anchor, deserialize_version, serialize_anchor, serialize_line_ending, serialize_version, split_operations, }, - range_from_lsp, Bias, Buffer, BufferRow, BufferSnapshot, CachedLspAdapter, Capability, - CodeLabel, ContextProvider, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation, + range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, CodeLabel, + ContextProvider, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, @@ -119,7 +118,7 @@ use task::{ HideStrategy, RevealStrategy, Shell, TaskContext, TaskTemplate, TaskVariables, VariableName, }; use terminals::Terminals; -use text::{Anchor, BufferId, LineEnding, Point}; +use text::{Anchor, BufferId, LineEnding}; use unicase::UniCase; use util::{ debug_panic, defer, maybe, merge_json_value_into, parse_env_output, post_inc, @@ -1175,24 +1174,11 @@ impl Project { if let Some((path, breakpoints)) = res { tasks.push( client.set_breakpoints( - path, + path.clone(), Some( breakpoints .iter() - .map(|b| SourceBreakpoint { - line: (buffer - .summary_for_anchor::( - &b.position.text_anchor, - ) - .row - + 1) - as u64, - condition: None, - hit_condition: None, - log_message: None, - column: None, - mode: None, - }) + .map(|b| b.to_source_breakpoint(buffer)) .collect::>(), ), ), @@ -1270,47 +1256,60 @@ impl Project { .insert(id, DebugAdapterClientState::Starting(task)); } - pub fn _update_breakpoint( - &mut self, - _buffer: Model, - _row: BufferRow, - _cx: &mut ModelContext, - ) { - // let breakpoints_for_buffer = self - // .breakpoints - // .entry(buffer.read(cx).remote_id()) - // .or_insert(Vec::new()); - - // if let Some(ix) = breakpoints_for_buffer - // .iter() - // .position(|breakpoint| breakpoint.row == row) - // { - // breakpoints_for_buffer.remove(ix); - // } else { - // breakpoints_for_buffer.push(Breakpoint { row }); - // } - - // let clients = self - // .debug_adapters - // .iter() - // .filter_map(|(_, state)| match state { - // DebugAdapterClientState::Starting(_) => None, - // DebugAdapterClientState::Running(client) => Some(client.clone()), - // }) - // .collect::>(); - - // let mut tasks = Vec::new(); - // for client in clients { - // tasks.push(self.send_breakpoints(client, cx)); - // } - - // cx.background_executor() - // .spawn(async move { - // try_join_all(tasks).await?; - - // anyhow::Ok(()) - // }) - // .detach_and_log_err(cx) + pub fn update_file_breakpoints(&self, buffer_id: BufferId, cx: &mut ModelContext) { + let clients = self + .debug_adapters + .iter() + .filter_map(|(_, state)| match state { + DebugAdapterClientState::Starting(_) => None, + DebugAdapterClientState::Running(client) => Some(client.clone()), + }) + .collect::>(); + + let Some(buffer) = self.buffer_for_id(buffer_id, cx) else { + return; + }; + + let buffer = buffer.read(cx); + + let file_path = maybe!({ + let project_path = buffer.project_path(cx)?; + let worktree = self.worktree_for_id(project_path.worktree_id, cx)?; + let path = worktree.read(cx).absolutize(&project_path.path).ok()?; + + Some(path) + }); + + let Some(file_path) = file_path else { + return; + }; + + let read_guard = self.breakpoints.read(); + + let breakpoints_locations = read_guard.get(&buffer_id); + + if let Some(breakpoints_locations) = breakpoints_locations { + let breakpoints_locations = Some( + breakpoints_locations + .iter() + .map(|bp| bp.to_source_breakpoint(&buffer)) + .collect(), + ); + + // TODO: Send correct value for sourceModified + for client in clients { + let bps = breakpoints_locations.clone(); + let file_path = file_path.clone(); + + cx.background_executor() + .spawn(async move { + client.set_breakpoints(file_path, bps).await?; + + anyhow::Ok(()) + }) + .detach_and_log_err(cx) + } + } } fn shutdown_language_servers( From 4c5deb0b4ee091c116ca87e525f7ac8a440b6e60 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Sun, 28 Jul 2024 18:13:14 -0400 Subject: [PATCH 19/22] Finalize refactoring breakpoints to use RWLocks --- crates/editor/src/editor.rs | 25 ++++++++++++++----------- crates/project/src/project.rs | 5 ++++- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3ad8c003b2d1f..d875451b597f9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -578,7 +578,6 @@ pub struct Editor { /// Otherwise it represents the point that the breakpoint will be shown pub gutter_breakpoint_indicator: Option, previous_search_ranges: Option]>>, - active_debuggers: bool, file_header_size: u8, breadcrumb_header: Option, focused_block: Option, @@ -1923,7 +1922,6 @@ impl Editor { tasks_update_task: None, linked_edit_ranges: Default::default(), previous_search_ranges: None, - active_debuggers: false, breadcrumb_header: None, focused_block: None, }; @@ -6020,19 +6018,24 @@ impl Editor { position: breakpoint_position, }; - let mut write_guard = breakpoints.write(); + // Putting the write guard within it's own scope so it's dropped + // before project updates it's breakpoints. This is done to prevent + // a data race condition where project waits to get a read lock + { + let mut write_guard = breakpoints.write(); - let breakpoint_set = write_guard.entry(buffer_id).or_default(); + let breakpoint_set = write_guard.entry(buffer_id).or_default(); - if !breakpoint_set.remove(&breakpoint) { - breakpoint_set.insert(breakpoint); + if !breakpoint_set.remove(&breakpoint) { + breakpoint_set.insert(breakpoint); + } } - if self.active_debuggers { - project.update(cx, |project, cx| { - project.update_file_breakpoints(buffer_id, cx) - }); - } + project.update(cx, |project, cx| { + if project.has_active_debugger() { + project.update_file_breakpoints(buffer_id, cx); + } + }); cx.notify(); } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e068665a8dd17..7df11f362017d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1195,6 +1195,10 @@ impl Project { }) } + pub fn has_active_debugger(&self) -> bool { + self.debug_adapters.len() > 0 + } + pub fn start_debug_adapter_client( &mut self, debug_task: task::ResolvedTask, @@ -1300,7 +1304,6 @@ impl Project { for client in clients { let bps = breakpoints_locations.clone(); let file_path = file_path.clone(); - cx.background_executor() .spawn(async move { client.set_breakpoints(file_path, bps).await?; From d222fbe84c102244584dcce2c7acd9e12d0b7577 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Mon, 29 Jul 2024 16:42:19 -0400 Subject: [PATCH 20/22] Fix breakpoint indicator lagging behind mouse in gutter --- crates/editor/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2c5c233d34629..d964d65841c98 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -703,11 +703,11 @@ impl EditorElement { editor.set_gutter_hovered(gutter_hovered, cx); if gutter_hovered { - // TODO: Show breakpoint indicator on line closest to mouse let point_for_position = position_map.point_for_position(text_hitbox.bounds, event.position); let position = point_for_position.previous_valid; editor.gutter_breakpoint_indicator = Some(position); + cx.notify(); } else { editor.gutter_breakpoint_indicator = None; } From 620e65411b9a0d57964d920796e055414793d271 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Mon, 29 Jul 2024 17:58:52 -0400 Subject: [PATCH 21/22] Fix crash that can happen after placing breakpoint in file and changing files The crash was happening because editor was trying to render all breakpoints even if they didn't belong to the current buffer. Breakpoint don't persistent after closing a tab, will need to change the breakpoint DS key from buffer_id to something more permament --- crates/editor/src/editor.rs | 2 +- crates/editor/src/element.rs | 70 ++++++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d875451b597f9..264c484b1c029 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6013,7 +6013,7 @@ impl Editor { }; let buffer_id = buffer.read(cx).remote_id(); - // let key = (buffer_id, breakpoint_position); + let breakpoint = Breakpoint { position: breakpoint_position, }; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d964d65841c98..fcc77fae50136 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -67,7 +67,10 @@ use ui::prelude::*; use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip}; use util::RangeExt; use util::ResultExt; -use workspace::{item::Item, Workspace}; +use workspace::{ + item::{FollowableItem, Item}, + Workspace, +}; struct SelectionLayout { head: DisplayPoint, @@ -1580,36 +1583,49 @@ impl EditorElement { return vec![]; }; - let mut breakpoints_to_render = breakpoints - .read() - .iter() - .flat_map(|(_buffer_id, breakpoint_set)| breakpoint_set.iter()) - .filter_map(|breakpoint| { - let point = breakpoint - .position - .to_display_point(&snapshot.display_snapshot); + let Some(active_buffer) = editor.buffer().read(cx).as_singleton() else { + return vec![]; + }; - let row = MultiBufferRow { 0: point.row().0 }; + let active_buffer_id = active_buffer.read(cx).remote_id(); + let read_guard = breakpoints.read(); - if snapshot.is_line_folded(row) { - return None; - } + let mut breakpoints_to_render = if let Some(breakpoint_set) = + read_guard.get(&active_buffer_id) + { + breakpoint_set + .iter() + .filter_map(|breakpoint| { + let point = breakpoint + .position + .to_display_point(&snapshot.display_snapshot); - let button = editor.render_breakpoint(breakpoint.position, point.row(), cx); + let row = MultiBufferRow { 0: point.row().0 }; - let button = prepaint_gutter_button( - button, - point.row(), - line_height, - gutter_dimensions, - scroll_pixel_position, - gutter_hitbox, - rows_with_hunk_bounds, - cx, - ); - Some(button) - }) - .collect_vec(); + if snapshot.is_line_folded(row) { + return None; + } + + let button = editor.render_breakpoint(breakpoint.position, point.row(), cx); + + let button = prepaint_gutter_button( + button, + point.row(), + line_height, + gutter_dimensions, + scroll_pixel_position, + gutter_hitbox, + rows_with_hunk_bounds, + cx, + ); + Some(button) + }) + .collect_vec() + } else { + vec![] + }; + + drop(read_guard); // See if a user is hovered over a gutter line & if they are display // a breakpoint indicator that they can click to add a breakpoint From 655b23c63558be29275cba22c837586da7436088 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Mon, 29 Jul 2024 18:48:27 -0400 Subject: [PATCH 22/22] Get clippy to pass --- .zed/tasks.json | 5 +++++ crates/dap/src/client.rs | 12 +++--------- crates/editor/src/element.rs | 8 +++----- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/.zed/tasks.json b/.zed/tasks.json index 259ab07f3e065..ca84769a0716d 100644 --- a/.zed/tasks.json +++ b/.zed/tasks.json @@ -3,5 +3,10 @@ "label": "clippy", "command": "./script/clippy", "args": [] + }, + { + "label": "Run Zed Tests", + "command": "cargo nextest run --workspace --no-fail-fast", + "args": [] } ] diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index 91b5981b33c5c..3fa0c26d9b150 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -399,8 +399,7 @@ impl DebugAdapterClient { request: crate::transport::Request, cx: &mut AsyncAppContext, ) -> Result<()> { - dbg!(&request); - let arguments: StartDebuggingRequestArguments = + let _arguments: StartDebuggingRequestArguments = serde_json::from_value(request.arguments.clone().unwrap_or_default())?; let sub_client = DebugAdapterClient::new( @@ -413,17 +412,12 @@ impl DebugAdapterClient { "127.0.0.1", ], PathBuf::from("/Users/remcosmits/Documents/code/prettier-test"), - |event, _cx| { - dbg!(event); - }, + |_event, _cx| {}, cx, ) .await?; - dbg!(&arguments); - - let res = sub_client.launch(request.arguments).await?; - dbg!(res); + let _res = sub_client.launch(request.arguments).await?; *this.sub_client.lock() = Some(sub_client); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fcc77fae50136..ecbced834d47d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -67,10 +67,7 @@ use ui::prelude::*; use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip}; use util::RangeExt; use util::ResultExt; -use workspace::{ - item::{FollowableItem, Item}, - Workspace, -}; +use workspace::{item::Item, Workspace}; struct SelectionLayout { head: DisplayPoint, @@ -1630,7 +1627,7 @@ impl EditorElement { // See if a user is hovered over a gutter line & if they are display // a breakpoint indicator that they can click to add a breakpoint // TODO: We should figure out a way to display this side by side with - // the code action button. They currently overlap + // the code action button. They currently overlap if let Some(gutter_breakpoint) = editor.gutter_breakpoint_indicator { let gutter_anchor = snapshot.display_point_to_anchor(gutter_breakpoint, Bias::Left); @@ -1661,6 +1658,7 @@ impl EditorElement { }) } + #[allow(clippy::too_many_arguments)] fn layout_run_indicators( &self, line_height: Pixels,