2026-05-02 20:25:00

Vim 的功能非常强大,但这种强大是有代价的——你得把它全部记住。
就拿替换命令来说。”查找替换”这么一个最常见的编辑操作,在 Vim 里有一堆变体:
g。c。i。或者在模式里写 \c。\v 是 very-magic,\V 是纯文本,还有默认的 magic 模式,各有各的转义规则。\< 和 \> 把模式包起来。容易忘,也容易打错。%。只替换选中区域?用 '<,'>。指定行范围?输入行号。于是你写出这样的命令:
:%s/\v(foo|bar)/baz/gci
新手要花很久才能记住这些标志位。有经验的用户也会忘掉不常用的那几个。
而且替换只是一个内置命令。真正麻烦的是插件——每个插件都有自己的命令、自己的参数和语法。天天用的那些还好,肌肉记忆自然形成;但那些偶尔才用一次的?每次都得重新翻文档。
这就是 Vim 界面的根本矛盾:命令行天生为速度优化,而不是为可发现性优化。如果你已经记住了命令,它快得飞起;如果你忘了——哪怕只是忘了一个标志位——就只能干瞪眼。
如果能把一个命令的所有可选项一次性展示在用户面前呢?不是藏在 :help 里,而是就在屏幕上,一目了然:

这是一个用 vim-quickui 搭建的查找替换对话框。所有选项清清楚楚:正则模式、大小写敏感、全词匹配、是否确认、替换范围。不管是第一次用,还是隔了几个月再回来用,完全没有记忆负担——你看到有什么选项,勾选你需要的,直接开始。
那么问题来了,在 Vim 里怎么才能做出这样的对话框?
原生工具帮不了你太多。Vim 给你的只有 input() 做单行输入和 inputlist() 做简单列表选择——没有文本框,没有复选框,没有单选按钮,没有办法把多个控件放在一个窗口里。如果要做一个多字段的表单,只能一个接一个地调用阻塞式的 input(),而且填错了前面的内容没法回头修改。
这种方式,不能扩展。
vim-quickui 是一个为 Vim 和 NeoVim 设计的 TUI 组件库,提供菜单、列表框、文本框等控件——全部用纯 VimScript 实现,不依赖任何外部工具。
在 1.5.0 版本中,它新增了一套数据驱动的对话框系统:你把控件声明为一组字典,quickui 将它们渲染到一个弹出窗口中;用户完成操作后,所有值以一个字典返回给你。
不需要 +python,不需要 Lua,不需要外部依赖,纯 VimScript 搞定。

对新手来说,这降低了上手 Vim 的门槛——不用先把每个命令和标志位都背下来,才敢去用一个功能。对老手来说,这减少了为偶尔使用的命令反复查文档的次数,让你保持在心流状态里。
用 vim-plug:
Plug 'skywind3000/vim-quickui'
或者用 Vim 内置包管理:
cd ~/.vim/pack/vendor/start && git clone https://github.com/skywind3000/vim-quickui
可选设置 Unicode 边框:
let g:quickui_border_style = 2
完事了,没有构建步骤,没有依赖。
来做一个简单的设置对话框,把下面的代码放到一个函数里:
function! MySettings()
let items = [
\ {'type': 'label', 'text': 'Settings:'},
\ {'type': 'input', 'name': 'name', 'prompt': 'Name:',
\ 'value': 'test'},
\ {'type': 'radio', 'name': 'choice', 'prompt': 'Pick:',
\ 'items': ['A', 'B', 'C']},
\ {'type': 'check', 'name': 'flag',
\ 'text': 'Enable Feature'},
\ {'type': 'button', 'name': 'confirm',
\ 'items': [' &OK ', ' &Cancel ']},
\ ]
let result = quickui#dialog#open(items, {'title': 'Settings'})
echo result
endfunc
执行 :call MySettings() 效果如下:

