Create format style for FuzzyRange

This commit is contained in:
1024jp 2023-12-26 12:48:56 +09:00
parent 114d0276b9
commit 4260b07841
3 changed files with 94 additions and 54 deletions

View File

@ -31,44 +31,88 @@ import Foundation
struct FuzzyRange: Equatable {
var location: Int
var length: Int
var length: Int = 0
}
// MARK: - Format Style
extension FuzzyRange {
/// Gets a formatted string from a format style.
///
/// - Parameter style: The fuzzy range format style.
/// - Returns: A formatted string.
func formatted(_ style: FuzzyRangeFormatStyle = .init()) -> FuzzyRangeFormatStyle.FormatOutput {
style.format(self)
}
}
extension FormatStyle where Self == FuzzyRange.FuzzyRangeFormatStyle {
static var fuzzyRange: FuzzyRange.FuzzyRangeFormatStyle {
FuzzyRange.FuzzyRangeFormatStyle()
}
}
extension FuzzyRange {
/// Creates a FuzzyRange instance from a string representation joined by `:`.
init?(string: String) {
struct FuzzyRangeFormatStyle: ParseableFormatStyle {
let components = string.components(separatedBy: ":").map(Int.init)
var parseStrategy: FuzzyRangeParseStrategy {
FuzzyRangeParseStrategy()
}
func format(_ value: FuzzyRange) -> String {
(0...1).contains(value.length)
? String(value.location)
: String(value.location) + ":" + String(value.length)
}
}
}
struct FuzzyRangeParseStrategy: ParseStrategy {
enum ParseError: Error {
case invalidValue
}
/// Creates an instance of the `ParseOutput` type from `value`.
///
/// - Parameter value: The string representation of `FuzzyRange` instance.
/// - Returns: A `FuzzyRange` instance.
/// - Throws: `ParseError`.
func parse(_ value: String) throws -> FuzzyRange {
let components = value.components(separatedBy: ":").map(Int.init)
guard
(1...2).contains(components.count),
let location = components[0],
let length = (components.count > 1) ? components[1] : 0
else { return nil }
else { throw ParseError.invalidValue }
self.location = location
self.length = length
}
/// String representation joined by `:`.
///
/// The length is omitted when it is 0 or 1.
var string: String {
(0...1).contains(self.length)
? String(self.location)
: String(self.location) + ":" + String(self.length)
return FuzzyRange(location: location, length: length)
}
}
// MARK: - NSRange conversion
extension String {
/// Converts FuzzyRange that allows negative values to the valid NSRange.
/// Converts FuzzyRange that allows negative values to a valid NSRange.
///
/// - Note:
/// A negative location accesses the element by counting backwards from the end.
@ -100,7 +144,7 @@ extension String {
///
/// - Note:
/// `location` of the passed-in range is 1-based. Passing a fuzzy range whose location is `0` returns `nil`.
/// The last new line character will be included to the return value.
/// The last line ending will be included to the return value.
///
/// - Parameters:
/// - lineRange: The line range that allows also negative values.
@ -140,7 +184,7 @@ extension String {
/// - Parameters:
/// - line: The number of the line that allows also negative values.
/// - column: The number of the column that allows also negative values.
/// - Throws: FuzzyLocationError
/// - Throws: `FuzzyLocationError`.
/// - Returns: An NSRange-based cursor location.
func fuzzyLocation(line: Int, column: Int = 0) throws -> Int {

View File

@ -29,29 +29,20 @@ struct GoToLineView: View {
weak var parent: NSHostingController<Self>?
@State private var value: String
private let completionHandler: (_ lineRange: FuzzyRange) -> Bool
/// The current line range.
@State var lineRange: FuzzyRange
/// The callback method to perform when the command was accepted.
let completionHandler: (_ lineRange: FuzzyRange) -> Bool
// MARK: View
/// Initializes view with given values.
///
/// - Parameters:
/// - lineRange: The current line range.
/// - completionHandler: The callback method to perform when the command was accepted.
init(lineRange: FuzzyRange, completionHandler: @escaping (_ lineRange: FuzzyRange) -> Bool) {
self._value = State(initialValue: lineRange.string)
self.completionHandler = completionHandler
}
var body: some View {
VStack {
Form {
TextField("Line:", text: $value, prompt: Text("Line Number"))
TextField("Line:", value: $lineRange, format: .fuzzyRange, prompt: Text("Line Number"))
.monospacedDigit()
.multilineTextAlignment(.trailing)
.onSubmit(self.submit)
@ -78,8 +69,7 @@ struct GoToLineView: View {
private func submit() {
guard
let lineRange = FuzzyRange(string: self.value),
self.completionHandler(lineRange)
self.completionHandler(self.lineRange)
else { return NSSound.beep() }
self.parent?.dismiss(nil)

View File

@ -71,24 +71,30 @@ final class FuzzyRangeTests: XCTestCase {
}
func testFuzzyRangeString() {
func testFormattingFuzzyRange() {
XCTAssertEqual(FuzzyRange(location: 0, length: 0).string, "0")
XCTAssertEqual(FuzzyRange(location: 1, length: 0).string, "1")
XCTAssertEqual(FuzzyRange(location: 1, length: 1).string, "1")
XCTAssertEqual(FuzzyRange(location: 1, length: 2).string, "1:2")
XCTAssertEqual(FuzzyRange(location: -1, length: 0).string, "-1")
XCTAssertEqual(FuzzyRange(location: -1, length: -1).string, "-1:-1")
XCTAssertEqual(FuzzyRange(location: 0, length: 0).formatted(), "0")
XCTAssertEqual(FuzzyRange(location: 1, length: 0).formatted(), "1")
XCTAssertEqual(FuzzyRange(location: 1, length: 1).formatted(), "1")
XCTAssertEqual(FuzzyRange(location: 1, length: 2).formatted(), "1:2")
XCTAssertEqual(FuzzyRange(location: -1, length: 0).formatted(), "-1")
XCTAssertEqual(FuzzyRange(location: -1, length: -1).formatted(), "-1:-1")
}
func testParsingFuzzyRange() throws {
XCTAssertEqual(FuzzyRange(string: "0"), FuzzyRange(location: 0, length: 0))
XCTAssertEqual(FuzzyRange(string: "1"), FuzzyRange(location: 1, length: 0))
XCTAssertEqual(FuzzyRange(string: "1:2"), FuzzyRange(location: 1, length: 2))
XCTAssertEqual(FuzzyRange(string: "-1"), FuzzyRange(location: -1, length: 0))
XCTAssertEqual(FuzzyRange(string: "-1:-1"), FuzzyRange(location: -1, length: -1))
XCTAssertNil(FuzzyRange(string: ""))
XCTAssertNil(FuzzyRange(string: "abc"))
XCTAssertNil(FuzzyRange(string: "1:a"))
XCTAssertNil(FuzzyRange(string: "1:1:1"))
let parser = FuzzyRangeParseStrategy()
XCTAssertEqual(try parser.parse("0"), FuzzyRange(location: 0, length: 0))
XCTAssertEqual(try parser.parse("1"), FuzzyRange(location: 1, length: 0))
XCTAssertEqual(try parser.parse("1:2"), FuzzyRange(location: 1, length: 2))
XCTAssertEqual(try parser.parse("-1"), FuzzyRange(location: -1, length: 0))
XCTAssertEqual(try parser.parse("-1:-1"), FuzzyRange(location: -1, length: -1))
XCTAssertThrowsError(try parser.parse(""))
XCTAssertThrowsError(try parser.parse("abc"))
XCTAssertThrowsError(try parser.parse("1:a"))
XCTAssertThrowsError(try parser.parse("1:1:1"))
}