code-editor: Show hover popover for diagnostics. (#924)
<img width="719" alt="image" src="https://github.com/user-attachments/assets/75fa9737-09e1-4baa-b7ac-304e847005c8" />
This commit is contained in:
parent
c3ec1deb97
commit
6aab9d3345
11 changed files with 326 additions and 54 deletions
|
|
@ -145,7 +145,7 @@ impl Example {
|
|||
vec![
|
||||
Marker::new("warning", (2, 1), (2, 31), "Import but not used."),
|
||||
Marker::new("error", (16, 10), (16, 46), "Syntax error."),
|
||||
Marker::new("info", (25, 10), (25, 20), "This is a info message."),
|
||||
Marker::new("info", (25, 10), (25, 20), "This is a info message, this is a very long message, with **Markdown** support."),
|
||||
Marker::new("hint", (36, 9), (40, 10), "This is a hint message."),
|
||||
],
|
||||
window,
|
||||
|
|
|
|||
|
|
@ -247,22 +247,32 @@ pub struct StatusColors {
|
|||
error: Option<Hsla>,
|
||||
#[serde(rename = "error.background")]
|
||||
error_background: Option<Hsla>,
|
||||
#[serde(rename = "error.border")]
|
||||
error_border: Option<Hsla>,
|
||||
#[serde(rename = "warning")]
|
||||
warning: Option<Hsla>,
|
||||
#[serde(rename = "warning.background")]
|
||||
warning_background: Option<Hsla>,
|
||||
#[serde(rename = "warning.border")]
|
||||
warning_border: Option<Hsla>,
|
||||
#[serde(rename = "info")]
|
||||
info: Option<Hsla>,
|
||||
#[serde(rename = "info.background")]
|
||||
info_background: Option<Hsla>,
|
||||
#[serde(rename = "info.border")]
|
||||
info_border: Option<Hsla>,
|
||||
#[serde(rename = "success")]
|
||||
success: Option<Hsla>,
|
||||
#[serde(rename = "success.background")]
|
||||
success_background: Option<Hsla>,
|
||||
#[serde(rename = "success.border")]
|
||||
success_border: Option<Hsla>,
|
||||
#[serde(rename = "hint")]
|
||||
hint: Option<Hsla>,
|
||||
#[serde(rename = "hint.background")]
|
||||
hint_background: Option<Hsla>,
|
||||
#[serde(rename = "hint.border")]
|
||||
hint_border: Option<Hsla>,
|
||||
}
|
||||
|
||||
impl StatusColors {
|
||||
|
|
@ -273,7 +283,12 @@ impl StatusColors {
|
|||
|
||||
#[inline]
|
||||
pub fn error_background(&self) -> Hsla {
|
||||
self.error_background.unwrap_or(self.error())
|
||||
self.error_background.unwrap_or(crate::red_200())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn error_border(&self) -> Hsla {
|
||||
self.error_border.unwrap_or(crate::red_500())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -283,7 +298,12 @@ impl StatusColors {
|
|||
|
||||
#[inline]
|
||||
pub fn warning_background(&self) -> Hsla {
|
||||
self.warning_background.unwrap_or(self.warning())
|
||||
self.warning_background.unwrap_or(crate::yellow_200())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn warning_border(&self) -> Hsla {
|
||||
self.warning_border.unwrap_or(crate::yellow_500())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -293,7 +313,12 @@ impl StatusColors {
|
|||
|
||||
#[inline]
|
||||
pub fn info_background(&self) -> Hsla {
|
||||
self.info_background.unwrap_or(self.info())
|
||||
self.info_background.unwrap_or(crate::blue_200())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn info_border(&self) -> Hsla {
|
||||
self.info_border.unwrap_or(crate::blue_500())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -303,17 +328,27 @@ impl StatusColors {
|
|||
|
||||
#[inline]
|
||||
pub fn success_background(&self) -> Hsla {
|
||||
self.success_background.unwrap_or(self.success())
|
||||
self.success_background.unwrap_or(crate::green_200())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn success_border(&self) -> Hsla {
|
||||
self.success_border.unwrap_or(crate::green_500())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hint(&self) -> Hsla {
|
||||
self.hint.unwrap_or(crate::cyan_500().opacity(0.5))
|
||||
self.hint.unwrap_or(crate::gray_500())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hint_background(&self) -> Hsla {
|
||||
self.hint_background.unwrap_or(self.hint())
|
||||
self.hint_background.unwrap_or(crate::gray_200())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hint_border(&self) -> Hsla {
|
||||
self.hint_border.unwrap_or(crate::gray_500())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@
|
|||
"hidden": "#9E9E9E",
|
||||
"hidden.background": null,
|
||||
"hidden.border": null,
|
||||
"hint": null,
|
||||
"hint.background": "#0C194D",
|
||||
"hint.border": "#082190",
|
||||
"hint": "#b283f8",
|
||||
"hint.background": "#250c4b",
|
||||
"hint.border": "#3f0891",
|
||||
"ignored": null,
|
||||
"ignored.background": null,
|
||||
"ignored.border": null,
|
||||
|
|
@ -162,4 +162,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,9 +22,9 @@
|
|||
"hidden": "#6D6D6D",
|
||||
"hidden.background": null,
|
||||
"hidden.border": null,
|
||||
"hint": null,
|
||||
"hint.background": "#E5F2FF",
|
||||
"hint.border": "#99CCFF",
|
||||
"hint": "#9e5dff",
|
||||
"hint.background": "#ece6ff",
|
||||
"hint.border": "#bc8fff",
|
||||
"ignored": null,
|
||||
"ignored.background": null,
|
||||
"ignored.border": null,
|
||||
|
|
@ -172,4 +172,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -407,7 +407,7 @@ impl TextElement {
|
|||
|
||||
let mut marker_styles = vec![];
|
||||
for marker in markers.iter() {
|
||||
if let Some(range) = marker.byte_range(&state) {
|
||||
if let Some(range) = &marker.range {
|
||||
if range.start < skipped_offset {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
133
crates/ui/src/input/hover_popover.rs
Normal file
133
crates/ui/src/input/hover_popover.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
canvas, deferred, div, px, App, AppContext as _, Bounds, Context, Empty, Entity,
|
||||
InteractiveElement, IntoElement, ParentElement as _, Pixels, Point, Render, Styled, Window,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
highlighter::LanguageRegistry,
|
||||
input::{InputState, Marker},
|
||||
text::TextView,
|
||||
ActiveTheme as _,
|
||||
};
|
||||
|
||||
pub struct DiagnosticPopover {
|
||||
state: Entity<InputState>,
|
||||
pub(super) marker: Rc<Marker>,
|
||||
bounds: Bounds<Pixels>,
|
||||
open: bool,
|
||||
}
|
||||
|
||||
impl DiagnosticPopover {
|
||||
pub fn new(marker: &Marker, state: Entity<InputState>, cx: &mut App) -> Entity<Self> {
|
||||
let marker = Rc::new(marker.clone());
|
||||
|
||||
cx.new(|_| Self {
|
||||
marker,
|
||||
state,
|
||||
bounds: Bounds::default(),
|
||||
open: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn origin(&self, cx: &App) -> Option<Point<Pixels>> {
|
||||
let Some(range) = self.marker.range.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let (_, _, start_pos) = self
|
||||
.state
|
||||
.read(cx)
|
||||
.line_and_position_for_offset(range.start);
|
||||
|
||||
start_pos
|
||||
}
|
||||
|
||||
pub(super) fn show(&mut self, cx: &mut Context<Self>) {
|
||||
self.open = true;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(super) fn hide(&mut self, cx: &mut Context<Self>) {
|
||||
self.open = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(super) fn check_to_hide(&mut self, mouse_position: Point<Pixels>, cx: &mut Context<Self>) {
|
||||
if !self.open {
|
||||
return;
|
||||
}
|
||||
|
||||
let padding = px(5.);
|
||||
let bounds = Bounds {
|
||||
origin: self.bounds.origin.map(|v| v - padding),
|
||||
size: self.bounds.size.map(|v| v + padding * 2.),
|
||||
};
|
||||
|
||||
if !bounds.contains(&mouse_position) {
|
||||
self.hide(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DiagnosticPopover {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut gpui::Context<Self>) -> impl IntoElement {
|
||||
if !self.open {
|
||||
return Empty.into_any_element();
|
||||
}
|
||||
|
||||
let view = cx.entity();
|
||||
let theme = LanguageRegistry::global(cx).theme(cx.theme().is_dark());
|
||||
|
||||
let message = self.marker.message.clone();
|
||||
let Some(pos) = self.origin(cx) else {
|
||||
return Empty.into_any_element();
|
||||
};
|
||||
let (border, bg, fg) = (
|
||||
self.marker.severity.border(theme),
|
||||
self.marker.severity.bg(theme),
|
||||
self.marker.severity.fg(theme),
|
||||
);
|
||||
|
||||
let scroll_origin = self.state.read(cx).scroll_handle.offset();
|
||||
|
||||
let y = pos.y - self.bounds.size.height + scroll_origin.y;
|
||||
let x = pos.x + scroll_origin.x;
|
||||
let max_width = px(500.).min(window.bounds().size.width - x);
|
||||
|
||||
deferred(
|
||||
div()
|
||||
.id("code-editor-diagnostic-popover")
|
||||
.absolute()
|
||||
.left(x)
|
||||
.top(y)
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.text_xs()
|
||||
.bg(bg)
|
||||
.w(max_width)
|
||||
.text_color(fg)
|
||||
.border_1()
|
||||
.border_color(border)
|
||||
.rounded(cx.theme().radius)
|
||||
.shadow_sm()
|
||||
.child(TextView::markdown("message", message))
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.top_0()
|
||||
.left_0()
|
||||
.absolute()
|
||||
.size_full(),
|
||||
)
|
||||
.on_mouse_down_out(cx.listener(|this, _, _, cx| {
|
||||
this.open = false;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{highlighter::HighlightTheme, input::InputState};
|
||||
use gpui::{px, HighlightStyle, SharedString, UnderlineStyle};
|
||||
use gpui::{px, HighlightStyle, Hsla, SharedString, UnderlineStyle};
|
||||
use itertools::Itertools;
|
||||
use std::ops::Range;
|
||||
|
||||
|
|
@ -9,6 +9,7 @@ pub struct Marker {
|
|||
pub severity: MarkerSeverity,
|
||||
pub start: LineColumn,
|
||||
pub end: LineColumn,
|
||||
pub(super) range: Option<Range<usize>>,
|
||||
/// The message associated with the marker, typically a description of the issue.
|
||||
pub message: SharedString,
|
||||
}
|
||||
|
|
@ -26,22 +27,34 @@ impl Marker {
|
|||
start: start.into(),
|
||||
end: end.into(),
|
||||
message: message.into(),
|
||||
range: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the range (zero-based) of bytes in the source code that this marker covers.
|
||||
pub(super) fn byte_range(&self, state: &InputState) -> Option<Range<usize>> {
|
||||
let start_line = state
|
||||
/// Prepare the marker to convert line, column to byte offsets.
|
||||
pub(super) fn prepare(&mut self, state: &InputState) {
|
||||
let Some(start_line) = state
|
||||
.text_wrapper
|
||||
.lines
|
||||
.get(self.start.line.saturating_sub(1))?;
|
||||
let start_line_str = state.text.get(start_line.range.clone())?;
|
||||
.get(self.start.line.saturating_sub(1))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let end_line = state
|
||||
let Some(start_line_str) = state.text.get(start_line.range.clone()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(end_line) = state
|
||||
.text_wrapper
|
||||
.lines
|
||||
.get(self.end.line.saturating_sub(1))?;
|
||||
let end_line_str = state.text.get(end_line.range.clone())?;
|
||||
.get(self.end.line.saturating_sub(1))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(end_line_str) = state.text.get(end_line.range.clone()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let start_byte = start_line.range.start
|
||||
+ start_line_str
|
||||
|
|
@ -58,7 +71,7 @@ impl Marker {
|
|||
.values()
|
||||
.sum::<usize>();
|
||||
|
||||
Some(start_byte..end_byte)
|
||||
self.range = Some(start_byte..end_byte);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +117,33 @@ impl From<&str> for MarkerSeverity {
|
|||
}
|
||||
|
||||
impl MarkerSeverity {
|
||||
/// Returns the [`HighlightStyle`] for the marker severity with the given theme style.
|
||||
pub(super) fn bg(&self, theme: &HighlightTheme) -> Hsla {
|
||||
match self {
|
||||
Self::Error => theme.style.status.error_background(),
|
||||
Self::Warning => theme.style.status.warning_background(),
|
||||
Self::Info => theme.style.status.info_background(),
|
||||
Self::Hint => theme.style.status.hint_background(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn fg(&self, theme: &HighlightTheme) -> Hsla {
|
||||
match self {
|
||||
Self::Error => theme.style.status.error(),
|
||||
Self::Warning => theme.style.status.warning(),
|
||||
Self::Info => theme.style.status.info(),
|
||||
Self::Hint => theme.style.status.hint(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn border(&self, theme: &HighlightTheme) -> Hsla {
|
||||
match self {
|
||||
Self::Error => theme.style.status.error_border(),
|
||||
Self::Warning => theme.style.status.warning_border(),
|
||||
Self::Info => theme.style.status.info_border(),
|
||||
Self::Hint => theme.style.status.hint_border(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn highlight_style(&self, theme: &HighlightTheme) -> HighlightStyle {
|
||||
let color = match self {
|
||||
Self::Error => Some(theme.style.status.error()),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ mod blink_cursor;
|
|||
mod change;
|
||||
mod clear_button;
|
||||
mod element;
|
||||
mod hover_popover;
|
||||
mod marker;
|
||||
mod mask_pattern;
|
||||
mod mode;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ pub enum InputMode {
|
|||
line_number: bool,
|
||||
language: SharedString,
|
||||
highlighter: Rc<RefCell<Option<SyntaxHighlighter>>>,
|
||||
markers: Vec<Marker>,
|
||||
markers: Rc<Vec<Marker>>,
|
||||
},
|
||||
AutoGrow {
|
||||
rows: usize,
|
||||
|
|
@ -182,20 +182,42 @@ impl InputMode {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn clear_markers(&mut self) {
|
||||
match self {
|
||||
InputMode::CodeEditor { markers, .. } => *markers = Rc::new(vec![]),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(super) fn markers(&self) -> Option<&Vec<Marker>> {
|
||||
match &self {
|
||||
pub(super) fn markers(&self) -> Option<&Rc<Vec<Marker>>> {
|
||||
match self {
|
||||
InputMode::CodeEditor { markers, .. } => Some(markers),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clear_markers(&mut self) {
|
||||
pub(super) fn set_markers(&mut self, new_markers: Vec<Marker>) {
|
||||
match self {
|
||||
InputMode::CodeEditor { markers, .. } => markers.clear(),
|
||||
InputMode::CodeEditor { markers, .. } => *markers = Rc::new(new_markers),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn marker_for_offset(&self, offset: usize) -> Option<&Marker> {
|
||||
let Some(markers) = self.markers() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
for marker in markers.iter() {
|
||||
if let Some(range) = marker.range.as_ref() {
|
||||
if range.contains(&offset) {
|
||||
return Some(marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use super::{
|
|||
number_input,
|
||||
text_wrapper::TextWrapper,
|
||||
};
|
||||
use crate::input::hover_popover::DiagnosticPopover;
|
||||
use crate::input::marker::Marker;
|
||||
use crate::{history::History, scroll::ScrollbarState, Root};
|
||||
|
||||
|
|
@ -258,6 +259,9 @@ pub struct InputState {
|
|||
pub(crate) mask_pattern: MaskPattern,
|
||||
pub(super) placeholder: SharedString,
|
||||
|
||||
/// Popover
|
||||
diagnostic_popover: Option<Entity<DiagnosticPopover>>,
|
||||
|
||||
/// To remember the horizontal column (x-coordinate) of the cursor position.
|
||||
preferred_x_offset: Option<Pixels>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
|
|
@ -329,6 +333,7 @@ impl InputState {
|
|||
line_number_width: px(0.),
|
||||
placeholder: SharedString::default(),
|
||||
mask_pattern: MaskPattern::default(),
|
||||
diagnostic_popover: None,
|
||||
_subscriptions,
|
||||
}
|
||||
}
|
||||
|
|
@ -382,7 +387,7 @@ impl InputState {
|
|||
highlighter: Rc::new(RefCell::new(None)),
|
||||
line_number: true,
|
||||
height: Some(relative(1.)),
|
||||
markers: vec![],
|
||||
markers: Rc::new(vec![]),
|
||||
};
|
||||
self
|
||||
}
|
||||
|
|
@ -465,16 +470,12 @@ impl InputState {
|
|||
/// Set markers, only for [`InputMode::CodeEditor`] mode.
|
||||
///
|
||||
/// For example to set the diagnostic markers in the code editor.
|
||||
pub fn set_markers(
|
||||
&mut self,
|
||||
new_markers: Vec<Marker>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let InputMode::CodeEditor { markers, .. } = &mut self.mode {
|
||||
*markers = new_markers;
|
||||
cx.notify();
|
||||
pub fn set_markers(&mut self, markers: Vec<Marker>, _: &mut Window, _: &mut Context<Self>) {
|
||||
let mut markers = markers;
|
||||
for marker in &mut markers {
|
||||
marker.prepare(self);
|
||||
}
|
||||
self.mode.set_markers(markers);
|
||||
}
|
||||
|
||||
/// Set placeholder
|
||||
|
|
@ -490,13 +491,12 @@ impl InputState {
|
|||
|
||||
/// Called after moving the cursor. Updates preferred_x_offset if we know where the cursor now is.
|
||||
fn update_preferred_x_offset(&mut self, _cx: &mut Context<Self>) {
|
||||
if let (Some(lines), Some(bounds)) = (&self.last_layout, &self.last_bounds) {
|
||||
if let (Some(_), Some(bounds)) = (&self.last_layout, &self.last_bounds) {
|
||||
let offset = self.cursor_offset();
|
||||
let line_height = self.last_line_height;
|
||||
|
||||
// Find which line and sub-line the cursor is on and its position
|
||||
let (_line_index, _sub_line_index, cursor_pos) =
|
||||
self.line_and_position_for_offset(offset, lines, line_height);
|
||||
self.line_and_position_for_offset(offset);
|
||||
|
||||
if let Some(pos) = cursor_pos {
|
||||
// Adjust by scroll offset
|
||||
|
|
@ -507,19 +507,29 @@ impl InputState {
|
|||
}
|
||||
|
||||
/// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.
|
||||
fn line_and_position_for_offset(
|
||||
///
|
||||
/// Returns:
|
||||
///
|
||||
/// - The index of the line (zero-based) containing the offset.
|
||||
/// - The index of the sub-line (zero-based) within the line containing the offset.
|
||||
/// - The position of the offset.
|
||||
pub(super) fn line_and_position_for_offset(
|
||||
&self,
|
||||
offset: usize,
|
||||
lines: &[WrappedLine],
|
||||
line_height: Pixels,
|
||||
) -> (usize, usize, Option<Point<Pixels>>) {
|
||||
let Some(lines) = &self.last_layout else {
|
||||
return (0, 0, None);
|
||||
};
|
||||
let line_height = self.last_line_height;
|
||||
let line_number_width = self.line_number_width;
|
||||
|
||||
let mut prev_lines_offset = 0;
|
||||
let mut y_offset = px(0.);
|
||||
for (line_index, line) in lines.iter().enumerate() {
|
||||
let local_offset = offset.saturating_sub(prev_lines_offset);
|
||||
if let Some(pos) = line.position_for_index(local_offset, line_height) {
|
||||
let sub_line_index = (pos.y.0 / line_height.0) as usize;
|
||||
let adjusted_pos = point(pos.x, pos.y + y_offset);
|
||||
let adjusted_pos = point(pos.x + line_number_width, pos.y + y_offset);
|
||||
return (line_index, sub_line_index, Some(adjusted_pos));
|
||||
}
|
||||
|
||||
|
|
@ -543,7 +553,7 @@ impl InputState {
|
|||
let offset = self.cursor_offset();
|
||||
let line_height = self.last_line_height;
|
||||
let (current_line_index, current_sub_line, current_pos) =
|
||||
self.line_and_position_for_offset(offset, lines, line_height);
|
||||
self.line_and_position_for_offset(offset);
|
||||
|
||||
let Some(current_pos) = current_pos else {
|
||||
return;
|
||||
|
|
@ -1419,6 +1429,35 @@ impl InputState {
|
|||
self.selected_word_range = None;
|
||||
}
|
||||
|
||||
pub(super) fn on_mouse_move(
|
||||
&mut self,
|
||||
event: &MouseMoveEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let offset = self.index_for_mouse_position(event.position, window, cx);
|
||||
if let Some(marker) = self.mode.marker_for_offset(offset) {
|
||||
if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {
|
||||
if diagnostic_popover.read(cx).marker.range == marker.range {
|
||||
diagnostic_popover.update(cx, |this, cx| {
|
||||
this.show(cx);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.diagnostic_popover = Some(DiagnosticPopover::new(marker, cx.entity(), cx));
|
||||
cx.notify();
|
||||
} else {
|
||||
if let Some(diagnostic_popover) = self.diagnostic_popover.as_mut() {
|
||||
diagnostic_popover.update(cx, |this, cx| {
|
||||
this.check_to_hide(event.position, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn on_scroll_wheel(
|
||||
&mut self,
|
||||
event: &ScrollWheelEvent,
|
||||
|
|
@ -1427,6 +1466,7 @@ impl InputState {
|
|||
) {
|
||||
let delta = event.delta.pixel_delta(self.last_line_height);
|
||||
self.update_scroll_offset(Some(self.scroll_handle.offset() + delta), cx);
|
||||
self.diagnostic_popover = None;
|
||||
}
|
||||
|
||||
fn update_scroll_offset(&mut self, offset: Option<Point<Pixels>>, cx: &mut Context<Self>) {
|
||||
|
|
@ -1751,7 +1791,7 @@ impl InputState {
|
|||
cx.notify()
|
||||
}
|
||||
|
||||
fn offset_from_utf16(&self, offset: usize) -> usize {
|
||||
pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {
|
||||
let mut utf8_offset = 0;
|
||||
let mut utf16_count = 0;
|
||||
|
||||
|
|
@ -1766,7 +1806,7 @@ impl InputState {
|
|||
utf8_offset
|
||||
}
|
||||
|
||||
fn offset_to_utf16(&self, offset: usize) -> usize {
|
||||
pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {
|
||||
let mut utf16_offset = 0;
|
||||
let mut utf8_count = 0;
|
||||
|
||||
|
|
@ -1781,11 +1821,11 @@ impl InputState {
|
|||
utf16_offset
|
||||
}
|
||||
|
||||
fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
|
||||
pub(super) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
|
||||
self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
|
||||
}
|
||||
|
||||
fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
|
||||
pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
|
||||
self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
|
||||
}
|
||||
|
||||
|
|
@ -2164,5 +2204,6 @@ impl Render for InputState {
|
|||
.flex_grow()
|
||||
.overflow_x_hidden()
|
||||
.child(TextElement::new(cx.entity().clone()).placeholder(self.placeholder.clone()))
|
||||
.children(self.diagnostic_popover.clone())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ impl RenderOnce for TextInput {
|
|||
MouseButton::Left,
|
||||
window.listener_for(&self.state, InputState::on_mouse_up),
|
||||
)
|
||||
.on_mouse_move(window.listener_for(&self.state, InputState::on_mouse_move))
|
||||
.on_scroll_wheel(window.listener_for(&self.state, InputState::on_scroll_wheel))
|
||||
.size_full()
|
||||
.line_height(LINE_HEIGHT)
|
||||
|
|
|
|||
Loading…
Reference in a new issue