Add expandable terminal reading summaries
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user