adriancmiranda/glimpse.nvim

github github
file-explorer
stars 6
issues 5
subscribers 0
forks 0
CREATED

UPDATED


glimpse.nvim

Fast multi-format file previewer with inline Kitty graphics support, external pane previews, and integrations for file explorers and pickers.

https://github.com/user-attachments/assets/686e39aa-1fa9-4a79-8a07-70ce5d4062bb

Features

  • 🖼️ Inline rendering via Kitty Graphics Protocol (Kitty, Ghostty)
  • 🎬 Inline video playback via Kitty Animation Protocol (play/pause, seek); thumbnail fallback for other terminals
  • 🧊 3D model preview via f3d, with Blender fallback for .blend files and configurable turntable animation
  • 📝 Markdown preview via a configurable CLI renderer (leaf, glow, mdcat, pandoc) with full ANSI color support
  • 📊 Diagram preview - PlantUML (.puml) and Mermaid (.mmd) rendered as inline images
  • 📦 Archive preview - list contents of zip/tar without extraction
  • 🗄️ SQLite preview - show tables and columns without modifying the database
  • 💾 Binary preview - detect binaries with file and show a short xxd hexdump
  • 🪟 External pane via WezTerm CLI, kitten icat, iTerm imgcat
  • 🎨 Font rendering via ImageMagick, with a textual fallback when rendering is unavailable
  • 🎨 Sixel fallback for terminals without Kitty Graphics support
  • 📂 Oil.nvim integration (<leader>p for preview, ; to open)
  • 🔭 Telescope integration (scoped previews for images, videos, archives, SQLite, fonts, keys, certificates, and binaries)
  • 🌳 Neo-tree integration
  • 🔐 Certificate preview - show subject, issuer, validity, and fingerprint for .crt/.pem
  • ⚡ Image conversion cache + background prefetch
  • 🔄 Auto re-render on window resize or tab switch
  • 💾 Auto-refresh on save - re-render open previews when the source file is saved
  • 📐 Contain resize (images always fully visible)

Requirements

  • Neovim >= 0.10
  • ImageMagick (magick CLI) - inline image conversion and font rendering
  • ffmpeg (optional) - video thumbnail extraction
  • f3d (optional) - 3D model thumbnails and turntable frames
  • Blender (optional) - fallback renderer for .blend files
  • plantuml (optional) - PlantUML diagram rendering (requires Java)
  • mmdc (optional) - Mermaid diagram rendering
  • One of leaf, glow, mdcat, or pandoc (optional) - Markdown rendering
  • OpenSSL (openssl CLI) - certificate metadata extraction
  • file (file CLI) - binary type detection
  • xxd (xxd CLI) - binary hexdump rendering
  • Terminal with support for at least one protocol:
    • Kitty Graphics (recommended): Kitty, Ghostty
    • Terminal CLI: WezTerm, iTerm2
    • Sixel: xterm, foot, mlterm, contour

[!NOTE] magick is required for inline image rendering and font rendering. Font preview still has a textual fallback if rendering is unavailable.

Feature Dependencies

Feature Dependency Required
Inline image preview magick Yes
Font rendering magick Yes, with textual fallback if unavailable
Video preview ffmpeg No
3D model preview f3d, with optional blender fallback for .blend No
Archive preview zipinfo, tar Yes when the archive type is used
SQLite preview sqlite3 Yes when the SQLite preview is used
Certificate preview openssl Yes when the certificate preview is used
Binary preview file, xxd Yes when the binary preview is used
SSH/GPG key preview ssh-keygen, gpg Yes when the key preview is used
Markdown preview leaf, glow, mdcat, pandoc, or cat First executable found wins
PlantUML preview plantuml + Java runtime Yes when the PlantUML previewer is used
Mermaid preview mmdc Yes when the Mermaid previewer is used

Installing dependencies

[!NOTE] Binary preview depends on file and xxd. They are usually available on Unix-like systems, but if your OS does not ship them by default, install them separately.

macOS (Homebrew)

brew install imagemagick ffmpeg f3d

Linux (apt)

sudo apt install imagemagick ffmpeg

Linux (pacman)

sudo pacman -S imagemagick ffmpeg

Verify installation

magick --version
f3d --version
file --version
xxd -h

Usage

Setup (lazy.nvim)

