open.nvim · Files & navigation · vimdoc

:help open

Open files, URLs, and paths from anywhere in Neovim

doc/open.txt — rendered from the plugin's own vimdoc

*open.txt*  Open files, URLs, and paths from anywhere in Neovim   *open.nvim*

Author:   Stefan Bartl
Version:  0.1.0

CONTENTS *open-contents*

  1. Introduction ................. |open-intro|
  2. Requirements ................. |open-requirements|
  3. Installation ................. |open-installation|
  4. Configuration ................ |open-config|
     4.1 Office document auto-redirect  |open-office-open|
  5. The :Open command ............ |:Open|
     5.1 Target argument .......... |open-target|
     5.2 Scope argument ........... |open-scope|
     5.3 Handlers ................. |open-handlers|
  6. Keywords ..................... |open-keywords|
     6.1 Built-in keywords ........ |open-keywords-builtin|
     6.2 User-defined keywords .... |open-keywords-user|
  7. Tab completion ............... |open-completion|
  8. The :Open viewer command ..... |:Open-viewer|
     8.1 Kind ..................... |open-viewer-kind|
     8.2 Scope .................... |open-viewer-scope|
     8.3 Options .................. |open-viewer-options|
     8.4 The picker ............... |open-viewer-picker|
     8.5 Outputs .................. |open-viewer-outputs|
  9. Lua API ...................... |open-api|
 10. Integrations ................. |open-integrations|
     10.1 urlview.nvim ............ |open-integrations-urlview|
     10.2 telescope.nvim .......... |open-integrations-telescope|
 11. Health check ................. |open-health|
 12. Architecture ................. |open-architecture|

1. INTRODUCTION *open-intro*

open.nvim provides a single :Open [target] [scope] command that routes the
thing under your cursor — path, URL, or plain text — to the right destination:
system file manager, browser (with named-browser support), GUI text editor,
terminal split rooted in the target's directory, inline image viewer (via
images.nvim, with fallback), or a Neovim split/tab.

Context-aware: knows when you are in a Neo-tree, nvim-tree, or netrw buffer
and opens the node under the cursor automatically.

Depends on lib.nvim (a deliberate shared dependency).

2. REQUIREMENTS *open-requirements*

  - Neovim 0.9 or later
  - lib.nvim
  - Platform tools as needed per handler (see |open-health|)

3. INSTALLATION *open-installation*

open.nvim only does anything once :Open is invoked, so it should be loaded
lazily on that command rather than eagerly (lazy = false) or on a UI event
(event = "VeryLazy") — those would just load the plugin sooner for no
benefit.

lazy.nvim:
  {
    "StefanBartl/open.nvim",
    cmd          = { "Open", "UrlView", "MDLinksView" },
    dependencies = { "StefanBartl/lib.nvim" },
    opts         = {},
  }
packer:
  use {
    "StefanBartl/open.nvim",
    requires = { "StefanBartl/lib.nvim" },
    cmd      = { "Open", "UrlView", "MDLinksView" },
    config   = function() require("open").setup() end,
  }

4. CONFIGURATION *open-config*

  require("open").setup({
    command             = "Open",        -- user command name
    default_filemanager = "filemanager", -- handler for paths (no explicit target)
    default_browser     = "browser",     -- handler for URLs  (no explicit target)
    handlers = {
      "filemanager",
      "browser",
      "notepad",
      "nvim_internal",
      "default",
      "terminal",
      "image",
    },
    builtin_keywords = true, -- set false to disable all built-in keywords
    keywords         = {},   -- user overrides / additions (keyword → path or fn)
    custom_handlers  = {},   -- user-defined handlers, registered alongside `handlers`
    keymaps          = {},   -- optional keymaps; none registered by default
    filemanager      = { reveal = true },  -- false: navigate instead of reveal/select
    office_open = {                        -- redirect MS Office docs to the system app
      enabled    = true,
      extensions = { "doc", "docx", "xls", "xlsx", "ppt", "pptx" },
    },
    debug            = false, -- true: log gather/resolve/dispatch steps to :messages
    picker           = { enabled = false }, -- true: show a picker on ambiguous no-target calls
  })
command
  Name of the registered user command. Change to avoid conflicts.

default_filemanager
  Handler key used when the context is path-like and no target was given.

default_browser
  Handler key used when the context looks like a URL and no target was given.

handlers
  List of handler module keys to load during setup(). Removing a key
  prevents those handlers from registering, hiding them from tab-completion.
  Valid values: "filemanager" | "browser" | "notepad" | "nvim_internal" |
  "default" | "terminal" | "image"

builtin_keywords
  Boolean (default true). When false, none of the built-in scope keywords
  (shell profiles, git, SSH, …) are loaded. See |open-keywords-builtin|.

keywords
  Table mapping keyword strings to paths. Values are either a string
  (expanded at resolution time) or a fun(): string|nil for dynamic paths.
  User entries override built-in entries with the same key.
  See |open-keywords-user|.

custom_handlers
  List of user-defined OpenNvim.Handler tables (key, desc, run),
  registered in addition to the built-in handlers modules. A key here
  overrides a built-in handler of the same name. Example:
    require("open").setup({
      custom_handlers = {
        {
          key  = "zathura",
          desc = "Open PDF in Zathura",
          run  = function(ctx)
            return require("open.util").run_detached({ "zathura", ctx.text }, "zathura")
          end,
        },
      },
    })
keymaps
  Optional keymaps for common invocations. None registered by default.
  Accepted keys come from the live handler registry, not a fixed list:
  open_default (bare :Open) plus open_<handler key> for every
  registered handler — open_browser, open_filemanager, open_split,
  open_vsplit, open_tab, open_terminal, open_image, open_notepad,
  the named browsers (open_firefox, open_chrome, ...), and any handler
  added via custom_handlers — plus the historical alias open_manager
  (= open_filemanager).

  An unrecognized key warns, names the accepted keys, and registers
  nothing. A handler switched off via opts.handlers is rejected the same
  way. Example:
    require("open").setup({
      keymaps = {
        open_default  = "<leader>oo",
        open_browser  = "<leader>ob",
        open_manager  = "<leader>of",
        open_split    = "<leader>os",
        open_terminal = "<leader>ot",
      },
    })
filemanager
  Settings for the filemanager handler. Keys:

    reveal    true (default): reveal a file (select it in its parent
              directory). false: navigate to it (open its parent directory
              without selecting). Directories are always navigated into
              regardless of this setting — there is nothing to select for a
              directory target.
    command   Launcher override: a string ("thunar") or an argv list
              ({ "dolphin", "--select" }). The resolved path is appended as
              the last argument and the platform dispatch is skipped.
              Default nil (detect per platform).

office_open
  Settings for the office-document auto-redirect (see |open-office-open|).
  Keys:

    enabled     true (default): install the BufReadCmd redirect.
    extensions  Bare extensions, no leading dot. Default:
                {"doc", "docx", "xls", "xlsx", "ppt", "pptx"}

debug
  Boolean (default false). When true, logs every context.gather(),
  context.resolve(), and registry.dispatch() step to :messages.

picker
  enabled (default false). When true, a no-target :Open/open.open()
  call whose context has more than one meaningful handler shows a picker
  (ui.kit.select, honoring any vim.ui.select override) instead
  of picking one automatically. Candidates:

    tree-buffer node          default_filemanager only (no prompt)
    looks like a URL          default_browser, notepad
    <cfile> is an existing path  default_filemanager, split, vsplit, tab
    anything else             default_filemanager only

  An explicit target always bypasses the picker.

viewer
  Settings for |:Open-viewer|. Keys:

    commands        Standalone wrapper command names, one per kind filter.
                    Set a value to false to skip registering it.
                      urls     default "UrlView"
                      mdlinks  default "MDLinksView"
                      all      default false (use :Open viewer)
    sort            Default ordering: "none" (default), "file", "kind",
                    "alpha".
    output          Default sink: "picker" (default), "table", "clipboard",
                    "mdlinks", "csv".
    mdlinks_output  Sink for out=mdlinks (default "clipboard").
    open_file       Handler used when a picked entry is a local file
                    (default "split"). Any registered handler key works.

  All of them are overridable per invocation — see |open-viewer-options|.

4.1 Office document auto-redirect *open-office-open*

.doc/.docx, .xls/.xlsx, .ppt/.pptx are binary containers — there
is nothing useful Neovim can show by reading one as text. A BufReadCmd
autocmd (see |open-architecture|, open/office_open.lua) intercepts a read
of any configured extension, hands the path to the same
lib.nvim.cross.open_default dispatch the default handler uses (Word,
Excel, PowerPoint, or whatever the OS has registered), and wipes the empty
placeholder buffer Neovim created for the read.

Unlike everything else in this plugin, this is NOT something you invoke:
BufReadCmd is Neovim's own read hook, so it fires for :e, gf, a picker
result, or a tree plugin's <CR> alike — including filetree.nvim,
nvim-tree, and neo-tree — without that caller needing to know open.nvim
exists.

Configure via opts.office_open (see |open-config|):
  require("open").setup({
    office_open = {
      enabled    = true,
      extensions = { "doc", "docx", "xls", "xlsx", "ppt", "pptx" },
    },
  })
Set enabled = false, or extensions = {}, to turn this off and get
Neovim's normal (garbled) text-buffer behavior back for these extensions.

5. THE :Open COMMAND *:Open*

  :Open [target] [scope]
Built via lib.nvim.bindings.usercmd.composer (single flat path = {} root route —
no subcommand tree), which is also what drives |open-completion|.

With no arguments, :Open uses a context-aware heuristic:
  - Current buffer is a tree (neo-tree / nvim-tree / netrw) → filemanager
  - cfile / cword looks like a URL → browser
  - Otherwise → filemanager

5.1 Target argument *open-target*

The first argument selects a handler. Any registered key is valid.

  :Open filemanager
  :Open browser
  :Open firefox
  :Open split

Omitting the target triggers the heuristic described above.

5.2 Scope argument *open-scope*

The second argument controls what text is passed to the handler.

  "%"           Current buffer's file path.
  "cfile"       <cfile> text under the cursor.
  "cwd"         Neovim's current working directory.
  "git"         Nearest Git root (nearest ancestor of the cwd with a .git).
  "path=<path>" Literal path (supports file completion after path=).
  <keyword>     Named scope alias — see |open-keywords|.
  <anything>    Any other text is used verbatim as the resolved path/URL.
  (omitted)     Target-aware heuristic:
                  PATH_TARGETS (filemanager/split/vsplit/tab):
                    cfile→disk, fallback → buffer path
                  Other targets:
                    visual selection → cWORD → buffer path

Examples using keywords:

  :Open split nvim_init        open Neovim init.lua in a horizontal split
  :Open tab   zshrc            open ~/.zshrc in a new tab
  :Open split pwsh_profile     open PowerShell $PROFILE in a split
  :Open split gitconfig        open ~/.gitconfig in a split
  :Open split MY_ROADMAP       open a user-defined keyword
  :Open filemanager git        open the current Git root in the file manager

5.3 Handlers *open-handlers*

default

  Open in the system default application — equivalent to a double-click.
  The OS picks the app based on the file extension or URL scheme:
  .pdf → PDF viewer, .docx → Word, https:// → default browser, etc.
  Windows:   explorer.exe <path>
  WSL:       converts path via wslpath, then same as Windows;
             falls back to xdg-open for Linux-only paths
  macOS:     open <path>
  Linux:     xdg-open <path> (requires xdg-utils)

browser

  Open a URL in the system default browser.
  Non-URL text is treated as a Google search query.
  Local file paths are wrapped in a file:// scheme.

chrome | chromium | firefox | edge | brave | opera | safari

  Same as browser but launch a specific named browser.
  safari only works on macOS.

filemanager

  Open a path in the system file manager.
  Windows: explorer.exe /select,<path> (reveals the file).
  WSL:     explorer.exe via wslpath conversion.
  macOS:   open -R <file> (Finder reveal) or open <dir>.
  Linux:   nautilus/nemo/dolphin --select/thunar/caja to reveal a file,
           xdg-open for a directory.
  With filemanager.reveal = false, a file target navigates to its parent
  directory instead of being revealed/selected there. See |open-config|.
  The dispatch itself lives in lib.nvim.cross.reveal_in_fm, shared with
  filetree.nvim's <leader>fm.

  On Windows the launch also brings the Explorer window to the FRONT, via
  a short PowerShell helper in lib.nvim. Spawning explorer.exe directly
  does create the window, but Windows grants SetForegroundWindow only to
  the process that owns the foreground window — running inside a terminal
  that is the terminal host, not nvim.exe — so the window was created
  behind everything and :Open filemanager looked like it did nothing.
  Under a GUI Neovim (Neovide, nvim-qt) nvim.exe IS the foreground process
  and the same code worked, which is why this looked intermittent. See
  lib.nvim's reveal_in_fm/README.md.

notepad | editor

  Write the context text to a temporary .txt file and open it in the
  platform GUI text editor:
    Windows:     notepad.exe
    WSL:         notepad.exe, with the temp path converted via wslpath
                 first — a Windows binary cannot resolve a Linux path.
    macOS:       open -e (TextEdit)
    Linux:       xdg-open, then gedit/kate/mousepad/leafpad/pluma/xed

split | vsplit | tab

  Open a file path inside the current Neovim session.
  URL contexts are rejected (use browser instead).
  split  → horizontal split   (split)
  vsplit → vertical split     (vsplit)
  tab    → new tab            (tabedit)

terminal

  Open a terminal split in the target's directory.
  URL contexts are rejected. A file target resolves to its parent directory;
  a directory target is used as-is.
    :Open terminal          terminal in the current buffer's directory
    :Open terminal cfile    terminal in <cfile>'s parent directory

6. KEYWORDS *open-keywords*

Named scope aliases let you refer to common config files by a short keyword
instead of typing out a full path. Keywords are valid as the second argument
to :Open and appear in tab-completion.

  :Open split  nvim_init      → opens Neovim init.lua in a split
  :Open tab    zshrc          → opens ~/.zshrc in a new tab
  :Open split  gitconfig      → opens ~/.gitconfig in a split
  :Open split  pwsh_profile   → opens PowerShell $PROFILE in a split

6.1 Built-in keywords *open-keywords-builtin*

Loaded automatically unless builtin_keywords = false.
Dynamic keywords (marked ✦) are resolved at invocation time.

Shell profiles

  pwsh_profile      PowerShell $PROFILE (✦ requires pwsh/powershell on PATH)
  zshrc             ~/.zshrc
  zprofile          ~/.zprofile
  bashrc            ~/.bashrc
  bash_profile      ~/.bash_profile
  profile           ~/.profile
  fish_config       ~/.config/fish/config.fish
  nushell_config    ~/.config/nushell/config.nu

Editor / IDE

  nvim_init         init.lua or init.vim inside the Neovim config dir (✦)
  vimrc             ~/.vimrc

Terminal emulators & multiplexers

  tmux_conf         ~/.config/tmux/tmux.conf  or  ~/.tmux.conf        (✦)
  wezterm_conf      ~/.config/wezterm/wezterm.lua  or  ~/.wezterm.lua  (✦)
  kitty_conf        ~/.config/kitty/kitty.conf
  alacritty_conf    .toml preferred, .yml fallback                     (✦)
  starship_conf     ~/.config/starship.toml

Git

  gitconfig         ~/.gitconfig
  gitignore_global  from git config core.excludesFile, then common paths (✦)
  gitmessage        from git config commit.template, then ~/.gitmessage   (✦)

SSH

  ssh_config            ~/.ssh/config
  ssh_known_hosts       ~/.ssh/known_hosts
  ssh_authorized_keys   ~/.ssh/authorized_keys

Package managers & runtimes

  npmrc             ~/.npmrc
  yarnrc            ~/.yarnrc.yml
  cargo_config      ~/.cargo/config.toml
  pip_conf          ~/.config/pip/pip.conf  (Unix)                    (✦)
                    %APPDATA%\pip\pip.ini   (Windows)
  gemrc             ~/.gemrc
  curlrc            ~/.curlrc

System / misc

  inputrc           ~/.inputrc  (Readline config)
  hosts             /etc/hosts (Unix) · C:\Windows\System32\drivers\etc\hosts (Win) (✦)
  docker_config     ~/.docker/config.json
  wsl_conf          /etc/wsl.conf                            (WSL only)
  wslconfig         ~/.wslconfig                             (Windows only)

6.2 User-defined keywords *open-keywords-user*

Add entries in setup(). User entries override built-in entries with the
same key.

  require("open").setup({
    keywords = {
      -- Static path (~ is expanded at resolution time):
      MY_ROADMAP  = "E:\\projects\\ROADMAP.md",

      -- Override a built-in:
      zshrc = "~/dotfiles/.zshrc",

      -- Dynamic resolver (function called when the keyword is used):
      MY_LOG = function()
        return vim.fn.expand("~/logs/") .. os.date("%Y-%m-%d") .. ".md"
      end,
    },
  })
To disable all built-in keywords:
  require("open").setup({ builtin_keywords = false })

7. TAB COMPLETION *open-completion*

:Open completes context-sensitively:

  :Open <Tab>                      all registered handler names
  :Open browser <Tab>              %  cfile  cwd  git  path=  <keywords>  <files>
  :Open split zsh<Tab>             → zshrc  zprofile  (keyword prefix match)
  :Open filemanager path=<Tab>     file/directory completion after path=
The viewer commands complete their own grammar:

  :Open viewer <Tab>               all urls mdlinks files paths % cwd buffers
  :Open viewer urls <Tab>          %  cwd  buffers  <files>
  :UrlView <Tab>                   %  cwd  buffers  <files>
  :UrlView cwd sort=<Tab>          none  file  kind  alpha
  :UrlView cwd out=<Tab>           picker table clipboard mdlinks csv echo file:

8. THE :Open viewer COMMAND *:Open-viewer*

:Open viewer [kind] [scope] [options]
:UrlView [scope] [options]                                        *:UrlView*
:MDLinksView [scope] [options]                                *:MDLinksView*

Collect links in a scope, then pick one to open — or export the whole list.
:UrlView and :MDLinksView are shallow wrappers that pin the kind, so
their first argument is the SCOPE, not a kind. Rename or disable them with
the viewer.commands setting (see |open-config|).

This replaces the former urlview.nvim dependency
(see |open-integrations-urlview|).

  :UrlView                         URLs in the current buffer -> picker
  :MDLinksView cwd                 markdown links under the cwd
  :Open viewer                     every link in the current buffer
  :Open viewer urls cwd            same as `:UrlView cwd`
  :'<,'>UrlView                    URLs in the visual selection only
Recognized links: bare URLs (https://..., ftp://..., www....), markdown
inline links (reported by target, with the label kept), and — with
--paths — filesystem paths that exist on disk. Links inside fenced code
blocks are skipped, a URL already inside a markdown link is not reported
twice, and bare in-document anchors ([Context](#context)) are dropped
unless --anchors is given.

Relative markdown targets are resolved against the directory of the file
they were found in, so [x](../../lua/init.lua) inside a nested document is
openable from anywhere.

Note that "viewer" is a reserved handler key: :Open viewer matches the
literal subcommand before the flat :Open [target] grammar sees it, so a
handler registered under that key would be unreachable.

8.1 Kind *open-viewer-kind*

The first argument of :Open viewer selects which links to keep.

  all       (default) everything
  urls      links whose TARGET is a URL, including [text](https://...)
  mdlinks   links written with markdown SYNTAX, whatever they point at
  files     links whose target is a local file or directory
  paths     bare filesystem paths (requires --paths)

urls and mdlinks deliberately overlap: urls asks "can a browser open
this?", mdlinks asks "was this written with brackets?". A
[docs](https://x.dev) is in both. That split is what makes :UrlView mean
"things a browser can open" rather than "things without brackets".

The kind is optional: :Open viewer cwd is read as "all kinds, cwd scope",
because cwd does not name a kind.

8.2 Scope *open-viewer-scope*

  (omitted) / %   Current buffer
  cwd             Every file under |getcwd()|, recursively
  buffers         Every listed, loaded buffer
  <path>          A file, or a directory (recursively)
  (a range)       :'<,'>UrlView or :10,20UrlView scans only those lines

Directory scans skip the conventional junk (.git, node_modules, ...) via
lib.nvim's shared ignore list, and skip binary and oversized files.

8.3 Options *open-viewer-options*

  sort={how}      Ordering: none (default) | file | kind | alpha
  out={where}     Output sink; see |open-viewer-outputs|
  match={pat}     Only scan files whose basename matches this Lua pattern,
                  e.g. match=%.md$
  --paths         Also report filesystem paths, not just URLs. Only paths
                  that exist on disk are reported.
  --anchors       Include bare in-document anchors, dropped by default
  --dupes         Keep duplicate targets (the default de-duplicates)
  --flat          Do not recurse into subdirectories

Flags and key=value options may appear in any order, before or after the
positional arguments.

8.4 The picker *open-viewer-picker*

The results list is ui.nvim's ui.kit.chooser, which means:

  - the WHOLE current line is highlighted (CursorLine:KitSelection)
  - the cursor moves only up and down — j/k and the arrow keys; h, l, 0,
    $, w, b and other horizontal motions are mapped to <Nop>
  - <CR> opens the entry, <Esc> or q closes

<CR> is kind-aware:

  a URL           opens in your browser, via the default_browser handler
  a local file    opens in a Neovim split (see viewer.open_file)
  a directory     opens in the system file manager

Following a markdown link therefore lands you in an editable buffer, not in
your file manager. A file.md#heading target jumps to that heading after
opening.

Columns are aligned across the whole result set and shortened to fit the
window: local targets are shown relative to the cwd, and long paths are
elided in the middle rather than pushing the target off the right edge.

8.5 Outputs *open-viewer-outputs*

  picker          Interactive list; see |open-viewer-picker|
  table           GFM table (Kind / Location / Text / Target) in a scratch
                  buffer
  csv             The same columns as CSV, in a scratch buffer
  mdlinks         [label](target) per line, copied to the clipboard
  clipboard       The rendered table, copied to the clipboard
  echo            Printed to the message area
  file:{path}     The rendered table, written to {path}

mdlinks reuses an existing markdown label when there is one, and otherwise
labels a URL with its host and a path with its basename — [](...) would
render as an invisible link.

  :UrlView cwd sort=file out=table         a table of every URL in the project
  :MDLinksView cwd                         every markdown link, as a picker
  :Open viewer files cwd --paths           local targets, incl. bare paths
  :UrlView cwd match=%.md$ out=mdlinks     doc URLs as markdown, to clipboard
  :UrlView % out=file:/tmp/links.md        write this buffer's URLs to a file

9. LUA API *open-api*

After calling setup(), the following functions are available:

                                                           *open.open()*
require("open").open([target [, scope]])

  Open programmatically with an optional explicit handler and scope.
  Behaves identically to the :Open command. Scope may be a keyword.

  target  String handler key, or nil for the context-aware default.
  scope   Scope token: "%", "cfile", "cwd", "git", "path=<path>", keyword,
            or literal.

  Examples:
    require("open").open()
    require("open").open("browser")
    require("open").open("filemanager", "%")
    require("open").open("split", "cfile")
    require("open").open("split", "nvim_init")
    require("open").open("tab",   "pwsh_profile")
                                                    *open.context.with_cache()*
require("open.context").with_cache(fn)

  For extension authors building on open.context directly (e.g. a picker
  that resolves several candidate handlers against the same context):
  memoizes context.gather()'s result for the duration of fn, so nested
  gather()/resolve() calls reuse one read of editor state instead of
  re-reading it. :Open itself already runs through this.

10. INTEGRATIONS *open-integrations*


10.1 urlview.nvim *open-integrations-urlview*

https://github.com/axieax/urlview.nvim lists URLs found in the current
buffer and lets you pick one. open.integrations.urlview registers a
custom urlview action, "open_in_browser", that routes the picked URL through
open.nvim's own registry/handler dispatch (the default_browser handler —
see |open-config|) instead of duplicating cross-platform browser-launch
logic in a second place.

lazy.nvim:
  {
    "axieax/urlview.nvim",
    lazy         = true,
    cmd          = { "UrlView" },
    dependencies = { "StefanBartl/open.nvim" },
    config       = function()
      require("open.integrations.urlview").setup()
    end,
  }
                                             *open.integrations.urlview*
require("open.integrations.urlview").setup([opts])

  Registers the "open_in_browser" action with urlview.actions, then calls
  urlview.setup(opts) with default_action = "open_in_browser" (and
  default_picker set to telescope/fzf-lua if available) unless already
  set in opts. Pass false instead of a table to only register the
  action without calling urlview.setup() yourself.

10.2 telescope.nvim *open-integrations-telescope*

Opt-in telescope.nvim source listing every registered handler, with a live
preview of what it would open for the current context, and dispatching
whichever one is picked — the same effect as :Open {key}, but browsable
and previewable first. Not loaded by open.setup().

  require("open.integrations.telescope").picker()
                                          *open.integrations.telescope.picker()*
require("open.integrations.telescope").picker([opts])

  Opens the picker. opts is forwarded to telescope's pickers.new().

                                       *open.integrations.telescope.extension()*
require("open.integrations.telescope").extension()

  Returns a table shaped for telescope.register_extension():
    require("telescope").register_extension(
      require("open.integrations.telescope").extension()
    )

    -- later, anywhere:
    require("telescope").extensions.open.open()

11. HEALTH CHECK *open-health*

  :checkhealth open
Reports:

  core          Neovim version, vim.system availability.
  lib.nvim      lib.nvim.notify and lib.nvim.bindings.usercmd.composer presence.
  platform      Detected OS (Windows / WSL / macOS / Linux).
  executables   Per-platform tool availability.
  office_open   Auto-redirect status and configured extensions.
  handlers      All registered handlers with descriptions.

12. ARCHITECTURE *open-architecture*

  plugin/open.lua           Load guard (vim.g.loaded_open)
  lua/open/
    init.lua                setup() — config, handler loading, :Open command
    config/
      DEFAULTS.lua          default configuration values
      init.lua              setup() merge + M.get() / M.is_debug()
    registry.lua            handler register / get / list
    context.lua             gather() + resolve() — two-stage resolution;
                            with_cache() memoizes gather() per invocation
    platform.lua            OS detection, cached per session
    util.lua                run_detached(), url_encode(), find_exec()
    keywords.lua            built-in named scope keyword table
    picker.lua              opt-in handler-choice picker (ui.kit)
    office_open.lua         BufReadCmd redirect for MS Office documents
    health.lua              :checkhealth open
    @types/init.lua         LuaLS type definitions
    bindings/
      usrcmds.lua           :Open — built on lib.nvim.bindings.usercmd.composer
      keymaps.lua           optional keymaps declared via cfg.keymaps
    handlers/
      default.lua           system default application (like a double-click)
      filemanager.lua       Explorer / Finder / xdg-open
      browser.lua           browser + chrome/chromium/firefox/edge/safari
      notepad.lua           notepad/editor (temp-file → GUI editor)
      nvim_internal.lua     split / vsplit / tab
      terminal.lua          terminal split in the target's directory
      image.lua             inline image render (images.nvim, with fallback)
    viewer/
      init.lua              :Open viewer — filter/sort/format/open
      scan.lua              link extraction (URLs, md links, paths)
    integrations/
      urlview.lua           urlview.nvim "open_in_browser" action (opt-in)
      telescope.lua         telescope.nvim handler-picker source (opt-in)
      menu.lua              nvzone/menu context-aware entries (opt-in)

Each leaf module exposes only plain functions or a register_all(fn) entry
point; init.lua is the only place that registers a user command.