一个真正的对话框,在 Vim 里,带多个控件。
逐行解释一下:
label —— 顶部的静态文本,不可聚焦input —— 带提示标签和默认值的文本输入框radio —— 单选按钮组,只能选一个check —— 复选框,可以切换开关button —— 底部的按钮行你可以用 Tab 和 Shift-Tab 在控件间切换焦点,在输入框里直接打字,按 Space 切换复选框或选择单选项,按 Enter 或点击按钮确认。
所有的值都在 result 字典里返回。
对话框关闭后,你需要知道两件事:用户是确认了还是取消了?如果确认了,是按了按钮还是在输入框里按了回车?
返回值有两个关键字段:
button_index —— 按了哪个按钮(0 起始),取消时为 -1
button —— 按钮控件的 name,如果是在非按钮控件上按回车或取消则为 ''
这是你在每个对话框里都会用到的判断模式:
let r = quickui#dialog#open(items, opts)
if r.button_index == -1
" 用户按了 ESC、Ctrl-C 或关闭按钮
echo 'Cancelled'
elseif r.button == ''
" 用户在输入框/单选/复选上按了回车
echo 'Confirmed (Enter): name=' . r.name
else
" 用户点击了某个按钮
echo 'Button pressed: ' . r.button . ' #' . r.button_index
endif
几个要点:
button_index 从 0 开始。第一个按钮返回 0,第二个返回 1,依此类推。button 区分回车和按钮点击。当 button_index 为 0 时,检查 r.button:如果为 '',说明是在非按钮控件上按了回车;如果非空,说明是点击了第一个按钮。r.name 等字段仍然包含用户在取消前输入的内容。下次重新打开对话框时可以恢复状态。大多数情况下,你只需要这样判断:
let r = quickui#dialog#open(items, opts)
if r.button_index >= 0 && r.button != ''
" 用户点击了某个按钮——处理返回值
echo 'Name: ' . r.name
endif
或者如果你有 OK 和 Cancel 两个按钮:
" ' &OK ' 是按钮 0,' &Cancel ' 是按钮 1
if r.button_index == 0 && r.button != ''
echo 'Accepted: ' . r.name
endif
来做一个更贴近真实插件的东西:一个”新建项目”的表单,包含所有控件类型:
function! NewProject()
let items = [
\ {'type': 'label', 'text': 'Create New Project:'},
\ {'type': 'input', 'name': 'project_name', 'prompt': 'Project:'},
\ {'type': 'input', 'name': 'email', 'prompt': 'Email:'},
\ {'type': 'dropdown', 'name': 'language', 'prompt': 'Language:',
\ 'items': ['Python', 'JavaScript', 'Go', 'Rust', 'C++'],
\ 'value': 0},
\ {'type': 'dropdown', 'name': 'build', 'prompt': 'Build:',
\ 'items': ['Make', 'CMake', 'Cargo', 'npm', 'pip'],
\ 'value': 0},
\ {'type': 'radio', 'name': 'license', 'prompt': 'License:',
\ 'items': ['&MIT', '&Apache', '&GPL', '&Proprietary'],
\ 'value': 0},
\ {'type': 'check', 'name': 'git_init',
\ 'text': 'Initialize git repo', 'value': 1},
\ {'type': 'check', 'name': 'ci',
\ 'text': 'Add CI config'},
\ {'type': 'button', 'name': 'confirm',
\ 'items': [' &Create ', ' Cancel ']},
\ ]
let opts = {'title': 'New Project', 'w': 50, 'focus': 'project_name'}
let result = quickui#dialog#open(items, opts)
" 检查用户是否点击了 "Create" 按钮(按钮 0)
if result.button_index == 0 && result.button != ''
let languages = ['Python', 'JavaScript', 'Go', 'Rust', 'C++']
let builds = ['Make', 'CMake', 'Cargo', 'npm', 'pip']
echo 'Project: ' . result.project_name
echo 'Email: ' . result.email
echo 'Language: ' . languages[result.language]
echo 'Build: ' . builds[result.build]
echo 'License: ' . result.license
echo 'Git: ' . (result.git_init ? 'yes' : 'no')
echo 'CI: ' . (result.ci ? 'yes' : 'no')
else
echo 'Cancelled'
endif
endfunc
效果截图:

