Improve performance for Logs
This commit is contained in:
@@ -206,9 +206,25 @@ class LogCoordinator {
|
||||
context.coordinator.cachedAttributedString = attributedString
|
||||
textView.attributedText = attributedString
|
||||
case let .incremental(from: startIndex):
|
||||
let attributedString = buildAttributedString(logs: logs, monoFont: Self.monoFont, defaultColor: Self.defaultColor, searchText: searchText, baseAttributedString: context.coordinator.cachedAttributedString, startIndex: startIndex)
|
||||
context.coordinator.cachedAttributedString = attributedString
|
||||
textView.attributedText = attributedString
|
||||
// Build only the new logs, not the entire attributed string
|
||||
let newLogsAttributedString = buildAttributedString(logs: logs, monoFont: Self.monoFont, defaultColor: Self.defaultColor, searchText: searchText, baseAttributedString: nil, startIndex: startIndex)
|
||||
|
||||
// Append to existing text storage instead of replacing
|
||||
let textStorage = textView.textStorage
|
||||
|
||||
// Add newline separator before appending if there's existing content
|
||||
if textStorage.length > 0 {
|
||||
let newline = NSAttributedString(string: "\n", attributes: [
|
||||
.foregroundColor: Self.defaultColor,
|
||||
.font: Self.monoFont,
|
||||
])
|
||||
textStorage.append(newline)
|
||||
}
|
||||
|
||||
textStorage.append(newLogsAttributedString)
|
||||
|
||||
// Update cache to full content
|
||||
context.coordinator.cachedAttributedString = NSAttributedString(attributedString: textStorage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +270,7 @@ class LogCoordinator {
|
||||
|
||||
func updateNSView(_ scrollView: NSScrollView, context: Context) {
|
||||
guard let textView = scrollView.documentView as? NSTextView else { return }
|
||||
guard let textStorage = textView.textStorage else { return }
|
||||
|
||||
let lastCount = context.coordinator.lastLogsCount
|
||||
let updateStrategy = context.coordinator.shouldUpdate(logs: logs, searchText: searchText)
|
||||
@@ -264,11 +281,25 @@ class LogCoordinator {
|
||||
case .fullRebuild:
|
||||
let attributedText = buildAttributedString(logs: logs, monoFont: Self.monoFont, defaultColor: Self.defaultColor, searchText: searchText)
|
||||
context.coordinator.cachedAttributedString = attributedText
|
||||
textView.textStorage?.setAttributedString(attributedText)
|
||||
textStorage.setAttributedString(attributedText)
|
||||
case let .incremental(from: startIndex):
|
||||
let attributedText = buildAttributedString(logs: logs, monoFont: Self.monoFont, defaultColor: Self.defaultColor, searchText: searchText, baseAttributedString: context.coordinator.cachedAttributedString, startIndex: startIndex)
|
||||
context.coordinator.cachedAttributedString = attributedText
|
||||
textView.textStorage?.setAttributedString(attributedText)
|
||||
// Build only the new logs, not the entire attributed string
|
||||
let newLogsAttributedString = buildAttributedString(logs: logs, monoFont: Self.monoFont, defaultColor: Self.defaultColor, searchText: searchText, baseAttributedString: nil, startIndex: startIndex)
|
||||
|
||||
// Add newline separator before appending if there's existing content
|
||||
if textStorage.length > 0 {
|
||||
let newline = NSAttributedString(string: "\n", attributes: [
|
||||
.foregroundColor: Self.defaultColor,
|
||||
.font: Self.monoFont,
|
||||
])
|
||||
textStorage.append(newline)
|
||||
}
|
||||
|
||||
// Append to existing text storage instead of replacing
|
||||
textStorage.append(newLogsAttributedString)
|
||||
|
||||
// Update cache to full content
|
||||
context.coordinator.cachedAttributedString = NSAttributedString(attributedString: textStorage)
|
||||
}
|
||||
|
||||
let shouldScroll = shouldAutoScroll && logs.count != lastCount
|
||||
|
||||
@@ -34,7 +34,7 @@ private struct LogViewContent: View {
|
||||
previewContent
|
||||
} else if viewModel.isEmpty {
|
||||
emptyContent
|
||||
} else if viewModel.filteredLogs.isEmpty {
|
||||
} else if viewModel.visibleLogs.isEmpty {
|
||||
emptyContent
|
||||
} else {
|
||||
logScrollView
|
||||
@@ -113,7 +113,7 @@ private struct LogViewContent: View {
|
||||
ScrollViewReader { reader in
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 8) {
|
||||
ForEach(viewModel.filteredLogs) { logEntry in
|
||||
ForEach(viewModel.visibleLogs) { logEntry in
|
||||
Text(highlightedText(for: logEntry.message))
|
||||
.font(logFont)
|
||||
.focusable()
|
||||
@@ -127,7 +127,7 @@ private struct LogViewContent: View {
|
||||
.onAppear {
|
||||
scrollToLastEntry(reader)
|
||||
}
|
||||
.onChangeCompat(of: viewModel.filteredLogs.count) { _ in
|
||||
.onChangeCompat(of: viewModel.visibleLogs.count) { _ in
|
||||
if !viewModel.isPaused {
|
||||
scrollToLastEntry(reader)
|
||||
}
|
||||
@@ -135,7 +135,7 @@ private struct LogViewContent: View {
|
||||
}
|
||||
#else
|
||||
LogTextView(
|
||||
logs: viewModel.filteredLogs,
|
||||
logs: viewModel.visibleLogs,
|
||||
font: logFont,
|
||||
shouldAutoScroll: !viewModel.isPaused,
|
||||
searchText: viewModel.searchText
|
||||
@@ -166,7 +166,7 @@ private struct LogViewContent: View {
|
||||
|
||||
#if os(tvOS)
|
||||
private func scrollToLastEntry(_ reader: ScrollViewProxy) {
|
||||
guard let lastEntry = viewModel.filteredLogs.last else { return }
|
||||
guard let lastEntry = viewModel.visibleLogs.last else { return }
|
||||
withAnimation {
|
||||
reader.scrollTo(lastEntry.id, anchor: .bottom)
|
||||
}
|
||||
|
||||
@@ -25,9 +25,21 @@ public class LogViewModel: ObservableObject {
|
||||
private var lastEffectiveLevel: Int?
|
||||
private var lastSearchText = ""
|
||||
|
||||
// Performance optimization: limit visible logs to prevent UI lag
|
||||
private static let maxVisibleLogs = 1000
|
||||
|
||||
public var isEmpty: Bool { commandClient.logList.isEmpty }
|
||||
public var isConnected: Bool { commandClient.isConnected }
|
||||
|
||||
// Only show last N logs for performance, but keep all in filteredLogs for export
|
||||
public var visibleLogs: [LogEntry] {
|
||||
if filteredLogs.count <= Self.maxVisibleLogs {
|
||||
return filteredLogs
|
||||
} else {
|
||||
return Array(filteredLogs.suffix(Self.maxVisibleLogs))
|
||||
}
|
||||
}
|
||||
|
||||
public init(commandClient: CommandClient) {
|
||||
self.commandClient = commandClient
|
||||
|
||||
|
||||
Reference in New Issue
Block a user