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

3
assets/icons/jump.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 1.02501V6.27499C7.5 6.56484 7.26484 6.79999 6.975 6.79999C6.68516 6.79999 6.45 6.56484 6.45 6.27499V2.29157L1.3969 7.34468C1.29365 7.44968 1.15934 7.49999 1.02503 7.49999C0.890713 7.49999 0.756401 7.44871 0.653808 7.34619C0.448731 7.14111 0.448731 6.80894 0.653808 6.60375L5.70844 1.55001H1.72502C1.43518 1.55001 1.20002 1.31595 1.20002 1.02501C1.20002 0.734077 1.43518 0.500015 1.72502 0.500015H6.975C7.26594 0.500015 7.5 0.736264 7.5 1.02501Z" fill="white" fill-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 593 B

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)
.with_padding_left(cx.gutter_padding + cx.scroll_x * cx.em_width)
.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(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,13 +1198,20 @@ mod tests {
});
}
fn editor_blocks(editor: &EditorSnapshot, cx: &AppContext) -> Vec<(u32, String)> {
editor
.blocks_in_range(0..editor.max_point().row())
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(&BlockContext {
.render(&mut BlockContext {
cx,
anchor_x: 0.,
scroll_x: 0.,
@ -1146,5 +1236,6 @@ mod tests {
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,7 +780,8 @@ impl EditorElement {
.x_for_index(align_to.column() as usize)
};
block.render(&BlockContext {
cx.render(&editor, |_, cx| {
block.render(&mut BlockContext {
cx,
anchor_x,
gutter_padding,
@ -783,6 +790,7 @@ impl EditorElement {
gutter_width,
em_width,
})
})
}
TransformBlock::ExcerptHeader {
buffer,
@ -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);

View file

@ -12,6 +12,7 @@ import workspace from "./workspace";
import contextMenu from "./contextMenu";
import projectDiagnostics from "./projectDiagnostics";
import contactNotification from "./contactNotification";
import tooltip from "./tooltip";
export const panel = {
padding: { top: 12, bottom: 12 },
@ -37,5 +38,6 @@ export default function app(theme: Theme): Object {
},
},
contactNotification: contactNotification(theme),
tooltip: tooltip(theme),
};
}

View file

@ -98,6 +98,14 @@ export default function editor(theme: Theme) {
background: backgroundColor(theme, 300),
iconWidthFactor: 1.5,
textScaleFactor: 0.857, // NateQ: Will we need dynamic sizing for text? If so let's create tokens for these.
jumpIcon: {
color: iconColor(theme, "primary"),
iconWidth: 10,
buttonWidth: 10,
hover: {
color: iconColor(theme, "active")
}
},
border: border(theme, "secondary", {
bottom: true,
top: true,

View file

@ -0,0 +1,22 @@
import Theme from "../themes/common/theme";
import { backgroundColor, border, shadow, text } from "./components";
export default function tooltip(theme: Theme) {
return {
background: backgroundColor(theme, 500),
border: border(theme, "secondary"),
padding: { top: 4, bottom: 4, left: 8, right: 8 },
margin: { top: 6, left: 6 },
shadow: shadow(theme),
cornerRadius: 6,
text: text(theme, "sans", "secondary", { size: "xs", weight: "bold" }),
keystroke: {
background: backgroundColor(theme, "on500"),
cornerRadius: 4,
margin: { left: 6 },
padding: { left: 3, right: 3 },
...text(theme, "mono", "muted", { size: "xs", weight: "bold" })
},
maxTextWidth: 200,
}
}