Skip to content

Commit 6ed9f10

Browse files
feat(Mountain): add Wind IPC handlers for terminal, output, and textFile commands
Add command handlers in WindServiceHandlers to expose terminal, output channel, and text file operations to the Wind frontend via Tauri IPC: - Terminal: create, sendText, dispose, show, hide - delegate to TerminalProvider - Output channels: create, append, appendLine, clear, show - emit Tauri events to Sky - TextFile: read, write, save - use tokio::fs for disk I/O These handlers complete the IPC bridge between Wind's service layer and Mountain's native backend capabilities.
1 parent 030b3c7 commit 6ed9f10

1 file changed

Lines changed: 201 additions & 0 deletions

File tree

Source/IPC/WindServiceHandlers.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,25 @@ pub async fn mountain_ipc_invoke(app_handle:AppHandle, command:String, args:Vec<
245245
"extensions:get" => handle_extensions_get(runtime.inner().clone(), args).await,
246246
"extensions:isActive" => handle_extensions_is_active(runtime.inner().clone(), args).await,
247247

248+
// Terminal commands
249+
"terminal:create" => handle_terminal_create(runtime.inner().clone(), args).await,
250+
"terminal:sendText" => handle_terminal_send_text(runtime.inner().clone(), args).await,
251+
"terminal:dispose" => handle_terminal_dispose(runtime.inner().clone(), args).await,
252+
"terminal:show" => handle_terminal_show(runtime.inner().clone(), args).await,
253+
"terminal:hide" => handle_terminal_hide(runtime.inner().clone(), args).await,
254+
255+
// Output channel commands
256+
"output:create" => handle_output_create(app_handle.clone(), args).await,
257+
"output:append" => handle_output_append(app_handle.clone(), args).await,
258+
"output:appendLine" => handle_output_append_line(app_handle.clone(), args).await,
259+
"output:clear" => handle_output_clear(app_handle.clone(), args).await,
260+
"output:show" => handle_output_show(app_handle.clone(), args).await,
261+
262+
// TextFile commands
263+
"textFile:read" => handle_textfile_read(runtime.inner().clone(), args).await,
264+
"textFile:write" => handle_textfile_write(runtime.inner().clone(), args).await,
265+
"textFile:save" => handle_textfile_save(runtime.inner().clone(), args).await,
266+
248267
// IPC status commands
249268
"mountain_get_status" => {
250269
let status = json!({
@@ -820,6 +839,188 @@ async fn handle_workbench_configuration(runtime:Arc<ApplicationRunTime>, _args:V
820839
Ok(config)
821840
}
822841

842+
// ============================================================================
843+
// Terminal Handlers
844+
// ============================================================================
845+
846+
/// Create a new PTY terminal via TerminalProvider.
847+
async fn handle_terminal_create(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
848+
use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
849+
850+
let Options = args.first().cloned().unwrap_or(Value::Null);
851+
runtime
852+
.Environment
853+
.CreateTerminal(Options)
854+
.await
855+
.map_err(|Error| format!("terminal:create failed: {}", Error))
856+
}
857+
858+
/// Write text to PTY stdin via TerminalProvider.
859+
async fn handle_terminal_send_text(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
860+
use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
861+
862+
let TerminalId = args
863+
.first()
864+
.and_then(|V| V.as_u64())
865+
.ok_or_else(|| "terminal:sendText requires terminal_id as first argument".to_string())?;
866+
let Text = args
867+
.get(1)
868+
.and_then(|V| V.as_str())
869+
.unwrap_or("")
870+
.to_string();
871+
872+
runtime
873+
.Environment
874+
.SendTextToTerminal(TerminalId, Text)
875+
.await
876+
.map(|()| Value::Null)
877+
.map_err(|Error| format!("terminal:sendText failed: {}", Error))
878+
}
879+
880+
/// Dispose a terminal via TerminalProvider.
881+
async fn handle_terminal_dispose(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
882+
use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
883+
884+
let TerminalId = args
885+
.first()
886+
.and_then(|V| V.as_u64())
887+
.ok_or_else(|| "terminal:dispose requires terminal_id as first argument".to_string())?;
888+
889+
runtime
890+
.Environment
891+
.DisposeTerminal(TerminalId)
892+
.await
893+
.map(|()| Value::Null)
894+
.map_err(|Error| format!("terminal:dispose failed: {}", Error))
895+
}
896+
897+
/// Show a terminal in the UI.
898+
async fn handle_terminal_show(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
899+
use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
900+
901+
let TerminalId = args.first().and_then(|V| V.as_u64()).unwrap_or(0);
902+
let PreserveFocus = args.get(1).and_then(|V| V.as_bool()).unwrap_or(false);
903+
904+
runtime
905+
.Environment
906+
.ShowTerminal(TerminalId, PreserveFocus)
907+
.await
908+
.map(|()| Value::Null)
909+
.map_err(|Error| format!("terminal:show failed: {}", Error))
910+
}
911+
912+
/// Hide a terminal.
913+
async fn handle_terminal_hide(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
914+
use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
915+
916+
let TerminalId = args.first().and_then(|V| V.as_u64()).unwrap_or(0);
917+
918+
runtime
919+
.Environment
920+
.HideTerminal(TerminalId)
921+
.await
922+
.map(|()| Value::Null)
923+
.map_err(|Error| format!("terminal:hide failed: {}", Error))
924+
}
925+
926+
// ============================================================================
927+
// Output Channel Handlers
928+
// ============================================================================
929+
930+
/// Create a named output channel. Returns the channel name as its handle.
931+
async fn handle_output_create(_app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
932+
let ChannelName = args
933+
.first()
934+
.and_then(|V| V.as_str())
935+
.unwrap_or("Output")
936+
.to_string();
937+
info!("[WindServiceHandlers] output:create channel='{}'", ChannelName);
938+
// Sky/frontend creates the channel panel on the `sky://output/create` event
939+
Ok(json!({ "channelName": ChannelName }))
940+
}
941+
942+
/// Append text to an output channel.
943+
async fn handle_output_append(app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
944+
use tauri::Emitter;
945+
946+
let ChannelName = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
947+
let Text = args.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
948+
949+
let _ = app_handle.emit("sky://output/append", json!({ "channel": ChannelName, "text": Text }));
950+
Ok(Value::Null)
951+
}
952+
953+
/// Append a line to an output channel (text + newline).
954+
async fn handle_output_append_line(app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
955+
use tauri::Emitter;
956+
957+
let ChannelName = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
958+
let Text = args.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
959+
let Line = format!("{}\n", Text);
960+
961+
let _ = app_handle.emit("sky://output/append", json!({ "channel": ChannelName, "text": Line }));
962+
Ok(Value::Null)
963+
}
964+
965+
/// Clear an output channel.
966+
async fn handle_output_clear(app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
967+
use tauri::Emitter;
968+
969+
let ChannelName = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
970+
let _ = app_handle.emit("sky://output/clear", json!({ "channel": ChannelName }));
971+
Ok(Value::Null)
972+
}
973+
974+
/// Show an output channel panel.
975+
async fn handle_output_show(app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
976+
use tauri::Emitter;
977+
978+
let ChannelName = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
979+
let _ = app_handle.emit("sky://output/show", json!({ "channel": ChannelName }));
980+
Ok(Value::Null)
981+
}
982+
983+
// ============================================================================
984+
// TextFile Handlers
985+
// ============================================================================
986+
987+
/// Read a text file from disk.
988+
async fn handle_textfile_read(_runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
989+
let Path = args
990+
.first()
991+
.and_then(|V| V.as_str())
992+
.ok_or_else(|| "textFile:read requires path as first argument".to_string())?;
993+
994+
tokio::fs::read_to_string(Path).await.map(Value::String).map_err(|Error| {
995+
format!("textFile:read failed: {}", Error)
996+
})
997+
}
998+
999+
/// Write text to a file on disk.
1000+
async fn handle_textfile_write(_runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1001+
let Path = args
1002+
.first()
1003+
.and_then(|V| V.as_str())
1004+
.ok_or_else(|| "textFile:write requires path as first argument".to_string())?;
1005+
let Content = args
1006+
.get(1)
1007+
.and_then(|V| V.as_str())
1008+
.unwrap_or("")
1009+
.to_string();
1010+
1011+
tokio::fs::write(Path, Content.as_bytes()).await.map(|()| Value::Null).map_err(|Error| {
1012+
format!("textFile:write failed: {}", Error)
1013+
})
1014+
}
1015+
1016+
/// Save a document — forward save intent to Sky frontend.
1017+
async fn handle_textfile_save(_runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1018+
// Actual disk write happens via textFile:write; this is a UI-dirty-state hint.
1019+
let _Uri = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1020+
info!("[WindServiceHandlers] textFile:save uri={:?}", _Uri);
1021+
Ok(Value::Null)
1022+
}
1023+
8231024
/// Register all Wind IPC command handlers
8241025
pub fn register_wind_ipc_handlers(app_handle:&tauri::AppHandle) -> Result<(), String> {
8251026
info!("[WindServiceHandlers] Registering Wind IPC command handlers");

0 commit comments

Comments
 (0)