Skip to content
Open
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
29 changes: 28 additions & 1 deletion src-tauri/src/commands/pty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ struct PtyInstance {
master: Box<dyn portable_pty::MasterPty + Send>,
}

/// Spawns a platform-appropriate shell attached to a new PTY and begins emitting its output.
///
/// Creates a new PTY, launches the shell process attached to the PTY, stores the PTY instance under `id`, and starts a background thread that emits `pty-data-<id>` events for output and `pty-exit-<id>` when the process exits. If a PTY instance already exists for `id`, the function returns immediately without replacing the existing instance.
///
/// # Returns
///
/// `Ok(())` on success; `Err(String)` with a formatted error message on failure.
///
/// # Examples
///
/// ```no_run
/// // let app: tauri::AppHandle = /* obtain AppHandle */ ;
/// // spawn_shell(app, "terminal-1".into()).unwrap();
/// ```
#[tauri::command]
pub fn spawn_shell(app: AppHandle, id: String) -> Result<(), String> {
use std::collections::hash_map::Entry;
Expand Down Expand Up @@ -150,6 +164,19 @@ pub fn resize_pty(id: String, rows: u16, cols: u16) -> Result<(), String> {
}
}

/// Terminates and removes the PTY instance identified by `id`.
///
/// Attempts to kill the child process for the PTY and remove its entry from the global PTY map.
///
/// # Returns
///
/// `Ok(())` on success; `Err(String)` with "PTY instance not found: <id>" if no matching instance exists.
///
/// # Examples
///
/// ```
/// let _ = kill_pty("my-pty".into());
/// ```
#[tauri::command]
pub fn kill_pty(id: String) -> Result<(), String> {
let mut instances = PTY_INSTANCES.lock().unwrap();
Expand All @@ -161,4 +188,4 @@ pub fn kill_pty(id: String) -> Result<(), String> {
} else {
Err(format!("PTY instance not found: {}", id))
}
}
}
7 changes: 6 additions & 1 deletion src/components/Terminal/TerminalTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { useRef, useEffect, useState } from "react";
import { useEditorStore } from "../../stores/editorStore";
import { themeNames, themeDisplayNames } from "../../lib/terminalThemes";

/**
* Renders the terminal tabs UI with controls for adding, selecting, and removing terminals, a theme picker, and an inline search bar.
*
* Provides keyboard shortcuts (Cmd/Ctrl+Shift+F to toggle search, Escape to close the search or theme dropdown), outside-click dismissal for the theme dropdown, and focus management for the search input.
*/
export function TerminalTabs() {
const {
terminals,
Expand Down Expand Up @@ -279,4 +284,4 @@ export function TerminalTabs() {
)}
</div>
);
}
}
14 changes: 13 additions & 1 deletion src/hooks/useTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ interface UseTerminalOptions {
remote?: { sessionId: string };
}

/**
* Creates and manages an xterm.js terminal instance tied to a local or remote PTY.
*
* @param options.id - Unique PTY identifier used for backend routing and event channels
* @param options.onExit - Optional callback invoked when the underlying process exits
* @param options.remote - Optional remote session info; when present, all backend calls use SSH variants and `remote.sessionId` is included
* @returns An API with:
* - `initTerminal(container)`: attaches and initializes the terminal into the provided HTML container,
* - `focus()`: focuses the terminal,
* - `search(query)`: searches forward for `query` (case-insensitive),
* - `clearSearch()`: clears search decorations
*/
export function useTerminal({ id, onExit, remote }: UseTerminalOptions) {
const terminalRef = useRef<Terminal | null>(null);
const fitAddonRef = useRef<FitAddon | null>(null);
Expand Down Expand Up @@ -199,4 +211,4 @@ export function useTerminal({ id, onExit, remote }: UseTerminalOptions) {
}, [id, isRemote, remote]);

return { initTerminal, focus, search, clearSearch };
}
}