{
  'adriancmiranda/glimpse.nvim',
  ft = { 'oil', 'neo-tree' },
  opts = {},
}

The setup above loads Glimpse when entering a supported file explorer. If auto_open = true, add event = 'BufReadPre' to the plugin spec so Glimpse can register its automatic preview handler before the file finishes loading. This intentionally applies to every file instead of duplicating the configurable format list in the lazy-loading specification. keys.preview configures the mapping installed by the file explorer integration; it is not a lazy.nvim loading trigger.

{
  strategy = 'auto',
  auto_open = false,
  auto_refresh = false,
  pane = {
    position = 'right',
    size = 40,
  },
  inline = {
    rerender_on_tab = true,
    close_with_q = true,
  },
  float = {
    markdown = { width = 100 },
    archive = { width = 70 },
    cert = { width = 90 },
    font = { width = 60 },
    key = { width = 70 },
    sqlite = { width = 80 },
    binary = { width = 100 },
  },
  keys = {
    preview = '<leader>p',
    open = ';',
    close = 'q',
  },
  debounce = {
    prefetch = 200,
    resize = 100,
  },
  cell_size = {
    width = 20,
    height = 40,
  },
  cache = {
    dir = vim.fn.stdpath('cache') .. '/glimpse',
    max_age_days = 7,
  },
  safety = {
    max_file_size = 50 * 1024 * 1024,
  },
  loading = {
    text = '  ⏳ Loading...',
  },
  image = {
    formats = {
      '.png', '.jpg', '.jpeg', '.gif', '.bmp',
      '.webp', '.avif', '.svg', '.pdf', '.pict',
    },
  },
  video = {
    formats = {
      '.mp4', '.mkv', '.avi', '.mov',
      '.webm', '.flv', '.wmv', '.m4v',
    },
    open = nil,
    frames = {
      strategy = 'auto',
      per_second = 10,
      limit = 120,
      width = 640,
    },
    keys = {
      toggle = '<CR>',
      seek_forward = 'l',
      seek_backward = 'h',
    },
  },
  markdown = {
    formats = { '.md', '.markdown', '.mdx', '.mdwn', '.mdown' },
    tools = {
      { 'leaf', '--inline', 'ansi:{width}', '{input}' },
      { 'glow', '-s', 'dark', '--width', '{width}', '{input}' },
      { 'mdcat', '{input}' },
      { 'pandoc', '--to', 'plain', '--wrap', 'none', '{input}' },
      { 'cat', '{input}' },
    },
  },
  pipelines = {
    plantuml = {
      steps = {
        {
          command = 'sh',
          args = function(input, output)
            return { '-c', 'plantuml -tpng -pipe < "$1" > "$2"', '--', input, output }
          end,
        },
      },
    },
    mermaid = {
      steps = {
        {
          command = 'mmdc',
          args = function(input, output)
            return { '-i', input, '-o', output }
          end,
        },
      },
    },
    model = {
      steps = {
        {
          command = 'f3d',
          output_ext = '.png',
          args = function(input, output)
            return { input, '--output', output, '--config=thumbnail' }
          end,
        },
      },
      renderer = {
        fps = 12,
        progressive = true,
      },
      keys = {
        toggle = '<CR>',
        seek_forward = 'l',
        seek_backward = 'h',
      },
    },
    ['.blend'] = {
      previewers = {
        {
          command = 'f3d',
          output_ext = '.png',
          args = function(input, output)
            return { input, '--output', output, '--config=thumbnail' }
          end,
        },
        {
          command = 'blender',
          output_ext = '.png',
          args = function(input, output)
            return {
              '--background', input,
              '--render-output', output:gsub('%.png$', '_'),
              '--render-format', 'PNG',
              '--render-frame', '1',
            }
          end,
          output_pattern = function(output)
            return output:gsub('%.png$', '_0001.png')
          end,
        },
      },
    },
  },
  archive = {
    formats = {
      '.zip', '.tar', '.tar.gz', '.tgz',
      '.tar.bz2', '.tar.xz', '.txz',
      '.jar', '.war', '.apk',
    },
  },
  integrations = {
    oil = {
      enable = true,
      open = 'edit',
      follow_cwd = true,
    },
    neotree = {
      enable = false,
      auto_preview = true,
    },
    telescope = {
      enable = true,
      pickers = { 'find_files' },
      follow_cwd = false,
      image = true,
      video = true,
      archive = true,
      sqlite = true,
      font = true,
      cert = true,
      key = true,
      binary = true,
    },
  },
}

