Add initial FreeBSD support (#20480)

Caleb Heydon created

This PR adds initial support for FreeBSD
(https://github.com/zed-industries/zed/issues/15309). While there is
still work left to be done, it seems to be usable. As discussed by
@syobocat (https://github.com/zed-industries/zed/discussions/10247), the
changes were just adding ```target_os = "freebsd"``` to wherever it
checks if the OS is Linux.


![image](https://github.com/user-attachments/assets/80ea5b29-047f-4cbd-8263-42e5fa6c94b7)

Needs to be build with ```RUSTFLAGS="-C link-dead-code"```

Known Issues:
- There's an issue in ```crates/project/src/environment.rs``` where a
command fails because ```/bin/sh``` on FreeBSD doesn't support the
```-l``` option.

![image](https://github.com/user-attachments/assets/c3c38633-160f-4f47-8840-e3da67f6ebc8)
- The file/folder choosers provided by the ```ashpd``` crate don't work
on FreeBSD (at least with KDE). This isn't that bad since a fallback
dialog is used.

![image](https://github.com/user-attachments/assets/29373006-1eb9-4ed0-bd52-2d0047fab418)
 - Moving to trash won't work.
- Numerous tests fail (when running on FreeBSD). While I haven't looked
into this much, it appears that the corresponding features seem to work
fine.

Release Notes:

- Added initial support for FreeBSD

Change summary

crates/cli/Cargo.toml                                |  2 
crates/cli/src/main.rs                               | 13 ++-
crates/client/src/telemetry.rs                       |  4 
crates/editor/src/editor.rs                          |  2 
crates/editor/src/element.rs                         |  6 +
crates/extension/src/extension_builder.rs            |  2 
crates/fs/Cargo.toml                                 |  2 
crates/fs/src/fs.rs                                  | 18 ++--
crates/fs/src/linux_watcher.rs                       |  3 
crates/gpui/Cargo.toml                               |  4 
crates/gpui/src/app.rs                               |  4 
crates/gpui/src/keymap/context.rs                    |  9 ++
crates/gpui/src/platform.rs                          | 49 +++++++++----
crates/gpui/src/platform/keystroke.rs                |  4 
crates/gpui/src/platform/linux/xdg_desktop_portal.rs |  2 
crates/gpui/src/platform/test/platform.rs            | 10 +-
crates/gpui/src/scene.rs                             | 25 +++++-
crates/gpui/src/style.rs                             |  2 
crates/gpui/src/window.rs                            |  2 
crates/markdown/src/markdown.rs                      |  2 
crates/paths/src/paths.rs                            |  6 
crates/sqlez/src/connection.rs                       | 10 +-
crates/sqlez_macros/src/sqlez_macros.rs              |  6 
crates/terminal/src/terminal.rs                      |  8 +-
crates/title_bar/src/title_bar.rs                    |  2 
crates/ui/src/styles/platform.rs                     |  2 
crates/util/src/paths.rs                             |  4 
crates/vim/src/replace.rs                            |  2 
crates/vim/src/state.rs                              |  8 +-
crates/worktree/src/worktree_tests.rs                |  2 
crates/zed/Cargo.toml                                |  2 
crates/zed/src/main.rs                               | 10 +-
crates/zed/src/zed.rs                                |  6 
crates/zed/src/zed/open_listener.rs                  |  2 
34 files changed, 139 insertions(+), 96 deletions(-)

Detailed changes

crates/cli/Cargo.toml 🔗

@@ -29,7 +29,7 @@ serde.workspace = true
 util.workspace = true
 tempfile.workspace = true
 
-[target.'cfg(target_os = "linux")'.dependencies]
+[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
 exec.workspace =  true
 fork.workspace = true
 

crates/cli/src/main.rs 🔗

@@ -1,4 +1,7 @@
-#![cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
+#![cfg_attr(
+    any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
+    allow(dead_code)
+)]
 
 use anyhow::{Context, Result};
 use clap::Parser;
@@ -88,7 +91,7 @@ fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
 
 fn main() -> Result<()> {
     // Exit flatpak sandbox if needed
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     {
         flatpak::try_restart_to_host();
         flatpak::ld_extra_libs();
@@ -106,7 +109,7 @@ fn main() -> Result<()> {
     }
     let args = Args::parse();
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     let args = flatpak::set_bin_if_no_escape(args);
 
     let app = Detect::detect(args.zed.as_deref()).context("Bundle detection")?;
@@ -220,7 +223,7 @@ fn main() -> Result<()> {
     Ok(())
 }
 
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 mod linux {
     use std::{
         env,
@@ -344,7 +347,7 @@ mod linux {
     }
 }
 
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 mod flatpak {
     use std::ffi::OsString;
     use std::path::PathBuf;

crates/client/src/telemetry.rs 🔗

@@ -100,7 +100,7 @@ pub fn os_name() -> String {
     {
         "macOS".to_string()
     }
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     {
         format!("Linux {}", gpui::guess_compositor())
     }
@@ -129,7 +129,7 @@ pub fn os_version() -> String {
             .to_string()
         }
     }
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     {
         use std::path::Path;
 

crates/editor/src/editor.rs 🔗

@@ -2564,7 +2564,7 @@ impl Editor {
         cx.invalidate_character_coordinates();
 
         // Copy selections to primary selection buffer
-        #[cfg(target_os = "linux")]
+        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
         if local {
             let selections = self.selections.all::<usize>(cx);
             let buffer_handle = self.buffer.read(cx).read(cx);

crates/editor/src/element.rs 🔗

@@ -650,12 +650,14 @@ impl EditorElement {
             cx.stop_propagation();
         } else if end_selection && pending_nonempty_selections {
             cx.stop_propagation();
-        } else if cfg!(target_os = "linux") && event.button == MouseButton::Middle {
+        } else if cfg!(any(target_os = "linux", target_os = "freebsd"))
+            && event.button == MouseButton::Middle
+        {
             if !text_hitbox.is_hovered(cx) || editor.read_only(cx) {
                 return;
             }
 
-            #[cfg(target_os = "linux")]
+            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
             if EditorSettings::get_global(cx).middle_click_paste {
                 if let Some(text) = cx.read_from_primary().and_then(|item| item.text()) {
                     let point_for_position =

crates/extension/src/extension_builder.rs 🔗

@@ -36,7 +36,7 @@ const WASI_ADAPTER_URL: &str =
 const WASI_SDK_URL: &str = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/";
 const WASI_SDK_ASSET_NAME: Option<&str> = if cfg!(target_os = "macos") {
     Some("wasi-sdk-21.0-macos.tar.gz")
-} else if cfg!(target_os = "linux") {
+} else if cfg!(any(target_os = "linux", target_os = "freebsd")) {
     Some("wasi-sdk-21.0-linux.tar.gz")
 } else if cfg!(target_os = "windows") {
     Some("wasi-sdk-21.0.m-mingw.tar.gz")

crates/fs/Cargo.toml 🔗

@@ -43,7 +43,7 @@ notify = "6.1.1"
 [target.'cfg(target_os = "windows")'.dependencies]
 windows.workspace = true
 
-[target.'cfg(target_os = "linux")'.dependencies]
+[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
 ashpd.workspace = true
 
 [dev-dependencies]

crates/fs/src/fs.rs 🔗

@@ -1,15 +1,15 @@
 #[cfg(target_os = "macos")]
 mod mac_watcher;
 
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 pub mod linux_watcher;
 
 use anyhow::{anyhow, Result};
 use git::GitHostingProviderRegistry;
 
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 use ashpd::desktop::trash;
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 use std::fs::File;
 #[cfg(unix)]
 use std::os::fd::AsFd;
@@ -217,7 +217,7 @@ impl FileHandle for std::fs::File {
         Ok(path)
     }
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
         let fd = self.as_fd();
         let fd_path = format!("/proc/self/fd/{}", fd.as_raw_fd());
@@ -391,7 +391,7 @@ impl Fs for RealFs {
         Ok(())
     }
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
         let file = File::open(path)?;
         match trash::trash_file(&file.as_fd()).await {
@@ -423,7 +423,7 @@ impl Fs for RealFs {
         self.trash_file(path, options).await
     }
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     async fn trash_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
         self.trash_file(path, options).await
     }
@@ -468,7 +468,7 @@ impl Fs for RealFs {
 
     async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
         smol::unblock(move || {
-            let mut tmp_file = if cfg!(target_os = "linux") {
+            let mut tmp_file = if cfg!(any(target_os = "linux", target_os = "freebsd")) {
                 // Use the directory of the destination as temp dir to avoid
                 // invalid cross-device link error, and XDG_CACHE_DIR for fallback.
                 // See https://github.com/zed-industries/zed/pull/8437 for more details.
@@ -634,7 +634,7 @@ impl Fs for RealFs {
         )
     }
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     async fn watch(
         &self,
         path: &Path,
@@ -781,7 +781,7 @@ impl Fs for RealFs {
     }
 }
 
-#[cfg(not(target_os = "linux"))]
+#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
 impl Watcher for RealWatcher {
     fn add(&self, _: &Path) -> Result<()> {
         Ok(())

crates/fs/src/linux_watcher.rs 🔗

@@ -85,7 +85,10 @@ impl Watcher for LinuxWatcher {
 
 pub struct GlobalWatcher {
     // two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
+    #[cfg(target_os = "linux")]
     pub(super) inotify: Mutex<notify::INotifyWatcher>,
+    #[cfg(target_os = "freebsd")]
+    pub(super) inotify: Mutex<notify::KqueueWatcher>,
     pub(super) watchers: Mutex<Vec<Box<dyn Fn(&notify::Event) + Send + Sync>>>,
 }
 

crates/gpui/Cargo.toml 🔗

@@ -139,10 +139,10 @@ media.workspace = true
 metal = "0.29"
 objc = "0.2"
 
-[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
+[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))'.dependencies]
 pathfinder_geometry = "0.5"
 
-[target.'cfg(target_os = "linux")'.dependencies]
+[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
 # Always used
 flume = "0.11"
 oo7 = "0.3.0"

crates/gpui/src/app.rs 🔗

@@ -614,7 +614,7 @@ impl AppContext {
 
     /// Writes data to the primary selection buffer.
     /// Only available on Linux.
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     pub fn write_to_primary(&self, item: ClipboardItem) {
         self.platform.write_to_primary(item)
     }
@@ -626,7 +626,7 @@ impl AppContext {
 
     /// Reads data from the primary selection buffer.
     /// Only available on Linux.
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     pub fn read_from_primary(&self) -> Option<ClipboardItem> {
         self.platform.read_from_primary()
     }

crates/gpui/src/keymap/context.rs 🔗

@@ -33,11 +33,16 @@ impl KeyContext {
         let mut context = Self::default();
         #[cfg(target_os = "macos")]
         context.set("os", "macos");
-        #[cfg(target_os = "linux")]
+        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
         context.set("os", "linux");
         #[cfg(target_os = "windows")]
         context.set("os", "windows");
-        #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
+        #[cfg(not(any(
+            target_os = "macos",
+            target_os = "linux",
+            target_os = "freebsd",
+            target_os = "windows"
+        )))]
         context.set("os", "unknown");
         context
     }

crates/gpui/src/platform.rs 🔗

@@ -4,14 +4,17 @@
 mod app_menu;
 mod keystroke;
 
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 mod linux;
 
 #[cfg(target_os = "macos")]
 mod mac;
 
 #[cfg(any(
-    all(target_os = "linux", any(feature = "x11", feature = "wayland")),
+    all(
+        any(target_os = "linux", target_os = "freebsd"),
+        any(feature = "x11", feature = "wayland")
+    ),
     target_os = "windows",
     feature = "macos-blade"
 ))]
@@ -57,7 +60,7 @@ use uuid::Uuid;
 pub use app_menu::*;
 pub use keystroke::*;
 
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 pub(crate) use linux::*;
 #[cfg(target_os = "macos")]
 pub(crate) use mac::*;
@@ -72,7 +75,7 @@ pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
     Rc::new(MacPlatform::new(headless))
 }
 
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
     if headless {
         return Rc::new(HeadlessClient::new());
@@ -92,7 +95,7 @@ pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
 
 /// Return which compositor we're guessing we'll use.
 /// Does not attempt to connect to the given compositor
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 #[inline]
 pub fn guess_compositor() -> &'static str {
     if std::env::var_os("ZED_HEADLESS").is_some() {
@@ -192,10 +195,10 @@ pub(crate) trait Platform: 'static {
     fn set_cursor_style(&self, style: CursorStyle);
     fn should_auto_hide_scrollbars(&self) -> bool;
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     fn write_to_primary(&self, item: ClipboardItem);
     fn write_to_clipboard(&self, item: ClipboardItem);
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     fn read_from_primary(&self) -> Option<ClipboardItem>;
     fn read_from_clipboard(&self) -> Option<ClipboardItem>;
 
@@ -508,7 +511,10 @@ pub(crate) enum AtlasKey {
 
 impl AtlasKey {
     #[cfg_attr(
-        all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
+        all(
+            any(target_os = "linux", target_os = "freebsd"),
+            not(any(feature = "x11", feature = "wayland"))
+        ),
         allow(dead_code)
     )]
     pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
@@ -572,7 +578,10 @@ pub(crate) struct AtlasTextureId {
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 #[repr(C)]
 #[cfg_attr(
-    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
+    all(
+        any(target_os = "linux", target_os = "freebsd"),
+        not(any(feature = "x11", feature = "wayland"))
+    ),
     allow(dead_code)
 )]
 pub(crate) enum AtlasTextureKind {
@@ -603,7 +612,10 @@ pub(crate) struct PlatformInputHandler {
 }
 
 #[cfg_attr(
-    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
+    all(
+        any(target_os = "linux", target_os = "freebsd"),
+        not(any(feature = "x11", feature = "wayland"))
+    ),
     allow(dead_code)
 )]
 impl PlatformInputHandler {
@@ -625,7 +637,7 @@ impl PlatformInputHandler {
             .flatten()
     }
 
-    #[cfg_attr(target_os = "linux", allow(dead_code))]
+    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
     fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
         self.cx
             .update(|cx| self.handler.text_for_range(range_utf16, cx))
@@ -814,7 +826,10 @@ pub struct WindowOptions {
 /// The variables that can be configured when creating a new window
 #[derive(Debug)]
 #[cfg_attr(
-    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
+    all(
+        any(target_os = "linux", target_os = "freebsd"),
+        not(any(feature = "x11", feature = "wayland"))
+    ),
     allow(dead_code)
 )]
 pub(crate) struct WindowParams {
@@ -825,17 +840,17 @@ pub(crate) struct WindowParams {
     pub titlebar: Option<TitlebarOptions>,
 
     /// The kind of window to create
-    #[cfg_attr(target_os = "linux", allow(dead_code))]
+    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
     pub kind: WindowKind,
 
     /// Whether the window should be movable by the user
-    #[cfg_attr(target_os = "linux", allow(dead_code))]
+    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
     pub is_movable: bool,
 
-    #[cfg_attr(target_os = "linux", allow(dead_code))]
+    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
     pub focus: bool,
 
-    #[cfg_attr(target_os = "linux", allow(dead_code))]
+    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
     pub show: bool,
 
     #[cfg_attr(feature = "wayland", allow(dead_code))]
@@ -1341,7 +1356,7 @@ impl ClipboardString {
             .and_then(|m| serde_json::from_str(m).ok())
     }
 
-    #[cfg_attr(target_os = "linux", allow(dead_code))]
+    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
     pub(crate) fn text_hash(text: &str) -> u64 {
         let mut hasher = SeaHasher::new();
         text.hash(&mut hasher);

crates/gpui/src/platform/keystroke.rs 🔗

@@ -134,7 +134,7 @@ impl Keystroke {
             #[cfg(target_os = "macos")]
             str.push_str("cmd-");
 
-            #[cfg(target_os = "linux")]
+            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
             str.push_str("super-");
 
             #[cfg(target_os = "windows")]
@@ -234,7 +234,7 @@ impl std::fmt::Display for Keystroke {
             #[cfg(target_os = "macos")]
             f.write_char('⌘')?;
 
-            #[cfg(target_os = "linux")]
+            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
             f.write_char('❖')?;
 
             #[cfg(target_os = "windows")]

crates/gpui/src/platform/linux/xdg_desktop_portal.rs 🔗

@@ -162,7 +162,7 @@ impl WindowAppearance {
         }
     }
 
-    #[cfg_attr(target_os = "linux", allow(dead_code))]
+    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
     fn set_native(&mut self, cs: ColorScheme) {
         *self = Self::from_native(cs);
     }

crates/gpui/src/platform/test/platform.rs 🔗

@@ -28,7 +28,7 @@ pub(crate) struct TestPlatform {
     active_display: Rc<dyn PlatformDisplay>,
     active_cursor: Mutex<CursorStyle>,
     current_clipboard_item: Mutex<Option<ClipboardItem>>,
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     current_primary_item: Mutex<Option<ClipboardItem>>,
     pub(crate) prompts: RefCell<TestPrompts>,
     pub opened_url: RefCell<Option<String>>,
@@ -59,7 +59,7 @@ impl TestPlatform {
         #[cfg(target_os = "macos")]
         let text_system = Arc::new(crate::platform::mac::MacTextSystem::new());
 
-        #[cfg(target_os = "linux")]
+        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
         let text_system = Arc::new(crate::platform::linux::CosmicTextSystem::new());
 
         #[cfg(target_os = "windows")]
@@ -76,7 +76,7 @@ impl TestPlatform {
             active_display: Rc::new(TestDisplay::new()),
             active_window: Default::default(),
             current_clipboard_item: Mutex::new(None),
-            #[cfg(target_os = "linux")]
+            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
             current_primary_item: Mutex::new(None),
             weak: weak.clone(),
             opened_url: Default::default(),
@@ -291,7 +291,7 @@ impl Platform for TestPlatform {
         false
     }
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     fn write_to_primary(&self, item: ClipboardItem) {
         *self.current_primary_item.lock() = Some(item);
     }
@@ -300,7 +300,7 @@ impl Platform for TestPlatform {
         *self.current_clipboard_item.lock() = Some(item);
     }
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     fn read_from_primary(&self) -> Option<ClipboardItem> {
         self.current_primary_item.lock().clone()
     }

crates/gpui/src/scene.rs 🔗

@@ -41,7 +41,10 @@ impl Scene {
     }
 
     #[cfg_attr(
-        all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
+        all(
+            any(target_os = "linux", target_os = "freebsd"),
+            not(any(feature = "x11", feature = "wayland"))
+        ),
         allow(dead_code)
     )]
     pub fn paths(&self) -> &[Path<ScaledPixels>] {
@@ -135,7 +138,10 @@ impl Scene {
     }
 
     #[cfg_attr(
-        all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
+        all(
+            any(target_os = "linux", target_os = "freebsd"),
+            not(any(feature = "x11", feature = "wayland"))
+        ),
         allow(dead_code)
     )]
     pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
@@ -167,7 +173,10 @@ impl Scene {
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
 #[cfg_attr(
-    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
+    all(
+        any(target_os = "linux", target_os = "freebsd"),
+        not(any(feature = "x11", feature = "wayland"))
+    ),
     allow(dead_code)
 )]
 pub(crate) enum PrimitiveKind {
@@ -225,7 +234,10 @@ impl Primitive {
 }
 
 #[cfg_attr(
-    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
+    all(
+        any(target_os = "linux", target_os = "freebsd"),
+        not(any(feature = "x11", feature = "wayland"))
+    ),
     allow(dead_code)
 )]
 struct BatchIterator<'a> {
@@ -415,7 +427,10 @@ impl<'a> Iterator for BatchIterator<'a> {
 
 #[derive(Debug)]
 #[cfg_attr(
-    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
+    all(
+        any(target_os = "linux", target_os = "freebsd"),
+        not(any(feature = "x11", feature = "wayland"))
+    ),
     allow(dead_code)
 )]
 pub(crate) enum PrimitiveBatch<'a> {

crates/gpui/src/style.rs 🔗

@@ -344,7 +344,7 @@ impl Default for TextStyle {
         TextStyle {
             color: black(),
             // todo(linux) make this configurable or choose better default
-            font_family: if cfg!(target_os = "linux") {
+            font_family: if cfg!(any(target_os = "linux", target_os = "freebsd")) {
                 "FreeMono".into()
             } else if cfg!(target_os = "windows") {
                 "Segoe UI".into()

crates/gpui/src/window.rs 🔗

@@ -1240,7 +1240,7 @@ impl<'a> WindowContext<'a> {
     /// that currently owns the mouse cursor.
     /// On mac, this is equivalent to `is_window_active`.
     pub fn is_window_hovered(&self) -> bool {
-        if cfg!(target_os = "linux") {
+        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
             self.window.hovered.get()
         } else {
             self.is_window_active()

crates/markdown/src/markdown.rs 🔗

@@ -475,7 +475,7 @@ impl MarkdownElement {
                     }
                 } else if markdown.selection.pending {
                     markdown.selection.pending = false;
-                    #[cfg(target_os = "linux")]
+                    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
                     {
                         let text = rendered_text
                             .text_for_range(markdown.selection.start..markdown.selection.end);

crates/paths/src/paths.rs 🔗

@@ -20,7 +20,7 @@ pub fn config_dir() -> &'static PathBuf {
                 .join("Zed");
         }
 
-        if cfg!(target_os = "linux") {
+        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
             return if let Ok(flatpak_xdg_config) = std::env::var("FLATPAK_XDG_CONFIG_HOME") {
                 flatpak_xdg_config.into()
             } else {
@@ -41,7 +41,7 @@ pub fn support_dir() -> &'static PathBuf {
             return home_dir().join("Library/Application Support/Zed");
         }
 
-        if cfg!(target_os = "linux") {
+        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
             return if let Ok(flatpak_xdg_data) = std::env::var("FLATPAK_XDG_DATA_HOME") {
                 flatpak_xdg_data.into()
             } else {
@@ -76,7 +76,7 @@ pub fn temp_dir() -> &'static PathBuf {
                 .join("Zed");
         }
 
-        if cfg!(target_os = "linux") {
+        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
             return if let Ok(flatpak_xdg_cache) = std::env::var("FLATPAK_XDG_CACHE_HOME") {
                 flatpak_xdg_cache.into()
             } else {

crates/sqlez/src/connection.rs 🔗

@@ -128,10 +128,10 @@ impl Connection {
                             &mut remaining_sql_ptr,
                         );
 
-                        #[cfg(not(target_os = "linux"))]
+                        #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
                         let offset = sqlite3_error_offset(temp_connection.sqlite3);
 
-                        #[cfg(target_os = "linux")]
+                        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
                         let offset = 0;
 
                         (
@@ -149,10 +149,10 @@ impl Connection {
                             &mut remaining_sql_ptr,
                         );
 
-                        #[cfg(not(target_os = "linux"))]
+                        #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
                         let offset = sqlite3_error_offset(self.sqlite3);
 
-                        #[cfg(target_os = "linux")]
+                        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
                         let offset = 0;
 
                         (
@@ -408,7 +408,7 @@ mod test {
         );
     }
 
-    #[cfg(not(target_os = "linux"))]
+    #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
     #[test]
     fn test_sql_has_syntax_errors() {
         let connection = Connection::open_memory(Some("test_sql_has_syntax_errors"));

crates/sqlez_macros/src/sqlez_macros.rs 🔗

@@ -1,7 +1,7 @@
 use proc_macro::{Delimiter, Span, TokenStream, TokenTree};
 use syn::Error;
 
-#[cfg(not(target_os = "linux"))]
+#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
 static SQLITE: std::sync::LazyLock<sqlez::thread_safe_connection::ThreadSafeConnection> =
     std::sync::LazyLock::new(|| {
         sqlez::thread_safe_connection::ThreadSafeConnection::new(
@@ -16,10 +16,10 @@ static SQLITE: std::sync::LazyLock<sqlez::thread_safe_connection::ThreadSafeConn
 pub fn sql(tokens: TokenStream) -> TokenStream {
     let (spans, sql) = make_sql(tokens);
 
-    #[cfg(not(target_os = "linux"))]
+    #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
     let error = SQLITE.sql_has_syntax_error(sql.trim());
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     let error: Option<(String, usize)> = None;
 
     let formatted_sql = sqlformat::format(&sql, &sqlformat::QueryParams::None, Default::default());

crates/terminal/src/terminal.rs 🔗

@@ -818,7 +818,7 @@ impl Terminal {
                         selection.update(point, AlacDirection::Right);
                         term.selection = Some(selection);
 
-                        #[cfg(target_os = "linux")]
+                        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
                         if let Some(selection_text) = term.selection_to_string() {
                             cx.write_to_primary(ClipboardItem::new_string(selection_text));
                         }
@@ -831,7 +831,7 @@ impl Terminal {
             InternalEvent::SetSelection(selection) => {
                 term.selection = selection.as_ref().map(|(sel, _)| sel.clone());
 
-                #[cfg(target_os = "linux")]
+                #[cfg(any(target_os = "linux", target_os = "freebsd"))]
                 if let Some(selection_text) = term.selection_to_string() {
                     cx.write_to_primary(ClipboardItem::new_string(selection_text));
                 }
@@ -852,7 +852,7 @@ impl Terminal {
                     selection.update(point, side);
                     term.selection = Some(selection);
 
-                    #[cfg(target_os = "linux")]
+                    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
                     if let Some(selection_text) = term.selection_to_string() {
                         cx.write_to_primary(ClipboardItem::new_string(selection_text));
                     }
@@ -1512,7 +1512,7 @@ impl Terminal {
                             .push_back(InternalEvent::SetSelection(Some((sel, point))));
                     }
                 }
-                #[cfg(target_os = "linux")]
+                #[cfg(any(target_os = "linux", target_os = "freebsd"))]
                 MouseButton::Middle => {
                     if let Some(item) = _cx.read_from_primary() {
                         let text = item.text().unwrap_or_default().to_string();

crates/title_bar/src/title_bar.rs 🔗

@@ -75,7 +75,7 @@ impl Render for TitleBar {
         let height = Self::height(cx);
         let supported_controls = cx.window_controls();
         let decorations = cx.window_decorations();
-        let titlebar_color = if cfg!(target_os = "linux") {
+        let titlebar_color = if cfg!(any(target_os = "linux", target_os = "freebsd")) {
             if cx.is_window_active() && !self.should_move {
                 cx.theme().colors().title_bar_background
             } else {

crates/ui/src/styles/platform.rs 🔗

@@ -14,7 +14,7 @@ pub enum PlatformStyle {
 impl PlatformStyle {
     /// Returns the [`PlatformStyle`] for the current platform.
     pub const fn platform() -> Self {
-        if cfg!(target_os = "linux") {
+        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
             Self::Linux
         } else if cfg!(target_os = "windows") {
             Self::Windows

crates/util/src/paths.rs 🔗

@@ -57,7 +57,7 @@ impl<T: AsRef<Path>> PathExt for T {
     ///   does not have the user's home directory prefix, or if we are not on
     ///   Linux or macOS, the original path is returned unchanged.
     fn compact(&self) -> PathBuf {
-        if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
+        if cfg!(any(target_os = "linux", target_os = "freebsd")) || cfg!(target_os = "macos") {
             match self.as_ref().strip_prefix(home_dir().as_path()) {
                 Ok(relative_path) => {
                     let mut shortened_path = PathBuf::new();
@@ -699,7 +699,7 @@ mod tests {
         ]
         .iter()
         .collect();
-        if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
+        if cfg!(any(target_os = "linux", target_os = "freebsd")) || cfg!(target_os = "macos") {
             assert_eq!(path.compact().to_str(), Some("~/some_file.txt"));
         } else {
             assert_eq!(path.compact().to_str(), path.to_str());

crates/vim/src/replace.rs 🔗

@@ -135,7 +135,7 @@ mod test {
     }
 
     #[gpui::test]
-    #[cfg(not(target_os = "linux"))]
+    #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
     async fn test_replace_mode(cx: &mut gpui::TestAppContext) {
         let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
 

crates/vim/src/state.rs 🔗

@@ -228,9 +228,9 @@ impl VimGlobals {
                     }
                     '*' => {
                         self.registers.insert('"', content.clone());
-                        #[cfg(target_os = "linux")]
+                        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
                         cx.write_to_primary(content.into());
-                        #[cfg(not(target_os = "linux"))]
+                        #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
                         cx.write_to_clipboard(content.into());
                     }
                     '"' => {
@@ -299,11 +299,11 @@ impl VimGlobals {
             '_' | ':' | '.' | '#' | '=' => None,
             '+' => cx.read_from_clipboard().map(|item| item.into()),
             '*' => {
-                #[cfg(target_os = "linux")]
+                #[cfg(any(target_os = "linux", target_os = "freebsd"))]
                 {
                     cx.read_from_primary().map(|item| item.into())
                 }
-                #[cfg(not(target_os = "linux"))]
+                #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
                 {
                     cx.read_from_clipboard().map(|item| item.into())
                 }

crates/worktree/src/worktree_tests.rs 🔗

@@ -842,7 +842,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
     .await
     .unwrap();
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     fs::linux_watcher::global(|_| {}).unwrap();
 
     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())

crates/zed/Cargo.toml 🔗

@@ -126,7 +126,7 @@ windows.workspace = true
 [target.'cfg(target_os = "windows")'.build-dependencies]
 winresource = "0.1"
 
-[target.'cfg(target_os = "linux")'.dependencies]
+[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
 ashpd.workspace = true
 
 [dev-dependencies]

crates/zed/src/main.rs 🔗

@@ -100,12 +100,12 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
     eprintln!(
         "Zed failed to open a window: {e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
     );
-    #[cfg(not(target_os = "linux"))]
+    #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
     {
         process::exit(1);
     }
 
-    #[cfg(target_os = "linux")]
+    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     {
         use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
         _cx.spawn(|_cx| async move {
@@ -172,7 +172,7 @@ fn main() {
         if *db::ZED_STATELESS || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
             false
         } else {
-            #[cfg(target_os = "linux")]
+            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
             {
                 crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
             }
@@ -426,7 +426,7 @@ fn main() {
 
         load_embedded_fonts(cx);
 
-        #[cfg(target_os = "linux")]
+        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
         crate::zed::linux_prompts::init(cx);
 
         app_state.languages.set_theme(cx.theme().clone());
@@ -942,7 +942,7 @@ fn init_logger() {
                     config_builder.set_time_offset(offset);
                 }
 
-                #[cfg(target_os = "linux")]
+                #[cfg(any(target_os = "linux", target_os = "freebsd"))]
                 {
                     config_builder.add_filter_ignore_str("zbus");
                     config_builder.add_filter_ignore_str("blade_graphics::hal::resource");

crates/zed/src/zed.rs 🔗

@@ -1,6 +1,6 @@
 mod app_menus;
 pub mod inline_completion_registry;
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 pub(crate) mod linux_prompts;
 #[cfg(target_os = "macos")]
 pub(crate) mod mac_only_instance;
@@ -153,7 +153,7 @@ pub fn initialize_workspace(
         })
         .detach();
 
-        #[cfg(target_os = "linux")]
+        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
         if let Err(e) = fs::linux_watcher::global(|_| {}) {
             let message = format!(db::indoc!{r#"
                 inotify_init returned {}
@@ -361,7 +361,7 @@ pub fn initialize_workspace(
             })
             .register_action(|_, _: &install_cli::Install, cx| {
                 cx.spawn(|workspace, mut cx| async move {
-                    if cfg!(target_os = "linux") {
+                    if cfg!(any(target_os = "linux", target_os = "freebsd")) {
                         let prompt = cx.prompt(
                             PromptLevel::Warning,
                             "CLI should already be installed",

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

@@ -144,7 +144,7 @@ impl OpenListener {
     }
 }
 
-#[cfg(target_os = "linux")]
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
 pub fn listen_for_cli_connections(opener: OpenListener) -> Result<()> {
     use release_channel::RELEASE_CHANNEL_NAME;
     use std::os::unix::net::UnixDatagram;