# jink **Repository Path**: solonlab/jink ## Basic Information - **Project Name**: jink - **Description**: Java 版终端 UI 框架,灵感来自 ink(React for CLI)。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 9 - **Created**: 2026-04-07 - **Last Updated**: 2026-04-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # jink **Java 版终端 UI 框架**,灵感来自 [ink](https://github.com/vadimdemedes/ink)(React for CLI)。 用声明式的方式构建终端界面:组件树 → Flexbox 布局 → ANSI 渲染 → 键盘交互。 ![Java 21+](https://img.shields.io/badge/Java-21%2B-blue) ![JLine 3](https://img.shields.io/badge/JLine-3.28.0-green) ![Tests](https://img.shields.io/badge/Tests-146%20passing-brightgreen) ![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg) --- ## 🖼️ 效果预览
布局 样式
交互 高级
> 📖 [查看全部示例与运行效果](docs/examples.md) --- ## ✨ 特性 | 功能 | 说明 | |:-----|:-----| | **组件模型** | Builder 模式构建 UI 组件树,类 React 有状态组件 | | **Flexbox 布局** | 纯 Java 实现,支持 direction/justify/align/gap/grow | | **丰富样式** | 16 色 / 256 色 / RGB 真彩色,粗体/斜体/下划线/反色 | | **9 种边框** | SINGLE, DOUBLE, ROUND, BOLD, CLASSIC, ARROW 等 | | **键盘输入** | 基于 JLine 3 raw mode,方向键/功能键/Ctrl 组合键 | | **CJK 支持** | 中日韩字符正确占 2 列宽度 | | **焦点管理** | Tab/Shift+Tab 导航,可编程聚焦 | | **全屏模式** | 备用屏幕缓冲区,退出后终端恢复干净 | | **帧率控制** | 可配置 maxFps,默认 30 | --- ## 📦 要求 - **Java 21+** - **Maven 3.6+** - 真实终端(Windows Terminal / iTerm2 / GNOME Terminal) --- ## 🚀 快速开始 ### 添加依赖 ```xml io.mybatis.jink jink 0.3.0 ``` ### Hello World ```java import io.mybatis.jink.Ink; import io.mybatis.jink.component.*; import io.mybatis.jink.style.*; public class HelloWorld { public static void main(String[] args) { Ink.renderOnce( Box.of( Text.of("Hello, Jink!").color(Color.GREEN).bold() ).borderStyle(BorderStyle.ROUND) .borderColor(Color.BRIGHT_MAGENTA) .paddingX(1), 40, 5 ); } } ``` ``` ╭──────────────────────────────────────╮ │ Hello, Jink! │ ╰──────────────────────────────────────╯ ``` --- ## 🆚 与 ink 对比 ink(React/TypeScript)的第一个示例是一个自动计数器: ```tsx // ink (TypeScript) import React, {useState, useEffect} from 'react'; import {render, Text} from 'ink'; const Counter = () => { const [counter, setCounter] = useState(0); useEffect(() => { const timer = setInterval(() => { setCounter(previousCounter => previousCounter + 1); }, 100); return () => clearInterval(timer); }, []); return {counter} tests passed; }; render(); ``` 等效的 jink(Java)实现: ```java // jink (Java) import io.mybatis.jink.Ink; import io.mybatis.jink.component.Component; import io.mybatis.jink.component.Renderable; import io.mybatis.jink.component.Text; import io.mybatis.jink.style.Color; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class Counter extends Component { record State(int count) {} private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); public Counter() { super(new State(0)); } @Override public void onMount() { scheduler.scheduleAtFixedRate(() -> { int next = getState().count() + 1; setState(new State(next)); if (next >= 100) { scheduler.shutdown(); } }, 100, 100, TimeUnit.MILLISECONDS); } @Override public void onUnmount() { scheduler.shutdownNow(); } @Override public Renderable render() { return Text.of(getState().count() + " tests passed") .color(Color.GREEN); } public static void main(String[] args) { Ink.render(new Counter()).waitUntilExit(); } } ``` --- ## 🎨 示例展示 ### 文本样式 ```java Box.of( Text.of("粗体").bold(), Text.of("红色").color(Color.RED), Text.of("RGB橙").color(Color.rgb(255, 165, 0)), Text.of("反色").inverse(), Text.of( Text.of("嵌套: ").color(Color.CYAN), Text.of("红色粗体").color(Color.RED).bold() ) ).flexDirection(FlexDirection.COLUMN); ``` ### Flexbox 布局 ```java // 水平等分面板 Box.of( Box.of(Text.of("左侧")).flexGrow(1).borderStyle(BorderStyle.SINGLE), Box.of(Text.of("右侧")).flexGrow(1).borderStyle(BorderStyle.SINGLE) ).width(60).height(5); // 垂直布局 + 弹性空白 Box.of( Text.of("标题").bold(), Spacer.create(), // 自动填充中间空间 Text.of("底部").dimmed() ).flexDirection(FlexDirection.COLUMN).height(10); ``` ### 有状态交互组件 ```java public class Counter extends Component { record State(int count) {} public Counter() { super(new State(0)); } @Override public Renderable render() { return Box.of( Text.of("计数: " + getState().count()).color(Color.GREEN).bold(), Text.of("↑ 增加 ↓ 减少 q 退出").dimmed() ).flexDirection(FlexDirection.COLUMN) .borderStyle(BorderStyle.ROUND).paddingX(1); } @Override public void onInput(String input, Key key) { if (key.upArrow()) setState(new State(getState().count() + 1)); else if (key.downArrow()) setState(new State(getState().count() - 1)); } public static void main(String[] args) { Ink.render(new Counter()).waitUntilExit(); } } ``` ### 完整 Copilot CLI 复刻 ```java // CopilotDemo: 完整复刻 GitHub Copilot CLI 界面 // 包含:标题框 + 消息滚动 + 多行输入 + 输入历史 + 快捷键栏 Ink.render(new CopilotDemo()).waitUntilExit(); ``` --- ## 📚 文档 | 文档 | 说明 | |:-----|:-----| | [快速入门](docs/getting-started.md) | 从零开始,7 个完整示例 | | [架构与 API 参考](docs/architecture.md) | 包结构、渲染管道、完整 API | | [示例集锦](docs/examples.md) | 所有 Demo 的操作步骤(适合录制 GIF) | | [ink vs jink 对比](docs/comparison.md) | 功能覆盖率、缺失项、不可移植项分析 | --- ## 🏃 运行 Demo ### 交互式菜单(推荐) 自动列出所有 Demo 类,选择序号运行: ```powershell # PowerShell(Windows) .\scripts\run.ps1 # 指定 JDK 路径(当系统 Java < 21 时) .\scripts\run.ps1 C:\path\to\jdk21 ``` ```bash # Bash(Linux/macOS) ./scripts/run.sh # 指定 JDK 路径 ./scripts/run.sh /path/to/jdk21 ``` ```cmd :: CMD(Windows) scripts\run.cmd :: 指定 JDK 路径 scripts\run.cmd C:\path\to\jdk21 ``` ### 直接运行指定 Demo ```powershell # PowerShell .\scripts\run-demo.ps1 io.mybatis.jink.demo.Counter .\scripts\run-demo.ps1 io.mybatis.jink.demo.FeatureShowcase .\scripts\run-demo.ps1 io.mybatis.jink.demo.CopilotDemo # 指定 JDK 路径(第二个参数) .\scripts\run-demo.ps1 io.mybatis.jink.demo.Counter C:\path\to\jdk21 ``` ```cmd :: CMD scripts\run-demo.cmd io.mybatis.jink.demo.Counter ``` > **JDK 优先级**:命令行参数 > `JINK_JAVA_HOME` 环境变量 > 系统 Java(须 >= 21) ### 输入诊断 当需要排查 Windows Terminal + JLine 下的键盘/鼠标输入时,可以运行诊断工具: ```powershell .\scripts\run-demo.ps1 io.mybatis.jink.demo.InputDiagnostic ``` 它会直接启用 `trackMouse()`,并打印收到的方向键、滚轮和其他 ESC 序列,方便确认当前终端实际发送的输入。 --- ## 🔧 构建 & 测试 ```bash # 编译 mvn clean compile # 运行 146 个单元测试 mvn test # 打包 mvn clean package ``` --- ## 🏗️ 项目结构 ``` io.mybatis.jink ├── Ink # 框架入口(render / renderToString / renderOnce) ├── component/ # 组件系统 │ ├── Component # 有状态组件基类 │ ├── Box # Flexbox 容器 │ ├── Text # 文本(支持嵌套和样式) │ ├── Spacer # 弹性空白 │ ├── Static # 增量渲染 │ └── FocusManager # 焦点管理 ├── style/ # 样式定义 │ ├── Color # 16/256/RGB 颜色 │ ├── BorderStyle # 9 种边框样式 │ └── FlexDirection ... # Flexbox 枚举 ├── layout/ # Flexbox 布局引擎 ├── render/ # 渲染管道(VirtualScreen → ANSI) ├── input/ # 键盘输入(Key + KeyParser) ├── dom/ # 虚拟 DOM(ElementNode/TextNode) ├── ansi/ # ANSI 转义码工具 └── util/ # StringWidth(CJK 宽度计算) ``` --- ## ⚖️ ink 功能对照 | 功能 | ink | jink | |:-----|:----|:-----| | 声明式 UI | JSX | Builder API | | 状态管理 | useState | Component.setState() | | 副作用 | useEffect | onMount/onUnmount | | 输入处理 | useInput | onInput 方法 | | Flexbox | Yoga (C++) | 纯 Java 实现 | | 颜色 | chalk | 内置 Color | | 边框 | boxen | 9 种 BorderStyle | | 焦点 | useFocus | FocusManager | | 静态内容 | \ | Static 组件 | | CJK 宽度 | string-width | StringWidth | | 终端控制 | 内置 | JLine 3 | | 最低版本 | Node.js 18+ | Java 21+ | --- ## 📋 已知限制 - CopilotDemo 的鼠标滚轮依赖 JLine Windows 终端的原生鼠标追踪;其他终端的鼠标事件格式可能需要额外适配 - 目前仅在 Windows Terminal + JLine 3 上测试,Linux/macOS 终端行为可能不同 - 尚未发布到 Maven Central(即将发布) --- ## 📄 License Apache License, Version 2.0 - 详见 [LICENSE](LICENSE) 文件。