这个例子展示了几个要点:
下拉列表控件(dropdown)显示为一个折叠的选择框。按 Enter 或 Space 弹出选项列表供选择。返回值是 0 起始的索引,需要自己映射回文本。
分隔线(separator)在复选框和按钮之间画一条水平线,替代了控件间的默认空行,保持布局整洁。
opts.focus 把初始焦点设置到 project_name 输入框,用户打开对话框就能立刻开始输入。
提示文本自动对齐。注意 Project:、Email:、Language:、Build: 和 License: 这些标签都是左对齐的,它们后面的控件起始位置在同一列。QuickUI 会自动计算最长的提示文本,补齐其余的。
热键标记。按钮、单选和复选文本中的 & 标记了热键字符。比如 &Create 让 C 成为热键——在对话框的任何地方(不在输入框中时)按 C 就能触发该按钮。单选组的 &MIT、&Apache 等同理。
分享几个我在开发对话框过程中总结的经验:
先设好 opts.w。 如果不设宽度,QuickUI 会自动计算。对于简单对话框没问题,但对于有多个字段的表单,显式设置一个宽度(比如 50)能让布局更一致。
用 'value' 设默认值。 每个控件都支持 value 字段。输入框接收字符串,单选/下拉/复选框接收数字。预填好默认值能让用户少打几个字。
复选框不需要提示标签。 和输入框、单选不同,复选框的文本本身就是标签,不加 prompt 看着更自然。如果想让它和其他有提示的控件对齐,也可以加 'prompt' 字段。
给按钮行命名。 如果只有一行按钮,默认名字 'button' 就够了。但如果有两行按钮(比如 “Apply/Reset” 和 “OK/Cancel”),要给它们不同的 name,这样才能区分用户点的是哪一行。
本文只覆盖了基础用法。对话框系统还有更多能力:
history 字段在多次调用间共享历史记录,按 Ctrl+Up / Ctrl+Down 浏览opts.validator 设置一个回调函数,在对话框关闭前验证字段值完整参考可查看 vim-quickui 仓库中的 Dialog Guide。
vim-quickui 不仅仅是对话框。它还提供:
全部是纯 VimScript 实现,全部同时支持 Vim 和 NeoVim。
详细文档参见:完整文档
如果你觉得 vim-quickui 有用,欢迎去 GitHub star 一下,也帮助更多人发现这个项目。
有问题或想法?欢迎提 issue 或者在下面评论。
![]()
The post 在 Vim 里实现可定制表单对话框 appeared first on Skywind Inside.
2026-04-03 22:48:39
最近小孩在学 C++(信奥),学了堆语法以后不知道干嘛,学了一年也只会对着黑窗口打印内容,正反馈太弱了,不像其他语言,学个几天就能做出漂亮的东西来,恰巧他对开发游戏感兴趣,我扫了一眼现在 C++ 的游戏开发框架,都太复杂了,SDL 概念琐碎,SFML 使用麻烦,所以我写了个针对初学者的游戏库,只有一个头文件 GameLib.h 零依赖,拷贝到代码目录 include 就能用,十行代码就能出个小 demo:

本着简化一切的思想,使用起来比 PyGame 还要简单:
#include "GameLib.h"
int main() {
GameLib game;
game.Open(640, 480, "My Game", true);
int x = 320, y = 240;
while (!game.IsClosed()) {
if (game.IsKeyDown(KEY_LEFT)) x -= 3;
if (game.IsKeyDown(KEY_RIGHT)) x += 3;
if (game.IsKeyDown(KEY_UP)) y -= 3;
if (game.IsKeyDown(KEY_DOWN)) y += 3;
game.Clear(COLOR_BLACK);
game.FillCircle(x, y, 15, COLOR_CYAN);
game.DrawText(10, 10, "Up/Down/Left/Right to move!", COLOR_WHITE);
game.Update();
game.WaitFrame(60);
}
return 0;
}
就这么几行代码,没有 SDL 里反锁的像素格式,消息机制,各种乱七八糟 SDL_ 开头的对象,也没有初始化就需要 500 行代码的 DirectX 那么麻烦,所有复杂的东西藏起来,只留下做游戏的乐趣。
编译:
g++ main.cpp -o game.exe
不需要加任何编译参数,很多初学者连命令行编译都不懂(比如我家小孩),只会直接在 DevC++ 里点编译+运行,让他们像用其他库一样添加一些类似 -ld3d9x 之类编译参数,可能直接就劝退一大群人,因此这个库完全使用默认编译参数,所有依赖都是动态库自己手工加载。
运行就能用方向键控制小球移动:

几行代码迅速看到反馈。
为什么做这个库呢?
市面上的游戏库(SDL、SFML、raylib)都很好,但对于刚接触 C++ 的初学者来说:
GameLib 的目标是零门槛:把 GameLib.h 拷到项目文件夹,写一个 .cpp 文件,点编译,游戏就跑起来了。
它专门为 Dev C++(很多学校编程课在用的 IDE)设计,兼容其自带的 GCC 4.9.2 编译器。当然,任何支持 C++11 的 Windows 编译器都可以用。
(点击下面展开更多)
特性一览
零配置:
开箱即用的绘图:
精灵系统:
键盘和鼠标:
声音:
游戏工具:
Tilemap 系统:
快速上手
第一步:下载
把 GameLib.h 放到你的项目文件夹里。
第二步: 写代码
创建一个 main.cpp:
#include "GameLib.h"
int main() {
GameLib game;
game.Open(800, 600, "Hello GameLib", true);
while (!game.IsClosed()) {
game.Clear(COLOR_DARK_BLUE);
game.DrawTextScale(200, 250, "Hello, World!", COLOR_GOLD, 3);
game.DrawText(280, 320, "Press ESC to exit", COLOR_GRAY);
if (game.IsKeyPressed(KEY_ESCAPE)) break;
game.Update();
game.WaitFrame(60);
}
return 0;
}
第三步:编译运行
Dev C++:新建项目 > 添加 main.cpp > 编译运行。
或者命令行:
g++ -o game.exe main.cpp -mwindows
就行了。
随机星空
再来个有点视觉效果的例子,随机星空:
#include "GameLib.h"
int main() {
GameLib game;
game.Open(800, 600, "Starfield", true);
// 生成 200 颗星星
int sx[200], sy[200], speed[200];
uint32_t colors[] = {COLOR_WHITE, COLOR_LIGHT_GRAY, COLOR_YELLOW, COLOR_CYAN};
for (int i = 0; i < 200; i++) {
sx[i] = GameLib::Random(0, 799);
sy[i] = GameLib::Random(0, 599);
speed[i] = GameLib::Random(1, 5);
}
while (!game.IsClosed()) {
game.Clear(COLOR_BLACK);
for (int i = 0; i < 200; i++) {
sx[i] -= speed[i];
if (sx[i] < 0) {
sx[i] = 800;
sy[i] = GameLib::Random(0, 599);
}
game.SetPixel(sx[i], sy[i], colors[speed[i] % 4]);
}
game.DrawText(250, 290, "Press ESC to exit", COLOR_GRAY);
if (game.IsKeyPressed(KEY_ESCAPE)) break;
game.Update();
game.WaitFrame(60);
}
return 0;
}
就这么简单,运行效果(点击播放 GIF 动画):

几行代码就能有看得见摸得着的东西,正反馈远远强过黑窗口打印文字。
这个 GameLib.h 适合开发哪些类型的游戏呢?
更多例子请访问项目主页:
https://github.com/skywind3000/GameLib
里面有 15 个例子程序,包括类似《打砖块》 ,《太空射击》之类游戏的代码:

