mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-09 03:57:39 +00:00
Merge branch 'main' into add-telemetry-tests
This commit is contained in:
commit
634f7f768c
11 changed files with 242 additions and 43 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -6145,6 +6145,7 @@ dependencies = [
|
|||
"smol",
|
||||
"sum_tree",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
]
|
||||
|
||||
|
|
|
@ -72,6 +72,9 @@
|
|||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
// Whether to automatically type closing characters for you. For example,
|
||||
// when you type (, Zed will automatically add a closing ) at the correct position.
|
||||
"use_autoclose": true,
|
||||
// Controls whether copilot provides suggestion immediately
|
||||
// or waits for a `copilot::Toggle`
|
||||
"show_copilot_suggestions": true,
|
||||
|
|
|
@ -349,15 +349,13 @@ impl ChatPanel {
|
|||
.when(!is_continuation_from_previous, |this| {
|
||||
this.pt_3().child(
|
||||
h_flex()
|
||||
.child(
|
||||
div().absolute().child(
|
||||
Avatar::new(message.sender.avatar_uri.clone())
|
||||
.size(cx.rem_size() * 1.5),
|
||||
),
|
||||
)
|
||||
.text_ui_sm()
|
||||
.child(div().absolute().child(
|
||||
Avatar::new(message.sender.avatar_uri.clone()).size(cx.rem_size()),
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.pl(cx.rem_size() * 1.5 + px(6.0))
|
||||
.pl(cx.rem_size() + px(6.0))
|
||||
.pr(px(8.0))
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.child(Label::new(message.sender.github_login.clone())),
|
||||
|
@ -597,7 +595,7 @@ impl Render for ChatPanel {
|
|||
el.child(
|
||||
div()
|
||||
.rounded_md()
|
||||
.h_7()
|
||||
.h_6()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().editor_background),
|
||||
)
|
||||
|
|
|
@ -18,7 +18,7 @@ use project::search::SearchQuery;
|
|||
use settings::Settings;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use theme::ThemeSettings;
|
||||
use ui::prelude::*;
|
||||
use ui::{prelude::*, UiTextSize};
|
||||
|
||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
|
@ -83,6 +83,7 @@ impl MessageEditor {
|
|||
let this = cx.view().downgrade();
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
editor.set_use_autoclose(false);
|
||||
editor.set_completion_provider(Box::new(MessageEditorCompletionProvider(this)));
|
||||
});
|
||||
|
||||
|
@ -325,7 +326,7 @@ impl Render for MessageEditor {
|
|||
},
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features,
|
||||
font_size: rems(0.875).into(),
|
||||
font_size: UiTextSize::Small.rems().into(),
|
||||
font_weight: FontWeight::NORMAL,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(1.3).into(),
|
||||
|
|
|
@ -409,6 +409,7 @@ pub struct Editor {
|
|||
style: Option<EditorStyle>,
|
||||
editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
|
||||
show_copilot_suggestions: bool,
|
||||
use_autoclose: bool,
|
||||
}
|
||||
|
||||
pub struct EditorSnapshot {
|
||||
|
@ -1411,6 +1412,7 @@ impl Editor {
|
|||
keymap_context_layers: Default::default(),
|
||||
input_enabled: true,
|
||||
read_only: false,
|
||||
use_autoclose: true,
|
||||
leader_peer_id: None,
|
||||
remote_id: None,
|
||||
hover_state: Default::default(),
|
||||
|
@ -1692,6 +1694,10 @@ impl Editor {
|
|||
self.read_only = read_only;
|
||||
}
|
||||
|
||||
pub fn set_use_autoclose(&mut self, autoclose: bool) {
|
||||
self.use_autoclose = autoclose;
|
||||
}
|
||||
|
||||
pub fn set_show_copilot_suggestions(&mut self, show_copilot_suggestions: bool) {
|
||||
self.show_copilot_suggestions = show_copilot_suggestions;
|
||||
}
|
||||
|
@ -2290,7 +2296,12 @@ impl Editor {
|
|||
),
|
||||
&bracket_pair.start[..prefix_len],
|
||||
));
|
||||
if following_text_allows_autoclose && preceding_text_matches_prefix {
|
||||
let autoclose = self.use_autoclose
|
||||
&& snapshot.settings_at(selection.start, cx).use_autoclose;
|
||||
if autoclose
|
||||
&& following_text_allows_autoclose
|
||||
&& preceding_text_matches_prefix
|
||||
{
|
||||
let anchor = snapshot.anchor_before(selection.end);
|
||||
new_selections.push((selection.map(|_| anchor), text.len()));
|
||||
new_autoclose_regions.push((
|
||||
|
|
|
@ -24,7 +24,7 @@ use taffy::style::Overflow;
|
|||
use util::ResultExt;
|
||||
|
||||
const DRAG_THRESHOLD: f64 = 2.;
|
||||
const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
|
||||
pub(crate) const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
|
||||
|
||||
pub struct GroupStyle {
|
||||
pub group: SharedString,
|
||||
|
@ -1718,8 +1718,8 @@ pub struct InteractiveElementState {
|
|||
}
|
||||
|
||||
pub struct ActiveTooltip {
|
||||
tooltip: Option<AnyTooltip>,
|
||||
_task: Option<Task<()>>,
|
||||
pub(crate) tooltip: Option<AnyTooltip>,
|
||||
pub(crate) _task: Option<Task<()>>,
|
||||
}
|
||||
|
||||
/// Whether or not the element or a group that contains it is clicked by the mouse.
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
use crate::{
|
||||
Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
|
||||
MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
|
||||
WhiteSpace, WindowContext, WrappedLine,
|
||||
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementId, HighlightStyle,
|
||||
IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point,
|
||||
SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, TOOLTIP_DELAY,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
mem,
|
||||
ops::Range,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
impl Element for &'static str {
|
||||
|
@ -289,6 +295,8 @@ pub struct InteractiveText {
|
|||
text: StyledText,
|
||||
click_listener:
|
||||
Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
|
||||
hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut WindowContext<'_>)>>,
|
||||
tooltip_builder: Option<Rc<dyn Fn(usize, &mut WindowContext<'_>) -> Option<AnyView>>>,
|
||||
clickable_ranges: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
|
@ -300,18 +308,25 @@ struct InteractiveTextClickEvent {
|
|||
pub struct InteractiveTextState {
|
||||
text_state: TextState,
|
||||
mouse_down_index: Rc<Cell<Option<usize>>>,
|
||||
hovered_index: Rc<Cell<Option<usize>>>,
|
||||
active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
|
||||
}
|
||||
|
||||
/// InteractiveTest is a wrapper around StyledText that adds mouse interactions.
|
||||
impl InteractiveText {
|
||||
pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
|
||||
Self {
|
||||
element_id: id.into(),
|
||||
text,
|
||||
click_listener: None,
|
||||
hover_listener: None,
|
||||
tooltip_builder: None,
|
||||
clickable_ranges: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// on_click is called when the user clicks on one of the given ranges, passing the index of
|
||||
/// the clicked range.
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
ranges: Vec<Range<usize>>,
|
||||
|
@ -328,6 +343,25 @@ impl InteractiveText {
|
|||
self.clickable_ranges = ranges;
|
||||
self
|
||||
}
|
||||
|
||||
/// on_hover is called when the mouse moves over a character within the text, passing the
|
||||
/// index of the hovered character, or None if the mouse leaves the text.
|
||||
pub fn on_hover(
|
||||
mut self,
|
||||
listener: impl Fn(Option<usize>, MouseMoveEvent, &mut WindowContext<'_>) + 'static,
|
||||
) -> Self {
|
||||
self.hover_listener = Some(Box::new(listener));
|
||||
self
|
||||
}
|
||||
|
||||
/// tooltip lets you specify a tooltip for a given character index in the string.
|
||||
pub fn tooltip(
|
||||
mut self,
|
||||
builder: impl Fn(usize, &mut WindowContext<'_>) -> Option<AnyView> + 'static,
|
||||
) -> Self {
|
||||
self.tooltip_builder = Some(Rc::new(builder));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for InteractiveText {
|
||||
|
@ -339,13 +373,18 @@ impl Element for InteractiveText {
|
|||
cx: &mut WindowContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
if let Some(InteractiveTextState {
|
||||
mouse_down_index, ..
|
||||
mouse_down_index,
|
||||
hovered_index,
|
||||
active_tooltip,
|
||||
..
|
||||
}) = state
|
||||
{
|
||||
let (layout_id, text_state) = self.text.request_layout(None, cx);
|
||||
let element_state = InteractiveTextState {
|
||||
text_state,
|
||||
mouse_down_index,
|
||||
hovered_index,
|
||||
active_tooltip,
|
||||
};
|
||||
(layout_id, element_state)
|
||||
} else {
|
||||
|
@ -353,6 +392,8 @@ impl Element for InteractiveText {
|
|||
let element_state = InteractiveTextState {
|
||||
text_state,
|
||||
mouse_down_index: Rc::default(),
|
||||
hovered_index: Rc::default(),
|
||||
active_tooltip: Rc::default(),
|
||||
};
|
||||
(layout_id, element_state)
|
||||
}
|
||||
|
@ -408,6 +449,83 @@ impl Element for InteractiveText {
|
|||
});
|
||||
}
|
||||
}
|
||||
if let Some(hover_listener) = self.hover_listener.take() {
|
||||
let text_state = state.text_state.clone();
|
||||
let hovered_index = state.hovered_index.clone();
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
let current = hovered_index.get();
|
||||
let updated = text_state.index_for_position(bounds, event.position);
|
||||
if current != updated {
|
||||
hovered_index.set(updated);
|
||||
hover_listener(updated, event.clone(), cx);
|
||||
cx.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
|
||||
let active_tooltip = state.active_tooltip.clone();
|
||||
let pending_mouse_down = state.mouse_down_index.clone();
|
||||
let text_state = state.text_state.clone();
|
||||
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
let position = text_state.index_for_position(bounds, event.position);
|
||||
let is_hovered = position.is_some() && pending_mouse_down.get().is_none();
|
||||
if !is_hovered {
|
||||
active_tooltip.take();
|
||||
return;
|
||||
}
|
||||
let position = position.unwrap();
|
||||
|
||||
if phase != DispatchPhase::Bubble {
|
||||
return;
|
||||
}
|
||||
|
||||
if active_tooltip.borrow().is_none() {
|
||||
let task = cx.spawn({
|
||||
let active_tooltip = active_tooltip.clone();
|
||||
let tooltip_builder = tooltip_builder.clone();
|
||||
|
||||
move |mut cx| async move {
|
||||
cx.background_executor().timer(TOOLTIP_DELAY).await;
|
||||
cx.update(|_, cx| {
|
||||
let new_tooltip =
|
||||
tooltip_builder(position, cx).map(|tooltip| ActiveTooltip {
|
||||
tooltip: Some(AnyTooltip {
|
||||
view: tooltip,
|
||||
cursor_offset: cx.mouse_position(),
|
||||
}),
|
||||
_task: None,
|
||||
});
|
||||
*active_tooltip.borrow_mut() = new_tooltip;
|
||||
cx.refresh();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
*active_tooltip.borrow_mut() = Some(ActiveTooltip {
|
||||
tooltip: None,
|
||||
_task: Some(task),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let active_tooltip = state.active_tooltip.clone();
|
||||
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
|
||||
active_tooltip.take();
|
||||
});
|
||||
|
||||
if let Some(tooltip) = state
|
||||
.active_tooltip
|
||||
.clone()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|at| at.tooltip.clone())
|
||||
{
|
||||
cx.set_tooltip(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
self.text.paint(bounds, &mut state.text_state, cx)
|
||||
}
|
||||
|
|
|
@ -91,6 +91,8 @@ pub struct LanguageSettings {
|
|||
pub extend_comment_on_newline: bool,
|
||||
/// Inlay hint related settings.
|
||||
pub inlay_hints: InlayHintSettings,
|
||||
/// Whether to automatically close brackets.
|
||||
pub use_autoclose: bool,
|
||||
}
|
||||
|
||||
/// The settings for [GitHub Copilot](https://github.com/features/copilot).
|
||||
|
@ -208,6 +210,11 @@ pub struct LanguageSettingsContent {
|
|||
/// Inlay hint related settings.
|
||||
#[serde(default)]
|
||||
pub inlay_hints: Option<InlayHintSettings>,
|
||||
/// Whether to automatically type closing characters for you. For example,
|
||||
/// when you type (, Zed will automatically add a closing ) at the correct position.
|
||||
///
|
||||
/// Default: true
|
||||
pub use_autoclose: Option<bool>,
|
||||
}
|
||||
|
||||
/// The contents of the GitHub Copilot settings.
|
||||
|
@ -540,6 +547,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
|
|||
merge(&mut settings.tab_size, src.tab_size);
|
||||
merge(&mut settings.hard_tabs, src.hard_tabs);
|
||||
merge(&mut settings.soft_wrap, src.soft_wrap);
|
||||
merge(&mut settings.use_autoclose, src.use_autoclose);
|
||||
merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
|
||||
merge(&mut settings.wrap_guides, src.wrap_guides.clone());
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ sum_tree = { path = "../sum_tree" }
|
|||
theme = { path = "../theme" }
|
||||
language = { path = "../language" }
|
||||
util = { path = "../util" }
|
||||
ui = { path = "../ui" }
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
lazy_static.workspace = true
|
||||
|
|
|
@ -6,6 +6,7 @@ use gpui::{
|
|||
use language::{HighlightId, Language, LanguageRegistry};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use theme::ActiveTheme;
|
||||
use ui::LinkPreview;
|
||||
use util::RangeExt;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -84,6 +85,18 @@ impl RichText {
|
|||
let link_urls = self.link_urls.clone();
|
||||
move |ix, cx| cx.open_url(&link_urls[ix])
|
||||
})
|
||||
.tooltip({
|
||||
let link_ranges = self.link_ranges.clone();
|
||||
let link_urls = self.link_urls.clone();
|
||||
move |idx, cx| {
|
||||
for (ix, range) in link_ranges.iter().enumerate() {
|
||||
if range.contains(&idx) {
|
||||
return Some(LinkPreview::new(&link_urls[ix], cx));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
@ -237,7 +250,7 @@ pub fn render_markdown_mut(
|
|||
_ => {}
|
||||
},
|
||||
Event::HardBreak => text.push('\n'),
|
||||
Event::SoftBreak => text.push(' '),
|
||||
Event::SoftBreak => text.push('\n'),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,29 +69,74 @@ impl Tooltip {
|
|||
|
||||
impl Render for Tooltip {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
|
||||
overlay().child(
|
||||
// padding to avoid mouse cursor
|
||||
div().pl_2().pt_2p5().child(
|
||||
v_flex()
|
||||
.elevation_2(cx)
|
||||
.font(ui_font)
|
||||
.text_ui()
|
||||
.text_color(cx.theme().colors().text)
|
||||
.py_1()
|
||||
.px_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_4()
|
||||
.child(self.title.clone())
|
||||
.when_some(self.key_binding.clone(), |this, key_binding| {
|
||||
this.justify_between().child(key_binding)
|
||||
}),
|
||||
)
|
||||
.when_some(self.meta.clone(), |this, meta| {
|
||||
this.child(Label::new(meta).size(LabelSize::Small).color(Color::Muted))
|
||||
tooltip_container(cx, |el, _| {
|
||||
el.child(
|
||||
h_flex()
|
||||
.gap_4()
|
||||
.child(self.title.clone())
|
||||
.when_some(self.key_binding.clone(), |this, key_binding| {
|
||||
this.justify_between().child(key_binding)
|
||||
}),
|
||||
),
|
||||
)
|
||||
)
|
||||
.when_some(self.meta.clone(), |this, meta| {
|
||||
this.child(Label::new(meta).size(LabelSize::Small).color(Color::Muted))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn tooltip_container<V>(
|
||||
cx: &mut ViewContext<V>,
|
||||
f: impl FnOnce(Div, &mut ViewContext<V>) -> Div,
|
||||
) -> impl IntoElement {
|
||||
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
|
||||
overlay().child(
|
||||
// padding to avoid mouse cursor
|
||||
div().pl_2().pt_2p5().child(
|
||||
v_flex()
|
||||
.elevation_2(cx)
|
||||
.font(ui_font)
|
||||
.text_ui()
|
||||
.text_color(cx.theme().colors().text)
|
||||
.py_1()
|
||||
.px_2()
|
||||
.map(|el| f(el, cx)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub struct LinkPreview {
|
||||
link: SharedString,
|
||||
}
|
||||
|
||||
impl LinkPreview {
|
||||
pub fn new(url: &str, cx: &mut WindowContext) -> AnyView {
|
||||
let mut wrapped_url = String::new();
|
||||
for (i, ch) in url.chars().enumerate() {
|
||||
if i == 500 {
|
||||
wrapped_url.push('…');
|
||||
break;
|
||||
}
|
||||
if i % 100 == 0 && i != 0 {
|
||||
wrapped_url.push('\n');
|
||||
}
|
||||
wrapped_url.push(ch);
|
||||
}
|
||||
cx.new_view(|_cx| LinkPreview {
|
||||
link: wrapped_url.into(),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for LinkPreview {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
tooltip_container(cx, |el, _| {
|
||||
el.child(
|
||||
Label::new(self.link.clone())
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue