mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Merge branch 'main' of github.com:zed-industries/zed into project_search_design
This commit is contained in:
commit
d727ba18f2
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3172,7 +3172,6 @@ dependencies = [
|
||||
name = "gpui_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
|
@ -265,7 +265,6 @@
|
||||
"alt-enter": "search::SelectAllMatches",
|
||||
"alt-cmd-c": "search::ToggleCaseSensitive",
|
||||
"alt-cmd-w": "search::ToggleWholeWord",
|
||||
"alt-cmd-r": "search::ActivateRegexMode",
|
||||
"alt-tab": "search::CycleMode",
|
||||
"alt-cmd-f": "project_search::ToggleFilters"
|
||||
}
|
||||
|
@ -101,6 +101,8 @@
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"v": "vim::ToggleVisual",
|
||||
"shift-v": "vim::ToggleVisualLine",
|
||||
"*": "vim::MoveToNext",
|
||||
"#": "vim::MoveToPrev",
|
||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||
@ -236,6 +238,14 @@
|
||||
"ctrl-w ctrl-q": "pane::CloseAllItems"
|
||||
}
|
||||
},
|
||||
{
|
||||
// escape is in its own section so that it cancels a pending count.
|
||||
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl+[": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == normal && (vim_operator == none || vim_operator == n) && !VimWaiting",
|
||||
"bindings": {
|
||||
@ -266,22 +276,6 @@
|
||||
"o": "vim::InsertLineBelow",
|
||||
"shift-o": "vim::InsertLineAbove",
|
||||
"~": "vim::ChangeCase",
|
||||
"v": [
|
||||
"vim::SwitchMode",
|
||||
{
|
||||
"Visual": {
|
||||
"line": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"shift-v": [
|
||||
"vim::SwitchMode",
|
||||
{
|
||||
"Visual": {
|
||||
"line": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"p": "vim::Paste",
|
||||
"u": "editor::Undo",
|
||||
"ctrl-r": "editor::Redo",
|
||||
@ -374,12 +368,14 @@
|
||||
"context": "Editor && vim_mode == visual && !VimWaiting",
|
||||
"bindings": {
|
||||
"u": "editor::Undo",
|
||||
"c": "vim::VisualChange",
|
||||
"o": "vim::OtherEnd",
|
||||
"shift-o": "vim::OtherEnd",
|
||||
"d": "vim::VisualDelete",
|
||||
"x": "vim::VisualDelete",
|
||||
"y": "vim::VisualYank",
|
||||
"p": "vim::VisualPaste",
|
||||
"s": "vim::Substitute",
|
||||
"c": "vim::Substitute",
|
||||
"~": "vim::ChangeCase",
|
||||
"r": [
|
||||
"vim::PushOperator",
|
||||
@ -389,6 +385,14 @@
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"ctrl+[": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
">": "editor::Indent",
|
||||
"<": "editor::Outdent"
|
||||
}
|
||||
|
@ -214,7 +214,9 @@
|
||||
"copilot": {
|
||||
// The set of glob patterns for which copilot should be disabled
|
||||
// in any matching file.
|
||||
"disabled_globs": [".env"]
|
||||
"disabled_globs": [
|
||||
".env"
|
||||
]
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
|
@ -318,7 +318,7 @@ impl View for ActivityIndicator {
|
||||
on_click,
|
||||
} = self.content_to_render(cx);
|
||||
|
||||
let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
|
||||
let mut element = MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
|
||||
let theme = &theme::current(cx).workspace.status_bar.lsp_status;
|
||||
let style = if state.hovered() && on_click.is_some() {
|
||||
theme.hovered.as_ref().unwrap_or(&theme.default)
|
||||
|
@ -348,7 +348,7 @@ impl AssistantPanel {
|
||||
enum History {}
|
||||
let theme = theme::current(cx);
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
MouseEventHandler::<History, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<History, _>(0, cx, |state, _| {
|
||||
let style = theme.assistant.hamburger_button.style_for(state);
|
||||
Svg::for_style(style.icon.clone())
|
||||
.contained()
|
||||
@ -380,7 +380,7 @@ impl AssistantPanel {
|
||||
fn render_split_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
|
||||
let theme = theme::current(cx);
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
MouseEventHandler::<Split, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<Split, _>(0, cx, |state, _| {
|
||||
let style = theme.assistant.split_button.style_for(state);
|
||||
Svg::for_style(style.icon.clone())
|
||||
.contained()
|
||||
@ -404,7 +404,7 @@ impl AssistantPanel {
|
||||
fn render_assist_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
|
||||
let theme = theme::current(cx);
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
MouseEventHandler::<Assist, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<Assist, _>(0, cx, |state, _| {
|
||||
let style = theme.assistant.assist_button.style_for(state);
|
||||
Svg::for_style(style.icon.clone())
|
||||
.contained()
|
||||
@ -422,7 +422,7 @@ impl AssistantPanel {
|
||||
fn render_quote_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
|
||||
let theme = theme::current(cx);
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
MouseEventHandler::<QuoteSelection, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<QuoteSelection, _>(0, cx, |state, _| {
|
||||
let style = theme.assistant.quote_button.style_for(state);
|
||||
Svg::for_style(style.icon.clone())
|
||||
.contained()
|
||||
@ -450,7 +450,7 @@ impl AssistantPanel {
|
||||
fn render_plus_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
|
||||
let theme = theme::current(cx);
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
MouseEventHandler::<NewConversation, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<NewConversation, _>(0, cx, |state, _| {
|
||||
let style = theme.assistant.plus_button.style_for(state);
|
||||
Svg::for_style(style.icon.clone())
|
||||
.contained()
|
||||
@ -480,7 +480,7 @@ impl AssistantPanel {
|
||||
&theme.assistant.zoom_in_button
|
||||
};
|
||||
|
||||
MouseEventHandler::<ToggleZoomButton, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<ToggleZoomButton, _>(0, cx, |state, _| {
|
||||
let style = style.style_for(state);
|
||||
Svg::for_style(style.icon.clone())
|
||||
.contained()
|
||||
@ -506,7 +506,7 @@ impl AssistantPanel {
|
||||
) -> impl Element<Self> {
|
||||
let conversation = &self.saved_conversations[index];
|
||||
let path = conversation.path.clone();
|
||||
MouseEventHandler::<SavedConversationMetadata, _>::new(index, cx, move |state, cx| {
|
||||
MouseEventHandler::new::<SavedConversationMetadata, _>(index, cx, move |state, cx| {
|
||||
let style = &theme::current(cx).assistant.saved_conversation;
|
||||
Flex::row()
|
||||
.with_child(
|
||||
@ -1818,7 +1818,7 @@ impl ConversationEditor {
|
||||
let theme = theme::current(cx);
|
||||
let style = &theme.assistant;
|
||||
let message_id = message.id;
|
||||
let sender = MouseEventHandler::<Sender, _>::new(
|
||||
let sender = MouseEventHandler::new::<Sender, _>(
|
||||
message_id.0,
|
||||
cx,
|
||||
|state, _| match message.role {
|
||||
@ -2044,7 +2044,7 @@ impl ConversationEditor {
|
||||
) -> impl Element<Self> {
|
||||
enum Model {}
|
||||
|
||||
MouseEventHandler::<Model, _>::new(0, cx, |state, cx| {
|
||||
MouseEventHandler::new::<Model, _>(0, cx, |state, cx| {
|
||||
let style = style.model.style_for(state);
|
||||
Label::new(self.conversation.read(cx).model.clone(), style.text.clone())
|
||||
.contained()
|
||||
|
@ -31,7 +31,7 @@ impl View for UpdateNotification {
|
||||
|
||||
let app_name = cx.global::<ReleaseChannel>().display_name();
|
||||
|
||||
MouseEventHandler::<ViewReleaseNotes, _>::new(0, cx, |state, cx| {
|
||||
MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Flex::row()
|
||||
@ -48,7 +48,7 @@ impl View for UpdateNotification {
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
|
@ -82,7 +82,7 @@ impl View for Breadcrumbs {
|
||||
.into_any();
|
||||
}
|
||||
|
||||
MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
|
||||
let style = style.style_for(state);
|
||||
crumbs.with_style(style.container)
|
||||
})
|
||||
|
@ -7953,7 +7953,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
inlay_cache.version(),
|
||||
edits_made,
|
||||
"Host editor update the cache version after every cache/view change",
|
||||
);
|
||||
});
|
||||
@ -7976,7 +7977,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
inlay_cache.version(),
|
||||
edits_made,
|
||||
"Guest editor update the cache version after every cache/view change"
|
||||
);
|
||||
});
|
||||
@ -7996,7 +7998,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
"Host should get hints from the 1st edit and 1st LSP query"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, edits_made);
|
||||
assert_eq!(inlay_cache.version(), edits_made);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
assert_eq!(
|
||||
@ -8010,7 +8012,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
"Guest should get hints the 1st edit and 2nd LSP query"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, edits_made);
|
||||
assert_eq!(inlay_cache.version(), edits_made);
|
||||
});
|
||||
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
@ -8035,7 +8037,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
4th query was made by guest (but not applied) due to cache invalidation logic"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, edits_made);
|
||||
assert_eq!(inlay_cache.version(), edits_made);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
assert_eq!(
|
||||
@ -8051,7 +8053,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
"Guest should get hints from 3rd edit, 6th LSP query"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, edits_made);
|
||||
assert_eq!(inlay_cache.version(), edits_made);
|
||||
});
|
||||
|
||||
fake_language_server
|
||||
@ -8077,7 +8079,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
inlay_cache.version(),
|
||||
edits_made,
|
||||
"Host should accepted all edits and bump its cache version every time"
|
||||
);
|
||||
});
|
||||
@ -8098,7 +8101,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version,
|
||||
inlay_cache.version(),
|
||||
edits_made,
|
||||
"Guest should accepted all edits and bump its cache version every time"
|
||||
);
|
||||
@ -8264,7 +8267,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, 0,
|
||||
inlay_cache.version(),
|
||||
0,
|
||||
"Host should not increment its cache version due to no changes",
|
||||
);
|
||||
});
|
||||
@ -8279,7 +8283,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
inlay_cache.version(),
|
||||
edits_made,
|
||||
"Guest editor update the cache version after every cache/view change"
|
||||
);
|
||||
});
|
||||
@ -8296,7 +8301,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, 0,
|
||||
inlay_cache.version(),
|
||||
0,
|
||||
"Host should not increment its cache version due to no changes",
|
||||
);
|
||||
});
|
||||
@ -8311,7 +8317,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
inlay_cache.version(),
|
||||
edits_made,
|
||||
"Guest should accepted all edits and bump its cache version every time"
|
||||
);
|
||||
});
|
||||
@ -8343,13 +8350,10 @@ fn room_participants(room: &ModelHandle<Room>, cx: &mut TestAppContext) -> RoomP
|
||||
|
||||
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
|
||||
let mut labels = Vec::new();
|
||||
for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
|
||||
let excerpt_hints = excerpt_hints.read();
|
||||
for (_, inlay) in excerpt_hints.hints.iter() {
|
||||
match &inlay.label {
|
||||
project::InlayHintLabel::String(s) => labels.push(s.to_string()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
for hint in editor.inlay_hint_cache().hints() {
|
||||
match hint.label {
|
||||
project::InlayHintLabel::String(s) => labels.push(s),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
labels
|
||||
|
@ -226,7 +226,7 @@ impl CollabTitlebarItem {
|
||||
let mut ret = Flex::row().with_child(
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ToggleProjectMenu, Self>::new(0, cx, |mouse_state, cx| {
|
||||
MouseEventHandler::new::<ToggleProjectMenu, _>(0, cx, |mouse_state, cx| {
|
||||
let style = project_style
|
||||
.in_state(self.project_popover.is_some())
|
||||
.style_for(mouse_state);
|
||||
@ -266,7 +266,7 @@ impl CollabTitlebarItem {
|
||||
.with_child(
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ToggleVcsMenu, Self>::new(
|
||||
MouseEventHandler::new::<ToggleVcsMenu, _>(
|
||||
0,
|
||||
cx,
|
||||
|mouse_state, cx| {
|
||||
@ -398,7 +398,7 @@ impl CollabTitlebarItem {
|
||||
self.branch_popover.as_ref().map(|child| {
|
||||
let theme = theme::current(cx).clone();
|
||||
let child = ChildView::new(child, cx);
|
||||
let child = MouseEventHandler::<BranchList, Self>::new(0, cx, |_, _| {
|
||||
let child = MouseEventHandler::new::<BranchList, _>(0, cx, |_, _| {
|
||||
child
|
||||
.flex(1., true)
|
||||
.contained()
|
||||
@ -433,7 +433,7 @@ impl CollabTitlebarItem {
|
||||
self.project_popover.as_ref().map(|child| {
|
||||
let theme = theme::current(cx).clone();
|
||||
let child = ChildView::new(child, cx);
|
||||
let child = MouseEventHandler::<RecentProjects, Self>::new(0, cx, |_, _| {
|
||||
let child = MouseEventHandler::new::<RecentProjects, _>(0, cx, |_, _| {
|
||||
child
|
||||
.flex(1., true)
|
||||
.contained()
|
||||
@ -560,7 +560,7 @@ impl CollabTitlebarItem {
|
||||
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ToggleContactsMenu, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<ToggleContactsMenu, _>(0, cx, |state, _| {
|
||||
let style = titlebar
|
||||
.toggle_contacts_button
|
||||
.in_state(self.contacts_popover.is_some())
|
||||
@ -610,7 +610,7 @@ impl CollabTitlebarItem {
|
||||
|
||||
let active = room.read(cx).is_screen_sharing();
|
||||
let titlebar = &theme.titlebar;
|
||||
MouseEventHandler::<ToggleScreenSharing, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<ToggleScreenSharing, _>(0, cx, |state, _| {
|
||||
let style = titlebar
|
||||
.screen_share_button
|
||||
.in_state(active)
|
||||
@ -659,7 +659,7 @@ impl CollabTitlebarItem {
|
||||
}
|
||||
|
||||
let titlebar = &theme.titlebar;
|
||||
MouseEventHandler::<ToggleMute, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<ToggleMute, _>(0, cx, |state, _| {
|
||||
let style = titlebar
|
||||
.toggle_microphone_button
|
||||
.in_state(is_muted)
|
||||
@ -712,7 +712,7 @@ impl CollabTitlebarItem {
|
||||
}
|
||||
|
||||
let titlebar = &theme.titlebar;
|
||||
MouseEventHandler::<ToggleDeafen, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<ToggleDeafen, _>(0, cx, |state, _| {
|
||||
let style = titlebar
|
||||
.toggle_speakers_button
|
||||
.in_state(is_deafened)
|
||||
@ -747,7 +747,7 @@ impl CollabTitlebarItem {
|
||||
let tooltip = "Leave call";
|
||||
|
||||
let titlebar = &theme.titlebar;
|
||||
MouseEventHandler::<LeaveCall, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<LeaveCall, _>(0, cx, |state, _| {
|
||||
let style = titlebar.leave_call_button.style_for(state);
|
||||
Svg::new(icon)
|
||||
.with_color(style.color)
|
||||
@ -801,7 +801,7 @@ impl CollabTitlebarItem {
|
||||
Some(
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<ShareUnshare, _>(0, cx, |state, _| {
|
||||
//TODO: Ensure this button has consistent width for both text variations
|
||||
let style = titlebar.share_button.inactive_state().style_for(state);
|
||||
Label::new(label, style.text.clone())
|
||||
@ -847,7 +847,7 @@ impl CollabTitlebarItem {
|
||||
let avatar_style = &user_menu_button_style.avatar;
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<ToggleUserMenu, _>(0, cx, |state, _| {
|
||||
let style = user_menu_button_style
|
||||
.user_menu
|
||||
.inactive_state()
|
||||
@ -907,7 +907,7 @@ impl CollabTitlebarItem {
|
||||
|
||||
fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let titlebar = &theme.titlebar;
|
||||
MouseEventHandler::<SignIn, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<SignIn, _>(0, cx, |state, _| {
|
||||
let style = titlebar.sign_in_button.inactive_state().style_for(state);
|
||||
Label::new("Sign In", style.text.clone())
|
||||
.contained()
|
||||
@ -1142,7 +1142,7 @@ impl CollabTitlebarItem {
|
||||
if let Some(replica_id) = replica_id {
|
||||
enum ToggleFollow {}
|
||||
|
||||
content = MouseEventHandler::<ToggleFollow, Self>::new(
|
||||
content = MouseEventHandler::new::<ToggleFollow, _>(
|
||||
replica_id.into(),
|
||||
cx,
|
||||
move |_, _| content,
|
||||
@ -1173,7 +1173,7 @@ impl CollabTitlebarItem {
|
||||
enum JoinProject {}
|
||||
|
||||
let user_id = user.id;
|
||||
content = MouseEventHandler::<JoinProject, Self>::new(
|
||||
content = MouseEventHandler::new::<JoinProject, _>(
|
||||
peer_id.as_u64() as usize,
|
||||
cx,
|
||||
move |_, _| content,
|
||||
@ -1261,7 +1261,7 @@ impl CollabTitlebarItem {
|
||||
.into_any(),
|
||||
),
|
||||
client::Status::UpgradeRequired => Some(
|
||||
MouseEventHandler::<ConnectionStatusButton, Self>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<ConnectionStatusButton, _>(0, cx, |_, _| {
|
||||
Label::new(
|
||||
"Please update Zed to collaborate",
|
||||
theme.titlebar.outdated_warning.text.clone(),
|
||||
|
@ -810,7 +810,7 @@ impl ContactList {
|
||||
worktree_root_names.join(", ")
|
||||
};
|
||||
|
||||
MouseEventHandler::<JoinProject, Self>::new(project_id as usize, cx, |mouse_state, _| {
|
||||
MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, _| {
|
||||
let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
|
||||
let row = theme
|
||||
.project_row
|
||||
@ -904,7 +904,7 @@ impl ContactList {
|
||||
let baseline_offset =
|
||||
row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
|
||||
|
||||
MouseEventHandler::<OpenSharedScreen, Self>::new(
|
||||
MouseEventHandler::new::<OpenSharedScreen, _>(
|
||||
peer_id.as_u64() as usize,
|
||||
cx,
|
||||
|mouse_state, _| {
|
||||
@ -1006,7 +1006,7 @@ impl ContactList {
|
||||
};
|
||||
let leave_call = if section == Section::ActiveCall {
|
||||
Some(
|
||||
MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<LeaveCallContactList, _>(0, cx, |state, _| {
|
||||
let style = theme.leave_call.style_for(state);
|
||||
Label::new("Leave Call", style.text.clone())
|
||||
.contained()
|
||||
@ -1024,7 +1024,7 @@ impl ContactList {
|
||||
};
|
||||
|
||||
let icon_size = theme.section_icon_size;
|
||||
MouseEventHandler::<Header, Self>::new(section as usize, cx, |_, _| {
|
||||
MouseEventHandler::new::<Header, _>(section as usize, cx, |_, _| {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Svg::new(if is_collapsed {
|
||||
@ -1075,7 +1075,7 @@ impl ContactList {
|
||||
let github_login = contact.user.github_login.clone();
|
||||
let initial_project = project.clone();
|
||||
let mut event_handler =
|
||||
MouseEventHandler::<Contact, Self>::new(contact.user.id as usize, cx, |_, cx| {
|
||||
MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |_, cx| {
|
||||
Flex::row()
|
||||
.with_children(contact.user.avatar.clone().map(|avatar| {
|
||||
let status_badge = if contact.online {
|
||||
@ -1114,7 +1114,7 @@ impl ContactList {
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Cancel, Self>::new(
|
||||
MouseEventHandler::new::<Cancel, _>(
|
||||
contact.user.id as usize,
|
||||
cx,
|
||||
|mouse_state, _| {
|
||||
@ -1208,7 +1208,7 @@ impl ContactList {
|
||||
|
||||
if is_incoming {
|
||||
row.add_child(
|
||||
MouseEventHandler::<Decline, Self>::new(user.id as usize, cx, |mouse_state, _| {
|
||||
MouseEventHandler::new::<Decline, _>(user.id as usize, cx, |mouse_state, _| {
|
||||
let button_style = if is_contact_request_pending {
|
||||
&theme.disabled_button
|
||||
} else {
|
||||
@ -1231,7 +1231,7 @@ impl ContactList {
|
||||
);
|
||||
|
||||
row.add_child(
|
||||
MouseEventHandler::<Accept, Self>::new(user.id as usize, cx, |mouse_state, _| {
|
||||
MouseEventHandler::new::<Accept, _>(user.id as usize, cx, |mouse_state, _| {
|
||||
let button_style = if is_contact_request_pending {
|
||||
&theme.disabled_button
|
||||
} else {
|
||||
@ -1254,7 +1254,7 @@ impl ContactList {
|
||||
);
|
||||
} else {
|
||||
row.add_child(
|
||||
MouseEventHandler::<Cancel, Self>::new(user.id as usize, cx, |mouse_state, _| {
|
||||
MouseEventHandler::new::<Cancel, _>(user.id as usize, cx, |mouse_state, _| {
|
||||
let button_style = if is_contact_request_pending {
|
||||
&theme.disabled_button
|
||||
} else {
|
||||
@ -1333,7 +1333,7 @@ impl View for ContactList {
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<AddContact, Self>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<AddContact, _>(0, cx, |_, _| {
|
||||
render_icon_button(
|
||||
&theme.contact_list.add_contact_button,
|
||||
"icons/user_plus_16.svg",
|
||||
|
@ -113,7 +113,7 @@ impl View for ContactsPopover {
|
||||
Child::ContactFinder(child) => ChildView::new(child, cx),
|
||||
};
|
||||
|
||||
MouseEventHandler::<ContactsPopover, Self>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<ContactsPopover, _>(0, cx, |_, _| {
|
||||
Flex::column()
|
||||
.with_child(child.flex(1., true))
|
||||
.contained()
|
||||
|
@ -173,7 +173,7 @@ impl IncomingCallNotification {
|
||||
let theme = theme::current(cx);
|
||||
Flex::column()
|
||||
.with_child(
|
||||
MouseEventHandler::<Accept, Self>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
|
||||
let theme = &theme.incoming_call_notification;
|
||||
Label::new("Accept", theme.accept_button.text.clone())
|
||||
.aligned()
|
||||
@ -187,7 +187,7 @@ impl IncomingCallNotification {
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Decline, Self>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
|
||||
let theme = &theme.incoming_call_notification;
|
||||
Label::new("Decline", theme.decline_button.text.clone())
|
||||
.aligned()
|
||||
|
@ -52,7 +52,7 @@ where
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Dismiss, V>::new(user.id as usize, cx, |state, _| {
|
||||
MouseEventHandler::new::<Dismiss, _>(user.id as usize, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
@ -92,7 +92,7 @@ where
|
||||
Flex::row()
|
||||
.with_children(buttons.into_iter().enumerate().map(
|
||||
|(ix, (message, handler))| {
|
||||
MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
|
||||
MouseEventHandler::new::<Button, _>(ix, cx, |state, _| {
|
||||
let button = theme.button.style_for(state);
|
||||
Label::new(message, button.text.clone())
|
||||
.contained()
|
||||
|
@ -170,7 +170,7 @@ impl ProjectSharedNotification {
|
||||
let theme = theme::current(cx);
|
||||
Flex::column()
|
||||
.with_child(
|
||||
MouseEventHandler::<Open, Self>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
|
||||
let theme = &theme.project_shared_notification;
|
||||
Label::new("Open", theme.open_button.text.clone())
|
||||
.aligned()
|
||||
@ -182,7 +182,7 @@ impl ProjectSharedNotification {
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<Dismiss, _>(0, cx, |_, _| {
|
||||
let theme = &theme.project_shared_notification;
|
||||
Label::new("Dismiss", theme.dismiss_button.text.clone())
|
||||
.aligned()
|
||||
|
@ -47,7 +47,7 @@ impl View for SharingStatusIndicator {
|
||||
Appearance::Dark | Appearance::VibrantDark => Color::white(),
|
||||
};
|
||||
|
||||
MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<Self, _>(0, cx, |_, _| {
|
||||
Svg::new("icons/disable_screen_sharing_12.svg")
|
||||
.with_color(color)
|
||||
.constrained()
|
||||
|
@ -439,14 +439,14 @@ impl ContextMenu {
|
||||
|
||||
let style = theme::current(cx).context_menu.clone();
|
||||
|
||||
MouseEventHandler::<Menu, ContextMenu>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::new::<Menu, _>(0, cx, |_, cx| {
|
||||
Flex::column()
|
||||
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
||||
match item {
|
||||
ContextMenuItem::Item { label, action } => {
|
||||
let action = action.clone();
|
||||
let view_id = self.parent_view_id;
|
||||
MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
|
||||
MouseEventHandler::new::<MenuItem, _>(ix, cx, |state, _| {
|
||||
let style = style.item.in_state(self.selected_index == Some(ix));
|
||||
let style = style.style_for(state);
|
||||
let keystroke = match &action {
|
||||
|
@ -113,7 +113,7 @@ impl CopilotCodeVerification {
|
||||
|
||||
let device_code_style = &style.auth.prompting.device_code;
|
||||
|
||||
MouseEventHandler::<Self, _>::new(0, cx, |state, _cx| {
|
||||
MouseEventHandler::new::<Self, _>(0, cx, |state, _cx| {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Label::new(data.user_code.clone(), device_code_style.text.clone())
|
||||
|
@ -62,7 +62,7 @@ impl View for CopilotButton {
|
||||
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self, _>::new(0, cx, {
|
||||
MouseEventHandler::new::<Self, _>(0, cx, {
|
||||
let theme = theme.clone();
|
||||
let status = status.clone();
|
||||
move |state, _cx| {
|
||||
|
@ -94,7 +94,7 @@ impl View for DiagnosticIndicator {
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
let in_progress = !self.in_progress_checks.is_empty();
|
||||
let mut element = Flex::row().with_child(
|
||||
MouseEventHandler::<Summary, _>::new(0, cx, |state, cx| {
|
||||
MouseEventHandler::new::<Summary, _>(0, cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme
|
||||
.workspace
|
||||
@ -195,7 +195,7 @@ impl View for DiagnosticIndicator {
|
||||
} else if let Some(diagnostic) = &self.current_diagnostic {
|
||||
let message_style = style.diagnostic_message.clone();
|
||||
element.add_child(
|
||||
MouseEventHandler::<Message, _>::new(1, cx, |state, _| {
|
||||
MouseEventHandler::new::<Message, _>(1, cx, |state, _| {
|
||||
Label::new(
|
||||
diagnostic.message.split('\n').next().unwrap().to_string(),
|
||||
message_style.style_for(state).text.clone(),
|
||||
|
@ -202,7 +202,7 @@ impl<V: View> DragAndDrop<V> {
|
||||
let position = (position - region_offset).round();
|
||||
Some(
|
||||
Overlay::new(
|
||||
MouseEventHandler::<DraggedElementHandler, V>::new(
|
||||
MouseEventHandler::new::<DraggedElementHandler, _>(
|
||||
0,
|
||||
cx,
|
||||
|_, cx| render(payload, cx),
|
||||
@ -235,7 +235,7 @@ impl<V: View> DragAndDrop<V> {
|
||||
}
|
||||
|
||||
State::Canceled => Some(
|
||||
MouseEventHandler::<DraggedElementHandler, V>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<DraggedElementHandler, _>(0, cx, |_, _| {
|
||||
Empty::new().constrained().with_width(0.).with_height(0.)
|
||||
})
|
||||
.on_up(MouseButton::Left, |_, _, cx| {
|
||||
@ -301,7 +301,7 @@ pub trait Draggable<V: View> {
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<Tag, V: View> Draggable<V> for MouseEventHandler<Tag, V> {
|
||||
impl<V: View> Draggable<V> for MouseEventHandler<V> {
|
||||
fn as_draggable<D: View, P: Any>(
|
||||
self,
|
||||
payload: P,
|
||||
|
@ -353,19 +353,26 @@ impl DisplaySnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
// used by line_mode selections and tries to match vim behaviour
|
||||
pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
|
||||
let mut new_start = self.prev_line_boundary(range.start).0;
|
||||
let mut new_end = self.next_line_boundary(range.end).0;
|
||||
let new_start = if range.start.row == 0 {
|
||||
Point::new(0, 0)
|
||||
} else if range.start.row == self.max_buffer_row()
|
||||
|| (range.end.column > 0 && range.end.row == self.max_buffer_row())
|
||||
{
|
||||
Point::new(range.start.row - 1, self.line_len(range.start.row - 1))
|
||||
} else {
|
||||
self.prev_line_boundary(range.start).0
|
||||
};
|
||||
|
||||
if new_start.row == range.start.row && new_end.row == range.end.row {
|
||||
if new_end.row < self.buffer_snapshot.max_point().row {
|
||||
new_end.row += 1;
|
||||
new_end.column = 0;
|
||||
} else if new_start.row > 0 {
|
||||
new_start.row -= 1;
|
||||
new_start.column = self.buffer_snapshot.line_len(new_start.row);
|
||||
}
|
||||
}
|
||||
let new_end = if range.end.column == 0 {
|
||||
range.end
|
||||
} else if range.end.row < self.max_buffer_row() {
|
||||
self.buffer_snapshot
|
||||
.clip_point(Point::new(range.end.row + 1, 0), Bias::Left)
|
||||
} else {
|
||||
self.buffer_snapshot.max_point()
|
||||
};
|
||||
|
||||
new_start..new_end
|
||||
}
|
||||
|
@ -867,7 +867,7 @@ impl CompletionsMenu {
|
||||
let completion = &completions[mat.candidate_id];
|
||||
let item_ix = start_ix + ix;
|
||||
items.push(
|
||||
MouseEventHandler::<CompletionTag, _>::new(
|
||||
MouseEventHandler::new::<CompletionTag, _>(
|
||||
mat.candidate_id,
|
||||
cx,
|
||||
|state, _| {
|
||||
@ -1044,7 +1044,7 @@ impl CodeActionsMenu {
|
||||
for (ix, action) in actions[range].iter().enumerate() {
|
||||
let item_ix = start_ix + ix;
|
||||
items.push(
|
||||
MouseEventHandler::<ActionTag, _>::new(item_ix, cx, |state, _| {
|
||||
MouseEventHandler::new::<ActionTag, _>(item_ix, cx, |state, _| {
|
||||
let item_style = if item_ix == selected_item {
|
||||
style.autocomplete.selected_item
|
||||
} else if state.hovered() {
|
||||
@ -2723,7 +2723,7 @@ impl Editor {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn excerpt_visible_offsets(
|
||||
pub fn excerpt_visible_offsets(
|
||||
&self,
|
||||
restrict_to_languages: Option<&HashSet<Arc<Language>>>,
|
||||
cx: &mut ViewContext<'_, '_, Editor>,
|
||||
@ -3547,7 +3547,7 @@ impl Editor {
|
||||
if self.available_code_actions.is_some() {
|
||||
enum CodeActions {}
|
||||
Some(
|
||||
MouseEventHandler::<CodeActions, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<CodeActions, _>(0, cx, |state, _| {
|
||||
Svg::new("icons/bolt_8.svg").with_color(
|
||||
style
|
||||
.code_actions
|
||||
@ -3594,7 +3594,7 @@ impl Editor {
|
||||
fold_data
|
||||
.map(|(fold_status, buffer_row, active)| {
|
||||
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
|
||||
MouseEventHandler::<FoldIndicators, _>::new(
|
||||
MouseEventHandler::new::<FoldIndicators, _>(
|
||||
ix as usize,
|
||||
cx,
|
||||
|mouse_state, _| {
|
||||
@ -8663,7 +8663,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
|
||||
let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
|
||||
let anchor_x = cx.anchor_x;
|
||||
enum BlockContextToolip {}
|
||||
MouseEventHandler::<BlockContext, _>::new(cx.block_id, cx, |_, _| {
|
||||
MouseEventHandler::new::<BlockContext, _>(cx.block_id, cx, |_, _| {
|
||||
Flex::column()
|
||||
.with_children(highlighted_lines.iter().map(|(line, highlights)| {
|
||||
Label::new(
|
||||
|
@ -63,6 +63,7 @@ struct SelectionLayout {
|
||||
cursor_shape: CursorShape,
|
||||
is_newest: bool,
|
||||
range: Range<DisplayPoint>,
|
||||
active_rows: Range<u32>,
|
||||
}
|
||||
|
||||
impl SelectionLayout {
|
||||
@ -73,25 +74,44 @@ impl SelectionLayout {
|
||||
map: &DisplaySnapshot,
|
||||
is_newest: bool,
|
||||
) -> Self {
|
||||
let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
||||
let display_selection = point_selection.map(|p| p.to_display_point(map));
|
||||
let mut range = display_selection.range();
|
||||
let mut head = display_selection.head();
|
||||
let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
|
||||
..map.next_line_boundary(point_selection.end).1.row();
|
||||
|
||||
// vim visual line mode
|
||||
if line_mode {
|
||||
let selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
||||
let point_range = map.expand_to_line(selection.range());
|
||||
Self {
|
||||
head: selection.head().to_display_point(map),
|
||||
cursor_shape,
|
||||
is_newest,
|
||||
range: point_range.start.to_display_point(map)
|
||||
..point_range.end.to_display_point(map),
|
||||
}
|
||||
} else {
|
||||
let selection = selection.map(|p| p.to_display_point(map));
|
||||
Self {
|
||||
head: selection.head(),
|
||||
cursor_shape,
|
||||
is_newest,
|
||||
range: selection.range(),
|
||||
let point_range = map.expand_to_line(point_selection.range());
|
||||
range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
|
||||
}
|
||||
|
||||
// any vim visual mode (including line mode)
|
||||
if cursor_shape == CursorShape::Block && !range.is_empty() && !selection.reversed {
|
||||
if head.column() > 0 {
|
||||
head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
|
||||
} else if head.row() > 0 && head != map.max_point() {
|
||||
head = map.clip_point(
|
||||
DisplayPoint::new(head.row() - 1, map.line_len(head.row() - 1)),
|
||||
Bias::Left,
|
||||
);
|
||||
// updating range.end is a no-op unless you're cursor is
|
||||
// on the newline containing a multi-buffer divider
|
||||
// in which case the clip_point may have moved the head up
|
||||
// an additional row.
|
||||
range.end = DisplayPoint::new(head.row() + 1, 0);
|
||||
active_rows.end = head.row();
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
head,
|
||||
cursor_shape,
|
||||
is_newest,
|
||||
range,
|
||||
active_rows,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1637,7 +1657,7 @@ impl EditorElement {
|
||||
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
|
||||
|
||||
enum JumpIcon {}
|
||||
MouseEventHandler::<JumpIcon, _>::new((*id).into(), cx, |state, _| {
|
||||
MouseEventHandler::new::<JumpIcon, _>((*id).into(), cx, |state, _| {
|
||||
let style = style.jump_icon.style_for(state);
|
||||
Svg::new("icons/arrow_up_right_8.svg")
|
||||
.with_color(style.color)
|
||||
@ -2152,22 +2172,37 @@ impl Element<Editor> for EditorElement {
|
||||
}
|
||||
selections.extend(remote_selections);
|
||||
|
||||
let mut newest_selection_head = None;
|
||||
|
||||
if editor.show_local_selections {
|
||||
let mut local_selections = editor
|
||||
let mut local_selections: Vec<Selection<Point>> = editor
|
||||
.selections
|
||||
.disjoint_in_range(start_anchor..end_anchor, cx);
|
||||
local_selections.extend(editor.selections.pending(cx));
|
||||
let mut layouts = Vec::new();
|
||||
let newest = editor.selections.newest(cx);
|
||||
for selection in &local_selections {
|
||||
for selection in local_selections.drain(..) {
|
||||
let is_empty = selection.start == selection.end;
|
||||
let selection_start = snapshot.prev_line_boundary(selection.start).1;
|
||||
let selection_end = snapshot.next_line_boundary(selection.end).1;
|
||||
for row in cmp::max(selection_start.row(), start_row)
|
||||
..=cmp::min(selection_end.row(), end_row)
|
||||
let is_newest = selection == newest;
|
||||
|
||||
let layout = SelectionLayout::new(
|
||||
selection,
|
||||
editor.selections.line_mode,
|
||||
editor.cursor_shape,
|
||||
&snapshot.display_snapshot,
|
||||
is_newest,
|
||||
);
|
||||
if is_newest {
|
||||
newest_selection_head = Some(layout.head);
|
||||
}
|
||||
|
||||
for row in cmp::max(layout.active_rows.start, start_row)
|
||||
..=cmp::min(layout.active_rows.end, end_row)
|
||||
{
|
||||
let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
|
||||
*contains_non_empty_selection |= !is_empty;
|
||||
}
|
||||
layouts.push(layout);
|
||||
}
|
||||
|
||||
// Render the local selections in the leader's color when following.
|
||||
@ -2175,22 +2210,7 @@ impl Element<Editor> for EditorElement {
|
||||
.leader_replica_id
|
||||
.unwrap_or_else(|| editor.replica_id(cx));
|
||||
|
||||
selections.push((
|
||||
local_replica_id,
|
||||
local_selections
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
let is_newest = selection == newest;
|
||||
SelectionLayout::new(
|
||||
selection,
|
||||
editor.selections.line_mode,
|
||||
editor.cursor_shape,
|
||||
&snapshot.display_snapshot,
|
||||
is_newest,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
selections.push((local_replica_id, layouts));
|
||||
}
|
||||
|
||||
let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
|
||||
@ -2295,28 +2315,26 @@ impl Element<Editor> for EditorElement {
|
||||
snapshot = editor.snapshot(cx);
|
||||
}
|
||||
|
||||
let newest_selection_head = editor
|
||||
.selections
|
||||
.newest::<usize>(cx)
|
||||
.head()
|
||||
.to_display_point(&snapshot);
|
||||
let style = editor.style(cx);
|
||||
|
||||
let mut context_menu = None;
|
||||
let mut code_actions_indicator = None;
|
||||
if (start_row..end_row).contains(&newest_selection_head.row()) {
|
||||
if editor.context_menu_visible() {
|
||||
context_menu = editor.render_context_menu(newest_selection_head, style.clone(), cx);
|
||||
if let Some(newest_selection_head) = newest_selection_head {
|
||||
if (start_row..end_row).contains(&newest_selection_head.row()) {
|
||||
if editor.context_menu_visible() {
|
||||
context_menu =
|
||||
editor.render_context_menu(newest_selection_head, style.clone(), cx);
|
||||
}
|
||||
|
||||
let active = matches!(
|
||||
editor.context_menu,
|
||||
Some(crate::ContextMenu::CodeActions(_))
|
||||
);
|
||||
|
||||
code_actions_indicator = editor
|
||||
.render_code_actions_indicator(&style, active, cx)
|
||||
.map(|indicator| (newest_selection_head.row(), indicator));
|
||||
}
|
||||
|
||||
let active = matches!(
|
||||
editor.context_menu,
|
||||
Some(crate::ContextMenu::CodeActions(_))
|
||||
);
|
||||
|
||||
code_actions_indicator = editor
|
||||
.render_code_actions_indicator(&style, active, cx)
|
||||
.map(|indicator| (newest_selection_head.row(), indicator));
|
||||
}
|
||||
|
||||
let visible_rows = start_row..start_row + line_layouts.len() as u32;
|
||||
@ -2995,6 +3013,154 @@ mod tests {
|
||||
assert_eq!(layouts.len(), 6);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_vim_visual_selections(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let editor = cx
|
||||
.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||
})
|
||||
.root(cx);
|
||||
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||
let (_, state) = editor.update(cx, |editor, cx| {
|
||||
editor.cursor_shape = CursorShape::Block;
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([
|
||||
Point::new(0, 0)..Point::new(1, 0),
|
||||
Point::new(3, 2)..Point::new(3, 3),
|
||||
Point::new(5, 6)..Point::new(6, 0),
|
||||
]);
|
||||
});
|
||||
let mut new_parents = Default::default();
|
||||
let mut notify_views_if_parents_change = Default::default();
|
||||
let mut layout_cx = LayoutContext::new(
|
||||
cx,
|
||||
&mut new_parents,
|
||||
&mut notify_views_if_parents_change,
|
||||
false,
|
||||
);
|
||||
element.layout(
|
||||
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
|
||||
editor,
|
||||
&mut layout_cx,
|
||||
)
|
||||
});
|
||||
assert_eq!(state.selections.len(), 1);
|
||||
let local_selections = &state.selections[0].1;
|
||||
assert_eq!(local_selections.len(), 3);
|
||||
// moves cursor back one line
|
||||
assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
|
||||
assert_eq!(
|
||||
local_selections[0].range,
|
||||
DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
|
||||
);
|
||||
|
||||
// moves cursor back one column
|
||||
assert_eq!(
|
||||
local_selections[1].range,
|
||||
DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
|
||||
);
|
||||
assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
|
||||
|
||||
// leaves cursor on the max point
|
||||
assert_eq!(
|
||||
local_selections[2].range,
|
||||
DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
|
||||
);
|
||||
assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
|
||||
|
||||
// active lines does not include 1 (even though the range of the selection does)
|
||||
assert_eq!(
|
||||
state.active_rows.keys().cloned().collect::<Vec<u32>>(),
|
||||
vec![0, 3, 5, 6]
|
||||
);
|
||||
|
||||
// multi-buffer support
|
||||
// in DisplayPoint co-ordinates, this is what we're dealing with:
|
||||
// 0: [[file
|
||||
// 1: header]]
|
||||
// 2: aaaaaa
|
||||
// 3: bbbbbb
|
||||
// 4: cccccc
|
||||
// 5:
|
||||
// 6: ...
|
||||
// 7: ffffff
|
||||
// 8: gggggg
|
||||
// 9: hhhhhh
|
||||
// 10:
|
||||
// 11: [[file
|
||||
// 12: header]]
|
||||
// 13: bbbbbb
|
||||
// 14: cccccc
|
||||
// 15: dddddd
|
||||
let editor = cx
|
||||
.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_multi(
|
||||
[
|
||||
(
|
||||
&(sample_text(8, 6, 'a') + "\n"),
|
||||
vec![
|
||||
Point::new(0, 0)..Point::new(3, 0),
|
||||
Point::new(4, 0)..Point::new(7, 0),
|
||||
],
|
||||
),
|
||||
(
|
||||
&(sample_text(8, 6, 'a') + "\n"),
|
||||
vec![Point::new(1, 0)..Point::new(3, 0)],
|
||||
),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||
})
|
||||
.root(cx);
|
||||
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||
let (_, state) = editor.update(cx, |editor, cx| {
|
||||
editor.cursor_shape = CursorShape::Block;
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([
|
||||
DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
|
||||
DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
|
||||
]);
|
||||
});
|
||||
let mut new_parents = Default::default();
|
||||
let mut notify_views_if_parents_change = Default::default();
|
||||
let mut layout_cx = LayoutContext::new(
|
||||
cx,
|
||||
&mut new_parents,
|
||||
&mut notify_views_if_parents_change,
|
||||
false,
|
||||
);
|
||||
element.layout(
|
||||
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
|
||||
editor,
|
||||
&mut layout_cx,
|
||||
)
|
||||
});
|
||||
|
||||
assert_eq!(state.selections.len(), 1);
|
||||
let local_selections = &state.selections[0].1;
|
||||
assert_eq!(local_selections.len(), 2);
|
||||
|
||||
// moves cursor on excerpt boundary back a line
|
||||
// and doesn't allow selection to bleed through
|
||||
assert_eq!(
|
||||
local_selections[0].range,
|
||||
DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
|
||||
);
|
||||
assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
|
||||
|
||||
// moves cursor on buffer boundary back two lines
|
||||
// and doesn't allow selection to bleed through
|
||||
assert_eq!(
|
||||
local_selections[1].range,
|
||||
DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
|
||||
);
|
||||
assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
@ -565,7 +565,7 @@ impl InfoPopover {
|
||||
)
|
||||
});
|
||||
|
||||
MouseEventHandler::<InfoPopover, _>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
|
||||
let mut region_id = 0;
|
||||
let view_id = cx.view_id();
|
||||
|
||||
@ -654,7 +654,7 @@ impl DiagnosticPopover {
|
||||
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
|
||||
MouseEventHandler::<DiagnosticPopover, _>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<DiagnosticPopover, _>(0, cx, |_, _| {
|
||||
text.with_soft_wrap(true)
|
||||
.contained()
|
||||
.with_style(container_style)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,13 @@ pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||
map.clip_point(point, Bias::Left)
|
||||
}
|
||||
|
||||
pub fn saturating_left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||
if point.column() > 0 {
|
||||
*point.column_mut() -= 1;
|
||||
}
|
||||
map.clip_point(point, Bias::Left)
|
||||
}
|
||||
|
||||
pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||
let max_column = map.line_len(point.row());
|
||||
if point.column() < max_column {
|
||||
@ -24,6 +31,11 @@ pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||
map.clip_point(point, Bias::Right)
|
||||
}
|
||||
|
||||
pub fn saturating_right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||
*point.column_mut() += 1;
|
||||
map.clip_point(point, Bias::Right)
|
||||
}
|
||||
|
||||
pub fn up(
|
||||
map: &DisplaySnapshot,
|
||||
start: DisplayPoint,
|
||||
|
@ -1565,6 +1565,25 @@ impl MultiBuffer {
|
||||
cx.add_model(|cx| Self::singleton(buffer, cx))
|
||||
}
|
||||
|
||||
pub fn build_multi<const COUNT: usize>(
|
||||
excerpts: [(&str, Vec<Range<Point>>); COUNT],
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> ModelHandle<Self> {
|
||||
let multi = cx.add_model(|_| Self::new(0));
|
||||
for (text, ranges) in excerpts {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
|
||||
let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange {
|
||||
context: range,
|
||||
primary: None,
|
||||
});
|
||||
multi.update(cx, |multi, cx| {
|
||||
multi.push_excerpts(buffer, excerpt_ranges, cx)
|
||||
});
|
||||
}
|
||||
|
||||
multi
|
||||
}
|
||||
|
||||
pub fn build_from_buffer(
|
||||
buffer: ModelHandle<Buffer>,
|
||||
cx: &mut gpui::AppContext,
|
||||
|
@ -13,7 +13,7 @@ use gpui::{
|
||||
};
|
||||
use language::{Bias, Point};
|
||||
use util::ResultExt;
|
||||
use workspace::{item::Item, WorkspaceId};
|
||||
use workspace::WorkspaceId;
|
||||
|
||||
use crate::{
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
@ -333,9 +333,7 @@ impl Editor {
|
||||
cx,
|
||||
);
|
||||
|
||||
if !self.is_singleton(cx) {
|
||||
self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx);
|
||||
}
|
||||
self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx);
|
||||
}
|
||||
|
||||
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
|
||||
|
@ -35,7 +35,7 @@ impl View for DeployFeedbackButton {
|
||||
let theme = theme::current(cx).clone();
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<Self, _>(0, cx, |state, _| {
|
||||
let style = &theme
|
||||
.workspace
|
||||
.status_bar
|
||||
|
@ -41,7 +41,7 @@ impl View for FeedbackInfoText {
|
||||
.aligned(),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<OpenZedCommunityRepo, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<OpenZedCommunityRepo, _>(0, cx, |state, _| {
|
||||
let contained_text = if state.hovered() {
|
||||
&theme.feedback.link_text_hover
|
||||
} else {
|
||||
|
@ -52,7 +52,7 @@ impl View for SubmitFeedbackButton {
|
||||
.map_or(true, |i| i.read(cx).allow_submission);
|
||||
|
||||
enum SubmitFeedbackButton {}
|
||||
MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<SubmitFeedbackButton, _>(0, cx, |state, _| {
|
||||
let text;
|
||||
let style = if allow_submission {
|
||||
text = "Submit as Markdown";
|
||||
|
237
crates/gpui/examples/components.rs
Normal file
237
crates/gpui/examples/components.rs
Normal file
@ -0,0 +1,237 @@
|
||||
use button_component::Button;
|
||||
|
||||
use gpui::{
|
||||
color::Color,
|
||||
elements::{Component, ContainerStyle, Flex, Label, ParentElement},
|
||||
fonts::{self, TextStyle},
|
||||
platform::WindowOptions,
|
||||
AnyElement, App, Element, Entity, View, ViewContext,
|
||||
};
|
||||
use log::LevelFilter;
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
use simplelog::SimpleLogger;
|
||||
use theme::Toggleable;
|
||||
use toggleable_button::ToggleableButton;
|
||||
|
||||
// cargo run -p gpui --example components
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||
|
||||
App::new(()).unwrap().run(|cx| {
|
||||
cx.platform().activate(true);
|
||||
cx.add_window(WindowOptions::with_bounds(vec2f(300., 200.)), |_| {
|
||||
TestView {
|
||||
count: 0,
|
||||
is_doubling: false,
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub struct TestView {
|
||||
count: usize,
|
||||
is_doubling: bool,
|
||||
}
|
||||
|
||||
impl TestView {
|
||||
fn increase_count(&mut self) {
|
||||
if self.is_doubling {
|
||||
self.count *= 2;
|
||||
} else {
|
||||
self.count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for TestView {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
type ButtonStyle = ContainerStyle;
|
||||
|
||||
impl View for TestView {
|
||||
fn ui_name() -> &'static str {
|
||||
"TestView"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
|
||||
fonts::with_font_cache(cx.font_cache.to_owned(), || {
|
||||
Flex::column()
|
||||
.with_child(Label::new(
|
||||
format!("Count: {}", self.count),
|
||||
TextStyle::for_color(Color::red()),
|
||||
))
|
||||
.with_child(
|
||||
Button::new(move |_, v: &mut Self, cx| {
|
||||
v.increase_count();
|
||||
cx.notify();
|
||||
})
|
||||
.with_text(
|
||||
"Hello from a counting BUTTON",
|
||||
TextStyle::for_color(Color::blue()),
|
||||
)
|
||||
.with_style(ButtonStyle::fill(Color::yellow()))
|
||||
.into_element(),
|
||||
)
|
||||
.with_child(
|
||||
ToggleableButton::new(self.is_doubling, move |_, v: &mut Self, cx| {
|
||||
v.is_doubling = !v.is_doubling;
|
||||
cx.notify();
|
||||
})
|
||||
.with_text("Double the count?", TextStyle::for_color(Color::black()))
|
||||
.with_style(Toggleable {
|
||||
inactive: ButtonStyle::fill(Color::red()),
|
||||
active: ButtonStyle::fill(Color::green()),
|
||||
})
|
||||
.into_element(),
|
||||
)
|
||||
.expanded()
|
||||
.contained()
|
||||
.with_background_color(Color::white())
|
||||
.into_any()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mod theme {
|
||||
pub struct Toggleable<T> {
|
||||
pub inactive: T,
|
||||
pub active: T,
|
||||
}
|
||||
|
||||
impl<T> Toggleable<T> {
|
||||
pub fn style_for(&self, active: bool) -> &T {
|
||||
if active {
|
||||
&self.active
|
||||
} else {
|
||||
&self.inactive
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Component creation:
|
||||
mod toggleable_button {
|
||||
use gpui::{
|
||||
elements::{Component, ContainerStyle, LabelStyle},
|
||||
scene::MouseClick,
|
||||
EventContext, View,
|
||||
};
|
||||
|
||||
use crate::{button_component::Button, theme::Toggleable};
|
||||
|
||||
pub struct ToggleableButton<V: View> {
|
||||
active: bool,
|
||||
style: Option<Toggleable<ContainerStyle>>,
|
||||
button: Button<V>,
|
||||
}
|
||||
|
||||
impl<V: View> ToggleableButton<V> {
|
||||
pub fn new<F>(active: bool, on_click: F) -> Self
|
||||
where
|
||||
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
Self {
|
||||
active,
|
||||
button: Button::new(on_click),
|
||||
style: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_text(self, text: &str, style: impl Into<LabelStyle>) -> ToggleableButton<V> {
|
||||
ToggleableButton {
|
||||
active: self.active,
|
||||
style: self.style,
|
||||
button: self.button.with_text(text, style),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_style(self, style: Toggleable<ContainerStyle>) -> ToggleableButton<V> {
|
||||
ToggleableButton {
|
||||
active: self.active,
|
||||
style: Some(style),
|
||||
button: self.button,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> Component<V> for ToggleableButton<V> {
|
||||
fn render(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
|
||||
let button = if let Some(style) = self.style {
|
||||
self.button.with_style(*style.style_for(self.active))
|
||||
} else {
|
||||
self.button
|
||||
};
|
||||
button.render(v, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod button_component {
|
||||
|
||||
use gpui::{
|
||||
elements::{Component, ContainerStyle, Label, LabelStyle, MouseEventHandler},
|
||||
platform::MouseButton,
|
||||
scene::MouseClick,
|
||||
AnyElement, Element, EventContext, TypeTag, View, ViewContext,
|
||||
};
|
||||
|
||||
type ClickHandler<V> = Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>)>;
|
||||
|
||||
pub struct Button<V: View> {
|
||||
click_handler: ClickHandler<V>,
|
||||
tag: TypeTag,
|
||||
contents: Option<AnyElement<V>>,
|
||||
style: Option<ContainerStyle>,
|
||||
}
|
||||
|
||||
impl<V: View> Button<V> {
|
||||
pub fn new<F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static>(handler: F) -> Self {
|
||||
Self {
|
||||
click_handler: Box::new(handler),
|
||||
tag: TypeTag::new::<F>(),
|
||||
style: None,
|
||||
contents: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_text(mut self, text: &str, style: impl Into<LabelStyle>) -> Self {
|
||||
self.contents = Some(Label::new(text.to_string(), style).into_any());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn _with_contents<E: Element<V>>(mut self, contents: E) -> Self {
|
||||
self.contents = Some(contents.into_any());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_style(mut self, style: ContainerStyle) -> Self {
|
||||
self.style = Some(style);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> Component<V> for Button<V> {
|
||||
fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
|
||||
let click_handler = self.click_handler;
|
||||
|
||||
let result = MouseEventHandler::new_dynamic(self.tag, 0, cx, |_, _| {
|
||||
self.contents
|
||||
.unwrap_or_else(|| gpui::elements::Empty::new().into_any())
|
||||
})
|
||||
.on_click(MouseButton::Left, move |click, v, cx| {
|
||||
click_handler(click, v, cx);
|
||||
})
|
||||
.contained();
|
||||
|
||||
let result = if let Some(style) = self.style {
|
||||
result.with_style(style)
|
||||
} else {
|
||||
result
|
||||
};
|
||||
|
||||
result.into_any()
|
||||
}
|
||||
}
|
||||
}
|
@ -3280,7 +3280,11 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
|
||||
}
|
||||
|
||||
pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
|
||||
let region_id = MouseRegionId::new::<Tag>(self.view_id, region_id);
|
||||
self.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id)
|
||||
}
|
||||
|
||||
pub fn mouse_state_dynamic(&self, tag: TypeTag, region_id: usize) -> MouseState {
|
||||
let region_id = MouseRegionId::new(tag, self.view_id, region_id);
|
||||
MouseState {
|
||||
hovered: self.window.hovered_region_ids.contains(®ion_id),
|
||||
clicked: if let Some((clicked_region_id, button)) = self.window.clicked_region {
|
||||
@ -3321,6 +3325,36 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct TypeTag {
|
||||
tag: TypeId,
|
||||
#[cfg(debug_assertions)]
|
||||
tag_type_name: &'static str,
|
||||
}
|
||||
|
||||
impl TypeTag {
|
||||
pub fn new<Tag: 'static>() -> Self {
|
||||
Self {
|
||||
tag: TypeId::of::<Tag>(),
|
||||
#[cfg(debug_assertions)]
|
||||
tag_type_name: std::any::type_name::<Tag>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self {
|
||||
Self {
|
||||
tag,
|
||||
#[cfg(debug_assertions)]
|
||||
tag_type_name: type_name,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) fn type_name(&self) -> &'static str {
|
||||
self.tag_type_name
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
|
||||
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
|
||||
BorrowAppContext::read_with(&*self.window_context, f)
|
||||
@ -5171,7 +5205,7 @@ mod tests {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
enum Handler {}
|
||||
let mouse_down_count = self.mouse_down_count.clone();
|
||||
MouseEventHandler::<Handler, _>::new(0, cx, |_, _| Empty::new())
|
||||
MouseEventHandler::new::<Handler, _>(0, cx, |_, _| Empty::new())
|
||||
.on_down(MouseButton::Left, move |_, _, _| {
|
||||
mouse_down_count.fetch_add(1, SeqCst);
|
||||
})
|
||||
|
@ -1,6 +1,7 @@
|
||||
mod align;
|
||||
mod canvas;
|
||||
mod clipped;
|
||||
mod component;
|
||||
mod constrained_box;
|
||||
mod container;
|
||||
mod empty;
|
||||
@ -21,9 +22,9 @@ mod tooltip;
|
||||
mod uniform_list;
|
||||
|
||||
pub use self::{
|
||||
align::*, canvas::*, constrained_box::*, container::*, empty::*, flex::*, hook::*, image::*,
|
||||
keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*, resizable::*,
|
||||
stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
|
||||
align::*, canvas::*, component::*, constrained_box::*, container::*, empty::*, flex::*,
|
||||
hook::*, image::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*,
|
||||
resizable::*, stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
|
||||
};
|
||||
pub use crate::window::ChildView;
|
||||
|
||||
@ -193,11 +194,11 @@ pub trait Element<V: View>: 'static {
|
||||
Resizable::new(self.into_any(), side, size, on_resize)
|
||||
}
|
||||
|
||||
fn mouse<Tag>(self, region_id: usize) -> MouseEventHandler<Tag, V>
|
||||
fn mouse<Tag: 'static>(self, region_id: usize) -> MouseEventHandler<V>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
MouseEventHandler::for_child(self.into_any(), region_id)
|
||||
MouseEventHandler::for_child::<Tag>(self.into_any(), region_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
87
crates/gpui/src/elements/component.rs
Normal file
87
crates/gpui/src/elements/component.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
||||
|
||||
use crate::{
|
||||
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
};
|
||||
|
||||
pub trait Component<V: View> {
|
||||
fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
|
||||
|
||||
fn into_element(self) -> ComponentAdapter<V, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ComponentAdapter::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ComponentAdapter<V, E> {
|
||||
component: Option<E>,
|
||||
phantom: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<E, V> ComponentAdapter<V, E> {
|
||||
pub fn new(e: E) -> Self {
|
||||
Self {
|
||||
component: Some(e),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
|
||||
type LayoutState = AnyElement<V>;
|
||||
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
view: &mut V,
|
||||
cx: &mut LayoutContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let component = self.component.take().unwrap();
|
||||
let mut element = component.render(view, cx.view_context());
|
||||
let constraint = element.layout(constraint, view, cx);
|
||||
(constraint, element)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
layout.paint(scene, bounds.origin(), visible_bounds, view, cx)
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
&self,
|
||||
range_utf16: std::ops::Range<usize>,
|
||||
_: RectF,
|
||||
_: RectF,
|
||||
element: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
element.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
&self,
|
||||
_: RectF,
|
||||
element: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
element.debug(view, cx)
|
||||
}
|
||||
}
|
@ -38,6 +38,15 @@ pub struct ContainerStyle {
|
||||
pub cursor: Option<CursorStyle>,
|
||||
}
|
||||
|
||||
impl ContainerStyle {
|
||||
pub fn fill(color: Color) -> Self {
|
||||
Self {
|
||||
background_color: Some(color),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Container<V: View> {
|
||||
child: AnyElement<V>,
|
||||
style: ContainerStyle,
|
||||
|
@ -11,12 +11,12 @@ use crate::{
|
||||
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
|
||||
},
|
||||
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
|
||||
SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
SceneBuilder, SizeConstraint, TypeTag, View, ViewContext,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::{marker::PhantomData, ops::Range};
|
||||
use std::ops::Range;
|
||||
|
||||
pub struct MouseEventHandler<Tag: 'static, V: View> {
|
||||
pub struct MouseEventHandler<V: View> {
|
||||
child: AnyElement<V>,
|
||||
region_id: usize,
|
||||
cursor_style: Option<CursorStyle>,
|
||||
@ -26,13 +26,13 @@ pub struct MouseEventHandler<Tag: 'static, V: View> {
|
||||
notify_on_click: bool,
|
||||
above: bool,
|
||||
padding: Padding,
|
||||
_tag: PhantomData<Tag>,
|
||||
tag: TypeTag,
|
||||
}
|
||||
|
||||
/// Element which provides a render_child callback with a MouseState and paints a mouse
|
||||
/// region under (or above) it for easy mouse event handling.
|
||||
impl<Tag, V: View> MouseEventHandler<Tag, V> {
|
||||
pub fn for_child(child: impl Element<V>, region_id: usize) -> Self {
|
||||
impl<V: View> MouseEventHandler<V> {
|
||||
pub fn for_child<Tag: 'static>(child: impl Element<V>, region_id: usize) -> Self {
|
||||
Self {
|
||||
child: child.into_any(),
|
||||
region_id,
|
||||
@ -43,16 +43,19 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
|
||||
hoverable: false,
|
||||
above: false,
|
||||
padding: Default::default(),
|
||||
_tag: PhantomData,
|
||||
tag: TypeTag::new::<Tag>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new<E, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
|
||||
pub fn new<Tag: 'static, E>(
|
||||
region_id: usize,
|
||||
cx: &mut ViewContext<V>,
|
||||
render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
|
||||
) -> Self
|
||||
where
|
||||
E: Element<V>,
|
||||
F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
|
||||
{
|
||||
let mut mouse_state = cx.mouse_state::<Tag>(region_id);
|
||||
let mut mouse_state = cx.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id);
|
||||
let child = render_child(&mut mouse_state, cx).into_any();
|
||||
let notify_on_hover = mouse_state.accessed_hovered();
|
||||
let notify_on_click = mouse_state.accessed_clicked();
|
||||
@ -66,19 +69,46 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
|
||||
hoverable: true,
|
||||
above: false,
|
||||
padding: Default::default(),
|
||||
_tag: PhantomData,
|
||||
tag: TypeTag::new::<Tag>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dynamic(
|
||||
tag: TypeTag,
|
||||
region_id: usize,
|
||||
cx: &mut ViewContext<V>,
|
||||
render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> AnyElement<V>,
|
||||
) -> Self {
|
||||
let mut mouse_state = cx.mouse_state_dynamic(tag, region_id);
|
||||
let child = render_child(&mut mouse_state, cx);
|
||||
let notify_on_hover = mouse_state.accessed_hovered();
|
||||
let notify_on_click = mouse_state.accessed_clicked();
|
||||
Self {
|
||||
child,
|
||||
region_id,
|
||||
cursor_style: None,
|
||||
handlers: Default::default(),
|
||||
notify_on_hover,
|
||||
notify_on_click,
|
||||
hoverable: true,
|
||||
above: false,
|
||||
padding: Default::default(),
|
||||
tag,
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
|
||||
/// for drag and drop handling and similar events which should be captured before the child
|
||||
/// gets the opportunity
|
||||
pub fn above<D, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
|
||||
pub fn above<Tag: 'static, D>(
|
||||
region_id: usize,
|
||||
cx: &mut ViewContext<V>,
|
||||
render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
|
||||
) -> Self
|
||||
where
|
||||
D: Element<V>,
|
||||
F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
|
||||
{
|
||||
let mut handler = Self::new(region_id, cx, render_child);
|
||||
let mut handler = Self::new::<Tag, _>(region_id, cx, render_child);
|
||||
handler.above = true;
|
||||
handler
|
||||
}
|
||||
@ -223,7 +253,8 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
|
||||
});
|
||||
}
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::from_handlers::<Tag>(
|
||||
MouseRegion::from_handlers(
|
||||
self.tag,
|
||||
cx.view_id(),
|
||||
self.region_id,
|
||||
hit_bounds,
|
||||
@ -236,7 +267,7 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
|
||||
impl<V: View> Element<V> for MouseEventHandler<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
|
@ -95,7 +95,7 @@ impl<V: View> Tooltip<V> {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let child = MouseEventHandler::<MouseEventHandlerState<Tag>, _>::new(id, cx, |_, _| child)
|
||||
let child = MouseEventHandler::new::<MouseEventHandlerState<Tag>, _>(id, cx, |_, _| child)
|
||||
.on_hover(move |e, _, cx| {
|
||||
let position = e.position;
|
||||
if e.started {
|
||||
|
@ -72,6 +72,13 @@ pub struct TextStyle {
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn for_color(color: Color) -> Self {
|
||||
Self {
|
||||
color,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
|
||||
TextStyle {
|
||||
color: refinement.color.unwrap_or(self.color),
|
||||
|
@ -24,6 +24,7 @@ use crate::{
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use async_task::Runnable;
|
||||
pub use event::*;
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
use postage::oneshot;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
@ -180,6 +181,16 @@ pub struct WindowOptions<'a> {
|
||||
pub screen: Option<Rc<dyn Screen>>,
|
||||
}
|
||||
|
||||
impl<'a> WindowOptions<'a> {
|
||||
pub fn with_bounds(bounds: Vector2F) -> Self {
|
||||
Self {
|
||||
bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), bounds)),
|
||||
center: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TitlebarOptions<'a> {
|
||||
pub title: Option<&'a str>,
|
||||
|
@ -1,13 +1,10 @@
|
||||
use crate::{platform::MouseButton, window::WindowContext, EventContext, View, ViewContext};
|
||||
use crate::{
|
||||
platform::MouseButton, window::WindowContext, EventContext, TypeTag, View, ViewContext,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
fmt::Debug,
|
||||
mem::Discriminant,
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{any::Any, fmt::Debug, mem::Discriminant, rc::Rc};
|
||||
|
||||
use super::{
|
||||
mouse_event::{
|
||||
@ -33,14 +30,27 @@ impl MouseRegion {
|
||||
/// should pass a different (consistent) region_id. If you have one big region that covers your
|
||||
/// whole component, just pass the view_id again.
|
||||
pub fn new<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
|
||||
Self::from_handlers::<Tag>(view_id, region_id, bounds, Default::default())
|
||||
Self::from_handlers(
|
||||
TypeTag::new::<Tag>(),
|
||||
view_id,
|
||||
region_id,
|
||||
bounds,
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn handle_all<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
|
||||
Self::from_handlers::<Tag>(view_id, region_id, bounds, HandlerSet::capture_all())
|
||||
Self::from_handlers(
|
||||
TypeTag::new::<Tag>(),
|
||||
view_id,
|
||||
region_id,
|
||||
bounds,
|
||||
HandlerSet::capture_all(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_handlers<Tag: 'static>(
|
||||
pub fn from_handlers(
|
||||
tag: TypeTag,
|
||||
view_id: usize,
|
||||
region_id: usize,
|
||||
bounds: RectF,
|
||||
@ -49,10 +59,8 @@ impl MouseRegion {
|
||||
Self {
|
||||
id: MouseRegionId {
|
||||
view_id,
|
||||
tag: TypeId::of::<Tag>(),
|
||||
tag,
|
||||
region_id,
|
||||
#[cfg(debug_assertions)]
|
||||
tag_type_name: std::any::type_name::<Tag>(),
|
||||
},
|
||||
bounds,
|
||||
handlers,
|
||||
@ -180,20 +188,16 @@ impl MouseRegion {
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub struct MouseRegionId {
|
||||
view_id: usize,
|
||||
tag: TypeId,
|
||||
tag: TypeTag,
|
||||
region_id: usize,
|
||||
#[cfg(debug_assertions)]
|
||||
tag_type_name: &'static str,
|
||||
}
|
||||
|
||||
impl MouseRegionId {
|
||||
pub(crate) fn new<Tag: 'static>(view_id: usize, region_id: usize) -> Self {
|
||||
pub(crate) fn new(tag: TypeTag, view_id: usize, region_id: usize) -> Self {
|
||||
MouseRegionId {
|
||||
view_id,
|
||||
region_id,
|
||||
tag: TypeId::of::<Tag>(),
|
||||
#[cfg(debug_assertions)]
|
||||
tag_type_name: std::any::type_name::<Tag>(),
|
||||
tag,
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,7 +207,7 @@ impl MouseRegionId {
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn tag_type_name(&self) -> &'static str {
|
||||
self.tag_type_name
|
||||
self.tag.type_name()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ impl View for Select {
|
||||
Default::default()
|
||||
};
|
||||
let mut result = Flex::column().with_child(
|
||||
MouseEventHandler::<Header, _>::new(self.handle.id(), cx, |mouse_state, cx| {
|
||||
MouseEventHandler::new::<Header, _>(self.handle.id(), cx, |mouse_state, cx| {
|
||||
(self.render_item)(
|
||||
self.selected_item_ix,
|
||||
ItemType::Header,
|
||||
@ -130,7 +130,7 @@ impl View for Select {
|
||||
let selected_item_ix = this.selected_item_ix;
|
||||
range.end = range.end.min(this.item_count);
|
||||
items.extend(range.map(|ix| {
|
||||
MouseEventHandler::<Item, _>::new(ix, cx, |mouse_state, cx| {
|
||||
MouseEventHandler::new::<Item, _>(ix, cx, |mouse_state, cx| {
|
||||
(this.render_item)(
|
||||
ix,
|
||||
if ix == selected_item_ix {
|
||||
|
@ -13,6 +13,3 @@ doctest = false
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
|
@ -53,7 +53,7 @@ impl View for ActiveBufferLanguage {
|
||||
"Unknown".to_string()
|
||||
};
|
||||
|
||||
MouseEventHandler::<Self, Self>::new(0, cx, |state, cx| {
|
||||
MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
|
||||
let theme = &theme::current(cx).workspace.status_bar;
|
||||
let style = theme.active_language.style_for(state);
|
||||
Label::new(active_language_text, style.text.clone())
|
||||
|
@ -573,7 +573,7 @@ impl View for LspLogToolbarItemView {
|
||||
.with_children(if self.menu_open {
|
||||
Some(
|
||||
Overlay::new(
|
||||
MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
|
||||
MouseEventHandler::new::<Menu, _>(0, cx, move |_, cx| {
|
||||
Flex::column()
|
||||
.with_children(menu_rows.into_iter().map(|row| {
|
||||
Self::render_language_server_menu_item(
|
||||
@ -672,7 +672,7 @@ impl LspLogToolbarItemView {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl Element<Self> {
|
||||
enum ToggleMenu {}
|
||||
MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, cx| {
|
||||
MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, cx| {
|
||||
let label: Cow<str> = current_server
|
||||
.and_then(|row| {
|
||||
let worktree = row.worktree.read(cx);
|
||||
@ -728,7 +728,7 @@ impl LspLogToolbarItemView {
|
||||
.with_height(theme.toolbar_dropdown_menu.row_height)
|
||||
})
|
||||
.with_child(
|
||||
MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, _| {
|
||||
MouseEventHandler::new::<ActivateLog, _>(id.0, cx, move |state, _| {
|
||||
let style = theme
|
||||
.toolbar_dropdown_menu
|
||||
.item
|
||||
@ -746,7 +746,7 @@ impl LspLogToolbarItemView {
|
||||
}),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<ActivateRpcTrace, _>::new(id.0, cx, move |state, cx| {
|
||||
MouseEventHandler::new::<ActivateRpcTrace, _>(id.0, cx, move |state, cx| {
|
||||
let style = theme
|
||||
.toolbar_dropdown_menu
|
||||
.item
|
||||
|
@ -389,7 +389,7 @@ impl View for SyntaxTreeView {
|
||||
{
|
||||
let layer = layer.clone();
|
||||
let theme = editor_theme.clone();
|
||||
return MouseEventHandler::<Self, Self>::new(0, cx, move |state, cx| {
|
||||
return MouseEventHandler::new::<Self, _>(0, cx, move |state, cx| {
|
||||
let list_hovered = state.hovered();
|
||||
UniformList::new(
|
||||
self.list_state.clone(),
|
||||
@ -505,7 +505,7 @@ impl SyntaxTreeToolbarItemView {
|
||||
.with_child(Self::render_header(&theme, &active_layer, cx))
|
||||
.with_children(self.menu_open.then(|| {
|
||||
Overlay::new(
|
||||
MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
|
||||
MouseEventHandler::new::<Menu, _>(0, cx, move |_, cx| {
|
||||
Flex::column()
|
||||
.with_children(active_buffer.syntax_layers().enumerate().map(
|
||||
|(ix, layer)| {
|
||||
@ -564,7 +564,7 @@ impl SyntaxTreeToolbarItemView {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl Element<Self> {
|
||||
enum ToggleMenu {}
|
||||
MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, _| {
|
||||
MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, _| {
|
||||
let style = theme.toolbar_dropdown_menu.header.style_for(state);
|
||||
Flex::row()
|
||||
.with_child(
|
||||
@ -596,7 +596,7 @@ impl SyntaxTreeToolbarItemView {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl Element<Self> {
|
||||
enum ActivateLayer {}
|
||||
MouseEventHandler::<ActivateLayer, _>::new(layer_ix, cx, move |state, _| {
|
||||
MouseEventHandler::new::<ActivateLayer, _>(layer_ix, cx, move |state, _| {
|
||||
let is_selected = layer.node() == active_layer.node();
|
||||
let style = theme
|
||||
.toolbar_dropdown_menu
|
||||
|
@ -434,7 +434,9 @@ impl LanguageServer {
|
||||
..Default::default()
|
||||
}),
|
||||
inlay_hint: Some(InlayHintClientCapabilities {
|
||||
resolve_support: None,
|
||||
resolve_support: Some(InlayHintResolveClientCapabilities {
|
||||
properties: vec!["textEdits".to_string(), "tooltip".to_string()],
|
||||
}),
|
||||
dynamic_registration: Some(false),
|
||||
}),
|
||||
..Default::default()
|
||||
|
@ -112,7 +112,7 @@ impl<D: PickerDelegate> View for Picker<D> {
|
||||
let selected_ix = this.delegate.selected_index();
|
||||
range.end = cmp::min(range.end, this.delegate.match_count());
|
||||
items.extend(range.map(move |ix| {
|
||||
MouseEventHandler::<D, _>::new(ix, cx, |state, cx| {
|
||||
MouseEventHandler::new::<D, _>(ix, cx, |state, cx| {
|
||||
this.delegate.render_match(ix, state, ix == selected_ix, cx)
|
||||
})
|
||||
// Capture mouse events
|
||||
|
@ -1954,7 +1954,7 @@ impl LspCommand for InlayHints {
|
||||
_: &mut Project,
|
||||
_: PeerId,
|
||||
buffer_version: &clock::Global,
|
||||
cx: &mut AppContext,
|
||||
_: &mut AppContext,
|
||||
) -> proto::InlayHintsResponse {
|
||||
proto::InlayHintsResponse {
|
||||
hints: response
|
||||
@ -1963,51 +1963,17 @@ impl LspCommand for InlayHints {
|
||||
position: Some(language::proto::serialize_anchor(&response_hint.position)),
|
||||
padding_left: response_hint.padding_left,
|
||||
padding_right: response_hint.padding_right,
|
||||
label: Some(proto::InlayHintLabel {
|
||||
label: Some(match response_hint.label {
|
||||
InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s),
|
||||
InlayHintLabel::LabelParts(label_parts) => {
|
||||
proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts {
|
||||
parts: label_parts.into_iter().map(|label_part| proto::InlayHintLabelPart {
|
||||
value: label_part.value,
|
||||
tooltip: label_part.tooltip.map(|tooltip| {
|
||||
let proto_tooltip = match tooltip {
|
||||
InlayHintLabelPartTooltip::String(s) => proto::inlay_hint_label_part_tooltip::Content::Value(s),
|
||||
InlayHintLabelPartTooltip::MarkupContent(markup_content) => proto::inlay_hint_label_part_tooltip::Content::MarkupContent(proto::MarkupContent {
|
||||
kind: markup_content.kind,
|
||||
value: markup_content.value,
|
||||
}),
|
||||
};
|
||||
proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)}
|
||||
}),
|
||||
location: label_part.location.map(|location| proto::Location {
|
||||
start: Some(serialize_anchor(&location.range.start)),
|
||||
end: Some(serialize_anchor(&location.range.end)),
|
||||
buffer_id: location.buffer.read(cx).remote_id(),
|
||||
}),
|
||||
}).collect()
|
||||
})
|
||||
}
|
||||
}),
|
||||
}),
|
||||
kind: response_hint.kind.map(|kind| kind.name().to_string()),
|
||||
tooltip: response_hint.tooltip.map(|response_tooltip| {
|
||||
let proto_tooltip = match response_tooltip {
|
||||
InlayHintTooltip::String(s) => {
|
||||
proto::inlay_hint_tooltip::Content::Value(s)
|
||||
}
|
||||
InlayHintTooltip::MarkupContent(markup_content) => {
|
||||
proto::inlay_hint_tooltip::Content::MarkupContent(
|
||||
proto::MarkupContent {
|
||||
kind: markup_content.kind,
|
||||
value: markup_content.value,
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
proto::InlayHintTooltip {
|
||||
content: Some(proto_tooltip),
|
||||
}
|
||||
// Do not pass extra data such as tooltips to clients: host can put tooltip data from the cache during resolution.
|
||||
tooltip: None,
|
||||
// Similarly, do not pass label parts to clients: host can return a detailed list during resolution.
|
||||
label: Some(proto::InlayHintLabel {
|
||||
label: Some(proto::inlay_hint_label::Label::Value(
|
||||
match response_hint.label {
|
||||
InlayHintLabel::String(s) => s,
|
||||
InlayHintLabel::LabelParts(_) => response_hint.text(),
|
||||
},
|
||||
)),
|
||||
}),
|
||||
})
|
||||
.collect(),
|
||||
|
@ -1407,7 +1407,7 @@ impl ProjectPanel {
|
||||
|
||||
let show_editor = details.is_editing && !details.is_processing;
|
||||
|
||||
MouseEventHandler::<Self, _>::new(entry_id.to_usize(), cx, |state, cx| {
|
||||
MouseEventHandler::new::<Self, _>(entry_id.to_usize(), cx, |state, cx| {
|
||||
let mut style = entry_style
|
||||
.in_state(details.is_selected)
|
||||
.style_for(state)
|
||||
@ -1519,7 +1519,7 @@ impl View for ProjectPanel {
|
||||
if has_worktree {
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ProjectPanel, _>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::new::<ProjectPanel, _>(0, cx, |_, cx| {
|
||||
UniformList::new(
|
||||
self.list.clone(),
|
||||
self.visible_entries
|
||||
@ -1563,7 +1563,7 @@ impl View for ProjectPanel {
|
||||
} else {
|
||||
Flex::column()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self, _>::new(2, cx, {
|
||||
MouseEventHandler::new::<Self, _>(2, cx, {
|
||||
let button_style = theme.open_project_button.clone();
|
||||
let context_menu_item_style = theme::current(cx).context_menu.item.clone();
|
||||
move |state, cx| {
|
||||
|
@ -494,7 +494,7 @@ impl BufferSearchBar {
|
||||
CursorStyle::default()
|
||||
};
|
||||
enum ActionButton {}
|
||||
MouseEventHandler::<ActionButton, _>::new(action_type_id, cx, |state, cx| {
|
||||
MouseEventHandler::new::<ActionButton, _>(action_type_id, cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme
|
||||
.search
|
||||
@ -519,6 +519,7 @@ impl BufferSearchBar {
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
||||
assert_ne!(
|
||||
mode,
|
||||
|
@ -406,7 +406,7 @@ impl View for ProjectSearchView {
|
||||
editor.set_placeholder_text(new_placeholder_text, cx);
|
||||
});
|
||||
|
||||
MouseEventHandler::<Status, _>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<Status, _>(0, cx, |_, _| {
|
||||
Flex::column()
|
||||
.with_child(Flex::column().contained().flex(1., true))
|
||||
.with_child(
|
||||
|
@ -23,7 +23,7 @@ pub(super) fn render_close_button<V: View>(
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
|
||||
enum CloseButton {}
|
||||
MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<CloseButton, _>(0, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
@ -68,7 +68,7 @@ pub(super) fn render_nav_button<V: View>(
|
||||
CursorStyle::default()
|
||||
};
|
||||
enum NavButton {}
|
||||
MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
|
||||
MouseEventHandler::new::<NavButton, _>(direction as usize, cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme
|
||||
.search
|
||||
@ -117,7 +117,7 @@ pub(crate) fn render_search_mode_button<V: View>(
|
||||
) -> AnyElement<V> {
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
enum SearchModeButton {}
|
||||
MouseEventHandler::<SearchModeButton, _>::new(mode.region_id(), cx, |state, cx| {
|
||||
MouseEventHandler::new::<SearchModeButton, _>(mode.region_id(), cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let mut style = theme
|
||||
.search
|
||||
@ -177,7 +177,7 @@ pub(crate) fn render_option_button_icon<V: View>(
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> AnyElement<V> {
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
MouseEventHandler::<V, _>::new(id, cx, |state, cx| {
|
||||
MouseEventHandler::new::<V, _>(id, cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme
|
||||
.search
|
||||
|
@ -987,6 +987,14 @@ impl Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_all(&mut self) {
|
||||
let term = self.term.lock();
|
||||
let start = Point::new(term.topmost_line(), Column(0));
|
||||
let end = Point::new(term.bottommost_line(), term.last_column());
|
||||
drop(term);
|
||||
self.set_selection(Some((make_selection(&(start..=end)), end)));
|
||||
}
|
||||
|
||||
fn set_selection(&mut self, selection: Option<(Selection, Point)>) {
|
||||
self.events
|
||||
.push_back(InternalEvent::SetSelection(selection));
|
||||
|
@ -80,6 +80,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(TerminalView::paste);
|
||||
cx.add_action(TerminalView::clear);
|
||||
cx.add_action(TerminalView::show_character_palette);
|
||||
cx.add_action(TerminalView::select_all)
|
||||
}
|
||||
|
||||
///A terminal view, maintains the PTY's file handles and communicates with the terminal
|
||||
@ -312,6 +313,11 @@ impl TerminalView {
|
||||
}
|
||||
}
|
||||
|
||||
fn select_all(&mut self, _: &editor::SelectAll, cx: &mut ViewContext<Self>) {
|
||||
self.terminal.update(cx, |term, _| term.select_all());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn clear(&mut self, _: &Clear, cx: &mut ViewContext<Self>) {
|
||||
self.terminal.update(cx, |term, _| term.clear());
|
||||
cx.notify();
|
||||
|
@ -34,7 +34,7 @@ pub fn checkbox<Tag, V, F>(
|
||||
id: usize,
|
||||
cx: &mut ViewContext<V>,
|
||||
change: F,
|
||||
) -> MouseEventHandler<Tag, V>
|
||||
) -> MouseEventHandler<V>
|
||||
where
|
||||
Tag: 'static,
|
||||
V: View,
|
||||
@ -43,7 +43,7 @@ where
|
||||
let label = Label::new(label, style.label.text.clone())
|
||||
.contained()
|
||||
.with_style(style.label.container);
|
||||
checkbox_with_label(label, style, checked, id, cx, change)
|
||||
checkbox_with_label::<Tag, _, _, _>(label, style, checked, id, cx, change)
|
||||
}
|
||||
|
||||
pub fn checkbox_with_label<Tag, D, V, F>(
|
||||
@ -53,14 +53,14 @@ pub fn checkbox_with_label<Tag, D, V, F>(
|
||||
id: usize,
|
||||
cx: &mut ViewContext<V>,
|
||||
change: F,
|
||||
) -> MouseEventHandler<Tag, V>
|
||||
) -> MouseEventHandler<V>
|
||||
where
|
||||
Tag: 'static,
|
||||
D: Element<V>,
|
||||
V: View,
|
||||
F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
|
||||
{
|
||||
MouseEventHandler::new(id, cx, |state, _| {
|
||||
MouseEventHandler::new::<Tag, _>(id, cx, |state, _| {
|
||||
let indicator = if checked {
|
||||
svg(&style.icon)
|
||||
} else {
|
||||
@ -143,14 +143,14 @@ pub fn cta_button<Tag, L, V, F>(
|
||||
style: &ButtonStyle,
|
||||
cx: &mut ViewContext<V>,
|
||||
f: F,
|
||||
) -> MouseEventHandler<Tag, V>
|
||||
) -> MouseEventHandler<V>
|
||||
where
|
||||
Tag: 'static,
|
||||
L: Into<Cow<'static, str>>,
|
||||
V: View,
|
||||
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<Tag, _>(0, cx, |state, _| {
|
||||
let style = style.style_for(state);
|
||||
Label::new(label, style.text.to_owned())
|
||||
.aligned()
|
||||
@ -205,7 +205,7 @@ where
|
||||
))
|
||||
.with_child(
|
||||
// FIXME: Get a better tag type
|
||||
MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
|
||||
MouseEventHandler::new::<Tag, _>(999999, cx, |state, _cx| {
|
||||
let style = style.close_icon.style_for(state);
|
||||
icon(style)
|
||||
})
|
||||
|
@ -295,7 +295,7 @@ impl PickerDelegate for BranchListDelegate {
|
||||
let style = theme.picker.footer.clone();
|
||||
enum BranchCreateButton {}
|
||||
Some(
|
||||
Flex::row().with_child(MouseEventHandler::<BranchCreateButton, _>::new(0, cx, |state, _| {
|
||||
Flex::row().with_child(MouseEventHandler::new::<BranchCreateButton, _>(0, cx, |state, _| {
|
||||
let style = style.style_for(state);
|
||||
Label::new("Create branch", style.label.clone())
|
||||
.contained()
|
||||
|
@ -87,7 +87,7 @@ impl View for ModeIndicator {
|
||||
Mode::Normal => "-- NORMAL --",
|
||||
Mode::Insert => "-- INSERT --",
|
||||
Mode::Visual { line: false } => "-- VISUAL --",
|
||||
Mode::Visual { line: true } => "VISUAL LINE ",
|
||||
Mode::Visual { line: true } => "VISUAL LINE",
|
||||
};
|
||||
Label::new(text, theme.vim_mode_indicator.text.clone())
|
||||
.contained()
|
||||
|
@ -383,8 +383,7 @@ impl Motion {
|
||||
|
||||
fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||
for _ in 0..times {
|
||||
*point.column_mut() = point.column().saturating_sub(1);
|
||||
point = map.clip_point(point, Bias::Left);
|
||||
point = movement::saturating_left(map, point);
|
||||
if point.column() == 0 {
|
||||
break;
|
||||
}
|
||||
@ -425,9 +424,7 @@ fn up(
|
||||
|
||||
pub(crate) fn right(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||
for _ in 0..times {
|
||||
let mut new_point = point;
|
||||
*new_point.column_mut() += 1;
|
||||
let new_point = map.clip_point(new_point, Bias::Right);
|
||||
let new_point = movement::saturating_right(map, point);
|
||||
if point == new_point {
|
||||
break;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ mod change;
|
||||
mod delete;
|
||||
mod scroll;
|
||||
mod search;
|
||||
mod substitute;
|
||||
pub mod substitute;
|
||||
mod yank;
|
||||
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
@ -1,34 +1,45 @@
|
||||
use gpui::WindowContext;
|
||||
use language::Point;
|
||||
|
||||
use crate::{motion::Motion, Mode, Vim};
|
||||
use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
|
||||
|
||||
pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
|
||||
let line_mode = vim.state.mode == Mode::Visual { line: true };
|
||||
vim.switch_mode(Mode::Insert, true, cx);
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.transact(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if selection.start == selection.end {
|
||||
Motion::Right.expand_selection(map, selection, count, true);
|
||||
}
|
||||
if line_mode {
|
||||
Motion::CurrentLine.expand_selection(map, selection, None, false);
|
||||
if let Some((point, _)) = Motion::FirstNonWhitespace.move_point(
|
||||
map,
|
||||
selection.start,
|
||||
selection.goal,
|
||||
None,
|
||||
) {
|
||||
selection.start = point;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
let selections = editor.selections.all::<Point>(cx);
|
||||
for selection in selections.into_iter().rev() {
|
||||
editor.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(selection.start..selection.end, "")], None, cx)
|
||||
})
|
||||
}
|
||||
copy_selections_content(editor, line_mode, cx);
|
||||
let selections = editor.selections.all::<Point>(cx).into_iter();
|
||||
let edits = selections.map(|selection| (selection.start..selection.end, ""));
|
||||
editor.edit(edits, cx);
|
||||
});
|
||||
editor.set_clip_at_line_ends(true, cx);
|
||||
});
|
||||
vim.switch_mode(Mode::Insert, true, cx)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{state::Mode, test::VimTestContext};
|
||||
use crate::{
|
||||
state::Mode,
|
||||
test::{NeovimBackedTestContext, VimTestContext},
|
||||
};
|
||||
use indoc::indoc;
|
||||
|
||||
#[gpui::test]
|
||||
@ -69,5 +80,86 @@ mod test {
|
||||
// should transactionally undo selection changes
|
||||
cx.simulate_keystrokes(["escape", "u"]);
|
||||
cx.assert_editor_state("ˇcàfé\n");
|
||||
|
||||
// it handles visual line mode
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
alpha
|
||||
beˇta
|
||||
gamma"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes(["shift-v", "s"]);
|
||||
cx.assert_editor_state(indoc! {"
|
||||
alpha
|
||||
ˇ
|
||||
gamma"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_visual_change(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state("The quick ˇbrown").await;
|
||||
cx.simulate_shared_keystrokes(["v", "w", "c"]).await;
|
||||
cx.assert_shared_state("The quick ˇ").await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
The ˇquick brown
|
||||
fox jumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "w", "j", "c"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The ˇver
|
||||
the lazy dog"})
|
||||
.await;
|
||||
|
||||
let cases = cx.each_marked_position(indoc! {"
|
||||
The ˇquick brown
|
||||
fox jumps ˇover
|
||||
the ˇlazy dog"});
|
||||
for initial_state in cases {
|
||||
cx.assert_neovim_compatible(&initial_state, ["v", "w", "j", "c"])
|
||||
.await;
|
||||
cx.assert_neovim_compatible(&initial_state, ["v", "w", "k", "c"])
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx)
|
||||
.await
|
||||
.binding(["shift-v", "c"]);
|
||||
cx.assert(indoc! {"
|
||||
The quˇick brown
|
||||
fox jumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
// Test pasting code copied on change
|
||||
cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
|
||||
cx.assert_state_matches().await;
|
||||
|
||||
cx.assert_all(indoc! {"
|
||||
The quick brown
|
||||
fox juˇmps over
|
||||
the laˇzy dog"})
|
||||
.await;
|
||||
let mut cx = cx.binding(["shift-v", "j", "c"]);
|
||||
cx.assert(indoc! {"
|
||||
The quˇick brown
|
||||
fox jumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
// Test pasting code copied on delete
|
||||
cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
|
||||
cx.assert_state_matches().await;
|
||||
|
||||
cx.assert_all(indoc! {"
|
||||
The quick brown
|
||||
fox juˇmps over
|
||||
the laˇzy dog"})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
@ -369,7 +369,7 @@ fn surrounding_markers(
|
||||
start = Some(point)
|
||||
} else {
|
||||
*point.column_mut() += char.len_utf8() as u32;
|
||||
start = Some(point);
|
||||
start = Some(point)
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -420,11 +420,38 @@ fn surrounding_markers(
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(start), Some(end)) = (start, end) {
|
||||
Some(start..end)
|
||||
} else {
|
||||
None
|
||||
let (Some(mut start), Some(mut end)) = (start, end) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if !around {
|
||||
// if a block starts with a newline, move the start to after the newline.
|
||||
let mut was_newline = false;
|
||||
for (char, point) in map.chars_at(start) {
|
||||
if was_newline {
|
||||
start = point;
|
||||
} else if char == '\n' {
|
||||
was_newline = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// if a block ends with a newline, then whitespace, then the delimeter,
|
||||
// move the end to after the newline.
|
||||
let mut new_end = end;
|
||||
for (char, point) in map.reverse_chars_at(end) {
|
||||
if char == '\n' {
|
||||
end = new_end;
|
||||
break;
|
||||
}
|
||||
if !char.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
new_end = point
|
||||
}
|
||||
}
|
||||
|
||||
Some(start..end)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -481,6 +508,12 @@ mod test {
|
||||
async fn test_visual_word_object(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state("The quick ˇbrown\nfox").await;
|
||||
cx.simulate_shared_keystrokes(["v"]).await;
|
||||
cx.assert_shared_state("The quick «bˇ»rown\nfox").await;
|
||||
cx.simulate_shared_keystrokes(["i", "w"]).await;
|
||||
cx.assert_shared_state("The quick «brownˇ»\nfox").await;
|
||||
|
||||
cx.assert_binding_matches_all(["v", "i", "w"], WORD_LOCATIONS)
|
||||
.await;
|
||||
cx.assert_binding_matches_all_exempted(
|
||||
@ -675,6 +708,48 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {
|
||||
"func empty(a string) bool {
|
||||
if a == \"\" {
|
||||
return true
|
||||
}
|
||||
ˇreturn false
|
||||
}"
|
||||
})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
func empty(a string) bool {
|
||||
« if a == \"\" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
ˇ»}"})
|
||||
.await;
|
||||
cx.set_shared_state(indoc! {
|
||||
"func empty(a string) bool {
|
||||
if a == \"\" {
|
||||
ˇreturn true
|
||||
}
|
||||
return false
|
||||
}"
|
||||
})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
func empty(a string) bool {
|
||||
if a == \"\" {
|
||||
« return true
|
||||
ˇ» }
|
||||
return false
|
||||
}"})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
@ -12,6 +12,15 @@ pub enum Mode {
|
||||
Visual { line: bool },
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn is_visual(&self) -> bool {
|
||||
match self {
|
||||
Mode::Normal | Mode::Insert => false,
|
||||
Mode::Visual { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Mode {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
@ -78,12 +87,11 @@ impl VimState {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn clip_at_line_end(&self) -> bool {
|
||||
!matches!(self.mode, Mode::Insert | Mode::Visual { .. })
|
||||
}
|
||||
|
||||
pub fn empty_selections_only(&self) -> bool {
|
||||
!matches!(self.mode, Mode::Visual { .. })
|
||||
pub fn clip_at_line_ends(&self) -> bool {
|
||||
match self.mode {
|
||||
Mode::Insert | Mode::Visual { .. } => false,
|
||||
Mode::Normal => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keymap_context_layer(&self) -> KeymapContext {
|
||||
|
@ -141,7 +141,7 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
// works in visuial mode
|
||||
cx.simulate_keystrokes(["shift-v", "down", ">"]);
|
||||
cx.assert_editor_state("aa\n b«b\n cˇ»c");
|
||||
cx.assert_editor_state("aa\n b«b\n ccˇ»");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@ -157,6 +157,16 @@ async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
|
||||
cx.assert_state("aˇbc\n", Mode::Insert);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
cx.set_state("aˇbˇc", Mode::Normal);
|
||||
cx.simulate_keystrokes(["escape"]);
|
||||
|
||||
cx.assert_state("aˇbc", Mode::Normal);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
@ -61,6 +61,9 @@ pub struct NeovimBackedTestContext<'a> {
|
||||
// bindings are exempted. If None, all bindings are ignored for that insertion text.
|
||||
exemptions: HashMap<String, Option<HashSet<String>>>,
|
||||
neovim: NeovimConnection,
|
||||
|
||||
last_set_state: Option<String>,
|
||||
recent_keystrokes: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a> NeovimBackedTestContext<'a> {
|
||||
@ -71,6 +74,9 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||
cx,
|
||||
exemptions: Default::default(),
|
||||
neovim: NeovimConnection::new(function_name).await,
|
||||
|
||||
last_set_state: None,
|
||||
recent_keystrokes: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,13 +108,21 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||
keystroke_texts: [&str; COUNT],
|
||||
) -> ContextHandle {
|
||||
for keystroke_text in keystroke_texts.into_iter() {
|
||||
self.recent_keystrokes.push(keystroke_text.to_string());
|
||||
self.neovim.send_keystroke(keystroke_text).await;
|
||||
}
|
||||
self.simulate_keystrokes(keystroke_texts)
|
||||
}
|
||||
|
||||
pub async fn set_shared_state(&mut self, marked_text: &str) -> ContextHandle {
|
||||
let context_handle = self.set_state(marked_text, Mode::Normal);
|
||||
let mode = if marked_text.contains("»") {
|
||||
Mode::Visual { line: false }
|
||||
} else {
|
||||
Mode::Normal
|
||||
};
|
||||
let context_handle = self.set_state(marked_text, mode);
|
||||
self.last_set_state = Some(marked_text.to_string());
|
||||
self.recent_keystrokes = Vec::new();
|
||||
self.neovim.set_state(marked_text).await;
|
||||
context_handle
|
||||
}
|
||||
@ -116,15 +130,25 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||
pub async fn assert_shared_state(&mut self, marked_text: &str) {
|
||||
let neovim = self.neovim_state().await;
|
||||
if neovim != marked_text {
|
||||
let initial_state = self
|
||||
.last_set_state
|
||||
.as_ref()
|
||||
.unwrap_or(&"N/A".to_string())
|
||||
.clone();
|
||||
panic!(
|
||||
indoc! {"Test is incorrect (currently expected != neovim state)
|
||||
|
||||
# initial state:
|
||||
{}
|
||||
# keystrokes:
|
||||
{}
|
||||
# currently expected:
|
||||
{}
|
||||
# neovim state:
|
||||
{}
|
||||
# zed state:
|
||||
{}"},
|
||||
initial_state,
|
||||
self.recent_keystrokes.join(" "),
|
||||
marked_text,
|
||||
neovim,
|
||||
self.editor_state(),
|
||||
@ -141,28 +165,40 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn neovim_mode(&mut self) -> Mode {
|
||||
self.neovim.mode().await.unwrap()
|
||||
}
|
||||
|
||||
async fn neovim_selection(&mut self) -> Range<usize> {
|
||||
let mut neovim_selection = self.neovim.selection().await;
|
||||
// Zed selections adjust themselves to make the end point visually make sense
|
||||
if neovim_selection.start > neovim_selection.end {
|
||||
neovim_selection.start.column += 1;
|
||||
}
|
||||
let neovim_selection = self.neovim.selection().await;
|
||||
neovim_selection.to_offset(&self.buffer_snapshot())
|
||||
}
|
||||
|
||||
pub async fn assert_state_matches(&mut self) {
|
||||
assert_eq!(
|
||||
self.neovim.text().await,
|
||||
self.buffer_text(),
|
||||
"{}",
|
||||
self.assertion_context()
|
||||
);
|
||||
let neovim = self.neovim_state().await;
|
||||
let editor = self.editor_state();
|
||||
let initial_state = self
|
||||
.last_set_state
|
||||
.as_ref()
|
||||
.unwrap_or(&"N/A".to_string())
|
||||
.clone();
|
||||
|
||||
let selections = vec![self.neovim_selection().await];
|
||||
self.assert_editor_selections(selections);
|
||||
|
||||
if let Some(neovim_mode) = self.neovim.mode().await {
|
||||
assert_eq!(neovim_mode, self.mode(), "{}", self.assertion_context(),);
|
||||
if neovim != editor {
|
||||
panic!(
|
||||
indoc! {"Test failed (zed does not match nvim behaviour)
|
||||
# initial state:
|
||||
{}
|
||||
# keystrokes:
|
||||
{}
|
||||
# neovim state:
|
||||
{}
|
||||
# zed state:
|
||||
{}"},
|
||||
initial_state,
|
||||
self.recent_keystrokes.join(" "),
|
||||
neovim,
|
||||
editor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,6 +243,29 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn each_marked_position(&self, marked_positions: &str) -> Vec<String> {
|
||||
let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
|
||||
let mut ret = Vec::with_capacity(cursor_offsets.len());
|
||||
|
||||
for cursor_offset in cursor_offsets.iter() {
|
||||
let mut marked_text = unmarked_text.clone();
|
||||
marked_text.insert(*cursor_offset, 'ˇ');
|
||||
ret.push(marked_text)
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub async fn assert_neovim_compatible<const COUNT: usize>(
|
||||
&mut self,
|
||||
marked_positions: &str,
|
||||
keystrokes: [&str; COUNT],
|
||||
) {
|
||||
self.set_shared_state(&marked_positions).await;
|
||||
self.simulate_shared_keystrokes(keystrokes).await;
|
||||
self.assert_state_matches().await;
|
||||
}
|
||||
|
||||
pub async fn assert_binding_matches_all_exempted<const COUNT: usize>(
|
||||
&mut self,
|
||||
keystrokes: [&str; COUNT],
|
||||
|
@ -213,6 +213,16 @@ impl NeovimConnection {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "neovim")]
|
||||
async fn read_position(&mut self, cmd: &str) -> u32 {
|
||||
self.nvim
|
||||
.command_output(cmd)
|
||||
.await
|
||||
.unwrap()
|
||||
.parse::<u32>()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[cfg(feature = "neovim")]
|
||||
pub async fn state(&mut self) -> (Option<Mode>, String, Range<Point>) {
|
||||
let nvim_buffer = self
|
||||
@ -226,22 +236,12 @@ impl NeovimConnection {
|
||||
.expect("Could not get buffer text")
|
||||
.join("\n");
|
||||
|
||||
let cursor_row: u32 = self
|
||||
.nvim
|
||||
.command_output("echo line('.')")
|
||||
.await
|
||||
.unwrap()
|
||||
.parse::<u32>()
|
||||
.unwrap()
|
||||
- 1; // Neovim rows start at 1
|
||||
let cursor_col: u32 = self
|
||||
.nvim
|
||||
.command_output("echo col('.')")
|
||||
.await
|
||||
.unwrap()
|
||||
.parse::<u32>()
|
||||
.unwrap()
|
||||
- 1; // Neovim columns start at 1
|
||||
// nvim columns are 1-based, so -1.
|
||||
let mut cursor_row = self.read_position("echo line('.')").await - 1;
|
||||
let mut cursor_col = self.read_position("echo col('.')").await - 1;
|
||||
let mut selection_row = self.read_position("echo line('v')").await - 1;
|
||||
let mut selection_col = self.read_position("echo col('v')").await - 1;
|
||||
let total_rows = self.read_position("echo line('$')").await - 1;
|
||||
|
||||
let nvim_mode_text = self
|
||||
.nvim
|
||||
@ -266,46 +266,38 @@ impl NeovimConnection {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let (start, end) = if let Some(Mode::Visual { .. }) = mode {
|
||||
self.nvim
|
||||
.input("<escape>")
|
||||
.await
|
||||
.expect("Could not exit visual mode");
|
||||
let nvim_buffer = self
|
||||
.nvim
|
||||
.get_current_buf()
|
||||
.await
|
||||
.expect("Could not get neovim buffer");
|
||||
let (start_row, start_col) = nvim_buffer
|
||||
.get_mark("<")
|
||||
.await
|
||||
.expect("Could not get selection start");
|
||||
let (end_row, end_col) = nvim_buffer
|
||||
.get_mark(">")
|
||||
.await
|
||||
.expect("Could not get selection end");
|
||||
self.nvim
|
||||
.input("gv")
|
||||
.await
|
||||
.expect("Could not reselect visual selection");
|
||||
|
||||
if cursor_row == start_row as u32 - 1 && cursor_col == start_col as u32 {
|
||||
(
|
||||
Point::new(end_row as u32 - 1, end_col as u32),
|
||||
Point::new(start_row as u32 - 1, start_col as u32),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Point::new(start_row as u32 - 1, start_col as u32),
|
||||
Point::new(end_row as u32 - 1, end_col as u32),
|
||||
)
|
||||
// Vim uses the index of the first and last character in the selection
|
||||
// Zed uses the index of the positions between the characters, so we need
|
||||
// to add one to the end in visual mode.
|
||||
match mode {
|
||||
Some(Mode::Visual { .. }) => {
|
||||
if selection_col > cursor_col {
|
||||
let selection_line_length =
|
||||
self.read_position("echo strlen(getline(line('v')))").await;
|
||||
if selection_line_length > selection_col {
|
||||
selection_col += 1;
|
||||
} else if selection_row < total_rows {
|
||||
selection_col = 0;
|
||||
selection_row += 1;
|
||||
}
|
||||
} else {
|
||||
let cursor_line_length =
|
||||
self.read_position("echo strlen(getline(line('.')))").await;
|
||||
if cursor_line_length > cursor_col {
|
||||
cursor_col += 1;
|
||||
} else if cursor_row < total_rows {
|
||||
cursor_col = 0;
|
||||
cursor_row += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(
|
||||
Point::new(cursor_row, cursor_col),
|
||||
Point::new(cursor_row, cursor_col),
|
||||
)
|
||||
};
|
||||
Some(Mode::Insert) | Some(Mode::Normal) | None => {}
|
||||
}
|
||||
|
||||
let (start, end) = (
|
||||
Point::new(selection_row, selection_col),
|
||||
Point::new(cursor_row, cursor_col),
|
||||
);
|
||||
|
||||
let state = NeovimData::Get {
|
||||
mode,
|
||||
|
@ -86,12 +86,13 @@ impl<'a> VimTestContext<'a> {
|
||||
|
||||
pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
|
||||
let window = self.window;
|
||||
let context_handle = self.cx.set_state(text);
|
||||
window.update(self.cx.cx.cx, |cx| {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.switch_mode(mode, false, cx);
|
||||
vim.switch_mode(mode, true, cx);
|
||||
})
|
||||
});
|
||||
self.cx.set_state(text)
|
||||
context_handle
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
@ -13,7 +13,7 @@ mod visual;
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::CommandPaletteFilter;
|
||||
use editor::{Bias, Editor, EditorMode, Event};
|
||||
use editor::{movement, Editor, EditorMode, Event};
|
||||
use gpui::{
|
||||
actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, AppContext,
|
||||
Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||
@ -181,6 +181,7 @@ impl Vim {
|
||||
}
|
||||
|
||||
fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
|
||||
let last_mode = self.state.mode;
|
||||
self.state.mode = mode;
|
||||
self.state.operator_stack.clear();
|
||||
|
||||
@ -197,12 +198,16 @@ impl Vim {
|
||||
self.update_active_editor(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if self.state.empty_selections_only() {
|
||||
let new_head = map.clip_point(selection.head(), Bias::Left);
|
||||
selection.collapse_to(new_head, selection.goal)
|
||||
} else {
|
||||
selection
|
||||
.set_head(map.clip_point(selection.head(), Bias::Left), selection.goal);
|
||||
if last_mode.is_visual() && !mode.is_visual() {
|
||||
let mut point = selection.head();
|
||||
if !selection.reversed {
|
||||
point = movement::left(map, selection.head());
|
||||
}
|
||||
selection.collapse_to(point, selection.goal)
|
||||
} else if !last_mode.is_visual() && mode.is_visual() {
|
||||
if selection.is_empty() {
|
||||
selection.end = movement::right(map, selection.start);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
@ -265,7 +270,7 @@ impl Vim {
|
||||
}
|
||||
Some(Operator::Replace) => match Vim::read(cx).state.mode {
|
||||
Mode::Normal => normal_replace(text, cx),
|
||||
Mode::Visual { line } => visual_replace(text, line, cx),
|
||||
Mode::Visual { .. } => visual_replace(text, cx),
|
||||
_ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
|
||||
},
|
||||
_ => {}
|
||||
@ -309,7 +314,7 @@ impl Vim {
|
||||
self.update_active_editor(cx, |editor, cx| {
|
||||
if self.enabled && editor.mode() == EditorMode::Full {
|
||||
editor.set_cursor_shape(cursor_shape, cx);
|
||||
editor.set_clip_at_line_ends(state.clip_at_line_end(), cx);
|
||||
editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
|
||||
editor.set_collapse_matches(true);
|
||||
editor.set_input_enabled(!state.vim_controlled());
|
||||
editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
|
||||
|
@ -16,10 +16,22 @@ use crate::{
|
||||
Vim,
|
||||
};
|
||||
|
||||
actions!(vim, [VisualDelete, VisualChange, VisualYank, VisualPaste]);
|
||||
actions!(
|
||||
vim,
|
||||
[
|
||||
ToggleVisual,
|
||||
ToggleVisualLine,
|
||||
VisualDelete,
|
||||
VisualYank,
|
||||
VisualPaste,
|
||||
OtherEnd,
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(change);
|
||||
cx.add_action(toggle_visual);
|
||||
cx.add_action(toggle_visual_line);
|
||||
cx.add_action(other_end);
|
||||
cx.add_action(delete);
|
||||
cx.add_action(yank);
|
||||
cx.add_action(paste);
|
||||
@ -32,24 +44,45 @@ pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContex
|
||||
s.move_with(|map, selection| {
|
||||
let was_reversed = selection.reversed;
|
||||
|
||||
if let Some((new_head, goal)) =
|
||||
motion.move_point(map, selection.head(), selection.goal, times)
|
||||
{
|
||||
selection.set_head(new_head, goal);
|
||||
let mut current_head = selection.head();
|
||||
|
||||
if was_reversed && !selection.reversed {
|
||||
// Head was at the start of the selection, and now is at the end. We need to move the start
|
||||
// back by one if possible in order to compensate for this change.
|
||||
*selection.start.column_mut() =
|
||||
selection.start.column().saturating_sub(1);
|
||||
selection.start = map.clip_point(selection.start, Bias::Left);
|
||||
} else if !was_reversed && selection.reversed {
|
||||
// Head was at the end of the selection, and now is at the start. We need to move the end
|
||||
// forward by one if possible in order to compensate for this change.
|
||||
*selection.end.column_mut() = selection.end.column() + 1;
|
||||
selection.end = map.clip_point(selection.end, Bias::Right);
|
||||
// our motions assume the current character is after the cursor,
|
||||
// but in (forward) visual mode the current character is just
|
||||
// before the end of the selection.
|
||||
|
||||
// If the file ends with a newline (which is common) we don't do this.
|
||||
// so that if you go to the end of such a file you can use "up" to go
|
||||
// to the previous line and have it work somewhat as expected.
|
||||
if !selection.reversed
|
||||
&& !selection.is_empty()
|
||||
&& !(selection.end.column() == 0 && selection.end == map.max_point())
|
||||
{
|
||||
current_head = movement::left(map, selection.end)
|
||||
}
|
||||
|
||||
let Some((new_head, goal)) =
|
||||
motion.move_point(map, current_head, selection.goal, times) else { return };
|
||||
|
||||
selection.set_head(new_head, goal);
|
||||
|
||||
// ensure the current character is included in the selection.
|
||||
if !selection.reversed {
|
||||
// TODO: maybe try clipping left for multi-buffers
|
||||
let next_point = movement::right(map, selection.end);
|
||||
|
||||
if !(next_point.column() == 0 && next_point == map.max_point()) {
|
||||
selection.end = movement::right(map, selection.end)
|
||||
}
|
||||
}
|
||||
|
||||
// vim always ensures the anchor character stays selected.
|
||||
// if our selection has reversed, we need to move the opposite end
|
||||
// to ensure the anchor is still selected.
|
||||
if was_reversed && !selection.reversed {
|
||||
selection.start = movement::left(map, selection.start);
|
||||
} else if !was_reversed && selection.reversed {
|
||||
selection.end = movement::right(map, selection.end);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -64,14 +97,29 @@ pub fn visual_object(object: Object, cx: &mut WindowContext) {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
let head = selection.head();
|
||||
if let Some(mut range) = object.range(map, head, around) {
|
||||
if !range.is_empty() {
|
||||
if let Some((_, end)) = map.reverse_chars_at(range.end).next() {
|
||||
range.end = end;
|
||||
}
|
||||
let mut head = selection.head();
|
||||
|
||||
if selection.is_empty() {
|
||||
// all our motions assume that the current character is
|
||||
// after the cursor; however in the case of a visual selection
|
||||
// the current character is before the cursor.
|
||||
if !selection.reversed {
|
||||
head = movement::left(map, head);
|
||||
}
|
||||
|
||||
if let Some(range) = object.range(map, head, around) {
|
||||
if !range.is_empty() {
|
||||
let expand_both_ways = if selection.is_empty() {
|
||||
true
|
||||
// contains only one character
|
||||
} else if let Some((_, start)) =
|
||||
map.reverse_chars_at(selection.end).next()
|
||||
{
|
||||
selection.start == start
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if expand_both_ways {
|
||||
selection.start = range.start;
|
||||
selection.end = range.end;
|
||||
} else if selection.reversed {
|
||||
@ -88,72 +136,58 @@ pub fn visual_object(object: Object, cx: &mut WindowContext) {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspace>) {
|
||||
pub fn toggle_visual(_: &mut Workspace, _: &ToggleVisual, cx: &mut ViewContext<Workspace>) {
|
||||
Vim::update(cx, |vim, cx| match vim.state.mode {
|
||||
Mode::Normal | Mode::Insert | Mode::Visual { line: true } => {
|
||||
vim.switch_mode(Mode::Visual { line: false }, false, cx);
|
||||
}
|
||||
Mode::Visual { line: false } => {
|
||||
vim.switch_mode(Mode::Normal, false, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn toggle_visual_line(
|
||||
_: &mut Workspace,
|
||||
_: &ToggleVisualLine,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
Vim::update(cx, |vim, cx| match vim.state.mode {
|
||||
Mode::Normal | Mode::Insert | Mode::Visual { line: false } => {
|
||||
vim.switch_mode(Mode::Visual { line: true }, false, cx);
|
||||
}
|
||||
Mode::Visual { line: true } => {
|
||||
vim.switch_mode(Mode::Normal, false, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace>) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
// Compute edits and resulting anchor selections. If in line mode, adjust
|
||||
// the anchor location and additional newline
|
||||
let mut edits = Vec::new();
|
||||
let mut new_selections = Vec::new();
|
||||
let line_mode = editor.selections.line_mode;
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if !selection.reversed {
|
||||
// Head is at the end of the selection. Adjust the end position to
|
||||
// to include the character under the cursor.
|
||||
*selection.end.column_mut() = selection.end.column() + 1;
|
||||
selection.end = map.clip_point(selection.end, Bias::Right);
|
||||
}
|
||||
|
||||
if line_mode {
|
||||
let range = selection.map(|p| p.to_point(map)).range();
|
||||
let expanded_range = map.expand_to_line(range);
|
||||
// If we are at the last line, the anchor needs to be after the newline so that
|
||||
// it is on a line of its own. Otherwise, the anchor may be after the newline
|
||||
let anchor = if expanded_range.end == map.buffer_snapshot.max_point() {
|
||||
map.buffer_snapshot.anchor_after(expanded_range.end)
|
||||
} else {
|
||||
map.buffer_snapshot.anchor_before(expanded_range.start)
|
||||
};
|
||||
|
||||
edits.push((expanded_range, "\n"));
|
||||
new_selections.push(selection.map(|_| anchor));
|
||||
} else {
|
||||
let range = selection.map(|p| p.to_point(map)).range();
|
||||
let anchor = map.buffer_snapshot.anchor_after(range.end);
|
||||
edits.push((range, ""));
|
||||
new_selections.push(selection.map(|_| anchor));
|
||||
}
|
||||
selection.goal = SelectionGoal::None;
|
||||
});
|
||||
});
|
||||
copy_selections_content(editor, editor.selections.line_mode, cx);
|
||||
editor.edit_with_autoindent(edits, cx);
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_anchors(new_selections);
|
||||
});
|
||||
});
|
||||
vim.switch_mode(Mode::Insert, false, cx);
|
||||
s.move_with(|_, selection| {
|
||||
selection.reversed = !selection.reversed;
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspace>) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let mut original_columns: HashMap<_, _> = Default::default();
|
||||
let line_mode = editor.selections.line_mode;
|
||||
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if line_mode {
|
||||
original_columns
|
||||
.insert(selection.id, selection.head().to_point(map).column);
|
||||
} else if !selection.reversed {
|
||||
// Head is at the end of the selection. Adjust the end position to
|
||||
// to include the character under the cursor.
|
||||
*selection.end.column_mut() = selection.end.column() + 1;
|
||||
selection.end = map.clip_point(selection.end, Bias::Right);
|
||||
let mut position = selection.head();
|
||||
if !selection.reversed {
|
||||
position = movement::left(map, position);
|
||||
}
|
||||
original_columns.insert(selection.id, position.to_point(map).column);
|
||||
}
|
||||
selection.goal = SelectionGoal::None;
|
||||
});
|
||||
@ -175,27 +209,14 @@ pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspac
|
||||
});
|
||||
});
|
||||
});
|
||||
vim.switch_mode(Mode::Normal, false, cx);
|
||||
vim.switch_mode(Mode::Normal, true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let line_mode = editor.selections.line_mode;
|
||||
if !line_mode {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if !selection.reversed {
|
||||
// Head is at the end of the selection. Adjust the end position to
|
||||
// to include the character under the cursor.
|
||||
*selection.end.column_mut() = selection.end.column() + 1;
|
||||
selection.end = map.clip_point(selection.end, Bias::Right);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
copy_selections_content(editor, line_mode, cx);
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.move_with(|_, selection| {
|
||||
@ -203,7 +224,7 @@ pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>)
|
||||
});
|
||||
});
|
||||
});
|
||||
vim.switch_mode(Mode::Normal, false, cx);
|
||||
vim.switch_mode(Mode::Normal, true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@ -256,11 +277,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
|
||||
|
||||
let mut selection = selection.clone();
|
||||
if !selection.reversed {
|
||||
let mut adjusted = selection.end;
|
||||
// Head is at the end of the selection. Adjust the end position to
|
||||
// to include the character under the cursor.
|
||||
*adjusted.column_mut() = adjusted.column() + 1;
|
||||
adjusted = display_map.clip_point(adjusted, Bias::Right);
|
||||
let adjusted = selection.end;
|
||||
// If the selection is empty, move both the start and end forward one
|
||||
// character
|
||||
if selection.is_empty() {
|
||||
@ -311,11 +328,11 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
|
||||
}
|
||||
});
|
||||
});
|
||||
vim.switch_mode(Mode::Normal, false, cx);
|
||||
vim.switch_mode(Mode::Normal, true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut WindowContext) {
|
||||
pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
@ -336,14 +353,7 @@ pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut WindowContext)
|
||||
|
||||
let mut edits = Vec::new();
|
||||
for selection in selections.iter() {
|
||||
let mut selection = selection.clone();
|
||||
if !line && !selection.reversed {
|
||||
// Head is at the end of the selection. Adjust the end position to
|
||||
// to include the character under the cursor.
|
||||
*selection.end.column_mut() = selection.end.column() + 1;
|
||||
selection.end = display_map.clip_point(selection.end, Bias::Right);
|
||||
}
|
||||
|
||||
let selection = selection.clone();
|
||||
for row_range in
|
||||
movement::split_display_range_by_lines(&display_map, selection.range())
|
||||
{
|
||||
@ -367,6 +377,7 @@ pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut WindowContext)
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use indoc::indoc;
|
||||
use workspace::item::Item;
|
||||
|
||||
use crate::{
|
||||
state::Mode,
|
||||
@ -375,19 +386,146 @@ mod test {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_enter_visual_mode(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx)
|
||||
.await
|
||||
.binding(["v", "w", "j"]);
|
||||
cx.assert_all(indoc! {"
|
||||
The ˇquick brown
|
||||
fox jumps ˇover
|
||||
the ˇlazy dog"})
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {
|
||||
"The ˇquick brown
|
||||
fox jumps over
|
||||
the lazy dog"
|
||||
})
|
||||
.await;
|
||||
let cursor = cx.update_editor(|editor, _| editor.pixel_position_of_cursor());
|
||||
|
||||
// entering visual mode should select the character
|
||||
// under cursor
|
||||
cx.simulate_shared_keystrokes(["v"]).await;
|
||||
cx.assert_shared_state(indoc! { "The «qˇ»uick brown
|
||||
fox jumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
let mut cx = cx.binding(["v", "b", "k"]);
|
||||
cx.assert_all(indoc! {"
|
||||
The ˇquick brown
|
||||
fox jumps ˇover
|
||||
the ˇlazy dog"})
|
||||
cx.update_editor(|editor, _| assert_eq!(cursor, editor.pixel_position_of_cursor()));
|
||||
|
||||
// forwards motions should extend the selection
|
||||
cx.simulate_shared_keystrokes(["w", "j"]).await;
|
||||
cx.assert_shared_state(indoc! { "The «quick brown
|
||||
fox jumps oˇ»ver
|
||||
the lazy dog"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes(["escape"]).await;
|
||||
assert_eq!(Mode::Normal, cx.neovim_mode().await);
|
||||
cx.assert_shared_state(indoc! { "The quick brown
|
||||
fox jumps ˇover
|
||||
the lazy dog"})
|
||||
.await;
|
||||
|
||||
// motions work backwards
|
||||
cx.simulate_shared_keystrokes(["v", "k", "b"]).await;
|
||||
cx.assert_shared_state(indoc! { "The «ˇquick brown
|
||||
fox jumps o»ver
|
||||
the lazy dog"})
|
||||
.await;
|
||||
|
||||
// works on empty lines
|
||||
cx.set_shared_state(indoc! {"
|
||||
a
|
||||
ˇ
|
||||
b
|
||||
"})
|
||||
.await;
|
||||
let cursor = cx.update_editor(|editor, _| editor.pixel_position_of_cursor());
|
||||
cx.simulate_shared_keystrokes(["v"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
a
|
||||
«
|
||||
ˇ»b
|
||||
"})
|
||||
.await;
|
||||
cx.update_editor(|editor, _| assert_eq!(cursor, editor.pixel_position_of_cursor()));
|
||||
|
||||
// toggles off again
|
||||
cx.simulate_shared_keystrokes(["v"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
a
|
||||
ˇ
|
||||
b
|
||||
"})
|
||||
.await;
|
||||
|
||||
// works at the end of a document
|
||||
cx.set_shared_state(indoc! {"
|
||||
a
|
||||
b
|
||||
ˇ"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes(["v"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
a
|
||||
b
|
||||
ˇ"})
|
||||
.await;
|
||||
assert_eq!(cx.mode(), cx.neovim_mode().await);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_enter_visual_line_mode(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {
|
||||
"The ˇquick brown
|
||||
fox jumps over
|
||||
the lazy dog"
|
||||
})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["shift-v"]).await;
|
||||
cx.assert_shared_state(indoc! { "The «qˇ»uick brown
|
||||
fox jumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
assert_eq!(cx.mode(), cx.neovim_mode().await);
|
||||
cx.simulate_shared_keystrokes(["x"]).await;
|
||||
cx.assert_shared_state(indoc! { "fox ˇjumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
|
||||
// it should work on empty lines
|
||||
cx.set_shared_state(indoc! {"
|
||||
a
|
||||
ˇ
|
||||
b"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["shift-v"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
a
|
||||
«
|
||||
ˇ»b"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["x"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
a
|
||||
ˇb"})
|
||||
.await;
|
||||
|
||||
// it should work at the end of the document
|
||||
cx.set_shared_state(indoc! {"
|
||||
a
|
||||
b
|
||||
ˇ"})
|
||||
.await;
|
||||
let cursor = cx.update_editor(|editor, _| editor.pixel_position_of_cursor());
|
||||
cx.simulate_shared_keystrokes(["shift-v"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
a
|
||||
b
|
||||
ˇ"})
|
||||
.await;
|
||||
assert_eq!(cx.mode(), cx.neovim_mode().await);
|
||||
cx.update_editor(|editor, _| assert_eq!(cursor, editor.pixel_position_of_cursor()));
|
||||
cx.simulate_shared_keystrokes(["x"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
a
|
||||
ˇb"})
|
||||
.await;
|
||||
}
|
||||
|
||||
@ -395,6 +533,9 @@ mod test {
|
||||
async fn test_visual_delete(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.assert_binding_matches(["v", "w"], "The quick ˇbrown")
|
||||
.await;
|
||||
|
||||
cx.assert_binding_matches(["v", "w", "x"], "The quick ˇbrown")
|
||||
.await;
|
||||
cx.assert_binding_matches(
|
||||
@ -457,62 +598,15 @@ mod test {
|
||||
fox juˇmps over
|
||||
the laˇzy dog"})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_visual_change(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx)
|
||||
.await
|
||||
.binding(["v", "w", "c"]);
|
||||
cx.assert("The quick ˇbrown").await;
|
||||
let mut cx = cx.binding(["v", "w", "j", "c"]);
|
||||
cx.assert_all(indoc! {"
|
||||
The ˇquick brown
|
||||
fox jumps ˇover
|
||||
the ˇlazy dog"})
|
||||
cx.set_shared_state(indoc! {"
|
||||
The ˇlong line
|
||||
should not
|
||||
crash
|
||||
"})
|
||||
.await;
|
||||
let mut cx = cx.binding(["v", "b", "k", "c"]);
|
||||
cx.assert_all(indoc! {"
|
||||
The ˇquick brown
|
||||
fox jumps ˇover
|
||||
the ˇlazy dog"})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx)
|
||||
.await
|
||||
.binding(["shift-v", "c"]);
|
||||
cx.assert(indoc! {"
|
||||
The quˇick brown
|
||||
fox jumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
// Test pasting code copied on change
|
||||
cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
|
||||
cx.simulate_shared_keystrokes(["shift-v", "$", "x"]).await;
|
||||
cx.assert_state_matches().await;
|
||||
|
||||
cx.assert_all(indoc! {"
|
||||
The quick brown
|
||||
fox juˇmps over
|
||||
the laˇzy dog"})
|
||||
.await;
|
||||
let mut cx = cx.binding(["shift-v", "j", "c"]);
|
||||
cx.assert(indoc! {"
|
||||
The quˇick brown
|
||||
fox jumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
// Test pasting code copied on delete
|
||||
cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
|
||||
cx.assert_state_matches().await;
|
||||
|
||||
cx.assert_all(indoc! {"
|
||||
The quick brown
|
||||
fox juˇmps over
|
||||
the laˇzy dog"})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@ -605,7 +699,7 @@ mod test {
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
The quick brown
|
||||
fox «jumpˇ»s over
|
||||
fox «jumpsˇ» over
|
||||
the lazy dog"},
|
||||
Mode::Visual { line: false },
|
||||
);
|
||||
@ -629,7 +723,7 @@ mod test {
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
The quick brown
|
||||
fox juˇmps over
|
||||
fox ju«mˇ»ps over
|
||||
the lazy dog"},
|
||||
Mode::Visual { line: true },
|
||||
);
|
||||
@ -643,7 +737,7 @@ mod test {
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
The quick brown
|
||||
the «lazˇ»y dog"},
|
||||
the «lazyˇ» dog"},
|
||||
Mode::Visual { line: false },
|
||||
);
|
||||
cx.simulate_keystroke("p");
|
||||
|
15
crates/vim/test_data/test_enter_visual_line_mode.json
Normal file
15
crates/vim/test_data/test_enter_visual_line_mode.json
Normal file
@ -0,0 +1,15 @@
|
||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":{"Visual":{"line":true}}}}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"fox ˇjumps over\nthe lazy dog","mode":"Normal"}}
|
||||
{"Put":{"state":"a\nˇ\nb"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Get":{"state":"a\n«\nˇ»b","mode":{"Visual":{"line":true}}}}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"a\nˇb","mode":"Normal"}}
|
||||
{"Put":{"state":"a\nb\nˇ"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Get":{"state":"a\nb\nˇ","mode":{"Visual":{"line":true}}}}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"a\nˇb","mode":"Normal"}}
|
@ -1,30 +1,20 @@
|
||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Key":"w"}
|
||||
{"Key":"j"}
|
||||
{"Get":{"state":"The «quick brown\nfox jumps ˇ»over\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
||||
{"Get":{"state":"The «quick brown\nfox jumps oˇ»ver\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Key":"escape"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog","mode":"Normal"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"j"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps «over\nˇ»the lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"j"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps over\nthe «lazy ˇ»dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"b"}
|
||||
{"Key":"k"}
|
||||
{"Get":{"state":"«ˇThe »quick brown\nfox jumps over\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"b"}
|
||||
{"Key":"k"}
|
||||
{"Get":{"state":"The «ˇquick brown\nfox jumps »over\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
||||
{"Get":{"state":"The «ˇquick brown\nfox jumps o»ver\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"a\nˇ\nb\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"b"}
|
||||
{"Key":"k"}
|
||||
{"Get":{"state":"The quick brown\n«ˇfox jumps over\nthe »lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"a\n«\nˇ»b\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Key":"v"}
|
||||
{"Get":{"state":"a\nˇ\nb\n","mode":"Normal"}}
|
||||
{"Put":{"state":"a\nb\nˇ"}}
|
||||
{"Key":"v"}
|
||||
{"Get":{"state":"a\nb\nˇ","mode":{"Visual":{"line":false}}}}
|
||||
|
@ -0,0 +1,10 @@
|
||||
{"Put":{"state":"func empty(a string) bool {\n if a == \"\" {\n return true\n }\n ˇreturn false\n}"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"{"}
|
||||
{"Get":{"state":"func empty(a string) bool {\n« if a == \"\" {\n return true\n }\n return false\nˇ»}","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"func empty(a string) bool {\n if a == \"\" {\n ˇreturn true\n }\n return false\n}"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"{"}
|
||||
{"Get":{"state":"func empty(a string) bool {\n if a == \"\" {\n« return true\nˇ» }\n return false\n}","mode":{"Visual":{"line":false}}}}
|
@ -9,33 +9,39 @@
|
||||
{"Key":"j"}
|
||||
{"Key":"c"}
|
||||
{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
|
||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"j"}
|
||||
{"Key":"c"}
|
||||
{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
|
||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"k"}
|
||||
{"Key":"c"}
|
||||
{"Get":{"state":"The ˇrown\nfox jumps over\nthe lazy dog","mode":"Insert"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"j"}
|
||||
{"Key":"c"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps ˇhe lazy dog","mode":"Insert"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"k"}
|
||||
{"Key":"c"}
|
||||
{"Get":{"state":"The quick brown\nˇver\nthe lazy dog","mode":"Insert"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"j"}
|
||||
{"Key":"c"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇog","mode":"Insert"}}
|
||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"b"}
|
||||
{"Key":"k"}
|
||||
{"Key":"c"}
|
||||
{"Get":{"state":"ˇuick brown\nfox jumps over\nthe lazy dog","mode":"Insert"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"b"}
|
||||
{"Key":"k"}
|
||||
{"Key":"c"}
|
||||
{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"b"}
|
||||
{"Key":"w"}
|
||||
{"Key":"k"}
|
||||
{"Key":"c"}
|
||||
{"Get":{"state":"The quick brown\nˇazy dog","mode":"Insert"}}
|
||||
{"Get":{"state":"The quick brown\nfox jumpsˇazy dog","mode":"Insert"}}
|
||||
|
@ -1,6 +1,10 @@
|
||||
{"Put":{"state":"The quick ˇbrown"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick «brownˇ»","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick ˇbrown"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"The quickˇ ","mode":"Normal"}}
|
||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
|
@ -29,3 +29,8 @@
|
||||
{"Key":"j"}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"The quick brown\nfox juˇmps over","mode":"Normal"}}
|
||||
{"Put":{"state":"The ˇlong line\nshould not\ncrash\n"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"$"}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"should noˇt\ncrash\n","mode":"Normal"}}
|
||||
|
@ -1,230 +1,236 @@
|
||||
{"Put":{"state":"The quick ˇbrown\nfox"}}
|
||||
{"Key":"v"}
|
||||
{"Get":{"state":"The quick «bˇ»rown\nfox","mode":{"Visual":{"line":false}}}}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick «brownˇ»\nfox","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick «browˇ»n \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick «browˇ»n \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown« ˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown« ˇ»\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpˇ»s over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpˇ»s over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps« ˇ»over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ» \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ»\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n«\nˇ»\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n«\nˇ»\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n«\nˇ»The-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«Thˇ»e-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«Theˇ»-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe«-ˇ»quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick« ˇ»brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick «browˇ»n \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick «brownˇ» \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown« ˇ»\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ» \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ»\n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ» \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ»\n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ» fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ»fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-«jumpˇ»s over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-«jumpsˇ» over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog« ˇ»\n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n«\nˇ»","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick «browˇ»n \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick «browˇ»n \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown« ˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown« ˇ»\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpˇ»s over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpˇ»s over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps« ˇ»over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ» \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ»\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n«\nˇ»\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n«\nˇ»\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n«\nˇ»The-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick« ˇ»brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick «browˇ»n \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick «brownˇ» \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown« ˇ»\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ» \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ»\n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ» \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ»\n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ» fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ»fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n «fox-jumpˇ»s over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n «fox-jumpsˇ» over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog« ˇ»\n\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n«\nˇ»","mode":{"Visual":{"line":false}}}}
|
||||
|
@ -497,9 +497,8 @@ impl View for PanelButtons {
|
||||
};
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self, _>::new(panel_ix, cx, |state, cx| {
|
||||
MouseEventHandler::new::<Self, _>(panel_ix, cx, |state, cx| {
|
||||
let style = button_style.in_state(is_active);
|
||||
|
||||
let style = style.style_for(state);
|
||||
Flex::row()
|
||||
.with_child(
|
||||
|
@ -290,7 +290,7 @@ pub mod simple_message_notification {
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
@ -319,7 +319,7 @@ pub mod simple_message_notification {
|
||||
.with_children({
|
||||
click_message
|
||||
.map(|click_message| {
|
||||
MouseEventHandler::<MessageNotificationTag, _>::new(
|
||||
MouseEventHandler::new::<MessageNotificationTag, _>(
|
||||
0,
|
||||
cx,
|
||||
|state, _| {
|
||||
|
@ -234,7 +234,7 @@ fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
|
||||
action_name: &str,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
) -> AnyElement<Pane> {
|
||||
MouseEventHandler::<A, _>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::new::<A, _>(0, cx, |state, _| {
|
||||
let style = if enabled {
|
||||
style.style_for(state)
|
||||
} else {
|
||||
@ -1317,7 +1317,7 @@ impl Pane {
|
||||
|
||||
enum Tab {}
|
||||
let mouse_event_handler =
|
||||
MouseEventHandler::<Tab, Pane>::new(ix, cx, |_, cx| {
|
||||
MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
|
||||
Self::render_tab(
|
||||
&item,
|
||||
pane.clone(),
|
||||
@ -1526,7 +1526,7 @@ impl Pane {
|
||||
let item_id = item.id();
|
||||
enum TabCloseButton {}
|
||||
let icon = Svg::new("icons/x_mark_8.svg");
|
||||
MouseEventHandler::<TabCloseButton, _>::new(item_id, cx, |mouse_state, _| {
|
||||
MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
|
||||
if mouse_state.hovered() {
|
||||
icon.with_color(tab_style.icon_close_active)
|
||||
} else {
|
||||
@ -1591,7 +1591,7 @@ impl Pane {
|
||||
) -> AnyElement<Pane> {
|
||||
enum TabBarButton {}
|
||||
|
||||
let mut button = MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
|
||||
let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
|
||||
let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
|
||||
let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
|
||||
Svg::new(icon)
|
||||
@ -1653,7 +1653,7 @@ impl View for Pane {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
enum MouseNavigationHandler {}
|
||||
|
||||
MouseEventHandler::<MouseNavigationHandler, _>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
|
||||
let active_item_index = self.active_item_index;
|
||||
|
||||
if let Some(active_item) = self.active_item() {
|
||||
@ -1665,7 +1665,7 @@ impl View for Pane {
|
||||
|
||||
enum TabBarEventHandler {}
|
||||
stack.add_child(
|
||||
MouseEventHandler::<TabBarEventHandler, _>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
|
||||
Empty::new()
|
||||
.contained()
|
||||
.with_style(theme.workspace.tab_bar.container)
|
||||
|
@ -19,7 +19,7 @@ pub fn dragged_item_receiver<Tag, D, F>(
|
||||
split_margin: Option<f32>,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
render_child: F,
|
||||
) -> MouseEventHandler<Tag, Pane>
|
||||
) -> MouseEventHandler<Pane>
|
||||
where
|
||||
Tag: 'static,
|
||||
D: Element<Pane>,
|
||||
@ -39,7 +39,7 @@ where
|
||||
None
|
||||
};
|
||||
|
||||
let mut handler = MouseEventHandler::<Tag, _>::above(region_id, cx, |state, cx| {
|
||||
let mut handler = MouseEventHandler::above::<Tag, _>(region_id, cx, |state, cx| {
|
||||
// Observing hovered will cause a render when the mouse enters regardless
|
||||
// of if mouse position was accessed before
|
||||
let drag_position = if state.hovered() { drag_position } else { None };
|
||||
|
@ -212,7 +212,7 @@ impl Member {
|
||||
let leader_user_id = leader.user.id;
|
||||
let app_state = Arc::downgrade(app_state);
|
||||
Some(
|
||||
MouseEventHandler::<FollowIntoExternalProject, _>::new(
|
||||
MouseEventHandler::new::<FollowIntoExternalProject, _>(
|
||||
pane.id(),
|
||||
cx,
|
||||
|_, _| {
|
||||
|
@ -72,7 +72,7 @@ impl View for SharedScreen {
|
||||
enum Focus {}
|
||||
|
||||
let frame = self.frame.clone();
|
||||
MouseEventHandler::<Focus, _>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
|
||||
Canvas::new(move |scene, bounds, _, _, _| {
|
||||
if let Some(frame) = frame.clone() {
|
||||
let size = constrain_size_preserving_aspect_ratio(
|
||||
|
@ -143,6 +143,61 @@ impl View for Toolbar {
|
||||
}
|
||||
}
|
||||
|
||||
// <<<<<<< HEAD
|
||||
// =======
|
||||
// #[allow(clippy::too_many_arguments)]
|
||||
// fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>)>(
|
||||
// svg_path: &'static str,
|
||||
// style: theme::Interactive<theme::IconButton>,
|
||||
// nav_button_height: f32,
|
||||
// tooltip_style: TooltipStyle,
|
||||
// enabled: bool,
|
||||
// spacing: f32,
|
||||
// on_click: F,
|
||||
// tooltip_action: A,
|
||||
// action_name: &'static str,
|
||||
// cx: &mut ViewContext<Toolbar>,
|
||||
// ) -> AnyElement<Toolbar> {
|
||||
// MouseEventHandler::new::<A, _>(0, cx, |state, _| {
|
||||
// let style = if enabled {
|
||||
// style.style_for(state)
|
||||
// } else {
|
||||
// style.disabled_style()
|
||||
// };
|
||||
// Svg::new(svg_path)
|
||||
// .with_color(style.color)
|
||||
// .constrained()
|
||||
// .with_width(style.icon_width)
|
||||
// .aligned()
|
||||
// .contained()
|
||||
// .with_style(style.container)
|
||||
// .constrained()
|
||||
// .with_width(style.button_width)
|
||||
// .with_height(nav_button_height)
|
||||
// .aligned()
|
||||
// .top()
|
||||
// })
|
||||
// .with_cursor_style(if enabled {
|
||||
// CursorStyle::PointingHand
|
||||
// } else {
|
||||
// CursorStyle::default()
|
||||
// })
|
||||
// .on_click(MouseButton::Left, move |_, toolbar, cx| {
|
||||
// on_click(toolbar, cx)
|
||||
// })
|
||||
// .with_tooltip::<A>(
|
||||
// 0,
|
||||
// action_name,
|
||||
// Some(Box::new(tooltip_action)),
|
||||
// tooltip_style,
|
||||
// cx,
|
||||
// )
|
||||
// .contained()
|
||||
// .with_margin_right(spacing)
|
||||
// .into_any_named("nav button")
|
||||
// }
|
||||
|
||||
// >>>>>>> 139cbbfd3aebd0863a7d51b0c12d748764cf0b2e
|
||||
impl Toolbar {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
@ -2560,7 +2560,7 @@ impl Workspace {
|
||||
};
|
||||
|
||||
enum TitleBar {}
|
||||
MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
|
||||
Stack::new()
|
||||
.with_children(
|
||||
self.titlebar_item
|
||||
@ -2649,7 +2649,7 @@ impl Workspace {
|
||||
if self.project.read(cx).is_read_only() {
|
||||
enum DisconnectedOverlay {}
|
||||
Some(
|
||||
MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
|
||||
let theme = &theme::current(cx);
|
||||
Label::new(
|
||||
"Your connection to the remote project has been lost.",
|
||||
|
@ -1,5 +1,5 @@
|
||||
name = "C++"
|
||||
path_suffixes = ["cc", "cpp", "h", "hpp"]
|
||||
path_suffixes = ["cc", "cpp", "h", "hpp", "cxx", "hxx", "inl"]
|
||||
line_comment = "// "
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
|
@ -1,5 +1,5 @@
|
||||
name = "JavaScript"
|
||||
path_suffixes = ["js", "jsx", "mjs"]
|
||||
path_suffixes = ["js", "jsx", "mjs", "cjs"]
|
||||
first_line_pattern = '^#!.*\bnode\b'
|
||||
line_comment = "// "
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,5 +1,5 @@
|
||||
name = "Python"
|
||||
path_suffixes = ["py", "pyi"]
|
||||
path_suffixes = ["py", "pyi", "mpy"]
|
||||
first_line_pattern = '^#!.*\bpython[0-9.]*\b'
|
||||
line_comment = "# "
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,5 +1,5 @@
|
||||
name = "TypeScript"
|
||||
path_suffixes = ["ts"]
|
||||
path_suffixes = ["ts", "cts", "d.cts", "d.mts", "mts"]
|
||||
line_comment = "// "
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
|
Loading…
Reference in New Issue
Block a user