Compare commits

...

14 Commits

Author SHA1 Message Date
Joseph T. Lyons
2f19e4262b zed 0.191.1 2025-06-12 11:58:40 -04:00
Piotr Osiewicz
8124758685 debugger: Allow use of externally-managed Delve for Go debugging (#32613)
Closes #ISSUE

Release Notes:

- Go debug scenarios can now use an externally-managed Delve instance.
Use `tcp_connection` in your debug scenario definition to provide
adapter's address.
2025-06-12 11:53:36 -04:00
Anthony Eid
441d738061 debugger: Handle session restart failures instead of hanging (#32595)
I also enabled the `Restart` action even for sessions that don't support
restarting because we have a restart fallback now.

Closes #31408

Release Notes:

- Fix bug where a debugger session would never be shutdown on a failed
restart attempt
2025-06-12 11:53:24 -04:00
Julia Ryan
fa003793ed debugger: Don't show VSCode worktree tasks when Zed ones exist (#32589)
Fixes #31699

Eventually we might want to merge the lists and deduplicate based on the
command and args that it's running. For now we'll just use the presence
of _any_ worktree local zed debug tasks to disable all VSCode ones.

Release Notes:

- N/A
2025-06-12 11:53:16 -04:00
Piotr Osiewicz
38e00a72a9 debugger: Fix DebugAdapterDelegate::worktree_root always using the first visible worktree (#32585)
Closes #32577

Release Notes:

- Fixed debugger malfunctioning when using ZED_WORKTREE_ROOT env
variable in multi-worktree workspaces.
2025-06-12 11:53:07 -04:00
Cole Miller
c14c370671 debugger: Special-case npm et al. as program field for JS debug definitions (#32549)
Send `runtimeExecutable` and `runtimeArgs` instead of `program` and
`args` to avoid the DAP implicitly wrapping the command in `node`.

This means that putting `pnpm vitest <file>` as the command in the
launch modal will work, as will this in debug.json:

```
[
  {
    "adapter": "JavaScript",
    "type": "pwa-node",
    "label": "Label",
    "request": "launch",
    "program": "pnpm",
    "args": ["vitest", "<file>"],
    "cwd": "/Users/name/project"
  }
]
```


Release Notes:

- Debugger Beta: made it possible to use commands like `pnpm
<subcommand> <args>` in the launch modal and debug.json
2025-06-12 11:51:48 -04:00
Cole Miller
bc6d75b4af debugger: Fix issues with launch.json handling (#32563)
After this PR we can run all the in-tree launch.json examples from [this
repo](https://github.com/microsoft/vscode-recipes).

Things done:

- Fill in default cwd at a lower level for all adapters
- Update launch.json parsing for DebugScenario changes
- Imitate how VS Code normalizes the `type` field for JS debug tasks
- Make version field optional
- Extend the variable replacer a bit

Release Notes:

- Debugger Beta: fixed issues preventing loading and running of debug
tasks from VS Code's launch.json.

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-12 11:51:37 -04:00
Conrad Irwin
55d751a1c9 Hush breakpoint deserialization logs (#32430)
Release Notes:

- debugger: Remove "Deserializing N breakpoints" from the Zed log
2025-06-12 11:51:20 -04:00
Conrad Irwin
2562daaa6d Rerun debug scenario now uses latest definition from JSON (#32569)
Co-authored-by: Piotr Osiewicz <piotr@zed.dev>

Closes #ISSUE

Release Notes:

- debugger: Re-running a debug scenario that has been edited on disk now
uses the latest version

Co-authored-by: Piotr Osiewicz <peterosiewicz@gmail.com>
2025-06-12 11:51:12 -04:00
Piotr Osiewicz
10d00e18c9 debugger: Fix preselection of debug adapters to not pick CodeLLDB by default (#32557)
Closes #ISSUE

Release Notes:

- debugger: Fix preselection of debug adapters to not pick CodeLLDB by
default
2025-06-12 11:51:01 -04:00
gcp-cherry-pick-bot[bot]
720fe2f437 agent: Don't stop following after edits (cherry-pick #32606) (#32608)
Cherry-picked agent: Don't stop following after edits (#32606)

This is reverting a change from #32071 which caused agent following to
stop after the file was edited.

This will reintroduce the behavior that the keyboard shortcuts don't
work until the model is done generating, but we will revisit that
afterwards.

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>

Release Notes:

- agent: Fix a regression in agent following behavior after file edits

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-06-12 15:49:02 +02:00
gcp-cherry-pick-bot[bot]
28f14093d1 gpui: Fix window cursor style flickering (cherry-pick #32596) (#32600)
Cherry-picked gpui: Fix window cursor style flickering (#32596)

Closes #32592
Follow-up to #31965 

This PR fixes the cursor style flickering on Linux systems. The issue
arose since the window cursor style was not reused anymore for
subsequent frames after the changes in #31965. This works on MacOS for
hiding cursors, since they are hidden until the next mouse movement
occurs, which is not the case for other systems.

This PR re-adds this whilst keeping the fixes applied in #31965. We now
determine the first cursor style that is hovered and continue searching
for a cursor style that should be applied globally. If one to apply for
the whole window is found, we return that cursor style early instead.

Alternatively, we could store window cursor style request in a vector
similar to normal cursor styles. That would require more memory in
exchange for fewer checks which cursor style to apply. I preferred the
approach here, though, but can change this should the other method be
preferred.

CC @smitbarmase since you assigned yourself that issue.

Release Notes:

- Fixed an issue where the cursor would flicker whilst typing.

Co-authored-by: Finn Evers <dev@bahn.sh>
2025-06-12 15:48:25 +05:30
gcp-cherry-pick-bot[bot]
ff6662052d Use buffer's main language when fetching language tasks (cherry-pick #32580) (#32582)
Cherry-picked Use buffer's main language when fetching language tasks
(#32580)

Closes https://github.com/zed-industries/zed/issues/32465

Release Notes:

- Fixed language tasks fetched incorrectly for certain selections

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-06-12 00:31:01 +03:00
Joseph T. Lyons
d8d6866191 v0.191.x preview 2025-06-11 11:18:53 -04:00
22 changed files with 439 additions and 161 deletions

2
Cargo.lock generated
View File

@@ -19742,7 +19742,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.191.0"
version = "0.191.1"
dependencies = [
"activity_indicator",
"agent",

View File

@@ -1513,7 +1513,7 @@ impl AgentDiff {
multibuffer.add_diff(diff_handle.clone(), cx);
});
let new_state = if thread.read(cx).has_pending_edit_tool_uses() {
let new_state = if thread.read(cx).is_generating() {
EditorState::Generating
} else {
EditorState::Reviewing

View File

@@ -21,18 +21,21 @@ impl CodeLldbDebugAdapter {
fn request_args(
&self,
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
) -> Result<dap::StartDebuggingRequestArguments> {
// CodeLLDB uses `name` for a terminal label.
let mut configuration = task_definition.config.clone();
configuration
let obj = configuration
.as_object_mut()
.context("CodeLLDB is not a valid json object")?
.insert(
"name".into(),
Value::String(String::from(task_definition.label.as_ref())),
);
.context("CodeLLDB is not a valid json object")?;
obj.entry("name")
.or_insert(Value::String(String::from(task_definition.label.as_ref())));
obj.entry("cwd")
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
let request = self.request_kind(&configuration)?;
@@ -365,7 +368,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
"--settings".into(),
json!({"sourceLanguages": ["cpp", "rust"]}).to_string(),
],
request_args: self.request_args(&config)?,
request_args: self.request_args(delegate, &config)?,
envs: HashMap::default(),
connection: None,
})

View File

@@ -177,10 +177,12 @@ impl DebugAdapter for GdbDebugAdapter {
let gdb_path = user_setting_path.unwrap_or(gdb_path?);
let request_args = StartDebuggingRequestArguments {
request: self.request_kind(&config.config)?,
configuration: config.config.clone(),
};
let mut configuration = config.config.clone();
if let Some(configuration) = configuration.as_object_mut() {
configuration
.entry("cwd")
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
}
Ok(DebugAdapterBinary {
command: Some(gdb_path),
@@ -188,7 +190,10 @@ impl DebugAdapter for GdbDebugAdapter {
envs: HashMap::default(),
cwd: Some(delegate.worktree_root_path().to_path_buf()),
connection: None,
request_args,
request_args: StartDebuggingRequestArguments {
request: self.request_kind(&config.config)?,
configuration,
},
})
}
}

View File

@@ -2,7 +2,7 @@ use anyhow::{Context as _, bail};
use dap::{
StartDebuggingRequestArguments,
adapters::{
DebugTaskDefinition, DownloadedFileType, download_adapter_from_github,
DebugTaskDefinition, DownloadedFileType, TcpArguments, download_adapter_from_github,
latest_github_release,
},
};
@@ -10,6 +10,7 @@ use dap::{
use gpui::{AsyncApp, SharedString};
use language::LanguageName;
use std::{collections::HashMap, env::consts, ffi::OsStr, path::PathBuf, sync::OnceLock};
use task::TcpArgumentsTemplate;
use util;
use crate::*;
@@ -433,10 +434,6 @@ impl DebugAdapter for GoDebugAdapter {
adapter_path.join("dlv").to_string_lossy().to_string()
};
let minidelve_path = self.install_shim(delegate).await?;
let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
let (host, port, _) = crate::configure_tcp_connection(tcp_connection).await?;
let cwd = task_definition
.config
@@ -445,31 +442,58 @@ impl DebugAdapter for GoDebugAdapter {
.map(PathBuf::from)
.unwrap_or_else(|| delegate.worktree_root_path().to_path_buf());
let arguments = if cfg!(windows) {
vec![
delve_path,
"dap".into(),
"--listen".into(),
format!("{}:{}", host, port),
"--headless".into(),
]
} else {
vec![
delve_path,
"dap".into(),
"--listen".into(),
format!("{}:{}", host, port),
]
};
let arguments;
let command;
let connection;
let mut configuration = task_definition.config.clone();
if let Some(configuration) = configuration.as_object_mut() {
configuration
.entry("cwd")
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
}
if let Some(connection_options) = &task_definition.tcp_connection {
command = None;
arguments = vec![];
let (host, port, timeout) =
crate::configure_tcp_connection(connection_options.clone()).await?;
connection = Some(TcpArguments {
host,
port,
timeout,
});
} else {
let minidelve_path = self.install_shim(delegate).await?;
let (host, port, _) =
crate::configure_tcp_connection(TcpArgumentsTemplate::default()).await?;
command = Some(minidelve_path.to_string_lossy().into_owned());
connection = None;
arguments = if cfg!(windows) {
vec![
delve_path,
"dap".into(),
"--listen".into(),
format!("{}:{}", host, port),
"--headless".into(),
]
} else {
vec![
delve_path,
"dap".into(),
"--listen".into(),
format!("{}:{}", host, port),
]
};
}
Ok(DebugAdapterBinary {
command: Some(minidelve_path.to_string_lossy().into_owned()),
command,
arguments,
cwd: Some(cwd),
envs: HashMap::default(),
connection: None,
connection,
request_args: StartDebuggingRequestArguments {
configuration: task_definition.config.clone(),
configuration,
request: self.request_kind(&task_definition.config)?,
},
})

View File

@@ -2,6 +2,7 @@ use adapters::latest_github_release;
use anyhow::Context as _;
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
use gpui::AsyncApp;
use serde_json::Value;
use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
use task::DebugRequest;
use util::ResultExt;
@@ -68,6 +69,35 @@ impl JsDebugAdapter {
let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
let mut configuration = task_definition.config.clone();
if let Some(configuration) = configuration.as_object_mut() {
if let Some(program) = configuration
.get("program")
.cloned()
.and_then(|value| value.as_str().map(str::to_owned))
{
match program.as_str() {
"npm" | "pnpm" | "yarn" | "bun"
if !configuration.contains_key("runtimeExecutable")
&& !configuration.contains_key("runtimeArgs") =>
{
configuration.remove("program");
configuration.insert("runtimeExecutable".to_owned(), program.into());
if let Some(args) = configuration.remove("args") {
configuration.insert("runtimeArgs".to_owned(), args);
}
}
_ => {}
}
}
configuration
.entry("cwd")
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
configuration.entry("type").and_modify(normalize_task_type);
}
Ok(DebugAdapterBinary {
command: Some(
delegate
@@ -93,7 +123,7 @@ impl JsDebugAdapter {
timeout,
}),
request_args: StartDebuggingRequestArguments {
configuration: task_definition.config.clone(),
configuration,
request: self.request_kind(&task_definition.config)?,
},
})
@@ -173,7 +203,7 @@ impl DebugAdapter for JsDebugAdapter {
"properties": {
"type": {
"type": "string",
"enum": ["pwa-node", "node", "chrome", "pwa-chrome", "edge", "pwa-edge"],
"enum": ["pwa-node", "node", "chrome", "pwa-chrome", "msedge", "pwa-msedge"],
"description": "The type of debug session",
"default": "pwa-node"
},
@@ -439,3 +469,19 @@ impl DebugAdapter for JsDebugAdapter {
Some(label.to_owned())
}
}
fn normalize_task_type(task_type: &mut Value) {
let Some(task_type_str) = task_type.as_str() else {
return;
};
let new_name = match task_type_str {
"node" | "pwa-node" => "pwa-node",
"chrome" | "pwa-chrome" => "pwa-chrome",
"edge" | "msedge" | "pwa-edge" | "pwa-msedge" => "pwa-msedge",
_ => task_type_str,
}
.to_owned();
*task_type = Value::String(new_name);
}

View File

@@ -71,6 +71,12 @@ impl PhpDebugAdapter {
let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
let mut configuration = task_definition.config.clone();
if let Some(obj) = configuration.as_object_mut() {
obj.entry("cwd")
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
}
Ok(DebugAdapterBinary {
command: Some(
delegate
@@ -95,7 +101,7 @@ impl PhpDebugAdapter {
cwd: Some(delegate.worktree_root_path().to_path_buf()),
envs: HashMap::default(),
request_args: StartDebuggingRequestArguments {
configuration: task_definition.config.clone(),
configuration,
request: <Self as DebugAdapter>::request_kind(self, &task_definition.config)?,
},
})

View File

@@ -83,6 +83,7 @@ impl PythonDebugAdapter {
fn request_args(
&self,
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
) -> Result<StartDebuggingRequestArguments> {
let request = self.request_kind(&task_definition.config)?;
@@ -95,6 +96,11 @@ impl PythonDebugAdapter {
}
}
if let Some(obj) = configuration.as_object_mut() {
obj.entry("cwd")
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
}
Ok(StartDebuggingRequestArguments {
configuration,
request,
@@ -196,7 +202,7 @@ impl PythonDebugAdapter {
}),
cwd: Some(delegate.worktree_root_path().to_path_buf()),
envs: HashMap::default(),
request_args: self.request_args(config)?,
request_args: self.request_args(delegate, config)?,
})
}
}

View File

@@ -174,6 +174,13 @@ impl DebugAdapter for RubyDebugAdapter {
arguments.extend(ruby_config.args);
let mut configuration = definition.config.clone();
if let Some(configuration) = configuration.as_object_mut() {
configuration
.entry("cwd")
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
}
Ok(DebugAdapterBinary {
command: Some(rdbg_path.to_string_lossy().to_string()),
arguments,
@@ -190,7 +197,7 @@ impl DebugAdapter for RubyDebugAdapter {
envs: ruby_config.env.into_iter().collect(),
request_args: StartDebuggingRequestArguments {
request: self.request_kind(&definition.config)?,
configuration: definition.config.clone(),
configuration,
},
})
}

View File

@@ -179,6 +179,19 @@ impl DebugPanel {
cx,
)
});
let worktree = worktree_id.or_else(|| {
active_buffer
.as_ref()
.and_then(|buffer| buffer.read(cx).file())
.map(|f| f.worktree_id(cx))
});
let Some(worktree) = worktree
.and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
.or_else(|| self.project.read(cx).visible_worktrees(cx).next())
else {
log::debug!("Could not find a worktree to spawn the debug session in");
return;
};
self.debug_scenario_scheduled_last = true;
if let Some(inventory) = self
.project
@@ -213,7 +226,7 @@ impl DebugPanel {
.await?;
dap_store
.update(cx, |dap_store, cx| {
dap_store.boot_session(session.clone(), definition, cx)
dap_store.boot_session(session.clone(), definition, worktree, cx)
})?
.await
}
@@ -336,8 +349,26 @@ impl DebugPanel {
});
(session, task)
})?;
Self::register_session(this.clone(), session, true, cx).await?;
task.await
Self::register_session(this.clone(), session.clone(), true, cx).await?;
if let Err(error) = task.await {
session
.update(cx, |session, cx| {
session
.console_output(cx)
.unbounded_send(format!(
"Session failed to restart with error: {}",
error
))
.ok();
session.shutdown(cx)
})?
.await;
return Err(error);
};
Ok(())
})
.detach_and_log_err(cx);
}

View File

@@ -111,7 +111,6 @@ pub fn init(cx: &mut App) {
}
let caps = running_state.capabilities(cx);
let supports_restart = caps.supports_restart_request.unwrap_or_default();
let supports_step_back = caps.supports_step_back.unwrap_or_default();
let supports_detach = running_state.session().read(cx).is_attached();
let status = running_state.thread_status(cx);
@@ -204,13 +203,13 @@ pub fn init(cx: &mut App) {
.ok();
})
})
.when(supports_restart, |div| {
.on_action({
let active_item = active_item.clone();
div.on_action(move |_: &Restart, _, cx| {
move |_: &Restart, _, cx| {
active_item
.update(cx, |item, cx| item.restart_session(cx))
.ok();
})
}
})
.on_action({
let active_item = active_item.clone();

View File

@@ -477,10 +477,9 @@ impl NewProcessModal {
.get_index_of(adapter.0.as_ref())
.unwrap_or(usize::MAX)
});
}
if self.debugger.is_none() {
self.debugger = available_adapters.first().cloned();
if self.debugger.is_none() {
self.debugger = available_adapters.first().cloned();
}
}
let label = self
@@ -1169,15 +1168,35 @@ impl DebugDelegate {
}
let dap_registry = cx.global::<DapRegistry>();
let hide_vscode = scenarios.iter().any(|(kind, _)| match kind {
TaskSourceKind::Worktree {
id: _,
directory_in_worktree: dir,
id_base: _,
} => dir.ends_with(".zed"),
_ => false,
});
self.candidates = recent
.into_iter()
.map(|scenario| Self::get_scenario_kind(&languages, &dap_registry, scenario))
.chain(scenarios.into_iter().map(|(kind, scenario)| {
let (language, scenario) =
Self::get_scenario_kind(&languages, &dap_registry, scenario);
(language.or(Some(kind)), scenario)
}))
.chain(
scenarios
.into_iter()
.filter(|(kind, _)| match kind {
TaskSourceKind::Worktree {
id: _,
directory_in_worktree: dir,
id_base: _,
} => !(hide_vscode && dir.ends_with(".vscode")),
_ => true,
})
.map(|(kind, scenario)| {
let (language, scenario) =
Self::get_scenario_kind(&languages, &dap_registry, scenario);
(language.or(Some(kind)), scenario)
}),
)
.collect();
}
}
@@ -1514,40 +1533,3 @@ pub(crate) fn resolve_path(path: &mut String) {
);
};
}
#[cfg(test)]
impl NewProcessModal {
// #[cfg(test)]
// pub(crate) fn set_configure(
// &mut self,
// program: impl AsRef<str>,
// cwd: impl AsRef<str>,
// stop_on_entry: bool,
// window: &mut Window,
// cx: &mut Context<Self>,
// ) {
// self.mode = NewProcessMode::Launch;
// self.debugger = Some(dap::adapters::DebugAdapterName("fake-adapter".into()));
// self.launch_mode.update(cx, |configure, cx| {
// configure.program.update(cx, |editor, cx| {
// editor.clear(window, cx);
// editor.set_text(program.as_ref(), window, cx);
// });
// configure.cwd.update(cx, |editor, cx| {
// editor.clear(window, cx);
// editor.set_text(cwd.as_ref(), window, cx);
// });
// configure.stop_on_entry = match stop_on_entry {
// true => ToggleState::Selected,
// _ => ToggleState::Unselected,
// }
// })
// }
// pub(crate) fn save_scenario(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
// self.save_debug_scenario(window, cx);
// }
}

View File

@@ -24,6 +24,8 @@ use core_video::pixel_buffer::CVPixelBuffer;
use derive_more::{Deref, DerefMut};
use futures::FutureExt;
use futures::channel::oneshot;
use itertools::FoldWhile::{Continue, Done};
use itertools::Itertools;
use parking_lot::RwLock;
use raw_window_handle::{HandleError, HasDisplayHandle, HasWindowHandle};
use refineable::Refineable;
@@ -408,7 +410,7 @@ pub(crate) type AnyMouseListener =
#[derive(Clone)]
pub(crate) struct CursorStyleRequest {
pub(crate) hitbox_id: HitboxId,
pub(crate) hitbox_id: Option<HitboxId>,
pub(crate) style: CursorStyle,
}
@@ -622,7 +624,6 @@ pub(crate) struct Frame {
pub(crate) input_handlers: Vec<Option<PlatformInputHandler>>,
pub(crate) tooltip_requests: Vec<Option<TooltipRequest>>,
pub(crate) cursor_styles: Vec<CursorStyleRequest>,
window_cursor_style: Option<CursorStyle>,
#[cfg(any(test, feature = "test-support"))]
pub(crate) debug_bounds: FxHashMap<String, Bounds<Pixels>>,
#[cfg(any(feature = "inspector", debug_assertions))]
@@ -667,7 +668,6 @@ impl Frame {
input_handlers: Vec::new(),
tooltip_requests: Vec::new(),
cursor_styles: Vec::new(),
window_cursor_style: None,
#[cfg(any(test, feature = "test-support"))]
debug_bounds: FxHashMap::default(),
@@ -693,7 +693,6 @@ impl Frame {
self.window_control_hitboxes.clear();
self.deferred_draws.clear();
self.focus = None;
self.window_cursor_style = None;
#[cfg(any(feature = "inspector", debug_assertions))]
{
@@ -703,14 +702,16 @@ impl Frame {
}
pub(crate) fn cursor_style(&self, window: &Window) -> Option<CursorStyle> {
self.window_cursor_style.or_else(|| {
self.cursor_styles.iter().rev().find_map(|request| {
request
.hitbox_id
.is_hovered(window)
.then_some(request.style)
self.cursor_styles
.iter()
.rev()
.fold_while(None, |style, request| match request.hitbox_id {
None => Done(Some(request.style)),
Some(hitbox_id) => Continue(
style.or_else(|| hitbox_id.is_hovered(window).then_some(request.style)),
),
})
})
.into_inner()
}
pub(crate) fn hit_test(&self, position: Point<Pixels>) -> HitTest {
@@ -2174,7 +2175,7 @@ impl Window {
pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: &Hitbox) {
self.invalidator.debug_assert_paint();
self.next_frame.cursor_styles.push(CursorStyleRequest {
hitbox_id: hitbox.id,
hitbox_id: Some(hitbox.id),
style,
});
}
@@ -2185,7 +2186,10 @@ impl Window {
/// phase of element drawing.
pub fn set_window_cursor_style(&mut self, style: CursorStyle) {
self.invalidator.debug_assert_paint();
self.next_frame.window_cursor_style = Some(style);
self.next_frame.cursor_styles.push(CursorStyleRequest {
hitbox_id: None,
style,
})
}
/// Sets a tooltip to be rendered for the upcoming frame. This method should only be called

View File

@@ -841,7 +841,7 @@ impl BreakpointStore {
} else {
"breakpoint"
};
log::info!("Deserialized {count} {breakpoint_str} at path: {path}");
log::debug!("Deserialized {count} {breakpoint_str} at path: {path}");
}
this.breakpoints = new_breakpoints;

View File

@@ -180,15 +180,12 @@ impl DapStore {
&mut self,
definition: DebugTaskDefinition,
session_id: SessionId,
worktree: &Entity<Worktree>,
console: UnboundedSender<String>,
cx: &mut Context<Self>,
) -> Task<Result<DebugAdapterBinary>> {
match &self.mode {
DapStoreMode::Local(_) => {
let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next()
else {
return Task::ready(Err(anyhow!("Failed to find a worktree")));
};
let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else {
return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
};
@@ -229,6 +226,7 @@ impl DapStore {
let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {
session_id: session_id.to_proto(),
project_id: ssh.upstream_project_id,
worktree_id: worktree.read(cx).id().to_proto(),
definition: Some(definition.to_proto()),
});
let ssh_client = ssh.ssh_client.clone();
@@ -401,12 +399,9 @@ impl DapStore {
&self,
session: Entity<Session>,
definition: DebugTaskDefinition,
worktree: Entity<Worktree>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {
return Task::ready(Err(anyhow!("Failed to find a worktree")));
};
let dap_store = cx.weak_entity();
let console = session.update(cx, |session, cx| session.console_output(cx));
let session_id = session.read(cx).session_id();
@@ -416,7 +411,13 @@ impl DapStore {
async move |this, cx| {
let binary = this
.update(cx, |this, cx| {
this.get_debug_adapter_binary(definition.clone(), session_id, console, cx)
this.get_debug_adapter_binary(
definition.clone(),
session_id,
&worktree,
console,
cx,
)
})?
.await?;
session
@@ -780,9 +781,22 @@ impl DapStore {
})
.detach();
let worktree = this
.update(&mut cx, |this, cx| {
this.worktree_store
.read(cx)
.worktree_for_id(WorktreeId::from_proto(envelope.payload.worktree_id), cx)
})?
.context("Failed to find worktree with a given ID")?;
let binary = this
.update(&mut cx, |this, cx| {
this.get_debug_adapter_binary(definition, SessionId::from_proto(session_id), tx, cx)
this.get_debug_adapter_binary(
definition,
SessionId::from_proto(session_id),
&worktree,
tx,
cx,
)
})?
.await?;
Ok(binary.to_proto())

View File

@@ -412,8 +412,7 @@ impl Inventory {
let fs = self.fs.clone();
let worktree = task_contexts.worktree();
let location = task_contexts.location();
let language = location
.and_then(|location| location.buffer.read(cx).language_at(location.range.start));
let language = location.and_then(|location| location.buffer.read(cx).language());
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
name: language.name().into(),
});
@@ -720,20 +719,37 @@ impl Inventory {
}
};
let new_templates = raw_tasks.into_iter().filter_map(|raw_template| {
serde_json::from_value::<DebugScenario>(raw_template).log_err()
});
let new_templates = raw_tasks
.into_iter()
.filter_map(|raw_template| {
serde_json::from_value::<DebugScenario>(raw_template).log_err()
})
.collect::<Vec<_>>();
let parsed_scenarios = &mut self.scenarios_from_settings;
let mut new_definitions: HashMap<_, _> = new_templates
.iter()
.map(|template| (template.label.clone(), template.clone()))
.collect();
let previously_existing_scenarios;
match location {
TaskSettingsLocation::Global(path) => {
previously_existing_scenarios = parsed_scenarios
.global_scenarios()
.map(|(_, scenario)| scenario.label.clone())
.collect::<HashSet<_>>();
parsed_scenarios
.global
.entry(path.to_owned())
.insert_entry(new_templates.collect());
.insert_entry(new_templates);
}
TaskSettingsLocation::Worktree(location) => {
let new_templates = new_templates.collect::<Vec<_>>();
previously_existing_scenarios = parsed_scenarios
.worktree_scenarios(location.worktree_id)
.map(|(_, scenario)| scenario.label.clone())
.collect::<HashSet<_>>();
if new_templates.is_empty() {
if let Some(worktree_tasks) =
parsed_scenarios.worktree.get_mut(&location.worktree_id)
@@ -749,6 +765,17 @@ impl Inventory {
}
}
}
self.last_scheduled_scenarios.retain_mut(|scenario| {
if !previously_existing_scenarios.contains(&scenario.label) {
return true;
}
if let Some(new_definition) = new_definitions.remove(&scenario.label) {
*scenario = new_definition;
true
} else {
false
}
});
Ok(())
}
@@ -1243,6 +1270,99 @@ mod tests {
);
}
#[gpui::test]
async fn test_reloading_debug_scenarios(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let inventory = cx.update(|cx| Inventory::new(fs, cx));
inventory.update(cx, |inventory, cx| {
inventory
.update_file_based_scenarios(
TaskSettingsLocation::Global(Path::new("")),
Some(
r#"
[{
"label": "test scenario",
"adapter": "CodeLLDB",
"request": "launch",
"program": "wowzer",
}]
"#,
),
)
.unwrap();
let (_, scenario) = inventory
.list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx)
.1
.first()
.unwrap()
.clone();
inventory.scenario_scheduled(scenario.clone());
assert_eq!(
inventory
.list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx)
.0
.first()
.unwrap()
.clone(),
scenario
);
inventory
.update_file_based_scenarios(
TaskSettingsLocation::Global(Path::new("")),
Some(
r#"
[{
"label": "test scenario",
"adapter": "Delve",
"request": "launch",
"program": "wowzer",
}]
"#,
),
)
.unwrap();
assert_eq!(
inventory
.list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx)
.0
.first()
.unwrap()
.adapter,
"Delve",
);
inventory
.update_file_based_scenarios(
TaskSettingsLocation::Global(Path::new("")),
Some(
r#"
[{
"label": "testing scenario",
"adapter": "Delve",
"request": "launch",
"program": "wowzer",
}]
"#,
),
)
.unwrap();
assert_eq!(
inventory
.list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx)
.0
.first(),
None
);
});
}
#[gpui::test]
async fn test_inventory_static_task_filters(cx: &mut TestAppContext) {
init_test(cx);

View File

@@ -493,6 +493,7 @@ message GetDebugAdapterBinary {
uint64 project_id = 1;
uint64 session_id = 3;
DebugTaskDefinition definition = 2;
uint64 worktree_id = 4;
}
message DebugAdapterBinary {

View File

@@ -1,5 +1,4 @@
use collections::HashMap;
use gpui::SharedString;
use serde::Deserialize;
use util::ResultExt as _;
@@ -20,40 +19,32 @@ enum Request {
struct VsCodeDebugTaskDefinition {
r#type: String,
name: String,
request: Request,
#[serde(default)]
program: Option<String>,
#[serde(default)]
args: Vec<String>,
#[serde(default)]
env: HashMap<String, Option<String>>,
// TODO envFile?
#[serde(default)]
cwd: Option<String>,
#[serde(default)]
port: Option<u16>,
#[serde(default)]
stop_on_entry: Option<bool>,
#[serde(flatten)]
other_attributes: serde_json::Value,
}
impl VsCodeDebugTaskDefinition {
fn try_to_zed(self, replacer: &EnvVariableReplacer) -> anyhow::Result<DebugScenario> {
let label = replacer.replace(&self.name).into();
// TODO based on grep.app results it seems that vscode supports whitespace-splitting this field (ugh)
let label = replacer.replace(&self.name);
let mut config = replacer.replace_value(self.other_attributes);
let adapter = task_type_to_adapter_name(&self.r#type);
if let Some(config) = config.as_object_mut() {
if adapter == "JavaScript" {
config.insert("type".to_owned(), self.r#type.clone().into());
}
}
let definition = DebugScenario {
label,
label: label.into(),
build: None,
adapter: task_type_to_adapter_name(&self.r#type),
// TODO host?
adapter: adapter.into(),
tcp_connection: self.port.map(|port| TcpArgumentsTemplate {
port: Some(port),
host: None,
timeout: None,
}),
config: replacer.replace_value(self.other_attributes),
config,
};
Ok(definition)
}
@@ -62,7 +53,8 @@ impl VsCodeDebugTaskDefinition {
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct VsCodeDebugTaskFile {
version: String,
#[serde(default)]
version: Option<String>,
configurations: Vec<VsCodeDebugTaskDefinition>,
}
@@ -75,7 +67,11 @@ impl TryFrom<VsCodeDebugTaskFile> for DebugTaskFile {
"workspaceFolder".to_owned(),
VariableName::WorktreeRoot.to_string(),
),
("file".to_owned(), VariableName::Filename.to_string()), // TODO other interesting variables?
(
"relativeFile".to_owned(),
VariableName::RelativeFile.to_string(),
),
("file".to_owned(), VariableName::File.to_string()),
]));
let templates = file
.configurations
@@ -86,10 +82,10 @@ impl TryFrom<VsCodeDebugTaskFile> for DebugTaskFile {
}
}
// todo(debugger) figure out how to make JsDebugAdapter::ADAPTER_NAME et al available here
fn task_type_to_adapter_name(task_type: &str) -> SharedString {
fn task_type_to_adapter_name(task_type: &str) -> String {
match task_type {
"node" => "JavaScript",
"pwa-node" | "node" | "chrome" | "pwa-chrome" | "edge" | "pwa-edge" | "msedge"
| "pwa-msedge" => "JavaScript",
"go" => "Delve",
"php" => "PHP",
"cppdbg" | "lldb" => "CodeLLDB",
@@ -98,7 +94,6 @@ fn task_type_to_adapter_name(task_type: &str) -> SharedString {
_ => task_type,
}
.to_owned()
.into()
}
#[cfg(test)]
@@ -141,7 +136,19 @@ mod tests {
label: "Debug my JS app".into(),
adapter: "JavaScript".into(),
config: json!({
"request": "launch",
"program": "${ZED_WORKTREE_ROOT}/xyz.js",
"showDevDebugOutput": false,
"stopOnEntry": true,
"args": [
"--foo",
"${ZED_WORKTREE_ROOT}/thing",
],
"cwd": "${ZED_WORKTREE_ROOT}/${FOO}/sub",
"env": {
"X": "Y",
},
"type": "node",
}),
tcp_connection: Some(TcpArgumentsTemplate {
port: Some(17),

View File

@@ -285,10 +285,12 @@ pub fn task_contexts(
.worktree_for_id(*worktree_id, cx)
.map_or(false, |worktree| is_visible_directory(&worktree, cx))
})
.or(workspace
.visible_worktrees(cx)
.next()
.map(|tree| tree.read(cx).id()));
.or_else(|| {
workspace
.visible_worktrees(cx)
.next()
.map(|tree| tree.read(cx).id())
});
let active_editor = active_item.and_then(|item| item.act_as::<Editor>(cx));

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition.workspace = true
name = "zed"
version = "0.191.0"
version = "0.191.1"
publish.workspace = true
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View File

@@ -1 +1 @@
dev
preview

View File

@@ -281,7 +281,7 @@ Given an externally-ran web server (e.g. with `npx serve` or `npx live-server`)
#### Go
Zed uses [delve](https://github.com/go-delve/delve?tab=readme-ov-file) to debug Go applications. Zed will automatically create debug scenarios for `func main` in your main packages, and also
for any tests, so you can use the Play button in the gutter to debug these without configuration. We do not yet support attaching to an existing running copy of delve.
for any tests, so you can use the Play button in the gutter to debug these without configuration.
##### Debug Go Packages
@@ -350,6 +350,27 @@ and the "build" command should build that.
}
```
##### Attaching to an existing instance of Delve
You might find yourself needing to connect to an existing instance of Delve that's not necessarily running on your machine; in such case, you can use `tcp_arguments` to instrument Zed's connection to Delve.
````
{
"adapter": "Delve",
"label": "Connect to a running Delve instance",
"program": "/Users/zed/Projects/language_repositories/golang/hello/hello",
"cwd": "/Users/zed/Projects/language_repositories/golang/hello",
"args": [],
"env": {},
"request": "launch",
"mode": "exec",
"stopOnEntry": false,
"tcp_connection": { "host": "123.456.789.012", "port": 53412 }
}
```
In such case Zed won't spawn a new instance of Delve, as it opts to use an existing one. The consequence of this is that *there will be no terminal* in Zed; you have to interact with the Delve instance directly, as it handles stdin/stdout of the debuggee.
### Ruby
To run a ruby task in the debugger, you will need to configure it in the `.zed/debug.json` file in your project. We don't yet have automatic detection of ruby tasks, nor do we support connecting to an existing process.
@@ -371,7 +392,7 @@ The configuration should look like this:
// "cwd": ""
}
}
```
````
## Breakpoints