Automatic opening

Set auto_open = true to render non-text previewable files opened directly with Neovim or selected normally in a file explorer. Native text formats such as Markdown and PlantUML remain editable; use the preview key to render them.

Auto-refresh on save

Set auto_refresh = true to re-render open previews whenever the source file is saved. Supports Markdown floats and pipeline-based previewers (PlantUML, Mermaid, 3D models). The refresh only runs while the preview window is still open; closing the preview stops the hook automatically.

Explicit preview command

Use :GlimpsePreview to preview the current buffer without depending on a file explorer integration:

:GlimpsePreview
:GlimpsePreview README.md
:GlimpsePreview %
:GlimpsePreview ~/models/scene.blend

The optional path supports file completion and expands relative paths, ~, and %. Glimpse currently selects the preview destination automatically according to the file type.

An optional [window] argument controls where the preview opens:

:GlimpsePreview right
:GlimpsePreview left README.md
:GlimpsePreview bottom ~/models/scene.blend

Valid values are float (default), right, left, bottom, and top.

Cache management

:GlimpseClearCache removes cached conversions so the next preview re-runs from scratch. Useful when a file has changed on disk but the cached version is stale, or to free up disk space.

:GlimpseClearCache           " clear everything
:GlimpseClearCache images    " image conversions only (disk + memory)
:GlimpseClearCache previews  " text preview cache only (markdown, sqlite, etc.)

Float sizing

float.width and float.height override every floating preview. A preview-specific entry such as float.markdown takes precedence over the global value. Numeric widths use that many columns and numeric heights cap the content height. width = 'auto' fills the available editor width; height = 'auto' fits the content within the available editor height. Float margins are always respected. Without overrides, the built-in sizes remain unchanged, except Markdown now defaults to 100 columns.

3D model turntable

The default pipeline renders a static thumbnail with f3d. To generate a progressive turntable animation instead, use a sequence step:

require('glimpse').setup({
  pipelines = {
    model = {
      steps = {
        {
          command = 'f3d',
          type = 'sequence',
          frames = 36,
          args = function(input, output, frame)
            return {
              input, '--output', output,
              '--config=thumbnail',
              '--camera-azimuth-angle=' .. (frame * 10),
            }
          end,
        },
      },
      renderer = { fps = 12, progressive = true },
    },
  },
})

.blend files try f3d first, then Blender when f3d is unavailable or fails. Blender renders frame 1 with the camera and render settings stored in the scene.

steps form a sequential conversion chain: each output becomes the next step's input. previewers are alternatives: Glimpse tries them in order, skipping missing commands and continuing after failed commands until one produces a preview. An extension-specific entry such as pipelines['.blend'] takes precedence over the type-level entry such as pipelines.model.

require('glimpse').setup({
  pipelines = {
    model = {
      previewers = {
        {
          command = 'first-model-renderer',
          args = { '{input}', '--output', '{output}' },
        },
        {
          command = 'fallback-model-renderer',
          args = { '{input}', '--output', '{output}' },
        },
      },
    },
  },
})

Keymaps (Oil.nvim)

Key Action
<leader>p Preview image/video side by side (reuses window)
; Open image (configurable: current tab or new tab)
q Close image buffer and residual empty window

When an image is opened, the current window follows that file's directory, so Oil.nvim opens in the same folder.

-- Toggle open/close
vim.keymap.set('n', '<leader>e', function()
  require('glimpse.integrations.oil').toggle_float()
end)

