Extract LineEnding to package

This commit is contained in:
1024jp 2024-07-03 20:47:12 +09:00
parent 356186cf0f
commit 565d043074
16 changed files with 103 additions and 62 deletions

View File

@ -266,7 +266,6 @@
2A50AA63204D513500D10A10 /* FileAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A50AA61204D513500D10A10 /* FileAttributes.swift */; };
2A53F56727585A0E00ED16DF /* RegularExpressionReferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A53F56627585A0E00ED16DF /* RegularExpressionReferenceView.swift */; };
2A53F56827585A0E00ED16DF /* RegularExpressionReferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A53F56627585A0E00ED16DF /* RegularExpressionReferenceView.swift */; };
2A54BE2C1D40EB24000816B0 /* LineEndingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A54BE2B1D40EB24000816B0 /* LineEndingTests.swift */; };
2A55D5D82B7A728A0092DE48 /* AdvancedCharacterCount.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2A55D5D72B7A728A0092DE48 /* AdvancedCharacterCount.xcstrings */; };
2A55D5D92B7A728A0092DE48 /* AdvancedCharacterCount.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2A55D5D72B7A728A0092DE48 /* AdvancedCharacterCount.xcstrings */; };
2A55D5EA2B7A86190092DE48 /* IssueReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A55D5E92B7A86190092DE48 /* IssueReport.swift */; };
@ -482,8 +481,8 @@
2AA175FB2AC5634500F6462C /* PopoverHolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA175F92AC5634500F6462C /* PopoverHolderView.swift */; };
2AA2C6FC24399A920017D1EC /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 2AA2C6FB24399A920017D1EC /* Yams */; };
2AA2C6FE24399AA20017D1EC /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 2AA2C6FD24399AA20017D1EC /* Yams */; };
2AA375471D40BDCB0080C27C /* LineEnding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA375461D40BDCB0080C27C /* LineEnding.swift */; };
2AA375481D40BDCB0080C27C /* LineEnding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA375461D40BDCB0080C27C /* LineEnding.swift */; };
2AA375471D40BDCB0080C27C /* LineEnding+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA375461D40BDCB0080C27C /* LineEnding+Localization.swift */; };
2AA375481D40BDCB0080C27C /* LineEnding+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA375461D40BDCB0080C27C /* LineEnding+Localization.swift */; };
2AA45A4B1D2E871900A1A401 /* EditorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA45A4A1D2E871900A1A401 /* EditorViewController.swift */; };
2AA45A4C1D2E871900A1A401 /* EditorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA45A4A1D2E871900A1A401 /* EditorViewController.swift */; };
2AA45A511D2E938500A1A401 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA45A501D2E938500A1A401 /* NavigationBar.swift */; };
@ -938,7 +937,6 @@
2A4E637F20ADC45F0033CE63 /* NSBezierPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSBezierPath.swift; sourceTree = "<group>"; };
2A50AA61204D513500D10A10 /* FileAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileAttributes.swift; sourceTree = "<group>"; };
2A53F56627585A0E00ED16DF /* RegularExpressionReferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegularExpressionReferenceView.swift; sourceTree = "<group>"; };
2A54BE2B1D40EB24000816B0 /* LineEndingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineEndingTests.swift; sourceTree = "<group>"; };
2A55D5D72B7A728A0092DE48 /* AdvancedCharacterCount.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = AdvancedCharacterCount.xcstrings; sourceTree = "<group>"; };
2A55D5E92B7A86190092DE48 /* IssueReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReport.swift; sourceTree = "<group>"; };
2A57B98E294ED75900771696 /* RangedIntegerFormatStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangedIntegerFormatStyle.swift; sourceTree = "<group>"; };
@ -1013,8 +1011,8 @@
2A78BFB21D1B240900A583D2 /* UpdaterManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdaterManager.swift; sourceTree = "<group>"; };
2A78BFBB1D1B376000A583D2 /* ServicesProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServicesProvider.swift; sourceTree = "<group>"; };
2A7C92FB29FD64A8008343C8 /* DefaultKey+FontType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultKey+FontType.swift"; sourceTree = "<group>"; };
2A7E06E52C1A711B00E5396D /* EditorKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = EditorKit; sourceTree = "<group>"; };
2A7DFA492BE96C06001D5BDD /* DirectoryDocument+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DirectoryDocument+Actions.swift"; sourceTree = "<group>"; };
2A7E06E52C1A711B00E5396D /* EditorKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = EditorKit; sourceTree = "<group>"; };
2A7F4E012871F46D0029CE66 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PrintPanelAccessory.storyboard; sourceTree = "<group>"; };
2A7FEF0A2B90B05C0042BEFF /* FilterField.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = FilterField.xcstrings; sourceTree = "<group>"; };
2A80BE8C27FFA61700D2F7FF /* LineEndingScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineEndingScanner.swift; sourceTree = "<group>"; };
@ -1057,7 +1055,7 @@
2AA14CFE1FA498E900EAF586 /* UnixScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnixScript.swift; sourceTree = "<group>"; };
2AA14D011FA4999200EAF586 /* PersistentOSAScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentOSAScript.swift; sourceTree = "<group>"; };
2AA175F92AC5634500F6462C /* PopoverHolderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverHolderView.swift; sourceTree = "<group>"; };
2AA375461D40BDCB0080C27C /* LineEnding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineEnding.swift; sourceTree = "<group>"; };
2AA375461D40BDCB0080C27C /* LineEnding+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LineEnding+Localization.swift"; sourceTree = "<group>"; };
2AA45A4A1D2E871900A1A401 /* EditorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorViewController.swift; sourceTree = "<group>"; };
2AA45A501D2E938500A1A401 /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = "<group>"; };
2AA45A531D2F22C600A1A401 /* NSFont+Size.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFont+Size.swift"; sourceTree = "<group>"; };
@ -1766,7 +1764,6 @@
2A505C07298952E5002080AA /* KeyBinding */,
2A78F571298C90520084B8B4 /* Snippet */,
2AC6BFCF21D00A8500FF325C /* Regex Parser */,
2AA375461D40BDCB0080C27C /* LineEnding.swift */,
2A8C338E1D3E1C040005B0B7 /* IncompatibleCharacter.swift */,
2A4257BB1D239F850086DAAD /* Invisible.swift */,
2A50AA61204D513500D10A10 /* FileAttributes.swift */,
@ -2000,6 +1997,7 @@
children = (
2A428D432C2B03670051AD4F /* ValueRange+Identifiable.swift */,
2AA7BDDA2C1B10C80075BB6C /* UnicodeNormalizationForm.swift */,
2AA375461D40BDCB0080C27C /* LineEnding+Localization.swift */,
2A1893A91FFF422D00AD244F /* SortPatternError+Localization.swift */,
);
name = Libraries;
@ -2111,7 +2109,6 @@
2A63CEC31D0B06D800ED8186 /* SyntaxTests.swift */,
2ACC65311C98033D000574DC /* ThemeTests.swift */,
2A9C07551CF9F982006D672D /* IncompatibleCharacterTests.swift */,
2A54BE2B1D40EB24000816B0 /* LineEndingTests.swift */,
2AED46721E43942300751C45 /* TextFinderTests.swift */,
2AC72EA1253478D5001D3CA0 /* FileDropItemTests.swift */,
2ABEFB6923DC0CA0008769F4 /* EditorCounterTests.swift */,
@ -2781,7 +2778,7 @@
2A64F2431D256FCB001B229F /* KeyBindingManager.swift in Sources */,
2AA4D3751D1AA0AC001D261D /* KeyBindingsSettingsView.swift in Sources */,
2A6FD9E81D394F5900A59784 /* LayoutManager.swift in Sources */,
2AA375481D40BDCB0080C27C /* LineEnding.swift in Sources */,
2AA375481D40BDCB0080C27C /* LineEnding+Localization.swift in Sources */,
2A80BE8D27FFA61700D2F7FF /* LineEndingScanner.swift in Sources */,
2A6416A41D2F9F7200FA9E1A /* LineNumberView.swift in Sources */,
2A1125C423F1A86B006A1DB2 /* LineRangeCacheable.swift in Sources */,
@ -2961,7 +2958,6 @@
2A9082EF1D325ED900228F50 /* GeometryTests.swift in Sources */,
2A9C07561CF9F982006D672D /* IncompatibleCharacterTests.swift in Sources */,
2A80BE9227FFFA8900D2F7FF /* LineEndingScannerTests.swift in Sources */,
2A54BE2C1D40EB24000816B0 /* LineEndingTests.swift in Sources */,
2A1125C123F180FF006A1DB2 /* LineRangeCacheableTests.swift in Sources */,
2AEBD25A246BB4C200EC97A3 /* NSAttributedStringTests.swift in Sources */,
2A89160C2394B87100AC13EE /* NSLayoutManagerTests.swift in Sources */,
@ -3102,7 +3098,7 @@
2A64F2421D256FCB001B229F /* KeyBindingManager.swift in Sources */,
2AA4D3741D1AA0AC001D261D /* KeyBindingsSettingsView.swift in Sources */,
2A6FD9E71D394F5900A59784 /* LayoutManager.swift in Sources */,
2AA375471D40BDCB0080C27C /* LineEnding.swift in Sources */,
2AA375471D40BDCB0080C27C /* LineEnding+Localization.swift in Sources */,
2A80BE8E27FFA61700D2F7FF /* LineEndingScanner.swift in Sources */,
2A6416A31D2F9F7200FA9E1A /* LineNumberView.swift in Sources */,
2A1125C323F1A86B006A1DB2 /* LineRangeCacheable.swift in Sources */,

