Merge pull request #1089 from zed-industries/jump-to-diagnostic

Jump to diagnostic
This commit is contained in:
Antonio Scandurra 2022-06-02 09:35:57 +02:00 committed by GitHub
commit f1964cf2a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 491 additions and 106 deletions

View file

@ -8,12 +8,13 @@ use editor::{
highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, MultiBuffer, ToOffset,
};
use gpui::{
actions, elements::*, fonts::TextStyle, serde_json, AnyViewHandle, AppContext, Entity,
ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
WeakViewHandle,
actions, elements::*, fonts::TextStyle, impl_internal_actions, platform::CursorStyle,
serde_json, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext,
Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use language::{
Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal,
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
SelectionGoal, ToPoint,
};
use project::{DiagnosticSummary, Project, ProjectPath};
use serde_json::json;
@ -27,15 +28,18 @@ use std::{
path::PathBuf,
sync::Arc,
};
use util::TryFutureExt;
use util::{ResultExt, TryFutureExt};
use workspace::{ItemHandle as _, ItemNavHistory, Workspace};
actions!(diagnostics, [Deploy]);
impl_internal_actions!(diagnostics, [Jump]);
const CONTEXT_LINE_COUNT: u32 = 1;
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ProjectDiagnosticsEditor::deploy);
cx.add_action(ProjectDiagnosticsEditor::jump);
items::init(cx);
}
@ -56,6 +60,13 @@ struct PathState {
diagnostic_groups: Vec<DiagnosticGroupState>,
}
#[derive(Clone, Debug)]
struct Jump {
path: ProjectPath,
position: Point,
anchor: Anchor,
}
struct DiagnosticGroupState {
primary_diagnostic: DiagnosticEntry<language::Anchor>,
primary_excerpt_ix: usize,
@ -177,6 +188,30 @@ impl ProjectDiagnosticsEditor {
}
}
fn jump(workspace: &mut Workspace, action: &Jump, cx: &mut ViewContext<Workspace>) {
let editor = workspace.open_path(action.path.clone(), true, cx);
let position = action.position;
let anchor = action.anchor;
cx.spawn_weak(|_, mut cx| async move {
let editor = editor.await.log_err()?.downcast::<Editor>()?;
editor.update(&mut cx, |editor, cx| {
let buffer = editor.buffer().read(cx).as_singleton()?;
let buffer = buffer.read(cx);
let cursor = if buffer.can_resolve(&anchor) {
anchor.to_point(buffer)
} else {
buffer.clip_point(position, Bias::Left)
};
editor.change_selections(Some(Autoscroll::Newest), cx, |s| {
s.select_ranges([cursor..cursor]);
});
Some(())
})?;
Some(())
})
.detach()
}
fn update_excerpts(&mut self, cx: &mut ViewContext<Self>) {
let paths = mem::take(&mut self.paths_to_update);
let project = self.project.clone();
@ -312,13 +347,20 @@ impl ProjectDiagnosticsEditor {
is_first_excerpt_for_group = false;
let mut primary =
group.entries[group.primary_ix].diagnostic.clone();
let anchor = group.entries[group.primary_ix].range.start;
let position = anchor.to_point(&snapshot);
primary.message =
primary.message.split('\n').next().unwrap().to_string();
group_state.block_count += 1;
blocks_to_add.push(BlockProperties {
position: header_position,
height: 2,
render: diagnostic_header_renderer(primary),
render: diagnostic_header_renderer(
primary,
path.clone(),
position,
anchor,
),
disposition: BlockDisposition::Above,
});
}
@ -575,12 +617,20 @@ impl workspace::Item for ProjectDiagnosticsEditor {
}
}
fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
fn diagnostic_header_renderer(
diagnostic: Diagnostic,
path: ProjectPath,
position: Point,
anchor: Anchor,
) -> RenderBlock {
enum JumpIcon {}
let (message, highlights) = highlight_diagnostic_message(&diagnostic.message);
Arc::new(move |cx| {
let settings = cx.global::<Settings>();
let tooltip_style = settings.theme.tooltip.clone();
let theme = &settings.theme.editor;
let style = &theme.diagnostic_header;
let style = theme.diagnostic_header.clone();
let font_size = (style.text_scale_factor * settings.buffer_font_size).round();
let icon_width = cx.em_width * style.icon_width_factor;
let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
@ -591,6 +641,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.with_color(theme.warning_diagnostic.message.text.color)
};
let x_padding = cx.gutter_padding + cx.scroll_x * cx.em_width;
Flex::row()
.with_child(
icon.constrained()
@ -618,9 +669,47 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.aligned()
.boxed()
}))
.with_child(
MouseEventHandler::new::<JumpIcon, _, _>(diagnostic.group_id, cx, |state, _| {
let style = style.jump_icon.style_for(state, false);
Svg::new("icons/jump.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
.aligned()
.contained()
.with_style(style.container)
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click({
let path = path.clone();
move |_, _, cx| {
cx.dispatch_action(Jump {
path: path.clone(),
position,
anchor,
});
}
})
.with_tooltip(
diagnostic.group_id,
"Jump to diagnostic".to_string(),
Some(Box::new(editor::OpenExcerpts)),
tooltip_style,
cx,
)
.aligned()
.flex_float()
.boxed(),
)
.contained()
.with_style(style.container)
.with_padding_left(cx.gutter_padding + cx.scroll_x * cx.em_width)
.with_padding_left(x_padding)
.with_padding_right(x_padding)
.expanded()
.named("diagnostic header")
})
@ -702,7 +791,7 @@ mod tests {
use super::*;
use editor::{
display_map::{BlockContext, TransformBlock},
DisplayPoint, EditorSnapshot,
DisplayPoint,
};
use gpui::TestAppContext;
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16};
@ -835,10 +924,8 @@ mod tests {
view.next_notification(&cx).await;
view.update(cx, |view, cx| {
let editor = view.editor.update(cx, |editor, cx| editor.snapshot(cx));
assert_eq!(
editor_blocks(&editor, cx),
editor_blocks(&view.editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
@ -848,7 +935,7 @@ mod tests {
]
);
assert_eq!(
editor.text(),
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
//
// main.rs
@ -923,10 +1010,8 @@ mod tests {
view.next_notification(&cx).await;
view.update(cx, |view, cx| {
let editor = view.editor.update(cx, |editor, cx| editor.snapshot(cx));
assert_eq!(
editor_blocks(&editor, cx),
editor_blocks(&view.editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
@ -938,7 +1023,7 @@ mod tests {
]
);
assert_eq!(
editor.text(),
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
//
// consts.rs
@ -1038,10 +1123,8 @@ mod tests {
view.next_notification(&cx).await;
view.update(cx, |view, cx| {
let editor = view.editor.update(cx, |editor, cx| editor.snapshot(cx));
assert_eq!(
editor_blocks(&editor, cx),
editor_blocks(&view.editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
@ -1055,7 +1138,7 @@ mod tests {
]
);
assert_eq!(
editor.text(),
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
//
// consts.rs
@ -1115,36 +1198,44 @@ mod tests {
});
}
fn editor_blocks(editor: &EditorSnapshot, cx: &AppContext) -> Vec<(u32, String)> {
editor
.blocks_in_range(0..editor.max_point().row())
.filter_map(|(row, block)| {
let name = match block {
TransformBlock::Custom(block) => block
.render(&BlockContext {
cx,
anchor_x: 0.,
scroll_x: 0.,
gutter_padding: 0.,
gutter_width: 0.,
line_height: 0.,
em_width: 0.,
})
.name()?
.to_string(),
TransformBlock::ExcerptHeader {
starts_new_buffer, ..
} => {
if *starts_new_buffer {
"path header block".to_string()
} else {
"collapsed context".to_string()
fn editor_blocks(
editor: &ViewHandle<Editor>,
cx: &mut MutableAppContext,
) -> Vec<(u32, String)> {
let mut presenter = cx.build_presenter(editor.id(), 0.);
let mut cx = presenter.build_layout_context(Default::default(), false, cx);
cx.render(editor, |editor, cx| {
let snapshot = editor.snapshot(cx);
snapshot
.blocks_in_range(0..snapshot.max_point().row())
.filter_map(|(row, block)| {
let name = match block {
TransformBlock::Custom(block) => block
.render(&mut BlockContext {
cx,
anchor_x: 0.,
scroll_x: 0.,
gutter_padding: 0.,
gutter_width: 0.,
line_height: 0.,
em_width: 0.,
})
.name()?
.to_string(),
TransformBlock::ExcerptHeader {
starts_new_buffer, ..
} => {
if *starts_new_buffer {
"path header block".to_string()
} else {
"collapsed context".to_string()
}
}
}
};
};
Some((row, name))
})
.collect()
Some((row, name))
})
.collect()
})
}
}

View file

@ -4,14 +4,14 @@ use super::{
};
use crate::{Anchor, ToPoint as _};
use collections::{Bound, HashMap, HashSet};
use gpui::{AppContext, ElementBox};
use gpui::{ElementBox, RenderContext};
use language::{BufferSnapshot, Chunk, Patch};
use parking_lot::Mutex;
use std::{
cell::RefCell,
cmp::{self, Ordering},
fmt::Debug,
ops::{Deref, Range},
ops::{Deref, DerefMut, Range},
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
@ -50,7 +50,7 @@ struct BlockRow(u32);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
struct WrapRow(u32);
pub type RenderBlock = Arc<dyn Fn(&BlockContext) -> ElementBox>;
pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> ElementBox>;
pub struct Block {
id: BlockId,
@ -67,12 +67,12 @@ where
{
pub position: P,
pub height: u8,
pub render: Arc<dyn Fn(&BlockContext) -> ElementBox>,
pub render: Arc<dyn Fn(&mut BlockContext) -> ElementBox>,
pub disposition: BlockDisposition,
}
pub struct BlockContext<'a> {
pub cx: &'a AppContext,
pub struct BlockContext<'a, 'b> {
pub cx: &'b mut RenderContext<'a, crate::Editor>,
pub anchor_x: f32,
pub scroll_x: f32,
pub gutter_width: f32,
@ -916,16 +916,22 @@ impl BlockDisposition {
}
}
impl<'a> Deref for BlockContext<'a> {
type Target = AppContext;
impl<'a, 'b> Deref for BlockContext<'a, 'b> {
type Target = RenderContext<'a, crate::Editor>;
fn deref(&self) -> &Self::Target {
&self.cx
self.cx
}
}
impl<'a, 'b> DerefMut for BlockContext<'a, 'b> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.cx
}
}
impl Block {
pub fn render(&self, cx: &BlockContext) -> ElementBox {
pub fn render(&self, cx: &mut BlockContext) -> ElementBox {
self.render.lock()(cx)
}
@ -1008,7 +1014,7 @@ mod tests {
let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
writer.insert(vec![
let block_ids = writer.insert(vec![
BlockProperties {
position: buffer_snapshot.anchor_after(Point::new(1, 0)),
height: 1,
@ -1036,22 +1042,7 @@ mod tests {
.blocks_in_range(0..8)
.map(|(start_row, block)| {
let block = block.as_custom().unwrap();
(
start_row..start_row + block.height as u32,
block
.render(&BlockContext {
cx,
anchor_x: 0.,
gutter_padding: 0.,
scroll_x: 0.,
gutter_width: 0.,
line_height: 0.,
em_width: 0.,
})
.name()
.unwrap()
.to_string(),
)
(start_row..start_row + block.height as u32, block.id)
})
.collect::<Vec<_>>();
@ -1059,9 +1050,9 @@ mod tests {
assert_eq!(
blocks,
&[
(1..2, "block 1".to_string()),
(2..4, "block 2".to_string()),
(7..10, "block 3".to_string()),
(1..2, block_ids[0]),
(2..4, block_ids[1]),
(7..10, block_ids[2]),
]
);

View file

@ -4745,7 +4745,7 @@ impl Editor {
height: 1,
render: Arc::new({
let editor = rename_editor.clone();
move |cx: &BlockContext| {
move |cx: &mut BlockContext| {
ChildView::new(editor.clone())
.contained()
.with_padding_left(cx.anchor_x)
@ -5866,7 +5866,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
highlighted_lines.push(highlight_diagnostic_message(line));
}
Arc::new(move |cx: &BlockContext| {
Arc::new(move |cx: &mut BlockContext| {
let settings = cx.global::<Settings>();
let theme = &settings.theme.editor;
let style = diagnostic_style(diagnostic.severity, is_valid, theme);

View file

@ -755,6 +755,12 @@ impl EditorElement {
line_layouts: &[text_layout::Line],
cx: &mut LayoutContext,
) -> Vec<(u32, ElementBox)> {
let editor = if let Some(editor) = self.view.upgrade(cx) {
editor
} else {
return Default::default();
};
let scroll_x = snapshot.scroll_position.x();
snapshot
.blocks_in_range(rows.clone())
@ -774,14 +780,16 @@ impl EditorElement {
.x_for_index(align_to.column() as usize)
};
block.render(&BlockContext {
cx,
anchor_x,
gutter_padding,
line_height,
scroll_x,
gutter_width,
em_width,
cx.render(&editor, |_, cx| {
block.render(&mut BlockContext {
cx,
anchor_x,
gutter_padding,
line_height,
scroll_x,
gutter_width,
em_width,
})
})
}
TransformBlock::ExcerptHeader {
@ -1611,7 +1619,7 @@ mod tests {
// Don't panic.
let bounds = RectF::new(Default::default(), size);
let mut paint_cx = presenter.build_paint_context(&mut scene, cx);
let mut paint_cx = presenter.build_paint_context(&mut scene, bounds.size(), cx);
element.paint(bounds, bounds, &mut state, &mut paint_cx);
}
}

View file

@ -16,13 +16,14 @@ mod overlay;
mod stack;
mod svg;
mod text;
mod tooltip;
mod uniform_list;
use self::expanded::Expanded;
pub use self::{
align::*, canvas::*, constrained_box::*, container::*, empty::*, event_handler::*, flex::*,
hook::*, image::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*,
stack::*, svg::*, text::*, uniform_list::*,
stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
};
pub use crate::presenter::ChildView;
use crate::{
@ -30,7 +31,8 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json, DebugContext, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
json, Action, DebugContext, Event, EventContext, LayoutContext, PaintContext, RenderContext,
SizeConstraint, View,
};
use core::panic;
use json::ToJson;
@ -154,6 +156,20 @@ pub trait Element {
{
FlexItem::new(self.boxed()).float()
}
fn with_tooltip<T: View>(
self,
id: usize,
text: String,
action: Option<Box<dyn Action>>,
style: TooltipStyle,
cx: &mut RenderContext<T>,
) -> Tooltip
where
Self: 'static + Sized,
{
Tooltip::new(id, text, action, style, self.boxed(), cx)
}
}
pub enum Lifecycle<T: Element> {

View file

@ -25,6 +25,7 @@ pub struct MouseEventHandler {
mouse_down_out: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
right_mouse_down_out: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
drag: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
hover: Option<Rc<dyn Fn(Vector2F, bool, &mut EventContext)>>,
padding: Padding,
}
@ -47,6 +48,7 @@ impl MouseEventHandler {
mouse_down_out: None,
right_mouse_down_out: None,
drag: None,
hover: None,
padding: Default::default(),
}
}
@ -109,6 +111,14 @@ impl MouseEventHandler {
self
}
pub fn on_hover(
mut self,
handler: impl Fn(Vector2F, bool, &mut EventContext) + 'static,
) -> Self {
self.hover = Some(Rc::new(handler));
self
}
pub fn with_padding(mut self, padding: Padding) -> Self {
self.padding = padding;
self
@ -153,7 +163,7 @@ impl Element for MouseEventHandler {
view_id: cx.current_view_id(),
discriminant: Some((self.tag, self.id)),
bounds: self.hit_bounds(bounds),
hover: None,
hover: self.hover.clone(),
click: self.click.clone(),
mouse_down: self.mouse_down.clone(),
right_click: self.right_click.clone(),

View file

@ -0,0 +1,217 @@
use super::{
ContainerStyle, Element, ElementBox, Flex, KeystrokeLabel, MouseEventHandler, ParentElement,
Text,
};
use crate::{
fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F},
json::json,
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
Task, View,
};
use serde::Deserialize;
use std::{
cell::{Cell, RefCell},
rc::Rc,
time::Duration,
};
const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500);
pub struct Tooltip {
child: ElementBox,
tooltip: Option<ElementBox>,
state: ElementStateHandle<Rc<TooltipState>>,
}
#[derive(Default)]
struct TooltipState {
visible: Cell<bool>,
position: Cell<Vector2F>,
debounce: RefCell<Option<Task<()>>>,
}
#[derive(Clone, Deserialize, Default)]
pub struct TooltipStyle {
#[serde(flatten)]
container: ContainerStyle,
text: TextStyle,
keystroke: KeystrokeStyle,
max_text_width: f32,
}
#[derive(Clone, Deserialize, Default)]
pub struct KeystrokeStyle {
#[serde(flatten)]
container: ContainerStyle,
#[serde(flatten)]
text: TextStyle,
}
impl Tooltip {
pub fn new<T: View>(
id: usize,
text: String,
action: Option<Box<dyn Action>>,
style: TooltipStyle,
child: ElementBox,
cx: &mut RenderContext<T>,
) -> Self {
let state_handle = cx.element_state::<TooltipState, Rc<TooltipState>>(id);
let state = state_handle.read(cx).clone();
let tooltip = if state.visible.get() {
let mut collapsed_tooltip = Self::render_tooltip(
text.clone(),
style.clone(),
action.as_ref().map(|a| a.boxed_clone()),
true,
)
.boxed();
Some(
Self::render_tooltip(text, style, action, false)
.constrained()
.dynamically(move |constraint, cx| {
SizeConstraint::strict_along(
Axis::Vertical,
collapsed_tooltip.layout(constraint, cx).y(),
)
})
.boxed(),
)
} else {
None
};
let child = MouseEventHandler::new::<Self, _, _>(id, cx, |_, _| child)
.on_hover(move |position, hover, cx| {
let window_id = cx.window_id();
if let Some(view_id) = cx.view_id() {
if hover {
if !state.visible.get() {
state.position.set(position);
let mut debounce = state.debounce.borrow_mut();
if debounce.is_none() {
*debounce = Some(cx.spawn({
let state = state.clone();
|mut cx| async move {
cx.background().timer(DEBOUNCE_TIMEOUT).await;
state.visible.set(true);
cx.update(|cx| cx.notify_view(window_id, view_id));
}
}));
}
}
} else {
state.visible.set(false);
state.debounce.take();
}
}
})
.boxed();
Self {
child,
tooltip,
state: state_handle,
}
}
fn render_tooltip(
text: String,
style: TooltipStyle,
action: Option<Box<dyn Action>>,
measure: bool,
) -> impl Element {
Flex::row()
.with_child({
let text = Text::new(text, style.text)
.constrained()
.with_max_width(style.max_text_width);
if measure {
text.flex(1., false).boxed()
} else {
text.flex(1., false).aligned().boxed()
}
})
.with_children(action.map(|action| {
let keystroke_label =
KeystrokeLabel::new(action, style.keystroke.container, style.keystroke.text);
if measure {
keystroke_label.boxed()
} else {
keystroke_label.aligned().boxed()
}
}))
.contained()
.with_style(style.container)
}
}
impl Element for Tooltip {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, cx);
if let Some(tooltip) = self.tooltip.as_mut() {
tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
}
(size, ())
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) {
self.child.paint(bounds.origin(), visible_bounds, cx);
if let Some(tooltip) = self.tooltip.as_mut() {
let origin = self.state.read(cx).position.get();
let mut bounds = RectF::new(origin, tooltip.size());
// Align tooltip to the left if its bounds overflow the window width.
if bounds.lower_right().x() > cx.window_size.x() {
bounds.set_origin_x(bounds.origin_x() - bounds.width());
}
// Align tooltip to the top if its bounds overflow the window height.
if bounds.lower_right().y() > cx.window_size.y() {
bounds.set_origin_y(bounds.origin_y() - bounds.height());
}
cx.scene.push_stacking_context(None);
tooltip.paint(bounds.origin(), bounds, cx);
cx.scene.pop_stacking_context();
}
}
fn dispatch_event(
&mut self,
event: &crate::Event,
_: RectF,
_: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
cx: &mut crate::EventContext,
) -> bool {
self.child.dispatch_event(event, cx)
}
fn debug(
&self,
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
cx: &crate::DebugContext,
) -> serde_json::Value {
json!({
"child": self.child.debug(cx),
"tooltip": self.tooltip.as_ref().map(|t| t.debug(cx)),
})
}
}

View file

@ -148,7 +148,7 @@ impl Presenter {
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
self.layout(window_size, refreshing, cx);
let mut paint_cx = self.build_paint_context(&mut scene, cx);
let mut paint_cx = self.build_paint_context(&mut scene, window_size, cx);
paint_cx.paint(
root_view_id,
Vector2F::zero(),
@ -205,10 +205,12 @@ impl Presenter {
pub fn build_paint_context<'a>(
&'a mut self,
scene: &'a mut Scene,
window_size: Vector2F,
cx: &'a mut MutableAppContext,
) -> PaintContext {
PaintContext {
scene,
window_size,
font_cache: &self.font_cache,
text_layout_cache: &self.text_layout_cache,
rendered_views: &mut self.rendered_views,
@ -311,7 +313,7 @@ impl Presenter {
if let Some(region_id) = region.id() {
if !self.hovered_region_ids.contains(&region_id) {
invalidated_views.push(region.view_id);
hovered_regions.push(region.clone());
hovered_regions.push((region.clone(), position));
self.hovered_region_ids.insert(region_id);
}
}
@ -319,7 +321,7 @@ impl Presenter {
if let Some(region_id) = region.id() {
if self.hovered_region_ids.contains(&region_id) {
invalidated_views.push(region.view_id);
unhovered_regions.push(region.clone());
unhovered_regions.push((region.clone(), position));
self.hovered_region_ids.remove(&region_id);
}
}
@ -348,20 +350,20 @@ impl Presenter {
let mut event_cx = self.build_event_context(cx);
let mut handled = false;
for unhovered_region in unhovered_regions {
for (unhovered_region, position) in unhovered_regions {
handled = true;
if let Some(hover_callback) = unhovered_region.hover {
event_cx.with_current_view(unhovered_region.view_id, |event_cx| {
hover_callback(false, event_cx);
hover_callback(position, false, event_cx);
})
}
}
for hovered_region in hovered_regions {
for (hovered_region, position) in hovered_regions {
handled = true;
if let Some(hover_callback) = hovered_region.hover {
event_cx.with_current_view(hovered_region.view_id, |event_cx| {
hover_callback(true, event_cx);
hover_callback(position, true, event_cx);
})
}
}
@ -449,6 +451,7 @@ impl Presenter {
view_stack: Default::default(),
invalidated_views: Default::default(),
notify_count: 0,
window_id: self.window_id,
app: cx,
}
}
@ -591,6 +594,7 @@ impl<'a> UpgradeViewHandle for LayoutContext<'a> {
pub struct PaintContext<'a> {
rendered_views: &'a mut HashMap<usize, ElementBox>,
view_stack: Vec<usize>,
pub window_size: Vector2F,
pub scene: &'a mut Scene,
pub font_cache: &'a FontCache,
pub text_layout_cache: &'a TextLayoutCache,
@ -626,6 +630,7 @@ pub struct EventContext<'a> {
pub font_cache: &'a FontCache,
pub text_layout_cache: &'a TextLayoutCache,
pub app: &'a mut MutableAppContext,
pub window_id: usize,
pub notify_count: usize,
view_stack: Vec<usize>,
invalidated_views: HashSet<usize>,
@ -653,6 +658,14 @@ impl<'a> EventContext<'a> {
result
}
pub fn window_id(&self) -> usize {
self.window_id
}
pub fn view_id(&self) -> Option<usize> {
self.view_stack.last().copied()
}
pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
self.dispatched_actions.push(DispatchDirective {
dispatcher_view_id: self.view_stack.last().copied(),

View file

@ -49,7 +49,7 @@ pub struct MouseRegion {
pub view_id: usize,
pub discriminant: Option<(TypeId, usize)>,
pub bounds: RectF,
pub hover: Option<Rc<dyn Fn(bool, &mut EventContext)>>,
pub hover: Option<Rc<dyn Fn(Vector2F, bool, &mut EventContext)>>,
pub mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
pub click: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
pub right_mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,

View file

@ -2,7 +2,7 @@ mod theme_registry;
use gpui::{
color::Color,
elements::{ContainerStyle, ImageStyle, LabelStyle},
elements::{ContainerStyle, ImageStyle, LabelStyle, TooltipStyle},
fonts::{HighlightStyle, TextStyle},
Border, MouseState,
};
@ -31,6 +31,7 @@ pub struct Theme {
pub project_diagnostics: ProjectDiagnostics,
pub breadcrumbs: ContainedText,
pub contact_notification: ContactNotification,
pub tooltip: TooltipStyle,
}
#[derive(Deserialize, Default)]
@ -317,7 +318,7 @@ pub struct Icon {
pub path: String,
}
#[derive(Deserialize, Default)]
#[derive(Clone, Deserialize, Default)]
pub struct IconButton {
#[serde(flatten)]
pub container: ContainerStyle,
@ -461,6 +462,7 @@ pub struct DiagnosticHeader {
pub code: ContainedText,
pub text_scale_factor: f32,
pub icon_width_factor: f32,
pub jump_icon: Interactive<IconButton>,
}
#[derive(Clone, Deserialize, Default)]

View file

@ -299,7 +299,9 @@ impl Pane {
) -> Box<dyn ItemHandle> {
let existing_item = pane.update(cx, |pane, cx| {
for (ix, item) in pane.items.iter().enumerate() {
if item.project_entry_ids(cx).as_slice() == &[project_entry_id] {
if item.project_path(cx).is_some()
&& item.project_entry_ids(cx).as_slice() == &[project_entry_id]
{
let item = item.boxed_clone();
pane.activate_item(ix, true, focus_item, cx);
return Some(item);