From 91e92e27e4187b631a2a4680ab19f14ef20ce884 Mon Sep 17 00:00:00 2001 From: Masashi Aso Date: Fri, 23 Oct 2020 16:34:20 +0900 Subject: [PATCH] Initial Commit --- .gitignore | 6 + Demo/main.swift | 29 +++ LICENSE | 22 ++ Package.swift | 32 +++ README.md | 55 +++++ Sources/AnsiTerm/ANSIString/ANSIString.swift | 122 +++++++++++ Sources/AnsiTerm/ANSIString/ANSIStrings.swift | 74 +++++++ Sources/AnsiTerm/ANSIString/Color.swift | 135 ++++++++++++ .../AnsiTerm/ANSIString/StringExtension.swift | 66 ++++++ Sources/AnsiTerm/ANSIString/Style.swift | 204 ++++++++++++++++++ .../AnsiTerm/ANSIString/StyleModifier.swift | 21 ++ Sources/AnsiTerm/AUXPort.swift | 16 ++ Sources/AnsiTerm/Console.swift | 41 ++++ Sources/AnsiTerm/Cursor.swift | 83 +++++++ Tests/AnsiTermTests/XCTestManifests.swift | 9 + .../AnsiTermTests/swift_ansi_termTests.swift | 15 ++ Tests/LinuxMain.swift | 7 + 17 files changed, 937 insertions(+) create mode 100644 .gitignore create mode 100644 Demo/main.swift create mode 100644 LICENSE create mode 100644 Package.swift create mode 100644 README.md create mode 100644 Sources/AnsiTerm/ANSIString/ANSIString.swift create mode 100644 Sources/AnsiTerm/ANSIString/ANSIStrings.swift create mode 100644 Sources/AnsiTerm/ANSIString/Color.swift create mode 100644 Sources/AnsiTerm/ANSIString/StringExtension.swift create mode 100644 Sources/AnsiTerm/ANSIString/Style.swift create mode 100644 Sources/AnsiTerm/ANSIString/StyleModifier.swift create mode 100644 Sources/AnsiTerm/AUXPort.swift create mode 100644 Sources/AnsiTerm/Console.swift create mode 100644 Sources/AnsiTerm/Cursor.swift create mode 100644 Tests/AnsiTermTests/XCTestManifests.swift create mode 100644 Tests/AnsiTermTests/swift_ansi_termTests.swift create mode 100644 Tests/LinuxMain.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b90dbe --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +.swiftpm diff --git a/Demo/main.swift b/Demo/main.swift new file mode 100644 index 0000000..0259d47 --- /dev/null +++ b/Demo/main.swift @@ -0,0 +1,29 @@ +import AnsiTerm +import Foundation + +// like object oriented programming +let style = Style(foreground: .cyan, bold: true, blink: true) +let cyanText = ANSIString("cyan, bold, blink", style: style) +print(cyanText) + +let first = "first".foreground(.blue) +let second = "second".style(Style(background: .fixed(52))) +let third = "third".style(Style(foreground: .red, bold: true)) +let strings: ANSIStrings = [first, second, third] +print(strings) + +sleep(2) +Cursor.move(up: 2) +Cursor.move(forward: 6) + +// like functional programming +let over = "over," + .foreground(.black) + .background(.purple) + .blink() +print(over) + +sleep(2) +Cursor.move(forward: 10) +Console.clearLineBeforeCursor() +print() // print '\n' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6f4f9d0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2020 Masashi Aso + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..69221e3 --- /dev/null +++ b/Package.swift @@ -0,0 +1,32 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "swift-ansi-term", + products: [ + .library( + name: "AnsiTerm", + targets: ["AnsiTerm"]), + .executable( + name: "demo", + targets: ["Demo"]) + ], + dependencies: [ + ], + targets: [ + .target( + name: "AnsiTerm", + dependencies: []), + + .target( + name: "Demo", + dependencies: ["AnsiTerm"], + path: "Demo"), + + .testTarget( + name: "AnsiTermTests", + dependencies: ["AnsiTerm"]), + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..bf479e7 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# Swift Ansi Term + +## Usage + +### Color asn Style + +#### Like Object Oriented Programming + +```swift +import AnsiTerm + +let style = Style(foreground: .red, bold: true, blink: true) +let hello = "Hello, AnsiTerm!" +print(hello) +``` + +#### Like Functional Programming + +```swift +import AnsiTerm + +let yellowBoldText = "You can use modifier" + .foreground(.yellow) + .bold() +print(yellowBoldText) +``` + +### Cursor and Console + +```swift +import AnsiTerm + +print("Hello", terminator: "") +Cursor.move(backward: 5) +prnit("World") +Console.clearLine() +``` + +## Example + +`Demo/main.swift` is minimam example. I'll add more example. + +## Install + +To use the `AnsiTerm` in a SwiftPM project, add the following line to the dependencies in your `Package.swift` file: + +```swift +.package(url: "https://github.com/masasam-shi/swift-ansi-term", from: "0.0.1"), +``` + +and include "AnsyTirm" as a dependency for your executable target: + +```swift +.product(name: "AnsiTerm", package: "swift-ansi-term"), +``` \ No newline at end of file diff --git a/Sources/AnsiTerm/ANSIString/ANSIString.swift b/Sources/AnsiTerm/ANSIString/ANSIString.swift new file mode 100644 index 0000000..c55974d --- /dev/null +++ b/Sources/AnsiTerm/ANSIString/ANSIString.swift @@ -0,0 +1,122 @@ +// +// ANSIString.swift +// +// +// Created by Masashi Aso on 2020/10/24. +// + +public struct ANSIString { + public var style: Style + public var string: String + + public init(_ string: String, style: Style) { + self.string = string + self.style = style + } + + public init(_ string: String, color: Color) { + self.init(string, style: Style(foreground: color)) + } +} + +extension ANSIString: CustomStringConvertible { + public var description: String { + style.prefix() + string + style.suffix() + } +} + +// MARK: - Modifier +extension ANSIString: StyleModifier { + public typealias Modified = ANSIString + + @inline(__always) @inlinable + public func bold(_ isOn: Bool = true) -> ANSIString { + var new = self + new.style = self.style.bold(isOn) + return new + } + + @inline(__always) @inlinable + public func dim(_ isOn: Bool = true) -> ANSIString { + var new = self + new.style = self.style.dim(isOn) + return new + } + + @inline(__always) @inlinable + public func italic(_ isOn: Bool = true) -> ANSIString { + var new = self + new.style = self.style.italic(isOn) + return new + } + + @inline(__always) @inlinable + public func underline(_ isOn: Bool = true) -> ANSIString { + var new = self + new.style = self.style.underline(isOn) + return new + } + + @inline(__always) @inlinable + public func blink(_ isOn: Bool = true) -> ANSIString { + var new = self + new.style = self.style.blink(isOn) + return new + } + + @inline(__always) @inlinable + public func reverse(_ isOn: Bool = true) -> ANSIString { + var new = self + new.style = self.style.reverse(isOn) + return new + } + + @inline(__always) @inlinable + public func hide(_ isOn: Bool = true) -> ANSIString { + var new = self + new.style = self.style.hide(isOn) + return new + } + + @inline(__always) @inlinable + public func strikethrough(_ isOn: Bool = true) -> ANSIString { + var new = self + new.style = self.style.strikethrough(isOn) + return new + } + + @inline(__always) @inlinable + public func foreground(_ color: Color?) -> ANSIString { + var new = self + new.style = self.style.foreground(color) + return new + } + + @inline(__always) @inlinable + public func background(_ color: Color?) -> ANSIString { + var new = self + new.style = self.style.background(color) + return new + } +} + +// MARK: StringProtocol +extension ANSIString: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self = .init(value, style: Style()) + } +} + +// TODO: improve performance +// make `ANSICharacter` and `ANSISubstring`, +// implement ANSIString for StringProtocol. +//extension ANSIString: Sequence { +// public typealias Element = ANSIString +// public typealias Iterator = IndexingIterator> +// +// public func makeIterator() -> Iterator { +// string +// .map { ANSIString(String($0), style: style) } +// .makeIterator() +// } +//} diff --git a/Sources/AnsiTerm/ANSIString/ANSIStrings.swift b/Sources/AnsiTerm/ANSIString/ANSIStrings.swift new file mode 100644 index 0000000..eb40f7f --- /dev/null +++ b/Sources/AnsiTerm/ANSIString/ANSIStrings.swift @@ -0,0 +1,74 @@ +// +// ANSIStrings.swift +// +// +// Created by Masashi Aso on 2020/10/24. +// + +public struct ANSIStrings { + private var strings: [ANSIString] + + public init(_ ansiStrings: [ANSIString]) { + self.strings = ansiStrings + } + + public init(_ ansiStrings: ANSIString...) { + self.strings = ansiStrings + } +} + +extension ANSIStrings: CustomStringConvertible { + public var description: String { + if strings.count == 0 { return "" } + if strings.count == 1 { return strings.first!.description } + + let prefix = strings.first!.style.prefix() + let zipped = zip(strings[..> + + public func makeIterator() -> Iterator { + strings.makeIterator() + } +} + +extension ANSIStrings: Collection { + public typealias Index = Int + + public var count: Int { strings.count } + + public var startIndex: Int { strings.startIndex } + public var endIndex: Int { strings.endIndex } + + public func index(after i: Int) -> Int { + strings.index(after: i) + } + + public subscript(position: Int) -> ANSIString { + strings[position] + } +} + +extension ANSIStrings: BidirectionalCollection { + public typealias SubSequence = ANSIStrings + + public func index(before i: Int) -> Int { + strings.index(before: i) + } +} + +extension ANSIStrings: RandomAccessCollection {} + +extension ANSIStrings: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Element...) { + self = .init(elements) + } +} diff --git a/Sources/AnsiTerm/ANSIString/Color.swift b/Sources/AnsiTerm/ANSIString/Color.swift new file mode 100644 index 0000000..16ac72d --- /dev/null +++ b/Sources/AnsiTerm/ANSIString/Color.swift @@ -0,0 +1,135 @@ +// +// Color.swift +// +// +// Created by Masashi Aso on 2020/10/24. +// + +public enum Color { + /// foreground code: 30, background code: 40 + case black + /// foreground code: 31, background code: 41 + case red + /// foreground code: 32, background code: 42 + case green + /// foreground code: 33, background code: 43 + case yellow + /// foreground code: 34, background code: 44 + case blue + /// foreground code: 35, background code: 45 + case purple + /// foreground code: 36, background code: 46 + case cyan + /// foreground code: 37, background code: 47 + case white + + /// 256 terminal color + /// + /// - 0 ~ 7: + /// - 8 ~ 15: + /// - 16 ~ 231: + /// - 232 ~ 255: + /// + /// foreground code: 38;5;n, background code: 48;5;n + case fixed(UInt8) + + /// 24-bit RGB color + /// + /// foreground code: 38;2;r;g;b, background code: 48;2;r;g;b + case rgb(UInt8, UInt8, UInt8) +} + +// MARK: - Modifier +extension Color: StyleModifier { + public typealias Modified = Style + + @inline(__always) @inlinable + public func bold(_ isOn: Bool = true) -> Style { + Style(foreground: self, bold: isOn) + } + + @inline(__always) @inlinable + public func dim(_ isOn: Bool = true) -> Style { + Style(foreground: self, dim: isOn) + } + + @inline(__always) @inlinable + public func italic(_ isOn: Bool = true) -> Style { + Style(foreground: self, italic: isOn) + } + + @inline(__always) @inlinable + public func underline(_ isOn: Bool = true) -> Style { + Style(foreground: self, underline: isOn) + } + + @inline(__always) @inlinable + public func blink(_ isOn: Bool = true) -> Style { + Style(foreground: self, blink: isOn) + } + + @inline(__always) @inlinable + public func reverse(_ isOn: Bool = true) -> Style { + Style(foreground: self, reverse: isOn) + } + + @inline(__always) @inlinable + public func hide(_ isOn: Bool = true) -> Style { + Style(foreground: self, hide: isOn) + } + + @inline(__always) @inlinable + public func strikethrough(_ isOn: Bool = true) -> Style { + Style(foreground: self, strikethrough: isOn) + } + + @inline(__always) @inlinable + public func foreground(_ color: Color?) -> Style { + Style(foreground: color) + } + + @inline(__always) @inlinable + public func background(_ color: Color?) -> Style { + Style(foreground: self, background: color) + } +} + +// MARK: - Generate a Code +internal extension Color { + func foregroundCode() -> String { + switch self { + case .black: return "30;" + case .red: return "31;" + case .green: return "32;" + case .yellow: return "33;" + case .blue: return "34;" + case .purple: return "35;" + case .cyan: return "36;" + case .white: return "37;" + case .fixed(let value): + return "38;5;\(value);" + case .rgb(let r, let g, let b): + return "38;2;\(r);\(g);\(b);" + } + } + + func backgroundCode() -> String { + switch self { + case .black: return "40;" + case .red: return "41;" + case .green: return "42;" + case .yellow: return "43;" + case .blue: return "44;" + case .purple: return "45;" + case .cyan: return "46;" + case .white: return "47;" + case .fixed(let value): + return "48;5;\(value);" + case .rgb(let r, let g, let b): + return "48;2;\(r);\(g);\(b);" + } + } +} + +// MARK: - Other +extension Color: Equatable, Hashable {} diff --git a/Sources/AnsiTerm/ANSIString/StringExtension.swift b/Sources/AnsiTerm/ANSIString/StringExtension.swift new file mode 100644 index 0000000..81d816d --- /dev/null +++ b/Sources/AnsiTerm/ANSIString/StringExtension.swift @@ -0,0 +1,66 @@ +// +// StringExtension.swift +// +// +// Created by Masashi Aso on 2020/10/24. +// + +extension String { + public func style(_ style: Style) -> ANSIString { + ANSIString(self, style: style) + } +} + +extension String: StyleModifier { + public typealias Modified = ANSIString + + @inline(__always) @inlinable + public func bold(_ isOn: Bool = true) -> ANSIString { + ANSIString(self, style: Style(bold: isOn)) + } + + @inline(__always) @inlinable + public func dim(_ isOn: Bool = true) -> ANSIString { + ANSIString(self, style: Style(dim: isOn)) + } + + @inline(__always) @inlinable + public func italic(_ isOn: Bool = true) -> ANSIString { + ANSIString(self, style: Style(italic: isOn)) + } + + @inline(__always) @inlinable + public func underline(_ isOn: Bool = true) -> ANSIString { + ANSIString(self, style: Style(underline: isOn)) + } + + @inline(__always) @inlinable + public func blink(_ isOn: Bool = true) -> ANSIString { + ANSIString(self, style: Style(blink: isOn)) + } + + @inline(__always) @inlinable + public func reverse(_ isOn: Bool = true) -> ANSIString { + ANSIString(self, style: Style(reverse: isOn)) + } + + @inline(__always) @inlinable + public func hide(_ isOn: Bool = true) -> ANSIString { + ANSIString(self, style: Style(hide: isOn)) + } + + @inline(__always) @inlinable + public func strikethrough(_ isOn: Bool = true) -> ANSIString { + ANSIString(self, style: Style(strikethrough: isOn)) + } + + @inline(__always) @inlinable + public func foreground(_ color: Color?) -> ANSIString { + ANSIString(self, style: Style(foreground: color)) + } + + @inline(__always) @inlinable + public func background(_ color: Color?) -> ANSIString { + ANSIString(self, style: Style(background: color)) + } +} diff --git a/Sources/AnsiTerm/ANSIString/Style.swift b/Sources/AnsiTerm/ANSIString/Style.swift new file mode 100644 index 0000000..2fe864a --- /dev/null +++ b/Sources/AnsiTerm/ANSIString/Style.swift @@ -0,0 +1,204 @@ +// +// Style.swift +// +// +// Created by Masashi Aso on 2020/10/24. +// + +public struct Style { + public var foregroundColor: Color? + public var backgroundColor: Color? + public var isBoldfaced: Bool + public var isDimmed: Bool + public var isItalicized: Bool + public var isUnderlined: Bool + public var isBlinked: Bool + public var isReversed: Bool + public var isHidden: Bool + public var isStrikethrough: Bool + + public init( + foreground: Color? = nil, + background: Color? = nil, + bold: Bool = false, + dim: Bool = false, + italic: Bool = false, + underline: Bool = false, + blink: Bool = false, + reverse: Bool = false, + hide: Bool = false, + strikethrough: Bool = false + ) { + self.foregroundColor = foreground + self.backgroundColor = background + self.isBoldfaced = bold + self.isDimmed = dim + self.isItalicized = italic + self.isUnderlined = underline + self.isBlinked = blink + self.isReversed = reverse + self.isHidden = hide + self.isStrikethrough = strikethrough + } +} + +// MARK: - Modifiers +extension Style: StyleModifier { + + public typealias Modified = Style + + @inline(__always) @inlinable + public func bold(_ isOn: Bool = true) -> Modified { + var new = self + new.isBoldfaced = isOn + return new + } + + @inline(__always) @inlinable + public func dim(_ isOn: Bool = true) -> Modified { + var new = self + new.isDimmed = isOn + return new + } + + @inline(__always) @inlinable + public func italic(_ isOn: Bool = true) -> Modified { + var new = self + new.isItalicized = isOn + return new + } + + @inline(__always) @inlinable + public func underline(_ isOn: Bool = true) -> Modified { + var new = self + new.isUnderlined = isOn + return new + } + + @inline(__always) @inlinable + public func blink(_ isOn: Bool = true) -> Modified { + var new = self + new.isBlinked = isOn + return new + } + + @inline(__always) @inlinable + public func reverse(_ isOn: Bool = true) -> Modified { + var new = self + new.isReversed = isOn + return new + } + + @inline(__always) @inlinable + public func hide(_ isOn: Bool = true) -> Modified { + var new = self + new.isHidden = isOn + return new + } + + @inline(__always) @inlinable + public func strikethrough(_ isOn: Bool = true) -> Modified { + var new = self + new.isStrikethrough = isOn + return new + } + + @inline(__always) @inlinable + public func foreground(_ color: Color?) -> Modified { + var new = self + new.foregroundColor = color + return new + } + + @inline(__always) @inlinable + public func background(_ color: Color?) -> Modified { + var new = self + new.backgroundColor = color + return new + } +} + +// MARK: - Generate Code +public extension Style { + func prefix() -> String { + if isPlain { return "" } + var text = "\u{1b}[" + + if isBoldfaced { text += "1;" } + if isDimmed { text += "2;" } + if isItalicized { text += "3;" } + if isUnderlined { text += "4;" } + if isBlinked { text += "5;" } + if isReversed { text += "7;" } + if isHidden { text += "8;" } + if isStrikethrough { text += "9;" } + + if let fb = foregroundColor { + text += fb.foregroundCode() + } + if let bg = backgroundColor { + text += bg.backgroundCode() + } + + // remove last ';' + text.removeLast() + text += "m" + return text + } + + func infix(next: Style) -> String { + if self == next { return "" } + if next.isPlain { return "\u{1b}[0m" } + var text = "\u{1b}[" + + func modifyCode( + for keyPath: KeyPath, toOn: UInt8, toOff: UInt8 + ) { + switch (self[keyPath: keyPath], next[keyPath: keyPath]) { + case (true, true), (false, false): break + case (false, true): text += "\(toOn);" + case (true, false): text += "\(toOff);" + } + } + + // bold and dim's disable code are same, 22. + if self.isBoldfaced || self.isDimmed { text += "22;" } + if next.isBoldfaced { text += "1;" } + if next.isDimmed { text += "2;" } + modifyCode(for: \.isItalicized, toOn: 3, toOff: 23) + modifyCode(for: \.isUnderlined, toOn: 4, toOff: 24) + modifyCode(for: \.isBlinked, toOn: 5, toOff: 25) + modifyCode(for: \.isReversed, toOn: 7, toOff: 27) + modifyCode(for: \.isHidden, toOn: 8, toOff: 28) + modifyCode(for: \.isStrikethrough, toOn: 9, toOff: 29) + + switch (self.foregroundColor, next.foregroundColor) { + case (.none, .none): break + case (.some(let a), .some(let b)) where a == b: break + case (_, .some(let new)): text += new.foregroundCode() + case (.some(_), .none): text += "39;" + } + switch (self.backgroundColor, next.backgroundColor) { + case (.none, .none): break + case (.some(let a), .some(let b)) where a == b: break + case (_, .some(let new)): text += new.backgroundCode() + case (.some(_), .none): text += "49;" + } + + // remove last ';' + text.removeLast() + text += "m" + return text + } + + func suffix() -> String { + isPlain ? "" : "\u{1b}[0m" + } +} + +// MARK: Other +extension Style: Equatable, Hashable { + var isPlain: Bool { + self == Style() + } +} diff --git a/Sources/AnsiTerm/ANSIString/StyleModifier.swift b/Sources/AnsiTerm/ANSIString/StyleModifier.swift new file mode 100644 index 0000000..6bb57ef --- /dev/null +++ b/Sources/AnsiTerm/ANSIString/StyleModifier.swift @@ -0,0 +1,21 @@ +// +// StyleModifier.swift +// +// +// Created by Masashi Aso on 2020/10/24. +// + +public protocol StyleModifier { + associatedtype Modified + + func bold (_ isOn: Bool) -> Modified + func dim (_ isOn: Bool) -> Modified + func italic (_ isOn: Bool) -> Modified + func underline (_ isOn: Bool) -> Modified + func blink (_ isOn: Bool) -> Modified + func reverse (_ isOn: Bool) -> Modified + func hide (_ isOn: Bool) -> Modified + func strikethrough(_ isOn: Bool) -> Modified + func foreground(_ color: Color?) -> Modified + func background(_ color: Color?) -> Modified +} diff --git a/Sources/AnsiTerm/AUXPort.swift b/Sources/AnsiTerm/AUXPort.swift new file mode 100644 index 0000000..d359b54 --- /dev/null +++ b/Sources/AnsiTerm/AUXPort.swift @@ -0,0 +1,16 @@ +// +// File.swift +// +// +// Created by Masashi Aso on 2020/10/25. +// + +public enum AUXPort { + public static func on() { + print("\u{1b}[5i") + } + + public static func off() { + print("\u{1b}[4i") + } +} diff --git a/Sources/AnsiTerm/Console.swift b/Sources/AnsiTerm/Console.swift new file mode 100644 index 0000000..69f70f6 --- /dev/null +++ b/Sources/AnsiTerm/Console.swift @@ -0,0 +1,41 @@ +// +// Console.swift +// +// +// Created by Masashi Aso on 2020/10/24. +// + +public enum Console { + public static func scroll(_ n: Int) { + if n >= 0 { + print("\u{1b}[\(n)S", terminator: "") + } else { + print("\u{1b}[\(abs(n))T", terminator: "") + } + } + + public static func clearAfterCursor() { + print("\u{1b}[0J", terminator: "") + } + + public static func clearBeforeCursor() { + print("\u{1b}[1J", terminator: "") + } + + public static func clearAll() { + print("\u{1b}[2K", terminator: "") + } + + public static func clearLineAfterCursor() { + print("\u{1b}[1K", terminator: "") + } + + public static func clearLineBeforeCursor() { + print("\u{1b}[1K", terminator: "") + } + + public static func clearLine() { + print("\u{1b}[2K", terminator: "") + } +} + diff --git a/Sources/AnsiTerm/Cursor.swift b/Sources/AnsiTerm/Cursor.swift new file mode 100644 index 0000000..511667e --- /dev/null +++ b/Sources/AnsiTerm/Cursor.swift @@ -0,0 +1,83 @@ +// +// Cursor.swift +// +// +// Created by Masashi Aso on 2020/10/24. +// + +public enum Cursor { + public static func move(up n: Int) { + if n >= 0 { + print("\u{1b}[\(n)A", terminator: "") + } else { + move(down: -n) + } + } + + public static func move(down n: Int) { + if n >= 0 { + print("\u{1b}[\(n)B", terminator: "") + } else { + move(up: -n) + } + } + + public static func move(forward n: Int) { + if n >= 0 { + print("\u{1b}[\(n)C", terminator: "") + } else { + move(backward: -n) + } + } + + public static func move(backward n: Int) { + if n >= 0 { + print("\u{1b}[\(n)D", terminator: "") + } else { + move(forward: -n) + } + } + + public static func move(startOfLine n: Int) { + if n >= 0 { + print("\u{1b}[\(n)E", terminator: "") + } else { + print("\u{1b}[\(n)F", terminator: "") + } + } + + public static func move(toN n: Int) { + precondition(n >= 0, "x must be greeter than 0") + print("`u{1b}[\(n)G", terminator: "") + } + + public static func move(toN n: Int, m: Int) { + precondition(n >= 0, "x must be greeter than 0") + precondition(m >= 0, "y must be greeter than 0") + print("\u{1b}[\(n);\(m)H", terminator: "") + } + +// public static func getPosition() -> (n: Int, m: Int)? { +// let process = Process() +// process.launchPath = "/usr/bin/env" +// process.arguments = ["echo", "\u{1b}[6n"] +// +// let pipe = Pipe() +// process.standardOutput = pipe +// process.launch() +// let data = pipe.fileHandleForReading.readDataToEndOfFile() +// +// print(String(data: data, encoding: .utf8) as Any) +// +// guard let result = String(data: data, encoding: .utf8), +// let separatorIndex = result.firstIndex(where: { $0 == ";" }) else { +// return nil +// } +// let startIndex = result.index(result.startIndex, offsetBy: 2) +// let n = result[startIndex.. [XCTestCaseEntry] { + return [ + testCase(swift_ansi_termTests.allTests), + ] +} +#endif diff --git a/Tests/AnsiTermTests/swift_ansi_termTests.swift b/Tests/AnsiTermTests/swift_ansi_termTests.swift new file mode 100644 index 0000000..636cddf --- /dev/null +++ b/Tests/AnsiTermTests/swift_ansi_termTests.swift @@ -0,0 +1,15 @@ +import XCTest +@testable import swift_ansi_term + +final class swift_ansi_termTests: XCTestCase { + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct + // results. + XCTAssertEqual(swift_ansi_term().text, "Hello, World!") + } + + static var allTests = [ + ("testExample", testExample), + ] +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift new file mode 100644 index 0000000..e4fc56d --- /dev/null +++ b/Tests/LinuxMain.swift @@ -0,0 +1,7 @@ +import XCTest + +import swift_ansi_termTests + +var tests = [XCTestCaseEntry]() +tests += swift_ansi_termTests.allTests() +XCTMain(tests)