Add expandable terminal reading summaries
Gitea Smoke / smoke (push) Successful in 0s
Gitea Android APK / build (push) Failing after 8m54s

This commit is contained in:
Codex
2026-07-12 02:14:25 +00:00
parent daf2990b15
commit 0119c7cb0e
2 changed files with 114 additions and 27 deletions
@@ -23,6 +23,7 @@ import android.os.Looper;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.InputType; import android.text.InputType;
import android.text.method.LinkMovementMethod;
import android.view.Gravity; import android.view.Gravity;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
import android.view.KeyEvent; import android.view.KeyEvent;
@@ -146,6 +147,7 @@ public final class MainActivity extends Activity {
private int lastActiveSessionCount = -1; private int lastActiveSessionCount = -1;
private TerminalScreenBuffer terminalScreen = new TerminalScreenBuffer(DEFAULT_TERMINAL_COLS, DEFAULT_TERMINAL_ROWS); private TerminalScreenBuffer terminalScreen = new TerminalScreenBuffer(DEFAULT_TERMINAL_COLS, DEFAULT_TERMINAL_ROWS);
private final StringBuilder queuedTerminalInput = new StringBuilder(); private final StringBuilder queuedTerminalInput = new StringBuilder();
private final Set<String> terminalExpandedBlocks = new HashSet<>();
private boolean terminalConnected; private boolean terminalConnected;
private boolean terminalConnecting; private boolean terminalConnecting;
private boolean terminalRenderPending; private boolean terminalRenderPending;
@@ -1603,6 +1605,7 @@ public final class MainActivity extends Activity {
terminalSelectionEnabled = false; terminalSelectionEnabled = false;
terminalFollowOutput = true; terminalFollowOutput = true;
terminalKeyPage = 0; terminalKeyPage = 0;
terminalExpandedBlocks.clear();
terminalImagePath = ""; terminalImagePath = "";
terminalImagePreviewBar = null; terminalImagePreviewBar = null;
terminalImagePreview = null; terminalImagePreview = null;
@@ -1739,6 +1742,7 @@ public final class MainActivity extends Activity {
terminalReadingMode = reading; terminalReadingMode = reading;
if (terminalText != null) { if (terminalText != null) {
terminalText.setHorizontallyScrolling(!reading); terminalText.setHorizontallyScrolling(!reading);
terminalText.setMovementMethod(reading ? LinkMovementMethod.getInstance() : null);
} }
styleTerminalReadingButton(); styleTerminalReadingButton();
renderTerminalNow(); renderTerminalNow();
@@ -3293,9 +3297,19 @@ public final class MainActivity extends Activity {
return; return;
} }
lastTerminalRenderMs = System.currentTimeMillis(); lastTerminalRenderMs = System.currentTimeMillis();
terminalText.setText(terminalReadingMode if (terminalReadingMode) {
? terminalScreen.renderFocused() terminalText.setMovementMethod(LinkMovementMethod.getInstance());
: terminalScreen.render()); terminalText.setHighlightColor(Color.TRANSPARENT);
terminalText.setText(terminalScreen.renderFocused(terminalExpandedBlocks, key -> {
if (!terminalExpandedBlocks.add(key)) {
terminalExpandedBlocks.remove(key);
}
renderTerminalNow();
}));
} else {
terminalText.setMovementMethod(null);
terminalText.setText(terminalScreen.render());
}
if (terminalFollowOutput) { if (terminalFollowOutput) {
terminalScroll.post(() -> terminalScroll.fullScroll(View.FOCUS_DOWN)); terminalScroll.post(() -> terminalScroll.fullScroll(View.FOCUS_DOWN));
} }
@@ -4,12 +4,17 @@ import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.BackgroundColorSpan; import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.view.View;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set;
final class TerminalScreenBuffer { final class TerminalScreenBuffer {
private static final int DEFAULT_FG = 0xffe6ebf2; private static final int DEFAULT_FG = 0xffe6ebf2;
@@ -30,6 +35,10 @@ final class TerminalScreenBuffer {
private boolean bold; private boolean bold;
private boolean dim; private boolean dim;
interface FocusToggle {
void toggle(String key);
}
TerminalScreenBuffer(int cols, int rows) { TerminalScreenBuffer(int cols, int rows) {
resize(cols, rows); resize(cols, rows);
} }
@@ -121,42 +130,106 @@ final class TerminalScreenBuffer {
return output; return output;
} }
CharSequence renderFocused() { CharSequence renderFocused(Set<String> expandedBlocks, FocusToggle toggle) {
List<String> visible = new ArrayList<>(); SpannableStringBuilder output = new SpannableStringBuilder();
int hiddenRows = 0; List<String> hiddenRows = new ArrayList<>();
int hiddenStart = -1;
for (int row = 0; row < rows; row++) { for (int row = 0; row < rows; row++) {
String text = rowText(row).trim(); String text = rowText(row).trim();
if (containsHan(text)) { if (containsHan(text)) {
if (hiddenRows > 0) { if (!hiddenRows.isEmpty()) {
visible.add("... 已折叠 " + hiddenRows + " 行终端内容 ..."); appendFocusBlock(output, hiddenRows, hiddenStart, row - 1, expandedBlocks, toggle);
hiddenRows = 0; hiddenRows.clear();
hiddenStart = -1;
} }
visible.add(text); appendLine(output, text);
} else if (!text.isEmpty()) { } else if (!text.isEmpty()) {
hiddenRows++; if (hiddenStart < 0) {
} hiddenStart = row;
}
if (hiddenRows > 0) {
visible.add("... 已折叠 " + hiddenRows + " 行终端内容 ...");
}
if (visible.isEmpty()) {
for (int row = Math.max(0, rows - 4); row < rows; row++) {
String text = rowText(row).trim();
if (!text.isEmpty()) {
visible.add(text);
} }
hiddenRows.add(text);
} }
} }
StringBuilder output = new StringBuilder(); if (!hiddenRows.isEmpty()) {
for (int index = 0; index < visible.size(); index++) { appendFocusBlock(output, hiddenRows, hiddenStart, rows - 1, expandedBlocks, toggle);
if (index > 0) { }
output.append('\n'); if (output.length() == 0) {
} output.append("暂无可读内容");
output.append(visible.get(index));
} }
return output; return output;
} }
private void appendFocusBlock(
SpannableStringBuilder output,
List<String> lines,
int startRow,
int endRow,
Set<String> expandedBlocks,
FocusToggle toggle
) {
String key = startRow + ":" + endRow;
boolean expanded = expandedBlocks.contains(key);
String summary = focusSummary(lines);
int actionStart = output.length();
appendLine(output, (expanded ? "" : "") + summary);
int actionEnd = output.length();
output.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
toggle.toggle(key);
}
@Override
public void updateDrawState(TextPaint paint) {
paint.setColor(0xff67da91);
paint.setUnderlineText(false);
paint.setFakeBoldText(true);
}
}, actionStart, actionEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
if (expanded) {
for (String line : lines) {
appendLine(output, " " + line);
}
}
}
private String focusSummary(List<String> lines) {
String joined = String.join(" ", lines).toLowerCase(Locale.ROOT);
String type;
if (joined.contains("error") || joined.contains("failed") || joined.contains("exception")) {
type = "错误输出";
} else if (joined.contains("working") || joined.contains("running")
|| joined.contains("waiting") || joined.contains("interrupt")) {
type = "运行状态";
} else if (joined.contains("test") || joined.contains("build") || joined.contains("compile")
|| joined.contains("gradle") || joined.contains("webpack") || joined.contains("npm")) {
type = "构建/测试";
} else if (joined.contains("git ") || joined.contains("commit") || joined.contains("push")) {
type = "Git 操作";
} else if (lines.get(0).startsWith(">") || lines.get(0).startsWith("$")
|| lines.get(0).startsWith("!")) {
type = "命令与输出";
} else {
type = "终端细节";
}
return type + " · " + lines.size() + " 行 · " + compactSummary(lines.get(0), 42);
}
private String compactSummary(String text, int maxChars) {
String compact = text.replaceAll("\\s+", " ").trim();
if (compact.length() <= maxChars) {
return compact;
}
return compact.substring(0, maxChars - 1) + "";
}
private void appendLine(SpannableStringBuilder output, String text) {
if (output.length() > 0) {
output.append('\n');
}
output.append(text);
}
private int handleEscape(String text, int index) { private int handleEscape(String text, int index) {
if (index + 1 >= text.length()) { if (index + 1 >= text.length()) {
return -1; return -1;