mirror of
https://github.com/coteditor/CotEditor.git
synced 2024-09-19 07:07:18 +03:00
Create format style for FuzzyRange
This commit is contained in:
parent
114d0276b9
commit
4260b07841
@ -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 {
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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"))
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user