Merge branch 'main' into splits

This commit is contained in:
Mikayla
2023-12-11 10:16:32 -08:00
137 changed files with 16769 additions and 11835 deletions
+1 -1
View File
@@ -770,7 +770,7 @@ pub mod test {
pub focus_handle: FocusHandle,
pub size: f32,
}
actions!(ToggleTestPanel);
actions!(test, [ToggleTestPanel]);
impl EventEmitter<PanelEvent> for TestPanel {}
+51 -17
View File
@@ -1,11 +1,32 @@
use gpui::{
div, prelude::*, px, AnyView, Div, FocusHandle, ManagedView, Render, Subscription, View,
ViewContext,
div, prelude::*, px, AnyView, Div, FocusHandle, ManagedView, Render, Subscription, Task, View,
ViewContext, WindowContext,
};
use ui::{h_stack, v_stack};
pub trait ModalView: ManagedView {
fn dismiss(&mut self, cx: &mut ViewContext<Self>) -> Task<bool> {
Task::ready(true)
}
}
trait ModalViewHandle {
fn should_dismiss(&mut self, cx: &mut WindowContext) -> Task<bool>;
fn view(&self) -> AnyView;
}
impl<V: ModalView> ModalViewHandle for View<V> {
fn should_dismiss(&mut self, cx: &mut WindowContext) -> Task<bool> {
self.update(cx, |this, cx| this.dismiss(cx))
}
fn view(&self) -> AnyView {
self.clone().into()
}
}
pub struct ActiveModal {
modal: AnyView,
modal: Box<dyn ModalViewHandle>,
subscription: Subscription,
previous_focus_handle: Option<FocusHandle>,
focus_handle: FocusHandle,
@@ -22,11 +43,11 @@ impl ModalLayer {
pub fn toggle_modal<V, B>(&mut self, cx: &mut ViewContext<Self>, build_view: B)
where
V: ManagedView,
V: ModalView,
B: FnOnce(&mut ViewContext<V>) -> V,
{
if let Some(active_modal) = &self.active_modal {
let is_close = active_modal.modal.clone().downcast::<V>().is_ok();
let is_close = active_modal.modal.view().downcast::<V>().is_ok();
self.hide_modal(cx);
if is_close {
return;
@@ -38,10 +59,10 @@ impl ModalLayer {
pub fn show_modal<V>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Self>)
where
V: ManagedView,
V: ModalView,
{
self.active_modal = Some(ActiveModal {
modal: new_modal.clone().into(),
modal: Box::new(new_modal.clone()),
subscription: cx.subscribe(&new_modal, |this, modal, e, cx| this.hide_modal(cx)),
previous_focus_handle: cx.focused(),
focus_handle: cx.focus_handle(),
@@ -51,15 +72,28 @@ impl ModalLayer {
}
pub fn hide_modal(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_modal) = self.active_modal.take() {
if let Some(previous_focus) = active_modal.previous_focus_handle {
if active_modal.focus_handle.contains_focused(cx) {
previous_focus.focus(cx);
}
}
}
let Some(active_modal) = self.active_modal.as_mut() else {
return;
};
cx.notify();
let dismiss = active_modal.modal.should_dismiss(cx);
cx.spawn(|this, mut cx| async move {
if dismiss.await {
this.update(&mut cx, |this, cx| {
if let Some(active_modal) = this.active_modal.take() {
if let Some(previous_focus) = active_modal.previous_focus_handle {
if active_modal.focus_handle.contains_focused(cx) {
previous_focus.focus(cx);
}
}
cx.notify();
}
})
.ok();
}
})
.detach();
}
pub fn active_modal<V>(&self) -> Option<View<V>>
@@ -67,7 +101,7 @@ impl ModalLayer {
V: 'static,
{
let active_modal = self.active_modal.as_ref()?;
active_modal.modal.clone().downcast::<V>().ok()
active_modal.modal.view().downcast::<V>().ok()
}
}
@@ -98,7 +132,7 @@ impl Render for ModalLayer {
.on_mouse_down_out(cx.listener(|this, _, cx| {
this.hide_modal(cx);
}))
.child(active_modal.modal.clone()),
.child(active_modal.modal.view()),
),
)
}
+199 -202
View File
@@ -7,10 +7,10 @@ use crate::{
use anyhow::Result;
use collections::{HashMap, HashSet, VecDeque};
use gpui::{
actions, overlay, prelude::*, rems, Action, AnchorCorner, AnyWeakView, AppContext,
AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable,
FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View, ViewContext,
VisualContext, WeakView, WindowContext,
actions, impl_actions, overlay, prelude::*, rems, Action, AnchorCorner, AnyWeakView,
AppContext, AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle,
Focusable, FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel,
Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
};
use parking_lot::Mutex;
use project::{Project, ProjectEntryId, ProjectPath};
@@ -52,9 +52,7 @@ pub enum SaveIntent {
Skip,
}
//todo!("Do we need the default bound on actions? Decide soon")
// #[register_action]
#[derive(Action, Clone, Deserialize, PartialEq, Debug)]
#[derive(Clone, Deserialize, PartialEq, Debug)]
pub struct ActivateItem(pub usize);
// #[derive(Clone, PartialEq)]
@@ -75,34 +73,38 @@ pub struct ActivateItem(pub usize);
// pub pane: WeakView<Pane>,
// }
#[derive(Clone, PartialEq, Debug, Deserialize, Default, Action)]
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CloseActiveItem {
pub save_intent: Option<SaveIntent>,
}
#[derive(Clone, PartialEq, Debug, Deserialize, Default, Action)]
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CloseAllItems {
pub save_intent: Option<SaveIntent>,
}
// todo!(These used to be under pane::{Action}. Are they now workspace::pane::{Action}?)
impl_actions!(pane, [CloseAllItems, CloseActiveItem, ActivateItem]);
actions!(
ActivatePrevItem,
ActivateNextItem,
ActivateLastItem,
CloseInactiveItems,
CloseCleanItems,
CloseItemsToTheLeft,
CloseItemsToTheRight,
GoBack,
GoForward,
ReopenClosedItem,
SplitLeft,
SplitUp,
SplitRight,
SplitDown,
pane,
[
ActivatePrevItem,
ActivateNextItem,
ActivateLastItem,
CloseInactiveItems,
CloseCleanItems,
CloseItemsToTheLeft,
CloseItemsToTheRight,
GoBack,
GoForward,
ReopenClosedItem,
SplitLeft,
SplitUp,
SplitRight,
SplitDown,
]
);
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
@@ -157,6 +159,7 @@ pub struct Pane {
items: Vec<Box<dyn ItemHandle>>,
activation_history: Vec<EntityId>,
zoomed: bool,
was_focused: bool,
active_item_index: usize,
last_focused_view_by_item: HashMap<EntityId, FocusHandle>,
autoscroll: bool,
@@ -315,6 +318,7 @@ impl Pane {
focus_handle: cx.focus_handle(),
items: Vec::new(),
activation_history: Vec::new(),
was_focused: false,
zoomed: false,
active_item_index: 0,
last_focused_view_by_item: Default::default(),
@@ -411,7 +415,8 @@ impl Pane {
}
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
if !self.has_focus(cx) {
if !self.was_focused {
self.was_focused = true;
cx.emit(Event::Focus);
cx.notify();
}
@@ -442,6 +447,7 @@ impl Pane {
}
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
self.was_focused = false;
self.toolbar.update(cx, |toolbar, cx| {
toolbar.focus_changed(false, cx);
});
@@ -2140,183 +2146,174 @@ impl Render for Pane {
.justify_center()
.child(Label::new("Open a file or project to get started.").color(Color::Muted))
})
// enum MouseNavigationHandler {}
// MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
// let active_item_index = self.active_item_index;
// if let Some(active_item) = self.active_item() {
// Flex::column()
// .with_child({
// let theme = theme::current(cx).clone();
// let mut stack = Stack::new();
// enum TabBarEventHandler {}
// stack.add_child(
// MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
// Empty::new()
// .contained()
// .with_style(theme.workspace.tab_bar.container)
// })
// .on_down(
// MouseButton::Left,
// move |_, this, cx| {
// this.activate_item(active_item_index, true, true, cx);
// },
// ),
// );
// let tooltip_style = theme.tooltip.clone();
// let tab_bar_theme = theme.workspace.tab_bar.clone();
// let nav_button_height = tab_bar_theme.height;
// let button_style = tab_bar_theme.nav_button;
// let border_for_nav_buttons = tab_bar_theme
// .tab_style(false, false)
// .container
// .border
// .clone();
// let mut tab_row = Flex::row()
// .with_child(nav_button(
// "icons/arrow_left.svg",
// button_style.clone(),
// nav_button_height,
// tooltip_style.clone(),
// self.can_navigate_backward(),
// {
// move |pane, cx| {
// if let Some(workspace) = pane.workspace.upgrade(cx) {
// let pane = cx.weak_handle();
// cx.window_context().defer(move |cx| {
// workspace.update(cx, |workspace, cx| {
// workspace
// .go_back(pane, cx)
// .detach_and_log_err(cx)
// })
// })
// }
// }
// },
// super::GoBack,
// "Go Back",
// cx,
// ))
// .with_child(
// nav_button(
// "icons/arrow_right.svg",
// button_style.clone(),
// nav_button_height,
// tooltip_style,
// self.can_navigate_forward(),
// {
// move |pane, cx| {
// if let Some(workspace) = pane.workspace.upgrade(cx) {
// let pane = cx.weak_handle();
// cx.window_context().defer(move |cx| {
// workspace.update(cx, |workspace, cx| {
// workspace
// .go_forward(pane, cx)
// .detach_and_log_err(cx)
// })
// })
// }
// }
// },
// super::GoForward,
// "Go Forward",
// cx,
// )
// .contained()
// .with_border(border_for_nav_buttons),
// )
// .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
// if self.has_focus {
// let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
// tab_row.add_child(
// (render_tab_bar_buttons)(self, cx)
// .contained()
// .with_style(theme.workspace.tab_bar.pane_button_container)
// .flex(1., false)
// .into_any(),
// )
// }
// stack.add_child(tab_row);
// stack
// .constrained()
// .with_height(theme.workspace.tab_bar.height)
// .flex(1., false)
// .into_any_named("tab bar")
// })
// .with_child({
// enum PaneContentTabDropTarget {}
// dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
// self,
// 0,
// self.active_item_index + 1,
// !self.can_split,
// if self.can_split { Some(100.) } else { None },
// cx,
// {
// let toolbar = self.toolbar.clone();
// let toolbar_hidden = toolbar.read(cx).hidden();
// move |_, cx| {
// Flex::column()
// .with_children(
// (!toolbar_hidden)
// .then(|| ChildView::new(&toolbar, cx).expanded()),
// )
// .with_child(
// ChildView::new(active_item.as_any(), cx).flex(1., true),
// )
// }
// },
// )
// .flex(1., true)
// })
// .with_child(ChildView::new(&self.tab_context_menu, cx))
// .into_any()
// } else {
// enum EmptyPane {}
// let theme = theme::current(cx).clone();
// dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
// self.render_blank_pane(&theme, cx)
// })
// .on_down(MouseButton::Left, |_, _, cx| {
// cx.focus_parent();
// })
// .into_any()
// }
// })
// .on_down(
// MouseButton::Navigate(NavigationDirection::Back),
// move |_, pane, cx| {
// if let Some(workspace) = pane.workspace.upgrade(cx) {
// let pane = cx.weak_handle();
// cx.window_context().defer(move |cx| {
// workspace.update(cx, |workspace, cx| {
// workspace.go_back(pane, cx).detach_and_log_err(cx)
// })
// })
// }
// },
// )
// .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
// move |_, pane, cx| {
// if let Some(workspace) = pane.workspace.upgrade(cx) {
// let pane = cx.weak_handle();
// cx.window_context().defer(move |cx| {
// workspace.update(cx, |workspace, cx| {
// workspace.go_forward(pane, cx).detach_and_log_err(cx)
// })
// })
// }
// }
// })
// enum MouseNavigationHandler {}
// MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
// let active_item_index = self.active_item_index;
// if let Some(active_item) = self.active_item() {
// Flex::column()
// .with_child({
// let theme = theme::current(cx).clone();
// let mut stack = Stack::new();
// enum TabBarEventHandler {}
// stack.add_child(
// MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
// Empty::new()
// .contained()
// .with_style(theme.workspace.tab_bar.container)
// })
// .on_down(
// MouseButton::Left,
// move |_, this, cx| {
// this.activate_item(active_item_index, true, true, cx);
// },
// ),
// );
// let tooltip_style = theme.tooltip.clone();
// let tab_bar_theme = theme.workspace.tab_bar.clone();
// let nav_button_height = tab_bar_theme.height;
// let button_style = tab_bar_theme.nav_button;
// let border_for_nav_buttons = tab_bar_theme
// .tab_style(false, false)
// .container
// .border
// .clone();
// let mut tab_row = Flex::row()
// .with_child(nav_button(
// "icons/arrow_left.svg",
// button_style.clone(),
// nav_button_height,
// tooltip_style.clone(),
// self.can_navigate_backward(),
// {
// move |pane, cx| {
// if let Some(workspace) = pane.workspace.upgrade(cx) {
// let pane = cx.weak_handle();
// cx.window_context().defer(move |cx| {
// workspace.update(cx, |workspace, cx| {
// workspace
// .go_back(pane, cx)
// .detach_and_log_err(cx)
// })
// })
// }
// }
// },
// super::GoBack,
// "Go Back",
// cx,
// ))
// .with_child(
// nav_button(
// "icons/arrow_right.svg",
// button_style.clone(),
// nav_button_height,
// tooltip_style,
// self.can_navigate_forward(),
// {
// move |pane, cx| {
// if let Some(workspace) = pane.workspace.upgrade(cx) {
// let pane = cx.weak_handle();
// cx.window_context().defer(move |cx| {
// workspace.update(cx, |workspace, cx| {
// workspace
// .go_forward(pane, cx)
// .detach_and_log_err(cx)
// })
// })
// }
// }
// },
// super::GoForward,
// "Go Forward",
// cx,
// )
// .contained()
// .with_border(border_for_nav_buttons),
// )
// .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
// if self.has_focus {
// let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
// tab_row.add_child(
// (render_tab_bar_buttons)(self, cx)
// .contained()
// .with_style(theme.workspace.tab_bar.pane_button_container)
// .flex(1., false)
// .into_any(),
// )
// }
// stack.add_child(tab_row);
// stack
// .constrained()
// .with_height(theme.workspace.tab_bar.height)
// .flex(1., false)
// .into_any_named("tab bar")
// })
// .with_child({
// enum PaneContentTabDropTarget {}
// dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
// self,
// 0,
// self.active_item_index + 1,
// !self.can_split,
// if self.can_split { Some(100.) } else { None },
// cx,
// {
// let toolbar = self.toolbar.clone();
// let toolbar_hidden = toolbar.read(cx).hidden();
// move |_, cx| {
// Flex::column()
// .with_children(
// (!toolbar_hidden)
// .then(|| ChildView::new(&toolbar, cx).expanded()),
// )
// .with_child(
// ChildView::new(active_item.as_any(), cx).flex(1., true),
// )
// }
// },
// )
// .flex(1., true)
// })
// .with_child(ChildView::new(&self.tab_context_menu, cx))
// .into_any()
// } else {
// enum EmptyPane {}
// let theme = theme::current(cx).clone();
// dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
// self.render_blank_pane(&theme, cx)
// })
// .on_down(MouseButton::Left, |_, _, cx| {
// cx.focus_parent();
// })
// .into_any()
// }
// })
.on_mouse_down(
MouseButton::Navigate(NavigationDirection::Back),
cx.listener(|pane, _, cx| {
if let Some(workspace) = pane.workspace.upgrade() {
let pane = cx.view().downgrade();
cx.window_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
workspace.go_back(pane, cx).detach_and_log_err(cx)
})
})
}
}),
)
.on_mouse_down(
MouseButton::Navigate(NavigationDirection::Forward),
cx.listener(|pane, _, cx| {
if let Some(workspace) = pane.workspace.upgrade() {
let pane = cx.view().downgrade();
cx.window_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
workspace.go_forward(pane, cx).detach_and_log_err(cx)
})
})
}
}),
)
// .into_any_named("pane")
}
+4 -29
View File
@@ -1,12 +1,10 @@
use std::any::TypeId;
use crate::{ItemHandle, Pane};
use gpui::{
div, AnyView, Div, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
WindowContext,
};
use ui::prelude::*;
use ui::{h_stack, Icon, IconButton};
use std::any::TypeId;
use ui::{h_stack, prelude::*};
use util::ResultExt;
pub trait StatusItemView: Render {
@@ -47,31 +45,8 @@ impl Render for StatusBar {
.w_full()
.h_8()
.bg(cx.theme().colors().status_bar_background)
.child(h_stack().gap_1().child(self.render_left_tools(cx)))
.child(
h_stack()
.gap_4()
.child(
h_stack().gap_1().child(
// Feedback Tool
div()
.border()
.border_color(gpui::red())
.child(IconButton::new("status-feedback", Icon::Envelope)),
),
)
.child(
// Right Dock
h_stack().gap_1().child(
// Terminal
div()
.border()
.border_color(gpui::red())
.child(IconButton::new("status-chat", Icon::MessageBubbles)),
),
)
.child(self.render_right_tools(cx)),
)
.child(self.render_left_tools(cx))
.child(self.render_right_tools(cx))
}
}
+56 -39
View File
@@ -29,12 +29,12 @@ use futures::{
Future, FutureExt, StreamExt,
};
use gpui::{
actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AnyWindowHandle, AppContext,
AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter,
FocusHandle, FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model,
ModelContext, ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled,
Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
WindowHandle, WindowOptions,
actions, div, impl_actions, point, size, Action, AnyModel, AnyView, AnyWeakView,
AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity,
EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels, InteractiveElement,
KeyContext, ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Point,
PromptLevel, Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext,
WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
use itertools::Itertools;
@@ -91,30 +91,33 @@ lazy_static! {
pub struct RemoveWorktreeFromProject(pub WorktreeId);
actions!(
Open,
NewFile,
NewWindow,
CloseWindow,
CloseInactiveTabsAndPanes,
AddFolderToProject,
Unfollow,
SaveAs,
ReloadActiveItem,
ActivatePreviousPane,
ActivateNextPane,
FollowNextCollaborator,
NewTerminal,
NewCenterTerminal,
ToggleTerminalFocus,
NewSearch,
Feedback,
Restart,
Welcome,
ToggleZoom,
ToggleLeftDock,
ToggleRightDock,
ToggleBottomDock,
CloseAllDocks,
workspace,
[
Open,
NewFile,
NewWindow,
CloseWindow,
CloseInactiveTabsAndPanes,
AddFolderToProject,
Unfollow,
SaveAs,
ReloadActiveItem,
ActivatePreviousPane,
ActivateNextPane,
FollowNextCollaborator,
NewTerminal,
NewCenterTerminal,
ToggleTerminalFocus,
NewSearch,
Feedback,
Restart,
Welcome,
ToggleZoom,
ToggleLeftDock,
ToggleRightDock,
ToggleBottomDock,
CloseAllDocks,
]
);
#[derive(Clone, PartialEq)]
@@ -122,36 +125,50 @@ pub struct OpenPaths {
pub paths: Vec<PathBuf>,
}
#[derive(Clone, Deserialize, PartialEq, Action)]
#[derive(Clone, Deserialize, PartialEq)]
pub struct ActivatePane(pub usize);
#[derive(Clone, Deserialize, PartialEq, Action)]
#[derive(Clone, Deserialize, PartialEq)]
pub struct ActivatePaneInDirection(pub SplitDirection);
#[derive(Clone, Deserialize, PartialEq, Action)]
#[derive(Clone, Deserialize, PartialEq)]
pub struct SwapPaneInDirection(pub SplitDirection);
#[derive(Clone, Deserialize, PartialEq, Action)]
#[derive(Clone, Deserialize, PartialEq)]
pub struct NewFileInDirection(pub SplitDirection);
#[derive(Clone, PartialEq, Debug, Deserialize, Action)]
#[derive(Clone, PartialEq, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SaveAll {
pub save_intent: Option<SaveIntent>,
}
#[derive(Clone, PartialEq, Debug, Deserialize, Action)]
#[derive(Clone, PartialEq, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Save {
pub save_intent: Option<SaveIntent>,
}
#[derive(Clone, PartialEq, Debug, Deserialize, Default, Action)]
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CloseAllItemsAndPanes {
pub save_intent: Option<SaveIntent>,
}
impl_actions!(
workspace,
[
ActivatePane,
ActivatePaneInDirection,
CloseAllItemsAndPanes,
NewFileInDirection,
OpenTerminal,
Save,
SaveAll,
SwapPaneInDirection,
]
);
#[derive(Deserialize)]
pub struct Toast {
id: usize,
@@ -197,7 +214,7 @@ impl Clone for Toast {
}
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Action)]
#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
pub struct OpenTerminal {
pub working_directory: PathBuf,
}
@@ -3414,7 +3431,7 @@ impl Workspace {
self.modal_layer.read(cx).active_modal()
}
pub fn toggle_modal<V: ManagedView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
where
B: FnOnce(&mut ViewContext<V>) -> V,
{