telescope-gist: GitHub Gists должны жить в Telescope
Собрал Neovim-плагин для GitHub Gists: список через Telescope, preview с подсветкой по filetype, открытие многофайловых gist как редактируемых буферов, push обратно по :w, создание из visual selection, удаление, копирование URL и быстрый двухслойный кеш.
Я использую GitHub Gists для вещей, которым не нужен отдельный репозиторий, но которые жалко потерять: маленькие скрипты, куски отладочных сессий, фрагменты конфигов, заметки, которые нужны на разных машинах. Это странная зона между «буфером обмена» и «проектом».
Бесило место, где они живут: браузер.
Когда я уже в Neovim, открыть GitHub, найти gist, скопировать файл, вставить в буфер, поправить, потом вернуться в браузер и сохранить новую версию — это слишком много переключения контекста для действия, которое должно быть обычным picker action.
Так появился telescope-gist — расширение для Telescope, которое позволяет управлять GitHub Gists прямо из Neovim.
Плагин написан под мой LazyVim-сетап, но не зависит от LazyVim: это обычное
Telescope extension. Без отдельного UI-фреймворка, без запроса GitHub-токена,
без новой системы авторизации. Если у вас уже работает gh auth login, плагин
использует это.
Демо
Чего не хватало
Плагины вокруг GitHub для Neovim уже есть, но ни один вариант не попал в нужный мне workflow:
gh.nvimзанимается репозиториями и issues, а не Gists.gist.nvimумеет листать и открывать Gists, но редактирование и удаление — не тот flow, который мне хотелось.telescope-github.nvimпоказывает Gists, но preview без нормальной подсветки, да и проект почти не двигается.
Планка была простая: Gists должны ощущаться как файлы в Telescope.
:Telescope gist listS init.lua 1 file 2h ago lazy.nvim experimentP notes.md 3 files 1d ago terminal notesS docker-compose.yml 1 file 3w ago throwaway serviceДальше всё на привычных действиях:
<CR>открывает выбранный gist для редактирования.:wотправляет буфер обратно на GitHub черезPATCH /gists/<id>.<C-d>удаляет gist после подтверждения.<C-n>создаёт новый gist из текущего буфера.<C-y>копирует URL.<C-r>принудительно обновляет список, если remote поменялся.
Visual mode тоже поддержан: выделяете строки, запускаете :'<,'>GistCreate, и
в gist уезжает только выделение.
Самое интересное — редактирование
Показать список Gists несложно. Сделать preview тоже. Опасная часть начинается там, где editor plugin получает право писать в remote.
telescope-gist открывает файлы как буферы с именами такого вида:
gist://<gist-id>/<filename>Это даёт каждому удалённому файлу стабильную идентичность, но не притворяется,
что файл лежит на локальном диске. У буфера стоит buftype=acwrite, поэтому
обычный :w не пытается записать gist://... через filesystem. Вместо этого
Neovim вызывает BufWriteCmd, а плагин берёт содержимое текущего буфера и
отправляет его в GitHub API через gh.
Именно эта мелочь делает фичу нативной: не нужно помнить отдельную команду
«сохранить gist». Я редактирую текст, жму :w, remote-версия меняется.
Многофайловые Gists открываются как отдельный буфер на каждый файл, в алфавитном порядке. Первый буфер показывается сразу; остальные уже есть в списке буферов, так что дальше работает обычная навигация Neovim. Если буфер уже открыт и в нём есть несохранённые локальные правки, повторное открытие gist не перезапишет его содержимое. Remote sync подождёт; локальная работа важнее, пока я сам её не сохраню или не выброшу.
Guard rail, без которого нельзя
GitHub возвращает содержимое файлов прямо в ответе gist API, но только примерно
до 1 MB на файл. Более крупные файлы помечаются как truncated, а полные bytes
нужно отдельно забирать по raw_url.
Для read-only preview это просто неприятно. Для редактирования — ловушка с
потерей данных: если открыть обрезанный prefix и разрешить :w, можно
перезаписать полный remote-файл только тем куском, который GitHub вернул в API.
Поэтому v0.1 отказывается пушить truncated-файлы:
telescope-gist: refusing to push truncated file (>1MB). Use `gh gist edit ...` directly.Это менее красиво, чем сразу скачивать raw-файл и давать полноценный edit mode, зато это правильное скучное поведение. Плагин, который один раз потерял gist, я больше не открою.
Путь для v0.2 очевиден: идти по raw_url, загружать полный файл и только после
этого включать редактирование. До тех пор отказ лучше порчи данных.
Быстро настолько, чтобы не замечать
Первая версия могла бы вызывать gh api /gists при каждом открытии picker. Это
просто, но тогда расширение постоянно ощущалось бы как remote-интерфейс.
Вместо этого там двухслойный кеш:
- in-memory кеш на текущую сессию Neovim;
- JSON-кеш на диске в
stdpath("cache") .. "/telescope-gist"с TTL.
Первое открытие picker платит за сеть и subprocess. После этого список
открывается мгновенно, даже после рестарта Neovim. Мутирующие действия
обновляют кеш точечно: delete удаляет одну строку, create добавляет новую
строку сверху, edit обновляет cached content конкретного gist и двигает
updated_at в списке.
Кеш намеренно скучный: один JSON-файл на ключ, schema version, атомарная запись через временный файл и rename. Без базы данных. Без общего state-файла, который каждое действие должно читать, менять и записывать обратно.
Почему сначала gh
Сейчас плагин использует gh api, а не ходит в GitHub напрямую из Lua. Это не
техническое ограничение, а продуктовый выбор.
gh в v0.1 даёт три вещи:
- не нужен auth UI;
- не нужны инструкции про personal access token;
- меньше мест, где можно неправильно обработать edge cases GitHub API.
Если gh auth login работает в терминале, telescope-gist работает в Neovim.
Для первого запуска это важнее, чем экономия одного subprocess.
Код уже разложен так, чтобы потом спокойно поменять backend. Весь GitHub I/O
живёт в lua/telescope-gist/gh.lua, а остальные модули работают с формой
данных, похожей на REST API. В v0.2 можно оставить gh только как auth
bootstrap (gh auth token) и перейти на прямые REST/GraphQL-запросы через
plenary.curl.
Выигрыш понятный: ETag, If-None-Match, отсутствие subprocess spawn и,
возможно, один GraphQL round-trip для списка плюс содержимого первого файла. Но
это не стоит того, чтобы первый релиз просил пользователя вставлять токен в
конфиг.
Установка
Для lazy.nvim или LazyVim:
{ "antlis/telescope-gist", dependencies = { { "nvim-telescope/telescope.nvim", cmd = "Telescope", dependencies = { "nvim-lua/plenary.nvim" }, }, }, config = function() require("telescope-gist").setup({}) require("telescope").load_extension("gist") end, keys = { { "<leader>gG", "<cmd>Telescope gist list<cr>", desc = "Gist List" }, { "<leader>gn", ":GistCreate<CR>", desc = "Create Gist", mode = { "n", "v" } }, },}Требования маленькие: Neovim 0.10+, Telescope, plenary и авторизованный gh
CLI.
Попробовать
:Telescope gist list:GistCreate:'<,'>GistCreateИсходники здесь: github.com/antlis/telescope-gist.
Плагин под MIT, достаточно маленький, чтобы прочитать за один присест, и собран вокруг простого workflow: найти gist, посмотреть preview с подсветкой, отредактировать как буфер, сохранить как буфер и вернуться к задаче, ради которой я вообще открыл Neovim.