Skip to content

Commit 122d568

Browse files
committed
Update
1 parent ab33926 commit 122d568

1 file changed

Lines changed: 74 additions & 48 deletions

File tree

app/src/main/java/com/omarea/krscript/ui/DialogLogFragment.kt

Lines changed: 74 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -224,16 +224,14 @@ class DialogLogFragment : DialogFragment() {
224224
private var hasError = false
225225
private var lineCount = 0
226226

227+
// logBuffer giờ chỉ chạy trên luồng gọi (background thread của Executor)
227228
private val logBuffer = SpannableStringBuilder()
228-
229-
// Vị trí bắt đầu của dòng hiện tại (dòng chưa kết thúc bằng \n) trong logBuffer.
230229
private var lineStart = 0
231-
// true nếu vừa gặp \r và đang chờ nội dung tiếp theo ghi đè lên dòng hiện tại
232-
// (bắt đầu từ lineStart) thay vì nối thêm vào cuối.
233230
private var pendingOverwrite = false
234231

235232
init {
236-
logView?.setText(logBuffer, TextView.BufferType.EDITABLE)
233+
// Không set trực tiếp editable ban đầu nữa, quản lý qua append tĩnh cho mượt
234+
logView?.text = ""
237235
}
238236

239237
private fun getColor(resId: Int): Int {
@@ -263,13 +261,11 @@ class DialogLogFragment : DialogFragment() {
263261
if (prompt.isNotEmpty()) {
264262
input.hint = prompt
265263
}
266-
input.post {
267-
input.isFocusable = true
268-
input.isFocusableInTouchMode = true
269-
input.requestFocus()
270-
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
271-
imm?.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT)
272-
}
264+
input.isFocusable = true
265+
input.isFocusableInTouchMode = true
266+
input.requestFocus()
267+
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
268+
imm?.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT)
273269
}
274270
}
275271
}
@@ -293,11 +289,15 @@ class DialogLogFragment : DialogFragment() {
293289

294290
private fun resetLogState() {
295291
AnsiColorParser.reset()
296-
lineCount = 0
297-
lineStart = 0
298-
pendingOverwrite = false
299-
logBuffer.clear()
300-
logViewRef.get()?.text = logBuffer
292+
synchronized(logBuffer) {
293+
lineCount = 0
294+
lineStart = 0
295+
pendingOverwrite = false
296+
logBuffer.clear()
297+
}
298+
logViewRef.get()?.post {
299+
logViewRef.get()?.text = ""
300+
}
301301
}
302302

303303
override fun onStart(forceStop: Runnable?) {
@@ -357,9 +357,6 @@ class DialogLogFragment : DialogFragment() {
357357
var parsedLog: CharSequence = AnsiColorParser.parse(text)
358358

359359
if (forcedColor != null && !text.contains("\u001B[")) {
360-
// Tô màu toàn bộ nội dung; các ký tự điều khiển \r/\n (nếu có, ở bất kỳ vị trí
361-
// nào trong chuỗi) sẽ được dispatchLogUpdate xử lý riêng, không bị vấn đề vì
362-
// ForegroundColorSpan không ảnh hưởng đến cách ngắt dòng.
363360
val spannable = SpannableStringBuilder(parsedLog)
364361
spannable.setSpan(
365362
ForegroundColorSpan(forcedColor),
@@ -374,50 +371,48 @@ class DialogLogFragment : DialogFragment() {
374371
}
375372

376373
/**
377-
* Ghi nội dung mới vào logBuffer, xử lý đúng ngữ nghĩa \r (ghi đè dòng hiện tại,
378-
* dùng cho progress %) và \n (xuống dòng thật), kể cả khi các ký tự này nằm
379-
* ở giữa chuỗi chứ không chỉ ở cuối.
374+
* XỬ LÝ CHUỖI TẠI BACKGROUND THREAD ĐỂ GIẢM TẢI UI THREAD
380375
*/
381376
private fun dispatchLogUpdate(formattedText: CharSequence) {
382377
val logView = logViewRef.get() ?: return
378+
379+
// Biến cục bộ để xác định xem dòng này có yêu cầu ghi đè hay không (\r)
380+
var isOverwriteAction = false
381+
val snapshotToDisplay: CharSequence
383382

384-
logView.post {
383+
synchronized(logBuffer) {
385384
var i = 0
386385
val len = formattedText.length
387386

388387
while (i < len) {
389-
// Tìm ký tự điều khiển (\r hoặc \n) gần nhất kể từ i
390388
var j = i
391389
while (j < len && formattedText[j] != '\r' && formattedText[j] != '\n') j++
392390

393-
// Phần nội dung thuần trước ký tự điều khiển (hoặc tới hết chuỗi)
394391
if (j > i) {
395392
val segment = formattedText.subSequence(i, j)
396393
if (pendingOverwrite) {
397394
if (lineStart in 0..logBuffer.length) {
398395
logBuffer.delete(lineStart, logBuffer.length)
399396
}
400397
pendingOverwrite = false
398+
isOverwriteAction = true
401399
}
402400
logBuffer.append(segment)
403401
}
404402

405403
if (j < len) {
406404
when (formattedText[j]) {
407405
'\r' -> {
408-
// \r\n (CRLF chuẩn) chỉ là xuống dòng bình thường, bỏ qua \r
409406
val isCRLF = j + 1 < len && formattedText[j + 1] == '\n'
410407
if (!isCRLF) {
411-
// Đánh dấu: nội dung tiếp theo sẽ ghi đè từ đầu dòng hiện tại
412408
pendingOverwrite = true
413409
}
414410
}
415411
'\n' -> {
416-
// Nếu đang chờ ghi đè mà gặp \n ngay (không có nội dung mới sau \r)
417-
// thì coi như dòng đó bị xoá trước khi xuống dòng thật
418412
if (pendingOverwrite && lineStart in 0..logBuffer.length) {
419413
logBuffer.delete(lineStart, logBuffer.length)
420414
pendingOverwrite = false
415+
isOverwriteAction = true
421416
}
422417
logBuffer.append('\n')
423418
lineCount++
@@ -429,27 +424,58 @@ class DialogLogFragment : DialogFragment() {
429424
i = j
430425
}
431426

432-
(logView.editableText ?: return@post).replace(
433-
0,
434-
logView.editableText.length,
435-
logBuffer
436-
)
437-
438-
// Thực hiện cuộn và giữ focus cho ô nhập liệu
439-
(logView.parent as? ScrollView)?.let { scrollView ->
440-
scrollView.post {
441-
// 1. Cuộn ScrollView xuống cuối cùng
442-
scrollView.fullScroll(ScrollView.FOCUS_DOWN)
443-
444-
// 2. Nếu ô nhập liệu đang hiện, ép hệ thống giữ con trỏ tại đây
445-
val inputRow = inputRowRef.get()
446-
val input = shellInputRef.get()
447-
if (inputRow != null && inputRow.visibility == View.VISIBLE && input != null) {
448-
input.post {
449-
input.requestFocus()
427+
// Cắt tỉa log tối đa 5000 dòng ngay tại luồng nền
428+
if (lineCount > 5000) {
429+
var deleteEndIndex = 0
430+
var linesToTrim = lineCount - 5000
431+
val currentCharSequence = logBuffer.toString()
432+
433+
for (k in currentCharSequence.indices) {
434+
if (currentCharSequence[k] == '\n') {
435+
linesToTrim--
436+
if (linesToTrim <= 0) {
437+
deleteEndIndex = k + 1
438+
break
450439
}
451440
}
452441
}
442+
if (deleteEndIndex > 0) {
443+
logBuffer.delete(0, deleteEndIndex)
444+
lineCount = 5000
445+
lineStart = (lineStart - deleteEndIndex).coerceAtLeast(0)
446+
isOverwriteAction = true // Cần reload toàn bộ vì cấu trúc buffer đã dịch chuyển
447+
}
448+
}
449+
450+
// Chụp lại trạng thái text hiện tại để ném lên UI hiển thị
451+
snapshotToDisplay = SpannableStringBuilder(logBuffer)
452+
}
453+
454+
// ĐẨY LÊN UI THREAD ĐỂ HIỂN THỊ
455+
logView.post {
456+
if (isOverwriteAction) {
457+
// Nếu có lệnh xóa dòng cũ đè dòng mới (\r) hoặc vừa bị cắt tỉa log > 5000 dòng, bắt buộc phải đặt lại text
458+
logView.text = snapshotToDisplay
459+
} else {
460+
// NẾU LÀ GHI TIẾP (APPEND): Đây là chìa khóa giúp log mượt!
461+
// Thay vì nạp lại toàn bộ 5000 dòng, chỉ truyền phần text mới tinh vào.
462+
val oldLen = logView.text.length
463+
if (snapshotToDisplay.length > oldLen) {
464+
val newChunk = snapshotToDisplay.subSequence(oldLen, snapshotToDisplay.length)
465+
logView.append(newChunk)
466+
}
467+
}
468+
469+
// Tối ưu cuộn ScrollView xuống cuối
470+
(logView.parent as? ScrollView)?.let { scrollView ->
471+
scrollView.fullScroll(ScrollView.FOCUS_DOWN)
472+
473+
// Giữ focus thông minh không cần lồng post{} nhiều tầng
474+
val inputRow = inputRowRef.get()
475+
val input = shellInputRef.get()
476+
if (inputRow != null && inputRow.visibility == View.VISIBLE && input != null) {
477+
if (!input.isFocused) input.requestFocus()
478+
}
453479
}
454480
}
455481
}

0 commit comments

Comments
 (0)