telescope-gist: GitHub Gists должны жить в Telescope

Собрал Neovim-плагин для GitHub Gists: список через Telescope, preview с подсветкой по filetype, открытие многофайловых gist как редактируемых буферов, push обратно по :w, создание из visual selection, удаление, копирование URL и быстрый двухслойный кеш.

Neovim Telescope picker со списком GitHub Gists, редактированием и кешем

Я использую 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 list
S init.lua 1 file 2h ago lazy.nvim experiment
P notes.md 3 files 1d ago terminal notes
S 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-интерфейс.

Вместо этого там двухслойный кеш:

  1. in-memory кеш на текущую сессию Neovim;
  2. 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.