一步一步教你从:窗口控制,键盘鼠标交互,图形绘制,精灵,音乐,卷轴等覆盖各个 GameLib.h 的功能点。
欢迎学会了 C++ 语法想做点什么的同学尝试。
![]()
The post 单头文件 C++ 游戏开发库(GameLib.h) appeared first on Skywind Inside.
2026-01-30 00:27:00
如何最快搭建个人云盘?Nextcloud 太大太臃肿,后面一堆 apache/nginx/php/mysql,命令行同步也不好用,私货还太多。
著名的 syncthing 要求有 P2P 中转服务;还要求同步时另一台电脑也开着机,不开机的话,你要在服务器上做一个同步点持续运行,那么做了这个服务和 C/S 架构的 rclone 有啥区别呢?还要多依赖一个 P2P 中转服务(虽然可以找到别人提供的),其他的开源产品也好不到哪里,相比之下 rclone 是最轻量级的解决方案了。
恰巧 rclone 最近几年一直在优化 bisync 功能,就是双向同步,网盘的核心功能,bisync 在 2022 年引入 rclone 一直处于 experimental/beta 阶段,其实几年前就基本可以用了,官方文档在 2025 年下半年正式移除 experimental/beta 字样,然后 bisync 功能正式转正。
所以我们用 rclone 的 bisync 功能来分别搭建客户端和服务端:
1️⃣ 服务端搭建
使用 supervisor 编辑 /etc/supervisor/conf.d/rclone-sftp.conf 文件:
[program:rclone-sftp]
command=/home/data/app/rclone/rclone serve sftp /home/data/sync --addr :2022 --user MYNAME --pass MYPASS --cache-dir /var/cache/rclone --vfs-cache-mode full
user=nobody
autorestart=true
然后重启:
sudo supervisorctl reload
一个 rclone 提供的 sftp 服务就启动了,就是这么简单,它速度快性能好,传输有加密。
最后我们在 sftp 的文件夹里建立一个叫做 bisync 的文件夹:
sudo mkdir /home/data/sync/bisync
sudo chown -R nobody:nogroup /home/data/sync
处理好权限,让 nobody 这个用户能够读写即可,就这么几步操作,比搭建个 Nextcloud 之类的服务方便太多了。
2️⃣ 客户端
我用 WSL,编辑配置 /root/.config/rclone/rclone.conf 内容如下:
[sftp1]
type = sftp
host = 192.168.1.12
user = MYNAME
port = 2022
pass = [运行 rclone obscure "MYPASS" 得到的加密字符串]
shell_type = unix
md5sum_command = md5sum
sha1sum_command = sha1sum
不想手工填写也可以用 rclone config 交互式的初始化并连接 sftp 服务,一旦服务配置好了,我么就能找个文件夹然后在定时任务里 bisync 了。
不过首先要初始化一下:
mkdir /mnt/e/Local/Cloud/rclone
rclone bisync /mnt/e/Local/Cloud/rclone sftp1:/bisync --resync
这个 --resync 参数用于首次同步初始化,后面就定时任务了。
编辑同步脚本 /home/data/shim/rclone-bisync-sftp1 内容如下:
#!/bin/bash
# configure
RCLONE_BIN="/home/data/app/rclone/rclone"
RCLONE_REMOTE=sftp1:/bisync
RCLONE_LOCAL=/mnt/e/Local/Cloud/rclone
LOGFILE="/var/log/rclone-bisync.log"
# start
$RCLONE_BIN bisync "$RCLONE_LOCAL" "$RCLONE_REMOTE" \
--create-empty-src-dirs \
--compare size,modtime \
--resilient \
--recover \
--max-lock 2m \
--verbose \
--log-file="$LOGFILE"
定时启动:
编辑 /etc/cron.d/rclone_bisync 内容如下:
*/5 * * * * root /home/data/shim/rclone-bisync-sftp1
即可,调试完正常可以把启动脚本里的 --verbose 去掉,没错误就不写日志了。你可以在多个客户端上进行类似的配置,不管 Windows 还是 Linux,要点就是先 config 上远端 sftp,然后初始化 --resync,然后就是定时调用 bisync 子命令就行。
然后你就得到了一个干净纯粹的,开源的,多端同步网盘了。
后记
在 bisync 之前,rclone 实现网盘主要靠 mount,这个功能类似 samba,每次读取写入都要走网络,局域网还行,跨机房就卡的不行了,它后来提供了 cache 机制有一定缓解,但一致性又有问题,经常不同步,这边改了不能像 samba 那样通知那边。
所以 rclone 做网盘体验最好的目前就是 bisync 功能了,bisync 的 bisync 可以用很多源,作为服务端,如果你不想搭建一大套 webserver / fileserver 来做源的话,最好就是用 rclone 自己可以 serve 的服务,包括:http,sftp,webdav,nfs 等等。
我试过 webdav 等,发现如果想要加密,前面还得再套一层 https 代理,或者又搞一大堆证书什么的,异常麻烦,所以体验最好,速度最快的是 sftp 服务。
![]()
The post 使用 rclone bisync 两步搭建个人云盘 appeared first on Skywind Inside.
2026-01-03 22:06:35
说起文本模式界面库,也许有人听过 Turbo Vision 2.0,这货是当年 Borland C++ 3.1 (不是 Turbo C 2.0)背后的商用界面库 TV2 的开源版,经过多年迭代,如今是一款支持 unicode 和现代 C++ 跨平台的 TUI 库了:

项目地址:https://github.com/magiblot/tvision
主要特性:
Linux 下大部分 TUI 程序都是平铺窗口,无法重叠,更无法自由拖动,而 Turbo Vision 2.0 完全像用图形界面程序一样,鼠标操作这些窗口自由移动,扩大缩小,全屏化:

而且 Linux 下面大部分程序对 ALT+ ,CTRL+ ,SHIFT+CTRL+ 等组合键支持非常有限,而 TV2 对不同平台的键盘鼠标做了很好的兼容,让你在远程终端里也能自由的使用各种组合键和功能键。
用过 Borland C++ 3.1 的人一定会以为 BC31 重生了,某种意义上来讲是的,不要觉得技术古老,Linux 下面 TUI 发展几十年,没一个打得过 Turbo Vision 2 的:

