Compare commits
6 Commits
acp-rewind
...
v0.187.1-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53faf0d78d | ||
|
|
f2050dfe2b | ||
|
|
4cfc49e2d4 | ||
|
|
72426a9608 | ||
|
|
1c638a1309 | ||
|
|
249597a4a8 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -18540,7 +18540,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.187.0"
|
||||
version = "0.187.1"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"agent",
|
||||
|
||||
@@ -383,18 +383,25 @@ fn render_markdown_code_block(
|
||||
)
|
||||
} else {
|
||||
let content = if let Some(parent) = path_range.path.parent() {
|
||||
let file_name = file_name.to_string_lossy().to_string();
|
||||
let path = parent.to_string_lossy().to_string();
|
||||
let path_and_file = format!("{}/{}", path, file_name);
|
||||
|
||||
h_flex()
|
||||
.id(("code-block-header-label", ix))
|
||||
.ml_1()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(file_name.to_string_lossy().to_string())
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Label::new(parent.to_string_lossy().to_string())
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.child(Label::new(file_name).size(LabelSize::Small))
|
||||
.child(Label::new(path).color(Color::Muted).size(LabelSize::Small))
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Jump to File",
|
||||
None,
|
||||
path_and_file.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.into_any_element()
|
||||
} else {
|
||||
Label::new(path_range.path.to_string_lossy().to_string())
|
||||
@@ -404,7 +411,7 @@ fn render_markdown_code_block(
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id(("code-block-header-label", ix))
|
||||
.id(("code-block-header-button", ix))
|
||||
.w_full()
|
||||
.max_w_full()
|
||||
.px_1()
|
||||
@@ -412,7 +419,6 @@ fn render_markdown_code_block(
|
||||
.cursor_pointer()
|
||||
.rounded_sm()
|
||||
.hover(|item| item.bg(cx.theme().colors().element_hover.opacity(0.5)))
|
||||
.tooltip(Tooltip::text("Jump to File"))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
@@ -462,10 +468,87 @@ fn render_markdown_code_block(
|
||||
.element_background
|
||||
.blend(cx.theme().colors().editor_foreground.opacity(0.01));
|
||||
|
||||
let control_buttons = h_flex()
|
||||
.visible_on_hover(CODEBLOCK_CONTAINER_GROUP)
|
||||
.absolute()
|
||||
.top_0()
|
||||
.right_0()
|
||||
.h_full()
|
||||
.bg(codeblock_header_bg)
|
||||
.rounded_tr_md()
|
||||
.px_1()
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::new(
|
||||
("copy-markdown-code", ix),
|
||||
if codeblock_was_copied {
|
||||
IconName::Check
|
||||
} else {
|
||||
IconName::Copy
|
||||
},
|
||||
)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::text("Copy Code"))
|
||||
.on_click({
|
||||
let active_thread = active_thread.clone();
|
||||
let parsed_markdown = parsed_markdown.clone();
|
||||
let code_block_range = metadata.content_range.clone();
|
||||
move |_event, _window, cx| {
|
||||
active_thread.update(cx, |this, cx| {
|
||||
this.copied_code_block_ids.insert((message_id, ix));
|
||||
|
||||
let code = parsed_markdown.source()[code_block_range.clone()].to_string();
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(code));
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||
|
||||
cx.update(|cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.copied_code_block_ids.remove(&(message_id, ix));
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
.when(can_expand, |header| {
|
||||
header.child(
|
||||
IconButton::new(
|
||||
("expand-collapse-code", ix),
|
||||
if is_expanded {
|
||||
IconName::ChevronUp
|
||||
} else {
|
||||
IconName::ChevronDown
|
||||
},
|
||||
)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::text(if is_expanded {
|
||||
"Collapse Code"
|
||||
} else {
|
||||
"Expand Code"
|
||||
}))
|
||||
.on_click({
|
||||
let active_thread = active_thread.clone();
|
||||
move |_event, _window, cx| {
|
||||
active_thread.update(cx, |this, cx| {
|
||||
this.toggle_codeblock_expanded(message_id, ix);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
});
|
||||
|
||||
let codeblock_header = h_flex()
|
||||
.py_1()
|
||||
.pl_1p5()
|
||||
.pr_1()
|
||||
.relative()
|
||||
.p_1()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.border_b_1()
|
||||
@@ -473,79 +556,7 @@ fn render_markdown_code_block(
|
||||
.bg(codeblock_header_bg)
|
||||
.rounded_t_md()
|
||||
.children(label)
|
||||
.child(
|
||||
h_flex()
|
||||
.visible_on_hover(CODEBLOCK_CONTAINER_GROUP)
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::new(
|
||||
("copy-markdown-code", ix),
|
||||
if codeblock_was_copied {
|
||||
IconName::Check
|
||||
} else {
|
||||
IconName::Copy
|
||||
},
|
||||
)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::text("Copy Code"))
|
||||
.on_click({
|
||||
let active_thread = active_thread.clone();
|
||||
let parsed_markdown = parsed_markdown.clone();
|
||||
let code_block_range = metadata.content_range.clone();
|
||||
move |_event, _window, cx| {
|
||||
active_thread.update(cx, |this, cx| {
|
||||
this.copied_code_block_ids.insert((message_id, ix));
|
||||
|
||||
let code =
|
||||
parsed_markdown.source()[code_block_range.clone()].to_string();
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(code));
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||
|
||||
cx.update(|cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.copied_code_block_ids.remove(&(message_id, ix));
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
.when(can_expand, |header| {
|
||||
header.child(
|
||||
IconButton::new(
|
||||
("expand-collapse-code", ix),
|
||||
if is_expanded {
|
||||
IconName::ChevronUp
|
||||
} else {
|
||||
IconName::ChevronDown
|
||||
},
|
||||
)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::text(if is_expanded {
|
||||
"Collapse Code"
|
||||
} else {
|
||||
"Expand Code"
|
||||
}))
|
||||
.on_click({
|
||||
let active_thread = active_thread.clone();
|
||||
move |_event, _window, cx| {
|
||||
active_thread.update(cx, |this, cx| {
|
||||
this.toggle_codeblock_expanded(message_id, ix);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
);
|
||||
.child(control_buttons);
|
||||
|
||||
v_flex()
|
||||
.group(CODEBLOCK_CONTAINER_GROUP)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::persistence::DebuggerPaneItem;
|
||||
use crate::session::DebugSession;
|
||||
use crate::session::running::RunningState;
|
||||
use crate::{
|
||||
ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
|
||||
FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart,
|
||||
@@ -30,7 +31,7 @@ use settings::Settings;
|
||||
use std::any::TypeId;
|
||||
use std::sync::Arc;
|
||||
use task::{DebugScenario, TaskContext};
|
||||
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
||||
use ui::{ContextMenu, Divider, Tooltip, prelude::*};
|
||||
use workspace::SplitDirection;
|
||||
use workspace::{
|
||||
Pane, Workspace,
|
||||
@@ -87,7 +88,20 @@ impl DebugPanel {
|
||||
})
|
||||
}
|
||||
|
||||
fn filter_action_types(&self, cx: &mut App) {
|
||||
pub(crate) fn sessions(&self) -> Vec<Entity<DebugSession>> {
|
||||
self.sessions.clone()
|
||||
}
|
||||
|
||||
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
||||
self.active_session.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn running_state(&self, cx: &mut App) -> Option<Entity<RunningState>> {
|
||||
self.active_session()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
}
|
||||
|
||||
pub(crate) fn filter_action_types(&self, cx: &mut App) {
|
||||
let (has_active_session, supports_restart, support_step_back, status) = self
|
||||
.active_session()
|
||||
.map(|item| {
|
||||
@@ -273,7 +287,7 @@ impl DebugPanel {
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
async fn register_session(
|
||||
pub(crate) async fn register_session(
|
||||
this: WeakEntity<Self>,
|
||||
session: Entity<Session>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
@@ -342,7 +356,7 @@ impl DebugPanel {
|
||||
Ok(debug_session)
|
||||
}
|
||||
|
||||
fn handle_restart_request(
|
||||
pub(crate) fn handle_restart_request(
|
||||
&mut self,
|
||||
mut curr_session: Entity<Session>,
|
||||
window: &mut Window,
|
||||
@@ -416,11 +430,12 @@ impl DebugPanel {
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
||||
self.active_session.clone()
|
||||
}
|
||||
|
||||
fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub(crate) fn close_session(
|
||||
&mut self,
|
||||
entity_id: EntityId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(session) = self
|
||||
.sessions
|
||||
.iter()
|
||||
@@ -474,93 +489,8 @@ impl DebugPanel {
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
fn sessions_drop_down_menu(
|
||||
&self,
|
||||
active_session: &Entity<DebugSession>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> DropdownMenu {
|
||||
let sessions = self.sessions.clone();
|
||||
let weak = cx.weak_entity();
|
||||
let label = active_session.read(cx).label_element(cx);
|
||||
|
||||
DropdownMenu::new_with_element(
|
||||
"debugger-session-list",
|
||||
label,
|
||||
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
||||
let context_menu = cx.weak_entity();
|
||||
for session in sessions.into_iter() {
|
||||
let weak_session = session.downgrade();
|
||||
let weak_session_id = weak_session.entity_id();
|
||||
|
||||
this = this.custom_entry(
|
||||
{
|
||||
let weak = weak.clone();
|
||||
let context_menu = context_menu.clone();
|
||||
move |_, cx| {
|
||||
weak_session
|
||||
.read_with(cx, |session, cx| {
|
||||
let context_menu = context_menu.clone();
|
||||
let id: SharedString =
|
||||
format!("debug-session-{}", session.session_id(cx).0)
|
||||
.into();
|
||||
h_flex()
|
||||
.w_full()
|
||||
.group(id.clone())
|
||||
.justify_between()
|
||||
.child(session.label_element(cx))
|
||||
.child(
|
||||
IconButton::new(
|
||||
"close-debug-session",
|
||||
IconName::Close,
|
||||
)
|
||||
.visible_on_hover(id.clone())
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let weak = weak.clone();
|
||||
move |_, window, cx| {
|
||||
weak.update(cx, |panel, cx| {
|
||||
panel.close_session(
|
||||
weak_session_id,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
context_menu
|
||||
.update(cx, |this, cx| {
|
||||
this.cancel(
|
||||
&Default::default(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.unwrap_or_else(|_| div().into_any_element())
|
||||
}
|
||||
},
|
||||
{
|
||||
let weak = weak.clone();
|
||||
move |window, cx| {
|
||||
weak.update(cx, |panel, cx| {
|
||||
panel.activate_session(session.clone(), window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
this
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn deploy_context_menu(
|
||||
pub(crate) fn deploy_context_menu(
|
||||
&mut self,
|
||||
position: Point<Pixels>,
|
||||
window: &mut Window,
|
||||
@@ -611,7 +541,11 @@ impl DebugPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
||||
pub(crate) fn top_controls_strip(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Div> {
|
||||
let active_session = self.active_session.clone();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let is_side = self.position(window, cx).axis() == gpui::Axis::Horizontal;
|
||||
@@ -651,12 +585,12 @@ impl DebugPanel {
|
||||
active_session
|
||||
.as_ref()
|
||||
.map(|session| session.read(cx).running_state()),
|
||||
|this, running_session| {
|
||||
|this, running_state| {
|
||||
let thread_status =
|
||||
running_session.read(cx).thread_status(cx).unwrap_or(
|
||||
running_state.read(cx).thread_status(cx).unwrap_or(
|
||||
project::debugger::session::ThreadStatus::Exited,
|
||||
);
|
||||
let capabilities = running_session.read(cx).capabilities(cx);
|
||||
let capabilities = running_state.read(cx).capabilities(cx);
|
||||
this.map(|this| {
|
||||
if thread_status == ThreadStatus::Running {
|
||||
this.child(
|
||||
@@ -667,7 +601,7 @@ impl DebugPanel {
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
&running_state,
|
||||
|this, _, _window, cx| {
|
||||
this.pause_thread(cx);
|
||||
},
|
||||
@@ -694,7 +628,7 @@ impl DebugPanel {
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
&running_state,
|
||||
|this, _, _window, cx| this.continue_thread(cx),
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
@@ -718,7 +652,7 @@ impl DebugPanel {
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
&running_state,
|
||||
|this, _, _window, cx| {
|
||||
this.step_over(cx);
|
||||
},
|
||||
@@ -742,7 +676,7 @@ impl DebugPanel {
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
&running_state,
|
||||
|this, _, _window, cx| {
|
||||
this.step_out(cx);
|
||||
},
|
||||
@@ -769,7 +703,7 @@ impl DebugPanel {
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
&running_state,
|
||||
|this, _, _window, cx| {
|
||||
this.step_in(cx);
|
||||
},
|
||||
@@ -819,7 +753,7 @@ impl DebugPanel {
|
||||
|| thread_status == ThreadStatus::Ended,
|
||||
)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
&running_state,
|
||||
|this, _, _window, cx| {
|
||||
this.toggle_ignore_breakpoints(cx);
|
||||
},
|
||||
@@ -842,7 +776,7 @@ impl DebugPanel {
|
||||
IconButton::new("debug-restart", IconName::DebugRestart)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
&running_state,
|
||||
|this, _, _window, cx| {
|
||||
this.restart_session(cx);
|
||||
},
|
||||
@@ -864,7 +798,7 @@ impl DebugPanel {
|
||||
IconButton::new("debug-stop", IconName::Power)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
&running_state,
|
||||
|this, _, _window, cx| {
|
||||
this.stop_thread(cx);
|
||||
},
|
||||
@@ -898,7 +832,7 @@ impl DebugPanel {
|
||||
IconButton::new("debug-disconnect", IconName::DebugDetach)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
&running_state,
|
||||
|this, _, _, cx| {
|
||||
this.detach_client(cx);
|
||||
},
|
||||
@@ -932,30 +866,42 @@ impl DebugPanel {
|
||||
.as_ref()
|
||||
.map(|session| session.read(cx).running_state())
|
||||
.cloned(),
|
||||
|this, session| {
|
||||
this.child(
|
||||
session.update(cx, |this, cx| {
|
||||
this.thread_dropdown(window, cx)
|
||||
}),
|
||||
)
|
||||
|this, running_state| {
|
||||
this.children({
|
||||
let running_state = running_state.clone();
|
||||
let threads =
|
||||
running_state.update(cx, |running_state, cx| {
|
||||
let session = running_state.session();
|
||||
session
|
||||
.update(cx, |session, cx| session.threads(cx))
|
||||
});
|
||||
|
||||
self.render_thread_dropdown(
|
||||
&running_state,
|
||||
threads,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.when(!is_side, |this| this.gap_2().child(Divider::vertical()))
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.when_some(active_session.as_ref(), |this, session| {
|
||||
let context_menu =
|
||||
self.sessions_drop_down_menu(session, window, cx);
|
||||
this.child(context_menu).gap_2().child(Divider::vertical())
|
||||
})
|
||||
.children(self.render_session_menu(
|
||||
self.active_session(),
|
||||
self.running_state(cx),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.when(!is_side, |this| this.child(new_session_button())),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn activate_pane_in_direction(
|
||||
pub(crate) fn activate_pane_in_direction(
|
||||
&mut self,
|
||||
direction: SplitDirection,
|
||||
window: &mut Window,
|
||||
@@ -970,7 +916,7 @@ impl DebugPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_item(
|
||||
pub(crate) fn activate_item(
|
||||
&mut self,
|
||||
item: DebuggerPaneItem,
|
||||
window: &mut Window,
|
||||
@@ -985,7 +931,7 @@ impl DebugPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_session(
|
||||
pub(crate) fn activate_session(
|
||||
&mut self,
|
||||
session_item: Entity<DebugSession>,
|
||||
window: &mut Window,
|
||||
|
||||
@@ -13,6 +13,7 @@ use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
|
||||
|
||||
pub mod attach_modal;
|
||||
pub mod debugger_panel;
|
||||
mod dropdown_menus;
|
||||
mod new_session_modal;
|
||||
mod persistence;
|
||||
pub(crate) mod session;
|
||||
|
||||
186
crates/debugger_ui/src/dropdown_menus.rs
Normal file
186
crates/debugger_ui/src/dropdown_menus.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use gpui::Entity;
|
||||
use project::debugger::session::{ThreadId, ThreadStatus};
|
||||
use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
|
||||
|
||||
use crate::{
|
||||
debugger_panel::DebugPanel,
|
||||
session::{DebugSession, running::RunningState},
|
||||
};
|
||||
|
||||
impl DebugPanel {
|
||||
fn dropdown_label(label: impl Into<SharedString>) -> Label {
|
||||
Label::new(label).size(LabelSize::Small)
|
||||
}
|
||||
|
||||
pub fn render_session_menu(
|
||||
&mut self,
|
||||
active_session: Option<Entity<DebugSession>>,
|
||||
running_state: Option<Entity<RunningState>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<impl IntoElement> {
|
||||
if let Some(running_state) = running_state {
|
||||
let sessions = self.sessions().clone();
|
||||
let weak = cx.weak_entity();
|
||||
let running_state = running_state.read(cx);
|
||||
let label = if let Some(active_session) = active_session {
|
||||
active_session.read(cx).session(cx).read(cx).label()
|
||||
} else {
|
||||
SharedString::new_static("Unknown Session")
|
||||
};
|
||||
|
||||
let is_terminated = running_state.session().read(cx).is_terminated();
|
||||
let session_state_indicator = {
|
||||
if is_terminated {
|
||||
Some(Indicator::dot().color(Color::Error))
|
||||
} else {
|
||||
match running_state.thread_status(cx).unwrap_or_default() {
|
||||
project::debugger::session::ThreadStatus::Stopped => {
|
||||
Some(Indicator::dot().color(Color::Conflict))
|
||||
}
|
||||
_ => Some(Indicator::dot().color(Color::Success)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let trigger = h_flex()
|
||||
.gap_2()
|
||||
.when_some(session_state_indicator, |this, indicator| {
|
||||
this.child(indicator)
|
||||
})
|
||||
.justify_between()
|
||||
.child(
|
||||
DebugPanel::dropdown_label(label)
|
||||
.when(is_terminated, |this| this.strikethrough()),
|
||||
)
|
||||
.into_any_element();
|
||||
|
||||
Some(
|
||||
DropdownMenu::new_with_element(
|
||||
"debugger-session-list",
|
||||
trigger,
|
||||
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
||||
let context_menu = cx.weak_entity();
|
||||
for session in sessions.into_iter() {
|
||||
let weak_session = session.downgrade();
|
||||
let weak_session_id = weak_session.entity_id();
|
||||
|
||||
this = this.custom_entry(
|
||||
{
|
||||
let weak = weak.clone();
|
||||
let context_menu = context_menu.clone();
|
||||
move |_, cx| {
|
||||
weak_session
|
||||
.read_with(cx, |session, cx| {
|
||||
let context_menu = context_menu.clone();
|
||||
let id: SharedString = format!(
|
||||
"debug-session-{}",
|
||||
session.session_id(cx).0
|
||||
)
|
||||
.into();
|
||||
h_flex()
|
||||
.w_full()
|
||||
.group(id.clone())
|
||||
.justify_between()
|
||||
.child(session.label_element(cx))
|
||||
.child(
|
||||
IconButton::new(
|
||||
"close-debug-session",
|
||||
IconName::Close,
|
||||
)
|
||||
.visible_on_hover(id.clone())
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let weak = weak.clone();
|
||||
move |_, window, cx| {
|
||||
weak.update(cx, |panel, cx| {
|
||||
panel.close_session(
|
||||
weak_session_id,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
context_menu
|
||||
.update(cx, |this, cx| {
|
||||
this.cancel(
|
||||
&Default::default(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.unwrap_or_else(|_| div().into_any_element())
|
||||
}
|
||||
},
|
||||
{
|
||||
let weak = weak.clone();
|
||||
move |window, cx| {
|
||||
weak.update(cx, |panel, cx| {
|
||||
panel.activate_session(session.clone(), window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
this
|
||||
}),
|
||||
)
|
||||
.style(DropdownStyle::Ghost),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn render_thread_dropdown(
|
||||
&self,
|
||||
running_state: &Entity<RunningState>,
|
||||
threads: Vec<(dap::Thread, ThreadStatus)>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<DropdownMenu> {
|
||||
let running_state = running_state.clone();
|
||||
let running_state_read = running_state.read(cx);
|
||||
let thread_id = running_state_read.thread_id();
|
||||
let session = running_state_read.session();
|
||||
let session_id = session.read(cx).session_id();
|
||||
let session_terminated = session.read(cx).is_terminated();
|
||||
let selected_thread_name = threads
|
||||
.iter()
|
||||
.find(|(thread, _)| thread_id.map(|id| id.0) == Some(thread.id))
|
||||
.map(|(thread, _)| thread.name.clone());
|
||||
|
||||
if let Some(selected_thread_name) = selected_thread_name {
|
||||
let trigger = DebugPanel::dropdown_label(selected_thread_name).into_any_element();
|
||||
Some(
|
||||
DropdownMenu::new_with_element(
|
||||
("thread-list", session_id.0),
|
||||
trigger,
|
||||
ContextMenu::build_eager(window, cx, move |mut this, _, _| {
|
||||
for (thread, _) in threads {
|
||||
let running_state = running_state.clone();
|
||||
let thread_id = thread.id;
|
||||
this = this.entry(thread.name, None, move |window, cx| {
|
||||
running_state.update(cx, |running_state, cx| {
|
||||
running_state.select_thread(ThreadId(thread_id), window, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
this
|
||||
}),
|
||||
)
|
||||
.disabled(session_terminated)
|
||||
.style(DropdownStyle::Ghost),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod running;
|
||||
|
||||
use std::{cell::OnceCell, sync::OnceLock};
|
||||
|
||||
use crate::{StackTraceView, debugger_panel::DebugPanel, persistence::SerializedLayout};
|
||||
use dap::client::SessionId;
|
||||
use gpui::{
|
||||
App, Axis, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
|
||||
@@ -11,14 +10,13 @@ use project::debugger::session::Session;
|
||||
use project::worktree_store::WorktreeStore;
|
||||
use rpc::proto;
|
||||
use running::RunningState;
|
||||
use std::{cell::OnceCell, sync::OnceLock};
|
||||
use ui::{Indicator, prelude::*};
|
||||
use workspace::{
|
||||
CollaboratorId, FollowableItem, ViewId, Workspace,
|
||||
item::{self, Item},
|
||||
};
|
||||
|
||||
use crate::{StackTraceView, debugger_panel::DebugPanel, persistence::SerializedLayout};
|
||||
|
||||
pub struct DebugSession {
|
||||
remote_id: Option<workspace::ViewId>,
|
||||
running_state: Entity<RunningState>,
|
||||
@@ -159,7 +157,11 @@ impl DebugSession {
|
||||
.gap_2()
|
||||
.when_some(icon, |this, indicator| this.child(indicator))
|
||||
.justify_between()
|
||||
.child(Label::new(label).when(is_terminated, |this| this.strikethrough()))
|
||||
.child(
|
||||
Label::new(label)
|
||||
.size(LabelSize::Small)
|
||||
.when(is_terminated, |this| this.strikethrough()),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,10 @@ use task::{
|
||||
};
|
||||
use terminal_view::TerminalView;
|
||||
use ui::{
|
||||
ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu,
|
||||
Disableable, DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement,
|
||||
IntoElement, Label, LabelCommon as _, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Tab, Tooltip, VisibleOnHover, VisualContext, Window, div,
|
||||
h_flex, v_flex,
|
||||
ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, FluentBuilder,
|
||||
IconButton, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon as _,
|
||||
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Tab, Tooltip,
|
||||
VisibleOnHover, VisualContext, Window, div, h_flex, v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use variable_list::VariableList;
|
||||
@@ -78,6 +77,12 @@ pub struct RunningState {
|
||||
_schedule_serialize: Option<Task<()>>,
|
||||
}
|
||||
|
||||
impl RunningState {
|
||||
pub(crate) fn thread_id(&self) -> Option<ThreadId> {
|
||||
self.thread_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for RunningState {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let zoomed_pane = self
|
||||
@@ -515,7 +520,7 @@ impl Focusable for DebugTerminal {
|
||||
}
|
||||
|
||||
impl RunningState {
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
session: Entity<Session>,
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
@@ -1311,7 +1316,12 @@ impl RunningState {
|
||||
.map(|id| self.session().read(cx).thread_status(id))
|
||||
}
|
||||
|
||||
fn select_thread(&mut self, thread_id: ThreadId, window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub(crate) fn select_thread(
|
||||
&mut self,
|
||||
thread_id: ThreadId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.thread_id.is_some_and(|id| id == thread_id) {
|
||||
return;
|
||||
}
|
||||
@@ -1448,38 +1458,6 @@ impl RunningState {
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn thread_dropdown(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, RunningState>,
|
||||
) -> DropdownMenu {
|
||||
let state = cx.entity();
|
||||
let session_terminated = self.session.read(cx).is_terminated();
|
||||
let threads = self.session.update(cx, |this, cx| this.threads(cx));
|
||||
let selected_thread_name = threads
|
||||
.iter()
|
||||
.find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
|
||||
.map(|(thread, _)| thread.name.clone())
|
||||
.unwrap_or("Threads".to_owned());
|
||||
DropdownMenu::new(
|
||||
("thread-list", self.session_id.0),
|
||||
selected_thread_name,
|
||||
ContextMenu::build_eager(window, cx, move |mut this, _, _| {
|
||||
for (thread, _) in threads {
|
||||
let state = state.clone();
|
||||
let thread_id = thread.id;
|
||||
this = this.entry(thread.name, None, move |window, cx| {
|
||||
state.update(cx, |state, cx| {
|
||||
state.select_thread(ThreadId(thread_id), window, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
this
|
||||
}),
|
||||
)
|
||||
.disabled(session_terminated)
|
||||
}
|
||||
|
||||
fn default_pane_layout(
|
||||
project: Entity<Project>,
|
||||
workspace: &WeakEntity<Workspace>,
|
||||
|
||||
@@ -326,8 +326,14 @@ struct GroupedModels {
|
||||
|
||||
impl GroupedModels {
|
||||
pub fn new(other: Vec<ModelInfo>, recommended: Vec<ModelInfo>) -> Self {
|
||||
let recommended_ids: HashSet<_> = recommended.iter().map(|info| info.model.id()).collect();
|
||||
|
||||
let mut other_by_provider: IndexMap<_, Vec<ModelInfo>> = IndexMap::default();
|
||||
for model in other {
|
||||
if recommended_ids.contains(&model.model.id()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let provider = model.model.provider_id();
|
||||
if let Some(models) = other_by_provider.get_mut(&provider) {
|
||||
models.push(model);
|
||||
@@ -889,4 +895,26 @@ mod tests {
|
||||
let results = matcher.fuzzy_search("z4n");
|
||||
assert_models_eq(results, vec!["zed/gpt-4.1-nano"]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_exclude_recommended_models(_cx: &mut TestAppContext) {
|
||||
let recommended_models = create_models(vec![("zed", "claude")]);
|
||||
let all_models = create_models(vec![
|
||||
("zed", "claude"), // Should be filtered out from "other"
|
||||
("zed", "gemini"),
|
||||
("copilot", "o3"),
|
||||
]);
|
||||
|
||||
let grouped_models = GroupedModels::new(all_models, recommended_models);
|
||||
|
||||
let actual_other_models = grouped_models
|
||||
.other
|
||||
.values()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Recommended models should not appear in "other"
|
||||
assert_models_eq(actual_other_models, vec!["zed/gemini", "copilot/o3"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +628,7 @@ impl GoogleEventMapper {
|
||||
// responds with `finish_reason: STOP`
|
||||
if wants_to_use_tool {
|
||||
self.stop_reason = StopReason::ToolUse;
|
||||
events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));
|
||||
}
|
||||
events
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
use gpui::{ClickEvent, Corner, CursorStyle, Entity, MouseButton};
|
||||
use gpui::{ClickEvent, Corner, CursorStyle, Entity, Hsla, MouseButton};
|
||||
|
||||
use crate::{ContextMenu, PopoverMenu, prelude::*};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum DropdownStyle {
|
||||
#[default]
|
||||
Solid,
|
||||
Ghost,
|
||||
}
|
||||
|
||||
enum LabelKind {
|
||||
Text(SharedString),
|
||||
Element(AnyElement),
|
||||
@@ -11,6 +18,7 @@ enum LabelKind {
|
||||
pub struct DropdownMenu {
|
||||
id: ElementId,
|
||||
label: LabelKind,
|
||||
style: DropdownStyle,
|
||||
menu: Entity<ContextMenu>,
|
||||
full_width: bool,
|
||||
disabled: bool,
|
||||
@@ -25,6 +33,7 @@ impl DropdownMenu {
|
||||
Self {
|
||||
id: id.into(),
|
||||
label: LabelKind::Text(label.into()),
|
||||
style: DropdownStyle::default(),
|
||||
menu,
|
||||
full_width: false,
|
||||
disabled: false,
|
||||
@@ -39,12 +48,18 @@ impl DropdownMenu {
|
||||
Self {
|
||||
id: id.into(),
|
||||
label: LabelKind::Element(label),
|
||||
style: DropdownStyle::default(),
|
||||
menu,
|
||||
full_width: false,
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style(mut self, style: DropdownStyle) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn full_width(mut self, full_width: bool) -> Self {
|
||||
self.full_width = full_width;
|
||||
self
|
||||
@@ -66,7 +81,8 @@ impl RenderOnce for DropdownMenu {
|
||||
.trigger(
|
||||
DropdownMenuTrigger::new(self.label)
|
||||
.full_width(self.full_width)
|
||||
.disabled(self.disabled),
|
||||
.disabled(self.disabled)
|
||||
.style(self.style),
|
||||
)
|
||||
.attach(Corner::BottomLeft)
|
||||
}
|
||||
@@ -135,12 +151,35 @@ impl Component for DropdownMenu {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DropdownTriggerStyle {
|
||||
pub bg: Hsla,
|
||||
}
|
||||
|
||||
impl DropdownTriggerStyle {
|
||||
pub fn for_style(style: DropdownStyle, cx: &App) -> Self {
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
if style == DropdownStyle::Solid {
|
||||
Self {
|
||||
// why is this editor_background?
|
||||
bg: colors.editor_background,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
bg: colors.ghost_element_background,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
struct DropdownMenuTrigger {
|
||||
label: LabelKind,
|
||||
full_width: bool,
|
||||
selected: bool,
|
||||
disabled: bool,
|
||||
style: DropdownStyle,
|
||||
cursor_style: CursorStyle,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||
}
|
||||
@@ -152,6 +191,7 @@ impl DropdownMenuTrigger {
|
||||
full_width: false,
|
||||
selected: false,
|
||||
disabled: false,
|
||||
style: DropdownStyle::default(),
|
||||
cursor_style: CursorStyle::default(),
|
||||
on_click: None,
|
||||
}
|
||||
@@ -161,6 +201,11 @@ impl DropdownMenuTrigger {
|
||||
self.full_width = full_width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn style(mut self, style: DropdownStyle) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Disableable for DropdownMenuTrigger {
|
||||
@@ -193,11 +238,13 @@ impl RenderOnce for DropdownMenuTrigger {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let disabled = self.disabled;
|
||||
|
||||
let style = DropdownTriggerStyle::for_style(self.style, cx);
|
||||
|
||||
h_flex()
|
||||
.id("dropdown-menu-trigger")
|
||||
.justify_between()
|
||||
.rounded_sm()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.bg(style.bg)
|
||||
.pl_2()
|
||||
.pr_1p5()
|
||||
.py_0p5()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition.workspace = true
|
||||
name = "zed"
|
||||
version = "0.187.0"
|
||||
version = "0.187.1"
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
preview
|
||||
Reference in New Issue
Block a user