Skip to content

Commit

Permalink
add TextDiffViewController
Browse files Browse the repository at this point in the history
  • Loading branch information
ObuchiYuki committed Feb 23, 2022
1 parent 4d47c87 commit 68ee76b
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 5 deletions.
4 changes: 2 additions & 2 deletions DevToys.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"repositoryURL": "https://github.com/ObuchiYuki/DiffMatchPatch",
"state": {
"branch": null,
"revision": "7f79f4a473e0e81d6864b425f101c78bccfd169b",
"version": "1.0.0"
"revision": "5db22e8f9ac588a130f62733105bbe5c5bdd7bc3",
"version": "1.0.3"
}
},
{
Expand Down
110 changes: 109 additions & 1 deletion DevToys/DevToys/Body/Text/TextDiffView+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,112 @@
// Created by yuki on 2022/02/23.
//

import Foundation
import DiffMatchPatch
import CoreUtil

final class TextDiffViewController: NSViewController {

@RestorableState("textdiff.operation") var operation: TextCheckOperation = .characters
@RestorableState("textdiff.input1") var input1 = ""
@RestorableState("textdiff.input2") var input2 = ""

@Observable var diffAttributedString = NSAttributedString()

private let cell = TextDiffView()

override func loadView() { self.view = cell }

override func viewDidLoad() {
self.$operation
.sink{[unowned self] in self.cell.checkOperationPicker.selectedItem = $0 }.store(in: &objectBag)
self.$input1
.sink{[unowned self] in self.cell.input1Section.string = $0 }.store(in: &objectBag)
self.$input2
.sink{[unowned self] in self.cell.input2Section.string = $0 }.store(in: &objectBag)
self.$diffAttributedString
.sink{[unowned self] in self.cell.outputSection.textView.textView.textStorage?.setAttributedString($0) }.store(in: &objectBag)

self.cell.input1Section.stringPublisher
.sink{[unowned self] in self.input1 = $0; updateDiff() }.store(in: &objectBag)
self.cell.input2Section.stringPublisher
.sink{[unowned self] in self.input2 = $0; updateDiff() }.store(in: &objectBag)
self.cell.checkOperationPicker.itemPublisher
.sink{[unowned self] in self.operation = $0; updateDiff() }.store(in: &objectBag)

self.updateDiff()
}

private func updateDiff() {
let diffs = TextDifferenceChecker.compare(input1, input2, operation: self.operation)
self.diffAttributedString = buildAttributedString(from: diffs)
}

private func buildAttributedString(from diffs: [Difference]) -> NSAttributedString {
let attributedString = NSMutableAttributedString()
let defaultAttributes = [
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.font : NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
]

for diff in diffs {
switch diff.operation {
case .equal:
attributedString.append(NSAttributedString(string: diff.text, attributes: defaultAttributes))
case .insert:
attributedString.append(NSAttributedString(string: diff.text, attributes: defaultAttributes.merging([
NSAttributedString.Key.backgroundColor : NSColor.systemGreen.withAlphaComponent(0.7)
], uniquingKeysWith: { a, _ in a }) ))
case .delete:
attributedString.append(NSAttributedString(string: diff.text, attributes: defaultAttributes.merging([
NSAttributedString.Key.backgroundColor : NSColor.systemRed.withAlphaComponent(0.7)
], uniquingKeysWith: { a, _ in a }) ))
}
}

return attributedString
}
}

extension TextCheckOperation: TextItem {
public static let allCases: [TextCheckOperation] = [.characters, .words, .lines]

var title: String {
switch self {
case .characters: return "Characters"
case .words: return "Words"
case .lines: return "Lines"
}
}
}

final private class TextDiffView: Page {
let checkOperationPicker = EnumPopupButton<TextCheckOperation>()

let input1Section = CodeViewSection(title: "Input 1".localized(), options: .defaultInput, language: .plaintext)
let input2Section = CodeViewSection(title: "Input 2".localized(), options: .defaultInput, language: .plaintext)
let outputSection = TextViewSection(title: "Output".localized(), options: .defaultOutput)

override func layout() {
super.layout()

}

override func onAwake() {
self.addSection(Section(title: "Configuration".localized(), items: [
Area(icon: R.Image.format, title: "Diff Style", control: checkOperationPicker)
]))

self.addSection2(input1Section, input2Section)
self.input1Section.snp.makeConstraints{ make in
make.height.equalTo(320)
}

self.addSection(outputSection)
self.outputSection.textView.textView.font = .monospacedSystemFont(ofSize: 12, weight: .regular)
self.outputSection.snp.makeConstraints{ make in
make.height.equalTo(320)
}

}
}

4 changes: 2 additions & 2 deletions DevToys/DevToys/Component/TextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import CoreUtil

final class TextView: NSLoadView {
private class _TextView: NSTextView {
class _TextView: NSTextView {
let stringPublisher = PassthroughSubject<String, Never>()
var sendingValue = false
override var string: String {
Expand Down Expand Up @@ -38,7 +38,7 @@ final class TextView: NSLoadView {

private let scrollView = _TextView.scrollableTextView()
private let backgroudLayer = ControlBackgroundLayer.animationDisabled()
private lazy var textView = scrollView.documentView as! _TextView
lazy var textView = scrollView.documentView as! _TextView

override func onAwake() {
self.wantsLayer = true
Expand Down
7 changes: 7 additions & 0 deletions DevToys/DevToys/Model/Tool+Default.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,18 @@ extension Tool {
sidebarTitle: "tool.regex.mintitle".localized(), toolDescription: "tool.regex.description".localized(),
viewController: RegexTesterViewController()
)
static let textDiff = Tool(
title: "Text Diff".localized(), identifier: "textdiff", category: .text, icon: R.Image.Tool.textInspector,
sidebarTitle: "Text Diff".localized(), toolDescription: "Compare two Text and display Diff".localized(),
viewController: TextDiffViewController()
)
static let hyphenationRemover = Tool(
title: "tool.hyphenremove.title".localized(), identifier: "hyphenremove", category: .text, icon: R.Image.Tool.textInspector,
sidebarTitle: "tool.hyphenremove.mintitle".localized(), toolDescription: "tool.hyphenremove.description".localized(),
viewController: HyphenationRemoverViewController()
)


// MARK: - Graphic -
static let imageOptimizer = Tool(
title: "tool.imageoptim.title".localized(), identifier: "imageoptim", category: .graphic, icon: R.Image.Tool.imageCompressor,
Expand Down Expand Up @@ -172,6 +178,7 @@ extension ToolManager {

toolManager.registerTool(.textInspector)
toolManager.registerTool(.regexTester)
toolManager.registerTool(.textDiff)
toolManager.registerTool(.hyphenationRemover)

toolManager.registerTool(.imageOptimizer)
Expand Down

0 comments on commit 68ee76b

Please sign in to comment.