这是 TV2 做的终端模拟器 tvterm,各位天天用 tmux 分屏模拟,但 tmux 发展了那么长时间都不支持子窗口互相重叠覆盖,鼠标拖动,TV2 可以让你像用桌面软件一样灵活的操作各个子窗口。
Vim/NeoVim 直到 2019 年才支持 popup/floatwin 可以实现上面类似的效果,而 Turbo Vision 2 在三十年前就做到了。
过去 TV2 只支持 ANSI 编码,无法显示中文,如今中文,日文都能正常显示:

包括 emoji:

你在 Linux 下大部分 TUI 程序想调整下设置都只能编写配置文件,配置 vim 也只能写 vimrc,但 TV2 里有丰富的 TUI 设置对话框:

大部分设置,对话框里点点鼠标就能搞定,毫无学习门槛,无需看很多帮助才知道怎么写配置:

当然 TV2 也有内置帮助系统,它有一套类似 .chm 等帮助系统,内部包含索引,跳转,可以在 TUI 内随时查看你的帮助文件,比如下面的快捷键帮助,基本上和 Windows 程序一脉相承:

最后是编程接口,linux 下一堆 tui 接口,比如 ncurses 这样的都是比较初级的原始的,没有任何高级控件,同时接口也是 C 的,而 TV2.0 基本上所有高级控件你都可以直接使用,写起代码来类似传统的 Qt 程序:
class HelloApp : public TApplication {
public:
HelloApp();
void showHelloDialog();
};
HelloApp::HelloApp() :
TProgInit(&TApplication::initStatusLine,
&TApplication::initMenuBar,
&TApplication::initDeskTop) {
// 启动时显示对话框
showHelloDialog();
}
void HelloApp::showHelloDialog() {
// 创建对话框 (x, y, width, height)
TDialog *dialog = new TDialog(TRect(0, 0, 40, 9), "Hello World");
if (dialog) {
// 居中对话框
dialog->options |= ofCentered;
// 添加 "Hello World" 静态文本 (居中显示)
TStaticText *text = new TStaticText(
TRect(2, 2, 38, 5),
"\003Hello World" // \003 表示居中对齐
);
dialog->insert(text);
// 添加 OK 按钮 (居中)
TButton *button = new TButton(
TRect(14, 5, 26, 7),
"~O~K",
cmOK,
bfDefault
);
dialog->insert(button);
// 显示模态对话框
deskTop->execView(dialog);
// 清理
TObject::destroy(dialog);
}
}
int main() {
HelloApp app;
app.run();
app.shutDown();
return 0;
}
基本就是类似 Qt 的编程方式:事件驱动+控件组合+OOP,不是 ncurses 那种 select/read 标准输入,自己解码半天终端控制码,在琐碎的控制各处显示,自己控制状态切换和重绘,写的你想吐。
Linux 下面基于 ncurses 二次封装的库也很多,但大多是个人项目,浅层封装,Turbo Vision 过去作为支持 Borland C++ 3.1 这种商用级 IDE 的界面库,不是这些玩票项目可以比得了的。
编程语言 TMBASIC 也是用 Turbo Vision 2 做的界面:

补充点历史,Turbo Vision 最初是 Borland 公司用 Pascal 实现的,用于 Turbo Pascal,后来在做 Borland C++ 3.x 的时候用 C++ 重新实现了一个版本,用于支持 BC31 的界面,在 BC31 里包含头文件和静态库,你可以直接用它构建 TUI 程序,接着 Borland 在 1997 年将 C++ 版本的完全开源了,就是我们上面说的这套的源头;
而 Pascal 版本的 Turbo Vision 一直没开源,Free Pascal 后面又根据 C++ 开源的版本重新实现了一套叫做 Free Vision 的界面库给 Free Pascal 做 IDE 用:

我很喜欢它的设置界面,根本不用读文档写配置文件,直接对话框里全部帮你归好类了:
c
是不是很眼熟?Free Pascal 的 IDE,背后使用的 Free Vision,和上面的 Turbo Vision 同出一源。
PS:Turbo Vision 还出过几本书:

想看的话到开头项目主页里有链接。
![]()
The post C++ 最好用的 TUI 界面库 Turbo Vision 2.0 appeared first on Skywind Inside.
2025-12-27 00:32:00
就一年没清理 C 盘,今天清理出 140GB 的东西来,而且还没影响使用,所有程序都在往你的 C 盘拉屎。不要网友问我怎么清理下来的:
1)右键 C 盘属性,清理;
2)自己到 TEMP 目录手动清空,注意有三个 Temp:AppData 下面两个,Windows 目录下有一个;
3)SpaceSniffer 找占用大的目录,清空不合理的,这个需要一点经验,拿不准可问下 AI 某目录能不能清空;
4)清浏览器缓存;
5)最后 CCleaner 跑一遍。

上面一系列任务做完基本就清理出 100GB 的内容了,然后再使用 Dism++ 删除系统还原点后,又多出 40GB 来:

最开始剩余空间只有 130GB,现在有 272GB 剩余,可以做一次年度全盘备份了。
![]()
The post 如何清理你的系统盘(C: 盘)? appeared first on Skywind Inside.
2025-10-30 13:48:00
Rust 并不适合开发游戏,它更擅长有明确定义,边界清晰的项目,这样你类型体操一遍做完问题不大,比如重构个已经十年没变过的 C 模块,但游戏领域并没有明确的定义,策划案不停修改,你的代码不但要求快速迭代还要求不停重构,那么类型体操在这过程中就变成锁死你的紧箍咒。
参考文章:3 年写了 10 万行代码开发者吐槽:当初用 Rust 是被忽悠了
这里解释一下,为什么当项目没有明确定义时,rust 为啥会显得笨拙?因为此时不但需求会随时间变来变去,更要命的是你没有一份十年没变过的 C 代码做参考,对项目整体实现缺乏全局的认识,只有自底向上的方法不断尝试和修正自己,不段反思和改进中上层代码,才能像盲人摸象那样逐步认清楚整个世界因该是啥样。
而此时类型体操会在这时勒得你喘不过气来,一个之前需要横着用的变量现在需要竖着用了,你思来想去发现你完全没办像其他语言那样改成法竖着用了,于是你只有引入更大范围的重写才能解决问题,你觉得这样很难受,去 rust 社区寻求帮助,但发现他们并不会帮助你真的解决语言问题,只会一个劲的指责你 “你觉得痛苦正是因为你对 rust 不熟悉导致的” 或者 “rust 逼迫你更大范围重写正是逼你尽早写出更好的代码”,他们这么说在定义清晰的项目里的确没问题,越早重构越好,但在定义不清晰的项目上里存在大量中间设计,你今天改成这样,八成不是最终形态,隔几天可能还要改,而此时 rust 却逼迫你每次都提前费精力进行更大范围的重写,即使这时完全没必要的,过两天就不需要的,他也不许你像其他语言那样用快速实现的方式应对新的中间状态,等需求稳定了,技术方案也收敛的情况下再进行迭代和完善,然后你就抓狂了!
网友 FENG DONG:项目没有明确定义时候,需要设计 cost function,然后让代码沿着 gradient 演进。但 Rust 不是一种可微的语言。
网友 Aaltonen:游戏开发是 “巨型可变状态球” (giant balls of mutable state),需求像 gradient descent 一样逐步演进,但 Rust 的借用检查器(borrow checker)和所有权模型强制你 “一次性想好”,导致小改动引发大重构。
那么 Rust 适合开发游戏引擎么?我只能说 depends,主要到了现在也没啥 rust 开发游戏引擎的成功例子啊,一天到晚到处搞营销的 rust 游戏引擎 bevy 基本上类似个玩具,到现在都没啥靠谱的商业游戏案例,顶多几个独立小游戏之类,而且它要求用 rust 来写业务逻辑,这个上面已经论述过,它并不适合开发游戏业务逻辑;
其次游戏引擎有很多部件,有需要高度优化各种汇编技巧的性能部分(类似 ffmpeg),有需要同各种系统图形 api 打交道的抽象硬件层部分(不断同 direct3d,opengl,vulkan 还有各种系统 api 打交道),还有需要大量整合第三方(基本是 c++ 库,比如 imgui, box2d, sdl, bgfx, physx)的胶水层,这些也并不适合 rust。除非你觉得 ffmpeg 这样的项目适合用 rust 重写,或者可以用 rust 把大量久经考验的第三方库全部发明一遍。
最后,成熟的游戏引擎里都是有各种 hack 或者 dirty trick 的,rust 做这些并不是特别方便。
配图:“But it’s SAFE !!”

![]()
The post Rust 适合开发游戏吗? appeared first on Skywind Inside.