View File

@ -31,6 +31,7 @@ import UniformTypeIdentifiers
import OSLog
import Defaults
import FileEncoding
import LineEnding
import UnicodeNormalization
extension KeyPath: @retroactive @unchecked Sendable { }

View File

@ -27,6 +27,7 @@
import AppKit
import FileEncoding
import FuzzyRange
import LineEnding
private enum OSALineEnding: FourCharCode {

View File

@ -33,6 +33,7 @@ import OSLog
import Defaults
import FileEncoding
import FilePermissions
import LineEnding
import Syntax
extension Document: EditorSource {

View File

@ -28,6 +28,7 @@ import Observation
import Combine
import FileEncoding
import FilePermissions
import LineEnding
import Syntax
final class DocumentInspectorViewController: NSHostingController<DocumentInspectorView>, DocumentOwner {

View File

@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
// © 2019-2023 1024jp
// © 2019-2024 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@
//
import AppKit
import LineEnding
extension EditorTextView {

View File

@ -27,6 +27,7 @@
import AppKit
import Combine
import Defaults
import LineEnding
import Shortcut
import Syntax
import StringBasics

View File

@ -26,6 +26,7 @@
import SwiftUI
import Defaults
import FileEncoding
import LineEnding
struct FormatSettingsView: View {

View File

@ -25,6 +25,7 @@
import SwiftUI
import Observation
import LineEnding
import ValueRange
struct InconsistentLineEndingsView: View {

View File

@ -0,0 +1,61 @@
//
// LineEnding+Localization.swift
//
// CotEditor
// https://coteditor.com
//
// Created by 1024jp on 2014-11-30.
//
// ---------------------------------------------------------------------------
//
// © 2014-2024 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import LineEnding
extension LineEnding {
var description: String {
switch self {
case .lf:
String(localized: "LineEnding.lf.description",
defaultValue: "macOS / Unix",
table: "LineEnding")
case .cr:
String(localized: "LineEnding.cr.description",
defaultValue: "Classic Mac OS",
table: "LineEnding")
case .crlf:
String(localized: "LineEnding.crlf.description",
defaultValue: "Windows", table: "LineEnding")
case .nel:
String(localized: "LineEnding.nel.description",
defaultValue: "Unix Next Line",
table: "LineEnding",
comment: "Since this is a technical term, it should be left as-is.")
case .lineSeparator:
String(localized: "LineEnding.lineSeparator.description",
defaultValue: "Unix Line Separator",
table: "LineEnding",
comment: "Since this is a technical term, it should be left as-is.")
case .paragraphSeparator:
String(localized: "LineEnding.paragraphSeparator.description",
defaultValue: "Unix Paragraph Separator",
table: "LineEnding",
comment: "Since this is a technical term, it should be left as-is.")
}
}
}

View File

@ -26,7 +26,8 @@
import Combine
import Foundation
import Observation
import class AppKit.NSTextStorage
import AppKit.NSTextStorage
import LineEnding
import ValueRange
@Observable final class LineEndingScanner {

View File

@ -28,6 +28,7 @@ import Observation
import Combine
import Defaults
import FileEncoding
import LineEnding
final class StatusBarController: NSHostingController<StatusBar> {

View File

@ -25,6 +25,7 @@
import AppKit
import SwiftUI
import LineEnding
import TextFind
import ValueRange

View File

@ -16,6 +16,7 @@ let package = Package(
"FileEncoding",
"FilePermissions",
"FuzzyRange",
"LineEnding",
"LineSort",
"Shortcut",
"StringBasics",
@ -33,6 +34,7 @@ let package = Package(
.library(name: "FileEncoding", targets: ["FileEncoding"]),
.library(name: "FilePermissions", targets: ["FilePermissions"]),
.library(name: "FuzzyRange", targets: ["FuzzyRange"]),
.library(name: "LineEnding", targets: ["LineEnding"]),
.library(name: "LineSort", targets: ["LineSort"]),
.library(name: "StringBasics", targets: ["StringBasics"]),
.library(name: "Syntax", targets: ["Syntax"]),
@ -62,6 +64,9 @@ let package = Package(
.target(name: "FuzzyRange"),
.testTarget(name: "FuzzyRangeTests", dependencies: ["FuzzyRange"]),
.target(name: "LineEnding", dependencies: ["ValueRange"]),
.testTarget(name: "LineEndingTests", dependencies: ["LineEnding"]),
.target(name: "LineSort", dependencies: ["StringBasics"]),
.testTarget(name: "LineSortTests", dependencies: ["LineSort"]),

View File

@ -1,5 +1,6 @@
//
// LineEnding.swift
// LineEnding
//
// CotEditor
// https://coteditor.com
@ -26,7 +27,7 @@
import Foundation
import ValueRange
enum LineEnding: Character, CaseIterable {
public enum LineEnding: Character, Sendable, CaseIterable {
case lf = "\n"
case cr = "\r"
@ -36,25 +37,25 @@ enum LineEnding: Character, CaseIterable {
case paragraphSeparator = "\u{2029}"
var string: String {
public var string: String {
String(self.rawValue)
}
var length: Int {
public var length: Int {
self.rawValue.unicodeScalars.count
}
var index: Int {
public var index: Int {
Self.allCases.firstIndex(of: self)!
}
var isBasic: Bool {
public var isBasic: Bool {
switch self {
case .lf, .cr, .crlf: true
@ -63,7 +64,7 @@ enum LineEnding: Character, CaseIterable {
}
var label: String {
public var label: String {
switch self {
case .lf: "LF"
@ -74,46 +75,13 @@ enum LineEnding: Character, CaseIterable {
case .paragraphSeparator: "PS"
}
}
var description: String {
switch self {
case .lf:
String(localized: "LineEnding.lf.description",
defaultValue: "macOS / Unix",
table: "LineEnding")
case .cr:
String(localized: "LineEnding.cr.description",
defaultValue: "Classic Mac OS",
table: "LineEnding")
case .crlf:
String(localized: "LineEnding.crlf.description",
defaultValue: "Windows", table: "LineEnding")
case .nel:
String(localized: "LineEnding.nel.description",
defaultValue: "Unix Next Line",
table: "LineEnding",
comment: "Since this is a technical term, it should be left as-is.")
case .lineSeparator:
String(localized: "LineEnding.lineSeparator.description",
defaultValue: "Unix Line Separator",
table: "LineEnding",
comment: "Since this is a technical term, it should be left as-is.")
case .paragraphSeparator:
String(localized: "LineEnding.paragraphSeparator.description",
defaultValue: "Unix Paragraph Separator",
table: "LineEnding",
comment: "Since this is a technical term, it should be left as-is.")
}
}
}
// MARK: -
extension String {
public extension String {
/// Collects ranges of all line endings per line ending type from the beginning.
///
@ -126,14 +94,15 @@ extension String {
var lineEndingRanges: [ValueRange<LineEnding>] = []
let string = self as NSString
let range = range ?? NSRange(..<self.utf16.count)
string.enumerateSubstrings(in: range ?? string.range, options: [.byLines, .substringNotRequired]) { (_, substringRange, enclosingRange, _) in
guard !enclosingRange.isEmpty else { return }
string.enumerateSubstrings(in: range, options: [.byLines, .substringNotRequired]) { (_, substringRange, enclosingRange, _) in
guard enclosingRange.length > 0 else { return }
let lineEndingRange = NSRange(substringRange.upperBound..<enclosingRange.upperBound)
guard
!lineEndingRange.isEmpty,
lineEndingRange.length > 0,
let lastCharacter = string.substring(with: lineEndingRange).first, // line ending must be a single character
let lineEnding = LineEnding(rawValue: lastCharacter)
else { return }
@ -146,7 +115,7 @@ extension String {
}
extension StringProtocol {
public extension StringProtocol {
/// Returns a new string in which all line endings in the receiver are replaced with the given line endings.
///
@ -160,7 +129,7 @@ extension StringProtocol {
}
extension NSMutableAttributedString {
public extension NSMutableAttributedString {
/// Replaces all line endings in the receiver with given line endings.
///
@ -171,7 +140,7 @@ extension NSMutableAttributedString {
// -> Intentionally avoid replacing characters in the mutableString directly,
// because it pots a quantity of small edited notifications,
// which costs high. (2023-11, macOS 14)
self.replaceCharacters(in: self.range, with: self.string.replacingLineEndings(with: lineEnding))
self.replaceCharacters(in: NSRange(..<self.length), with: self.string.replacingLineEndings(with: lineEnding))
}
}

View File

@ -1,6 +1,6 @@
//
// LineEndingTests.swift
// Tests
// LineEndingTests
//
// CotEditor
// https://coteditor.com
@ -27,7 +27,7 @@
import Foundation
import Testing
import ValueRange
@testable import CotEditor
@testable import LineEnding
struct LineEndingTests {
@ -75,7 +75,6 @@ struct LineEndingTests {
}
private extension ValueRange where Value == LineEnding {
init(value: LineEnding, location: Int) {