Skip to content

Commit b95ea62

Browse files
committed
Initial commit: openapi.nvim
0 parents  commit b95ea62

32 files changed

Lines changed: 1909 additions & 0 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
name: Bug report
3+
about: Report a problem with openapi.nvim
4+
title: "[bug] "
5+
labels: bug
6+
---
7+
8+
**Describe the bug**
9+
A clear and concise description of what the bug is.
10+
11+
**To reproduce**
12+
Steps to reproduce, ideally with a minimal OpenAPI spec snippet:
13+
14+
```yaml
15+
# minimal spec that triggers the issue
16+
```
17+
18+
1. Open the spec
19+
2. Move the cursor to ...
20+
3. Press ...
21+
22+
**Expected behaviour**
23+
What you expected to happen.
24+
25+
**Environment**
26+
- Neovim version (`nvim --version`):
27+
- openapi.nvim version / commit:
28+
- snacks.nvim present: yes / no
29+
- `yaml` treesitter parser installed: yes / no
30+
31+
**Additional context**
32+
Anything else that helps (screenshots, `:messages`, etc.).
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
name: Feature request
3+
about: Suggest an idea for openapi.nvim
4+
title: "[feature] "
5+
labels: enhancement
6+
---
7+
8+
**Problem**
9+
What are you trying to do that openapi.nvim doesn't support today?
10+
11+
**Proposed solution**
12+
A clear and concise description of what you'd like to happen.
13+
14+
**Alternatives considered**
15+
Other approaches or workarounds you've tried.
16+
17+
**Additional context**
18+
Anything else, e.g. how other tools handle this.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## Summary
2+
3+
<!-- What does this PR change and why? -->
4+
5+
## Changes
6+
7+
<!-- Bullet list of the notable changes. -->
8+
9+
-
10+
11+
## Checklist
12+
13+
- [ ] `stylua --check lua/ plugin/ tests/` passes
14+
- [ ] `luacheck lua/ plugin/` passes
15+
- [ ] `nvim --headless -u tests/minimal_init.lua -l tests/smoke.lua` passes
16+
- [ ] Updated `README.md` / `doc/openapi.txt` if behaviour changed
17+
- [ ] Added an entry to `CHANGELOG.md` under `[Unreleased]`

.github/workflows/ci.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
stylua:
10+
name: stylua
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: JohnnyMorganz/stylua-action@v4
15+
with:
16+
token: ${{ secrets.GITHUB_TOKEN }}
17+
version: latest
18+
args: --check lua/ plugin/ tests/
19+
20+
luacheck:
21+
name: luacheck
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v4
25+
- uses: leafo/gh-actions-lua@v10
26+
with:
27+
luaVersion: "luajit-2.1.0-beta3"
28+
- uses: leafo/gh-actions-luarocks@v4
29+
- name: Install luacheck
30+
run: luarocks install luacheck
31+
- name: Run luacheck
32+
run: luacheck lua/ plugin/
33+
34+
test:
35+
name: test (nvim ${{ matrix.neovim }})
36+
runs-on: ubuntu-latest
37+
strategy:
38+
fail-fast: false
39+
matrix:
40+
neovim: [stable, nightly]
41+
steps:
42+
- uses: actions/checkout@v4
43+
- name: Install Neovim
44+
uses: rhysd/action-setup-vim@v1
45+
with:
46+
neovim: true
47+
version: ${{ matrix.neovim }}
48+
- name: Cache test deps
49+
uses: actions/cache@v4
50+
with:
51+
path: .test-deps
52+
key: test-deps-${{ matrix.neovim }}-${{ hashFiles('tests/minimal_init.lua') }}
53+
- name: Run headless smoke test
54+
env:
55+
OPENAPI_TEST_DEPS: ${{ github.workspace }}/.test-deps
56+
run: |
57+
nvim --headless -u tests/minimal_init.lua -l tests/smoke.lua

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Lua
2+
*.luac
3+
4+
# Generated vim helptags
5+
/doc/tags
6+
7+
# lua-language-server local config
8+
.luarc.json
9+
10+
# OS
11+
.DS_Store

.luacheckrc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- luacheck configuration for openapi.nvim
2+
std = "luajit"
3+
cache = true
4+
codes = true
5+
6+
-- Globals provided by the Neovim runtime / plugins.
7+
read_globals = {
8+
"vim",
9+
"Snacks",
10+
}
11+
12+
-- Neovim's API frequently produces long lines; relax the limit.
13+
max_line_length = 120
14+
15+
exclude_files = {
16+
".luarocks",
17+
}

