Make a macro for less boilerplate when moving variables (#12182)

Mikayla Maki created

Also: 
- Simplify open listener implementation
- Add set_global API to global traits

Release Notes:

- N/A

Change summary

crates/gpui/src/global.rs           |  12 ++
crates/util/src/util.rs             | 136 +++++++++++++++++++++++++++++++
crates/zed/src/main.rs              |  22 ++--
crates/zed/src/zed.rs               |   2 
crates/zed/src/zed/open_listener.rs |  23 +---
5 files changed, 164 insertions(+), 31 deletions(-)

Detailed changes

crates/gpui/src/global.rs 🔗

@@ -49,6 +49,11 @@ pub trait UpdateGlobal {
     where
         C: BorrowAppContext,
         F: FnOnce(&mut Self, &mut C) -> R;
+
+    /// Set the global instance of the implementing type.
+    fn set_global<C>(cx: &mut C, global: Self)
+    where
+        C: BorrowAppContext;
 }
 
 impl<T: Global> UpdateGlobal for T {
@@ -59,4 +64,11 @@ impl<T: Global> UpdateGlobal for T {
     {
         cx.update_global(update)
     }
+
+    fn set_global<C>(cx: &mut C, global: Self)
+    where
+        C: BorrowAppContext,
+    {
+        cx.set_global(global)
+    }
 }

crates/util/src/util.rs 🔗

@@ -34,6 +34,142 @@ macro_rules! debug_panic {
     };
 }
 
+#[macro_export]
+macro_rules! with_clone {
+    ($i:ident, move ||$l:expr) => {{
+        let $i = $i.clone();
+        move || {
+            $l
+        }
+    }};
+    ($i:ident, move |$($k:pat_param),*|$l:expr) => {{
+        let $i = $i.clone();
+        move |$( $k ),*| {
+            $l
+        }
+    }};
+
+    (($($i:ident),+), move ||$l:expr) => {{
+        let ($($i),+) = ($($i.clone()),+);
+        move || {
+            $l
+        }
+    }};
+    (($($i:ident),+), move |$($k:pat_param),*|$l:expr) => {{
+        let ($($i),+) = ($($i.clone()),+);
+        move |$( $k ),*| {
+            $l
+        }
+    }};
+}
+
+mod test_with_clone {
+
+    // If this test compiles, it works
+    #[test]
+    fn test() {
+        let x = "String".to_string();
+        let y = std::sync::Arc::new(5);
+
+        fn no_arg(f: impl FnOnce()) {
+            f()
+        }
+
+        no_arg(with_clone!(x, move || {
+            drop(x);
+        }));
+
+        no_arg(with_clone!((x, y), move || {
+            drop(x);
+            drop(y);
+        }));
+
+        fn one_arg(f: impl FnOnce(usize)) {
+            f(1)
+        }
+
+        one_arg(with_clone!(x, move |_| {
+            drop(x);
+        }));
+        one_arg(with_clone!((x, y), move |b| {
+            drop(x);
+            drop(y);
+            println!("{}", b);
+        }));
+
+        fn two_arg(f: impl FnOnce(usize, bool)) {
+            f(5, true)
+        }
+
+        two_arg(with_clone!((x, y), move |a, b| {
+            drop(x);
+            drop(y);
+            println!("{}{}", a, b)
+        }));
+        two_arg(with_clone!((x, y), move |a, _| {
+            drop(x);
+            drop(y);
+            println!("{}", a)
+        }));
+        two_arg(with_clone!((x, y), move |_, b| {
+            drop(x);
+            drop(y);
+            println!("{}", b)
+        }));
+
+        struct Example {
+            z: usize,
+        }
+
+        fn destructuring_example(f: impl FnOnce(Example)) {
+            f(Example { z: 10 })
+        }
+
+        destructuring_example(with_clone!(x, move |Example { z }| {
+            drop(x);
+            println!("{}", z);
+        }));
+
+        let a_long_variable_1 = "".to_string();
+        let a_long_variable_2 = "".to_string();
+        let a_long_variable_3 = "".to_string();
+        let a_long_variable_4 = "".to_string();
+        two_arg(with_clone!(
+            (
+                x,
+                y,
+                a_long_variable_1,
+                a_long_variable_2,
+                a_long_variable_3,
+                a_long_variable_4
+            ),
+            move |a, b| {
+                drop(x);
+                drop(y);
+                drop(a_long_variable_1);
+                drop(a_long_variable_2);
+                drop(a_long_variable_3);
+                drop(a_long_variable_4);
+                println!("{}{}", a, b)
+            }
+        ));
+
+        fn single_expression_body(f: impl FnOnce(usize) -> usize) -> usize {
+            f(20)
+        }
+
+        let _result = single_expression_body(with_clone!(y, move |z| *y + z));
+
+        // Explicitly move all variables
+        drop(x);
+        drop(y);
+        drop(a_long_variable_1);
+        drop(a_long_variable_2);
+        drop(a_long_variable_3);
+        drop(a_long_variable_4);
+    }
+}
+
 pub fn truncate(s: &str, max_chars: usize) -> &str {
     match s.char_indices().nth(max_chars) {
         None => s,

crates/zed/src/main.rs 🔗

@@ -17,7 +17,9 @@ use env_logger::Builder;
 use fs::RealFs;
 use futures::{future, StreamExt};
 use git::GitHostingProviderRegistry;
-use gpui::{App, AppContext, AsyncAppContext, Context, Global, Task, VisualContext};
+use gpui::{
+    App, AppContext, AsyncAppContext, Context, Global, Task, UpdateGlobal as _, VisualContext,
+};
 use image_viewer;
 use language::LanguageRegistry;
 use log::LevelFilter;
@@ -38,11 +40,7 @@ use std::{
     sync::Arc,
 };
 use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
-use util::{
-    maybe, parse_env_output,
-    paths::{self},
-    ResultExt, TryFutureExt,
-};
+use util::{maybe, parse_env_output, paths, with_clone, ResultExt, TryFutureExt};
 use uuid::Uuid;
 use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
 use workspace::{AppState, WorkspaceSettings, WorkspaceStore};
@@ -260,13 +258,11 @@ fn main() {
     let session_id = Uuid::new_v4().to_string();
     reliability::init_panic_hook(&app, installation_id.clone(), session_id.clone());
 
-    let (listener, mut open_rx) = OpenListener::new();
-    let listener = Arc::new(listener);
-    let open_listener = listener.clone();
+    let (open_listener, mut open_rx) = OpenListener::new();
 
     #[cfg(target_os = "linux")]
     {
-        if crate::zed::listen_for_cli_connections(listener.clone()).is_err() {
+        if crate::zed::listen_for_cli_connections(open_listener.clone()).is_err() {
             println!("zed is already running");
             return;
         }
@@ -317,7 +313,7 @@ fn main() {
         })
     };
 
-    app.on_open_urls(move |urls| open_listener.open_urls(urls));
+    app.on_open_urls(with_clone!(open_listener, move |urls| open_listener.open_urls(urls)));
     app.on_reopen(move |cx| {
         if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
         {
@@ -338,7 +334,7 @@ fn main() {
         GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
         git_hosting_providers::init(cx);
 
-        OpenListener::set_global(listener.clone(), cx);
+        OpenListener::set_global(cx, open_listener.clone());
 
         settings::init(cx);
         handle_settings_file_changes(user_settings_file_rx, cx);
@@ -396,7 +392,7 @@ fn main() {
             .collect();
 
         if !urls.is_empty() {
-            listener.open_urls(urls)
+            open_listener.open_urls(urls)
         }
 
         match open_rx

crates/zed/src/zed.rs 🔗

@@ -11,7 +11,7 @@ use collections::VecDeque;
 use editor::{scroll::Autoscroll, Editor, MultiBuffer};
 use gpui::{
     actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem, PromptLevel,
-    TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions,
+    ReadGlobal, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions,
 };
 pub use open_listener::*;
 

crates/zed/src/zed/open_listener.rs 🔗

@@ -90,30 +90,19 @@ impl OpenRequest {
     }
 }
 
-pub struct OpenListener {
-    tx: UnboundedSender<Vec<String>>,
-}
-
-struct GlobalOpenListener(Arc<OpenListener>);
+#[derive(Clone)]
+pub struct OpenListener(UnboundedSender<Vec<String>>);
 
-impl Global for GlobalOpenListener {}
+impl Global for OpenListener {}
 
 impl OpenListener {
-    pub fn global(cx: &AppContext) -> Arc<Self> {
-        cx.global::<GlobalOpenListener>().0.clone()
-    }
-
-    pub fn set_global(listener: Arc<OpenListener>, cx: &mut AppContext) {
-        cx.set_global(GlobalOpenListener(listener))
-    }
-
     pub fn new() -> (Self, UnboundedReceiver<Vec<String>>) {
         let (tx, rx) = mpsc::unbounded();
-        (OpenListener { tx }, rx)
+        (OpenListener(tx), rx)
     }
 
     pub fn open_urls(&self, urls: Vec<String>) {
-        self.tx
+        self.0
             .unbounded_send(urls)
             .map_err(|_| anyhow!("no listener for open requests"))
             .log_err();
@@ -121,7 +110,7 @@ impl OpenListener {
 }
 
 #[cfg(target_os = "linux")]
-pub fn listen_for_cli_connections(opener: Arc<OpenListener>) -> Result<()> {
+pub fn listen_for_cli_connections(opener: OpenListener) -> Result<()> {
     use release_channel::RELEASE_CHANNEL_NAME;
     use std::os::{linux::net::SocketAddrExt, unix::net::SocketAddr, unix::net::UnixDatagram};