File size: 2,487 Bytes
0dc8570
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import SwiftUI
import AppKit
import SwiftTerm

// SSH runs under a local PTY so keyboard input, terminal modes and resizing
// propagate to the remote login-node PTY.
@MainActor final class ARCConsole:NSObject,ObservableObject,@preconcurrency LocalProcessTerminalViewDelegate {
 @Published var running=false
 @Published var session="No session"
 let view=LocalProcessTerminalView(frame:NSRect(x:0,y:0,width:1000,height:500),font:NSFont.monospacedSystemFont(ofSize:14,weight:.regular),options:TerminalOptions(scrollback:5000))
 override init(){super.init();view.processDelegate=self;view.feed(text:"Connect to ARC, then choose Start login terminal.\r\n")}
 static func request(_ p:Profile)throws->String {
  try p.validate()
  return "exec env TERM=xterm-256color bash -il"
 }
 func start(profile:Profile,ssh:[String],target:String)throws {
  guard !running else{return}
  let command=try Self.request(profile)
  var env=ProcessInfo.processInfo.environment;env["TERM"]="xterm-256color"
  view.feed(text:"\r\nConnecting to login node \(target)\r\nLogin node is not for compute intensive jobs.\r\n")
  view.startProcess(executable:"/usr/bin/ssh",args:ssh+["-o","BatchMode=yes","-tt",target,"bash -lc "+shellQuote(command)],environment:env.map{"\($0.key)=\($0.value)"})
  guard view.process.running else{throw Failure(message:"Unable to start SSH terminal.")}
  running=true;session="\(target) · login-node shell";view.window?.makeFirstResponder(view)
 }
 func write(_ text:String){guard running else{return};view.send(txt:text)}
 func stop(){guard running else{return};view.terminate();session="Closing SSH session…"}
 func sizeChanged(source:LocalProcessTerminalView,newCols:Int,newRows:Int){}
 func setTerminalTitle(source:LocalProcessTerminalView,title:String){}
 func hostCurrentDirectoryUpdate(source:TerminalView,directory:String?){}
 func processTerminated(source:TerminalView,exitCode:Int32?){running=false;session="Session ended (SSH exit \(exitCode.map(String.init) ?? "unknown"))";view.feed(text:"\r\n[\(session)]\r\n")}
}

struct NativeTerminal:NSViewRepresentable {
 @ObservedObject var console:ARCConsole
 let dark:Bool
 func makeNSView(context:Context)->LocalProcessTerminalView{configure();return console.view}
 func updateNSView(_ view:LocalProcessTerminalView,context:Context){configure()}
 private func configure(){
  console.view.nativeForegroundColor=dark ? .white:.black
  console.view.nativeBackgroundColor=dark ? NSColor(calibratedWhite:0.04,alpha:1):.white
 }
}