Merge remote-tracking branch 'origin/main' into panels
This commit is contained in:
commit
208ff2fba7
33 changed files with 827 additions and 309 deletions
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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, |_| {});
|
||||
|
|
|
@ -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>)>,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue