Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

btv.utils

btv.utils.path_style()

btv.utils.path_style(): the path convention the editor’s paths follow — "windows" or "unix" (the same spellings btv.glob’s style option takes).

Under "windows", / and \ both separate when a path is read, a path can be rooted at a drive (C:\, C:/) or a UNC share (\\server\share\), and paths the editor produces use \. Under "unix", / is the only separator and \ is an ordinary character.

This is the single place the path helpers (btv.utils.path_sep, path_root, is_absolute, dirname, and btv.fname.modify built on them) decide which platform’s rules apply. It follows the session’s path syntax, which the server publishes as btv._path_style ("posix" or "windows") alongside btv._cwd: the daemon’s in a daemon session — a Windows client editing a unix host still sees unix paths — and the host’s otherwise. Before the first publish it answers for the host (btv.utils.is_windows).

Defined in utils.lua.

btv.utils.path_sep()

btv.utils.path_sep(): the separator the editor writes paths with — "\" under the "windows" path style, "/" otherwise. Use it to build a path that will be shown or compared against one the editor reports; when reading a path, accept either separator on Windows (btv.utils.path_root, dirname and basename already do).

Defined in utils.lua.

btv.utils.path_join(...)

btv.utils.path_join(...): join path segments into ONE filesystem path spelled with the session’s separator (btv.utils.path_sep), so a path built from a directory the editor reported (C:\Users\me\data) never comes out half \, half /. Under the "windows" style a / inside a segment is respelled \ too, so path_join(cfg, "lua/plugins.lua") is one native path; under "unix" a \ is an ordinary character and left alone. Separators already at a seam are not doubled, and nil / "" segments are skipped.

btv.utils.path_join("/home/me", "plugins", "foo")   -- "/home/me/plugins/foo"
btv.utils.path_join([[C:\data]], "lua/x.lua")        -- [[C:\data\lua\x.lua]]

It follows the session’s path style — in a daemon session, the daemon’s — so it is for paths on the session filesystem (btv.fs, buffer names, getcwd()). A path on THIS machine (btv.fs_local, stdpath, package.path) is joined with btv.utils.local_path_join. This is for paths handed to the filesystem or shown as files: btv.utils.joinpath stays neovim’s vim.fs.joinpath (always /), and a glob keeps its own /.

Defined in utils.lua.

btv.utils.local_path_join(...)

btv.utils.local_path_join(...): btv.utils.path_join, but by THIS machine’s rules (btv.utils.is_windows) whatever the session’s — the twin for paths on the local filesystem (btv.fs_local, stdpath, the plugin manager’s directories, package.path), as btv.fs_local is the twin of btv.fs. Identical to path_join outside a daemon session.

Defined in utils.lua.

btv.utils.path_root(path)

btv.utils.path_root(path): the root prefix of path, exactly as spelled in it, or "" for a relative path. path:sub(#root + 1) is then the part below the root.

unix:     "/a/b"                -> "/"
windows:  "C:\a" / "C:/a"       -> "C:\" / "C:/"
          "\\srv\share\a"       -> "\\srv\share\"
          "\a"                  -> "\"   (rooted on the current drive)
both:     "a/b", ""             -> ""

A drive-relative C:a has no root (it is relative to that drive’s own cwd).

Defined in utils.lua.

btv.utils.is_absolute(path)

btv.utils.is_absolute(path): is path rooted — /… on Unix; a drive (C:\…), UNC share (\\server\share\…) or bare separator (\…) on Windows? A relative path is the one a cwd has to be prepended to (see btv.fname.modify(path, ":p")).

Defined in utils.lua.

btv.utils.dirname(path)

btv.utils.dirname(path): the directory part of path — everything before the last separator ("/a/b/c.txt" → "/a/b"). "" for an entry directly under the root ("/a" → "", and on Windows "C:\a" → ""); a path with no separator comes back unchanged (there is nothing to strip), which callers walking upward detect as “no parent”. On Windows both / and \ separate.

Defined in utils.lua.

btv.utils.basename(path)

btv.utils.basename(path): the last /- or \-separated component of path, ignoring trailing separators ("/a/b/" → "b"). nil when nothing remains (the root "/", an empty string).

Defined in utils.lua.

btv.utils.expanduser(path)

btv.utils.expanduser(path): expand a leading ~ / ~/ to $HOME, so a config value can point at a home-relative path ("~/work/foo"). Only the leading tilde is touched — a mid-path ~ is a literal path component, and the ~user form is not resolved (returned unchanged, like vim with an unknown user). With no $HOME in the environment the path is returned unchanged rather than mangled.

Defined in utils.lua.

btv.utils.ancestors(path)

btv.utils.ancestors(path): iterate the ancestor directories of path, nearest first — dirname(path), then its parent, and so on. Stops before the empty string, so the filesystem root itself is never produced. A relative path is first resolved against the editor’s working directory (btv.fname.modify(path, ":p"), which also collapses . / ..) — the ancestry of a relative path is only meaningful against the cwd, and the paths callers walk are buffer names (btv.buf.name, an autocmd’s ev.file), which are whatever the user typed and so routinely relative (bemtvi src/main.rs). Without that, the walk would stop at the first typed component and never reach the project root. The upward-walk loop behind .editorconfig discovery and LSP root-marker search:

for dir in btv.utils.ancestors("/a/b/c.txt") do
  -- "/a/b", then "/a"
end

Defined in utils.lua.

btv.utils.joinpath(...)

btv.utils.joinpath(...): join path components with a single /, collapsing the separators at each seam so joinpath("/a/", "/b/", "c") is "/a/b/c". An empty or nil component is skipped rather than producing a doubled slash, so a conditionally-absent middle segment doesn’t corrupt the result. A leading / on the FIRST component is preserved (the only place an absolute path is decided); everything after it is treated as relative, so a later "/b" appends rather than restarting at the root. Pure string math — nothing is resolved against the filesystem or the cwd.

btv.utils.joinpath(root, "node_modules/.bin", cmd)

Defined in utils.lua.

btv.utils.normalize(path)

btv.utils.normalize(path): canonicalize a path as pure string math — expand a leading ~, fold \ to /, collapse repeated separators, drop . components, and resolve .. against the component before it. Trailing separators are stripped (except on the root itself). Nothing touches the filesystem, so this does NOT resolve symlinks — a .. after a symlinked directory resolves lexically, which is what a config comparing two configured paths wants (for the real thing, btv.fs.realpath is the async op).

A .. that would escape a relative path is KEPT ("a/../../b" → "../b"), since there is no cwd here to resolve it against; on an absolute path it is dropped at the root ("/../a" → "/a"), matching every OS.

Defined in utils.lua.

btv.utils.relpath(base, target)

btv.utils.relpath(base, target): target expressed relative to base, or nil when target is not inside base. Both are normalized first, and the comparison is on whole path COMPONENTS — so relpath("/a/b", "/a/bc") is nil, not "c", which a plain prefix-match would get wrong. target == base yields ".".

The “is this file under that directory?” test, which is how a config decides a buffer belongs to a dependency tree (a cargo registry checkout, a Go module cache) rather than the project:

if btv.utils.relpath(cargo_registry, file) then --[[ it's a library file ]] end

Defined in utils.lua.

btv.utils.is_windows()

btv.utils.is_windows(): is this a Windows host? Answered from package.config’s directory separator, which Lua fills in at build time — so it is correct in every build (native, daemon-side, wasm) without a host round-trip or an env-var guess.

The check behind “which name does this program have here?” — a tool installed as foo on Unix is foo.exe or foo.bat on Windows, and a plugin resolving an executable has to know which to look for. Prefer btv.fs.which, which searches for you; reach for this when the name itself differs.

Defined in utils.lua.

btv.utils.uri_from_path(path)

btv.utils.uri_from_path(path): the file:// URI naming path.

Percent-encodes everything outside the URI unreserved set, / excepted — a path holding a space, a # or a non-ASCII character is otherwise a malformed URI the server silently misreads (it truncates at the #, or fails to match the document it already has open under the encoded spelling). The path is normalized first, so the same file always produces the same URI and a server’s document map hits, and a RELATIVE path (a buffer name as typed) is resolved against the editor’s cwd, since a file:// URI can only name an absolute path.

A Windows path takes the shape every LSP server (and the url crate’s Url::from_file_path) expects: C:\Users\x\a.py → file:///C:/Users/x/a.py, the drive’s : left unescaped; a UNC path names its server as the URI’s host (\\srv\share\a → file://srv/share/a).

Defined in utils.lua.

btv.utils.uri_from_buf(bufnr)

btv.utils.uri_from_buf(bufnr): buffer bufnr’s file:// URI (0/nil = the current buffer), or "" for a buffer with no file — which is what a server should be told, rather than a file:// naming nothing.

Defined in utils.lua.

btv.utils.uri_to_path(uri)

btv.utils.uri_to_path(uri): the filesystem path a file:// URI names, with its percent-escapes decoded, or nil for any other scheme.

nil rather than the raw string: a deno: / jdt: / untitled: URI names a document that has no path at all, and treating one as a path creates a buffer for a file that will never exist. Callers branch on the nil.

Under the Windows path style (btv.utils.path_style) the result is a native path: file:///C:/x/a.py → C:\x\a.py, and a URI with a host is a UNC path (file://srv/share/a → \\srv\share\a).

Defined in utils.lua.

btv.utils.argv(spec)

btv.utils.argv(spec): build a flat argv list from a run-family spec — spec.cmd is the program (a string, or an argv list whose first element is the program), spec.args an optional list appended after it, so { cmd = "git", args = { "log" } } and { cmd = { "git", "log" } } produce the same argv. The normalizer behind btv.run / btv.run_stream / btv.run_local, public so a plugin composing specs for those APIs (a task runner, a DAP adapter) shares the exact same shape.

Defined in utils.lua.

btv.utils.str_list(spec, what)

btv.utils.str_list(spec, what): normalize a config value that may be one string or a list of strings — a bare string becomes a one-element list, a list of strings passes through, nil becomes {} (the caller’s default applies). Anything else raises, with what naming the option in the message (e.g. "btv.snippet.setup: jump_next"). The shared normalizer behind key-spec options (btv.complete.setup / btv.snippet.setup), public for any config surface that accepts string | string[].

Defined in utils.lua.

btv.utils.caller_source()

btv.utils.caller_source(): the source path of the file whose code called into the current function — walks up the Lua stack to the nearest real-file frame. bemtvi sources every config / plugin / required file with an @<path> chunk name, while the embedded prelude chunks are named bemtvi:prelude/* (no @) and C frames carry =[C] — so the first @-prefixed source above the caller is the file whose code is running. nil when nothing on the stack is @-named. Behind vim’s <sfile> / <script> (btv.expand) and the plugin-persistence namespace attribution (btv._resolve_namespace).

Defined in utils.lua.

btv.utils.debounce(fn, ms)

btv.utils.debounce(fn, ms): wrap fn into a trailing-edge debounce over btv.timer — the returned value runs fn once, ms after the LAST call, so a burst of rapid calls collapses to a single invocation with the most recent arguments. A timing/control-flow helper (which-key’s show-delay, on-change handlers, resize / scroll reactions); it runs nothing on the input path.

It is callback-shaped, NOT promise-shaped: debounce coalesces a stream of many calls, whereas a promise models one eventual value — different jobs. They compose, though: pass an btv.async function as fn to kick awaitable work after the quiet period, and reach for btv.promise.delay when you want an await-able one-shot sleep instead.

The result is callable AND carries:

:cancel()  drop a pending invocation (the next call re-arms)
:flush()   run a pending invocation now (no-op when idle)

Each call (re)arms the timer; nothing fires until the calls stop for ms.

Defined in utils.lua.

debounced:cancel()

No documentation comment in the prelude.

Defined in utils.lua.

debounced:flush()

No documentation comment in the prelude.

Defined in utils.lua.