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
81 lines
2.3 KiB
Rust
81 lines
2.3 KiB
Rust
//! `Helpers/Extensions/Array+Extension.swift`.
|
|
|
|
use std::ffi::CString;
|
|
|
|
/// `extension Array`.
|
|
pub trait ArrayExt<T> {
|
|
/// `subscript(safe:)`.
|
|
fn get_safe(&self, index: isize) -> Option<&T>;
|
|
/// Returns the index before i, with wraparound. Assumes i is a valid index.
|
|
fn index_wrapping_before(&self, i: usize) -> usize;
|
|
/// Returns the index after i, with wraparound. Assumes i is a valid index.
|
|
fn index_wrapping_after(&self, i: usize) -> usize;
|
|
}
|
|
|
|
impl<T> ArrayExt<T> for [T] {
|
|
fn get_safe(&self, index: isize) -> Option<&T> {
|
|
usize::try_from(index).ok().and_then(|i| self.get(i))
|
|
}
|
|
|
|
fn index_wrapping_before(&self, i: usize) -> usize {
|
|
if i == 0 {
|
|
return self.len() - 1;
|
|
}
|
|
i - 1
|
|
}
|
|
|
|
fn index_wrapping_after(&self, i: usize) -> usize {
|
|
if i == self.len() - 1 {
|
|
return 0;
|
|
}
|
|
i + 1
|
|
}
|
|
}
|
|
|
|
/// `Array<String>.withCStrings`: NUL terminated copies of the strings (the
|
|
/// returned `CString`s own the memory the pointers of `as_ptr()` refer to).
|
|
/// A string containing an interior NUL is truncated at it, as a C consumer
|
|
/// would read it.
|
|
#[must_use]
|
|
pub fn to_c_strings<S: AsRef<str>>(strings: &[S]) -> Vec<CString> {
|
|
strings
|
|
.iter()
|
|
.map(|s| {
|
|
let bytes = s.as_ref().as_bytes();
|
|
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
|
|
CString::new(&bytes[..end]).unwrap_or_default()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn safe_subscript() {
|
|
let a = [1, 2, 3];
|
|
assert_eq!(a.get_safe(0), Some(&1));
|
|
assert_eq!(a.get_safe(3), None);
|
|
assert_eq!(a.get_safe(-1), None);
|
|
}
|
|
|
|
#[test]
|
|
fn wrapping_indices() {
|
|
let a = [1, 2, 3];
|
|
assert_eq!(a.index_wrapping_before(0), 2);
|
|
assert_eq!(a.index_wrapping_before(2), 1);
|
|
assert_eq!(a.index_wrapping_after(2), 0);
|
|
assert_eq!(a.index_wrapping_after(0), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn c_strings() {
|
|
let c = to_c_strings(&["a", "b\0c", ""]);
|
|
assert_eq!(c[0].to_str().unwrap(), "a");
|
|
assert_eq!(c[1].to_str().unwrap(), "b");
|
|
assert_eq!(c[2].as_bytes().len(), 0);
|
|
assert_eq!(to_c_strings::<&str>(&[]).len(), 0);
|
|
}
|
|
}
|