Skip to content

Commit

Permalink
LazyTextStoringMonitor and CompositeTextStoringMonitor
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Oct 4, 2021
1 parent dd8afec commit 060316d
Show file tree
Hide file tree
Showing 13 changed files with 510 additions and 3 deletions.
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

130 changes: 130 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/TextStory-Package.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextStory"
BuildableName = "TextStory"
BlueprintName = "TextStory"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextStoryTesting"
BuildableName = "TextStoryTesting"
BlueprintName = "TextStoryTesting"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextStoryTests"
BuildableName = "TextStoryTests"
BlueprintName = "TextStoryTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Internal"
BuildableName = "Internal"
BlueprintName = "Internal"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextStory"
BuildableName = "TextStory"
BlueprintName = "TextStory"
ReferencedContainer = "container:">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextStoryTests"
BuildableName = "TextStoryTests"
BlueprintName = "TextStoryTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextStory"
BuildableName = "TextStory"
BlueprintName = "TextStory"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
67 changes: 67 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/TextStory.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextStory"
BuildableName = "TextStory"
BlueprintName = "TextStory"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextStory"
BuildableName = "TextStory"
BlueprintName = "TextStory"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
67 changes: 67 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/TextStoryTesting.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextStoryTesting"
BuildableName = "TextStoryTesting"
BlueprintName = "TextStoryTesting"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextStoryTesting"
BuildableName = "TextStoryTesting"
BlueprintName = "TextStoryTesting"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ let package = Package(
platforms: [.macOS(.v10_12), .iOS(.v10)],
products: [
.library(name: "TextStory", targets: ["TextStory"]),
.library(name: "TextStoryTesting", targets: ["TextStoryTesting"]),
],
dependencies: [
.package(url: "https://github.com/ChimeHQ/Rearrange.git", from: "1.1.2")
Expand All @@ -24,6 +25,7 @@ let package = Package(
// hack #3 - the import statement needs to be conditional, so Xcode builds work normally
swiftSettings: [.define("SPM_BUILD")]
),
.testTarget(name: "TextStoryTests", dependencies: ["TextStory"], path: "TextStoryTests/"),
.target(name: "TextStoryTesting", dependencies: ["TextStory"], path: "TextStoryTesting"),
.testTarget(name: "TextStoryTests", dependencies: ["TextStory", "TextStoryTesting"], path: "TextStoryTests/"),
]
)
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ func applyNextChange()

A simple protocol that abstracts string storage. This is very useful for standardizing behavior across `NSTextStorage`, `NSTextView` and other objects you may use for text manipulation. Particularly handy for testing and decoupling systems from Apple's classes behaviors/APIs.

## TextStoringMonitor

Standard interface for systems that need to observe and react to changes in a `TextStoring` instance.

## LazyTextStoringMonitor

A concrete `TextStoringMonitor` class that implements progressive, on-demand access to a wrapped `TextStoringMonitor`. This makes it easy to add lazy semantics on top of an existing `TextStoringMonitor`, which can be very helpful for handling large documents.

## CompositeTextStoringMonitor

An easy way to group together a collection of `TextStoringMonitor` instances and tread them as a single unit.

### Suggestions or Feedback

