Display concrete find progress

This commit is contained in:
1024jp 2022-12-11 11:31:45 +09:00
parent 865004349d
commit 95f8734584
7 changed files with 59 additions and 17 deletions

View File

@ -7,6 +7,8 @@ Change Log
### Improvements
- Optimize large find/replace task performance.
- Display the concrete progress of the find/replace task in the progress dialog.
- Update the Unicode block name list for the character inspector from Unicode 14.0.0 to Unicode 15.0.0.
- [trivial] Tweak the setting summary display in the print dialog.

View File

@ -29,14 +29,32 @@ final class FindProgress: ObservableObject {
var count = 0
var scope: Range<Int>
var completedUnit = 0
@Published var isCancelled = false
@Published var isFinished = false
/// The fraction of task completed in between 0...1.0.
var fractionCompleted: Double? {
/// Instantiate a progress.
///
/// - Parameter scope: The range of progress unit to work with.
init(scope: Range<Int>) {
self.isFinished ? 1 : nil
self.scope = scope
}
/// The fraction of task completed in between 0...1.0.
var fractionCompleted: Double {
if self.isFinished {
return 1
} else if self.scope.isEmpty {
return 0
} else {
return Double(self.completedUnit) / Double(self.scope.count)
}
}
}

View File

@ -158,7 +158,7 @@ struct FindProgressView_Previews: PreviewProvider {
static var previews: some View {
FindProgressView("Label", unit: .find, progress: .init())
FindProgressView("Label", unit: .find, progress: .init(scope: 0..<100))
}
}

View File

@ -41,7 +41,7 @@ extension MultipleReplacement {
textView.isEditable = false
// setup progress sheet
let progress = FindProgress()
let progress = FindProgress(scope: 0..<self.replacements.count)
let closesAutomatically = UserDefaults.standard[.findClosesIndicatorWhenDone]
let indicatorView = FindProgressView("Highlight All", unit: .find, progress: progress)
let indicator = NSHostingController(rootView: indicatorView)
@ -56,8 +56,10 @@ extension MultipleReplacement {
stop = true
return
}
progress.count += 1
} unitChanged: {
progress.completedUnit += 1
}
.sorted(\.location)
@ -110,7 +112,7 @@ extension MultipleReplacement {
textView.isEditable = false
// setup progress sheet
let progress = FindProgress()
let progress = FindProgress(scope: 0..<self.replacements.count)
let closesAutomatically = UserDefaults.standard[.findClosesIndicatorWhenDone]
let indicatorView = FindProgressView("Replace All", unit: .replacement, progress: progress)
let indicator = NSHostingController(rootView: indicatorView)
@ -125,8 +127,10 @@ extension MultipleReplacement {
stop = true
return
}
progress.count += 1
} unitChanged: {
progress.completedUnit += 1
}
DispatchQueue.main.async {

View File

@ -87,8 +87,9 @@ extension MultipleReplacement {
/// - inSelection: Whether find only in selection.
/// - block: The block enumerates the matches.
/// - stop: A reference to a Bool value. The block can set the value to true to stop further processing.
/// - unitChanged: The block invoked when the task did change to the next replecement definition.
/// - Returns: The found ranges. This method will return first all search finished.
func find(string: String, ranges: [NSRange], inSelection: Bool, using block: (_ stop: inout Bool) -> Void) -> [NSRange] {
func find(string: String, ranges: [NSRange], inSelection: Bool, using block: (_ stop: inout Bool) -> Void, unitChanged: () -> Void) -> [NSRange] {
var result: [NSRange] = []
@ -110,6 +111,9 @@ extension MultipleReplacement {
}
guard !isCancelled else { return [] }
// notify
unitChanged()
}
return result
@ -124,8 +128,9 @@ extension MultipleReplacement {
/// - inSelection: Whether replace only in selection.
/// - block: The block enumerates the matches.
/// - stop: A reference to a Bool value. The block can set the value to true to stop further processing.
/// - unitChanged: The block invoked when the task did change to the next replecement definition.
/// - Returns: The result of the replacement. This method will return first all replacement finished.
func replace(string: String, ranges: [NSRange], inSelection: Bool, using block: @escaping (_ stop: inout Bool) -> Void) -> Result {
func replace(string: String, ranges: [NSRange], inSelection: Bool, using block: @escaping (_ stop: inout Bool) -> Void, unitChanged: () -> Void) -> Result {
var result = Result(string: string, selectedRanges: ranges)
@ -140,7 +145,7 @@ extension MultipleReplacement {
// process replacement
var isCancelled = false
let (replacementItems, selectedRanges) = textFind.replaceAll(with: replacement.replacementString) { (flag, stop) in
let (replacementItems, selectedRanges) = textFind.replaceAll(with: replacement.replacementString) { (flag, _, stop) in
switch flag {
case .findProgress:
@ -162,6 +167,9 @@ extension MultipleReplacement {
// update selected ranges
result.selectedRanges = selectedRanges
// notify
unitChanged()
}
return result

View File

@ -172,6 +172,13 @@ final class TextFind {
}
/// The range large enough to contain all scope ranges.
var scopeRange: Range<Int> {
self.scopeRanges.map(\.lowerBound).min()!..<self.scopeRanges.map(\.upperBound).max()!
}
/// Return the nearest match from the insertion point.
///
/// - Parameters:
@ -301,11 +308,12 @@ final class TextFind {
/// - replacementString: The string with which to replace.
/// - block: The Block enumerates the matches.
/// - flag: The current state of the replacing progress.
/// - range: The matched range.
/// - stop: The Block can set the value to true to stop further processing of the array.
/// - Returns:
/// - replacementItems: ReplacementItem per selectedRange.
/// - selectedRanges: New selections for textView only if the replacement is performed within selection. Otherwise, nil.
func replaceAll(with replacementString: String, using block: @escaping (_ flag: ReplacingFlag, _ stop: inout Bool) -> Void) -> (replacementItems: [ReplacementItem], selectedRanges: [NSRange]?) {
func replaceAll(with replacementString: String, using block: @escaping (_ flag: ReplacingFlag, _ range: NSRange, _ stop: inout Bool) -> Void) -> (replacementItems: [ReplacementItem], selectedRanges: [NSRange]?) {
let replacementString = self.replacementString(from: replacementString)
var replacementItems: [ReplacementItem] = []
@ -324,7 +332,7 @@ final class TextFind {
items.append(ReplacementItem(string: replacedString, range: matchedRange))
block(.findProgress, &ioStop)
block(.findProgress, matchedRange, &ioStop)
stop = ioStop
}
@ -338,7 +346,7 @@ final class TextFind {
let replacedString = NSMutableString(string: (self.string as NSString).substring(with: scopeRange))
for item in items.reversed() {
var ioStop = false
block(.replacementProgress, &ioStop)
block(.replacementProgress, item.range, &ioStop)
if ioStop { break }
// -> Do not convert to Range<Index>. It can fail when the range is smaller than String.Character.

View File

@ -301,7 +301,7 @@ final class TextFinder: NSResponder, NSMenuItemValidation {
let replacementString = self.replacementString
// setup progress sheet
let progress = FindProgress()
let progress = FindProgress(scope: textFind.scopeRange)
let closesAutomatically = UserDefaults.standard[.findClosesIndicatorWhenDone]
let indicatorView = FindProgressView("Replace All", unit: .replacement, progress: progress)
let indicator = NSHostingController(rootView: indicatorView)
@ -311,7 +311,7 @@ final class TextFinder: NSResponder, NSMenuItemValidation {
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self else { return }
let (replacementItems, selectedRanges) = textFind.replaceAll(with: replacementString) { (flag, stop) in
let (replacementItems, selectedRanges) = textFind.replaceAll(with: replacementString) { (flag, range, stop) in
guard !progress.isCancelled else {
stop = true
return
@ -321,6 +321,7 @@ final class TextFinder: NSResponder, NSMenuItemValidation {
case .findProgress:
break
case .replacementProgress:
progress.completedUnit = range.upperBound
progress.count += 1
}
}
@ -551,7 +552,7 @@ final class TextFinder: NSResponder, NSMenuItemValidation {
let lineCounter = LineCounter(textFind.string as NSString)
// setup progress sheet
let progress = FindProgress()
let progress = FindProgress(scope: textFind.scopeRange)
let closesAutomatically = UserDefaults.standard[.findClosesIndicatorWhenDone]
let indicatorView = FindProgressView(actionName, unit: .find, progress: progress)
let indicator = NSHostingController(rootView: indicatorView)
@ -599,6 +600,7 @@ final class TextFinder: NSResponder, NSMenuItemValidation {
results.append(TextFindResult(range: matchedRange, lineNumber: lineNumber, attributedLineString: attrLineString, inlineRange: inlineRange))
}
progress.completedUnit = matches[0].upperBound
progress.count += 1
}