Refactor to use `SharedString` in more places (#23813)

Richard Feldman and Nathan created

Splitting this off from
https://github.com/zed-industries/zed/pull/23808, per @maxdeviant's
suggestion!

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>

Change summary

crates/editor/src/editor.rs                       |  2 
crates/gpui/src/shared_string.rs                  |  5 +++
crates/language/src/buffer_tests.rs               |  2 
crates/language/src/language.rs                   |  4 +-
crates/language/src/language_registry.rs          | 24 ++++++++++++----
crates/language_selector/src/language_selector.rs |  4 +-
crates/language_tools/src/syntax_tree_view.rs     |  2 
crates/project/src/lsp_command/signature_help.rs  |  2 
crates/project/src/project.rs                     |  2 
crates/project/src/task_inventory.rs              |  8 ++--
crates/project/src/toolchain_store.rs             |  5 ++
crates/prompt_library/src/prompts.rs              |  2 
crates/repl/src/repl_editor.rs                    |  2 
crates/workspace/src/persistence.rs               |  4 +-
crates/zed/src/zed/quick_action_bar/repl_menu.rs  |  2 
15 files changed, 45 insertions(+), 25 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -7042,7 +7042,7 @@ impl Editor {
             let mut should_rewrap = is_vim_mode == IsVimMode::Yes;
 
             if let Some(language_scope) = buffer.language_scope_at(selection.head()) {
-                match language_scope.language_name().0.as_ref() {
+                match language_scope.language_name().as_ref() {
                     "Markdown" | "Plain Text" => {
                         should_rewrap = true;
                     }

crates/gpui/src/shared_string.rs 🔗

@@ -15,6 +15,11 @@ impl SharedString {
     pub const fn new_static(str: &'static str) -> Self {
         Self(ArcCow::Borrowed(str))
     }
+
+    /// Creates a [`SharedString`] from anything that can become an Arc<str>
+    pub fn new(str: impl Into<Arc<str>>) -> Self {
+        SharedString(ArcCow::Owned(str.into()))
+    }
 }
 
 impl JsonSchema for SharedString {

crates/language/src/buffer_tests.rs 🔗

@@ -2424,7 +2424,7 @@ fn test_language_at_with_hidden_languages(cx: &mut App) {
             assert_eq!(config.language_name(), "Markdown".into());
 
             let language = snapshot.language_at(point).unwrap();
-            assert_eq!(language.name().0.as_ref(), "Markdown");
+            assert_eq!(language.name().as_ref(), "Markdown");
         }
 
         buffer

crates/language/src/language.rs 🔗

@@ -256,7 +256,7 @@ impl CachedLspAdapter {
 
     pub fn language_id(&self, language_name: &LanguageName) -> String {
         self.language_ids
-            .get(language_name.0.as_ref())
+            .get(language_name.as_ref())
             .cloned()
             .unwrap_or_else(|| language_name.lsp_id())
     }
@@ -1462,7 +1462,7 @@ impl Language {
         self.config
             .code_fence_block_name
             .clone()
-            .unwrap_or_else(|| self.config.name.0.to_lowercase().into())
+            .unwrap_or_else(|| self.config.name.as_ref().to_lowercase().into())
     }
 
     pub fn context_provider(&self) -> Option<Arc<dyn ContextProvider>> {

crates/language/src/language_registry.rs 🔗

@@ -14,7 +14,7 @@ use futures::{
     Future,
 };
 use globset::GlobSet;
-use gpui::{App, BackgroundExecutor};
+use gpui::{App, BackgroundExecutor, SharedString};
 use lsp::LanguageServerId;
 use parking_lot::{Mutex, RwLock};
 use postage::watch;
@@ -36,15 +36,15 @@ use util::{maybe, paths::PathExt, post_inc, ResultExt};
 #[derive(
     Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
 )]
-pub struct LanguageName(pub Arc<str>);
+pub struct LanguageName(SharedString);
 
 impl LanguageName {
     pub fn new(s: &str) -> Self {
-        Self(Arc::from(s))
+        Self(SharedString::new(s))
     }
 
     pub fn from_proto(s: String) -> Self {
-        Self(Arc::from(s))
+        Self(SharedString::from(s))
     }
     pub fn to_proto(self) -> String {
         self.0.to_string()
@@ -57,6 +57,18 @@ impl LanguageName {
     }
 }
 
+impl From<LanguageName> for SharedString {
+    fn from(value: LanguageName) -> Self {
+        value.0
+    }
+}
+
+impl AsRef<str> for LanguageName {
+    fn as_ref(&self) -> &str {
+        self.0.as_ref()
+    }
+}
+
 impl Borrow<str> for LanguageName {
     fn borrow(&self) -> &str {
         self.0.as_ref()
@@ -71,7 +83,7 @@ impl std::fmt::Display for LanguageName {
 
 impl<'a> From<&'a str> for LanguageName {
     fn from(str: &'a str) -> LanguageName {
-        LanguageName(str.into())
+        LanguageName(SharedString::new(str))
     }
 }
 
@@ -657,7 +669,7 @@ impl LanguageRegistry {
                 .iter()
                 .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
             let custom_suffixes = user_file_types
-                .and_then(|types| types.get(&language_name.0))
+                .and_then(|types| types.get(language_name.as_ref()))
                 .unwrap_or(&empty);
             let path_matches_custom_suffix = path_suffixes
                 .iter()

crates/language_selector/src/language_selector.rs 🔗

@@ -141,7 +141,7 @@ impl LanguageSelectorDelegate {
         let need_icon = FileFinderSettings::get_global(cx).file_icons;
         if let Some(buffer_language) = buffer_language {
             let buffer_language_name = buffer_language.name();
-            if buffer_language_name.0.as_ref() == mat.string.as_str() {
+            if buffer_language_name.as_ref() == mat.string.as_str() {
                 label.push_str(" (current)");
                 let icon = need_icon
                     .then(|| self.language_icon(&buffer_language.config().matcher, cx))
@@ -154,7 +154,7 @@ impl LanguageSelectorDelegate {
             let language_name = LanguageName::new(mat.string.as_str());
             match self
                 .language_registry
-                .available_language_for_name(&language_name.0)
+                .available_language_for_name(language_name.as_ref())
             {
                 Some(available_language) => {
                     let icon = self.language_icon(available_language.matcher(), cx);

crates/language_tools/src/syntax_tree_view.rs 🔗

@@ -499,7 +499,7 @@ impl SyntaxTreeToolbarItemView {
 
     fn render_header(active_layer: &OwnedSyntaxLayer) -> ButtonLike {
         ButtonLike::new("syntax tree header")
-            .child(Label::new(active_layer.language.name().0))
+            .child(Label::new(active_layer.language.name()))
             .child(Label::new(format_node_range(active_layer.node())))
     }
 }

crates/project/src/lsp_command/signature_help.rs 🔗

@@ -86,7 +86,7 @@ impl SignatureHelp {
         } else {
             let markdown = markdown.join(str_for_join);
             let language_name = language
-                .map(|n| n.name().0.to_lowercase())
+                .map(|n| n.name().as_ref().to_lowercase())
                 .unwrap_or_default();
 
             let markdown = if function_options_count >= 2 {

crates/project/src/project.rs 🔗

@@ -2579,7 +2579,7 @@ impl Project {
         language_name: LanguageName,
     ) -> Option<SharedString> {
         languages
-            .language_for_name(&language_name.0)
+            .language_for_name(language_name.as_ref())
             .await
             .ok()?
             .toolchain_lister()

crates/project/src/task_inventory.rs 🔗

@@ -10,7 +10,7 @@ use std::{
 
 use anyhow::{Context as _, Result};
 use collections::{HashMap, HashSet, VecDeque};
-use gpui::{App, AppContext as _, Entity, Task};
+use gpui::{App, AppContext as _, Entity, SharedString, Task};
 use itertools::Itertools;
 use language::{ContextProvider, File, Language, LanguageToolchainStore, Location};
 use settings::{parse_json_with_comments, SettingsLocation};
@@ -53,7 +53,7 @@ pub enum TaskSourceKind {
         abs_path: PathBuf,
     },
     /// Languages-specific tasks coming from extensions.
-    Language { name: Arc<str> },
+    Language { name: SharedString },
 }
 
 impl TaskSourceKind {
@@ -91,7 +91,7 @@ impl Inventory {
         cx: &App,
     ) -> Vec<(TaskSourceKind, TaskTemplate)> {
         let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
-            name: language.name().0,
+            name: language.name().into(),
         });
         let global_tasks = self.global_templates_from_settings();
         let language_tasks = language
@@ -124,7 +124,7 @@ impl Inventory {
             .as_ref()
             .and_then(|location| location.buffer.read(cx).language_at(location.range.start));
         let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
-            name: language.name().0,
+            name: language.name().into(),
         });
         let file = location
             .as_ref()

crates/project/src/toolchain_store.rs 🔗

@@ -316,7 +316,10 @@ impl LocalToolchainStore {
 
             cx.background_executor()
                 .spawn(async move {
-                    let language = registry.language_for_name(&language_name.0).await.ok()?;
+                    let language = registry
+                        .language_for_name(language_name.as_ref())
+                        .await
+                        .ok()?;
                     let toolchains = language.toolchain_lister()?;
                     Some(toolchains.list(root.to_path_buf(), project_env).await)
                 })

crates/prompt_library/src/prompts.rs 🔗

@@ -224,7 +224,7 @@ impl PromptBuilder {
         buffer: BufferSnapshot,
         range: Range<usize>,
     ) -> Result<String, RenderError> {
-        let content_type = match language_name.as_ref().map(|l| l.0.as_ref()) {
+        let content_type = match language_name.as_ref().map(|l| l.as_ref()) {
             None | Some("Markdown" | "Plain Text") => "text",
             Some(_) => "code",
         };

crates/repl/src/repl_editor.rs 🔗

@@ -454,7 +454,7 @@ fn markdown_code_blocks(buffer: &BufferSnapshot, range: Range<Point>) -> Vec<Ran
 }
 
 fn language_supported(language: &Arc<Language>) -> bool {
-    match language.name().0.as_ref() {
+    match language.name().as_ref() {
         "TypeScript" | "Python" => true,
         _ => false,
     }

crates/workspace/src/persistence.rs 🔗

@@ -1088,7 +1088,7 @@ impl WorkspaceDb {
                 .context("Preparing insertion")?;
 
             let toolchain: Vec<(String, String, String)> =
-                select((workspace_id, language_name.0.to_owned(), worktree_id.to_usize()))?;
+                select((workspace_id, language_name.as_ref().to_string(), worktree_id.to_usize()))?;
 
             Ok(toolchain.into_iter().next().and_then(|(name, path, raw_json)| Some(Toolchain {
                 name: name.into(),
@@ -1144,7 +1144,7 @@ impl WorkspaceDb {
             insert((
                 workspace_id,
                 worktree_id.to_usize(),
-                toolchain.language_name.0.as_ref(),
+                toolchain.language_name.as_ref(),
                 toolchain.name.as_ref(),
                 toolchain.path.as_ref(),
             ))?;

crates/zed/src/zed/quick_action_bar/repl_menu.rs 🔗

@@ -69,7 +69,7 @@ impl QuickActionBar {
                 return self.render_repl_launch_menu(spec, cx);
             }
             SessionSupport::RequiresSetup(language) => {
-                return self.render_repl_setup(&language.0, cx);
+                return self.render_repl_setup(language.as_ref(), cx);
             }
             SessionSupport::Unsupported => return None,
         };