.stylua.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
column_width = 120
2+
line_endings = "Unix"
3+
indent_type = "Spaces"
4+
indent_width = 2
5+
quote_style = "AutoPreferDouble"
6+
call_parentheses = "Always"

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Added
11+
12+
- Operations picker (`:OpenapiOperations`, `<leader>oo`) listing every
13+
`METHOD /path` with its `operationId` and `summary`.
14+
- Components picker (`:OpenapiComponents [kind]`, `:OpenapiSchemas`,
15+
`<leader>os`) covering all `components.*` kinds, with optional kind filter.
16+
- Go to declaration (`gd`) from a `$ref` to its definition, with fallback to
17+
the default LSP definition behaviour.
18+
- Find references (`gr`) from a component definition or `$ref`, with fallback
19+
to the default LSP references behaviour.
20+
- `$ref`-under-cursor highlight via the `OpenapiRefUnderCursor` group.
21+
- Buffer-local detection of OpenAPI / Swagger YAML specs; keymaps and commands
22+
are scoped to detected buffers only.
23+
- Per-buffer model cache keyed by `changedtick`.
24+
25+
[Unreleased]: https://github.com/devdammit/openapi.nvim/commits/main

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 devdammit
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# openapi.nvim
2+
3+
![Neovim](https://img.shields.io/badge/Neovim-0.10+-57A143?logo=neovim&logoColor=white)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
5+
[![CI](https://github.com/devdammit/openapi.nvim/actions/workflows/ci.yml/badge.svg)](https://github.com/devdammit/openapi.nvim/actions/workflows/ci.yml)
6+
![Lua](https://img.shields.io/badge/Made%20with-Lua-2C2D72?logo=lua&logoColor=white)
7+
8+
Navigate OpenAPI / Swagger YAML specifications directly inside Neovim — no
9+
Swagger UI, no external tools. List operations, fuzzy-find schemas and
10+
components, and jump between `$ref` declarations and references just like you
11+
would with LSP.
12+
13+
Built for [LazyVim](https://www.lazyvim.org/) (Neovim 0.10+), powered by
14+
[nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) for
15+
parsing and [snacks.nvim](https://github.com/folke/snacks.nvim) for the picker
16+
UI.
17+
18+
<!-- Record a short clip showing the operations picker, gd on a $ref, and the
19+
$ref highlight, then drop it at assets/demo.gif -->
20+
21+
![demo](assets/demo.gif)
22+
23+
## Features
24+
25+
- **Operations picker** — browse every `METHOD /path` with its `operationId`
26+
and `summary`.
27+
- **Components picker** — fuzzy-search all `components.*` (schemas, parameters,
28+
responses, requestBodies, headers, examples, links, callbacks,
29+
securitySchemes, pathItems), optionally filtered by kind.
30+
- **Go to declaration (`gd`)** — jump from a `$ref` to its definition.
31+
- **Find references (`gr`)** — from a component definition (or a `$ref`), list
32+
every place that `$ref`s it.
33+
- **`$ref` highlight** — the reference target under the cursor is underlined
34+
with an accent colour so you can see at a glance what you're about to follow.
35+
- **Native keys, scoped to spec buffers**`gd`/`gr` are remapped only in
36+
buffers detected as OpenAPI specs, and fall back to the default LSP behaviour
37+
when the cursor isn't on a `$ref` or a component definition.
38+
39+
## Requirements
40+
41+
- **Neovim 0.10+**.
42+
- The `yaml` treesitter parser (`:TSInstall yaml` via nvim-treesitter).
43+
- `snacks.nvim` (bundled with LazyVim) for the picker UI. Without it the plugin
44+
falls back to `vim.ui.select`.
45+
46+
## Installation
47+
48+
### lazy.nvim
49+
50+
```lua
51+
-- ~/.config/nvim/lua/plugins/openapi.lua
52+
return {
53+
{
54+
"devdammit/openapi.nvim",
55+
ft = "yaml",
56+
dependencies = { "nvim-treesitter/nvim-treesitter" },
57+
opts = {},
58+
},
59+
}
60+
```
61+
62+
### Local development
63+
64+
```lua
65+
return {
66+
{
67+
dir = "~/path/to/openapi.nvim",
68+
ft = "yaml",
69+
opts = {},
70+
},
71+
}
72+
```
73+
74+
## Configuration
75+
76+
Defaults are shown below — pass any subset via `opts`:
77+
78+
```lua
79+
opts = {
80+
keys = {
81+
declaration = "gd", -- go to declaration on $ref
82+
references = "gr", -- find $ref usages
83+
operations = "<leader>oo", -- operations picker
84+
components = "<leader>os", -- components picker
85+
},
86+
-- Component kinds to index ("all" indexes every kind that appears).
87+
components = "all",
88+
-- How many leading lines to scan for `openapi:`/`swagger:` when detecting a spec.
89+
detect_lines = 50,
90+
}
91+
```
92+
93+
Set any key to `false` to disable that mapping.
94+
95+
## Commands
96+
97+
These are created buffer-locally in detected OpenAPI buffers:
98+
99+
| Command | Description |
100+
| --------------------------- | ------------------------------------------------- |
101+
| `:OpenapiOperations` | Open the operations picker |
102+
| `:OpenapiComponents [kind]` | Open the components picker (optional kind filter) |
103+
| `:OpenapiSchemas` | Components picker filtered to `schemas` |
104+
| `:OpenapiReferences` | Find references to the target under the cursor |
105+
106+
## Highlights
107+
108+
The `$ref` value under the cursor is highlighted with the `OpenapiRefUnderCursor`
109+
group (underline + bold + accent colour). It is defined with `default = true`,
110+
so your colorscheme or config can override it:
111+
112+
```lua
113+
vim.api.nvim_set_hl(0, "OpenapiRefUnderCursor", {
114+
underline = true,
115+
fg = "#7aa2f7",
116+
})
117+
```
118+
119+
## How detection works
120+
121+
A buffer is treated as an OpenAPI spec when it has `filetype=yaml` and one of
122+
its first `detect_lines` lines contains a top-level `openapi:` or `swagger:`
123+
key. Regular YAML files are left untouched.
124+
125+
## Notes & limitations
126+
127+
- **YAML only.** JSON specs are not parsed.
128+
- **Single-file `$ref` only (for now).** Internal refs (`#/components/...`) are
129+
fully supported. External / multi-file refs (`./schemas/User.yaml#/...`) are
130+
detected but navigation across files is not implemented yet — `gd` on an
131+
external ref reports that it's unsupported rather than guessing.
132+
- Parsing is resilient to partially-invalid YAML (e.g. while you're mid-edit):
133+
whatever treesitter can recover is indexed.
134+
135+
## Alternatives
136+
137+
- [armsnyder/openapi-language-server](https://github.com/armsnyder/openapi-language-server)
138+
— a standalone LSP server providing jump-to-definition / find-references for
139+
OpenAPI. `openapi.nvim` does this natively in-process via treesitter, with no
140+
separate server, plus snacks pickers and `$ref` highlighting.
141+
- [codeasashu/oas.nvim](https://github.com/codeasashu/oas.nvim) — focuses on
142+
rendering previews (Redoc) rather than in-editor navigation.
143+
144+
## Development
145+
146+
```sh
147+
# format check
148+
stylua --check lua/ plugin/
149+
150+
# lint
151+
luacheck lua/ plugin/
152+
```
153+
154+
There is no test framework dependency: the CI runs a headless Neovim smoke test
155+
that parses a sample spec and asserts the model, navigation, and highlight
156+
behaviour. See `.github/workflows/ci.yml`.
157+
158+
## Architecture
159+
160+
```
161+
lua/openapi/
162+
init.lua setup() + FileType / LspAttach autocmds
163+
config.lua defaults + merge
164+
detect.lua is_openapi_buf()
165+
attach.lua buffer-local keymaps + commands
166+
highlight.lua $ref-under-cursor highlight
167+
cache.lua per-buffer model cache (keyed by changedtick)
168+
util.lua jump + picker availability
169+
parser/
170+
init.lua parse(bufnr) -> model (cached)
171+
ts.lua YAML AST traversal helpers
172+
model.lua operations / components / refs / targets
173+
ref.lua $ref parsing + JSON-pointer handling
174+
nav/
175+
context.lua resolve target under the cursor
176+
declaration.lua gd
177+
reference.lua gr
178+
pickers/
179+
operations.lua
180+
components.lua
181+
```
182+
183+
## License
184+
185+
[MIT](./LICENSE)

0 commit comments

Comments
 (0)