f4 0.1.1-alpha: первый публичный релиз асинхронного клона Far Manager на Go

от автора

Привет, Хабр! Если вы читаете мои дайджесты, то знаете, что обычно я пишу о развитии проекта far2l — порта Far Manager под Linux, macOS и BSD. Но сегодня случай особый. На прошлых выходных я обещал вам рассказать про f4 — написанный с нуля клон far2l на языке Go.

Сегодня состоялся релиз первой публичной альфа-версии 0.1.1-alpha. В этой статье я расскажу, как я пришел к идее переписать легендарный файловый менеджер, почему выбрал Go, как в этом помогли нейросети и почему современному консольному приложению не обязательно страдать от «наследия предков».

Диалог копирования f4. Выпадающее меню позволяет выбрать, копировать отдельым потоком или добавлять задачу в общую очередь файловых операций с оптимизацией под многопоточность.

Диалог копирования f4. Выпадающее меню позволяет выбрать, копировать отдельым потоком или добавлять задачу в общую очередь файловых операций с оптимизацией под многопоточность.

Сразу спойлер: что под капотом f4?

Прежде чем углубляться в историю, давайте сразу обозначим, ради чего всё это затевалось. Чем f4 принципиально отличается от своего прародителя?

  1. Тотальная асинхронность. Пользовательский интерфейс больше не «фризится» при чтении директории по медленному SFTP. Вы можете запустить тяжелую операцию и продолжать работу в панелях. Есть и выстраивание операций в очередь с управленим числом потоков на ресурс для максимальной эффективностью IO, как в менеджерах загрузок.

  2. Out-of-process плагины (RPC). Никакой боли с бинарной совместимостью и C++ ABI. Плагины общаются с ядром через stdin/stdout по протоколу MessagePack. Пишите плагины на Python, Rust, Node.js или Lua — если язык умеет писать в консоль, он может расширять f4. Экосистема превыше всего!

  3. FISH+ (WIP). Эволюция удаленного управления файлами. Вместо скачивания файла для поиска по нему, f4 будет слать команду на сервер, чтобы он нашёл сам. Это не только спасает на медленных линках, но и решает проблему WSL: можно запустить f4 в Linux-контейнере и управлять файлами хостовой Windows с нативной скоростью!

  4. Собственный UI-фреймворк (vtui). Написанная с нуля библиотека компонентов в духе классического Turbo Vision, но изначально асинхронный, с поддержкой современных протоколов ввода (kitty keyboard, win32-input-mode) и Zero-allocation рендерингом.

Демонстрационное приложение фреймворка vtui, написанного специально для f4.

Демонстрационное приложение фреймворка vtui, написанного специально для f4.

Как я пришёл к переписыванию Far’а

Работая над far2l, я часто ловил себя на мысли, что узкое место разработки консольных файловых менеджеров — это не процессорные такты, а ввод и вывод. И поэтому ручное управление памятью в C/C++ скорее отнимает ресурсы разработчика, которые могли бы уйти на оптимизацию I/O, чем приносит пользу за счёт собственной эффективности. И, конечно, очень сильно ограничивала развитие синхронная природа старого кода: фоновое копирование в far2l на самом деле есть, но из-за синхронного ядра оно реализовано так, что вы скорее всего даже не знаете, что оно там есть, и как его найти.

Когда-то давно, только начав изучать программирование, я после BASIC’а и Turbo Pascal’я сразу прыгнул в Ассемблер. Долго не мог понять: зачем вообще нужен промежуточный язык между Паскалем и ассемблером, который даёт бинарник той же эффективности что и Паскаль, но при этом предоставляет куда больше способов прострелить себе ногу, включая самые изощрённые. Со временем, конечно, я в той или иной степени смирился (думаю, как многие из нас), но интуитивное чувство, что интерфейс должен писаться проще, меня не покидало. И в особенности потому что я избалован высокоуровневыми языками как php, и мне не очень интересно тратить время на отслеживание утечек памяти в коде отрисовки выпадающего меню.

Кстати, оригинальный Far 2 поддерживал плагины и а Pascal/Delphi, и это было круто (и у меня как-то даже получилось завести это и в far2l). Но Pascal сейчас не в моде, а разработчиков, знакомых с ним — мало (к слову, на мой взгляд, причина провала — то, что дефолтные строки не в UTF8 и только это).

Были и другие проблемы, фундаментально неразрешимы на базе существующего кода:

Зависимости и дистрибуция. Ещё одна боль. Мы слишком долго ждали, пока far2l возьмут в Debian, и так и не дождались пакета в официальной Fedora. Зависимость от версий libc, wxWidgets, glib — всё это делало дистрибуцию сложной. Я хотел, чтобы файловый менеджер в Linux работал как Telegram, Tor Browser или оригинальный Far в Windows: скачал один бинарник, положил куда угодно, и он просто работает, независимо от того, Ubuntu у тебя, Arch, старый CentOS или вообще Gentoo. А ведь ещё есть плагины! Сейчас пользователю плагина надо самому подсовывать его исходники в дерево far2l, куда такое годится?

Плагины, включенные в состав far2l. О существовании других никто не знает, потому что нет организованной системы дистрибьюции.

Плагины, включенные в состав far2l. О существовании других никто не знает, потому что нет организованной системы дистрибьюции.

Плагины и экосистема. Сам я особо не пользовался плагинами Far не «из коробки» на винде — всего хватало и так, но всегда понимал их важность. При этом заставлять людей учить отдельный язык, Lua, только ради написания плагина — на мой взгляд, утопия. И тут я вспомнил гениальную мысль создателя OpenStreetMap (OSM): «Дайте людям простой инструмент здесь и сейчас, и занимайтесь сообществом» (они там годами шлифовали идеальную модель данных при нуле контрибьютеров, а он дал людям точку, линию и полигон, с возможностью привязывать к ним пары ключ-значение, и всё поехало). Хотелось дать людям универсальный штепсель, куда можно будет цепляться хоть из Питона, хоть из бинарного кода. Таким штепселем стал бинарный RPC интерфейс через stdin/stdout.

Обратное портирование и ИИ. Наконец, у меня была давняя мечта — портировать far2l обратно на Windows (звучит как шутка, вообще-то получить единую базу кода для всех платформ было бы очень неплохо, и это не что-то неосуществимое, в cygwin мы даже когда-то работали). Проблема в том, что внутри far2l есть прослойка WinPort, которая эмулирует (частично подмножество, а частично надмножество) Windows API под Linux. Когда я пробовал натравливать на этот код нейросети для обратного портирования, они сходили с ума, путаясь между WinPort и реальным WinAPI, куда я пытался WinPort транслировать. С человеком, скорее всего, случилось бы то же самое.

Одновременно с этим главный мейнтейнер far2l как-то обмолвился о зашкаливающей цикломатической сложности некоторых старых участков кода. Прекрасно помню, чего стоило добавить в редактор поддержку переноса по словам. Сказать, что этот код запутан, значит, ничего не сказать! И я подумал: а не проще ли уже начать понемногу параллельно переписывать это всё с нуля, сразу правильно? С асинхронностью, отдельным удобным UI-фреймворком и на современном языке.

Почему именно Go?

Итак, было принято решение разделить проект на две части: мощную UI-библиотеку (по смыслу — асинхронного наследника Turbo Vision) и сам файловый менеджер на её основе. Перебрал несколько вариантов:

  • Rust: Да, он быстрый. Но цена владения (borrow checker) при написании UI-деревьев с перекрестными ссылками (фокус, паренты, линки) слишком высока. Нейросети в нём тоже иногда откровенно плывут. Код UI не должен быть сложным в разработке и генерации.

  • Node.js / Python: Была безумная мысль написать или взять готовый плюсовый UI движок (как современный форк того же Turbo Vision) и сделать к нему биндинги из JS или Python. Но биндинги — это всегда хрупко. А ещё Python отпал из-за GIL (прощай, нормальная асинхронность) и кошмара с дистрибуцией (pip, например, на современных Ubuntu ругается на externally managed среду, и это не даёт установить хоть что-нибудь сложнее тривиального). Тащить с каждым плагином свой VirtualEnv? Нет, спасибо. К тому же, для тулзы, которая открывается по 100 раз на дню, время старта скриптовых виртуальных машин слишком велико.

Go подошел идеально. Он компилируется в статически слинкованный бинарник, решая проблему дистрибуции. У него есть сборщик мусора, так что мне не придётся тратить время на отлов сегфолтов в UI. У Go мощнейшая встроенная поддержка многопоточности, горутины и каналы. За ним стоит огромная корпорация и развитая экосистема, так что мне не пришлось писать с нуля алгоритмы ZIP, TAR или крипотграфию для SFTP. И современные нейросети пишут на Go на очень приличном уровне!

Архитектура и магия скорости

Бытует заблуждение, что Go — медленный язык из-за сборщика мусора. Это просто не так: ограничение не в языке, а в стиле разработки. Уже сейчас, в состоянии альфы, f4 субъективно ощущается отзывчивее, чем far2l на C++.

В чём магия? Я исходил всё из той же идеи, что узкое место консольного файлового менеджера — это ввод-вывод, а не отрисовка кнопок. Поэтому как раз ввод-вывод в f4 оптимизирован маниакально. Например,

  • Zero-allocation рендеринг: Фреймворк vtui использует систему двойной буферизации. При вызове Flush() он побитово сравнивает логический экран с теневым буфером терминала и генерирует минимальные ANSI-последовательности для отрисовки только изменений, без единой аллокации памяти в куче. Сборщик мусора просто отдыхает, пока вы быстро скроллите список файлов.

  • PieceTable в Редакторе: Вместо загрузки файла в память целиком, редактор f4 использует структуру данных Piece Table, натравленную на асинхронный VFS-буфер. В результате f4 открывает гигабайтные логи по SSH мгновенно, не выедая ОЗУ, и позволяет мгновенно прыгать в конец файла.

  • Event Draining: Обработка ввода (например, вставку больших кусков текста в терминал в режиме Bracketed Paste) собирается в пакеты, и экран перерисовывается только один раз в конце.

Редактор прекрсно справляется со случайным 80-мегабитным бинарником.

Редактор прекрсно справляется со случайным 80-мегабитным бинарником.

В скомпилированном виде без отладочной информации f4 весит около 15 Мб и жмется UPX’ом до 4-5. По меркам 2025 года это ничто, субъективно запускается он мгновенно.

Минусы

15 Мб отлично для десктопа или виртуалки, но даже 5 многовато для копеечного роутера с 16 Мб флеша, там far2l или mc чувствуют себя лучше. Эту проблему можно обойти тремя способами:

  1. Выкинуть зависимости. Большую часть веса в f4 дают библиотеки криптографии (SSH/SFTP) и архиваторы. Если сделать им легковесные аналоги, где вместо встроенных библиотек плагины используются обертки над внешними утилитами (ziptarssh и т.д.), бинарник похудеет до 5-6 Мб, что уже будет сопоставимо с весом конкурентов.

  2. Использовать FISH+. Зачем вообще тащить тяжелый комбайн на роутер? Запускаем f4 на рабочей машине, а к роутеру цепляемся по SSH. Согласно протоколу FISH+ на роутер закидывается крошечный шелл-скрипт (или микробинарник), который берет на себя рекурсивный подсчет размеров, поиск файлов сервером, парсинг директорий и т.д. — всё то, что удаленно делать долго, будет делаться на стороне SSH сервера. Это же решает и проблему WSL — работая в f4 внутри виртуалки, вы сможете мгновенно и без тормозов управлять файлами хостовой Windows, если там есть OpenSSH сервер, который давно ставится одним кликом.

  3. А почему бы не допилить mc? Можно сделать (и я подумываю как-нибудь заняться этим) форк Midnight Commander, добавив туда привычные фаровские шорткаты (хотя бы по галке, но вообще-то, пользователей Far на Windows в мире всё же несоизмеримо больше) и поддержку современных терминальных протоколов (kitty, win32im, osc52, bracketed paste). Это закроет нишу embedded окончательно.

mc, работающий во встроенном терминале f4. Добавить передачу всех сочетаний клавиш и буфера обмена между ними — и станет хорошо!

mc, работающий во встроенном терминале f4. Добавить передачу всех сочетаний клавиш и буфера обмена между ними — и станет хорошо!

f4 и far2l

Сразу отвечу на уже интересовавших многих вопрос: я не отказываюсь от разработки far2l полностью в пользу f4. Буквально недавно занимался для far2l тестами переноса по словам в редакторе.

f4 — это экспериментальный полигон. Здесь можно пробовать то, что долго тащить в legacy C++ код или когда страшно что-нибудь сломать. У f4 параноидальное покрытие тестами — экспериментируйте на здоровье, что-то здесь сломать будет очень непросто! А если фича приживётся, её можно будет перенести и в far2l. Например, реализованную в f4 идею навигации по элементам диалога только с помощью обычных стрелок (а не только Tab) уже просили перенести в старшего брата.

И, конечно, f4 полностью понимает far2l terminal extensions. Он идеально работает внутри встроенного терминала far2l (с общим буфером обмена и всеми хоткеями), и наоборот — far2l прекрасно работает в терминале f4.

Более того, архитектура f4 вовсю заимствует самые удачные идеи своего предка. Например, как и в far2l, в f4 реализован нативный графический бэкенд для X11. Да, консольный файловый менеджер на Go может рисовать себя напрямую в иксовое окно, минуя эмуляторы терминала. Зачем? Потому что терминалы обожают перехватывать нужные нам сочетания клавиш.

При отрисовке в иксы символы рисования рамок приходится рендерить кастомно, чтоб не было дырок в рамках из-за сглаживания шрифтов. Для части символов кастомную отрисовку ещё предстоит добавить.

При отрисовке в иксы символы рисования рамок приходится рендерить кастомно, чтоб не было дырок в рамках из-за сглаживания шрифтов. Для части символов кастомную отрисовку ещё предстоит добавить.

Приглашаю к тестированию!

Проект находится в стадии alpha. Базовый функционал уже работает стабильно: панели, архивы (через встроенный плагин), SFTP/FTP (тоже встроенный плагин NetFox), редактор, вьювер, встроенный терминал с поддержкой PTY.

Если вы любите консоль, соскучились по Фару или просто хотите посмотреть, на что способны современные TUI на Go — добро пожаловать!

Скачать готовые бинарники для Windows, macOS и Linux (включая ARM) можно прямо на главной странице репозитория:
https://github.com/unxed/f4

Буду рад вашим звездочкам, баг-репортам и идеям в Issues. А если вы знаете Go или хотите написать свой RPC-плагин — в репозитории есть готовый SDK и пример dummy_rpc. И, пожалуйста, поделитесь опытом знакомства в f4 в комментариях!

Есть и пасхалочка 🙂

ссылка на оригинал статьи https://habr.com/ru/articles/1030444/