{"id":16395673,"url":"https://github.com/wallpants/bunvim","last_synced_at":"2025-06-19T08:08:56.219Z","repository":{"id":194542273,"uuid":"690776138","full_name":"wallpants/bunvim","owner":"wallpants","description":"Neovim Bun client.","archived":false,"fork":false,"pushed_at":"2025-03-01T22:34:11.000Z","size":762,"stargazers_count":27,"open_issues_count":12,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-11T20:11:28.065Z","etag":null,"topics":["bun","neovim","neovim-plugin","nodejs","rpc","rpc-client","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wallpants.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2023-09-12T21:18:29.000Z","updated_at":"2025-05-31T16:45:50.000Z","dependencies_parsed_at":"2023-09-14T01:01:30.387Z","dependency_job_id":"d1faa59a-1737-44ae-ae6d-ce0a7043fe86","html_url":"https://github.com/wallpants/bunvim","commit_stats":null,"previous_names":["gualcasas/bunvim"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/wallpants/bunvim","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wallpants%2Fbunvim","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wallpants%2Fbunvim/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wallpants%2Fbunvim/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wallpants%2Fbunvim/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wallpants","download_url":"https://codeload.github.com/wallpants/bunvim/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wallpants%2Fbunvim/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260713324,"owners_count":23050824,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["bun","neovim","neovim-plugin","nodejs","rpc","rpc-client","typescript"],"created_at":"2024-10-11T05:05:21.283Z","updated_at":"2025-06-19T08:08:51.205Z","avatar_url":"https://github.com/wallpants.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bunvim\n\n\u003cimg src=\"docs/nvim.svg\" height=\"60px\" align=\"right\" /\u003e\n\u003cimg src=\"docs/bun.svg\" height=\"60px\" align=\"right\" /\u003e\n\nBunvim is a [Bun](https://bun.sh/) client that allows you to interact with Neovim through RPC\nusing TypeScript and JavaScript. If you're familiar with Neovim's Lua API, you'll find this client easy to use.\n\nThis client includes [TypeScript definitions](https://github.com/wallpants/bunvim/blob/main/src/neovim-api.types.ts),\ngenerated from [Neovim's api-metadata](https://neovim.io/doc/user/api.html#api-metadata), describing API function signatures,\nincluding function parameters and return values.\n\nAll functionality is implemented in [one file](https://github.com/wallpants/bunvim/blob/main/src/attach.ts).\nIf you're looking for higher levels of abstraction, take a look at [neovim/node-client](https://github.com/neovim/node-client)\nand [neoclide/neovim](https://github.com/neoclide/neovim). Good luck.\n\n## ✅ Requirements\n\n- [x] [Bun](https://bun.sh/)\n- [x] [Neovim](https://neovim.io/)\n\n## 📦 Installation\n\n```sh\nbun install bunvim\n```\n\n## 💻 Usage\n\nFor examples of plugins using Bunvim, take a look at [napoleon.nvim](https://github.com/wallpants/napoleon.nvim),\n[ghost-text.nvim](https://github.com/wallpants/ghost-text.nvim) and [github-preview.nvim](https://github.com/wallpants/github-preview.nvim).\n\nYou should keep a tab open with [Neovim API docs](https://neovim.io/doc/user/api.html) when working with Bunvim.\nAlthough this client includes generated TypeScript types, you'll find the detailed descriptions in the official docs very helpful if not necessary.\n\nCreate a script:\n\n```typescript\n// my-plugin.ts\nimport { attach } from \"bunvim\";\n\n// RPC listenning address\nconst SOCKET = \"/tmp/bunvim.nvim.socket\";\n\nconst nvim = await attach({\n  socket: SOCKET,\n  client: { name: \"my-plugin-name\" },\n});\n\n// append \"hello world\" to current buffer\nawait nvim.call(\"nvim_buf_set_lines\", [0, -1, -1, true, [\"hello world\"]]);\n\n// disable relative numbers\nawait nvim.call(\"nvim_set_option_value\", [\"relativenumber\", false, {}]);\n\n// create a vertical split\nawait nvim.call(\"nvim_command\", [\"vs\"]);\n\n// print cursor position on cursor move\nawait nvim.call(\"nvim_create_autocmd\", [\n  [\"CursorHold\", \"CursorHoldI\"],\n  {\n    desc: \"Print Cursor Position\",\n    command: `lua\n            local cursor_pos = vim.api.nvim_win_get_cursor(0)\n            vim.print(cursor_pos)`,\n  },\n]);\n\nnvim.detach();\n```\n\nInitialize Neovim with the [RPC listening address](https://neovim.io/doc/user/starting.html#--listen) specified above:\n\n```bash\nnvim --listen /tmp/bunvim.nvim.socket\n```\n\nExecute your script from another terminal:\n\n```sh\nbun run my-plugin.ts\n```\n\nIf your plugin is executed as a child process of Neovim:\n\n```lua\n-- somewhere in your neovim lua config files\n\nlocal function run_script()\n\n    -- neovim sets the environment variable NVIM in all its child processes\n    -- NVIM = the RPC listening address assigned to the current neovim instance\n    -- neovim sets an RPC listening address if you don't manually specify one\n    -- https://neovim.io/doc/user/builtin.html#jobstart-env\n\n    vim.fn.jobstart(\"bun run my-plugin.ts\", {\n        cwd = vim.fn.expand(\"~/path/to/plugin/\"),\n    })\nend\n\nvim.api.nvim_create_user_command(\"RunMyScript\", run_script, {})\n```\n\nYou could then open Neovim without manually specifying an RPC listening address, just `nvim` and then run the command `:RunMyScript`.\nYour Bun process would then have access to the `NVIM` environment variable.\n\n```typescript\n// my-plugin.ts\nimport { attach } from \"bunvim\";\n\nconst SOCKET = process.env[\"NVIM\"];\nif (!SOCKET) throw Error(\"socket missing\");\n\nconst nvim = await attach({\n  socket: SOCKET,\n  client: { name: \"my-plugin-name\" },\n});\n```\n\n## 📖 API Reference\n\nThis module exports only one method `attach` and a bunch of TypeScript types. `attach` returns\nan `Nvim` object that can be used to interact with Neovim.\n\n\u003cdetails\u003e\n    \u003csummary\u003e\n        \u003ccode\u003envim.call(function: string, args: unknown[])\u003c/code\u003e\n    \u003c/summary\u003e\n\n\u003e\n\n\u003e Used to call [any of these functions](https://neovim.io/doc/user/api.html). They're all typed. You should\n\u003e get function names autocompletion \u0026 warnings from TypeScript if the parameters don't match the expected types.\n\u003e Some function calls return a value, others don't.\n\u003e\n\u003e ```typescript\n\u003e const bufferContent = await nvim.call(\"nvim_buf_get_lines\", [0, 0, -1, true]);\n\u003e ```\n\n\u003e ---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\n        \u003ccode\u003envim.channelId\u003c/code\u003e\n    \u003c/summary\u003e\n\n\u003e\n\n\u003e RPC Channel ID.\n\u003e\n\u003e ```typescript\n\u003e const channelId = nvim.channelId;\n\u003e\n\u003e await nvim.call(\"nvim_create_autocmd\", [\n\u003e   [\"CursorMove\"],\n\u003e   {\n\u003e     desc: \"Notify my-plugin\",\n\u003e     command: `lua\n\u003e         vim.rpcnotify(${channelId}, \"my-notification\")`,\n\u003e   },\n\u003e ]);\n\u003e ```\n\n\u003e ---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\n        \u003ccode\u003envim.onNotification(notification: string, callback: function)\u003c/code\u003e\n    \u003c/summary\u003e\n\n\u003e\n\n\u003e Registers a handler for a specific RPC Notification.\n\u003e\n\u003e Notifications must be typed before you declare a handler for them, or TypeScript will complain.\n\u003e\n\u003e ```typescript\n\u003e import { attach, type BaseEvents, type EventsMap } from \"bunvim\";\n\u003e\n\u003e // an interface to define your notifications and their args\n\u003e interface MyEvents extends BaseEvents {\n\u003e     requests: EventsMap; // default type\n\u003e     notifications: {\n\u003e         // declare custom notification: \"cursor_move\",\n\u003e         // that would be called with args: [row: number, col: number]\n\u003e         \"cursor_move\": [row: number, col: number];\n\u003e     };\n\u003e }\n\u003e\n\u003e // attach to neovim\n\u003e const nvim = await attach\u003cMyEvents\u003e({ ... })\n\u003e\n\u003e let count = 0;\n\u003e\n\u003e // register a handler for the notification \"cursor_move\"\n\u003e nvim.onNotification(\"cursor_move\", async ([row, col]) =\u003e {\n\u003e     // \"row\" and \"col\" are of type \"number\" as specified above\n\u003e\n\u003e     // CAUTION:\n\u003e     // it's up to you to make sure the handler receives the correct args,\n\u003e     // bunvim doesn't do any validations\n\u003e\n\u003e     // print row and col in neovim\n\u003e     await nvim.call(\"nvim_exec_lua\", [`print(\"row: ${row} - col: ${col}\")`, []]);\n\u003e\n\u003e     // return `true` to remove handler\n\u003e     return count++ \u003e= 5;\n\u003e });\n\u003e\n\u003e // multiple handlers can be registered for the same notification\n\u003e nvim.onNotification(\"cursor_move\", async ([row, col]) =\u003e {\n\u003e     // replace contents in current buffer lines 1 and 2\n\u003e     await nvim.call(\"nvim_buf_set_lines\", [0, 0, 2, true, [`row: ${row}`, `col: ${col}`]]);\n\u003e });\n\u003e\n\u003e const channelId = nvim.channelId;\n\u003e\n\u003e // create autocommand to notify our plugin via `vim.rpcnotify`\n\u003e // whenever the cursor moves\n\u003e await nvim.call(\"nvim_create_autocmd\", [\n\u003e     [\"CursorHold\", \"CursorHoldI\"],\n\u003e     {\n\u003e         desc: \"Notify on Cursor Move\",\n\u003e         command: `lua\n\u003e             local cursor_pos = vim.api.nvim_win_get_cursor(0)\n\u003e             local row = cursor_pos[1]\n\u003e             local col = cursor_pos[2]\n\u003e             vim.rpcnotify(${channelId}, \"cursor_move\", row, col)`,\n\u003e     },\n\u003e ]);\n\u003e ```\n\n\u003e ---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\n        \u003ccode\u003envim.onRequest(request: string, callback: function)\u003c/code\u003e\n    \u003c/summary\u003e\n\n\u003e\n\n\u003e Registers a handler for a specific RPC Request.\n\u003e\n\u003e Requests must be typed before you declare a handler for them, or TypeScript will complain.\n\u003e\n\u003e The difference between an RPC Notification and an RPC Request, is that requests block neovim\n\u003e until a response is returned. Notifications are non-blocking.\n\u003e\n\u003e ```typescript\n\u003e import { attach, type BaseEvents, type EventsMap } from \"bunvim\";\n\u003e import { gracefulShutdown } from \"./utils.ts\";\n\u003e\n\u003e // an interface to define your requests and their args\n\u003e interface MyEvents extends BaseEvents {\n\u003e     notifications: EventsMap; // default type\n\u003e     requests: {\n\u003e         // declare custom request: \"before_exit\",\n\u003e         // that would be called with args: [buffer_name: string]\n\u003e         \"before_exit\": [buffer_name: string];\n\u003e     };\n\u003e }\n\u003e\n\u003e // attach to neovim\n\u003e const nvim = await attach\u003cMyEvents\u003e({ ... })\n\u003e\n\u003e // register a handler for the request \"before_exit\"\n\u003e nvim.onRequest(\"before_exit\", async ([buffer_name]) =\u003e {\n\u003e     // \"buffer_name\" is of type \"string\" as specified above\n\u003e\n\u003e     // CAUTION:\n\u003e     // it's up to you to make sure the handler receives the correct args,\n\u003e     // bunvim doesn't do any validations\n\u003e\n\u003e     // this should actually never get called,\n\u003e     // because this handler gets overwritten below\n\u003e     console.log(\"buffer_name: \", buffer_name);\n\u003e\n\u003e     // we must return something to unblock neovim\n\u003e     return null;\n\u003e });\n\u003e\n\u003e // only one handler per request may be registered.\n\u003e // if you call `nvim.onRequest` for an already registered handler,\n\u003e // the older handler is replaced with the new one.\n\u003e nvim.onRequest(\"before_exit\", async ([buffer_name]) =\u003e {\n\u003e     gracefulShutdown(buffer_name);\n\u003e     return null;\n\u003e });\n\u003e\n\u003e const channelId = nvim.channelId;\n\u003e\n\u003e // create autocommand to call our function via `vim.rpcrequest`\n\u003e // whenever neovim is about to close\n\u003e await nvim.call(\"nvim_create_autocmd\", [\n\u003e     [\"VimLeavePre\"],\n\u003e     {\n\u003e         desc: \"RPC Request before exit\",\n\u003e         command: `lua\n\u003e             local buffer_name = vim.api.nvim_get_current_buf()\n\u003e             vim.rpcrequest(${channelId}, \"before_exit\", buffer_name)`,\n\u003e     },\n\u003e ]);\n\u003e ```\n\n\u003e ---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\n        \u003ccode\u003envim.detach()\u003c/code\u003e\n    \u003c/summary\u003e\n\n\u003e\n\n\u003e Closes connection with neovim.\n\u003e\n\u003e ```ts\n\u003e nvim.detach();\n\u003e ```\n\n\u003e ---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\n        \u003ccode\u003envim.logger\u003c/code\u003e\n    \u003c/summary\u003e\n\n\u003e\n\n\u003e Instance of [winston logger](https://github.com/winstonjs/winston).\n\u003e May be `undefined` if logging was not enabled.\n\u003e\n\u003e Used to log data to console and/or file. Does not log/print messages to Neovim.\n\u003e\n\u003e [See Logging](#%EF%B8%8F-logging).\n\u003e\n\u003e ```typescript\n\u003e // log functions sorted from highest to lowest priority:\n\u003e\n\u003e nvim.logger?.error(\"error message\");\n\u003e nvim.logger?.warn(\"warn message\");\n\u003e nvim.logger?.info(\"info message\");\n\u003e nvim.logger?.http(\"http message\");\n\u003e nvim.logger?.verbose(\"verbose message\");\n\u003e nvim.logger?.debug(\"debug message\");\n\u003e nvim.logger?.silly(\"silly message\");\n\u003e ```\n\n\u003e ---\n\n\u003c/details\u003e\n\n## 🖨️ Logging\n\nTo enable logging to **Console** and/or **File**, a `logging.level`\nmust be specified when calling the `attach` method.\n\n```typescript\nconst nvim = await attach({\n  socket: SOCKET,\n  client: { name: \"my-plugin-name\" },\n  logging: {\n    level: \"debug\", // \u003c= LOG LEVEL\n  },\n});\n\nnvim.logger?.info(\"hello world\");\n```\n\n**Bunvim** internally logs with `logger.debug()` and `logger.error()`.\nSet `logging.level` higher than `debug` to not display bunvim's internal logs when\nprinting logs for your plugin.\n\nLevels from highest to lowest priority:\n\n1.  error\n2.  warn\n3.  info\n4.  http\n5.  verbose\n6.  debug\n7.  silly\n\n### Console\n\nAfter setting a `logging.level`, you can see your logs live with the command `bunvim logs` and\nspecifying the `client.name` you defined in your `attach`.\n\nIn a terminal, run the command:\n\n```sh\n# this process will listen for logs and print them to the console\n# Ctrl-C to stop process\n\nbunx bunvim logs my-plugin-name\n```\n\nFor more information about the CLI tool, run the command:\n\n```sh\nbunx bunvim --help\n```\n\n### File\n\nYou can also write your logs to a `file` by specifying a path when calling the `attach` method:\n\n```typescript\nconst nvim = await attach({\n    socket: SOCKET,\n    client: { name: \"my-plugin-name\" },\n    logging: {\n        level: \"debug\", // \u003c= LOG LEVEL\n        file: : \"~/my-plugin-name.log\" // \u003c= PATH TO LOG FILE\n    },\n});\n```\n\n### Neovim\n\nIf you want to log/print a message to the user in Neovim, use:\n\n```typescript\nimport { NVIM_LOG_LEVELS } from \"bunvim\";\n\nawait nvim.call(\"nvim_notify\", [\"some message\", NVIM_LOG_LEVELS.INFO, {}]);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwallpants%2Fbunvim","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwallpants%2Fbunvim","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwallpants%2Fbunvim/lists"}