Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions src-tauri/src/commands/file_ops.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::fs;

use crate::PendingOpenFiles;

#[tauri::command]
pub async fn read_file(path: String) -> Result<String, String> {
fs::read_to_string(&path).map_err(|e| format!("Failed to read file: {}", e))
Expand All @@ -18,6 +20,7 @@ pub async fn get_file_size(path: String) -> Result<u64, String> {
}

/// Returns the file path passed as a CLI argument (for file associations), if any.
/// Works on Linux/Windows where the OS passes the file path via argv.
#[tauri::command]
pub async fn get_open_file_arg() -> Option<String> {
let args: Vec<String> = std::env::args().collect();
Expand All @@ -30,3 +33,13 @@ pub async fn get_open_file_arg() -> Option<String> {
}
})
}

/// Returns and drains any file paths received via macOS Apple Events (RunEvent::Opened)
/// before the frontend was ready. Called by the frontend on init to handle cold-launch opens.
#[tauri::command]
pub async fn get_pending_open_file(state: tauri::State<'_, PendingOpenFiles>) -> Result<Option<String>, String> {
let mut pending = state.0.lock().unwrap();
let first = pending.first().cloned();
pending.clear();
Ok(first)
}
35 changes: 33 additions & 2 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ mod commands;
#[cfg(desktop)]
mod platform;

use std::sync::Mutex;
use tauri::{Emitter, Manager};

/// Stores file paths received via macOS Apple Events (RunEvent::Opened)
/// before the frontend is ready to handle them.
pub struct PendingOpenFiles(pub Mutex<Vec<String>>);

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_store::Builder::default().build())
.manage(PendingOpenFiles(Mutex::new(Vec::new())))
.invoke_handler(tauri::generate_handler![
commands::file_ops::read_file,
commands::file_ops::write_file,
Expand All @@ -20,6 +28,7 @@ pub fn run() {
commands::recent::add_recent_file,
commands::recent::clear_recent_files,
commands::file_ops::get_open_file_arg,
commands::file_ops::get_pending_open_file,
#[cfg(desktop)]
platform::desktop::watch_file,
#[cfg(desktop)]
Expand All @@ -41,6 +50,28 @@ pub fn run() {
}

builder
.run(tauri::generate_context!())
.expect("error running Inkwell");
.build(tauri::generate_context!())
.expect("error building Inkwell")
.run(|app_handle, event| {
if let tauri::RunEvent::Opened { urls } = event {
let paths: Vec<String> = urls
.iter()
.filter_map(|url| url.to_file_path().ok())
.filter_map(|p| p.to_str().map(String::from))
.collect();

if paths.is_empty() {
return;
}

// Try to emit to frontend (works when app is already running).
// Also store in state for cold-launch (frontend polls on init).
let state = app_handle.state::<PendingOpenFiles>();
let mut pending = state.0.lock().unwrap();
for path in &paths {
pending.push(path.clone());
let _ = app_handle.emit("open-file-requested", path.clone());
}
}
});
}
17 changes: 14 additions & 3 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,21 @@ async function init() {
document.getElementById("status-version").textContent = `v${version}`;
} catch {}

// Check if app was launched with a file argument (file association / "Open With")
// Handle macOS file-open events for when the app is already running
// (registered early so no events are missed during the checks below)
await listen("open-file-requested", async (event) => {
await loadFile(event.payload);
});

// Check if app was launched with a file argument
// 1. CLI argv (Linux/Windows file associations)
// 2. macOS Apple Events buffered in AppState (RunEvent::Opened fires before webview is ready)
const fileArg = await invoke("get_open_file_arg");
if (fileArg) {
await loadFile(fileArg);
const pendingFile = !fileArg ? await invoke("get_pending_open_file") : null;
const openPath = fileArg || pendingFile;

if (openPath) {
await loadFile(openPath);
} else {
await showWelcome();
}
Expand Down
14 changes: 11 additions & 3 deletions src/preview/scroll-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
let editorScroller = null;
let previewEl = null;
let syncSource = null;
let syncResetTimer = null;

function deferSyncReset() {
clearTimeout(syncResetTimer);
syncResetTimer = setTimeout(() => { syncSource = null; }, 200);
Comment on lines +7 to +9
}

export function setupScrollSync(editorView, previewElement) {
editorScroller = editorView.scrollDOM;
Expand All @@ -17,7 +23,7 @@ export function setupScrollSync(editorView, previewElement) {
const previewMax = previewEl.scrollHeight - previewEl.clientHeight;
previewEl.scrollTop = ratio * previewMax;
}
requestAnimationFrame(() => { syncSource = null; });
deferSyncReset();
});

// Preview -> Editor
Expand All @@ -30,7 +36,7 @@ export function setupScrollSync(editorView, previewElement) {
const editorMax = editorScroller.scrollHeight - editorScroller.clientHeight;
editorScroller.scrollTop = ratio * editorMax;
}
requestAnimationFrame(() => { syncSource = null; });
deferSyncReset();
});
}

Expand All @@ -52,7 +58,9 @@ export function getScrollRatio(activeMode) {
return 0;
}

// Apply a scroll ratio to the target pane(s), suppressing sync feedback
// Apply a scroll ratio to the target pane(s), suppressing sync feedback.
// Uses a single requestAnimationFrame guard (not the 200ms debounce) since
// this is a one-shot restore that shouldn't block user scroll input.
export function applyScrollRatio(ratio, targetMode) {
syncSource = "restore";

Expand Down
Loading