Lanterna 全屏 TUI 形态的 Renderer 适配器。 设计:本类不拥有 Lanterna 主循环,而是作为现有 LanternaWindow 的"视图"。 真正的 GUI 主循环由 com.tcode.tui.TuiBootstrap(或后续 Main)启动。 本类把 Renderer 方法委托到 CenterPane/StatusPane 等 widget。 所有 widget 操作必须在 GUI 事件线程执行,本类内部用 {@link LanternaWindow#
| 31 | * {@link LanternaWindow#runOnGuiThread} 封送。 |
| 32 | */ |
| 33 | public final class LanternaRenderer implements Renderer { |
| 34 | |
| 35 | private final LanternaWindow window; |
| 36 | private final CenterPane centerPane; |
| 37 | private final StatusPane statusPane; |
| 38 | private final WindowBasedTextGUI gui; |
| 39 | private final PrintStream stream; |
| 40 | private volatile boolean closed; |
| 41 | |
| 42 | public LanternaRenderer(LanternaWindow window) { |
| 43 | this.window = Objects.requireNonNull(window); |
| 44 | this.centerPane = window.getRootPane().getCenterPane(); |
| 45 | this.statusPane = window.getRootPane().getStatusPane(); |
| 46 | this.gui = window.getGui(); |
| 47 | this.stream = new PrintStream(new CenterPaneSink(), true, StandardCharsets.UTF_8); |
| 48 | } |
| 49 | |
| 50 | @Override |
| 51 | public void start() { |
| 52 | // GUI 主循环由 LanternaWindow.start() 驱动,本方法 no-op。 |
| 53 | } |
| 54 | |
| 55 | @Override |
| 56 | public void close() { |
| 57 | if (closed) { |
| 58 | return; |
| 59 | } |
| 60 | closed = true; |
| 61 | try { |
| 62 | window.close(); |
| 63 | } catch (Exception ignored) { |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | @Override |
| 68 | public PrintStream stream() { |
| 69 | return stream; |
| 70 | } |
| 71 | |
| 72 | @Override |
| 73 | public void appendToolCalls(List<LlmClient.ToolCall> toolCalls) { |
| 74 | if (toolCalls == null || toolCalls.isEmpty()) { |
| 75 | return; |
| 76 | } |
| 77 | for (LlmClient.ToolCall tc : toolCalls) { |
| 78 | String name = tc.function().name(); |
| 79 | String args = tc.function().arguments(); |
| 80 | window.runOnGuiThread(() -> centerPane.appendToolCall(name, args)); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | @Override |
| 85 | public void appendDiff(String filePath, String before, String after) { |
| 86 | StringBuilder sb = new StringBuilder(); |
| 87 | sb.append("📝 ").append(filePath == null ? "(unnamed)" : filePath).append("\n"); |
| 88 | if (before == null) { |
| 89 | sb.append("(新建文件,").append(after == null ? 0 : after.length()).append(" 字符)"); |
| 90 | } else if (after == null) { |
nothing calls this directly
no outgoing calls
no test coverage detected