util: Replace `lazy_static!` with `OnceLock` (#13215)

Marshall Bowers created

This PR replaces the `lazy_static!` usages in the `util` crate with
`OnceLock` from the standard library.

This allows us to drop the `lazy_static` dependency from this crate.

Release Notes:

- N/A

Change summary

Cargo.lock                      |  1 -
crates/paths/src/paths.rs       | 12 ++++++------
crates/util/Cargo.toml          |  1 -
crates/util/src/paths.rs        | 11 +++++++----
crates/util/src/util.rs         | 22 +++++++++++++---------
crates/worktree/src/worktree.rs |  6 +++---
6 files changed, 29 insertions(+), 24 deletions(-)

Detailed changes

Cargo.lock ๐Ÿ”—

@@ -11703,7 +11703,6 @@ dependencies = [
  "futures-lite 1.13.0",
  "git2",
  "globset",
- "lazy_static",
  "log",
  "rand 0.8.5",
  "regex",

crates/paths/src/paths.rs ๐Ÿ”—

@@ -3,7 +3,7 @@
 use std::path::{Path, PathBuf};
 use std::sync::OnceLock;
 
-use util::paths::HOME;
+pub use util::paths::home_dir;
 
 /// Returns the path to the configuration directory used by Zed.
 pub fn config_dir() -> &'static PathBuf {
@@ -24,7 +24,7 @@ pub fn config_dir() -> &'static PathBuf {
             .join("zed");
         }
 
-        HOME.join(".config").join("zed")
+        home_dir().join(".config").join("zed")
     })
 }
 
@@ -33,7 +33,7 @@ pub fn support_dir() -> &'static PathBuf {
     static SUPPORT_DIR: OnceLock<PathBuf> = OnceLock::new();
     SUPPORT_DIR.get_or_init(|| {
         if cfg!(target_os = "macos") {
-            return HOME.join("Library/Application Support/Zed");
+            return home_dir().join("Library/Application Support/Zed");
         }
 
         if cfg!(target_os = "linux") {
@@ -74,7 +74,7 @@ pub fn temp_dir() -> &'static PathBuf {
             .join("zed");
         }
 
-        HOME.join(".cache").join("zed")
+        home_dir().join(".cache").join("zed")
     })
 }
 
