From 7d5ba19d51db256e3694edb5366b0bb6e1ae3cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 13 Oct 2025 08:17:29 +0800 Subject: [PATCH] Improve LogView --- .../Views/Log}/ANSIColors.swift | 25 +++++++++++++- ApplicationLibrary/Views/Log/LogView.swift | 34 ++++++++++--------- 2 files changed, 42 insertions(+), 17 deletions(-) rename {Library/Shared => ApplicationLibrary/Views/Log}/ANSIColors.swift (87%) diff --git a/Library/Shared/ANSIColors.swift b/ApplicationLibrary/Views/Log/ANSIColors.swift similarity index 87% rename from Library/Shared/ANSIColors.swift rename to ApplicationLibrary/Views/Log/ANSIColors.swift index 5fc1f32..35406ad 100644 --- a/Library/Shared/ANSIColors.swift +++ b/ApplicationLibrary/Views/Log/ANSIColors.swift @@ -3,6 +3,11 @@ import SwiftUI public enum ANSIColors { private static let ansiRegex = try! NSRegularExpression(pattern: "\u{001B}\\[[;\\d]*m") + private static let cache: NSCache = { + let cache = NSCache() + cache.countLimit = 3000 + return cache + }() private static let logRed = Color(red: 1.0, green: 0.13, blue: 0.35) private static let logGreen = Color(red: 0.18, green: 0.8, blue: 0.44) @@ -11,13 +16,22 @@ public enum ANSIColors { private static let logPurple = Color(red: 0.61, green: 0.35, blue: 0.71) private static let logBlueLight = Color(red: 0.36, green: 0.68, blue: 0.89) private static let logWhite = Color(red: 0.93, green: 0.94, blue: 0.95) + + public static func clearCache() { + cache.removeAllObjects() + } public static func parseAnsiString(_ text: String) -> AttributedString { let nsString = text as NSString + if let cached = cache.object(forKey: nsString) { + return cached.value + } let matches = ansiRegex.matches(in: text, range: NSRange(location: 0, length: nsString.length)) if matches.isEmpty { - return AttributedString(text) + let plain = AttributedString(text) + cache.setObject(CachedAttributedString(plain), forKey: nsString) + return plain } var cleanText = text @@ -60,6 +74,7 @@ public enum ANSIColors { attributedString[startIndex...].mergeAttributes(existingStyle) } + cache.setObject(CachedAttributedString(attributedString), forKey: nsString) return attributedString } @@ -125,3 +140,11 @@ public enum ANSIColors { return hasAttribute ? container : nil } } + +private final class CachedAttributedString: NSObject { + let value: AttributedString + + init(_ value: AttributedString) { + self.value = value + } +} diff --git a/ApplicationLibrary/Views/Log/LogView.swift b/ApplicationLibrary/Views/Log/LogView.swift index 75255ff..f873299 100644 --- a/ApplicationLibrary/Views/Log/LogView.swift +++ b/ApplicationLibrary/Views/Log/LogView.swift @@ -1,5 +1,5 @@ -import Library import SwiftUI +import Library public struct LogView: View { @Environment(\.selection) private var selection @@ -27,14 +27,13 @@ public struct LogView: View { "sing-box started (1.666s)", ] ScrollView { - VStack(alignment: .leading, spacing: 0) { - ForEach(Array(logList.enumerated()), id: \.offset) { it in - Text(ANSIColors.parseAnsiString(it.element)) + LazyVStack(alignment: .leading, spacing: 8) { + ForEach(logList.indices, id: \.self) { index in + Text(ANSIColors.parseAnsiString(logList[index])) .font(logFont) #if os(tvOS) .focusable() #endif - Spacer(minLength: 8) } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) @@ -57,20 +56,13 @@ public struct LogView: View { } else { ScrollViewReader { reader in ScrollView { - LazyVGrid(columns: [GridItem(.flexible())], alignment: .leading, spacing: 0) { - ForEach(Array(commandClient.logList.enumerated()), id: \.offset) { it in - Text(ANSIColors.parseAnsiString(it.element)) + LazyVStack(alignment: .leading, spacing: 8) { + ForEach(commandClient.logList.indices, id: \.self) { index in + Text(ANSIColors.parseAnsiString(commandClient.logList[index])) .font(logFont) #if os(tvOS) .focusable() #endif - Spacer(minLength: 8) - } - - .onChangeCompat(of: commandClient.logList.count) { newCount in - withAnimation { - reader.scrollTo(newCount - 1) - } } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) @@ -81,7 +73,17 @@ public struct LogView: View { .focusSection() #endif .onAppear { - reader.scrollTo(commandClient.logList.count - 1) + if let lastIndex = commandClient.logList.indices.last { + reader.scrollTo(lastIndex, anchor: .bottom) + } + } + .onChangeCompat(of: commandClient.logList.count) { _ in + guard let lastIndex = commandClient.logList.indices.last else { + return + } + withAnimation { + reader.scrollTo(lastIndex, anchor: .bottom) + } } } }