Skip to content

Commit

Permalink
[global] Initial working proof-of-concept with SwiftTerm
Browse files Browse the repository at this point in the history
  • Loading branch information
kirb committed Apr 2, 2021
1 parent d57474f commit b990bb7
Show file tree
Hide file tree
Showing 16 changed files with 513 additions and 94 deletions.
32 changes: 26 additions & 6 deletions Common/Controllers/Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class Preferences: NSObject {
let themesPlist = NSDictionary(contentsOf: Bundle.main.url(forResource: "Themes", withExtension: "plist")!)!

public var fontMetrics: FontMetrics!
@objc public var colorMap: VT100ColorMap!
public var colorMap: ColorMap!

private var kvoContext = 0

Expand Down Expand Up @@ -153,17 +153,34 @@ public class Preferences: NSObject {
private func fontMetricsChanged() {
var regularFont: Font?
var boldFont: Font?
var italicFont: Font?
var boldItalicFont: Font?

if fontName == "SF Mono" {
if #available(iOS 13, macOS 10.15, *) {
regularFont = Font.monospacedSystemFont(ofSize: fontSize, weight: .regular)
boldFont = Font.monospacedSystemFont(ofSize: fontSize, weight: .bold)

if let fontDescriptor = regularFont?.fontDescriptor.withSymbolicTraits(.traitItalic) {
italicFont = Font(descriptor: fontDescriptor, size: fontSize)
}
if let fontDescriptor = boldFont?.fontDescriptor.withSymbolicTraits(.traitItalic) {
boldItalicFont = Font(descriptor: fontDescriptor, size: fontSize)
}
}
} else {
if let family = fontsPlist[fontName] as? [String: String] {
if family["Regular"] != nil && family["Bold"] != nil {
regularFont = Font(name: family["Regular"]!, size: fontSize)
boldFont = Font(name: family["Bold"]!, size: fontSize)
if let name = family["Regular"] {
regularFont = Font(name: name, size: fontSize)
}
if let name = family["Bold"] {
boldFont = Font(name: name, size: fontSize)
}
if let name = family["Italic"] {
italicFont = Font(name: name, size: fontSize)
}
if let name = family["BoldItalic"] {
boldItalicFont = Font(name: name, size: fontSize)
}
}
}
Expand All @@ -178,7 +195,10 @@ public class Preferences: NSObject {
return
}

fontMetrics = FontMetrics(regularFont: regularFont!, boldFont: boldFont!)
fontMetrics = FontMetrics(regularFont: regularFont!,
boldFont: boldFont!,
italicFont: italicFont ?? regularFont!,
boldItalicFont: boldItalicFont ?? boldFont!)
}

private func colorMapChanged() {
Expand All @@ -190,7 +210,7 @@ public class Preferences: NSObject {
return
}

colorMap = VT100ColorMap(dictionary: theme)
colorMap = ColorMap(dictionary: theme)

#if os(macOS)
NSApp.appearance = NSAppearance(named: colorMap.appearanceStyle)
Expand Down
2 changes: 1 addition & 1 deletion Common/Controllers/SubProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class SubProcess: NSObject {
let bashArgs = ([ "bash", "--login", "-i" ] as NSArray).cStringArray()!

let env = ([
"TERM=xterm-color",
"TERM=xterm-256color",
"LANG=\(localeCode)",
"TERM_PROGRAM=NewTerm",
"LC_TERMINAL=NewTerm"
Expand Down
129 changes: 74 additions & 55 deletions Common/Controllers/TerminalController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,30 @@ import AppKit
#else
import UIKit
#endif
import SwiftTerm

public protocol TerminalControllerDelegate: AnyObject {

func refresh(attributedString: NSAttributedString, backgroundColor: Color)
func refresh(attributedString: NSAttributedString, backgroundColor: UIColor)
func activateBell()
func titleDidChange(_ title: String?)

func close()
func didReceiveError(error: Error)

func openSettings()

}

public class TerminalController: VT100 {
public class TerminalController {

public weak var delegate: TerminalControllerDelegate?

private var isDirty = false
private var updateTimer: Timer?

private var stringSupplier = VT100StringSupplier()
private var stringSupplier = StringSupplier()

public var colorMap: VT100ColorMap {
public var colorMap: ColorMap {
get { return stringSupplier.colorMap! }
set { stringSupplier.colorMap = newValue }
}
Expand All @@ -42,26 +44,31 @@ public class TerminalController: VT100 {
set { stringSupplier.fontMetrics = newValue }
}

private var terminal: Terminal?
private var subProcess: SubProcess?
private var processLaunchDate: Date?

override public var screenSize: ScreenSize {
get { return super.screenSize }

set {
super.screenSize = newValue

public var screenSize: ScreenSize? {
didSet {
// Send the terminal the actual size of our vt100 view. This should be called any time we
// change the size of the view. This should be a no-op if the size has not changed since the
// last time we called it.
subProcess?.screenSize = screenSize
if let screenSize = screenSize {
subProcess?.screenSize = screenSize
terminal?.resize(cols: Int(screenSize.width),
rows: Int(screenSize.height))
}
}
}

public override init() {
super.init()
public var scrollbackLines: Int { (terminal?.rows ?? 0) - (terminal?.getTopVisibleRow() ?? 0) }

stringSupplier.screenBuffer = self
public init() {
let options = TerminalOptions(termName: "xterm-256color",
scrollback: 1000)
terminal = Terminal(delegate: self, options: options)

stringSupplier.terminal = terminal

NotificationCenter.default.addObserver(self, selector: #selector(self.preferencesUpdated), name: Preferences.didChangeNotification, object: nil)
preferencesUpdated()
Expand All @@ -74,7 +81,7 @@ public class TerminalController: VT100 {
stringSupplier.colorMap = preferences.colorMap
stringSupplier.fontMetrics = preferences.fontMetrics!

refresh()
terminal?.refresh(startRow: 0, endRow: terminal?.rows ?? 0)
}

// MARK: - Sub Process
Expand All @@ -90,50 +97,23 @@ public class TerminalController: VT100 {
try subProcess!.stop()
}

// MARK: - Object lifecycle

deinit {
updateTimer?.invalidate()
}

}

extension TerminalController: TerminalInputProtocol {

public func receiveKeyboardInput(data: Data) {
// Forward the data from the keyboard directly to the subprocess
subProcess!.write(data: data)
}

public func openSettings() {
delegate!.openSettings()
}

}

// ScreenBufferRefreshDelegate
extension TerminalController {

override public func refresh() {
super.refresh()

// TODO: This is called due to -[VT100 init], and we aren’t ready yet… We’ll be called when we
// are anyway, so don’t worry about it
if updateTimer == nil {
return
}
// MARK: - Terminal

isDirty = true
public func readInputStream(_ data: Data) {
let bytes = Array<UInt8>(data)
terminal?.feed(byteArray: bytes)
}

@objc private func updateTimerFired() {
if !isDirty {
if terminal?.getUpdateRange() == nil {
return
}

terminal?.clearUpdateRange()

// TODO: We should handle the scrollback separately so it only appears if the user scrolls
let attributedString = self.stringSupplier.attributedString()!
let backgroundColor = self.stringSupplier.colorMap!.background!
let attributedString = self.stringSupplier.attributedString()
let backgroundColor = self.stringSupplier.colorMap!.background

self.delegate?.refresh(attributedString: attributedString, backgroundColor: backgroundColor)

Expand All @@ -144,15 +124,54 @@ extension TerminalController {
// self.delegate?.refresh(attributedString: attributedString, backgroundColor: backgroundColor)
// }
// }
}

// MARK: - Object lifecycle

self.isDirty = false
deinit {
updateTimer?.invalidate()
}

override public func activateBell() {
super.activateBell()
}

extension TerminalController: TerminalDelegate {

public func send(source: Terminal, data: ArraySlice<UInt8>) {
subProcess?.write(data: Data(data))
}

public func bell(source: Terminal) {
delegate?.activateBell()
}

public func sizeChanged(source: Terminal) {
// TODO
// let screenSize = ScreenSize(width: UInt16(source.cols),
// height: UInt16(source.rows))
// if self.screenSize != screenSize {
// self.screenSize = screenSize
// }
}

public func setTerminalTitle(source: Terminal, title: String) {
delegate?.titleDidChange(title)
}

}

extension TerminalController: TerminalInputProtocol {

public var applicationCursor: Bool { terminal?.applicationCursor ?? false }

public func receiveKeyboardInput(data: Data) {
// Forward the data from the keyboard directly to the subprocess
subProcess!.write(data: data)
}

public func openSettings() {
delegate!.openSettings()
}

}

extension TerminalController: SubProcessDelegate {
Expand Down
22 changes: 22 additions & 0 deletions Common/Extensions/Color+Additions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Color+Additions.swift
// NewTerm Common
//
// Created by Adam Demasi on 2/4/21.
//

import SwiftTerm

extension Color {

convenience init(_ uiColor: UIColor) {
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
uiColor.getRed(&r, green: &g, blue: &b, alpha: nil)
self.init(red: UInt16(r * 255),
green: UInt16(g * 255),
blue: UInt16(b * 255))
}

}
7 changes: 3 additions & 4 deletions Common/UI/CrossPlatformUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
#if os(macOS)
import AppKit

public typealias Color = NSColor
public typealias Font = NSFont
public typealias UIFont = NSFont
public typealias UIColor = NSColor
public typealias Font = NSFont
public typealias UIFont = NSFont
public typealias UIFontDescriptor = NSFontDescriptor
#else
import UIKit

public typealias Color = UIColor
public typealias Font = UIFont
#endif
Loading

0 comments on commit b990bb7

Please sign in to comment.