Cargo.lock 🔗
@@ -10132,6 +10132,7 @@ dependencies = [
"chrono",
"gpui2",
"itertools 0.11.0",
+ "menu2",
"rand 0.8.5",
"serde",
"settings2",
Kirill Bulatov created
Release Notes:
- N/A
Cargo.lock | 1
crates/terminal_view2/Cargo.toml | 1
crates/terminal_view2/src/terminal_element.rs | 62 +++++-----
crates/terminal_view2/src/terminal_view.rs | 115 ++++++++++++--------
crates/ui2/Cargo.toml | 1
crates/ui2/src/components/context_menu.rs | 47 ++++++-
crates/ui2/src/components/label.rs | 2
crates/ui2/src/components/list.rs | 30 ++++-
crates/zed2/Cargo.toml | 1
crates/zed2/src/main.rs | 1
10 files changed, 163 insertions(+), 98 deletions(-)
@@ -10132,6 +10132,7 @@ dependencies = [
"chrono",
"gpui2",
"itertools 0.11.0",
+ "menu2",
"rand 0.8.5",
"serde",
"settings2",
@@ -9,7 +9,6 @@ path = "src/terminal_view.rs"
doctest = false
[dependencies]
-# context_menu = { package = "context_menu2", path = "../context_menu2" }
editor = { package = "editor2", path = "../editor2" }
language = { package = "language2", path = "../language2" }
gpui = { package = "gpui2", path = "../gpui2" }
@@ -1,8 +1,8 @@
// use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
// use gpui::{
-// AnyElement, AppContext, Bounds, Component, Element, HighlightStyle, Hsla, LayoutId, Line,
-// ModelContext, MouseButton, Pixels, Point, TextStyle, Underline, ViewContext, WeakModel,
-// WindowContext,
+// point, transparent_black, AnyElement, AppContext, Bounds, Component, CursorStyle, Element,
+// FontStyle, FontWeight, HighlightStyle, Hsla, LayoutId, Line, ModelContext, MouseButton,
+// Overlay, Pixels, Point, Quad, TextStyle, Underline, ViewContext, WeakModel, WindowContext,
// };
// use itertools::Itertools;
// use language::CursorShape;
@@ -23,6 +23,7 @@
// TerminalSize,
// };
// use theme::ThemeSettings;
+// use workspace::ElementId;
// use std::mem;
// use std::{fmt::Debug, ops::RangeInclusive};
@@ -130,23 +131,24 @@
// cx: &mut ViewContext<TerminalView>,
// ) {
// let position = {
-// let point = self.point;
-// vec2f(
-// (origin.x() + point.column as f32 * layout.size.cell_width).floor(),
-// origin.y() + point.line as f32 * layout.size.line_height,
+// let alac_point = self.point;
+// point(
+// (origin.x + alac_point.column as f32 * layout.size.cell_width).floor(),
+// origin.y + alac_point.line as f32 * layout.size.line_height,
// )
// };
-// let size = vec2f(
+// let size = point(
// (layout.size.cell_width * self.num_of_cells as f32).ceil(),
// layout.size.line_height,
-// );
+// )
+// .into();
// cx.paint_quad(
// Bounds::new(position, size),
// Default::default(),
// self.color,
// Default::default(),
-// Default::default(),
+// transparent_black(),
// );
// }
// }
@@ -281,9 +283,9 @@
// cursor_point: DisplayCursor,
// size: TerminalSize,
// text_fragment: &Line,
-// ) -> Option<(Vector2F, f32)> {
+// ) -> Option<(Point<Pixels>, Pixels)> {
// if cursor_point.line() < size.total_lines() as i32 {
-// let cursor_width = if text_fragment.width == 0. {
+// let cursor_width = if text_fragment.width == Pixels::ZERO {
// size.cell_width()
// } else {
// text_fragment.width
@@ -292,7 +294,7 @@
// //Cursor should always surround as much of the text as possible,
// //hence when on pixel boundaries round the origin down and the width up
// Some((
-// vec2f(
+// point(
// (cursor_point.col() as f32 * size.cell_width()).floor(),
// (cursor_point.line() as f32 * size.line_height()).floor(),
// ),
@@ -332,15 +334,15 @@
// let mut properties = Properties::new();
// if indexed.flags.intersects(Flags::BOLD | Flags::DIM_BOLD) {
-// properties = *properties.weight(Weight::BOLD);
+// properties = *properties.weight(FontWeight::BOLD);
// }
// if indexed.flags.intersects(Flags::ITALIC) {
-// properties = *properties.style(Italic);
+// properties = *properties.style(FontStyle::Italic);
// }
// let font_id = font_cache
-// .select_font(text_style.font_family_id, &properties)
-// .unwrap_or(8text_style.font_id);
+// .select_font(text_style.font_family, &properties)
+// .unwrap_or(text_style.font_id);
// let mut result = RunStyle {
// color: fg,
@@ -366,7 +368,7 @@
// fn generic_button_handler<E>(
// connection: WeakModel<Terminal>,
// origin: Point<Pixels>,
-// f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext<Terminal>),
+// f: impl Fn(&mut Terminal, Point<Pixels>, E, &mut ModelContext<Terminal>),
// ) -> impl Fn(E, &mut TerminalView, &mut EventContext<TerminalView>) {
// move |event, _: &mut TerminalView, cx| {
// cx.focus_parent();
@@ -522,9 +524,9 @@
// fn layout(
// &mut self,
// view_state: &mut TerminalView,
-// element_state: &mut Self::ElementState,
+// element_state: Option<Self::ElementState>,
// cx: &mut ViewContext<TerminalView>,
-// ) -> LayoutId {
+// ) -> (LayoutId, Self::ElementState) {
// let settings = ThemeSettings::get_global(cx);
// let terminal_settings = TerminalSettings::get_global(cx);
@@ -569,7 +571,7 @@
// let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size);
// gutter = cell_width;
-// let size = constraint.max - vec2f(gutter, 0.);
+// let size = constraint.max - point(gutter, 0.);
// TerminalSize::new(line_height, cell_width, size)
// };
@@ -607,11 +609,11 @@
// cx,
// ),
// )
-// .with_position_mode(gpui::elements::OverlayPositionMode::Local)
+// .with_position_mode(gpui::OverlayPositionMode::Local)
// .into_any();
// tooltip.layout(
-// SizeConstraint::new(Vector2F::zero(), cx.window_size()),
+// SizeConstraint::new(Point::zero(), cx.window_size()),
// view_state,
// cx,
// );
@@ -735,7 +737,7 @@
// let clip_bounds = Some(visible_bounds);
// cx.paint_layer(clip_bounds, |cx| {
-// let origin = bounds.origin() + vec2f(element_state.gutter, 0.);
+// let origin = bounds.origin + point(element_state.gutter, 0.);
// // Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
// self.attach_mouse_handlers(origin, visible_bounds, element_state.mode, cx);
@@ -808,7 +810,7 @@
// });
// }
-// fn id(&self) -> Option<gpui::ElementId> {
+// fn element_id(&self) -> Option<ElementId> {
// todo!()
// }
@@ -842,12 +844,12 @@
// // ) -> Option<Bounds<Pixels>> {
// // // Use the same origin that's passed to `Cursor::paint` in the paint
// // // method bove.
-// // let mut origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
+// // let mut origin = bounds.origin() + point(layout.size.cell_width, 0.);
// // // TODO - Why is it necessary to move downward one line to get correct
// // // positioning? I would think that we'd want the same rect that is
// // // painted for the cursor.
-// // origin += vec2f(0., layout.size.line_height);
+// // origin += point(0., layout.size.line_height);
// // Some(layout.cursor.as_ref()?.bounding_rect(origin))
// // }
@@ -886,7 +888,7 @@
// range: &RangeInclusive<AlacPoint>,
// layout: &LayoutState,
// origin: Point<Pixels>,
-// ) -> Option<(f32, Vec<HighlightedRangeLine>)> {
+// ) -> Option<(Pixels, Vec<HighlightedRangeLine>)> {
// // Step 1. Normalize the points to be viewport relative.
// // When display_offset = 1, here's how the grid is arranged:
// //-2,0 -2,1...
@@ -937,8 +939,8 @@
// }
// highlighted_range_lines.push(HighlightedRangeLine {
-// start_x: origin.x() + line_start as f32 * layout.size.cell_width,
-// end_x: origin.x() + line_end as f32 * layout.size.cell_width,
+// start_x: origin.x + line_start as f32 * layout.size.cell_width,
+// end_x: origin.x + line_end as f32 * layout.size.cell_width,
// });
// }
@@ -7,27 +7,17 @@ pub mod terminal_panel;
// todo!()
// use crate::terminal_element::TerminalElement;
-use anyhow::Context;
-use dirs::home_dir;
use editor::{scroll::autoscroll::Autoscroll, Editor};
use gpui::{
actions, div, img, red, register_action, AnyElement, AppContext, Component, DispatchPhase, Div,
EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView,
- InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, ParentComponent, Pixels,
- Render, SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView,
+ InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, MouseButton,
+ ParentComponent, Pixels, Render, SharedString, Styled, Task, View, ViewContext, VisualContext,
+ WeakView,
};
use language::Bias;
use persistence::TERMINAL_DB;
use project::{search::SearchQuery, LocalWorktree, Project};
-use serde::Deserialize;
-use settings::Settings;
-use smol::Timer;
-use std::{
- ops::RangeInclusive,
- path::{Path, PathBuf},
- sync::Arc,
- time::Duration,
-};
use terminal::{
alacritty_terminal::{
index::Point,
@@ -42,7 +32,21 @@ use workspace::{
notifications::NotifyResultExt,
register_deserializable_item,
searchable::{SearchEvent, SearchOptions, SearchableItem},
- NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
+ ui::{ContextMenu, ContextMenuItem, Label},
+ CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
+};
+
+use anyhow::Context;
+use dirs::home_dir;
+use serde::Deserialize;
+use settings::Settings;
+use smol::Timer;
+
+use std::{
+ ops::RangeInclusive,
+ path::{Path, PathBuf},
+ sync::Arc,
+ time::Duration,
};
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
@@ -62,6 +66,7 @@ pub struct SendKeystroke(String);
actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest);
pub fn init(cx: &mut AppContext) {
+ workspace::ui::init(cx);
terminal_panel::init(cx);
terminal::init(cx);
@@ -82,7 +87,7 @@ pub struct TerminalView {
has_new_content: bool,
//Currently using iTerm bell, show bell emoji in tab until input is received
has_bell: bool,
- // context_menu: View<ContextMenu>,
+ context_menu: Option<ContextMenu>,
blink_state: bool,
blinking_on: bool,
blinking_paused: bool,
@@ -265,8 +270,7 @@ impl TerminalView {
has_new_content: true,
has_bell: false,
focus_handle: cx.focus_handle(),
- // todo!()
- // context_menu: cx.build_view(|cx| ContextMenu::new(view_id, cx)),
+ context_menu: None,
blink_state: true,
blinking_on: false,
blinking_paused: false,
@@ -293,18 +297,20 @@ impl TerminalView {
cx.emit(Event::Wakeup);
}
- pub fn deploy_context_menu(&mut self, _position: Point<Pixels>, _cx: &mut ViewContext<Self>) {
- //todo!(context_menu)
- // let menu_entries = vec![
- // ContextMenuItem::action("Clear", Clear),
- // ContextMenuItem::action("Close", pane::CloseActiveItem { save_intent: None }),
- // ];
-
- // self.context_menu.update(cx, |menu, cx| {
- // menu.show(position, AnchorCorner::TopLeft, menu_entries, cx)
- // });
-
- // cx.notify();
+ pub fn deploy_context_menu(
+ &mut self,
+ position: gpui::Point<Pixels>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.context_menu = Some(ContextMenu::new(vec![
+ ContextMenuItem::entry(Label::new("Clear"), Clear),
+ ContextMenuItem::entry(Label::new("Close"), CloseActiveItem { save_intent: None }),
+ ]));
+ dbg!(&position);
+ // todo!()
+ // self.context_menu
+ // .show(position, AnchorCorner::TopLeft, menu_entries, cx);
+ // cx.notify();
}
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
@@ -541,28 +547,41 @@ impl Render for TerminalView {
let focused = self.focus_handle.is_focused(cx);
div()
+ .relative()
+ .child(
+ div()
+ .z_index(0)
+ .absolute()
+ .on_key_down(Self::key_down)
+ .on_action(TerminalView::send_text)
+ .on_action(TerminalView::send_keystroke)
+ .on_action(TerminalView::copy)
+ .on_action(TerminalView::paste)
+ .on_action(TerminalView::clear)
+ .on_action(TerminalView::show_character_palette)
+ .on_action(TerminalView::select_all)
+ // todo!()
+ .child(
+ "TERMINAL HERE", // TerminalElement::new(
+ // terminal_handle,
+ // focused,
+ // self.should_show_cursor(focused, cx),
+ // self.can_navigate_to_selected_word,
+ // )
+ )
+ .on_mouse_down(MouseButton::Right, |this, event, cx| {
+ this.deploy_context_menu(event.position, cx);
+ cx.notify();
+ }),
+ )
+ .children(
+ self.context_menu
+ .clone()
+ .map(|context_menu| div().z_index(1).absolute().child(context_menu.render())),
+ )
.track_focus(&self.focus_handle)
.on_focus_in(Self::focus_in)
.on_focus_out(Self::focus_out)
- .on_key_down(Self::key_down)
- .on_action(TerminalView::send_text)
- .on_action(TerminalView::send_keystroke)
- .on_action(TerminalView::copy)
- .on_action(TerminalView::paste)
- .on_action(TerminalView::clear)
- .on_action(TerminalView::show_character_palette)
- .on_action(TerminalView::select_all)
- // todo!()
- .child(
- "TERMINAL HERE", // TerminalElement::new(
- // terminal_handle,
- // focused,
- // self.should_show_cursor(focused, cx),
- // self.can_navigate_to_selected_word,
- // )
- )
- // todo!()
- // .child(ChildView::new(&self.context_menu, cx))
}
}
@@ -9,6 +9,7 @@ anyhow.workspace = true
chrono = "0.4"
gpui = { package = "gpui2", path = "../gpui2" }
itertools = { version = "0.11.0", optional = true }
+menu = { package = "menu2", path = "../menu2"}
serde.workspace = true
settings2 = { path = "../settings2" }
smallvec.workspace = true
@@ -3,17 +3,29 @@ use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHea
pub enum ContextMenuItem {
Header(SharedString),
- Entry(Label),
+ Entry(Label, Box<dyn gpui::Action>),
Separator,
}
+impl Clone for ContextMenuItem {
+ fn clone(&self) -> Self {
+ match self {
+ ContextMenuItem::Header(name) => ContextMenuItem::Header(name.clone()),
+ ContextMenuItem::Entry(label, action) => {
+ ContextMenuItem::Entry(label.clone(), action.boxed_clone())
+ }
+ ContextMenuItem::Separator => ContextMenuItem::Separator,
+ }
+ }
+}
impl ContextMenuItem {
fn to_list_item<V: 'static>(self) -> ListItem {
match self {
ContextMenuItem::Header(label) => ListSubHeader::new(label).into(),
- ContextMenuItem::Entry(label) => {
- ListEntry::new(label).variant(ListItemVariant::Inset).into()
- }
+ ContextMenuItem::Entry(label, action) => ListEntry::new(label)
+ .variant(ListItemVariant::Inset)
+ .on_click(action)
+ .into(),
ContextMenuItem::Separator => ListSeparator::new().into(),
}
}
@@ -26,12 +38,12 @@ impl ContextMenuItem {
Self::Separator
}
- pub fn entry(label: Label) -> Self {
- Self::Entry(label)
+ pub fn entry(label: Label, action: impl Action) -> Self {
+ Self::Entry(label, Box::new(action))
}
}
-#[derive(Component)]
+#[derive(Component, Clone)]
pub struct ContextMenu {
items: Vec<ContextMenuItem>,
}
@@ -42,7 +54,12 @@ impl ContextMenu {
items: items.into_iter().collect(),
}
}
-
+ // todo!()
+ // cx.add_action(ContextMenu::select_first);
+ // cx.add_action(ContextMenu::select_last);
+ // cx.add_action(ContextMenu::select_next);
+ // cx.add_action(ContextMenu::select_prev);
+ // cx.add_action(ContextMenu::confirm);
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
v_stack()
.flex()
@@ -55,9 +72,11 @@ impl ContextMenu {
.map(ContextMenuItem::to_list_item::<V>)
.collect(),
))
+ .on_mouse_down_out(|_, _, cx| cx.dispatch_action(Box::new(menu::Cancel)))
}
}
+use gpui::Action;
#[cfg(feature = "stories")]
pub use stories::*;
@@ -65,7 +84,7 @@ pub use stories::*;
mod stories {
use super::*;
use crate::story::Story;
- use gpui::{Div, Render};
+ use gpui::{action, Div, Render};
pub struct ContextMenuStory;
@@ -73,14 +92,22 @@ mod stories {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ #[action]
+ struct PrintCurrentDate {}
+
Story::container(cx)
.child(Story::title_for::<_, ContextMenu>(cx))
.child(Story::label(cx, "Default"))
.child(ContextMenu::new([
ContextMenuItem::header("Section header"),
ContextMenuItem::Separator,
- ContextMenuItem::entry(Label::new("Some entry")),
+ ContextMenuItem::entry(Label::new("Print current time"), PrintCurrentDate {}),
]))
+ .on_action(|_, _: &PrintCurrentDate, _| {
+ if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {
+ println!("Current Unix time is {:?}", unix_time.as_secs());
+ }
+ })
}
}
}
@@ -60,7 +60,7 @@ pub enum LineHeightStyle {
UILabel,
}
-#[derive(Component)]
+#[derive(Clone, Component)]
pub struct Label {
label: SharedString,
size: LabelSize,
@@ -1,11 +1,10 @@
-use gpui::div;
+use gpui::{div, Action};
-use crate::prelude::*;
use crate::settings::user_settings;
use crate::{
- disclosure_control, h_stack, v_stack, Avatar, GraphicSlot, Icon, IconElement, IconSize, Label,
- TextColor, Toggle,
+ disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle,
};
+use crate::{prelude::*, GraphicSlot};
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum ListItemVariant {
@@ -232,6 +231,7 @@ pub struct ListEntry {
size: ListEntrySize,
toggle: Toggle,
variant: ListItemVariant,
+ on_click: Option<Box<dyn Action>>,
}
impl ListEntry {
@@ -245,9 +245,15 @@ impl ListEntry {
size: ListEntrySize::default(),
toggle: Toggle::NotToggleable,
variant: ListItemVariant::default(),
+ on_click: Default::default(),
}
}
+ pub fn on_click(mut self, action: impl Into<Box<dyn Action>>) -> Self {
+ self.on_click = Some(action.into());
+ self
+ }
+
pub fn variant(mut self, variant: ListItemVariant) -> Self {
self.variant = variant;
self
@@ -303,9 +309,21 @@ impl ListEntry {
ListEntrySize::Small => div().h_6(),
ListEntrySize::Medium => div().h_7(),
};
-
div()
.relative()
+ .hover(|mut style| {
+ style.background = Some(cx.theme().colors().editor_background.into());
+ style
+ })
+ .on_mouse_down(gpui::MouseButton::Left, {
+ let action = self.on_click.map(|action| action.boxed_clone());
+
+ move |entry: &mut V, event, cx| {
+ if let Some(action) = action.as_ref() {
+ cx.dispatch_action(action.boxed_clone());
+ }
+ }
+ })
.group("")
.bg(cx.theme().colors().surface_background)
// TODO: Add focus state
@@ -401,7 +419,7 @@ impl List {
v_stack()
.w_full()
.py_1()
- .children(self.header)
+ .children(self.header.map(|header| header))
.child(list_content)
}
}
@@ -27,7 +27,6 @@ collab_ui = { package = "collab_ui2", path = "../collab_ui2" }
collections = { path = "../collections" }
command_palette = { package="command_palette2", path = "../command_palette2" }
# component_test = { path = "../component_test" }
-# context_menu = { path = "../context_menu" }
client = { package = "client2", path = "../client2" }
# clock = { path = "../clock" }
copilot = { package = "copilot2", path = "../copilot2" }
@@ -141,7 +141,6 @@ fn main() {
cx.set_global(client.clone());
theme::init(cx);
- // context_menu::init(cx);
project::Project::init(&client, cx);
client::init(&client, cx);
command_palette::init(cx);