mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 02:46:43 +00:00
commit
638e9f9477
8 changed files with 228 additions and 13 deletions
|
@ -76,6 +76,7 @@ use util::{post_inc, ResultExt, TryFutureExt};
|
|||
use workspace::{ItemNavHistory, Workspace};
|
||||
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
||||
const MAX_LINE_LEN: usize = 1024;
|
||||
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
|
||||
const MAX_SELECTION_HISTORY_LEN: usize = 1024;
|
||||
|
@ -238,6 +239,9 @@ pub enum Direction {
|
|||
Next,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ScrollbarAutoHide(bool);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(Editor::new_file);
|
||||
cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx));
|
||||
|
@ -427,6 +431,8 @@ pub struct Editor {
|
|||
focused: bool,
|
||||
show_local_cursors: bool,
|
||||
show_local_selections: bool,
|
||||
show_scrollbars: bool,
|
||||
hide_scrollbar_task: Option<Task<()>>,
|
||||
blink_epoch: usize,
|
||||
blinking_paused: bool,
|
||||
mode: EditorMode,
|
||||
|
@ -1029,6 +1035,8 @@ impl Editor {
|
|||
focused: false,
|
||||
show_local_cursors: false,
|
||||
show_local_selections: true,
|
||||
show_scrollbars: true,
|
||||
hide_scrollbar_task: None,
|
||||
blink_epoch: 0,
|
||||
blinking_paused: false,
|
||||
mode,
|
||||
|
@ -1061,10 +1069,16 @@ impl Editor {
|
|||
],
|
||||
};
|
||||
this.end_selection(cx);
|
||||
this.make_scrollbar_visible(cx);
|
||||
|
||||
let editor_created_event = EditorCreated(cx.handle());
|
||||
cx.emit_global(editor_created_event);
|
||||
|
||||
if mode == EditorMode::Full {
|
||||
let should_auto_hide_scrollbars = cx.platform().should_auto_hide_scrollbars();
|
||||
cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
|
||||
}
|
||||
|
||||
this.report_event("open editor", cx);
|
||||
this
|
||||
}
|
||||
|
@ -1181,6 +1195,7 @@ impl Editor {
|
|||
self.scroll_top_anchor = anchor;
|
||||
}
|
||||
|
||||
self.make_scrollbar_visible(cx);
|
||||
self.autoscroll_request.take();
|
||||
hide_hover(self, cx);
|
||||
|
||||
|
@ -5952,6 +5967,31 @@ impl Editor {
|
|||
self.show_local_cursors && self.focused
|
||||
}
|
||||
|
||||
pub fn show_scrollbars(&self) -> bool {
|
||||
self.show_scrollbars
|
||||
}
|
||||
|
||||
fn make_scrollbar_visible(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if !self.show_scrollbars {
|
||||
self.show_scrollbars = true;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
if cx.default_global::<ScrollbarAutoHide>().0 {
|
||||
self.hide_scrollbar_task = Some(cx.spawn_weak(|this, mut cx| async move {
|
||||
Timer::after(SCROLLBAR_SHOW_INTERVAL).await;
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.show_scrollbars = false;
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
self.hide_scrollbar_task = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn on_buffer_changed(&mut self, _: ModelHandle<MultiBuffer>, cx: &mut ViewContext<Self>) {
|
||||
cx.notify();
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ use std::{
|
|||
cmp::{self, Ordering},
|
||||
fmt::Write,
|
||||
iter,
|
||||
ops::Range,
|
||||
ops::{DerefMut, Range},
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::DiffStyle;
|
||||
|
@ -454,7 +454,6 @@ impl EditorElement {
|
|||
let bounds = gutter_bounds.union_rect(text_bounds);
|
||||
let scroll_top =
|
||||
layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
|
||||
let editor = self.view(cx.app);
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: gutter_bounds,
|
||||
background: Some(self.style.gutter_background),
|
||||
|
@ -468,7 +467,7 @@ impl EditorElement {
|
|||
corner_radius: 0.,
|
||||
});
|
||||
|
||||
if let EditorMode::Full = editor.mode {
|
||||
if let EditorMode::Full = layout.mode {
|
||||
let mut active_rows = layout.active_rows.iter().peekable();
|
||||
while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
|
||||
let mut end_row = *start_row;
|
||||
|
@ -909,6 +908,125 @@ impl EditorElement {
|
|||
cx.scene.pop_layer();
|
||||
}
|
||||
|
||||
fn paint_scrollbar(&mut self, bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
|
||||
enum ScrollbarMouseHandlers {}
|
||||
if layout.mode != EditorMode::Full {
|
||||
return;
|
||||
}
|
||||
|
||||
let view = self.view.clone();
|
||||
let style = &self.style.theme.scrollbar;
|
||||
let min_thumb_height =
|
||||
style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size);
|
||||
|
||||
let top = bounds.min_y();
|
||||
let bottom = bounds.max_y();
|
||||
let right = bounds.max_x();
|
||||
let left = right - style.width;
|
||||
let height = bounds.height();
|
||||
let row_range = &layout.scrollbar_row_range;
|
||||
let max_row = layout.max_row + ((row_range.end - row_range.start) as u32);
|
||||
let scrollbar_start = row_range.start as f32 / max_row as f32;
|
||||
let scrollbar_end = row_range.end as f32 / max_row as f32;
|
||||
|
||||
let mut thumb_top = top + scrollbar_start * height;
|
||||
let mut thumb_bottom = top + scrollbar_end * height;
|
||||
let thumb_center = (thumb_top + thumb_bottom) / 2.0;
|
||||
|
||||
if thumb_bottom - thumb_top < min_thumb_height {
|
||||
thumb_top = thumb_center - min_thumb_height / 2.0;
|
||||
thumb_bottom = thumb_center + min_thumb_height / 2.0;
|
||||
if thumb_top < top {
|
||||
thumb_top = top;
|
||||
thumb_bottom = top + min_thumb_height;
|
||||
}
|
||||
if thumb_bottom > bottom {
|
||||
thumb_bottom = bottom;
|
||||
thumb_top = bottom - min_thumb_height;
|
||||
}
|
||||
}
|
||||
|
||||
let track_bounds = RectF::from_points(vec2f(left, top), vec2f(right, bottom));
|
||||
let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
|
||||
|
||||
if layout.show_scrollbars {
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: track_bounds,
|
||||
border: style.track.border,
|
||||
background: style.track.background_color,
|
||||
..Default::default()
|
||||
});
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: thumb_bounds,
|
||||
border: style.thumb.border,
|
||||
background: style.thumb.background_color,
|
||||
corner_radius: style.thumb.corner_radius,
|
||||
});
|
||||
}
|
||||
|
||||
cx.scene.push_cursor_region(CursorRegion {
|
||||
bounds: track_bounds,
|
||||
style: CursorStyle::Arrow,
|
||||
});
|
||||
cx.scene.push_mouse_region(
|
||||
MouseRegion::new::<ScrollbarMouseHandlers>(view.id(), view.id(), track_bounds)
|
||||
.on_move({
|
||||
let view = view.clone();
|
||||
move |_, cx| {
|
||||
if let Some(view) = view.upgrade(cx.deref_mut()) {
|
||||
view.update(cx.deref_mut(), |view, cx| {
|
||||
view.make_scrollbar_visible(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_down(MouseButton::Left, {
|
||||
let view = view.clone();
|
||||
let row_range = row_range.clone();
|
||||
move |e, cx| {
|
||||
let y = e.position.y();
|
||||
if let Some(view) = view.upgrade(cx.deref_mut()) {
|
||||
view.update(cx.deref_mut(), |view, cx| {
|
||||
if y < thumb_top || thumb_bottom < y {
|
||||
let center_row =
|
||||
((y - top) * max_row as f32 / height).round() as u32;
|
||||
let top_row = center_row.saturating_sub(
|
||||
(row_range.end - row_range.start) as u32 / 2,
|
||||
);
|
||||
let mut position = view.scroll_position(cx);
|
||||
position.set_y(top_row as f32);
|
||||
view.set_scroll_position(position, cx);
|
||||
} else {
|
||||
view.make_scrollbar_visible(cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_drag(MouseButton::Left, {
|
||||
let view = view.clone();
|
||||
move |e, cx| {
|
||||
let y = e.prev_mouse_position.y();
|
||||
let new_y = e.position.y();
|
||||
if thumb_top < y && y < thumb_bottom {
|
||||
if let Some(view) = view.upgrade(cx.deref_mut()) {
|
||||
view.update(cx.deref_mut(), |view, cx| {
|
||||
let mut position = view.scroll_position(cx);
|
||||
position.set_y(
|
||||
position.y() + (new_y - y) * (max_row as f32) / height,
|
||||
);
|
||||
if position.y() < 0.0 {
|
||||
position.set_y(0.);
|
||||
}
|
||||
view.set_scroll_position(position, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn paint_highlighted_range(
|
||||
&self,
|
||||
|
@ -1469,13 +1587,11 @@ impl Element for EditorElement {
|
|||
// The scroll position is a fractional point, the whole number of which represents
|
||||
// the top of the window in terms of display rows.
|
||||
let start_row = scroll_position.y() as u32;
|
||||
let scroll_top = scroll_position.y() * line_height;
|
||||
let visible_row_count = (size.y() / line_height).ceil() as u32;
|
||||
let max_row = snapshot.max_point().row();
|
||||
|
||||
// Add 1 to ensure selections bleed off screen
|
||||
let end_row = 1 + cmp::min(
|
||||
((scroll_top + size.y()) / line_height).ceil() as u32,
|
||||
snapshot.max_point().row(),
|
||||
);
|
||||
let end_row = 1 + cmp::min(start_row + visible_row_count, max_row);
|
||||
|
||||
let start_anchor = if start_row == 0 {
|
||||
Anchor::min()
|
||||
|
@ -1484,7 +1600,7 @@ impl Element for EditorElement {
|
|||
.buffer_snapshot
|
||||
.anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
|
||||
};
|
||||
let end_anchor = if end_row > snapshot.max_point().row() {
|
||||
let end_anchor = if end_row > max_row {
|
||||
Anchor::max()
|
||||
} else {
|
||||
snapshot
|
||||
|
@ -1496,6 +1612,7 @@ impl Element for EditorElement {
|
|||
let mut active_rows = BTreeMap::new();
|
||||
let mut highlighted_rows = None;
|
||||
let mut highlighted_ranges = Vec::new();
|
||||
let mut show_scrollbars = false;
|
||||
self.update_view(cx.app, |view, cx| {
|
||||
let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
|
||||
|
@ -1556,6 +1673,8 @@ impl Element for EditorElement {
|
|||
.collect(),
|
||||
));
|
||||
}
|
||||
|
||||
show_scrollbars = view.show_scrollbars();
|
||||
});
|
||||
|
||||
let line_number_layouts =
|
||||
|
@ -1566,6 +1685,9 @@ impl Element for EditorElement {
|
|||
.git_diff_hunks_in_range(start_row..end_row)
|
||||
.collect();
|
||||
|
||||
let scrollbar_row_range =
|
||||
scroll_position.y()..(scroll_position.y() + visible_row_count as f32);
|
||||
|
||||
let mut max_visible_line_width = 0.0;
|
||||
let line_layouts = self.layout_lines(start_row..end_row, &snapshot, cx);
|
||||
for line in &line_layouts {
|
||||
|
@ -1599,7 +1721,6 @@ impl Element for EditorElement {
|
|||
cx,
|
||||
);
|
||||
|
||||
let max_row = snapshot.max_point().row();
|
||||
let scroll_max = vec2f(
|
||||
((scroll_width - text_size.x()) / em_width).max(0.0),
|
||||
max_row.saturating_sub(1) as f32,
|
||||
|
@ -1629,6 +1750,7 @@ impl Element for EditorElement {
|
|||
let mut context_menu = None;
|
||||
let mut code_actions_indicator = None;
|
||||
let mut hover = None;
|
||||
let mut mode = EditorMode::Full;
|
||||
cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| {
|
||||
let newest_selection_head = view
|
||||
.selections
|
||||
|
@ -1650,6 +1772,7 @@ impl Element for EditorElement {
|
|||
|
||||
let visible_rows = start_row..start_row + line_layouts.len() as u32;
|
||||
hover = view.hover_state.render(&snapshot, &style, visible_rows, cx);
|
||||
mode = view.mode;
|
||||
});
|
||||
|
||||
if let Some((_, context_menu)) = context_menu.as_mut() {
|
||||
|
@ -1697,6 +1820,7 @@ impl Element for EditorElement {
|
|||
(
|
||||
size,
|
||||
LayoutState {
|
||||
mode,
|
||||
position_map: Arc::new(PositionMap {
|
||||
size,
|
||||
scroll_max,
|
||||
|
@ -1709,6 +1833,9 @@ impl Element for EditorElement {
|
|||
gutter_size,
|
||||
gutter_padding,
|
||||
text_size,
|
||||
scrollbar_row_range,
|
||||
show_scrollbars,
|
||||
max_row,
|
||||
gutter_margin,
|
||||
active_rows,
|
||||
highlighted_rows,
|
||||
|
@ -1756,11 +1883,12 @@ impl Element for EditorElement {
|
|||
}
|
||||
self.paint_text(text_bounds, visible_bounds, layout, cx);
|
||||
|
||||
cx.scene.push_layer(Some(bounds));
|
||||
if !layout.blocks.is_empty() {
|
||||
cx.scene.push_layer(Some(bounds));
|
||||
self.paint_blocks(bounds, visible_bounds, layout, cx);
|
||||
cx.scene.pop_layer();
|
||||
}
|
||||
self.paint_scrollbar(bounds, layout, cx);
|
||||
cx.scene.pop_layer();
|
||||
|
||||
cx.scene.pop_layer();
|
||||
}
|
||||
|
@ -1846,12 +1974,16 @@ pub struct LayoutState {
|
|||
gutter_padding: f32,
|
||||
gutter_margin: f32,
|
||||
text_size: Vector2F,
|
||||
mode: EditorMode,
|
||||
active_rows: BTreeMap<u32, bool>,
|
||||
highlighted_rows: Option<Range<u32>>,
|
||||
line_number_layouts: Vec<Option<text_layout::Line>>,
|
||||
blocks: Vec<BlockLayout>,
|
||||
highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
|
||||
selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
|
||||
scrollbar_row_range: Range<f32>,
|
||||
show_scrollbars: bool,
|
||||
max_row: u32,
|
||||
context_menu: Option<(DisplayPoint, ElementBox)>,
|
||||
diff_hunks: Vec<DiffHunk<u32>>,
|
||||
code_actions_indicator: Option<(u32, ElementBox)>,
|
||||
|
|
|
@ -65,6 +65,7 @@ pub trait Platform: Send + Sync {
|
|||
fn delete_credentials(&self, url: &str) -> Result<()>;
|
||||
|
||||
fn set_cursor_style(&self, style: CursorStyle);
|
||||
fn should_auto_hide_scrollbars(&self) -> bool;
|
||||
|
||||
fn local_timezone(&self) -> UtcOffset;
|
||||
|
||||
|
|
|
@ -709,6 +709,16 @@ impl platform::Platform for MacPlatform {
|
|||
}
|
||||
}
|
||||
|
||||
fn should_auto_hide_scrollbars(&self) -> bool {
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSScrollerStyleOverlay: NSInteger = 1;
|
||||
|
||||
unsafe {
|
||||
let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
|
||||
style == NSScrollerStyleOverlay
|
||||
}
|
||||
}
|
||||
|
||||
fn local_timezone(&self) -> UtcOffset {
|
||||
unsafe {
|
||||
let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
|
||||
|
|
|
@ -181,6 +181,10 @@ impl super::Platform for Platform {
|
|||
*self.cursor.lock() = style;
|
||||
}
|
||||
|
||||
fn should_auto_hide_scrollbars(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn local_timezone(&self) -> UtcOffset {
|
||||
UtcOffset::UTC
|
||||
}
|
||||
|
|
|
@ -554,6 +554,15 @@ pub struct Editor {
|
|||
pub link_definition: HighlightStyle,
|
||||
pub composition_mark: HighlightStyle,
|
||||
pub jump_icon: Interactive<IconButton>,
|
||||
pub scrollbar: Scrollbar,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
pub struct Scrollbar {
|
||||
pub track: ContainerStyle,
|
||||
pub thumb: ContainerStyle,
|
||||
pub width: f32,
|
||||
pub min_height_factor: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Theme from "../themes/common/theme";
|
||||
import { withOpacity } from "../utils/color";
|
||||
import {
|
||||
backgroundColor,
|
||||
border,
|
||||
|
@ -170,6 +171,24 @@ export default function editor(theme: Theme) {
|
|||
background: backgroundColor(theme, "on500"),
|
||||
},
|
||||
},
|
||||
scrollbar: {
|
||||
width: 12,
|
||||
minHeightFactor: 1.0,
|
||||
track: {
|
||||
border: {
|
||||
left: true,
|
||||
width: 1,
|
||||
color: borderColor(theme, "secondary"),
|
||||
},
|
||||
},
|
||||
thumb: {
|
||||
background: withOpacity(borderColor(theme, "secondary"), 0.5),
|
||||
border: {
|
||||
width: 1,
|
||||
color: withOpacity(borderColor(theme, 'muted'), 0.5),
|
||||
}
|
||||
}
|
||||
},
|
||||
compositionMark: {
|
||||
underline: {
|
||||
thickness: 1.0,
|
||||
|
|
|
@ -123,7 +123,7 @@ export function createTheme(
|
|||
const borderColor = {
|
||||
primary: sample(ramps.neutral, isLight ? 1.5 : 0),
|
||||
secondary: sample(ramps.neutral, isLight ? 1.25 : 1),
|
||||
muted: sample(ramps.neutral, isLight ? 1 : 3),
|
||||
muted: sample(ramps.neutral, isLight ? 1.25 : 3),
|
||||
active: sample(ramps.neutral, isLight ? 4 : 3),
|
||||
onMedia: withOpacity(darkest, 0.1),
|
||||
ok: sample(ramps.green, 0.3),
|
||||
|
|
Loading…
Reference in a new issue