Rename the `OpenFile` action to `OpenSelectedFilename` to better reflect its function (#22494)

Cole Miller created

Release Notes:

- Renamed the `OpenFile` action to `OpenSelectedFilename` for clarity

Change summary

crates/editor/src/actions.rs              |  5 +
crates/editor/src/editor.rs               |  2 
crates/editor/src/element.rs              |  2 
crates/gpui/src/action.rs                 | 65 +++++++++++++++++++++++-
crates/gpui/src/app.rs                    |  5 +
crates/gpui/src/keymap.rs                 |  2 
crates/gpui_macros/src/register_action.rs |  1 
crates/languages/src/json.rs              |  3 
crates/settings/src/keymap_file.rs        | 61 +++++++++++++++--------
9 files changed, 113 insertions(+), 33 deletions(-)

Detailed changes

crates/editor/src/actions.rs 🔗

@@ -1,6 +1,6 @@
 //! This module contains all actions supported by [`Editor`].
 use super::*;
-use gpui::action_as;
+use gpui::{action_aliases, action_as};
 use util::serde::default_true;
 
 #[derive(PartialEq, Clone, Deserialize, Default)]
@@ -311,7 +311,6 @@ gpui::actions!(
         OpenExcerpts,
         OpenExcerptsSplit,
         OpenProposedChangesEditor,
-        OpenFile,
         OpenDocs,
         OpenPermalinkToLine,
         OpenUrl,
@@ -389,3 +388,5 @@ gpui::actions!(
 );
 
 action_as!(go_to_line, ToggleGoToLine as Toggle);
+
+action_aliases!(editor, OpenSelectedFilename, [OpenFile]);

crates/editor/src/editor.rs 🔗

@@ -9503,7 +9503,7 @@ impl Editor {
         url_finder.detach();
     }
 
-    pub fn open_file(&mut self, _: &OpenFile, cx: &mut ViewContext<Self>) {
+    pub fn open_selected_filename(&mut self, _: &OpenSelectedFilename, cx: &mut ViewContext<Self>) {
         let Some(workspace) = self.workspace() else {
             return;
         };

crates/editor/src/element.rs 🔗

@@ -342,7 +342,7 @@ impl EditorElement {
                 .detach_and_log_err(cx);
         });
         register_action(view, cx, Editor::open_url);
-        register_action(view, cx, Editor::open_file);
+        register_action(view, cx, Editor::open_selected_filename);
         register_action(view, cx, Editor::fold);
         register_action(view, cx, Editor::fold_at_level);
         register_action(view, cx, Editor::fold_all);

crates/gpui/src/action.rs 🔗

@@ -62,6 +62,14 @@ pub trait Action: 'static + Send {
     fn build(value: serde_json::Value) -> Result<Box<dyn Action>>
     where
         Self: Sized;
+
+    /// A list of alternate, deprecated names for this action.
+    fn deprecated_aliases() -> &'static [&'static str]
+    where
+        Self: Sized,
+    {
+        &[]
+    }
 }
 
 impl std::fmt::Debug for dyn Action {
@@ -85,6 +93,7 @@ pub(crate) struct ActionRegistry {
     builders_by_name: HashMap<SharedString, ActionBuilder>,
     names_by_type_id: HashMap<TypeId, SharedString>,
     all_names: Vec<SharedString>, // So we can return a static slice.
+    deprecations: Vec<(SharedString, SharedString)>,
 }
 
 impl Default for ActionRegistry {
@@ -93,6 +102,7 @@ impl Default for ActionRegistry {
             builders_by_name: Default::default(),
             names_by_type_id: Default::default(),
             all_names: Default::default(),
+            deprecations: Default::default(),
         };
 
         this.load_actions();
@@ -111,6 +121,7 @@ pub type MacroActionBuilder = fn() -> ActionData;
 #[doc(hidden)]
 pub struct ActionData {
     pub name: &'static str,
+    pub aliases: &'static [&'static str],
     pub type_id: TypeId,
     pub build: ActionBuilder,
 }
@@ -134,6 +145,7 @@ impl ActionRegistry {
     pub(crate) fn load_action<A: Action>(&mut self) {
         self.insert_action(ActionData {
             name: A::debug_name(),
+            aliases: A::deprecated_aliases(),
             type_id: TypeId::of::<A>(),
             build: A::build,
         });
@@ -142,6 +154,10 @@ impl ActionRegistry {
     fn insert_action(&mut self, action: ActionData) {
         let name: SharedString = action.name.into();
         self.builders_by_name.insert(name.clone(), action.build);
+        for &alias in action.aliases {
+            self.builders_by_name.insert(alias.into(), action.build);
+            self.deprecations.push((alias.into(), name.clone()));
+        }
         self.names_by_type_id.insert(action.type_id, name.clone());
         self.all_names.push(name);
     }
@@ -174,6 +190,10 @@ impl ActionRegistry {
     pub fn all_action_names(&self) -> &[SharedString] {
         self.all_names.as_slice()
     }
+
+    pub fn action_deprecations(&self) -> &[(SharedString, SharedString)] {
+        self.deprecations.as_slice()
+    }
 }
 
 /// Defines unit structs that can be used as actions.
@@ -206,7 +226,7 @@ macro_rules! actions {
 /// `impl_action_as!`
 #[macro_export]
 macro_rules! action_as {
-    ($namespace:path, $name:ident as $visual_name:tt) => {
+    ($namespace:path, $name:ident as $visual_name:ident) => {
         #[doc = "The `"]
         #[doc = stringify!($name)]
         #[doc = "` action, see [`gpui::actions!`]"]
@@ -228,6 +248,43 @@ macro_rules! action_as {
     };
 }
 
+/// Defines a unit struct that can be used as an action, with some deprecated aliases.
+#[macro_export]
+macro_rules! action_aliases {
+    ($namespace:path, $name:ident, [$($alias:ident),* $(,)?]) => {
+        #[doc = "The `"]
+        #[doc = stringify!($name)]
+        #[doc = "` action, see [`gpui::actions!`]"]
+        #[derive(
+            ::std::cmp::PartialEq,
+            ::std::clone::Clone,
+            ::std::default::Default,
+            ::std::fmt::Debug,
+            gpui::private::serde_derive::Deserialize,
+        )]
+        #[serde(crate = "gpui::private::serde")]
+        pub struct $name;
+
+        gpui::__impl_action!(
+            $namespace,
+            $name,
+            $name,
+            fn build(
+                _: gpui::private::serde_json::Value,
+            ) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
+                Ok(Box::new(Self))
+            },
+            fn deprecated_aliases() -> &'static [&'static str] {
+                &[
+                    $(concat!(stringify!($namespace), "::", stringify!($alias))),*
+                ]
+            }
+        );
+
+        gpui::register_action!($name);
+    };
+}
+
 /// Implements the Action trait for any struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize
 #[macro_export]
 macro_rules! impl_actions {
@@ -269,7 +326,7 @@ macro_rules! impl_action_as {
 #[doc(hidden)]
 #[macro_export]
 macro_rules! __impl_action {
-    ($namespace:path, $name:ident, $visual_name:tt, $build:item) => {
+    ($namespace:path, $name:ident, $visual_name:tt, $($items:item),*) => {
         impl gpui::Action for $name {
             fn name(&self) -> &'static str
             {
@@ -291,8 +348,6 @@ macro_rules! __impl_action {
                 )
             }
 
-            $build
-
             fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
                 action
                     .as_any()
@@ -307,6 +362,8 @@ macro_rules! __impl_action {
             fn as_any(&self) -> &dyn ::std::any::Any {
                 self
             }
+
+            $($items)*
         }
     };
 }

crates/gpui/src/app.rs 🔗

@@ -1226,6 +1226,11 @@ impl AppContext {
         self.actions.all_action_names()
     }
 
+    /// Get a list of all deprecated action aliases and their canonical names.
+    pub fn action_deprecations(&self) -> &[(SharedString, SharedString)] {
+        self.actions.action_deprecations()
+    }
+
     /// Register a callback to be invoked when the application is about to quit.
     /// It is not possible to cancel the quit event at this point.
     pub fn on_app_quit<Fut>(

crates/gpui/src/keymap.rs 🔗

@@ -134,8 +134,6 @@ impl Keymap {
     /// If a user has disabled a binding with `"x": null` it will not be returned. Disabled
     /// bindings are evaluated with the same precedence rules so you can disable a rule in
     /// a given context only.
-    ///
-    /// In the case of multi-key bindings, the
     pub fn bindings_for_input(
         &self,
         input: &[Keystroke],

crates/gpui_macros/src/register_action.rs 🔗

@@ -32,6 +32,7 @@ pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream {
                 fn #action_builder_fn_name() -> gpui::ActionData {
                     gpui::ActionData {
                         name: <#type_name as gpui::Action>::debug_name(),
+                        aliases: <#type_name as gpui::Action>::deprecated_aliases(),
                         type_id: ::std::any::TypeId::of::<#type_name>(),
                         build: <#type_name as gpui::Action>::build,
                     }

crates/languages/src/json.rs 🔗

@@ -76,6 +76,7 @@ impl JsonLspAdapter {
 
     fn get_workspace_config(language_names: Vec<String>, cx: &mut AppContext) -> Value {
         let action_names = cx.all_action_names();
+        let deprecations = cx.action_deprecations();
 
         let font_names = &cx.text_system().all_font_names();
         let settings_schema = cx.global::<SettingsStore>().json_schema(
@@ -116,7 +117,7 @@ impl JsonLspAdapter {
                     },
                     {
                         "fileMatch": [schema_file_match(paths::keymap_file())],
-                        "schema": KeymapFile::generate_json_schema(action_names),
+                        "schema": KeymapFile::generate_json_schema(action_names, deprecations),
                     },
                     {
                         "fileMatch": [

crates/settings/src/keymap_file.rs 🔗

@@ -5,7 +5,7 @@ use gpui::{Action, AppContext, KeyBinding, SharedString};
 use schemars::{
     gen::{SchemaGenerator, SchemaSettings},
     schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
-    JsonSchema,
+    JsonSchema, Map,
 };
 use serde::Deserialize;
 use serde_json::Value;
@@ -139,34 +139,51 @@ impl KeymapFile {
         Ok(())
     }
 
-    pub fn generate_json_schema(action_names: &[SharedString]) -> serde_json::Value {
+    pub fn generate_json_schema(
+        action_names: &[SharedString],
+        deprecations: &[(SharedString, SharedString)],
+    ) -> serde_json::Value {
         let mut root_schema = SchemaSettings::draft07()
             .with(|settings| settings.option_add_null_type = false)
             .into_generator()
             .into_root_schema_for::<KeymapFile>();
 
+        let mut alternatives = vec![
+            Schema::Object(SchemaObject {
+                instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
+                enum_values: Some(
+                    action_names
+                        .iter()
+                        .map(|name| Value::String(name.to_string()))
+                        .collect(),
+                ),
+                ..Default::default()
+            }),
+            Schema::Object(SchemaObject {
+                instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Array))),
+                ..Default::default()
+            }),
+            Schema::Object(SchemaObject {
+                instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Null))),
+                ..Default::default()
+            }),
+        ];
+        for (old, new) in deprecations {
+            alternatives.push(Schema::Object(SchemaObject {
+                instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
+                const_value: Some(Value::String(old.to_string())),
+                extensions: Map::from_iter([(
+                    // deprecationMessage is not part of the JSON Schema spec,
+                    // but json-language-server recognizes it.
+                    "deprecationMessage".to_owned(),
+                    format!("Deprecated, use {new}").into(),
+                )]),
+                ..Default::default()
+            }));
+        }
         let action_schema = Schema::Object(SchemaObject {
             subschemas: Some(Box::new(SubschemaValidation {
-                one_of: Some(vec![
-                    Schema::Object(SchemaObject {
-                        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
-                        enum_values: Some(
-                            action_names
-                                .iter()
-                                .map(|name| Value::String(name.to_string()))
-                                .collect(),
-                        ),
-                        ..Default::default()
-                    }),
-                    Schema::Object(SchemaObject {
-                        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Array))),
-                        ..Default::default()
-                    }),
-                    Schema::Object(SchemaObject {
-                        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Null))),
-                        ..Default::default()
-                    }),
-                ]),
+                one_of: Some(alternatives),
                 ..Default::default()
             })),
             ..Default::default()