Get project compiling with type-safe actions
This commit is contained in:
parent
638b533fc7
commit
86effd64a2
13 changed files with 229 additions and 206 deletions
|
@ -132,7 +132,7 @@ impl AnyAction for () {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! action {
|
macro_rules! action {
|
||||||
($name:ident, $arg:ty) => {
|
($name:ident, $arg:ty) => {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct $name(pub $arg);
|
pub struct $name(pub $arg);
|
||||||
|
|
||||||
impl $crate::Action for $name {
|
impl $crate::Action for $name {
|
||||||
|
|
|
@ -2,6 +2,7 @@ use anyhow::anyhow;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
fmt::Debug,
|
||||||
};
|
};
|
||||||
use tree_sitter::{Language, Node, Parser};
|
use tree_sitter::{Language, Node, Parser};
|
||||||
|
|
||||||
|
@ -148,7 +149,7 @@ impl Keymap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod menu {
|
pub mod menu {
|
||||||
use crate::action;
|
use crate::action;
|
||||||
|
|
||||||
action!(SelectPrev);
|
action!(SelectPrev);
|
||||||
|
@ -425,6 +426,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Eq for A {}
|
impl Eq for A {}
|
||||||
|
impl Debug for A {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "A({:?})", &self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
struct ActionArg {
|
struct ActionArg {
|
||||||
|
@ -472,12 +478,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Matcher {
|
impl Matcher {
|
||||||
fn test_keystroke<A: Action + Eq>(
|
fn test_keystroke<A>(&mut self, keystroke: &str, view_id: usize, cx: &Context) -> Option<A>
|
||||||
&mut self,
|
where
|
||||||
keystroke: &str,
|
A: Action + Debug + Eq,
|
||||||
view_id: usize,
|
{
|
||||||
cx: &Context,
|
|
||||||
) -> Option<A> {
|
|
||||||
if let MatchResult::Action(action) =
|
if let MatchResult::Action(action) =
|
||||||
self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, cx)
|
self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, cx)
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,7 +29,6 @@ use objc::{
|
||||||
};
|
};
|
||||||
use ptr::null_mut;
|
use ptr::null_mut;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
ffi::{c_void, CStr},
|
ffi::{c_void, CStr},
|
||||||
|
|
|
@ -934,7 +934,7 @@ mod tests {
|
||||||
use std::{path::Path, sync::Arc, time::Duration};
|
use std::{path::Path, sync::Arc, time::Duration};
|
||||||
use zed::{
|
use zed::{
|
||||||
channel::{Channel, ChannelDetails, ChannelList},
|
channel::{Channel, ChannelDetails, ChannelList},
|
||||||
editor::Editor,
|
editor::{Editor, Insert},
|
||||||
fs::{FakeFs, Fs as _},
|
fs::{FakeFs, Fs as _},
|
||||||
language::LanguageRegistry,
|
language::LanguageRegistry,
|
||||||
rpc::Client,
|
rpc::Client,
|
||||||
|
@ -1023,7 +1023,7 @@ mod tests {
|
||||||
|
|
||||||
// Edit the buffer as client B and see that edit as client A.
|
// Edit the buffer as client B and see that edit as client A.
|
||||||
editor_b.update(&mut cx_b, |editor, cx| {
|
editor_b.update(&mut cx_b, |editor, cx| {
|
||||||
editor.insert(&"ok, ".to_string(), cx)
|
editor.insert(&Insert("ok, ".into()), cx)
|
||||||
});
|
});
|
||||||
buffer_a
|
buffer_a
|
||||||
.condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents")
|
.condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents")
|
||||||
|
|
|
@ -6,8 +6,13 @@ use crate::{
|
||||||
worktree::{match_paths, PathMatch},
|
worktree::{match_paths, PathMatch},
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
action,
|
||||||
elements::*,
|
elements::*,
|
||||||
keymap::{self, Binding},
|
keymap::{
|
||||||
|
self,
|
||||||
|
menu::{SelectNext, SelectPrev},
|
||||||
|
Binding,
|
||||||
|
},
|
||||||
AppContext, Axis, Entity, MutableAppContext, RenderContext, Task, View, ViewContext,
|
AppContext, Axis, Entity, MutableAppContext, RenderContext, Task, View, ViewContext,
|
||||||
ViewHandle, WeakViewHandle,
|
ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
|
@ -36,17 +41,27 @@ pub struct FileFinder {
|
||||||
list_state: UniformListState,
|
list_state: UniformListState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
action!(Toggle);
|
||||||
|
action!(Confirm);
|
||||||
|
action!(Select, Entry);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Entry {
|
||||||
|
worktree_id: usize,
|
||||||
|
path: Arc<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action("file_finder:toggle", FileFinder::toggle);
|
cx.add_action(FileFinder::toggle);
|
||||||
cx.add_action("file_finder:confirm", FileFinder::confirm);
|
cx.add_action(FileFinder::confirm);
|
||||||
cx.add_action("file_finder:select", FileFinder::select);
|
cx.add_action(FileFinder::select);
|
||||||
cx.add_action("menu:select_prev", FileFinder::select_prev);
|
cx.add_action(FileFinder::select_prev);
|
||||||
cx.add_action("menu:select_next", FileFinder::select_next);
|
cx.add_action(FileFinder::select_next);
|
||||||
|
|
||||||
cx.add_bindings(vec![
|
cx.add_bindings(vec![
|
||||||
Binding::new("cmd-p", "file_finder:toggle", None),
|
Binding::new("cmd-p", Toggle, None),
|
||||||
Binding::new("escape", "file_finder:toggle", Some("FileFinder")),
|
Binding::new("escape", Toggle, Some("FileFinder")),
|
||||||
Binding::new("enter", "file_finder:confirm", Some("FileFinder")),
|
Binding::new("enter", Confirm, Some("FileFinder")),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,10 +211,13 @@ impl FileFinder {
|
||||||
)
|
)
|
||||||
.with_style(&style.container);
|
.with_style(&style.container);
|
||||||
|
|
||||||
let entry = (path_match.tree_id, path_match.path.clone());
|
let action = Select(Entry {
|
||||||
|
worktree_id: path_match.tree_id,
|
||||||
|
path: path_match.path.clone(),
|
||||||
|
});
|
||||||
EventHandler::new(container.boxed())
|
EventHandler::new(container.boxed())
|
||||||
.on_mouse_down(move |cx| {
|
.on_mouse_down(move |cx| {
|
||||||
cx.dispatch_action("file_finder:select", entry.clone());
|
cx.dispatch_action(action.clone());
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
.named("match")
|
.named("match")
|
||||||
|
@ -230,7 +248,7 @@ impl FileFinder {
|
||||||
(file_name, file_name_positions, full_path, path_positions)
|
(file_name, file_name_positions, full_path, path_positions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle(workspace: &mut Workspace, _: &(), cx: &mut ViewContext<Workspace>) {
|
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||||
workspace.toggle_modal(cx, |cx, workspace| {
|
workspace.toggle_modal(cx, |cx, workspace| {
|
||||||
let handle = cx.handle();
|
let handle = cx.handle();
|
||||||
let finder = cx.add_view(|cx| Self::new(workspace.settings.clone(), handle, cx));
|
let finder = cx.add_view(|cx| Self::new(workspace.settings.clone(), handle, cx));
|
||||||
|
@ -328,7 +346,7 @@ impl FileFinder {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_prev(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
||||||
let mut selected_index = self.selected_index();
|
let mut selected_index = self.selected_index();
|
||||||
if selected_index > 0 {
|
if selected_index > 0 {
|
||||||
selected_index -= 1;
|
selected_index -= 1;
|
||||||
|
@ -339,7 +357,7 @@ impl FileFinder {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_next(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||||
let mut selected_index = self.selected_index();
|
let mut selected_index = self.selected_index();
|
||||||
if selected_index + 1 < self.matches.len() {
|
if selected_index + 1 < self.matches.len() {
|
||||||
selected_index += 1;
|
selected_index += 1;
|
||||||
|
@ -350,14 +368,14 @@ impl FileFinder {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(m) = self.matches.get(self.selected_index()) {
|
if let Some(m) = self.matches.get(self.selected_index()) {
|
||||||
cx.emit(Event::Selected(m.tree_id, m.path.clone()));
|
cx.emit(Event::Selected(m.tree_id, m.path.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select(&mut self, (tree_id, path): &(usize, Arc<Path>), cx: &mut ViewContext<Self>) {
|
fn select(&mut self, Select(entry): &Select, cx: &mut ViewContext<Self>) {
|
||||||
cx.emit(Event::Selected(*tree_id, path.clone()));
|
cx.emit(Event::Selected(entry.worktree_id, entry.path.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -417,7 +435,7 @@ impl FileFinder {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
editor,
|
editor::{self, Insert},
|
||||||
fs::FakeFs,
|
fs::FakeFs,
|
||||||
test::{build_app_state, temp_tree},
|
test::{build_app_state, temp_tree},
|
||||||
workspace::Workspace,
|
workspace::Workspace,
|
||||||
|
@ -447,12 +465,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||||
.await;
|
.await;
|
||||||
cx.dispatch_action(
|
cx.dispatch_action(window_id, vec![workspace.id()], Toggle);
|
||||||
window_id,
|
|
||||||
vec![workspace.id()],
|
|
||||||
"file_finder:toggle".into(),
|
|
||||||
(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let finder = cx.read(|cx| {
|
let finder = cx.read(|cx| {
|
||||||
workspace
|
workspace
|
||||||
|
@ -466,26 +479,16 @@ mod tests {
|
||||||
let query_buffer = cx.read(|cx| finder.read(cx).query_buffer.clone());
|
let query_buffer = cx.read(|cx| finder.read(cx).query_buffer.clone());
|
||||||
|
|
||||||
let chain = vec![finder.id(), query_buffer.id()];
|
let chain = vec![finder.id(), query_buffer.id()];
|
||||||
cx.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string());
|
cx.dispatch_action(window_id, chain.clone(), Insert("b".into()));
|
||||||
cx.dispatch_action(window_id, chain.clone(), "buffer:insert", "n".to_string());
|
cx.dispatch_action(window_id, chain.clone(), Insert("n".into()));
|
||||||
cx.dispatch_action(window_id, chain.clone(), "buffer:insert", "a".to_string());
|
cx.dispatch_action(window_id, chain.clone(), Insert("a".into()));
|
||||||
finder
|
finder
|
||||||
.condition(&cx, |finder, _| finder.matches.len() == 2)
|
.condition(&cx, |finder, _| finder.matches.len() == 2)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
||||||
cx.dispatch_action(
|
cx.dispatch_action(window_id, vec![workspace.id(), finder.id()], SelectNext);
|
||||||
window_id,
|
cx.dispatch_action(window_id, vec![workspace.id(), finder.id()], Confirm);
|
||||||
vec![workspace.id(), finder.id()],
|
|
||||||
"menu:select_next",
|
|
||||||
(),
|
|
||||||
);
|
|
||||||
cx.dispatch_action(
|
|
||||||
window_id,
|
|
||||||
vec![workspace.id(), finder.id()],
|
|
||||||
"file_finder:confirm",
|
|
||||||
(),
|
|
||||||
);
|
|
||||||
active_pane
|
active_pane
|
||||||
.condition(&cx, |pane, _| pane.active_item().is_some())
|
.condition(&cx, |pane, _| pane.active_item().is_some())
|
||||||
.await;
|
.await;
|
||||||
|
@ -648,9 +651,9 @@ mod tests {
|
||||||
finder.update(&mut cx, |f, cx| {
|
finder.update(&mut cx, |f, cx| {
|
||||||
assert_eq!(f.matches.len(), 2);
|
assert_eq!(f.matches.len(), 2);
|
||||||
assert_eq!(f.selected_index(), 0);
|
assert_eq!(f.selected_index(), 0);
|
||||||
f.select_next(&(), cx);
|
f.select_next(&SelectNext, cx);
|
||||||
assert_eq!(f.selected_index(), 1);
|
assert_eq!(f.selected_index(), 1);
|
||||||
f.select_prev(&(), cx);
|
f.select_prev(&SelectPrev, cx);
|
||||||
assert_eq!(f.selected_index(), 0);
|
assert_eq!(f.selected_index(), 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,16 @@ mod util;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
pub mod worktree;
|
pub mod worktree;
|
||||||
|
|
||||||
|
use gpui::action;
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
action!(About);
|
||||||
|
action!(Quit);
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub settings_tx: Arc<Mutex<watch::Sender<Settings>>>,
|
pub settings_tx: Arc<Mutex<watch::Sender<Settings>>>,
|
||||||
pub settings: watch::Receiver<Settings>,
|
pub settings: watch::Receiver<Settings>,
|
||||||
|
@ -35,9 +39,9 @@ pub struct AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut gpui::MutableAppContext) {
|
pub fn init(cx: &mut gpui::MutableAppContext) {
|
||||||
cx.add_global_action("app:quit", quit);
|
cx.add_global_action(quit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quit(_: &(), cx: &mut gpui::MutableAppContext) {
|
fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
|
||||||
cx.platform().quit();
|
cx.platform().quit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use zed::{
|
||||||
self, assets, editor, file_finder,
|
self, assets, editor, file_finder,
|
||||||
fs::RealFs,
|
fs::RealFs,
|
||||||
language, menus, rpc, settings, theme_selector,
|
language, menus, rpc, settings, theme_selector,
|
||||||
workspace::{self, OpenParams},
|
workspace::{self, OpenParams, OpenPaths},
|
||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,13 +51,10 @@ fn main() {
|
||||||
|
|
||||||
let paths = collect_path_args();
|
let paths = collect_path_args();
|
||||||
if !paths.is_empty() {
|
if !paths.is_empty() {
|
||||||
cx.dispatch_global_action(
|
cx.dispatch_global_action(OpenPaths(OpenParams {
|
||||||
"workspace:open_paths",
|
|
||||||
OpenParams {
|
|
||||||
paths,
|
paths,
|
||||||
app_state: app_state.clone(),
|
app_state: app_state.clone(),
|
||||||
},
|
}));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::AppState;
|
use crate::{workspace, AppState};
|
||||||
use gpui::{Menu, MenuItem};
|
use gpui::{Menu, MenuItem};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
|
pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
|
||||||
|
use crate::editor;
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
Menu {
|
Menu {
|
||||||
name: "Zed",
|
name: "Zed",
|
||||||
|
@ -11,27 +13,23 @@ pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "About Zed…",
|
name: "About Zed…",
|
||||||
keystroke: None,
|
keystroke: None,
|
||||||
action: "app:about-zed",
|
action: Box::new(super::About),
|
||||||
arg: None,
|
|
||||||
},
|
},
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Share",
|
name: "Share",
|
||||||
keystroke: None,
|
keystroke: None,
|
||||||
action: "workspace:share_worktree",
|
action: Box::new(workspace::ShareWorktree),
|
||||||
arg: None,
|
|
||||||
},
|
},
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Join",
|
name: "Join",
|
||||||
keystroke: None,
|
keystroke: None,
|
||||||
action: "workspace:join_worktree",
|
action: Box::new(workspace::JoinWorktree(state.clone())),
|
||||||
arg: None,
|
|
||||||
},
|
},
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Quit",
|
name: "Quit",
|
||||||
keystroke: Some("cmd-q"),
|
keystroke: Some("cmd-q"),
|
||||||
action: "app:quit",
|
action: Box::new(super::Quit),
|
||||||
arg: None,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -41,15 +39,13 @@ pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "New",
|
name: "New",
|
||||||
keystroke: Some("cmd-n"),
|
keystroke: Some("cmd-n"),
|
||||||
action: "workspace:new_file",
|
action: Box::new(workspace::OpenNew(state.clone())),
|
||||||
arg: Some(Box::new(state.clone())),
|
|
||||||
},
|
},
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Open…",
|
name: "Open…",
|
||||||
keystroke: Some("cmd-o"),
|
keystroke: Some("cmd-o"),
|
||||||
action: "workspace:open",
|
action: Box::new(workspace::Open(state.clone())),
|
||||||
arg: Some(Box::new(state.clone())),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -59,33 +55,28 @@ pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Undo",
|
name: "Undo",
|
||||||
keystroke: Some("cmd-z"),
|
keystroke: Some("cmd-z"),
|
||||||
action: "buffer:undo",
|
action: Box::new(editor::Undo),
|
||||||
arg: None,
|
|
||||||
},
|
},
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Redo",
|
name: "Redo",
|
||||||
keystroke: Some("cmd-Z"),
|
keystroke: Some("cmd-Z"),
|
||||||
action: "buffer:redo",
|
action: Box::new(editor::Redo),
|
||||||
arg: None,
|
|
||||||
},
|
},
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Cut",
|
name: "Cut",
|
||||||
keystroke: Some("cmd-x"),
|
keystroke: Some("cmd-x"),
|
||||||
action: "buffer:cut",
|
action: Box::new(editor::Cut),
|
||||||
arg: None,
|
|
||||||
},
|
},
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Copy",
|
name: "Copy",
|
||||||
keystroke: Some("cmd-c"),
|
keystroke: Some("cmd-c"),
|
||||||
action: "buffer:copy",
|
action: Box::new(editor::Copy),
|
||||||
arg: None,
|
|
||||||
},
|
},
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Paste",
|
name: "Paste",
|
||||||
keystroke: Some("cmd-v"),
|
keystroke: Some("cmd-v"),
|
||||||
action: "buffer:paste",
|
action: Box::new(editor::Paste),
|
||||||
arg: None,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,11 +8,12 @@ use crate::{
|
||||||
AppState, Settings,
|
AppState, Settings,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
action,
|
||||||
elements::{
|
elements::{
|
||||||
Align, ChildView, ConstrainedBox, Container, Expanded, Flex, Label, ParentElement,
|
Align, ChildView, ConstrainedBox, Container, Expanded, Flex, Label, ParentElement,
|
||||||
UniformList, UniformListState,
|
UniformList, UniformListState,
|
||||||
},
|
},
|
||||||
keymap::{self, Binding},
|
keymap::{self, menu, Binding},
|
||||||
AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, View,
|
AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, View,
|
||||||
ViewContext, ViewHandle,
|
ViewContext, ViewHandle,
|
||||||
};
|
};
|
||||||
|
@ -29,19 +30,22 @@ pub struct ThemeSelector {
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
action!(Confirm);
|
||||||
|
action!(Toggle, Arc<AppState>);
|
||||||
|
action!(Reload, Arc<AppState>);
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext, app_state: &Arc<AppState>) {
|
pub fn init(cx: &mut MutableAppContext, app_state: &Arc<AppState>) {
|
||||||
cx.add_action("theme_selector:confirm", ThemeSelector::confirm);
|
cx.add_action(ThemeSelector::confirm);
|
||||||
cx.add_action("menu:select_prev", ThemeSelector::select_prev);
|
cx.add_action(ThemeSelector::select_prev);
|
||||||
cx.add_action("menu:select_next", ThemeSelector::select_next);
|
cx.add_action(ThemeSelector::select_next);
|
||||||
cx.add_action("theme_selector:toggle", ThemeSelector::toggle);
|
cx.add_action(ThemeSelector::toggle);
|
||||||
cx.add_action("theme_selector:reload", ThemeSelector::reload);
|
cx.add_action(ThemeSelector::reload);
|
||||||
|
|
||||||
cx.add_bindings(vec![
|
cx.add_bindings(vec![
|
||||||
Binding::new("cmd-k cmd-t", "theme_selector:toggle", None).with_arg(app_state.clone()),
|
Binding::new("cmd-k cmd-t", Toggle(app_state.clone()), None),
|
||||||
Binding::new("cmd-k t", "theme_selector:reload", None).with_arg(app_state.clone()),
|
Binding::new("cmd-k t", Reload(app_state.clone()), None),
|
||||||
Binding::new("escape", "theme_selector:toggle", Some("ThemeSelector"))
|
Binding::new("escape", Toggle(app_state.clone()), Some("ThemeSelector")),
|
||||||
.with_arg(app_state.clone()),
|
Binding::new("enter", Confirm, Some("ThemeSelector")),
|
||||||
Binding::new("enter", "theme_selector:confirm", Some("ThemeSelector")),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,17 +76,13 @@ impl ThemeSelector {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle(
|
fn toggle(workspace: &mut Workspace, action: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||||
workspace: &mut Workspace,
|
|
||||||
app_state: &Arc<AppState>,
|
|
||||||
cx: &mut ViewContext<Workspace>,
|
|
||||||
) {
|
|
||||||
workspace.toggle_modal(cx, |cx, _| {
|
workspace.toggle_modal(cx, |cx, _| {
|
||||||
let selector = cx.add_view(|cx| {
|
let selector = cx.add_view(|cx| {
|
||||||
Self::new(
|
Self::new(
|
||||||
app_state.settings_tx.clone(),
|
action.0.settings_tx.clone(),
|
||||||
app_state.settings.clone(),
|
action.0.settings.clone(),
|
||||||
app_state.themes.clone(),
|
action.0.themes.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -91,13 +91,13 @@ impl ThemeSelector {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reload(_: &mut Workspace, app_state: &Arc<AppState>, cx: &mut ViewContext<Workspace>) {
|
fn reload(_: &mut Workspace, action: &Reload, cx: &mut ViewContext<Workspace>) {
|
||||||
let current_theme_name = app_state.settings.borrow().theme.name.clone();
|
let current_theme_name = action.0.settings.borrow().theme.name.clone();
|
||||||
app_state.themes.clear();
|
action.0.themes.clear();
|
||||||
match app_state.themes.get(¤t_theme_name) {
|
match action.0.themes.get(¤t_theme_name) {
|
||||||
Ok(theme) => {
|
Ok(theme) => {
|
||||||
cx.notify_all();
|
cx.notify_all();
|
||||||
app_state.settings_tx.lock().borrow_mut().theme = theme;
|
action.0.settings_tx.lock().borrow_mut().theme = theme;
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::error!("failed to load theme {}: {:?}", current_theme_name, error)
|
log::error!("failed to load theme {}: {:?}", current_theme_name, error)
|
||||||
|
@ -105,7 +105,7 @@ impl ThemeSelector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(mat) = self.matches.get(self.selected_index) {
|
if let Some(mat) = self.matches.get(self.selected_index) {
|
||||||
match self.registry.get(&mat.string) {
|
match self.registry.get(&mat.string) {
|
||||||
Ok(theme) => {
|
Ok(theme) => {
|
||||||
|
@ -118,7 +118,7 @@ impl ThemeSelector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_prev(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
|
||||||
if self.selected_index > 0 {
|
if self.selected_index > 0 {
|
||||||
self.selected_index -= 1;
|
self.selected_index -= 1;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ impl ThemeSelector {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_next(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
|
||||||
if self.selected_index + 1 < self.matches.len() {
|
if self.selected_index + 1 < self.matches.len() {
|
||||||
self.selected_index += 1;
|
self.selected_index += 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
action,
|
||||||
elements::*,
|
elements::*,
|
||||||
geometry::{rect::RectF, vector::vec2f},
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
json::to_string_pretty,
|
json::to_string_pretty,
|
||||||
|
@ -36,37 +37,43 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
action!(Open, Arc<AppState>);
|
||||||
|
action!(OpenPaths, OpenParams);
|
||||||
|
action!(OpenNew, Arc<AppState>);
|
||||||
|
action!(ShareWorktree);
|
||||||
|
action!(JoinWorktree, Arc<AppState>);
|
||||||
|
action!(Save);
|
||||||
|
action!(DebugElements);
|
||||||
|
action!(ToggleSidebarItem, (Side, usize));
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_global_action("workspace:open", open);
|
cx.add_global_action(open);
|
||||||
cx.add_global_action(
|
cx.add_global_action(|action: &OpenPaths, cx: &mut MutableAppContext| {
|
||||||
"workspace:open_paths",
|
open_paths(action, cx).detach()
|
||||||
|params: &OpenParams, cx: &mut MutableAppContext| open_paths(params, cx).detach(),
|
});
|
||||||
);
|
cx.add_global_action(open_new);
|
||||||
cx.add_global_action("workspace:new_file", open_new);
|
cx.add_global_action(join_worktree);
|
||||||
cx.add_global_action("workspace:join_worktree", join_worktree);
|
cx.add_action(Workspace::save_active_item);
|
||||||
cx.add_action("workspace:save", Workspace::save_active_item);
|
cx.add_action(Workspace::debug_elements);
|
||||||
cx.add_action("workspace:debug_elements", Workspace::debug_elements);
|
cx.add_action(Workspace::open_new_file);
|
||||||
cx.add_action("workspace:new_file", Workspace::open_new_file);
|
cx.add_action(Workspace::share_worktree);
|
||||||
cx.add_action("workspace:share_worktree", Workspace::share_worktree);
|
cx.add_action(Workspace::join_worktree);
|
||||||
cx.add_action("workspace:join_worktree", Workspace::join_worktree);
|
cx.add_action(Workspace::toggle_sidebar_item);
|
||||||
cx.add_action(
|
|
||||||
"workspace:toggle_sidebar_item",
|
|
||||||
Workspace::toggle_sidebar_item,
|
|
||||||
);
|
|
||||||
cx.add_bindings(vec![
|
cx.add_bindings(vec![
|
||||||
Binding::new("cmd-s", "workspace:save", None),
|
Binding::new("cmd-s", Save, None),
|
||||||
Binding::new("cmd-alt-i", "workspace:debug_elements", None),
|
Binding::new("cmd-alt-i", DebugElements, None),
|
||||||
]);
|
]);
|
||||||
pane::init(cx);
|
pane::init(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct OpenParams {
|
pub struct OpenParams {
|
||||||
pub paths: Vec<PathBuf>,
|
pub paths: Vec<PathBuf>,
|
||||||
pub app_state: Arc<AppState>,
|
pub app_state: Arc<AppState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
fn open(action: &Open, cx: &mut MutableAppContext) {
|
||||||
let app_state = app_state.clone();
|
let app_state = action.0.clone();
|
||||||
cx.prompt_for_paths(
|
cx.prompt_for_paths(
|
||||||
PathPromptOptions {
|
PathPromptOptions {
|
||||||
files: true,
|
files: true,
|
||||||
|
@ -75,22 +82,22 @@ fn open(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
||||||
},
|
},
|
||||||
move |paths, cx| {
|
move |paths, cx| {
|
||||||
if let Some(paths) = paths {
|
if let Some(paths) = paths {
|
||||||
cx.dispatch_global_action("workspace:open_paths", OpenParams { paths, app_state });
|
cx.dispatch_global_action(OpenPaths(OpenParams { paths, app_state }));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_paths(params: &OpenParams, cx: &mut MutableAppContext) -> Task<()> {
|
fn open_paths(action: &OpenPaths, cx: &mut MutableAppContext) -> Task<()> {
|
||||||
log::info!("open paths {:?}", params.paths);
|
log::info!("open paths {:?}", action.0.paths);
|
||||||
|
|
||||||
// Open paths in existing workspace if possible
|
// Open paths in existing workspace if possible
|
||||||
for window_id in cx.window_ids().collect::<Vec<_>>() {
|
for window_id in cx.window_ids().collect::<Vec<_>>() {
|
||||||
if let Some(handle) = cx.root_view::<Workspace>(window_id) {
|
if let Some(handle) = cx.root_view::<Workspace>(window_id) {
|
||||||
let task = handle.update(cx, |view, cx| {
|
let task = handle.update(cx, |view, cx| {
|
||||||
if view.contains_paths(¶ms.paths, cx.as_ref()) {
|
if view.contains_paths(&action.0.paths, cx.as_ref()) {
|
||||||
log::info!("open paths on existing workspace");
|
log::info!("open paths on existing workspace");
|
||||||
Some(view.open_paths(¶ms.paths, cx))
|
Some(view.open_paths(&action.0.paths, cx))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -106,23 +113,26 @@ fn open_paths(params: &OpenParams, cx: &mut MutableAppContext) -> Task<()> {
|
||||||
|
|
||||||
// Add a new workspace if necessary
|
// Add a new workspace if necessary
|
||||||
|
|
||||||
let (_, workspace) =
|
let (_, workspace) = cx.add_window(window_options(), |cx| {
|
||||||
cx.add_window(window_options(), |cx| Workspace::new(¶ms.app_state, cx));
|
Workspace::new(&action.0.app_state, cx)
|
||||||
workspace.update(cx, |workspace, cx| workspace.open_paths(¶ms.paths, cx))
|
});
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.open_paths(&action.0.paths, cx)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
fn open_new(action: &OpenNew, cx: &mut MutableAppContext) {
|
||||||
cx.add_window(window_options(), |cx| {
|
cx.add_window(window_options(), |cx| {
|
||||||
let mut view = Workspace::new(app_state.as_ref(), cx);
|
let mut view = Workspace::new(action.0.as_ref(), cx);
|
||||||
view.open_new_file(&app_state, cx);
|
view.open_new_file(&action, cx);
|
||||||
view
|
view
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_worktree(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
fn join_worktree(action: &JoinWorktree, cx: &mut MutableAppContext) {
|
||||||
cx.add_window(window_options(), |cx| {
|
cx.add_window(window_options(), |cx| {
|
||||||
let mut view = Workspace::new(app_state.as_ref(), cx);
|
let mut view = Workspace::new(action.0.as_ref(), cx);
|
||||||
view.join_worktree(&(), cx);
|
view.join_worktree(action, cx);
|
||||||
view
|
view
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -544,7 +554,7 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_new_file(&mut self, _: &Arc<AppState>, cx: &mut ViewContext<Self>) {
|
pub fn open_new_file(&mut self, _: &OpenNew, cx: &mut ViewContext<Self>) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
|
||||||
let buffer_view =
|
let buffer_view =
|
||||||
cx.add_view(|cx| Editor::for_buffer(buffer.clone(), self.settings.clone(), cx));
|
cx.add_view(|cx| Editor::for_buffer(buffer.clone(), self.settings.clone(), cx));
|
||||||
|
@ -677,7 +687,7 @@ impl Workspace {
|
||||||
self.active_pane().read(cx).active_item()
|
self.active_pane().read(cx).active_item()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_active_item(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
pub fn save_active_item(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(item) = self.active_item(cx) {
|
if let Some(item) = self.active_item(cx) {
|
||||||
let handle = cx.handle();
|
let handle = cx.handle();
|
||||||
if item.entry_id(cx.as_ref()).is_none() {
|
if item.entry_id(cx.as_ref()).is_none() {
|
||||||
|
@ -744,7 +754,7 @@ impl Workspace {
|
||||||
|
|
||||||
pub fn toggle_sidebar_item(
|
pub fn toggle_sidebar_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
(side, item_ix): &(Side, usize),
|
ToggleSidebarItem((side, item_ix)): &ToggleSidebarItem,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
let sidebar = match side {
|
let sidebar = match side {
|
||||||
|
@ -755,7 +765,7 @@ impl Workspace {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_elements(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
pub fn debug_elements(&mut self, _: &DebugElements, cx: &mut ViewContext<Self>) {
|
||||||
match to_string_pretty(&cx.debug_elements()) {
|
match to_string_pretty(&cx.debug_elements()) {
|
||||||
Ok(json) => {
|
Ok(json) => {
|
||||||
let kib = json.len() as f32 / 1024.;
|
let kib = json.len() as f32 / 1024.;
|
||||||
|
@ -771,7 +781,7 @@ impl Workspace {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn share_worktree(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
fn share_worktree(&mut self, _: &ShareWorktree, cx: &mut ViewContext<Self>) {
|
||||||
let rpc = self.rpc.clone();
|
let rpc = self.rpc.clone();
|
||||||
let platform = cx.platform();
|
let platform = cx.platform();
|
||||||
|
|
||||||
|
@ -803,7 +813,7 @@ impl Workspace {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_worktree(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
fn join_worktree(&mut self, _: &JoinWorktree, cx: &mut ViewContext<Self>) {
|
||||||
let rpc = self.rpc.clone();
|
let rpc = self.rpc.clone();
|
||||||
let languages = self.languages.clone();
|
let languages = self.languages.clone();
|
||||||
|
|
||||||
|
@ -1000,7 +1010,7 @@ impl WorkspaceHandle for ViewHandle<Workspace> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::Editor,
|
editor::{Editor, Insert},
|
||||||
fs::FakeFs,
|
fs::FakeFs,
|
||||||
test::{build_app_state, temp_tree},
|
test::{build_app_state, temp_tree},
|
||||||
worktree::WorktreeHandle,
|
worktree::WorktreeHandle,
|
||||||
|
@ -1029,13 +1039,13 @@ mod tests {
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
open_paths(
|
open_paths(
|
||||||
&OpenParams {
|
&OpenPaths(OpenParams {
|
||||||
paths: vec![
|
paths: vec![
|
||||||
dir.path().join("a").to_path_buf(),
|
dir.path().join("a").to_path_buf(),
|
||||||
dir.path().join("b").to_path_buf(),
|
dir.path().join("b").to_path_buf(),
|
||||||
],
|
],
|
||||||
app_state: app_state.clone(),
|
app_state: app_state.clone(),
|
||||||
},
|
}),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -1044,10 +1054,10 @@ mod tests {
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
open_paths(
|
open_paths(
|
||||||
&OpenParams {
|
&OpenPaths(OpenParams {
|
||||||
paths: vec![dir.path().join("a").to_path_buf()],
|
paths: vec![dir.path().join("a").to_path_buf()],
|
||||||
app_state: app_state.clone(),
|
app_state: app_state.clone(),
|
||||||
},
|
}),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -1060,13 +1070,13 @@ mod tests {
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
open_paths(
|
open_paths(
|
||||||
&OpenParams {
|
&OpenPaths(OpenParams {
|
||||||
paths: vec![
|
paths: vec![
|
||||||
dir.path().join("b").to_path_buf(),
|
dir.path().join("b").to_path_buf(),
|
||||||
dir.path().join("c").to_path_buf(),
|
dir.path().join("c").to_path_buf(),
|
||||||
],
|
],
|
||||||
app_state: app_state.clone(),
|
app_state: app_state.clone(),
|
||||||
},
|
}),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -1284,14 +1294,14 @@ mod tests {
|
||||||
item.to_any().downcast::<Editor>().unwrap()
|
item.to_any().downcast::<Editor>().unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.update(|cx| editor.update(cx, |editor, cx| editor.insert(&"x".to_string(), cx)));
|
cx.update(|cx| editor.update(cx, |editor, cx| editor.insert(&Insert("x".into()), cx)));
|
||||||
fs::write(dir.path().join("a.txt"), "changed").unwrap();
|
fs::write(dir.path().join("a.txt"), "changed").unwrap();
|
||||||
editor
|
editor
|
||||||
.condition(&cx, |editor, cx| editor.has_conflict(cx))
|
.condition(&cx, |editor, cx| editor.has_conflict(cx))
|
||||||
.await;
|
.await;
|
||||||
cx.read(|cx| assert!(editor.is_dirty(cx)));
|
cx.read(|cx| assert!(editor.is_dirty(cx)));
|
||||||
|
|
||||||
cx.update(|cx| workspace.update(cx, |w, cx| w.save_active_item(&(), cx)));
|
cx.update(|cx| workspace.update(cx, |w, cx| w.save_active_item(&Save, cx)));
|
||||||
cx.simulate_prompt_answer(window_id, 0);
|
cx.simulate_prompt_answer(window_id, 0);
|
||||||
editor
|
editor
|
||||||
.condition(&cx, |editor, cx| !editor.is_dirty(cx))
|
.condition(&cx, |editor, cx| !editor.is_dirty(cx))
|
||||||
|
@ -1323,7 +1333,7 @@ mod tests {
|
||||||
|
|
||||||
// Create a new untitled buffer
|
// Create a new untitled buffer
|
||||||
let editor = workspace.update(&mut cx, |workspace, cx| {
|
let editor = workspace.update(&mut cx, |workspace, cx| {
|
||||||
workspace.open_new_file(&app_state, cx);
|
workspace.open_new_file(&OpenNew(app_state.clone()), cx);
|
||||||
workspace
|
workspace
|
||||||
.active_item(cx)
|
.active_item(cx)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -1335,12 +1345,14 @@ mod tests {
|
||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert!(!editor.is_dirty(cx.as_ref()));
|
assert!(!editor.is_dirty(cx.as_ref()));
|
||||||
assert_eq!(editor.title(cx.as_ref()), "untitled");
|
assert_eq!(editor.title(cx.as_ref()), "untitled");
|
||||||
editor.insert(&"hi".to_string(), cx);
|
editor.insert(&Insert("hi".into()), cx);
|
||||||
assert!(editor.is_dirty(cx.as_ref()));
|
assert!(editor.is_dirty(cx.as_ref()));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save the buffer. This prompts for a filename.
|
// Save the buffer. This prompts for a filename.
|
||||||
workspace.update(&mut cx, |workspace, cx| workspace.save_active_item(&(), cx));
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.save_active_item(&Save, cx)
|
||||||
|
});
|
||||||
cx.simulate_new_path_selection(|parent_dir| {
|
cx.simulate_new_path_selection(|parent_dir| {
|
||||||
assert_eq!(parent_dir, dir.path());
|
assert_eq!(parent_dir, dir.path());
|
||||||
Some(parent_dir.join("the-new-name"))
|
Some(parent_dir.join("the-new-name"))
|
||||||
|
@ -1361,10 +1373,12 @@ mod tests {
|
||||||
|
|
||||||
// Edit the file and save it again. This time, there is no filename prompt.
|
// Edit the file and save it again. This time, there is no filename prompt.
|
||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
editor.insert(&" there".to_string(), cx);
|
editor.insert(&Insert(" there".into()), cx);
|
||||||
assert_eq!(editor.is_dirty(cx.as_ref()), true);
|
assert_eq!(editor.is_dirty(cx.as_ref()), true);
|
||||||
});
|
});
|
||||||
workspace.update(&mut cx, |workspace, cx| workspace.save_active_item(&(), cx));
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.save_active_item(&Save, cx)
|
||||||
|
});
|
||||||
assert!(!cx.did_prompt_for_new_path());
|
assert!(!cx.did_prompt_for_new_path());
|
||||||
editor
|
editor
|
||||||
.condition(&cx, |editor, cx| !editor.is_dirty(cx))
|
.condition(&cx, |editor, cx| !editor.is_dirty(cx))
|
||||||
|
@ -1374,7 +1388,7 @@ mod tests {
|
||||||
// Open the same newly-created file in another pane item. The new editor should reuse
|
// Open the same newly-created file in another pane item. The new editor should reuse
|
||||||
// the same buffer.
|
// the same buffer.
|
||||||
workspace.update(&mut cx, |workspace, cx| {
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
workspace.open_new_file(&app_state, cx);
|
workspace.open_new_file(&OpenNew(app_state.clone()), cx);
|
||||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||||
assert!(workspace
|
assert!(workspace
|
||||||
.open_entry((tree.id(), Path::new("the-new-name").into()), cx)
|
.open_entry((tree.id(), Path::new("the-new-name").into()), cx)
|
||||||
|
@ -1398,7 +1412,7 @@ mod tests {
|
||||||
cx.update(init);
|
cx.update(init);
|
||||||
|
|
||||||
let app_state = cx.read(build_app_state);
|
let app_state = cx.read(build_app_state);
|
||||||
cx.dispatch_global_action("workspace:new_file", app_state);
|
cx.dispatch_global_action(OpenNew(app_state));
|
||||||
let window_id = *cx.window_ids().first().unwrap();
|
let window_id = *cx.window_ids().first().unwrap();
|
||||||
let workspace = cx.root_view::<Workspace>(window_id).unwrap();
|
let workspace = cx.root_view::<Workspace>(window_id).unwrap();
|
||||||
let editor = workspace.update(&mut cx, |workspace, cx| {
|
let editor = workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
@ -1414,7 +1428,9 @@ mod tests {
|
||||||
assert!(editor.text(cx).is_empty());
|
assert!(editor.text(cx).is_empty());
|
||||||
});
|
});
|
||||||
|
|
||||||
workspace.update(&mut cx, |workspace, cx| workspace.save_active_item(&(), cx));
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.save_active_item(&Save, cx)
|
||||||
|
});
|
||||||
|
|
||||||
let dir = TempDir::new("test-new-empty-workspace").unwrap();
|
let dir = TempDir::new("test-new-empty-workspace").unwrap();
|
||||||
cx.simulate_new_path_selection(|_| {
|
cx.simulate_new_path_selection(|_| {
|
||||||
|
@ -1467,7 +1483,11 @@ mod tests {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
|
cx.dispatch_action(
|
||||||
|
window_id,
|
||||||
|
vec![pane_1.id()],
|
||||||
|
pane::Split(SplitDirection::Right),
|
||||||
|
);
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let pane_2 = workspace.read(cx).active_pane().clone();
|
let pane_2 = workspace.read(cx).active_pane().clone();
|
||||||
assert_ne!(pane_1, pane_2);
|
assert_ne!(pane_1, pane_2);
|
||||||
|
@ -1475,7 +1495,7 @@ mod tests {
|
||||||
let pane2_item = pane_2.read(cx).active_item().unwrap();
|
let pane2_item = pane_2.read(cx).active_item().unwrap();
|
||||||
assert_eq!(pane2_item.entry_id(cx.as_ref()), Some(file1.clone()));
|
assert_eq!(pane2_item.entry_id(cx.as_ref()), Some(file1.clone()));
|
||||||
|
|
||||||
cx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
|
cx.dispatch_action(window_id, vec![pane_2.id()], &CloseActiveItem);
|
||||||
let workspace = workspace.read(cx);
|
let workspace = workspace.read(cx);
|
||||||
assert_eq!(workspace.panes.len(), 1);
|
assert_eq!(workspace.panes.len(), 1);
|
||||||
assert_eq!(workspace.active_pane(), &pane_1);
|
assert_eq!(workspace.active_pane(), &pane_1);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::{ItemViewHandle, SplitDirection};
|
use super::{ItemViewHandle, SplitDirection};
|
||||||
use crate::{settings::Settings, theme};
|
use crate::{settings::Settings, theme};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
action,
|
||||||
color::Color,
|
color::Color,
|
||||||
elements::*,
|
elements::*,
|
||||||
geometry::{rect::RectF, vector::vec2f},
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
|
@ -11,46 +12,41 @@ use gpui::{
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use std::{cmp, path::Path, sync::Arc};
|
use std::{cmp, path::Path, sync::Arc};
|
||||||
|
|
||||||
|
action!(Split, SplitDirection);
|
||||||
|
action!(ActivateItem, usize);
|
||||||
|
action!(ActivatePrevItem);
|
||||||
|
action!(ActivateNextItem);
|
||||||
|
action!(CloseActiveItem);
|
||||||
|
action!(CloseItem, usize);
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(
|
cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
|
||||||
"pane:activate_item",
|
pane.activate_item(action.0, cx);
|
||||||
|pane: &mut Pane, index: &usize, cx| {
|
});
|
||||||
pane.activate_item(*index, cx);
|
cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
|
||||||
},
|
|
||||||
);
|
|
||||||
cx.add_action("pane:activate_prev_item", |pane: &mut Pane, _: &(), cx| {
|
|
||||||
pane.activate_prev_item(cx);
|
pane.activate_prev_item(cx);
|
||||||
});
|
});
|
||||||
cx.add_action("pane:activate_next_item", |pane: &mut Pane, _: &(), cx| {
|
cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
|
||||||
pane.activate_next_item(cx);
|
pane.activate_next_item(cx);
|
||||||
});
|
});
|
||||||
cx.add_action("pane:close_active_item", |pane: &mut Pane, _: &(), cx| {
|
cx.add_action(|pane: &mut Pane, _: &CloseActiveItem, cx| {
|
||||||
pane.close_active_item(cx);
|
pane.close_active_item(cx);
|
||||||
});
|
});
|
||||||
cx.add_action("pane:close_item", |pane: &mut Pane, item_id: &usize, cx| {
|
cx.add_action(|pane: &mut Pane, action: &CloseItem, cx| {
|
||||||
pane.close_item(*item_id, cx);
|
pane.close_item(action.0, cx);
|
||||||
});
|
});
|
||||||
cx.add_action("pane:split_up", |pane: &mut Pane, _: &(), cx| {
|
cx.add_action(|pane: &mut Pane, action: &Split, cx| {
|
||||||
pane.split(SplitDirection::Up, cx);
|
pane.split(action.0, cx);
|
||||||
});
|
|
||||||
cx.add_action("pane:split_down", |pane: &mut Pane, _: &(), cx| {
|
|
||||||
pane.split(SplitDirection::Down, cx);
|
|
||||||
});
|
|
||||||
cx.add_action("pane:split_left", |pane: &mut Pane, _: &(), cx| {
|
|
||||||
pane.split(SplitDirection::Left, cx);
|
|
||||||
});
|
|
||||||
cx.add_action("pane:split_right", |pane: &mut Pane, _: &(), cx| {
|
|
||||||
pane.split(SplitDirection::Right, cx);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.add_bindings(vec![
|
cx.add_bindings(vec![
|
||||||
Binding::new("shift-cmd-{", "pane:activate_prev_item", Some("Pane")),
|
Binding::new("shift-cmd-{", ActivatePrevItem, Some("Pane")),
|
||||||
Binding::new("shift-cmd-}", "pane:activate_next_item", Some("Pane")),
|
Binding::new("shift-cmd-}", ActivateNextItem, Some("Pane")),
|
||||||
Binding::new("cmd-w", "pane:close_active_item", Some("Pane")),
|
Binding::new("cmd-w", CloseActiveItem, Some("Pane")),
|
||||||
Binding::new("cmd-k up", "pane:split_up", Some("Pane")),
|
Binding::new("cmd-k up", Split(SplitDirection::Up), Some("Pane")),
|
||||||
Binding::new("cmd-k down", "pane:split_down", Some("Pane")),
|
Binding::new("cmd-k down", Split(SplitDirection::Down), Some("Pane")),
|
||||||
Binding::new("cmd-k left", "pane:split_left", Some("Pane")),
|
Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")),
|
||||||
Binding::new("cmd-k right", "pane:split_right", Some("Pane")),
|
Binding::new("cmd-k right", Split(SplitDirection::Right), Some("Pane")),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +249,7 @@ impl Pane {
|
||||||
ConstrainedBox::new(
|
ConstrainedBox::new(
|
||||||
EventHandler::new(container.boxed())
|
EventHandler::new(container.boxed())
|
||||||
.on_mouse_down(move |cx| {
|
.on_mouse_down(move |cx| {
|
||||||
cx.dispatch_action("pane:activate_item", ix);
|
cx.dispatch_action(ActivateItem(ix));
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
.boxed(),
|
.boxed(),
|
||||||
|
@ -338,7 +334,7 @@ impl Pane {
|
||||||
icon.boxed()
|
icon.boxed()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_click(move |cx| cx.dispatch_action("pane:close_item", item_id))
|
.on_click(move |cx| cx.dispatch_action(CloseItem(item_id)))
|
||||||
.named("close-tab-icon")
|
.named("close-tab-icon")
|
||||||
} else {
|
} else {
|
||||||
let diameter = 8.;
|
let diameter = 8.;
|
||||||
|
|
|
@ -184,7 +184,7 @@ impl PaneAxis {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum SplitDirection {
|
pub enum SplitDirection {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::Settings;
|
use crate::Settings;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
action,
|
||||||
elements::{
|
elements::{
|
||||||
Align, ConstrainedBox, Container, Flex, MouseEventHandler, ParentElement as _, Svg,
|
Align, ConstrainedBox, Container, Flex, MouseEventHandler, ParentElement as _, Svg,
|
||||||
},
|
},
|
||||||
|
@ -23,6 +24,14 @@ struct Item {
|
||||||
view: AnyViewHandle,
|
view: AnyViewHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
action!(ToggleSidebarItem, ToggleArg);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ToggleArg {
|
||||||
|
side: Side,
|
||||||
|
item_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl Sidebar {
|
impl Sidebar {
|
||||||
pub fn new(side: Side) -> Self {
|
pub fn new(side: Side) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -59,8 +68,8 @@ impl Sidebar {
|
||||||
|
|
||||||
Container::new(
|
Container::new(
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_children(self.items.iter().enumerate().map(|(item_ix, item)| {
|
.with_children(self.items.iter().enumerate().map(|(item_index, item)| {
|
||||||
let theme = if Some(item_ix) == self.active_item_ix {
|
let theme = if Some(item_index) == self.active_item_ix {
|
||||||
&settings.theme.active_sidebar_icon
|
&settings.theme.active_sidebar_icon
|
||||||
} else {
|
} else {
|
||||||
&settings.theme.sidebar_icon
|
&settings.theme.sidebar_icon
|
||||||
|
@ -81,7 +90,7 @@ impl Sidebar {
|
||||||
.boxed()
|
.boxed()
|
||||||
})
|
})
|
||||||
.on_click(move |cx| {
|
.on_click(move |cx| {
|
||||||
cx.dispatch_action("workspace:toggle_sidebar_item", (side, item_ix))
|
cx.dispatch_action(ToggleSidebarItem(ToggleArg { side, item_index }))
|
||||||
})
|
})
|
||||||
.boxed()
|
.boxed()
|
||||||
}))
|
}))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue