Краткое описание: Продолжаю историю CraftHub — кроссплатформенного редактора, который показывает JSON как таблицу. В прошлых частях мы разбирались с табличным редактированием и расширением для VS Code. В этот раз — самое частое пожелание из issue: «дайте работать с директорией, а не открывать файлы через диалог каждый раз». Рассказываю, как я прикрутил файловый проводник в стиле VS Code, почему сохранение в файл оказалось не таким тривиальным, и на какие грабли Avalonia наступил по дороге.
Для тех, кто пропустил предыдущие серии
CraftHub — это open-source редактор JSON, который открывает массив объектов не как «простыню» текста, а как таблицу: колонки — это поля, строки — элементы массива. Есть десктопная версия на Avalonia (Windows/macOS/Linux) и расширение для VS Code. Идея простая: перестать редактировать данные вручную в текстовом виде и ошибаться в запятых.
Проект живёт за счёт обратной связи. И самое частое, что просили в последнее время, звучало примерно так:
«Неудобно каждый раз открывать файл через диалог. Хочу видеть папку проекта и переключаться между файлами, как в VS Code».
Это стало issue про работу в директории. В этой статье — как я его закрывал.
Проблема: диалог открытия файла — это трение
Раньше цикл работы выглядел так: «Импорт JSON» → системный диалог → найти файл → открыть → поправить → «Экспорт» → снова диалог → выбрать путь. Если файлов в проекте десяток и ты прыгаешь между ними, это утомляет. Каждое переключение — несколько кликов и потеря контекста.
По сути не хватало двух вещей:
Навигации — видеть дерево файлов проекта и открывать их в один-два клика.
Нормального сохранения — чтобы
Ctrl+Sписал в тот же файл, а не спрашивал путь заново.
Звучит как «просто добавь панель слева». На практике вторая часть оказалась интереснее первой.
Решение: панель проводника в стиле VS Code
Первое — сама панель. Выбираешь корневую папку проекта, и слева появляется дерево: папки и .json-файлы. Двойной клик — файл открывается во вкладке. Есть фильтр (по умолчанию показываем только JSON, чтобы не тонуть в мусоре), поиск по файлам и кнопка показать/скрыть панель. Состояние — корневая папка, ширина, видимость — запоминается между запусками.
Ленивое дерево, чтобы не подвешивать UI
Наивная реализация — рекурсивно обойти всю папку при открытии — умрёт на большом проекте (привет, node_modules). Поэтому дерево грузится лениво, как в нормальных проводниках: у папки, в которой есть содержимое, добавляется «пустышка»-заглушка, чтобы отрисовать стрелку раскрытия, а реальные дети подгружаются только в момент разворачивания.
partial void OnIsExpandedChanged(bool value) { if (value && IsDirectory && !_childrenLoaded) LoadChildren(); // читаем диск только когда узел реально раскрыли }
Самое интересное: сохранение обратно в файл
Вот тут пришлось подумать про модель. У вкладки раньше не было понятия «я привязана к файлу на диске». Импорт просто читал содержимое, экспорт каждый раз спрашивал путь. Чтобы Ctrl+S работал как в редакторе, я ввёл у вкладки состояние «связана с файлом»:
Открыл файл (из проводника или через «Импорт») → вкладка запоминает путь.
Ctrl+Sсериализует данные и пишет в этот же файл, без диалога.Если файла ещё нет (новая вкладка) →
Ctrl+Sпредлагает «Сохранить как», по умолчанию в текущую папку проекта, и после этого вкладка привязывается к новому файлу.
Дальше пошли нюансы, без которых «сохранение» ощущается сломанным:
Индикатор несохранённого. На вкладке загорается точка ●, если данные изменены. Сначала я хотел ловить это через события коллекций строк — но они срабатывают и при импорте, и пришлось бы городить флаги «сейчас идёт загрузка». В итоге сигналом «данные поменялись» стал сам сервис Undo/Redo: раз действие попало в историю — значит, пользователь что-то изменил.
Внешние изменения. Если файл на диске поменяли другой программой (сравниваю время модификации), перед перезаписью честно спрашиваю, а не молча затираю чужие правки.
Переименование = переименование файла. Логичное следствие модели: переименовал вкладку — переименовался и файл на диске (с сохранением расширения), а дерево обновилось. Иначе получается враньё: подпись одна, файл другой.
Файловые операции: как в проводнике
Раз уж есть дерево — от него ждут поведения файлового менеджера. Добавил прямо в панель:
создание нового файла (в корне или в конкретной папке);
удаление — через контекстное меню и по клавише
Delete;копирование / вырезание / вставку файлов (
Ctrl+C/Ctrl+X/Ctrl+V);drag & drop — перетащить файл или папку в другую папку;
«показать в системном проводнике» и «скопировать путь».
Копирование-вставка и drag & drop под капотом ходят в один и тот же движок переноса: он разруливает конфликты имён (создаёт name copy.json), не даёт бросить папку саму в себя или в свою подпапку, и обновляет только те узлы дерева, которых коснулись, а не перестраивает всё целиком.
Что дальше
Дорожную карту во многом рисует не мой список, а ваши issue. Если чего-то не хватает или что-то раздражает — заходите в репозиторий и напишите. Ровно так появилась и эта фича.
GitHub: https://github.com/c3n9/CraftHub
Прошлые части — про табличное редактирование и расширение для VS Code — в моём профиле.
Спасибо, что читаете и подсказываете.