We'd love to hear from you! Get in touch via [twitter](https://twitter.com/chimehq), an issue, or a pull request.
Expand Down
12 changes: 12 additions & 0 deletions TextStory.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
C9C7D7AE23BEDD3A00282686 /* NSTextStorage+TextStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C7D7AD23BEDD3900282686 /* NSTextStorage+TextStoring.swift */; };
C9C7D7B423BEDDFF00282686 /* BufferingTextStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C7D7B223BEDDFF00282686 /* BufferingTextStorageTests.swift */; };
C9C7D7B723BF682200282686 /* Rearrange.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9C7D77623AE798F00282686 /* Rearrange.framework */; };
C9F74F61270B2D6500AEA7F1 /* TextStoringMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F74F60270B2D6500AEA7F1 /* TextStoringMonitor.swift */; };
C9F74F63270B2DAE00AEA7F1 /* CompositeTextStoringMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F74F62270B2DAE00AEA7F1 /* CompositeTextStoringMonitor.swift */; };
C9F74F65270B2DD600AEA7F1 /* LazyTextStoringMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F74F64270B2DD600AEA7F1 /* LazyTextStoringMonitor.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -50,6 +53,9 @@
C9C7D7AD23BEDD3900282686 /* NSTextStorage+TextStoring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextStorage+TextStoring.swift"; sourceTree = "<group>"; };
C9C7D7B223BEDDFF00282686 /* BufferingTextStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BufferingTextStorageTests.swift; sourceTree = "<group>"; };
C9C7D7B823BF684900282686 /* TextStoryTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TextStoryTests.xcconfig; sourceTree = "<group>"; };
C9F74F60270B2D6500AEA7F1 /* TextStoringMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextStoringMonitor.swift; sourceTree = "<group>"; };
C9F74F62270B2DAE00AEA7F1 /* CompositeTextStoringMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositeTextStoringMonitor.swift; sourceTree = "<group>"; };
C9F74F64270B2DD600AEA7F1 /* LazyTextStoringMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyTextStoringMonitor.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -105,6 +111,9 @@
C9C7D7AB23BEDD2C00282686 /* TextStoring.swift */,
C9C7D7AD23BEDD3900282686 /* NSTextStorage+TextStoring.swift */,
C990C99223D48CBA00203A47 /* Project.xcconfig */,
C9F74F60270B2D6500AEA7F1 /* TextStoringMonitor.swift */,
C9F74F62270B2DAE00AEA7F1 /* CompositeTextStoringMonitor.swift */,
C9F74F64270B2DD600AEA7F1 /* LazyTextStoringMonitor.swift */,
);
path = TextStory;
sourceTree = "<group>";
Expand Down Expand Up @@ -262,10 +271,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C9F74F61270B2D6500AEA7F1 /* TextStoringMonitor.swift in Sources */,
C9C7D7A523BE780E00282686 /* TSYTextStorage.m in Sources */,
C9F74F65270B2DD600AEA7F1 /* LazyTextStoringMonitor.swift in Sources */,
C9C7D7AE23BEDD3A00282686 /* NSTextStorage+TextStoring.swift in Sources */,
C9C7D7A723BE7B1400282686 /* BufferingTextStorage.swift in Sources */,
C9C7D7AA23BE7BEE00282686 /* TextMutation.swift in Sources */,
C9F74F63270B2DAE00AEA7F1 /* CompositeTextStoringMonitor.swift in Sources */,
C9C7D7AC23BEDD2C00282686 /* TextStoring.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
36 changes: 36 additions & 0 deletions TextStory/CompositeTextStoringMonitor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation

public struct CompositeTextStoringMonitor {
public var monitors: [TextStoringMonitor]
private var queue: DispatchQueue

init(monitors: [TextStoringMonitor], queue: DispatchQueue = DispatchQueue.main) {
self.monitors = monitors
self.queue = queue
}
}

extension CompositeTextStoringMonitor: TextStoringMonitor {
public func willApplyMutation(_ mutation: TextMutation, to storage: TextStoring) {
monitors.forEach({ $0.willApplyMutation(mutation, to: storage) })
}

public func didApplyMutation(_ mutation: TextMutation, to storage: TextStoring, completionHandler: @escaping () -> Void) {
let group = DispatchGroup()

for monitor in monitors {
group.enter()
monitor.didApplyMutation(mutation, to: storage) {
group.leave()
}
}

group.notify(queue: queue, execute: {
completionHandler()
})
}

public func didCompleteChangeProcessing(of mutation: TextMutation?, in storage: TextStoring) {
monitors.forEach({ $0.didCompleteChangeProcessing(of: mutation, in: storage) })
}
}
Loading

0 comments on commit 060316d

Please sign in to comment.