merge integration

This commit is contained in:
jess 2026-03-31 17:33:28 -07:00
commit 5e5ab706b2
3 changed files with 142 additions and 1 deletions

View File

@ -28,3 +28,7 @@ dirs = "6"
arboard = "3" arboard = "3"
zip = "2" zip = "2"
muda = "0.17" muda = "0.17"
[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6"
objc2-foundation = { version = "0.3", features = ["NSAppleEventDescriptor", "NSAppleEventManager", "NSString"] }

View File

@ -6,9 +6,15 @@ use iced::widget::{
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Shadow, Subscription}; use iced::{Background, Border, Color, Element, Fill, Length, Padding, Shadow, Subscription};
use iced::{mouse, keyboard, window, Point, Vector}; use iced::{mouse, keyboard, window, Point, Vector};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::{Arc, Mutex, OnceLock};
use std::time::Duration; use std::time::Duration;
static OPEN_QUEUE: OnceLock<Mutex<Vec<PathBuf>>> = OnceLock::new();
pub fn open_queue() -> &'static Mutex<Vec<PathBuf>> {
OPEN_QUEUE.get_or_init(|| Mutex::new(Vec::new()))
}
use cord_expr::{classify, classify_from, expr_to_sdf, parse_expr, parse_expr_scene, ExprInfo}; use cord_expr::{classify, classify_from, expr_to_sdf, parse_expr, parse_expr_scene, ExprInfo};
use crate::highlight::{CordHighlighter, CordHighlighterSettings, format_token}; use crate::highlight::{CordHighlighter, CordHighlighterSettings, format_token};
use crate::viewport::SdfViewport; use crate::viewport::SdfViewport;
@ -333,6 +339,11 @@ impl App {
self.menu_ready = true; self.menu_ready = true;
} }
self.poll_menu_events(); self.poll_menu_events();
if let Ok(mut q) = open_queue().lock() {
for path in q.drain(..) {
self.open_path(&path);
}
}
let new_line = self.source.cursor().position.line; let new_line = self.source.cursor().position.line;
if new_line != self.cursor_line { if new_line != self.cursor_line {
self.cursor_line = new_line; self.cursor_line = new_line;

View File

@ -14,7 +14,133 @@ fn title(app: &App) -> String {
app.title() app.title()
} }
#[cfg(target_os = "macos")]
mod apple_events {
use std::path::PathBuf;
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, NSObject};
use objc2::{define_class, msg_send, sel, AnyThread};
// kCoreEventClass = 'aevt', kAEOpenDocuments = 'odoc', keyDirectObject = '----'
const KAEVT: u32 = u32::from_be_bytes(*b"aevt");
const KODOC: u32 = u32::from_be_bytes(*b"odoc");
const KEY_DIRECT_OBJECT: u32 = u32::from_be_bytes(*b"----");
define_class!(
#[unsafe(super(NSObject))]
#[name = "CordOpenHandler"]
struct OpenHandler;
impl OpenHandler {
#[unsafe(method(handleOpenEvent:withReplyEvent:))]
fn handle_open_event(&self, event: &AnyObject, _reply: &AnyObject) {
handle_odoc(event);
}
}
);
impl OpenHandler {
fn new() -> Retained<Self> {
let this = Self::alloc().set_ivars(());
unsafe { msg_send![super(this), init] }
}
}
fn handle_odoc(event: &AnyObject) {
let file_list: Option<Retained<AnyObject>> = unsafe {
msg_send![event, paramDescriptorForKeyword: KEY_DIRECT_OBJECT]
};
let file_list = match file_list {
Some(fl) => fl,
None => return,
};
let count: isize = unsafe { msg_send![&*file_list, numberOfItems] };
let queue = crate::app::open_queue();
for i in 1..=count {
let desc: Option<Retained<AnyObject>> = unsafe {
msg_send![&*file_list, descriptorAtIndex: i]
};
let desc = match desc {
Some(d) => d,
None => continue,
};
let url_str: Option<Retained<AnyObject>> = unsafe {
msg_send![&*desc, stringValue]
};
let url_str = match url_str {
Some(s) => s,
None => continue,
};
let raw: *const std::ffi::c_char = unsafe { msg_send![&*url_str, UTF8String] };
if raw.is_null() {
continue;
}
let cstr = unsafe { std::ffi::CStr::from_ptr(raw) };
let s = match cstr.to_str() {
Ok(s) => s,
Err(_) => continue,
};
let path_str = if let Some(stripped) = s.strip_prefix("file://") {
percent_decode(stripped)
} else {
s.to_string()
};
let path = PathBuf::from(&path_str);
if path.exists() {
if let Ok(mut q) = queue.lock() {
q.push(path);
}
}
}
}
fn percent_decode(input: &str) -> String {
let mut out = Vec::with_capacity(input.len());
let bytes = input.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' && i + 2 < bytes.len() {
if let Ok(byte) = u8::from_str_radix(
&input[i + 1..i + 3],
16,
) {
out.push(byte);
i += 3;
continue;
}
}
out.push(bytes[i]);
i += 1;
}
String::from_utf8(out).unwrap_or_else(|_| input.to_string())
}
pub fn register_open_handler() {
use objc2_foundation::NSAppleEventManager;
let handler = OpenHandler::new();
let mgr = NSAppleEventManager::sharedAppleEventManager();
let sel = sel!(handleOpenEvent:withReplyEvent:);
unsafe {
let _: () = msg_send![
&*mgr,
setEventHandler: &*handler,
andSelector: sel,
forEventClass: KAEVT,
andEventID: KODOC
];
}
// Leak the handler so it lives for the process lifetime
std::mem::forget(handler);
}
}
fn main() -> iced::Result { fn main() -> iced::Result {
#[cfg(target_os = "macos")]
apple_events::register_open_handler();
iced::application(App::new, App::update, App::view) iced::application(App::new, App::update, App::view)
.title(title) .title(title)
.theme(theme) .theme(theme)