Merge remote-tracking branch 'origin/main' into panels

This commit is contained in:
Antonio Scandurra 2023-05-23 08:24:28 +02:00
commit 208ff2fba7
33 changed files with 827 additions and 309 deletions

View file

@ -216,6 +216,8 @@ actions!(
MoveToNextSubwordEnd,
MoveToBeginningOfLine,
MoveToEndOfLine,
MoveToStartOfParagraph,
MoveToEndOfParagraph,
MoveToBeginning,
MoveToEnd,
SelectUp,
@ -226,6 +228,8 @@ actions!(
SelectToPreviousSubwordStart,
SelectToNextWordEnd,
SelectToNextSubwordEnd,
SelectToStartOfParagraph,
SelectToEndOfParagraph,
SelectToBeginning,
SelectToEnd,
SelectAll,
@ -337,6 +341,8 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::move_to_next_subword_end);
cx.add_action(Editor::move_to_beginning_of_line);
cx.add_action(Editor::move_to_end_of_line);
cx.add_action(Editor::move_to_start_of_paragraph);
cx.add_action(Editor::move_to_end_of_paragraph);
cx.add_action(Editor::move_to_beginning);
cx.add_action(Editor::move_to_end);
cx.add_action(Editor::select_up);
@ -349,6 +355,8 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::select_to_next_subword_end);
cx.add_action(Editor::select_to_beginning_of_line);
cx.add_action(Editor::select_to_end_of_line);
cx.add_action(Editor::select_to_start_of_paragraph);
cx.add_action(Editor::select_to_end_of_paragraph);
cx.add_action(Editor::select_to_beginning);
cx.add_action(Editor::select_to_end);
cx.add_action(Editor::select_all);
@ -525,15 +533,6 @@ pub struct EditorSnapshot {
ongoing_scroll: OngoingScroll,
}
impl EditorSnapshot {
fn has_scrollbar_info(&self) -> bool {
self.buffer_snapshot
.git_diff_hunks_in_range(0..self.max_point().row())
.next()
.is_some()
}
}
#[derive(Clone, Debug)]
struct SelectionHistoryEntry {
selections: Arc<[Selection<Anchor>]>,
@ -4762,6 +4761,80 @@ impl Editor {
});
}
pub fn move_to_start_of_paragraph(
&mut self,
_: &MoveToStartOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|map, selection| {
selection.collapse_to(
movement::start_of_paragraph(map, selection.head()),
SelectionGoal::None,
)
});
})
}
pub fn move_to_end_of_paragraph(
&mut self,
_: &MoveToEndOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|map, selection| {
selection.collapse_to(
movement::end_of_paragraph(map, selection.head()),
SelectionGoal::None,
)
});
})
}
pub fn select_to_start_of_paragraph(
&mut self,
_: &SelectToStartOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_heads_with(|map, head, _| {
(movement::start_of_paragraph(map, head), SelectionGoal::None)
});
})
}
pub fn select_to_end_of_paragraph(
&mut self,
_: &SelectToEndOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_heads_with(|map, head, _| {
(movement::end_of_paragraph(map, head), SelectionGoal::None)
});
})
}
pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
@ -7128,6 +7201,7 @@ pub enum Event {
BufferEdited,
Edited,
Reparsed,
Focused,
Blurred,
DirtyChanged,
Saved,
@ -7181,6 +7255,7 @@ impl View for Editor {
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
if cx.is_self_focused() {
let focused_event = EditorFocused(cx.handle());
cx.emit(Event::Focused);
cx.emit_global(focused_event);
}
if let Some(rename) = self.pending_rename.as_ref() {

View file

@ -7,25 +7,36 @@ pub struct EditorSettings {
pub cursor_blink: bool,
pub hover_popover_enabled: bool,
pub show_completions_on_input: bool,
pub show_scrollbars: ShowScrollbars,
pub scrollbar: Scrollbar,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct Scrollbar {
pub show: ShowScrollbar,
pub git_diff: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ShowScrollbars {
#[default]
pub enum ShowScrollbar {
Auto,
System,
Always,
Never,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct EditorSettingsContent {
pub cursor_blink: Option<bool>,
pub hover_popover_enabled: Option<bool>,
pub show_completions_on_input: Option<bool>,
pub show_scrollbars: Option<ShowScrollbars>,
pub scrollbar: Option<ScrollbarContent>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct ScrollbarContent {
pub show: Option<ShowScrollbar>,
pub git_diff: Option<bool>,
}
impl Setting for EditorSettings {

View file

@ -1243,6 +1243,118 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
cx.set_state(
&r#"ˇone
two
three
fourˇ
five
six"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
cx.assert_editor_state(
&r#"one
two
ˇ
three
four
five
ˇ
six"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
cx.assert_editor_state(
&r#"one
two
three
four
five
ˇ
sixˇ"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
cx.assert_editor_state(
&r#"ˇone
two
three
four
five
sixˇ"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
cx.assert_editor_state(
&r#"ˇone
two
ˇ
three
four
five
six"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
cx.assert_editor_state(
&r#"ˇone
two
three
four
five
sixˇ"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
cx.assert_editor_state(
&r#"one
two
three
four
five
ˇ
sixˇ"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
cx.assert_editor_state(
&r#"one
two
ˇ
three
four
five
ˇ
six"#
.unindent(),
);
}
#[gpui::test]
async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});

View file

@ -5,7 +5,7 @@ use super::{
};
use crate::{
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock},
editor_settings::ShowScrollbars,
editor_settings::ShowScrollbar,
git::{diff_hunk_to_display, DisplayDiffHunk},
hover_popover::{
hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH,
@ -1052,51 +1052,53 @@ impl EditorElement {
..Default::default()
});
let diff_style = theme::current(cx).editor.diff.clone();
for hunk in layout
.position_map
.snapshot
.buffer_snapshot
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
{
let start_display = Point::new(hunk.buffer_range.start, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let end_display = Point::new(hunk.buffer_range.end, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let start_y = y_for_row(start_display.row() as f32);
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
y_for_row((end_display.row() + 1) as f32)
} else {
y_for_row((end_display.row()) as f32)
};
if layout.is_singleton && settings::get::<EditorSettings>(cx).scrollbar.git_diff {
let diff_style = theme::current(cx).editor.scrollbar.git.clone();
for hunk in layout
.position_map
.snapshot
.buffer_snapshot
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
{
let start_display = Point::new(hunk.buffer_range.start, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let end_display = Point::new(hunk.buffer_range.end, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let start_y = y_for_row(start_display.row() as f32);
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
y_for_row((end_display.row() + 1) as f32)
} else {
y_for_row((end_display.row()) as f32)
};
if end_y - start_y < 1. {
end_y = start_y + 1.;
if end_y - start_y < 1. {
end_y = start_y + 1.;
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
let color = match hunk.status() {
DiffHunkStatus::Added => diff_style.inserted,
DiffHunkStatus::Modified => diff_style.modified,
DiffHunkStatus::Removed => diff_style.deleted,
};
let border = Border {
width: 1.,
color: style.thumb.border.color,
overlay: false,
top: false,
right: true,
bottom: false,
left: true,
};
scene.push_quad(Quad {
bounds,
background: Some(color),
border,
corner_radius: style.thumb.corner_radius,
})
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
let color = match hunk.status() {
DiffHunkStatus::Added => diff_style.inserted,
DiffHunkStatus::Modified => diff_style.modified,
DiffHunkStatus::Removed => diff_style.deleted,
};
let border = Border {
width: 1.,
color: style.thumb.border.color,
overlay: false,
top: false,
right: true,
bottom: false,
left: true,
};
scene.push_quad(Quad {
bounds,
background: Some(color),
border,
corner_radius: style.thumb.corner_radius,
})
}
scene.push_quad(Quad {
@ -2065,13 +2067,17 @@ impl Element<Editor> for EditorElement {
));
}
let show_scrollbars = match settings::get::<EditorSettings>(cx).show_scrollbars {
ShowScrollbars::Auto => {
snapshot.has_scrollbar_info() || editor.scroll_manager.scrollbars_visible()
let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
let show_scrollbars = match scrollbar_settings.show {
ShowScrollbar::Auto => {
// Git
(is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
// Scrollmanager
|| editor.scroll_manager.scrollbars_visible()
}
ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(),
ShowScrollbars::Always => true,
ShowScrollbars::Never => false,
ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
ShowScrollbar::Always => true,
ShowScrollbar::Never => false,
};
let include_root = editor
@ -2290,6 +2296,7 @@ impl Element<Editor> for EditorElement {
text_size,
scrollbar_row_range,
show_scrollbars,
is_singleton,
max_row,
gutter_margin,
active_rows,
@ -2445,6 +2452,7 @@ pub struct LayoutState {
selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
scrollbar_row_range: Range<f32>,
show_scrollbars: bool,
is_singleton: bool,
max_row: u32,
context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
code_actions_indicator: Option<(u32, AnyElement<Editor>)>,

View file

@ -193,6 +193,44 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo
})
}
pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
let point = display_point.to_point(map);
if point.row == 0 {
return map.max_point();
}
let mut found_non_blank_line = false;
for row in (0..point.row + 1).rev() {
let blank = map.buffer_snapshot.is_line_blank(row);
if found_non_blank_line && blank {
return Point::new(row, 0).to_display_point(map);
}
found_non_blank_line |= !blank;
}
DisplayPoint::zero()
}
pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
let point = display_point.to_point(map);
if point.row == map.max_buffer_row() {
return DisplayPoint::zero();
}
let mut found_non_blank_line = false;
for row in point.row..map.max_buffer_row() + 1 {
let blank = map.buffer_snapshot.is_line_blank(row);
if found_non_blank_line && blank {
return Point::new(row, 0).to_display_point(map);
}
found_non_blank_line |= !blank;
}
map.max_point()
}
/// Scans for a boundary preceding the given start point `from` until a boundary is found, indicated by the
/// given predicate returning true. The predicate is called with the character to the left and right
/// of the candidate boundary location, and will be called with `\n` characters indicating the start

View file

@ -2841,6 +2841,15 @@ impl MultiBufferSnapshot {
})
}
pub fn has_git_diffs(&self) -> bool {
for excerpt in self.excerpts.iter() {
if !excerpt.buffer.git_diff.is_empty() {
return true;
}
}
false
}
pub fn git_diff_hunks_in_range_rev<'a>(
&'a self,
row_range: Range<u32>,