{"id":23593081,"url":"https://github.com/junekelly/openinneovim.spoon","last_synced_at":"2026-05-18T17:03:51.253Z","repository":{"id":269842257,"uuid":"908628472","full_name":"JuneKelly/OpenInNeovim.spoon","owner":"JuneKelly","description":"A HammerSpoon plugin to open a file in neovim (for use with Phoenix LiveView)","archived":false,"fork":false,"pushed_at":"2025-01-04T15:21:02.000Z","size":1676,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-14T00:32:04.589Z","etag":null,"topics":["hammerspoon","neovim","nvim","phoenix","phoenix-liveview"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JuneKelly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2024-12-26T14:45:20.000Z","updated_at":"2025-06-10T06:47:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"a609f682-4bfd-4c35-87c7-270b515c514f","html_url":"https://github.com/JuneKelly/OpenInNeovim.spoon","commit_stats":null,"previous_names":["junekelly/openinneovim.spoon"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/JuneKelly/OpenInNeovim.spoon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuneKelly%2FOpenInNeovim.spoon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuneKelly%2FOpenInNeovim.spoon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuneKelly%2FOpenInNeovim.spoon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuneKelly%2FOpenInNeovim.spoon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JuneKelly","download_url":"https://codeload.github.com/JuneKelly/OpenInNeovim.spoon/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuneKelly%2FOpenInNeovim.spoon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33184769,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T09:27:30.708Z","status":"ssl_error","status_checked_at":"2026-05-18T09:27:28.300Z","response_time":71,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["hammerspoon","neovim","nvim","phoenix","phoenix-liveview"],"created_at":"2024-12-27T08:18:05.466Z","updated_at":"2026-05-18T17:03:51.218Z","avatar_url":"https://github.com/JuneKelly.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HammerSpoon - Open in Neovim\n\nSet up a [HammerSpoon](https://www.hammerspoon.org/) URL event to open a file\nin neovim. Can be used with\n[phoenix-live-reload](https://github.com/phoenixframework/phoenix_live_reload)\nto jump to the definition (or caller) of a phoenix live-view component.\n\n![A demo of OpenInNeovim](./open-in-neovim-demo.gif)\n\n- [Installation](#installation)\n- [API Documentation](#api-documentation)\n  - [Configuration Options](#configuration-options)\n  - [URL Format](#url-format)\n- [Usage Examples](#usage-examples)\n  - [Prerequisites](#prerequisites)\n  - [Minimal Example: One Instance of Neovim](#minimal-example-one-instance-of-neovim)\n  - [Realistic Example: Multiple Instances of Neovim](#realistic-example-multiple-instances-of-neovim)\n- [License](#license)\n\n## Installation\n\nFirst, install [HammerSpoon](https://www.hammerspoon.org/).\n\nThen, clone this repository to `~/.hammerspoon/Spoons/OpenInNeovim.spoon`, like\nso...\n\n```sh\ngit clone https://github.com/JuneKelly/OpenInNeovim.spoon ~/.hammerspoon/Spoons/OpenInNeovim.spoon\n```\n\n## API Documentation\n\nIn hammerspoon code (either in the console, or in `~/.hammerspoon/init.lua`),\nthis spoon can be loaded like so:\n\n```lua\nopenInNeovim = hs.loadSpoon(\"OpenInNeovim\")\n```\n\nTo bind a URL event handler, call `openInNeovim.bind`, with a table of\nconfiguration options.\n\n### Configuration Options\n\n- `token`: (required, minimum length 12 characters) the URL _must_ include this\ntoken as a query parameter `token`. If the URL does not contain this parameter,\nor it does not match, then the error will be shown in a notification.\n  - Why is this required? As much as we try to defend against security issues,\n  in some sense this feature is fundamentally built around telling `nvim` to\n  execute some code, so it is best to guard the entry-point so it can only be\n  activated by URLs having this secret token.\n\n- `nvimPath`: (required) full path to the `nvim` executable\n  - (this can be found easily by running `command -v nvim` in your shell)\n\n- `nvimServerPipePath`: (required) full path to the `nvim` server pipe file\n  - (this is the file path you provided when starting `nvim` with the\n  `--listen` flag)\n\n- `eventName`: (optional, default `\"openInNeovim\"`) name of the hammerspoon\nevent, which in practice means the part of the URL that comes after\n`hammerspoon://`.\n\n- `foregroundApp`: (optional, default `nil`) if present, bring this app to the\nforeground after the file has been opened. Must be the name of a MacOS app,\nlike `\"iTerm2\"` or `Ghostty`\n\n- `translateRootPath`: (optional, default `nil`) a table with two fields:\n`from`, and `to`. If non-nil, the file path is altered to replace the segment\nmatching `from` at the start, with to string `to`. Useful if your phoenix\nserver runs in a docker environment where it's filesystem is different from the\nhost where your `nvim` editor is running\n\n- `skipValidateFileExists`: (optional, default `false`) By default, we check\nthat the target file path is a real path, and exists, before sending the\ncommand to `nvim`. This helps defend against remote code execution attacks. If\nthis option is set to `true`, the validation is skipped. This could be useful\nif the `nvim` instance is operating on a different filesystem from hammerspoon.\n\nHere's an example using all of the configuration options:\n\n```lua\nopenInNeovim = hs.loadSpoon(\"OpenInNeovim\")\n\nopenInNeovim.bind({\n  nvimPath = \"/opt/homebrew/bin/nvim\",\n  nvimServerPipePath = \"/Users/somebody/.cache/nvim/server.pipe\",\n  token = \"a_dreadful_secret\",\n  foregroundApp = \"iTerm2\",\n  eventName = \"aNiceCustomEventName\",\n  translateRootPath = {\n    from = \"/app/inside/docker/\",\n    to = \"/Users/somebody/projects/cool-web-app/\"\n  }\n})\n```\n\n### URL Format\n\nThis event handler is triggered by opening a URL that looks like:\n\n```txt\nhammerspoon://openInNeovim?file=\u003cFile Path\u003e\u0026line=\u003cLine Number\u003e\n```\n\nThe following query parameters are supported:\n\n- `file`: (required) path to the file to open (URL encoded)\n- `line`: (required) line number to open\n- `token`: (optional) secret token to check against `config.token`\n\n## Usage Examples\n\n### Prerequisites\n\nAs a prerequisite, we need to find the full path to the `nvim` executable:\n\n```sh\ncommand -v nvim\n```\n\nIf you've installed neovim via [homebrew](https://brew.sh), then the result is probably something like `/opt/homebrew/bin/nvim`.\n\n### Minimal Example: One Instance of Neovim\n\nIn this example, we have one instance of `nvim`, acting as a server. We then\nconfigure OpenInNeovim to open files in this single `nvim` server. If you tend\nto work on one project at a time, this should be sufficient.\n\n#### 1. Start `nvim` with `--listen`, and a path to a pipe file\n\nTo start `nvim` in server mode, we pass the `--listen \u003cpath\u003e` parameter, where\n`\u003cpath\u003e` is a path to a pipe file, which neovim will create:\n\n```sh\nnvim --listen ~/.cache/nvim/server.pipe\n```\n\nYou can make this easier to do repeatedly by creating an alias. For example, in\n`zsh`:\n\n```sh\n# add this to .zshrc\nalias nvim-server 'nvim --listen ~/.cache/nvim/server.pipe'\n```\n\n...or in `fish`:\n\n```sh\n# run this once in fish shell\nalias --save nvim-server='nvim --listen ~/.cache/nvim/server.pipe'\n```\n\n#### 2. Generate a secret token\n\nWe need a secret token, to secure this URL endpoint. An easy way to do this is\nby running `uuidgen` in the shell:\n\n```sh\nuuidgen\n# =\u003e 07048977-9...\n```\n\n#### 3. Configure OpenInNeovim, in the Hammerspoon config file\n\nAdd the following to `~/.hammerspoon/init.lua`:\n\n```lua\nopenInNeovim = hs.loadSpoon(\"OpenInNeovim\")\n\nopenInNeovim.bind({\n nvimPath = \"\u003cfull path to nvim executable\u003e\",\n nvimServerPipePath = \"\u003cfull path to nvim server pipe\u003e\",\n token = \"\u003crandom token string\u003e\",\n})\n```\n\nQuit and re-open Hammerspoon. Look in the Hammerspoon console, and you should\nsee log lines indicating that OpenInNeovim has been loaded, and a URL handler\nhas been bound:\n\n```txt\n2024-12-26 14:26:31: -- Loading Spoon: OpenInNeovim\n2024-12-26 14:26:31: [OpenInNeovim] Bind {\n  ...\n}\n2024-12-26 14:26:31: [OpenInNeovim] Binding to URL 'openInNeovim'\n```\n\n#### 4. Configure `phoenix_live_reload` to trigger this URL event\n\nSee the [\"Jumping to HEEX Function\nDefinitions\"](https://github.com/phoenixframework/phoenix_live_reload?tab=readme-ov-file#jumping-to-heex-function-definitions)\nsection of the `phoenix_live_reload` README file.\n\n```txt\nPLUG_EDITOR = 'hammerspoon://openInNeovim?token=\u003cTOKEN\u003e\u0026file=__FILE__\u0026line=__LINE__'\n```\n\nNow, when you hold `d` and click a phoenix live-view component in the browser,\nit _should_ open the component definition in neovim, and show a notification to\nthat effect. If not, check the hammerspoon logs.\n\n### Realistic Example: Multiple Instances of Neovim\n\nThe previous example doesn't work so well if we tend to keep multiple instances of neovim open at a given time, like if we work on several projects at once. In this case, we want to start multiple neovim servers, one for each project, and configure multiple instances of OpenInNeovim, each pointing to the relevant neovim server.\n\nLet's imagine that we regularly work on two phoenix projects: `statler` and `waldorf`. Both of these projects live in our `~/code` directory:\n\n- `/Users/somebody/code/statler`\n- `/Users/somebody/code/waldorf`\n\n#### 1. Start `nvim` with `--listen`, and a pipe file derived from the PWD\n\nFirst, we need a way to start an `nvim` server with a pipe file at a predictable location on the file-system. We can solve this problem by taking the following steps:\n\n1. Take the current working directory from `pwd`, as a string\n2. Change all `/` characters to `_`, to form a \"slug\" representation of the working directory\n3. Use this slug to name a pipe file in the user's temp directory (`$TMPDIR`)\n\nFor example, in `zsh`:\n\n```sh\necho \"${TMPDIR}nvim-server$(pwd | sed 's/\\//_/g').pipe\"\n```\n\nThis will produce a string like:\n\n```txt\n/var/folders/vr/c_awj73s1264r2bz1f72y99m1241zl/T/nvim-server_Users_somebody_code_waldorf.pipe\n```\n\nIf we create our pipe file in this way, we can identify separate neovim servers\nin separate project directories:\n\n```sh\n# in zsh\nnvim --listen \"${TMPDIR}nvim-server$(pwd | sed 's/\\//_/g').pipe\"\n\n# in fish\nnvim --listen (string join '' $TMPDIR \"nvim-server\" (pwd | sed 's/\\\\//_/g') \".pipe\")\n```\n\nTo make this invocation easier, we can create a shell function.\n\nIf you're using `zsh` (the default shell on recent versions of macOS), add the following to `~/.zshrc`:\n\n```sh\nfunction nvim-server() {\n  nvim --listen \"${TMPDIR}nvim-server$(pwd | sed 's/\\//_/g').pipe\" \"$@\"\n}\n```\n\nOr, if you use `fish`, add the following to `~/.config/fish/functions/nvim-server.fish`:\n\n```sh\nfunction nvim-server --wraps='nvim' --description 'Start nvim server with pipe file based on PWD'\n    nvim --listen (string join '' $TMPDIR \"nvim-server\" (pwd | sed 's/\\\\//_/g') \".pipe\") $argv\nend\n```\n\nOpen a new shell, run `nvim-server`, and look in `$TMPDIR` (by running `ls -la $TMPDIR`). You should see a file ending in `.pipe`, with a name based on the directory in which you ran `nvim-server`.\n\nFor example, if we were to navigate to each of our demo projects (`statler` and `waldorf`), and run `nvim-server` in each of them, when we run `ls -la $TMPDIR` we should see two files like:\n\n```txt\n... nvim-server_Users_somebody_code_statler.pipe\n... nvim-server_Users_somebody_code_waldorf.pipe\n```\n\n#### 2. Generate a secret token\n\nWe need one secret token for each project, to secure each URL endpoint. An easy way to do this is\nby running `uuidgen` in the shell multiple times:\n\n```sh\nuuidgen\n# =\u003e 07048977-9...\n\nuuidgen\n# =\u003e AC7EA84C-6...\n```\n\n#### 3. Configure OpenInNeovim for each project\n\nLet's configure OpenInNeovim for each of our projects, `statler` and `waldorf`.\n\nAdd the following to `~/.hammerspoon/init.lua`, replacing the placeholder\nstrings with your own values:\n\n```lua\n\nopenInNeovim = hs.loadSpoon(\"OpenInNeovim\")\n\nlocal function pipePath(path)\n return os.getenv(\"TMPDIR\") .. \"nvim-server\" .. path:gsub(\"/\", \"_\") .. \".pipe\"\nend\n\nlocal nvimPath = \"\u003cfull path to nvim executable\u003e\"\n\nopenInNeovim.bind({\n eventName = \"openInNeovim_statler\",\n nvimPath = nvimPath,\n nvimServerPipePath = pipePath(\"/Users/somebody/code/statler\"),\n token = \"\u003ca random string\u003e\",\n foregroundApp = \"iTerm2\",\n})\n\nopenInNeovim.bind({\n eventName = \"openInNeovim_waldorf\",\n nvimPath = nvimPath,\n nvimServerPipePath = pipePath(\"/Users/somebody/code/waldorf\"),\n token = \"\u003ca different random string\u003e\",\n foregroundApp = \"iTerm2\",\n})\n```\n\n#### 4. Configure `phoenix_live_reload` in each project\n\nFinally, in each of our projects, we can set `PLUG_EDITOR`:\n\nIn the `statler` project:\n\n```txt\nPLUG_EDITOR = 'hammerspoon://openInNeovim_statler?token=\u003cTOKEN\u003e\u0026file=__FILE__\u0026line=__LINE__'\n```\n\nAnd in the `waldorf` project:\n\n```txt\nPLUG_EDITOR = 'hammerspoon://openInNeovim_waldorf?token=\u003cTOKEN\u003e\u0026file=__FILE__\u0026line=__LINE__'\n```\n\n(See the [\"Jumping to HEEX Function\nDefinitions\"](https://github.com/phoenixframework/phoenix_live_reload?tab=readme-ov-file#jumping-to-heex-function-definitions)\nsection of the `phoenix_live_reload` README file.)\n\n## License\n\nThis software is published under the MIT license:\n\nCopyright 2025 June Kelly\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the “Software”), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjunekelly%2Fopeninneovim.spoon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjunekelly%2Fopeninneovim.spoon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjunekelly%2Fopeninneovim.spoon/lists"}