-- Always open (useful if you handle close separately via Oil's own keymap)
vim.keymap.set('n', '<leader>E', function()
  require('glimpse.integrations.oil').open_float()
end)

Both ensure the float opens at the directory Oil is currently browsing, even when the cursor is on an image buffer. To close, use Oil's built-in close keymap (default q inside the float).

Keymaps (Neo-tree)

Enable with integrations = { neotree = { enable = true } } in setup.

Key Action
<leader>p Preview image/video side by side
; Open image inline or video with external player

Telescope

Enable with integrations = { telescope = { enable = true } } in setup. With lazy.nvim, glimpse applies its previewer when telescope.nvim loads. By default, only :Telescope find_files receives Glimpse previews.

require('glimpse').setup({
  integrations = {
    telescope = {
      enable = true,
    },
  },
})

To choose the Telescope pickers that receive Glimpse previews:

require('glimpse').setup({
  integrations = {
    telescope = {
      enable = true,
      pickers = { 'find_files', 'git_files' },
    },
  },
})

To disable specific preview kinds inside Telescope while keeping the integration enabled:

require('glimpse').setup({
  integrations = {
    telescope = {
      enable = true,
      pickers = { 'find_files', 'git_files' },
      image = true,
      video = true,
      archive = true,
      sqlite = false,
      font = false,
      cert = true,
      key = true,
      binary = false,
    },
  },
})

If you prefer configuring Telescope manually, use the exported previewer:

require('telescope').setup({
  pickers = {
    find_files = {
      previewer = require('glimpse.integrations.telescope').previewer(),
    },
  },
})
  • Images are rendered inline via Kitty Graphics Protocol with a 100ms debounce
  • Videos extract a thumbnail via ffmpeg before rendering
  • Archives, SQLite databases, fonts, keys, certificates, and binaries use the matching Glimpse previewer inside the Telescope preview pane
  • Other files fall back to Telescope's default previewer

Switching between files is fast after thumbnails and conversions are cached.

API

local img = require('glimpse')

img.can_preview(filepath)    -- check whether Glimpse knows how to preview it
img.get_preview_kind(filepath) -- return the preview kind that would be used
img.show(filepath)           -- show image or video thumbnail
img.preview(filepath)        -- show reusing existing window
img.close()                  -- close active preview
img.is_image(filepath)       -- check if supported image
img.is_video(filepath)       -- check if supported video
img.is_archive(filepath)     -- check if supported archive
img.is_sqlite(filepath)      -- check if supported SQLite database
img.is_previewable(filepath) -- check if supported previewable file
img.is_cert(filepath)        -- check if supported certificate
img.is_font(filepath)        -- check if supported font
img.is_key(filepath)         -- check if supported GPG/SSH key
img.get_terminal()           -- return detected terminal
img.supports_inline()        -- check whether inline rendering is supported
img.in_tmux()                -- check whether Neovim runs inside tmux

Security & Privacy

glimpse.nvim runs only local commands on files you explicitly select. It never makes network requests or sends data externally.

[!IMPORTANT] The plugin only uses local tools on files you explicitly select. When a tool is missing, the affected previewer fails safely instead of breaking the rest of the plugin.

File validation

  • Symlinks are rejected (prevents reading unintended targets)
  • Large files above safety.max_file_size are skipped (default: 50MB)
  • SVG files are processed with restricted XML parsing (no entity expansion, no external resources)
  • Archive preview never extracts files, only reads metadata
  • Shell commands use list arguments (no shell interpolation)

External tools used

Tool Purpose When
magick (ImageMagick) Image resize/conversion Image preview
ffmpeg Video thumbnail extraction Video preview
openssl X.509 certificate metadata extraction Certificate preview
zipinfo Archive listing (read-only) Archive preview
tar Archive listing (read-only) tar/tgz preview
sqlite3 Schema listing (read-only) SQLite preview
leaf / glow / mdcat / pandoc / cat Markdown rendering Markdown preview
plantuml PlantUML diagram rendering PlantUML preview
mmdc Mermaid diagram rendering Mermaid preview

No files are extracted, modified, or uploaded. All processing is local and read-only.

Optional extra protection: place this in ~/.config/ImageMagick/policy.xml.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policymap [
  <!ELEMENT policymap (policy)*>
  <!ATTLIST policymap xmlns CDATA #FIXED "">
  <!ELEMENT policy EMPTY>
  <!ATTLIST policy xmlns CDATA #FIXED "">
  <!ATTLIST policy domain NMTOKEN #REQUIRED>
  <!ATTLIST policy name NMTOKEN #IMPLIED>
  <!ATTLIST policy pattern CDATA #IMPLIED>
  <!ATTLIST policy rights NMTOKEN #IMPLIED>
  <!ATTLIST policy stealth NMTOKEN #IMPLIED>
  <!ATTLIST policy value CDATA #IMPLIED>
]>
<policymap>
  <policy domain="resource" name="memory" value="256MiB"/>
  <policy domain="resource" name="map" value="512MiB"/>
  <policy domain="resource" name="disk" value="1GiB"/>
  <policy domain="resource" name="time" value="30"/>
  <policy domain="resource" name="thread" value="2"/>
  <policy domain="resource" name="list-length" value="64"/>
  <!--
  "area" is an extra safety limit and may block very large images.
  If Glimpse starts rejecting legitimate previews, remove or
  increase it.
  -->
  <policy domain="resource" name="area" value="100MP"/>
</policymap>

Supported terminals

Terminal Strategy Method
Kitty inline Kitty Graphics + unicode placeholders
Ghostty inline Kitty Graphics + unicode placeholders
WezTerm pane wezterm cli split-pane + wezterm imgcat
iTerm2 pane imgcat
xterm/foot/mlterm pane (sixel) magick ... sixel:- via tmux

WezTerm + tmux

wezterm cli needs access to the WezTerm GUI socket. Inside tmux, the WEZTERM_UNIX_SOCKET variable can become stale if WezTerm restarts.

Add to tmux.conf:

set -ga update-environment WEZTERM_UNIX_SOCKET

If preview stops working, update manually:

# Find the active socket
ls ~/.local/share/wezterm/gui-sock-*

# Export the correct one
tmux set-environment WEZTERM_UNIX_SOCKET ~/.local/share/wezterm/gui-sock-<PID>

Supported formats

Images: PNG, JPG, JPEG, GIF, BMP, WebP, AVIF, SVG, PDF, PICT

Certificates: CRT, PEM (X.509 certificates)

Videos: MP4, MKV, AVI, MOV, WebM, FLV, WMV, M4V (requires ffmpeg)

Markdown: MD, MARKDOWN, MDX, MDWN, MDOWN (requires leaf, glow, mdcat, pandoc, or cat)

Diagrams: PUML, PLANTUML, PU, WSD, IUML (PlantUML, requires plantuml + Java), MMD, MERMAID (Mermaid, requires mmdc)

Architecture

lua/
├── glimpse/
│   ├── init.lua              -- Public API: setup(), show(), preview(), close(), helpers
│   ├── detect.lua            -- Terminal detection via tmux client_termname
│   ├── kitty.lua             -- Kitty Graphics Protocol (transmit, delete, prefetch)
│   ├── renderer.lua          -- Placement management and extmarks
│   ├── sixel.lua             -- Sixel protocol (fallback)
│   ├── thumbnail.lua         -- Video thumbnail extraction (ffmpeg, async)
│   ├── magickwand.lua        -- FFI bindings for libMagickWand
│   ├── util.lua              -- Format detection (image, video, model, diagram, markdown...)
│   ├── archive.lua           -- Archive listing and suspicious path detection
│   ├── font.lua              -- Font metadata extraction and rendering
│   ├── sqlite.lua            -- SQLite schema preview
│   ├── safety.lua            -- File validation and safety checks
│   ├── pipeline.lua          -- Steps-based conversion pipeline (run_steps, run_sequence)
│   ├── pipeline_previewer.lua -- Shared runtime for pipeline-based previewers (tokens, animation, cleanup)
│   ├── previewer/
│   │   ├── archive.lua       -- Archive previewer
│   │   ├── cert.lua          -- X.509 certificate previewer
│   │   ├── binary.lua        -- Binary previewer (file + hexdump)
│   │   ├── font.lua          -- Font previewer
│   │   ├── image.lua         -- Inline image previewer
│   │   ├── key.lua           -- GPG/SSH key previewer
│   │   ├── markdown.lua      -- Markdown previewer (leaf/glow/mdcat/pandoc, terminal buffer)
│   │   ├── mermaid.lua       -- Mermaid diagram previewer (mmdc)
│   │   ├── plantuml.lua      -- PlantUML diagram previewer (plantuml -pipe)
│   │   └── sqlite.lua        -- SQLite previewer
│   ├── strategy/
│   │   ├── inline.lua        -- Inline rendering + autocmds
│   │   └── pane.lua          -- External pane rendering (WezTerm, iTerm2)
│   └── integrations/
│       ├── oil.lua           -- Oil.nvim integration
│       ├── neotree.lua       -- Neo-tree integration (auto-preview)
│       └── telescope.lua     -- Telescope integration (scoped picker preview)

Credits

  • snacks.nvim - inspiration for the rendering protocol
  • Yazi - inspiration for performance optimizations
  • Reddit post - original WezTerm preview concept