@@ -83,7 +83,7 @@ pub fn logs_dir() -> &'static PathBuf {
     static LOGS_DIR: OnceLock<PathBuf> = OnceLock::new();
     LOGS_DIR.get_or_init(|| {
         if cfg!(target_os = "macos") {
-            HOME.join("Library/Logs/Zed")
+            home_dir().join("Library/Logs/Zed")
         } else {
             support_dir().join("logs")
         }
@@ -112,7 +112,7 @@ pub fn database_dir() -> &'static PathBuf {
 pub fn crashes_dir() -> &'static Option<PathBuf> {
     static CRASHES_DIR: OnceLock<Option<PathBuf>> = OnceLock::new();
     CRASHES_DIR.get_or_init(|| {
-        cfg!(target_os = "macos").then_some(HOME.join("Library/Logs/DiagnosticReports"))
+        cfg!(target_os = "macos").then_some(home_dir().join("Library/Logs/DiagnosticReports"))
     })
 }
 

crates/util/Cargo.toml ๐Ÿ”—

@@ -22,7 +22,6 @@ dirs.workspace = true
 futures.workspace = true
 git2 = { workspace = true, optional = true }
 globset.workspace = true
-lazy_static.workspace = true
 log.workspace = true
 rand.workspace = true
 regex.workspace = true

crates/util/src/paths.rs ๐Ÿ”—

@@ -1,3 +1,4 @@
+use std::sync::OnceLock;
 use std::{
     ffi::OsStr,
     path::{Path, PathBuf},
@@ -6,8 +7,10 @@ use std::{
 use globset::{Glob, GlobSet, GlobSetBuilder};
 use serde::{Deserialize, Serialize};
 
-lazy_static::lazy_static! {
-    pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory");
+/// Returns the path to the user's home directory.
+pub fn home_dir() -> &'static PathBuf {
+    static HOME_DIR: OnceLock<PathBuf> = OnceLock::new();
+    HOME_DIR.get_or_init(|| dirs::home_dir().expect("failed to determine home directory"))
 }
 
 pub trait PathExt {
@@ -50,7 +53,7 @@ impl<T: AsRef<Path>> PathExt for T {
     ///   Linux or macOS, the original path is returned unchanged.
     fn compact(&self) -> PathBuf {
         if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
-            match self.as_ref().strip_prefix(HOME.as_path()) {
+            match self.as_ref().strip_prefix(home_dir().as_path()) {
                 Ok(relative_path) => {
                     let mut shortened_path = PathBuf::new();
                     shortened_path.push("~");
@@ -477,7 +480,7 @@ mod tests {
     #[test]
     fn test_path_compact() {
         let path: PathBuf = [
-            HOME.to_string_lossy().to_string(),
+            home_dir().to_string_lossy().to_string(),
             "some_file.txt".to_string(),
         ]
         .iter()

crates/util/src/util.rs ๐Ÿ”—

@@ -6,8 +6,9 @@ pub mod serde;
 pub mod test;
 
 use futures::Future;
-use lazy_static::lazy_static;
 use rand::{seq::SliceRandom, Rng};
+use regex::Regex;
+use std::sync::OnceLock;
 use std::{
     borrow::Cow,
     cmp::{self, Ordering},
@@ -309,13 +310,14 @@ pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut se
 }
 
 pub fn measure<R>(label: &str, f: impl FnOnce() -> R) -> R {
-    lazy_static! {
-        pub static ref ZED_MEASUREMENTS: bool = env::var("ZED_MEASUREMENTS")
+    static ZED_MEASUREMENTS: OnceLock<bool> = OnceLock::new();
+    let zed_measurements = ZED_MEASUREMENTS.get_or_init(|| {
+        env::var("ZED_MEASUREMENTS")
             .map(|measurements| measurements == "1" || measurements == "true")
-            .unwrap_or(false);
-    }
+            .unwrap_or(false)
+    });
 
-    if *ZED_MEASUREMENTS {
+    if *zed_measurements {
         let start = Instant::now();
         let result = f();
         let elapsed = start.elapsed();
@@ -662,15 +664,17 @@ impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
         Some(self.cmp(other))
     }
 }
-lazy_static! {
-    static ref EMOJI_REGEX: regex::Regex = regex::Regex::new("(\\p{Emoji}|\u{200D})").unwrap();
+
+fn emoji_regex() -> &'static Regex {
+    static EMOJI_REGEX: OnceLock<Regex> = OnceLock::new();
+    EMOJI_REGEX.get_or_init(|| Regex::new("(\\p{Emoji}|\u{200D})").unwrap())
 }
 
 /// Returns true if the given string consists of emojis only.
 /// E.g. "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง๐Ÿ‘‹" will return true, but "๐Ÿ‘‹!" will return false.
 pub fn word_consists_of_emojis(s: &str) -> bool {
     let mut prev_end = 0;
-    for capture in EMOJI_REGEX.find_iter(s) {
+    for capture in emoji_regex().find_iter(s) {
         if capture.start() != prev_end {
             return false;
         }

crates/worktree/src/worktree.rs ๐Ÿ”—

@@ -58,7 +58,7 @@ use std::{
 };
 use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet};
 use text::{LineEnding, Rope};
-use util::{paths::HOME, ResultExt};
+use util::{paths::home_dir, ResultExt};
 pub use worktree_settings::WorktreeSettings;
 
 #[cfg(feature = "test-support")]
@@ -2968,9 +2968,9 @@ impl language::File for File {
         } else {
             let path = worktree.abs_path();
 
-            if worktree.is_local() && path.starts_with(HOME.as_path()) {
+            if worktree.is_local() && path.starts_with(home_dir().as_path()) {
                 full_path.push("~");
-                full_path.push(path.strip_prefix(HOME.as_path()).unwrap());
+                full_path.push(path.strip_prefix(home_dir().as_path()).unwrap());
             } else {
                 full_path.push(path)
             }