Move PTY spawn from context layer to view layer so event subscription happens before spawn, preventing loss of initial terminal output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
140 lines
4.3 KiB
Rust
140 lines
4.3 KiB
Rust
use std::collections::HashMap;
|
|
|
|
#[cfg(feature = "tauri-runtime")]
|
|
use tauri::Manager;
|
|
#[cfg(feature = "tauri-runtime")]
|
|
use tauri::State;
|
|
|
|
use crate::git_credential;
|
|
#[cfg(feature = "tauri-runtime")]
|
|
use crate::terminal::error::TerminalError;
|
|
#[cfg(feature = "tauri-runtime")]
|
|
use crate::terminal::manager::{SpawnOptions, TerminalManager};
|
|
#[cfg(feature = "tauri-runtime")]
|
|
use crate::terminal::types::TerminalInfo;
|
|
#[cfg(feature = "tauri-runtime")]
|
|
use crate::web::event_bridge::EventEmitter;
|
|
|
|
/// Build extra env vars for the terminal session.
|
|
///
|
|
/// Uses `credential.helper` with a script that calls the app binary with
|
|
/// `--credential-helper`. The binary opens the DB, looks up the matching
|
|
/// account, and outputs credentials. No credentials are written to disk.
|
|
pub(crate) fn prepare_credential_env(
|
|
app_data_dir: &std::path::Path,
|
|
) -> Option<HashMap<String, String>> {
|
|
// Get the path to the current running binary
|
|
let app_binary = match std::env::current_exe() {
|
|
Ok(p) => p,
|
|
Err(e) => {
|
|
eprintln!("[TERM] failed to get current exe path: {}", e);
|
|
return None;
|
|
}
|
|
};
|
|
|
|
let helper_script = match git_credential::create_credential_helper_script(
|
|
app_data_dir,
|
|
&app_binary,
|
|
) {
|
|
Ok(p) => p,
|
|
Err(e) => {
|
|
eprintln!("[TERM] failed to create credential helper script: {}", e);
|
|
return None;
|
|
}
|
|
};
|
|
|
|
let helper_path_str = helper_script.to_string_lossy().to_string();
|
|
|
|
// GIT_CONFIG_COUNT adds config entries that are tried BEFORE file-based config.
|
|
// For multi-valued keys like credential.helper, this means our helper runs first;
|
|
// if it exits 0 with no output, git falls through to the user's existing helpers.
|
|
let mut env = HashMap::new();
|
|
env.insert("GIT_CONFIG_COUNT".to_string(), "1".to_string());
|
|
env.insert(
|
|
"GIT_CONFIG_KEY_0".to_string(),
|
|
"credential.helper".to_string(),
|
|
);
|
|
// The '!' prefix tells git to run as a raw shell command (not git-credential-<name>).
|
|
// Paths with spaces (e.g. "Application Support") must be quoted.
|
|
env.insert(
|
|
"GIT_CONFIG_VALUE_0".to_string(),
|
|
format!("!\"{}\"", helper_path_str),
|
|
);
|
|
|
|
Some(env)
|
|
}
|
|
|
|
#[cfg(feature = "tauri-runtime")]
|
|
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
|
pub async fn terminal_spawn(
|
|
working_dir: String,
|
|
initial_command: Option<String>,
|
|
terminal_id: Option<String>,
|
|
manager: State<'_, TerminalManager>,
|
|
app_handle: tauri::AppHandle,
|
|
window: tauri::WebviewWindow,
|
|
) -> Result<String, TerminalError> {
|
|
let terminal_id = terminal_id
|
|
.filter(|id| !id.is_empty() && id.len() <= 256)
|
|
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
|
|
|
|
let app_data_dir = app_handle
|
|
.path()
|
|
.app_data_dir()
|
|
.map_err(|e| TerminalError::SpawnFailed(e.to_string()))?;
|
|
|
|
let extra_env = prepare_credential_env(&app_data_dir);
|
|
|
|
manager.spawn_with_id(
|
|
SpawnOptions {
|
|
terminal_id,
|
|
working_dir,
|
|
owner_window_label: window.label().to_string(),
|
|
initial_command,
|
|
extra_env,
|
|
temp_files: vec![],
|
|
},
|
|
EventEmitter::Tauri(app_handle),
|
|
)
|
|
}
|
|
|
|
#[cfg(feature = "tauri-runtime")]
|
|
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
|
pub fn terminal_write(
|
|
terminal_id: String,
|
|
data: String,
|
|
manager: State<'_, TerminalManager>,
|
|
) -> Result<(), TerminalError> {
|
|
manager.write(&terminal_id, data.as_bytes())
|
|
}
|
|
|
|
#[cfg(feature = "tauri-runtime")]
|
|
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
|
pub fn terminal_resize(
|
|
terminal_id: String,
|
|
cols: u16,
|
|
rows: u16,
|
|
manager: State<'_, TerminalManager>,
|
|
) -> Result<(), TerminalError> {
|
|
manager.resize(&terminal_id, cols, rows)
|
|
}
|
|
|
|
#[cfg(feature = "tauri-runtime")]
|
|
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
|
pub fn terminal_kill(
|
|
terminal_id: String,
|
|
manager: State<'_, TerminalManager>,
|
|
) -> Result<(), TerminalError> {
|
|
manager.kill(&terminal_id)
|
|
}
|
|
|
|
#[cfg(feature = "tauri-runtime")]
|
|
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
|
pub fn terminal_list(
|
|
manager: State<'_, TerminalManager>,
|
|
app_handle: tauri::AppHandle,
|
|
) -> Result<Vec<TerminalInfo>, TerminalError> {
|
|
let emitter = EventEmitter::Tauri(app_handle);
|
|
Ok(manager.list_with_exit_check(Some(&emitter)))
|
|
}
|