diff --git a/Sources/ChromaShell/ChromaFrame.swift b/Sources/ChromaShell/ChromaFrame.swift index 075842e..4bb096e 100644 --- a/Sources/ChromaShell/ChromaFrame.swift +++ b/Sources/ChromaShell/ChromaFrame.swift @@ -15,17 +15,16 @@ public struct ChromaFrame { /// NOTE: we can not test this as the test terminal has no size. I /// wonder if can detect if testing or just have to work around this. init( - fill char: Character, + fill char: Character, _ height: Int, _ width: Int, _ foreground: Color = .default, _ background: Color = .default ) { var out = "" - let size = Terminal.size() - for y in 0...size.y { - for _ in 1...size.x { + for y in 0...height { + for _ in 1...width { out += "\(char)" } - if y != size.y { + if y != height { out += "\n" } } diff --git a/Sources/ChromaShell/InteractionLoop.swift b/Sources/ChromaShell/InteractionLoop.swift index 742aebe..e9af030 100644 --- a/Sources/ChromaShell/InteractionLoop.swift +++ b/Sources/ChromaShell/InteractionLoop.swift @@ -6,29 +6,22 @@ import ScribeCore /// having a deinit for structs. struct InteractionLoop: ~Copyable { - private let originalConfig = Terminal.enableRawMode() private let scribe: Scribe + let terminal: Terminal = Terminal() init(_ block: some Block) async { - let size = Terminal.size() self.scribe = await Scribe( - observing: block, width: size.x, height: size.y, draw(_:_:_:)) - Terminal.setup() + observing: block, width: terminal.size.x, height: terminal.size.y, draw(_:_:_:)) } - deinit { - Terminal.restore(originalConfig) - Terminal.reset() - } func start() async { let standardInput = Process().standardInput let fileHandleForStandardIn = standardInput as! FileHandle - var size = Terminal.size() // Draw the first frame let frame = await scribe.current - draw(frame, size.x, size.y) + draw(frame, terminal.size.x, terminal.size.y) do { for try await byte in fileHandleForStandardIn.asyncByteIterator() { // update on input @@ -37,8 +30,7 @@ struct InteractionLoop: ~Copyable { } // Update size for next frame - size = Terminal.size() - await scribe.updateSize(width: size.x, height: size.y) + await scribe.updateSize(width: terminal.size.x, height: terminal.size.y) switch await scribe.mode { case .input: @@ -75,7 +67,6 @@ struct InteractionLoop: ~Copyable { } catch let error { // Lets keep all errors internal as we are changing the default behavior of the Terminal // TODO logging - Terminal.reset() print(error.localizedDescription) return } diff --git a/Sources/ChromaShell/Terminal/Terminal.swift b/Sources/ChromaShell/Terminal/Terminal.swift index 8520aa6..dcde1e4 100644 --- a/Sources/ChromaShell/Terminal/Terminal.swift +++ b/Sources/ChromaShell/Terminal/Terminal.swift @@ -17,11 +17,31 @@ import Foundation /// they are pressed. This is definitely a HACK on top of the terminal to test /// out an idea I have been stuck on as It seemed easier then learning how /// MacOS and other operating systems send key commands to programs. -public enum Terminal { - static func enableRawMode() -> termios { +public struct Terminal: ~Copyable { + private let prev: termios + + public init() { + self.prev = Terminal.enableRawMode() + Terminal.setup() + } + + /// Restore the original terminal config + /// Clear the last frame from the screen + deinit { + Terminal.restore(prev) + Terminal.reset() + } + + private static func restore(_ originalConfig: termios) { + var term = originalConfig + // restores the original terminal state + tcsetattr(FileHandle.standardInput.fileDescriptor, TCSAFLUSH, &term) + } + + private static func enableRawMode() -> termios { // see https://stackoverflow.com/a/24335355/669586 // init raw: termios variable - var raw: termios = initCStruct() + var raw: termios = Terminal.initCStruct() // sets raw to a copy of the file handlers attributes tcgetattr(FileHandle.standardInput.fileDescriptor, &raw) // saves a copy of the original standard output file descriptor to revert back to @@ -39,24 +59,17 @@ public enum Terminal { return originalConfig } - static func restore(_ originalConfig: termios) { - var term = originalConfig - // restores the original terminal state - tcsetattr(FileHandle.standardInput.fileDescriptor, TCSAFLUSH, &term) - } - - static func initCStruct() -> S { + private static func initCStruct() -> S { let structPointer = UnsafeMutablePointer.allocate(capacity: 1) let structMemory = structPointer.pointee structPointer.deallocate() return structMemory } - /// Returns the max dimensions of the current Terminal - public static func size() -> TerminalSize { + public var size: TerminalSize { // TODO look into the SIGWINCH signal maybe replace this function or // its call sites. - var w: winsize = initCStruct() + var w: winsize = Terminal.initCStruct() //???: Is it possible to get a call back or notification of when the window is resized _ = ioctl(STDOUT_FILENO, UInt(TIOCGWINSZ), &w) // Check that we have a valid window size @@ -69,10 +82,6 @@ public enum Terminal { } } - enum TerminalSizeError: Error { - case hight - case width - } } public struct TerminalSize: Hashable { @@ -93,13 +102,13 @@ extension Terminal { } /// Used to write the contents of of the frame to the screen. - static func write(frame strFrame: String) { + public static func write(frame strFrame: String) { clear() FileHandle.standardOutput.write(Data(strFrame.utf8)) } /// clears the screen to setup, reset or write a new frame to the screen. - static func clear() { + private static func clear() { FileHandle.standardOutput.write(Data(Terminal.clearCode.utf8)) } @@ -109,17 +118,17 @@ extension Terminal { FileHandle.standardOutput.write(Data(Terminal.restCode.utf8)) } - static var restCode: String { + private static var restCode: String { AnsiEscapeCode.Cursor.show.rawValue + AnsiEscapeCode.Cursor.Style.Block.blinking.rawValue + AnsiEscapeCode.home.rawValue } - static var setupCode: String { + private static var setupCode: String { AnsiEscapeCode.Cursor.hide.rawValue + clearCode } - static var clearCode: String { + private static var clearCode: String { AnsiEscapeCode.eraseScreen.rawValue + AnsiEscapeCode.eraseSaved.rawValue + AnsiEscapeCode.home.rawValue + AnsiEscapeCode.Cursor.Style.Block.blinking.rawValue