93 lines
4.7 KiB
Swift
93 lines
4.7 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import Foundation
|
|
import TermindApplication
|
|
import TermindDomain
|
|
|
|
nonisolated enum RemoteTerminalDraft {
|
|
static func command(
|
|
context: TerminalShellContext, terminal: String, helper: String?,
|
|
clientIdentity: String?, request: UUID, replacement: String?, original: String? = nil,
|
|
charset: RemoteCharset = .utf8, target: RemoteMultiplexerDraftTarget? = nil
|
|
) throws -> String {
|
|
let identity = context.identity
|
|
guard context.promptReady, ["bash", "zsh", "fish"].contains(context.shell),
|
|
identity.shellInstance.hasPrefix("ctx."),
|
|
identity.shellInstance.utf8.allSatisfy({
|
|
(48...57).contains($0) || (65...90).contains($0) || (97...122).contains($0) || $0 == 46
|
|
}), replacement?.utf8.count ?? 0 <= 4096, replacement?.contains("\0") != true,
|
|
original?.utf8.count ?? 0 <= 4096, original?.contains("\0") != true
|
|
else { throw TerminalRuntimeError.staleContext }
|
|
let root: String
|
|
let trigger: String
|
|
if let target {
|
|
guard RemoteMultiplexerRegistry.dialect(.init(rawValue: identity.backend)) != nil else {
|
|
throw TerminalRuntimeError.staleContext
|
|
}
|
|
root = target.root
|
|
trigger = target.trigger
|
|
} else if identity.backend == "tmux" {
|
|
let target = try RemoteTmuxRuntimeDialect.draftTarget(
|
|
context: context, terminal: terminal, clientIdentity: clientIdentity, replacing: replacement != nil)
|
|
root = target.root
|
|
trigger = target.trigger
|
|
} else {
|
|
guard identity.backend == "shell", let helper else { throw TerminalRuntimeError.staleContext }
|
|
root = "helper=\(try RemoteMultiplexerCommands.quote(helper))"
|
|
// The worker sends the negotiated widget key after this request is installed.
|
|
trigger = "printf 'READY\\n'"
|
|
}
|
|
let bytes = try RemoteCharsetStreamConverter.encode(replacement ?? "", charset: charset)
|
|
guard bytes.count <= 4096 else { throw TerminalRuntimeError.staleContext }
|
|
let encoded = bytes.map { String(format: "%%%02X", $0) }.joined()
|
|
let baselineBytes = try original.map { try RemoteCharsetStreamConverter.encode($0, charset: charset) }
|
|
guard baselineBytes?.count ?? 0 <= 4096 else { throw TerminalRuntimeError.staleContext }
|
|
let baseline = baselineBytes?.map { String(format: "%%%02X", $0) }.joined() ?? "-"
|
|
return """
|
|
umask 077
|
|
\(root)
|
|
case "$helper" in /tmp/termind-shell.*) ;; *) exit 79 ;; esac
|
|
suffix=${helper#/tmp/termind-shell.}
|
|
case "$suffix" in ''|*[!a-zA-Z0-9]*) exit 79 ;; esac
|
|
dir="$helper/\(identity.shellInstance)"
|
|
[ -d "$dir" ] && [ ! -L "$dir" ] || exit 79
|
|
IFS='|' read -r tag seq ready cwd < "$dir/state" || exit 79
|
|
[ "$seq" = '\(context.sequence)' ] && [ "$ready" = 1 ] || exit 79
|
|
printf '%s|%s|%s|%s|%s|%s\\n' '\(request.uuidString)' '\(context.sequence)' \
|
|
'\(replacement == nil ? "read" : "replace")' '\(encoded)' "$(( $(date +%s) + 2 ))" '\(baseline)' > "$dir/request.tmp"
|
|
mv "$dir/request.tmp" "$dir/request"
|
|
\(trigger)
|
|
"""
|
|
}
|
|
|
|
static func resultCommand(context: TerminalShellContext, helper: String?, request: UUID) throws -> String {
|
|
let root: String
|
|
if context.identity.backend == "tmux" {
|
|
guard let session = context.identity.session.split(separator: ":").first else {
|
|
throw TerminalRuntimeError.staleContext
|
|
}
|
|
root = """
|
|
\(RemoteTmuxCommands.executionPreamble)
|
|
helper=$(tmux show-environment -t \(try RemoteMultiplexerCommands.quote(String(session))) TERMIND_TMUX_HELPERS) || exit 79
|
|
helper=${helper#TERMIND_TMUX_HELPERS=}
|
|
"""
|
|
} else {
|
|
guard let helper else { throw TerminalRuntimeError.staleContext }
|
|
root = "helper=\(try RemoteMultiplexerCommands.quote(helper))"
|
|
}
|
|
return """
|
|
\(root)
|
|
case "$helper" in /tmp/termind-shell.*) ;; *) exit 79 ;; esac
|
|
suffix=${helper#/tmp/termind-shell.}
|
|
case "$suffix" in ''|*[!a-zA-Z0-9]*) exit 79 ;; esac
|
|
file="$helper/\(context.identity.shellInstance)/journal"
|
|
for attempt in 1 2 3 4 5 6 7 8 9 10; do
|
|
if [ -f "$file" ] && grep -F '|draft|' "$file" | grep -q -F '|\(request.uuidString)'; then cat "$file"; exit 0; fi
|
|
sleep 0.1
|
|
done
|
|
exit 79
|
|
"""
|
|
}
|
|
}
|