Show diffs without word wrapping

This commit is contained in:
Georg Bauer
2026-07-31 17:14:34 +02:00
parent e39695fc60
commit 5b29e18942
2 changed files with 74 additions and 84 deletions

View File

@@ -738,6 +738,60 @@ final class RepositoryHistoryViewController: RefreshingTableViewController {
}
}
private final class CodeScrollView: UIScrollView {
private let textView = UITextView(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
alwaysBounceVertical = true
alwaysBounceHorizontal = true
isDirectionalLockEnabled = true
showsHorizontalScrollIndicator = true
contentInsetAdjustmentBehavior = .never
textView.textContainer.lineBreakMode = .byClipping
textView.textContainer.widthTracksTextView = false
textView.isEditable = false
textView.isScrollEnabled = false
textView.isSelectable = true
addSubview(textView)
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
func display(_ text: NSAttributedString) {
let text = NSMutableAttributedString(attributedString: text)
let paragraph = NSMutableParagraphStyle()
paragraph.lineBreakMode = .byClipping
text.addAttribute(.paragraphStyle, value: paragraph, range: NSRange(location: 0, length: text.length))
textView.textStorage.setAttributedString(text)
setNeedsLayout()
}
override func layoutSubviews() {
super.layoutSubviews()
textView.textContainer.size = CGSize(
width: CGFloat.greatestFiniteMagnitude,
height: CGFloat.greatestFiniteMagnitude
)
textView.layoutManager.ensureLayout(for: textView.textContainer)
let textSize = textView.layoutManager.usedRect(for: textView.textContainer).size
let size = CGSize(
width: max(
bounds.width,
ceil(textSize.width + textView.textContainerInset.left + textView.textContainerInset.right)
),
height: max(
bounds.height,
ceil(textSize.height + textView.textContainerInset.top + textView.textContainerInset.bottom)
)
)
textView.frame = CGRect(origin: .zero, size: size)
contentSize = size
}
}
@MainActor
final class RepositoryFileViewController: UIViewController, QLPreviewControllerDataSource {
private let context: AppContext
@@ -753,8 +807,6 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
private var historyController: RepositoryHistoryViewController?
private var displayedController: UIViewController?
private var displayedView: UIView?
private weak var sourceScrollView: UIScrollView?
private weak var sourceTextView: UITextView?
init(context: AppContext, owner: String, repository: String, path: String, name: String) {
self.context = context
@@ -791,11 +843,6 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateSourceLayout()
}
deinit {
loadingTask?.cancel()
if let previewURL { try? FileManager.default.removeItem(at: previewURL) }
@@ -889,41 +936,16 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
.font: UIFont.monospacedSystemFont(ofSize: 13, weight: .regular),
.foregroundColor: UIColor.label,
])
let text = NSMutableAttributedString(attributedString: highlighted)
let paragraph = NSMutableParagraphStyle()
paragraph.lineBreakMode = .byClipping
text.addAttribute(.paragraphStyle, value: paragraph, range: NSRange(location: 0, length: text.length))
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.alwaysBounceHorizontal = true
scrollView.isDirectionalLockEnabled = true
scrollView.showsHorizontalScrollIndicator = true
scrollView.contentInsetAdjustmentBehavior = .never
let textView = UITextView(frame: .zero)
textView.textContainer.size = CGSize(
width: CGFloat.greatestFiniteMagnitude,
height: CGFloat.greatestFiniteMagnitude
)
textView.textContainer.lineBreakMode = .byClipping
textView.textContainer.widthTracksTextView = false
textView.textStorage.setAttributedString(text)
textView.isEditable = false
textView.isScrollEnabled = false
textView.isSelectable = true
sourceScrollView = scrollView
sourceTextView = textView
scrollView.addSubview(textView)
displayedView = scrollView
view.addSubview(scrollView)
let codeView = CodeScrollView()
codeView.display(highlighted)
displayedView = codeView
view.addSubview(codeView)
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
codeView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
codeView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
codeView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
codeView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
updateSourceLayout()
}
private func removeDisplayedView() {
@@ -932,31 +954,6 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
displayedController?.removeFromParent()
displayedController = nil
displayedView = nil
sourceScrollView = nil
sourceTextView = nil
}
private func updateSourceLayout() {
guard let scrollView = sourceScrollView, let textView = sourceTextView else { return }
textView.textContainer.widthTracksTextView = false
textView.textContainer.size = CGSize(
width: CGFloat.greatestFiniteMagnitude,
height: CGFloat.greatestFiniteMagnitude
)
textView.layoutManager.ensureLayout(for: textView.textContainer)
let textSize = textView.layoutManager.usedRect(for: textView.textContainer).size
let contentSize = CGSize(
width: max(
scrollView.bounds.width,
ceil(textSize.width + textView.textContainerInset.left + textView.textContainerInset.right)
),
height: max(
scrollView.bounds.height,
ceil(textSize.height + textView.textContainerInset.top + textView.textContainerInset.bottom)
)
)
textView.frame = CGRect(origin: .zero, size: contentSize)
scrollView.contentSize = contentSize
}
private func showQuickLook(_ data: Data, name: String) throws {
@@ -1002,7 +999,7 @@ enum DiffSource {
final class DiffViewController: UIViewController {
private let context: AppContext
private let source: DiffSource
private let textView = UITextView()
private let codeView = CodeScrollView()
private let spinner = UIActivityIndicatorView(style: .medium)
private var loadingTask: Task<Void, Never>?
@@ -1018,22 +1015,14 @@ final class DiffViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isEditable = false
textView.isSelectable = true
textView.alwaysBounceVertical = true
textView.alwaysBounceHorizontal = true
textView.showsHorizontalScrollIndicator = true
textView.textContainer.widthTracksTextView = false
textView.textContainer.lineFragmentPadding = 8
textView.refreshControl = UIRefreshControl()
textView.refreshControl?.addTarget(self, action: #selector(reload), for: .valueChanged)
view.addSubview(textView)
codeView.refreshControl = UIRefreshControl()
codeView.refreshControl?.addTarget(self, action: #selector(reload), for: .valueChanged)
view.addSubview(codeView)
NSLayoutConstraint.activate([
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
textView.topAnchor.constraint(equalTo: view.topAnchor),
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
codeView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
codeView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
codeView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
codeView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
beginNavigationLoading(spinner)
reload()
@@ -1063,12 +1052,12 @@ final class DiffViewController: UIViewController {
)
}
title = page.title
textView.attributedText = diffText(page)
codeView.display(diffText(page))
} catch {
if !Task.isCancelled { show(error: error) }
}
endNavigationLoading(spinner)
textView.refreshControl?.endRefreshing()
codeView.refreshControl?.endRefreshing()
}
}