Skip to content

Commit 9947698

Browse files
committed
Update
1 parent 83ff0cd commit 9947698

3 files changed

Lines changed: 331 additions & 92 deletions

File tree

.github/module/termux/bin/axel

67.1 KB
Binary file not shown.

app/src/main/java/com/tool/tree/AnsiColorParser.kt

Lines changed: 204 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,66 +6,167 @@ import android.text.Spannable
66
import android.text.SpannableStringBuilder
77
import android.text.style.BackgroundColorSpan
88
import android.text.style.ForegroundColorSpan
9+
import android.text.style.StrikethroughSpan
910
import android.text.style.StyleSpan
11+
import android.text.style.URLSpan
1012
import android.text.style.UnderlineSpan
1113
import java.util.regex.Pattern
1214

1315
object AnsiColorParser {
14-
private val ANSI_PATTERN = Pattern.compile("\\u001B\\[([\\d;]*)m")
15-
16-
private const val DEFAULT_FOREGROUND = Color.LTGRAY
17-
private const val DEFAULT_BACKGROUND = Color.TRANSPARENT // Mặc định không có màu nền
1816

19-
// Cấu trúc lưu trữ trạng thái định dạng hiện tại của luồng log
17+
// CSI: ESC [ params intermediate finalByte (SGR dùng finalByte = 'm', ngoài ra còn
18+
// cursor move/clear line/hide cursor... với finalByte khác -> bị bỏ qua không render)
19+
private val CSI_PATTERN = Pattern.compile(
20+
"\\u001B\\[([0-9;:?]*)([ -/]*)([@-~])"
21+
)
22+
23+
// OSC: ESC ] body (BEL | ESC \) — dùng cho hyperlink (8;;URL) và các OSC khác (vd 0;title)
24+
private val OSC_PATTERN = Pattern.compile(
25+
"\\u001B\\]([^\\u0007\\u001B]*)(?:\\u0007|\\u001B\\\\)"
26+
)
27+
28+
// Escape đơn dạng ESC + 1 ký tự không phải '[' hoặc ']' (vd chọn charset ESC(B, ESC)0...)
29+
private val SIMPLE_ESC_PATTERN = Pattern.compile(
30+
"\\u001B[()#][A-Za-z0-9]|\\u001B[=>]"
31+
)
32+
33+
private const val DEFAULT_FOREGROUND = Color.LTGRAY
34+
private const val DEFAULT_BACKGROUND = Color.TRANSPARENT
35+
36+
// Trạng thái định dạng hiện tại của luồng log (giữ nguyên xuyên suốt các lần parse)
2037
private var currentFgColor = DEFAULT_FOREGROUND
2138
private var currentBgColor = DEFAULT_BACKGROUND
2239
private var isBold = false
40+
private var isItalic = false
2341
private var isUnderline = false
42+
private var isStrikethrough = false
43+
private var isReverse = false
44+
private var isDim = false
2445

25-
// Bảng 16 màu ANSI cơ bản (cho cả Foreground và Background)
2646
private val ANSI_16_COLORS = intArrayOf(
27-
Color.BLACK, // 0 - Black
28-
Color.RED, // 1 - Red
29-
Color.GREEN, // 2 - Green
30-
Color.rgb(200, 150, 0), // 3 - Yellow (Tối đi một chút để tránh chói mắt)
31-
Color.BLUE, // 4 - Blue
32-
Color.MAGENTA, // 5 - Magenta
33-
Color.CYAN, // 6 - Cyan
34-
Color.WHITE, // 7 - White
35-
// 8 - 15: Bright Colors (Màu sáng)
36-
Color.DKGRAY, // 8 - Bright Black (Dark Gray)
37-
Color.rgb(255, 85, 85), // 9 - Bright Red
38-
Color.rgb(85, 255, 85), // 10 - Bright Green
39-
Color.rgb(255, 255, 85), // 11 - Bright Yellow
40-
Color.rgb(85, 85, 255), // 12 - Bright Blue
41-
Color.rgb(255, 85, 255), // 13 - Bright Magenta
42-
Color.rgb(85, 255, 255), // 14 - Bright Cyan
43-
Color.WHITE // 15 - Bright White
47+
Color.BLACK,
48+
Color.RED,
49+
Color.GREEN,
50+
Color.rgb(200, 150, 0),
51+
Color.BLUE,
52+
Color.MAGENTA,
53+
Color.CYAN,
54+
Color.WHITE,
55+
Color.DKGRAY,
56+
Color.rgb(255, 85, 85),
57+
Color.rgb(85, 255, 85),
58+
Color.rgb(255, 255, 85),
59+
Color.rgb(85, 85, 255),
60+
Color.rgb(255, 85, 255),
61+
Color.rgb(85, 255, 255),
62+
Color.WHITE
4463
)
4564

4665
fun parse(text: String?): CharSequence {
4766
if (text == null) return ""
67+
4868
val builder = SpannableStringBuilder()
49-
val matcher = ANSI_PATTERN.matcher(text)
50-
var lastHeight = 0
69+
val plainBuffer = StringBuilder()
70+
71+
var pendingLinkUrl: String? = null
72+
var pendingLinkStart = -1
5173

52-
while (matcher.find()) {
53-
val start = matcher.start()
54-
val end = matcher.end()
74+
var i = 0
75+
val len = text.length
5576

56-
if (start > lastHeight) {
57-
val part = text.substring(lastHeight, start)
58-
appendStyledText(builder, part)
77+
fun flushPlainBuffer() {
78+
if (plainBuffer.isNotEmpty()) {
79+
appendStyledText(builder, plainBuffer.toString())
80+
plainBuffer.setLength(0)
5981
}
82+
}
83+
84+
while (i < len) {
85+
val c = text[i]
6086

61-
val paramsStr = matcher.group(1) ?: ""
62-
parseAnsiParameters(paramsStr)
63-
lastHeight = end
87+
if (c != '\u001B') {
88+
plainBuffer.append(c)
89+
i++
90+
continue
91+
}
92+
93+
// Gặp ESC -> flush phần text thường đã tích lũy trước khi xử lý escape
94+
flushPlainBuffer()
95+
96+
val nextChar = if (i + 1 < len) text[i + 1] else '\u0000'
97+
98+
when (nextChar) {
99+
'[' -> {
100+
val m = CSI_PATTERN.matcher(text)
101+
m.region(i, len)
102+
if (m.lookingAt()) {
103+
val params = m.group(1) ?: ""
104+
val finalByte = m.group(3)
105+
if (finalByte == "m") {
106+
parseAnsiParameters(params)
107+
}
108+
// Các CSI khác (cursor move, clear line, hide cursor...) -> bỏ qua, không render
109+
i = m.end()
110+
} else {
111+
i++ // escape hỏng/không đầy đủ, bỏ qua 1 ký tự để tránh vòng lặp vô hạn
112+
}
113+
}
114+
115+
']' -> {
116+
val m = OSC_PATTERN.matcher(text)
117+
m.region(i, len)
118+
if (m.lookingAt()) {
119+
val body = m.group(1) ?: ""
120+
if (body.startsWith("8;")) {
121+
// OSC 8 hyperlink: "8;params;URL"
122+
val url = body.substringAfterLast(';', "")
123+
if (url.isEmpty()) {
124+
// Thẻ đóng link
125+
if (pendingLinkUrl != null && pendingLinkStart in 0..builder.length) {
126+
builder.setSpan(
127+
URLSpan(pendingLinkUrl),
128+
pendingLinkStart,
129+
builder.length,
130+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
131+
)
132+
}
133+
pendingLinkUrl = null
134+
pendingLinkStart = -1
135+
} else {
136+
// Thẻ mở link
137+
pendingLinkUrl = url
138+
pendingLinkStart = builder.length
139+
}
140+
}
141+
// Các OSC khác (đặt title cửa sổ, v.v.) -> bỏ qua, không hiển thị ra text
142+
i = m.end()
143+
} else {
144+
i++
145+
}
146+
}
147+
148+
else -> {
149+
val m = SIMPLE_ESC_PATTERN.matcher(text)
150+
m.region(i, len)
151+
if (m.lookingAt()) {
152+
i = m.end() // bỏ qua, không có nội dung hiển thị
153+
} else {
154+
i++ // ESC đơn lẻ không rõ dạng -> bỏ qua chính nó
155+
}
156+
}
157+
}
64158
}
65159

66-
if (lastHeight < text.length) {
67-
val part = text.substring(lastHeight)
68-
appendStyledText(builder, part)
160+
flushPlainBuffer()
161+
162+
// Nếu chuỗi kết thúc mà link vẫn chưa đóng (log bị cắt giữa chừng), vẫn áp span cho phần đã có
163+
if (pendingLinkUrl != null && pendingLinkStart in 0 until builder.length) {
164+
builder.setSpan(
165+
URLSpan(pendingLinkUrl),
166+
pendingLinkStart,
167+
builder.length,
168+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
169+
)
69170
}
70171

71172
return builder
@@ -81,47 +182,78 @@ object AnsiColorParser {
81182

82183
if (spanStart == spanEnd) return
83184

84-
// 1. Áp dụng màu chữ (Foreground)
185+
var fg = currentFgColor
186+
var bg = currentBgColor
187+
188+
// Reverse video: hoán đổi fg/bg khi hiển thị
189+
if (isReverse) {
190+
val realBg = if (bg == Color.TRANSPARENT) Color.BLACK else bg
191+
val tmp = fg
192+
fg = realBg
193+
bg = tmp
194+
}
195+
196+
// Dim/faint: giảm độ chói của màu chữ
197+
if (isDim) {
198+
fg = Color.argb(
199+
180,
200+
Color.red(fg), Color.green(fg), Color.blue(fg)
201+
)
202+
}
203+
85204
builder.setSpan(
86-
ForegroundColorSpan(currentFgColor),
87-
spanStart,
88-
spanEnd,
205+
ForegroundColorSpan(fg),
206+
spanStart, spanEnd,
89207
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
90208
)
91209

92-
// 2. Áp dụng màu nền (Background) nếu có thiết lập màu khác trong suốt
93-
if (currentBgColor != Color.TRANSPARENT) {
210+
if (bg != Color.TRANSPARENT) {
94211
builder.setSpan(
95-
BackgroundColorSpan(currentBgColor),
96-
spanStart,
97-
spanEnd,
212+
BackgroundColorSpan(bg),
213+
spanStart, spanEnd,
98214
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
99215
)
100216
}
101217

102-
// 3. Áp dụng kiểu chữ in đậm (Bold)
103-
if (isBold) {
218+
if (isBold && isItalic) {
219+
builder.setSpan(
220+
StyleSpan(Typeface.BOLD_ITALIC),
221+
spanStart, spanEnd,
222+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
223+
)
224+
} else if (isBold) {
104225
builder.setSpan(
105226
StyleSpan(Typeface.BOLD),
106-
spanStart,
107-
spanEnd,
227+
spanStart, spanEnd,
228+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
229+
)
230+
} else if (isItalic) {
231+
builder.setSpan(
232+
StyleSpan(Typeface.ITALIC),
233+
spanStart, spanEnd,
108234
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
109235
)
110236
}
111237

112-
// 4. Áp dụng kiểu chữ gạch chân (Underline)
113238
if (isUnderline) {
114239
builder.setSpan(
115240
UnderlineSpan(),
116-
spanStart,
117-
spanEnd,
241+
spanStart, spanEnd,
242+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
243+
)
244+
}
245+
246+
if (isStrikethrough) {
247+
builder.setSpan(
248+
StrikethroughSpan(),
249+
spanStart, spanEnd,
118250
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
119251
)
120252
}
121253
}
122254

123255
/**
124-
* Parse chuỗi tham số ANSI để cập nhật trạng thái định dạng (in đậm, màu chữ, màu nền,...)
256+
* Parse chuỗi tham số SGR để cập nhật trạng thái định dạng
125257
*/
126258
private fun parseAnsiParameters(paramsStr: String) {
127259
if (paramsStr.isEmpty() || paramsStr == "0") {
@@ -135,31 +267,32 @@ object AnsiColorParser {
135267
while (i < tokens.size) {
136268
val code = tokens[i].toIntOrNull() ?: 0
137269
when {
138-
// Reset định dạng về mặc định
139270
code == 0 -> resetToDefault()
140271

141-
// Kiểu chữ (Style)
142272
code == 1 -> isBold = true
273+
code == 2 -> isDim = true
274+
code == 3 -> isItalic = true
143275
code == 4 -> isUnderline = true
144-
code == 21 -> isBold = false // Tắt in đậm (Double underline hoặc bold off tùy terminal)
145-
code == 22 -> isBold = false // Tắt in đậm thông thường
146-
code == 24 -> isUnderline = false // Tắt gạch chân
276+
code == 7 -> isReverse = true
277+
code == 9 -> isStrikethrough = true
147278

148-
// Màu chữ mặc định (Default Foreground)
149-
code == 39 -> currentFgColor = DEFAULT_FOREGROUND
279+
code == 21 -> isBold = false // double underline / bold-off tùy terminal
280+
code == 22 -> { isBold = false; isDim = false }
281+
code == 23 -> isItalic = false
282+
code == 24 -> isUnderline = false
283+
code == 27 -> isReverse = false
284+
code == 29 -> isStrikethrough = false
150285

151-
// Màu nền mặc định (Default Background)
286+
code == 39 -> currentFgColor = DEFAULT_FOREGROUND
152287
code == 49 -> currentBgColor = DEFAULT_BACKGROUND
153288

154-
// Màu chữ cơ bản (30 - 37) và màu chữ sáng (90 - 97)
155289
code in 30..37 -> currentFgColor = ANSI_16_COLORS[code - 30]
156290
code in 90..97 -> currentFgColor = ANSI_16_COLORS[code - 90 + 8]
157291

158-
// Màu nền cơ bản (40 - 47) và màu nền sáng (100 - 107)
159292
code in 40..47 -> currentBgColor = ANSI_16_COLORS[code - 40]
160293
code in 100..107 -> currentBgColor = ANSI_16_COLORS[code - 100 + 8]
161294

162-
// Chế độ màu chữ mở rộng (38;5;ID hoặc 38;2;R;G;B)
295+
// Màu chữ mở rộng: 38;5;ID hoặc 38;2;R;G;B
163296
code == 38 -> {
164297
if (i + 1 < tokens.size) {
165298
val mode = tokens[i + 1].toIntOrNull() ?: 0
@@ -177,7 +310,7 @@ object AnsiColorParser {
177310
}
178311
}
179312

180-
// Chế độ màu nền mở rộng (48;5;ID hoặc 48;2;R;G;B)
313+
// Màu nền mở rộng: 48;5;ID hoặc 48;2;R;G;B
181314
code == 48 -> {
182315
if (i + 1 < tokens.size) {
183316
val mode = tokens[i + 1].toIntOrNull() ?: 0
@@ -221,10 +354,14 @@ object AnsiColorParser {
221354
currentFgColor = DEFAULT_FOREGROUND
222355
currentBgColor = DEFAULT_BACKGROUND
223356
isBold = false
357+
isItalic = false
224358
isUnderline = false
359+
isStrikethrough = false
360+
isReverse = false
361+
isDim = false
225362
}
226363

227364
fun reset() {
228365
resetToDefault()
229366
}
230-
}
367+
}

0 commit comments

Comments
 (0)