Files
ghostty-rs/crates/ghostty-macos/src/helpers/extensions/string.rs
T
ddidderr 0137168c66 feat(macos): foundation of the macOS app ported from Swift to Rust
Phase 9, MA1: crates/ghostty-macos (AppKit via objc2; empty stub on
non-Apple targets) ports the foundation of Ghostty 1.3.1's Swift macOS
app (ghostty/macos): main, AppDelegate (+Ghostty) with lifecycle, dock
menu, open-file, notifications, shortcut sync and quit/reopen paths;
MainMenu.xib as generated menu tables built into NSMenus; the
Ghostty.App/Config/Surface wrappers driving libghostty in-process via
ghostty-lib; the generic helpers; scripts/macos-bundle.sh to assemble
Ghostty.app (dry-run on Linux). Feature areas are stubbed for MA2-MA5.
Design and type mapping: docs/rewrite/notes/macos-app-design.md
(App Intents is a Swift-only framework and cannot be ported).

Compile-checked only (no macOS here): just check-macos passes for
aarch64/x86_64-apple-darwin with -D warnings; 125 platform-independent
tests run on Linux.

Test Plan:
- just check-macos
- cargo test -p ghostty-macos
2026-10-12 19:25:44 +02:00

121 lines
3.6 KiB
Rust

//! `Helpers/Extensions/String+Extension.swift`.
use std::path::PathBuf;
/// `String.truncate(length:trailing:)`: counts Characters (extended grapheme
/// clusters in Swift; Unicode scalars here, which is identical for the text
/// this is used on).
#[must_use]
pub fn truncate(s: &str, length: usize, trailing: &str) -> String {
let trailing_len = trailing.chars().count();
let count = s.chars().count();
if length <= trailing_len || s.is_empty() || count <= length {
return s.to_string();
}
let max_length = length - trailing_len;
let mut out: String = s.chars().take(max_length).collect();
out.push_str(trailing);
out
}
/// `truncate(length:)` with the default `"…"` trailing text.
#[must_use]
pub fn truncate_default(s: &str, length: usize) -> String {
truncate(s, length, "…")
}
/// `abbreviatedPath`: the path with the home directory abbreviated as `~`.
#[must_use]
pub fn abbreviated_path(path: &str, home: &str) -> String {
match path.strip_prefix(home) {
Some(rest) => format!("~{rest}"),
None => path.to_string(),
}
}
/// `String.temporaryFile(_:)`: write the string to `<tmp>/<filename>.txt`
/// (errors are ignored like `try?`) and return the URL path.
#[must_use]
pub fn temporary_file(contents: &str, temp_dir: &std::path::Path, filename: &str) -> PathBuf {
let mut path = temp_dir.join(filename);
path.set_extension("txt");
let _ = std::fs::write(&path, contents);
path
}
/// `String.fourCharCode`: a four-character ASCII string as a big-endian
/// `FourCharCode` (`UInt32`).
#[must_use]
pub fn four_char_code(s: &str) -> u32 {
debug_assert!(
s.chars().count() <= 4,
"FourCharCode string must be at most 4 characters"
);
s.bytes()
.take(4)
.fold(0u32, |acc, b| (acc << 8) | u32::from(b))
}
/// The home-dependent wrapper (`FileManager.homeDirectoryForCurrentUser`).
#[must_use]
pub fn abbreviated_path_for_current_user(path: &str) -> String {
let home = std::env::var("HOME").unwrap_or_default();
abbreviated_path(path, &home)
}
pub trait StringExt {
fn truncate(&self, length: usize) -> String;
fn abbreviated_path(&self) -> String;
fn four_char_code(&self) -> u32;
}
impl StringExt for str {
fn truncate(&self, length: usize) -> String {
truncate_default(self, length)
}
fn abbreviated_path(&self) -> String {
abbreviated_path_for_current_user(self)
}
fn four_char_code(&self) -> u32 {
four_char_code(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncate_cases() {
assert_eq!(truncate_default("hello", 10), "hello");
assert_eq!(truncate_default("hello world", 8), "hello w…");
assert_eq!(truncate_default("hello", 5), "hello");
assert_eq!(truncate_default("", 3), "");
// maxLength <= 0 returns self
assert_eq!(truncate("abcdef", 1, "…"), "abcdef");
assert_eq!(truncate("abcdef", 3, ".."), "a..");
}
#[test]
fn abbreviates_home() {
assert_eq!(abbreviated_path("/Users/x/dev", "/Users/x"), "~/dev");
assert_eq!(abbreviated_path("/opt/x", "/Users/x"), "/opt/x");
}
#[test]
fn four_cc() {
assert_eq!(four_char_code("why?"), 0x7768_793F);
assert_eq!(four_char_code("ab"), 0x6162);
assert_eq!(four_char_code(""), 0);
}
#[test]
fn temp_file_written() {
let dir = std::env::temp_dir();
let p = temporary_file("data", &dir, "ghostty-macos-test-temp");
assert_eq!(p.extension().unwrap(), "txt");
assert_eq!(std::fs::read_to_string(&p).unwrap(), "data");
let _ = std::fs::remove_file(p);
}
}