debugger: Add stack frame multibuffer (#30395)
This PR adds the ability to expand a debugger stack trace into a multi buffer and view each frame as it's own excerpt. Release Notes: - N/A --------- Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
parent
6f297132b4
commit
68afe4fdda
10 changed files with 604 additions and 37 deletions
|
@ -2,8 +2,9 @@ use crate::persistence::DebuggerPaneItem;
|
|||
use crate::session::DebugSession;
|
||||
use crate::{
|
||||
ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
|
||||
FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart, StepBack,
|
||||
StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, persistence,
|
||||
FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart,
|
||||
ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
|
||||
persistence,
|
||||
};
|
||||
use anyhow::{Result, anyhow};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
|
@ -67,11 +68,7 @@ pub struct DebugPanel {
|
|||
}
|
||||
|
||||
impl DebugPanel {
|
||||
pub fn new(
|
||||
workspace: &Workspace,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Entity<Self> {
|
||||
pub fn new(workspace: &Workspace, cx: &mut Context<Workspace>) -> Entity<Self> {
|
||||
cx.new(|cx| {
|
||||
let project = workspace.project().clone();
|
||||
|
||||
|
@ -119,6 +116,7 @@ impl DebugPanel {
|
|||
TypeId::of::<StepOver>(),
|
||||
TypeId::of::<StepInto>(),
|
||||
TypeId::of::<StepOut>(),
|
||||
TypeId::of::<ShowStackTrace>(),
|
||||
TypeId::of::<editor::actions::DebuggerRunToCursor>(),
|
||||
TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),
|
||||
];
|
||||
|
@ -170,8 +168,8 @@ impl DebugPanel {
|
|||
cx: &mut AsyncWindowContext,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
cx.spawn(async move |cx| {
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let debug_panel = DebugPanel::new(workspace, window, cx);
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let debug_panel = DebugPanel::new(workspace, cx);
|
||||
|
||||
workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
|
||||
workspace.project().read(cx).breakpoint_store().update(
|
||||
|
@ -421,6 +419,7 @@ impl DebugPanel {
|
|||
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>) {
|
||||
let Some(session) = self
|
||||
.sessions
|
||||
|
@ -999,7 +998,7 @@ impl DebugPanel {
|
|||
this.go_to_selected_stack_frame(window, cx);
|
||||
});
|
||||
});
|
||||
self.active_session = Some(session_item);
|
||||
self.active_session = Some(session_item.clone());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
|
|
@ -7,14 +7,16 @@ use new_session_modal::NewSessionModal;
|
|||
use project::debugger::{self, breakpoint_store::SourceBreakpoint};
|
||||
use session::DebugSession;
|
||||
use settings::Settings;
|
||||
use stack_trace_view::StackTraceView;
|
||||
use util::maybe;
|
||||
use workspace::{ShutdownDebugAdapters, Workspace};
|
||||
use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
|
||||
|
||||
pub mod attach_modal;
|
||||
pub mod debugger_panel;
|
||||
mod new_session_modal;
|
||||
mod persistence;
|
||||
pub(crate) mod session;
|
||||
mod stack_trace_view;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod tests;
|
||||
|
@ -41,6 +43,7 @@ actions!(
|
|||
FocusModules,
|
||||
FocusLoadedSources,
|
||||
FocusTerminal,
|
||||
ShowStackTrace,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -146,6 +149,38 @@ pub fn init(cx: &mut App) {
|
|||
})
|
||||
},
|
||||
)
|
||||
.register_action(
|
||||
|workspace: &mut Workspace, _: &ShowStackTrace, window, cx| {
|
||||
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx) {
|
||||
let is_active = workspace
|
||||
.active_item(cx)
|
||||
.is_some_and(|item| item.item_id() == existing.item_id());
|
||||
workspace.activate_item(&existing, true, !is_active, window, cx);
|
||||
} else {
|
||||
let Some(active_session) = debug_panel.read(cx).active_session() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let project = workspace.project();
|
||||
|
||||
let stack_trace_view = active_session.update(cx, |session, cx| {
|
||||
session.stack_trace_view(project, window, cx).clone()
|
||||
});
|
||||
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(stack_trace_view),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
.register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
|
||||
NewSessionModal::show(workspace, window, cx);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pub mod running;
|
||||
|
||||
use std::sync::OnceLock;
|
||||
use std::{cell::OnceCell, sync::OnceLock};
|
||||
|
||||
use dap::client::SessionId;
|
||||
use gpui::{
|
||||
|
@ -17,15 +17,16 @@ use workspace::{
|
|||
item::{self, Item},
|
||||
};
|
||||
|
||||
use crate::{debugger_panel::DebugPanel, persistence::SerializedLayout};
|
||||
use crate::{StackTraceView, debugger_panel::DebugPanel, persistence::SerializedLayout};
|
||||
|
||||
pub struct DebugSession {
|
||||
remote_id: Option<workspace::ViewId>,
|
||||
running_state: Entity<RunningState>,
|
||||
label: OnceLock<SharedString>,
|
||||
stack_trace_view: OnceCell<Entity<StackTraceView>>,
|
||||
_debug_panel: WeakEntity<DebugPanel>,
|
||||
_worktree_store: WeakEntity<WorktreeStore>,
|
||||
_workspace: WeakEntity<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
_subscriptions: [Subscription; 1],
|
||||
}
|
||||
|
||||
|
@ -66,8 +67,9 @@ impl DebugSession {
|
|||
running_state,
|
||||
label: OnceLock::new(),
|
||||
_debug_panel,
|
||||
stack_trace_view: OnceCell::new(),
|
||||
_worktree_store: project.read(cx).worktree_store().downgrade(),
|
||||
_workspace: workspace,
|
||||
workspace,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -75,6 +77,32 @@ impl DebugSession {
|
|||
self.running_state.read(cx).session_id()
|
||||
}
|
||||
|
||||
pub(crate) fn stack_trace_view(
|
||||
&mut self,
|
||||
project: &Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> &Entity<StackTraceView> {
|
||||
let workspace = self.workspace.clone();
|
||||
let running_state = self.running_state.clone();
|
||||
|
||||
self.stack_trace_view.get_or_init(|| {
|
||||
let stackframe_list = running_state.read(cx).stack_frame_list().clone();
|
||||
|
||||
let stack_frame_view = cx.new(|cx| {
|
||||
StackTraceView::new(
|
||||
workspace.clone(),
|
||||
project.clone(),
|
||||
stackframe_list,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
stack_frame_view
|
||||
})
|
||||
}
|
||||
|
||||
pub fn session(&self, cx: &App) -> Entity<Session> {
|
||||
self.running_state.read(cx).session().clone()
|
||||
}
|
||||
|
|
|
@ -1235,8 +1235,7 @@ impl RunningState {
|
|||
self.stack_frame_list.read(cx).selected_stack_frame_id()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
|
||||
pub(crate) fn stack_frame_list(&self) -> &Entity<StackFrameList> {
|
||||
&self.stack_frame_list
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,6 @@ impl Console {
|
|||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor
|
||||
});
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let this = cx.weak_entity();
|
||||
let query_bar = cx.new(|cx| {
|
||||
|
@ -77,6 +76,8 @@ impl Console {
|
|||
editor
|
||||
});
|
||||
|
||||
let focus_handle = query_bar.focus_handle(cx);
|
||||
|
||||
let _subscriptions =
|
||||
vec![cx.subscribe(&stack_frame_list, Self::handle_stack_frame_list_events)];
|
||||
|
||||
|
@ -110,6 +111,7 @@ impl Console {
|
|||
) {
|
||||
match event {
|
||||
StackFrameListEvent::SelectedStackFrameChanged(_) => cx.notify(),
|
||||
StackFrameListEvent::BuiltEntries => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,13 +15,16 @@ use project::debugger::session::{Session, SessionEvent, StackFrame};
|
|||
use project::{ProjectItem, ProjectPath};
|
||||
use ui::{Scrollbar, ScrollbarState, Tooltip, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
use workspace::{ItemHandle, Workspace};
|
||||
|
||||
use crate::StackTraceView;
|
||||
|
||||
use super::RunningState;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StackFrameListEvent {
|
||||
SelectedStackFrameChanged(StackFrameId),
|
||||
BuiltEntries,
|
||||
}
|
||||
|
||||
pub struct StackFrameList {
|
||||
|
@ -101,13 +104,18 @@ impl StackFrameList {
|
|||
&self.entries
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn flatten_entries(&self) -> Vec<dap::StackFrame> {
|
||||
pub(crate) fn flatten_entries(&self, show_collapsed: bool) -> Vec<dap::StackFrame> {
|
||||
self.entries
|
||||
.iter()
|
||||
.flat_map(|frame| match frame {
|
||||
StackFrameEntry::Normal(frame) => vec![frame.clone()],
|
||||
StackFrameEntry::Collapsed(frames) => frames.clone(),
|
||||
StackFrameEntry::Collapsed(frames) => {
|
||||
if show_collapsed {
|
||||
frames.clone()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
@ -136,6 +144,25 @@ impl StackFrameList {
|
|||
self.selected_stack_frame_id
|
||||
}
|
||||
|
||||
pub(crate) fn select_stack_frame_id(
|
||||
&mut self,
|
||||
id: StackFrameId,
|
||||
window: &Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if !self.entries.iter().any(|entry| match entry {
|
||||
StackFrameEntry::Normal(entry) => entry.id == id,
|
||||
StackFrameEntry::Collapsed(stack_frames) => {
|
||||
stack_frames.iter().any(|frame| frame.id == id)
|
||||
}
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.selected_stack_frame_id = Some(id);
|
||||
self.go_to_selected_stack_frame(window, cx);
|
||||
}
|
||||
|
||||
pub(super) fn schedule_refresh(
|
||||
&mut self,
|
||||
select_first: bool,
|
||||
|
@ -206,6 +233,7 @@ impl StackFrameList {
|
|||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
cx.emit(StackFrameListEvent::BuiltEntries);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
@ -255,7 +283,7 @@ impl StackFrameList {
|
|||
|
||||
let row = (stack_frame.line.saturating_sub(1)) as u32;
|
||||
|
||||
let Some(abs_path) = self.abs_path_from_stack_frame(&stack_frame) else {
|
||||
let Some(abs_path) = Self::abs_path_from_stack_frame(&stack_frame) else {
|
||||
return Task::ready(Err(anyhow!("Project path not found")));
|
||||
};
|
||||
|
||||
|
@ -294,12 +322,22 @@ impl StackFrameList {
|
|||
let project_path = buffer.read(cx).project_path(cx).ok_or_else(|| {
|
||||
anyhow!("Could not select a stack frame for unnamed buffer")
|
||||
})?;
|
||||
|
||||
let open_preview = !workspace
|
||||
.item_of_type::<StackTraceView>(cx)
|
||||
.map(|viewer| {
|
||||
workspace
|
||||
.active_item(cx)
|
||||
.is_some_and(|item| item.item_id() == viewer.item_id())
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
anyhow::Ok(workspace.open_path_preview(
|
||||
project_path,
|
||||
None,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
open_preview,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
|
@ -332,7 +370,7 @@ impl StackFrameList {
|
|||
})
|
||||
}
|
||||
|
||||
fn abs_path_from_stack_frame(&self, stack_frame: &dap::StackFrame) -> Option<Arc<Path>> {
|
||||
pub(crate) fn abs_path_from_stack_frame(stack_frame: &dap::StackFrame) -> Option<Arc<Path>> {
|
||||
stack_frame.source.as_ref().and_then(|s| {
|
||||
s.path
|
||||
.as_deref()
|
||||
|
|
|
@ -302,6 +302,7 @@ impl VariableList {
|
|||
self.selected_stack_frame_id = Some(*stack_frame_id);
|
||||
cx.notify();
|
||||
}
|
||||
StackFrameListEvent::BuiltEntries => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
453
crates/debugger_ui/src/stack_trace_view.rs
Normal file
453
crates/debugger_ui/src/stack_trace_view.rs
Normal file
|
@ -0,0 +1,453 @@
|
|||
use std::any::{Any, TypeId};
|
||||
|
||||
use collections::HashMap;
|
||||
use dap::StackFrameId;
|
||||
use editor::{
|
||||
Anchor, Bias, DebugStackFrameLine, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer,
|
||||
RowHighlightOptions, ToPoint, scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{
|
||||
AnyView, App, AppContext, Entity, EventEmitter, Focusable, IntoElement, Render, SharedString,
|
||||
Subscription, Task, WeakEntity, Window,
|
||||
};
|
||||
use language::{BufferSnapshot, Capability, Point, Selection, SelectionGoal, TreeSitterOptions};
|
||||
use project::{Project, ProjectPath};
|
||||
use ui::{ActiveTheme as _, Context, ParentElement as _, Styled as _, div};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{
|
||||
Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace,
|
||||
item::{BreadcrumbText, ItemEvent},
|
||||
searchable::SearchableItemHandle,
|
||||
};
|
||||
|
||||
use crate::session::running::stack_frame_list::{StackFrameList, StackFrameListEvent};
|
||||
use anyhow::Result;
|
||||
|
||||
pub(crate) struct StackTraceView {
|
||||
editor: Entity<Editor>,
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
stack_frame_list: Entity<StackFrameList>,
|
||||
selected_stack_frame_id: Option<StackFrameId>,
|
||||
highlights: Vec<(StackFrameId, Anchor)>,
|
||||
excerpt_for_frames: collections::HashMap<ExcerptId, StackFrameId>,
|
||||
refresh_task: Option<Task<Result<()>>>,
|
||||
_subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl StackTraceView {
|
||||
pub(crate) fn new(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
stack_frame_list: Entity<StackFrameList>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor =
|
||||
Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
|
||||
editor.set_vertical_scroll_margin(5, cx);
|
||||
editor
|
||||
});
|
||||
|
||||
cx.subscribe_in(&editor, window, |this, editor, event, window, cx| {
|
||||
if let EditorEvent::SelectionsChanged { local: true } = event {
|
||||
let excerpt_id = editor.update(cx, |editor, cx| {
|
||||
let position: Point = editor.selections.newest(cx).head();
|
||||
|
||||
editor
|
||||
.snapshot(window, cx)
|
||||
.buffer_snapshot
|
||||
.excerpt_containing(position..position)
|
||||
.map(|excerpt| excerpt.id())
|
||||
});
|
||||
|
||||
if let Some(stack_frame_id) = excerpt_id
|
||||
.and_then(|id| this.excerpt_for_frames.get(&id))
|
||||
.filter(|id| Some(**id) != this.selected_stack_frame_id)
|
||||
{
|
||||
this.stack_frame_list.update(cx, |list, cx| {
|
||||
list.select_stack_frame_id(*stack_frame_id, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.subscribe_in(
|
||||
&stack_frame_list,
|
||||
window,
|
||||
|this, stack_frame_list, event, window, cx| match event {
|
||||
StackFrameListEvent::BuiltEntries => {
|
||||
this.selected_stack_frame_id =
|
||||
stack_frame_list.read(cx).selected_stack_frame_id();
|
||||
this.update_excerpts(window, cx);
|
||||
}
|
||||
StackFrameListEvent::SelectedStackFrameChanged(selected_frame_id) => {
|
||||
this.selected_stack_frame_id = Some(*selected_frame_id);
|
||||
this.update_highlights(window, cx);
|
||||
|
||||
if let Some(frame_anchor) = this
|
||||
.highlights
|
||||
.iter()
|
||||
.find(|(frame_id, _)| frame_id == selected_frame_id)
|
||||
.map(|highlight| highlight.1)
|
||||
{
|
||||
this.editor.update(cx, |editor, cx| {
|
||||
if frame_anchor.excerpt_id
|
||||
!= editor.selections.newest_anchor().head().excerpt_id
|
||||
{
|
||||
let auto_scroll =
|
||||
Some(Autoscroll::center().for_anchor(frame_anchor));
|
||||
|
||||
editor.change_selections(auto_scroll, window, cx, |selections| {
|
||||
let selection_id = selections.new_selection_id();
|
||||
|
||||
let selection = Selection {
|
||||
id: selection_id,
|
||||
start: frame_anchor,
|
||||
end: frame_anchor,
|
||||
goal: SelectionGoal::None,
|
||||
reversed: false,
|
||||
};
|
||||
|
||||
selections.select_anchors(vec![selection]);
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
let mut this = Self {
|
||||
editor,
|
||||
multibuffer,
|
||||
workspace,
|
||||
project,
|
||||
excerpt_for_frames: HashMap::default(),
|
||||
highlights: Vec::default(),
|
||||
stack_frame_list,
|
||||
selected_stack_frame_id: None,
|
||||
refresh_task: None,
|
||||
_subscription: None,
|
||||
};
|
||||
|
||||
this.update_excerpts(window, cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.refresh_task.take();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.clear_highlights::<DebugStackFrameLine>(cx)
|
||||
});
|
||||
|
||||
let stack_frames = self
|
||||
.stack_frame_list
|
||||
.update(cx, |list, _| list.flatten_entries(false));
|
||||
|
||||
let frames_to_open: Vec<_> = stack_frames
|
||||
.into_iter()
|
||||
.filter_map(|frame| {
|
||||
Some((
|
||||
frame.id,
|
||||
frame.line as u32 - 1,
|
||||
StackFrameList::abs_path_from_stack_frame(&frame)?,
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.multibuffer
|
||||
.update(cx, |multi_buffer, cx| multi_buffer.clear(cx));
|
||||
|
||||
let task = cx.spawn_in(window, async move |this, cx| {
|
||||
let mut to_highlights = Vec::default();
|
||||
|
||||
for (stack_frame_id, line, abs_path) in frames_to_open {
|
||||
let (worktree, relative_path) = this
|
||||
.update(cx, |this, cx| {
|
||||
this.workspace.update(cx, |workspace, cx| {
|
||||
workspace.project().update(cx, |this, cx| {
|
||||
this.find_or_create_worktree(&abs_path, false, cx)
|
||||
})
|
||||
})
|
||||
})??
|
||||
.await?;
|
||||
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: worktree.read_with(cx, |tree, _| tree.id())?,
|
||||
path: relative_path.into(),
|
||||
};
|
||||
|
||||
if let Some(buffer) = this
|
||||
.read_with(cx, |this, _| this.project.clone())?
|
||||
.update(cx, |project, cx| project.open_buffer(project_path, cx))?
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
this.update(cx, |this, cx| {
|
||||
this.multibuffer.update(cx, |multi_buffer, cx| {
|
||||
let line_point = Point::new(line, 0);
|
||||
let start_context = Self::heuristic_syntactic_expand(
|
||||
&buffer.read(cx).snapshot(),
|
||||
line_point,
|
||||
);
|
||||
|
||||
// Users will want to see what happened before an active debug line in most cases
|
||||
let range = ExcerptRange {
|
||||
context: start_context..Point::new(line.saturating_add(1), 0),
|
||||
primary: line_point..line_point,
|
||||
};
|
||||
multi_buffer.push_excerpts(buffer.clone(), vec![range], cx);
|
||||
|
||||
let line_anchor =
|
||||
multi_buffer.buffer_point_to_anchor(&buffer, line_point, cx);
|
||||
|
||||
if let Some(line_anchor) = line_anchor {
|
||||
this.excerpt_for_frames
|
||||
.insert(line_anchor.excerpt_id, stack_frame_id);
|
||||
to_highlights.push((stack_frame_id, line_anchor));
|
||||
}
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.highlights = to_highlights;
|
||||
this.update_highlights(window, cx);
|
||||
})
|
||||
.ok();
|
||||
|
||||
anyhow::Ok(())
|
||||
});
|
||||
|
||||
self.refresh_task = Some(task);
|
||||
}
|
||||
|
||||
fn update_highlights(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.editor.update(cx, |editor, _| {
|
||||
editor.clear_row_highlights::<DebugStackFrameLine>()
|
||||
});
|
||||
|
||||
let stack_frames = self
|
||||
.stack_frame_list
|
||||
.update(cx, |session, _| session.flatten_entries(false));
|
||||
|
||||
let active_idx = self
|
||||
.selected_stack_frame_id
|
||||
.and_then(|id| {
|
||||
stack_frames
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(idx, frame)| if frame.id == id { Some(idx) } else { None })
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(window, cx).display_snapshot;
|
||||
let first_color = cx.theme().colors().editor_debugger_active_line_background;
|
||||
|
||||
let color = first_color.opacity(0.5);
|
||||
|
||||
let mut is_first = true;
|
||||
|
||||
for (_, highlight) in self.highlights.iter().skip(active_idx) {
|
||||
let position = highlight.to_point(&snapshot.buffer_snapshot);
|
||||
let color = if is_first {
|
||||
is_first = false;
|
||||
first_color
|
||||
} else {
|
||||
color
|
||||
};
|
||||
|
||||
let start = snapshot
|
||||
.buffer_snapshot
|
||||
.clip_point(Point::new(position.row, 0), Bias::Left);
|
||||
let end = start + Point::new(1, 0);
|
||||
let start = snapshot.buffer_snapshot.anchor_before(start);
|
||||
let end = snapshot.buffer_snapshot.anchor_before(end);
|
||||
editor.highlight_rows::<DebugStackFrameLine>(
|
||||
start..end,
|
||||
color,
|
||||
RowHighlightOptions::default(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn heuristic_syntactic_expand(snapshot: &BufferSnapshot, selected_point: Point) -> Point {
|
||||
let mut text_objects = snapshot.text_object_ranges(
|
||||
selected_point..selected_point,
|
||||
TreeSitterOptions::max_start_depth(4),
|
||||
);
|
||||
|
||||
let mut start_position = text_objects
|
||||
.find(|(_, obj)| matches!(obj, language::TextObject::AroundFunction))
|
||||
.map(|(range, _)| snapshot.offset_to_point(range.start))
|
||||
.map(|point| Point::new(point.row.max(selected_point.row.saturating_sub(8)), 0))
|
||||
.unwrap_or(selected_point);
|
||||
|
||||
if start_position.row == selected_point.row {
|
||||
start_position.row = start_position.row.saturating_sub(1);
|
||||
}
|
||||
|
||||
start_position
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for StackTraceView {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
div().size_full().child(self.editor.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for StackTraceView {}
|
||||
impl Focusable for StackTraceView {
|
||||
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for StackTraceView {
|
||||
type Event = EditorEvent;
|
||||
|
||||
fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
|
||||
Editor::to_item_events(event, f)
|
||||
}
|
||||
|
||||
fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.deactivated(window, cx));
|
||||
}
|
||||
|
||||
fn navigate(
|
||||
&mut self,
|
||||
data: Box<dyn Any>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.navigate(data, window, cx))
|
||||
}
|
||||
|
||||
fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> {
|
||||
Some("Stack Frame Viewer".into())
|
||||
}
|
||||
|
||||
fn tab_content_text(&self, _detail: usize, _: &App) -> SharedString {
|
||||
"Stack Frames".into()
|
||||
}
|
||||
|
||||
fn for_each_project_item(
|
||||
&self,
|
||||
cx: &App,
|
||||
f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem),
|
||||
) {
|
||||
self.editor.for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn is_singleton(&self, _: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
nav_history: ItemNavHistory,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, _| {
|
||||
editor.set_nav_history(Some(nav_history));
|
||||
});
|
||||
}
|
||||
|
||||
fn is_dirty(&self, cx: &App) -> bool {
|
||||
self.multibuffer.read(cx).is_dirty(cx)
|
||||
}
|
||||
|
||||
fn has_deleted_file(&self, cx: &App) -> bool {
|
||||
self.multibuffer.read(cx).has_deleted_file(cx)
|
||||
}
|
||||
|
||||
fn has_conflict(&self, cx: &App) -> bool {
|
||||
self.multibuffer.read(cx).has_conflict(cx)
|
||||
}
|
||||
|
||||
fn can_save(&self, _: &App) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn save(
|
||||
&mut self,
|
||||
format: bool,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.editor.save(format, project, window, cx)
|
||||
}
|
||||
|
||||
fn save_as(
|
||||
&mut self,
|
||||
_: Entity<Project>,
|
||||
_: ProjectPath,
|
||||
_window: &mut Window,
|
||||
_: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn reload(
|
||||
&mut self,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.editor.reload(project, window, cx)
|
||||
}
|
||||
|
||||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
self_handle: &'a Entity<Self>,
|
||||
_: &'a App,
|
||||
) -> Option<AnyView> {
|
||||
if type_id == TypeId::of::<Self>() {
|
||||
Some(self_handle.to_any())
|
||||
} else if type_id == TypeId::of::<Editor>() {
|
||||
Some(self.editor.to_any())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation {
|
||||
ToolbarItemLocation::PrimaryLeft
|
||||
}
|
||||
|
||||
fn breadcrumbs(&self, theme: &theme::Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
|
||||
self.editor.breadcrumbs(theme, cx)
|
||||
}
|
||||
|
||||
fn added_to_workspace(
|
||||
&mut self,
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.added_to_workspace(workspace, window, cx)
|
||||
});
|
||||
}
|
||||
}
|
|
@ -190,7 +190,7 @@ async fn test_basic_fetch_initial_scope_and_variables(
|
|||
running_state.update(cx, |running_state, cx| {
|
||||
let (stack_frame_list, stack_frame_id) =
|
||||
running_state.stack_frame_list().update(cx, |list, _| {
|
||||
(list.flatten_entries(), list.selected_stack_frame_id())
|
||||
(list.flatten_entries(true), list.selected_stack_frame_id())
|
||||
});
|
||||
|
||||
assert_eq!(stack_frames, stack_frame_list);
|
||||
|
@ -431,7 +431,7 @@ async fn test_fetch_variables_for_multiple_scopes(
|
|||
running_state.update(cx, |running_state, cx| {
|
||||
let (stack_frame_list, stack_frame_id) =
|
||||
running_state.stack_frame_list().update(cx, |list, _| {
|
||||
(list.flatten_entries(), list.selected_stack_frame_id())
|
||||
(list.flatten_entries(true), list.selected_stack_frame_id())
|
||||
});
|
||||
|
||||
assert_eq!(Some(1), stack_frame_id);
|
||||
|
@ -1452,7 +1452,7 @@ async fn test_variable_list_only_sends_requests_when_rendering(
|
|||
running_state.update(cx, |running_state, cx| {
|
||||
let (stack_frame_list, stack_frame_id) =
|
||||
running_state.stack_frame_list().update(cx, |list, _| {
|
||||
(list.flatten_entries(), list.selected_stack_frame_id())
|
||||
(list.flatten_entries(true), list.selected_stack_frame_id())
|
||||
});
|
||||
|
||||
assert_eq!(Some(1), stack_frame_id);
|
||||
|
@ -1734,7 +1734,7 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
|
|||
running_state.update(cx, |running_state, cx| {
|
||||
let (stack_frame_list, stack_frame_id) =
|
||||
running_state.stack_frame_list().update(cx, |list, _| {
|
||||
(list.flatten_entries(), list.selected_stack_frame_id())
|
||||
(list.flatten_entries(true), list.selected_stack_frame_id())
|
||||
});
|
||||
|
||||
let variable_list = running_state.variable_list().read(cx);
|
||||
|
@ -1789,7 +1789,7 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
|
|||
running_state.update(cx, |running_state, cx| {
|
||||
let (stack_frame_list, stack_frame_id) =
|
||||
running_state.stack_frame_list().update(cx, |list, _| {
|
||||
(list.flatten_entries(), list.selected_stack_frame_id())
|
||||
(list.flatten_entries(true), list.selected_stack_frame_id())
|
||||
});
|
||||
|
||||
let variable_list = running_state.variable_list().read(cx);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue