feat(macos): trigger Apple Intelligence writing tools from the editor

This commit is contained in:
pj committed 2026-08-10 18:37:23 +05:30
1 parent fe39274ed7
commit 9e310a34bb
5 files changed
+108 -2

No files matched your search

+9 -1
View File
@@ -45,7 +45,15 @@ url = "2"
# in the graph via Tauri, so this adds nothing new to the build.
[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6"
objc2-app-kit = { version = "0.3", features = ["NSSpellChecker"] }
objc2-app-kit = { version = "0.3", features = [
"NSSpellChecker",
"NSApplication",
"NSEvent",
"NSMenu",
"NSMenuItem",
"NSResponder",
"NSWritingToolsCoordinator",
] }
objc2-foundation = { version = "0.3", features = ["NSString", "NSArray", "NSRange", "NSTextCheckingResult"] }
# Other platforms fall back to the bundled Hunspell dictionary via spellbook.
+3
View File
@@ -7,6 +7,7 @@ mod macspell;
mod pdf;
mod project;
mod proofing;
mod writingtools;
#[cfg(desktop)]
use tauri::menu::{Menu, MenuItemBuilder, MenuItemKind, PredefinedMenuItem, SubmenuBuilder};
@@ -151,6 +152,7 @@ pub fn run() {
.manage(gdrive::GDriveState::default())
.setup(|app| {
gdrive::init_session(app.handle());
writingtools::label_writing_tool_shortcuts();
Ok(())
});
@@ -193,6 +195,7 @@ pub fn run() {
library::delete_book,
proofing::proof_text,
proofing::remember_word,
writingtools::run_writing_tool,
gdrive::gdrive_connect,
gdrive::gdrive_disconnect,
gdrive::gdrive_status,
+81
View File
@@ -0,0 +1,81 @@
#[cfg(target_os = "macos")]
mod mac {
use objc2::rc::Retained;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use objc2_foundation::{NSArray, NSString};
const SHORTCUTS: [(&str, &str); 2] = [("Proofread", "f"), ("Rewrite", "r")];
fn submenu_named(items: &NSArray<NSMenuItem>, title: &str) -> Option<Retained<NSMenu>> {
for i in 0..items.count() {
let item = items.objectAtIndex(i);
if item.title().to_string() == title {
return item.submenu();
}
}
None
}
fn edit_menu(mtm: MainThreadMarker) -> Option<Retained<NSMenu>> {
let main = NSApplication::sharedApplication(mtm).mainMenu()?;
for i in 0..main.numberOfItems() {
let Some(item) = main.itemAtIndex(i) else { continue };
let Some(submenu) = item.submenu() else { continue };
if submenu.title().to_string() == "Edit" || item.title().to_string() == "Edit" {
return Some(submenu);
}
}
None
}
fn writing_tools_menu(mtm: MainThreadMarker) -> Option<Retained<NSMenu>> {
submenu_named(&edit_menu(mtm)?.itemArray(), "Writing Tools")
}
pub fn label_shortcuts() {
let Some(mtm) = MainThreadMarker::new() else { return };
let Some(menu) = writing_tools_menu(mtm) else { return };
let items = menu.itemArray();
for i in 0..items.count() {
let item = items.objectAtIndex(i);
let title = item.title().to_string();
let Some((_, key)) = SHORTCUTS.iter().find(|(name, _)| *name == title) else { continue };
item.setKeyEquivalent(&NSString::from_str(key));
item.setKeyEquivalentModifierMask(
NSEventModifierFlags::Shift | NSEventModifierFlags::Option,
);
}
}
pub fn perform(tool: &str) {
let Some(mtm) = MainThreadMarker::new() else { return };
let Some(menu) = writing_tools_menu(mtm) else { return };
let items = menu.itemArray();
for i in 0..items.count() {
if items.objectAtIndex(i).title().to_string() == tool {
menu.performActionForItemAtIndex(i as isize);
return;
}
}
}
}
pub fn label_writing_tool_shortcuts() {
#[cfg(target_os = "macos")]
mac::label_shortcuts();
}
#[tauri::command]
pub fn run_writing_tool(app: tauri::AppHandle, tool: String) -> Result<(), String> {
#[cfg(target_os = "macos")]
{
app.run_on_main_thread(move || mac::perform(&tool))
.map_err(|e| e.to_string())
}
#[cfg(not(target_os = "macos"))]
{
let _ = (app, tool);
Ok(())
}
}