{"id":35559689,"url":"https://github.com/juniorsundar/cling.nvim","last_synced_at":"2026-04-25T18:01:57.796Z","repository":{"id":331440357,"uuid":"1126627200","full_name":"juniorsundar/cling.nvim","owner":"juniorsundar","description":"Thin wrapper around your command-line","archived":false,"fork":false,"pushed_at":"2026-04-23T18:58:30.000Z","size":280,"stargazers_count":22,"open_issues_count":1,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-23T20:27:32.325Z","etag":null,"topics":["cli","lua","neovim","neovim-plugin"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juniorsundar.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-02T09:35:04.000Z","updated_at":"2026-04-23T18:58:35.000Z","dependencies_parsed_at":"2026-02-02T05:33:03.042Z","dependency_job_id":null,"html_url":"https://github.com/juniorsundar/cling.nvim","commit_stats":null,"previous_names":["juniorsundar/cling.nvim"],"tags_count":4,"template":false,"template_full_name":"ellisonleao/nvim-plugin-template","purl":"pkg:github/juniorsundar/cling.nvim","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juniorsundar%2Fcling.nvim","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juniorsundar%2Fcling.nvim/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juniorsundar%2Fcling.nvim/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juniorsundar%2Fcling.nvim/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juniorsundar","download_url":"https://codeload.github.com/juniorsundar/cling.nvim/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juniorsundar%2Fcling.nvim/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32271244,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T09:15:33.318Z","status":"ssl_error","status_checked_at":"2026-04-25T09:15:31.997Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","lua","neovim","neovim-plugin"],"created_at":"2026-01-04T10:17:03.006Z","updated_at":"2026-04-25T18:01:57.790Z","avatar_url":"https://github.com/juniorsundar.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# cling.nvim\n\n![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/juniorsundar/cling.nvim/lint-test.yml?branch=main\u0026style=for-the-badge)\n![Lua](https://img.shields.io/badge/Made%20with%20Lua-blueviolet.svg?style=for-the-badge\u0026logo=lua)\n\n\u003cimg src=\"./assets/clinging.jpg\" width=\"25%\" /\u003e\n\n\u003c/div\u003e\n\u003cdiv align=\"center\"\u003e\n\u003cbr\u003e\n\u003c/div\u003e\n\n\n`cling.nvim` implements a customisable and thin CLI wrapper around executable binaries in Neovim.\n\nIt can be used to quickly execute terminal commands:\n- without leaving the Neovim context via multiplexing or `Ctrl+z`,\n- without losing the text formatting of the command outputs,\n- and also to interact with the output of those commands as a text-buffer.\n\nThe plugin can also be configured to wrap CLI commands that you commonly use (like `jj`, `docker`, etc.) and:\n- automatically generate tab-completions in Neovim,\n- implement custom keymaps for those wrapped CLI output buffers,\n- automatically close the terminal buffer when the process exits (useful for interactive TUI tools).\n\n\u003e [!NOTE]\n\u003e\n\u003e Autogenerating tab-completions in Neovim is an experimental feature.\n\u003e\n\u003e It may not work for all available CLI tools as there is no standard way to implement subcommands and completion functions in Bash.\n\u003e If such as instance is encountered, please raise an Issue ticket.\n\n## Requirements\n\n- Neovim 0.10+\n- `bash-completion`: Many CLI tools' completion scripts depend on this.\n- `bash` *(obviously)*\n- `curl` (optional) for fetching remote completion scripts (Method 4).\n\n## Installation\n\nUsing [lazy.nvim](https://github.com/folke/lazy.nvim):\n\n```lua\nreturn {\n    \"juniorsundar/cling.nvim\",\n    config = function()\n        require(\"cling\").setup({\n            wrappers = {\n                {\n                    binary = \"jj\",\n                    command = \"JJ\",\n                    completion_cmd = \"jj util completion bash\",\n                },\n                -- {}, ...\n            }\n        })\n    end,\n}\n```\n\n## Usage\n\n### The `Cling` Command\n\nThe plugin exposes the global `:Cling` command, which serves as a generic entry point for executing shell commands within the plugin's environment:\n\n*   **`:Cling`**: Opens an input prompt to enter a shell command interactively.\n*   **`:Cling with-env`**: Executes command with an `.env` file assigned interactively.\n*   **`:Cling last`**: Executes the last executed command with `.env`.\n*   **`:Cling -- \u003ccommand\u003e`**: Executes the command, treating everything after `--` as the command string. This defaults to executing in current working directory.\n\n### Output Buffer Keymaps\n\nWhen a command is executed, the output is displayed in a dedicated terminal-filetype buffer. The following default keymaps are available:\n\n*   **`q`**: Closes the Cling window.\n*   **`\u003cCR\u003e` (Enter)**: Smart file navigation. If the cursor is on a file path (common in `grep`, `ls`, or compiler output), pressing Enter will attempt to open that file in the previous window. It supports `file:line:col` formats to jump directly to the specific location.\n*   **`ge`**: Export the terminal output to a file. ANSI escape codes are stripped and metadata (command, CWD, timestamp) is appended as comments.\n\n### Split Modes\n\nThe `:Cling` command (and all wrapper commands) respect Neovim's built-in\ncommand modifiers for controlling split direction:\n\n| Modifier | Result |\n|---|---|\n| `:Cling -- ls` | Bottom horizontal split (default) |\n| `:vert Cling -- ls` | Vertical split |\n| `:tab Cling -- ls` | New tab |\n| `:top Cling -- ls` | Top horizontal split |\n| `:bot Cling -- ls` | Bottom horizontal split (explicit) |\n\nThese modifiers work with wrapper commands as well:\n\n```vim\n:vert JJ log\n:tab Docker ps\n```\n\n### Exporting Output\n\nYou can export the terminal output from any Cling buffer to a file by\npressing `ge` in normal mode while in the output buffer. The export will:\n\n*   Prompt you for a file path (defaults to `cling-output.log` in CWD)\n*   Append a metadata footer as vim modeline comments:\n\n```\n-- Command: echo hello\n-- CWD: /home/user/project\n-- Timestamp: 2026-03-06T12:00:00Z\n-- vim: ft=log\n```\n\n## Configuration\n\nYou can define custom wrappers for your CLI tools in the `setup` function. Wrappers allow you to create specific Neovim user commands (e.g., `:JJ`, `:Docker`) with autocompletions that can either be derived from the CLI tool itself, or from the completion bash file.\n\n### Setup Options\n\n| Option | Type | Description |\n|---|---|---|\n| `separate_history` | `boolean` | Enable per-CWD command history. When `true` (default), commands are grouped by working directory and persisted to `stdpath(\"data\")/cling/history/`. When `false`, uses Neovim's native input history for the command prompt. |\n\n### Wrapper Fields\n\n| Field | Type | Description |\n|---|---|---|\n| `binary` | `string` | The binary (or shell command string) to execute. |\n| `command` | `string` | The Neovim user command name to register (e.g. `\"Lazygit\"`). |\n| `help_cmd` | `string` | Flag passed to the binary to crawl help output for completions. |\n| `completion_cmd` | `string` | Shell command that outputs a Bash completion script. |\n| `completion_file` | `string` | Path or URL to an existing Bash completion script. |\n| `keymaps` | `fun(buf: integer)` | Callback to define buffer-local keymaps for the output buffer. |\n| `close_on_exit` | `boolean` | If `true`, the terminal buffer is automatically wiped when the process exits. Defaults to `false`. |\n| `cwd` | `string\\|fun(): string` | Working directory for the command. Can be a static string or a function evaluated at invocation time. Defaults to `vim.fn.getcwd()`. |\n| `no_history` | `boolean` | If `true`, running this wrapper does not update `:Cling`'s last command history. Defaults to `true` for all wrappers - set to `false` to opt a wrapper back into history. |\n\n\u003e [!TIP]\n\u003e\n\u003e `close_on_exit = true` is ideal for **interactive fullscreen TUI tools** (e.g. `lazygit`, `yazi`) whose\n\u003e terminal buffer has no useful output to read after exit. Leave it `false` (the default) for\n\u003e output-producing commands where you want to scroll, search, or export the results afterwards.\n\n### Completion Generation\n\n`cling.nvim` provides **4 ways** to generate subcommands and completions for your wrappers:\n\n1.  **Help Crawling (`help_cmd`)**:\n    *   Recursively runs the binary with a help flag (e.g., `--help`) to parse subcommands and flags.\n    *   *Best for:* Tools that don't provide bash completion scripts but have structured help output.\n2.  **Completion Command (`completion_cmd`)**:\n    *   Executes a specific command that outputs a Bash completion script, which is then parsed by the plugin.\n    *   *Best for:* Modern tools that can generate their own shell completions (e.g., `cobra`-based CLIs).\n3.  **Local Completion File (`completion_file`)**:\n    *   Points to an existing Bash completion script on your local filesystem.\n    *   *Best for:* Standard system tools where the completion file is already installed (e.g., `/usr/share/bash-completion/completions/`).\n4.  **Remote Completion File (`completion_file` as URL)**:\n    *   Points to a URL serving a raw Bash completion script. The plugin will `curl` this file.\n    *   *Best for:* Tools where you want to fetch the latest completions directly from the repository without manual installation.\n\n### Caching \u0026 Updates\n\nTo optimize performance, `cling.nvim` parses and caches the generated completions in `stdpath(\"data\")/cling/completions/\u003cbinary\u003e.lua`.\n\nThese cached files are loaded on subsequent startups to avoid expensive re-parsing. If you update the underlying CLI tool or want to refresh the completions, you can force a re-parse by passing the `--reparse-completions` flag to your wrapper command:\n\n```vim\n:\u003cWrapperCommand\u003e --reparse-completions\n```\n\nYou can also manually add completions if you want as they are all just `.lua` files that export a table.\n\n## Examples\n\n### Wrapping Jujutsu (jj) with custom keymaps\n\nThis example shows how to wrap the [Jujutsu](https://github.com/martinvonz/jj) VCS and to implement a custom keymap to send the outputs of `jj show` to a quickfix list.\n\nIt uses the `completion_cmd` method to generate completions dynamically.\n\n```lua\nreturn {\n    \"juniorsundar/cling.nvim\",\n    config = function()\n        local function strip_ansi(str)\n            return str:gsub(\"\\27%[[0-9;]*m\", \"\")\n        end\n\n        local function get_file_from_line(line)\n            local clean = strip_ansi(line)\n            local file = clean:match \"^Modified regular file (.*):$\"\n            if file then\n                return file, \"Modified\"\n            end\n            file = clean:match \"^Added regular file (.*):$\"\n            if file then\n                return file, \"Added\"\n            end\n            file = clean:match \"^Removed regular file (.*):$\"\n            if file then\n                return file, \"Removed\"\n            end\n            file = clean:match \"^Renamed .* to (.*):$\"\n            if file then\n                return file, \"Renamed\"\n            end\n            local _, b = clean:match \"^diff %-%-git a/(.*) b/(.*)\"\n            if b then\n                return b, \"Git Diff\"\n            end\n            return nil\n        end\n\n        local function populate_quickfix(buf)\n            local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)\n            local qf_list = {}\n            local current_file = nil\n            local current_type = nil\n            local last_was_gap = true\n\n            for _, raw_line in ipairs(lines) do\n                local line = strip_ansi(raw_line)\n                local file, type = get_file_from_line(line)\n\n                if file then\n                    current_file = vim.trim(file)\n                    current_type = type\n                    last_was_gap = true\n                elseif line:match \"^%s*%.%.%.%s*$\" then\n                    last_was_gap = true\n                elseif current_file then\n                    local old, new = line:match \"^%s*([0-9]*)%s+([0-9]*):\"\n                    if old or new then\n                        if last_was_gap then\n                            local lnum = tonumber(new) or tonumber(old) or 1\n                            local text = line:sub((line:find \":\" or 0) + 1)\n                            table.insert(qf_list, {\n                                filename = current_file,\n                                lnum = lnum,\n                                text = string.format(\"[%s] %s\", current_type or \"Change\", vim.trim(text)),\n                            })\n                            last_was_gap = false\n                        end\n                    end\n                end\n            end\n\n            if #qf_list \u003e 0 then\n                vim.fn.setqflist(qf_list, \"r\")\n                vim.notify(\"Quickfix populated with \" .. #qf_list .. \" entries\", vim.log.levels.INFO)\n                vim.cmd \"copen\"\n            else\n                vim.notify(\"No file headers or hunks found\", vim.log.levels.WARN)\n            end\n        end\n\n        require(\"cling\").setup {\n            wrappers = {\n                {\n                    binary = \"jj\",\n                    command = \"JJ\",\n                    completion_cmd = \"jj util completion bash\",\n                    keymaps = function(buf)\n                        vim.keymap.set(\"n\", \"\u003cC-q\u003e\", function()\n                            populate_quickfix(buf)\n                        end, { buffer = buf, silent = true, desc = \"JJ: Move diffs to quickfix\" })\n                    end,\n                },\n            },\n        }\n    end,\n}\n```\n\n\u003c/details\u003e\n\n\n### Wrapping TUI tools with `close_on_exit`\n\nFor interactive fullscreen TUI tools like `lazygit` or `yazi`, the terminal buffer has no useful content once the tool exits. Setting `close_on_exit = true` wipes the buffer automatically so a Cling buffer doesn't persist with `[Process exited 0]`.\n\n#### Example: `lazygit`\n\n[lazygit](https://github.com/jesseduffield/lazygit) is a terminal UI for git.\n\n```lua\nrequire(\"cling\").setup {\n    wrappers = {\n        {\n            binary = \"lazygit\",\n            command = \"Lazygit\",\n            help_cmd = \"--help\",\n            close_on_exit = true,\n        },\n    },\n}\n\nvim.keymap.set(\"n\", \"\u003cleader\u003eGL\", function()\n    vim.cmd \"Lazygit\"\n    vim.cmd \"wincmd T\"\n    vim.cmd \"startinsert\"\nend, { desc = \"lazygit\" })\n```\n\n#### Example: `yazi`\n\n[yazi](https://github.com/sxyazi/yazi) is a terminal file manager. Because cling runs tools inside a Neovim terminal buffer, naively wrapping `yazi` would cause it to open files in a **nested** Neovim instance rather than the parent one.\n\nThe solution is to use `yazi`'s built-in `--chooser-file` flag. Instead of opening files directly, `yazi` writes the selected path to a temp file on exit. A small inline shell script then reads that path and uses `nvim --server \"$NVIM\" --remote` to instruct the **parent** Neovim instance to open it via RPC. The `$NVIM` socket is automatically exposed by Neovim to all its terminal children.\n\n```lua\nrequire(\"cling\").setup {\n    wrappers = {\n        {\n            binary = [[sh -c 'f=$(mktemp); yazi --chooser-file=\"$f\"; sel=$(cat \"$f\"); rm -f \"$f\"; [ -n \"$sel\" ] \u0026\u0026 nvim --server \"$NVIM\" --remote \"$sel\"']],\n            command = \"Yazi\",\n            close_on_exit = true,\n            cwd = function()\n                return vim.fn.expand \"%:p:h\"\n            end,\n        },\n    },\n}\n\nvim.keymap.set(\"n\", \"\u003cleader\u003eo\", function()\n    vim.cmd \"Yazi\"\n    vim.cmd \"startinsert\"\nend, { desc = \"Yazi (File Explorer)\" })\n```\n\n### Different methods of generating tab-completion\n\nGenerating tab-completions can be achieved through following 4 methods:\n\n```lua\nwrappers = {\n    -- Method 1: Recursive Help Crawling\n    {\n        binary = \"docker\",\n        command = \"Docker\",\n        help_cmd = \"--help\",\n    },\n\n    -- Method 2: Completion Command\n    {\n        binary = \"jj\",\n        command = \"JJ\",\n        completion_cmd = \"jj util completion bash\",\n    },\n\n    -- Method 3: Local File\n    {\n        binary = \"git\",\n        command = \"Git\",\n        completion_file = \"/usr/share/bash-completion/completions/git\",\n    },\n\n    -- Method 4: Remote URL (requires curl)\n    {\n        binary = \"eza\",\n        command = \"Eza\",\n        completion_file = \"https://raw.githubusercontent.com/eza-community/eza/main/completions/bash/eza\",\n    },\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuniorsundar%2Fcling.nvim","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuniorsundar%2Fcling.nvim","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuniorsundar%2Fcling.nvim/lists"}