mirror of
https://github.com/macocianradu/nvim-http.git
synced 2026-03-18 21:00:05 +00:00
MVP
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Local JSONPlaceholder fixtures
|
||||||
|
/testdata/jsonplaceholder/
|
||||||
166
README.md
Normal file
166
README.md
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# nvim-http
|
||||||
|
|
||||||
|
A Neovim plugin for quick HTTP execution from your notes/code buffers.
|
||||||
|
|
||||||
|
## Current status
|
||||||
|
|
||||||
|
Current features:
|
||||||
|
|
||||||
|
- `setup()` configuration
|
||||||
|
- Run HTTP commands under cursor
|
||||||
|
- Optional YAML `header(s)` and `body`/`request` blocks under the request line
|
||||||
|
- Telescope response tree with expandable `headers` and nested JSON `result`
|
||||||
|
- `:NvimHttpRun` and `:NvimHttpClear` commands
|
||||||
|
- `:help nvim-http` docs
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
"your-name/nvim-http",
|
||||||
|
dependencies = {
|
||||||
|
"nvim-lua/plenary.nvim",
|
||||||
|
"nvim-telescope/telescope.nvim",
|
||||||
|
},
|
||||||
|
build = "rockspec",
|
||||||
|
config = function()
|
||||||
|
require("nvim_http").setup()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Put cursor on a line like:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
curl https://httpbin.org/get
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
:NvimHttpRun
|
||||||
|
```
|
||||||
|
|
||||||
|
This opens a Telescope window with:
|
||||||
|
- `response_code`
|
||||||
|
- a collapsed `request` block (`url`, `headers`, `body`)
|
||||||
|
- a collapsed `headers` block
|
||||||
|
- a collapsed `result` block (nested JSON tree when response is JSON)
|
||||||
|
|
||||||
|
Use `<CR>` or `<Tab>` to expand/collapse the selected node and `q` to close.
|
||||||
|
|
||||||
|
Default keymap:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
<leader>hr
|
||||||
|
```
|
||||||
|
|
||||||
|
Default clear keymap:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
<leader>hc
|
||||||
|
```
|
||||||
|
|
||||||
|
This also supports lines like:
|
||||||
|
|
||||||
|
- `GET https://httpbin.org/get`
|
||||||
|
- `https://httpbin.org/get` (auto-translated to `curl -sS <url>`)
|
||||||
|
|
||||||
|
### YAML blocks under request line
|
||||||
|
|
||||||
|
You can add request headers/body directly under the request line using YAML blocks:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST https://jsonplaceholder.typicode.com/users
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer token
|
||||||
|
body:
|
||||||
|
name: Jane
|
||||||
|
username: jane1
|
||||||
|
```
|
||||||
|
|
||||||
|
Also accepted block names:
|
||||||
|
- `header` or `headers`
|
||||||
|
- `body` or `request`
|
||||||
|
|
||||||
|
Parsing rules:
|
||||||
|
- only lines directly under the request are scanned (bounded lookahead; not whole file)
|
||||||
|
- leading whitespace is ignored
|
||||||
|
- only the first `header(s)` and first `body`/`request` block are used
|
||||||
|
- parsing stops once both blocks are found
|
||||||
|
- parsing also stops if another top-level block appears after parsing started
|
||||||
|
- request blocks are currently applied to curl-style requests
|
||||||
|
- YAML block decoding requires the `lyaml` Lua rock
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```text
|
||||||
|
url
|
||||||
|
header:
|
||||||
|
X-Test: 1
|
||||||
|
body:
|
||||||
|
a: 1
|
||||||
|
```
|
||||||
|
uses both `header` and `body`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
url
|
||||||
|
header:
|
||||||
|
X-Test: 1
|
||||||
|
body:
|
||||||
|
a: 1
|
||||||
|
header2:
|
||||||
|
ignored: true
|
||||||
|
```
|
||||||
|
stops after `body`, so `header2` is ignored.
|
||||||
|
|
||||||
|
```text
|
||||||
|
url
|
||||||
|
header:
|
||||||
|
X-Test: 1
|
||||||
|
header2:
|
||||||
|
nope: 1
|
||||||
|
body:
|
||||||
|
ignored: true
|
||||||
|
```
|
||||||
|
stops when `header2` is reached, so `body` is ignored.
|
||||||
|
|
||||||
|
```text
|
||||||
|
url
|
||||||
|
body:
|
||||||
|
a: 1
|
||||||
|
header:
|
||||||
|
X-Test: 1
|
||||||
|
```
|
||||||
|
works (body-first order is accepted).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```lua
|
||||||
|
require("nvim_http").setup({
|
||||||
|
http = {
|
||||||
|
enabled = true,
|
||||||
|
execute_keymap = "<leader>hr",
|
||||||
|
clear_keymap = "<leader>hc",
|
||||||
|
timeout_ms = 10000,
|
||||||
|
highlight_group = "Comment",
|
||||||
|
command_patterns = {
|
||||||
|
"^curl%s+",
|
||||||
|
"^http%s+",
|
||||||
|
"^wget%s+",
|
||||||
|
"^%u+%s+https?://",
|
||||||
|
"^https?://",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
- `plugin/nvim-http.lua`: User command registration
|
||||||
|
- `lua/nvim_http/init.lua`: Plugin API and HTTP runner
|
||||||
|
- `lua/nvim_http/config.lua`: Defaults and config merging
|
||||||
|
- `doc/nvim-http.txt`: Help documentation
|
||||||
148
doc/nvim-http.txt
Normal file
148
doc/nvim-http.txt
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
*nvim-http.txt* nvim-http
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
CONTENTS *nvim-http-contents*
|
||||||
|
|
||||||
|
1. Introduction.............................................|nvim-http-intro|
|
||||||
|
2. Setup....................................................|nvim-http-setup|
|
||||||
|
3. Commands.................................................|nvim-http-commands|
|
||||||
|
4. HTTP Runner..............................................|nvim-http-http|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
INTRODUCTION *nvim-http-intro*
|
||||||
|
|
||||||
|
nvim-http supports:
|
||||||
|
- Running HTTP commands from the current line under cursor
|
||||||
|
- Parsing optional YAML header/body blocks under the request line
|
||||||
|
- Showing command output in a Telescope tree window
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
SETUP *nvim-http-setup*
|
||||||
|
|
||||||
|
Using lazy.nvim:
|
||||||
|
|
||||||
|
>lua
|
||||||
|
{
|
||||||
|
"macocianradu/nvim-http",
|
||||||
|
dependencies = {
|
||||||
|
"nvim-lua/plenary.nvim",
|
||||||
|
"nvim-telescope/telescope.nvim",
|
||||||
|
},
|
||||||
|
build = "rockspec",
|
||||||
|
config = function()
|
||||||
|
require("nvim_http").setup({
|
||||||
|
http = {
|
||||||
|
enabled = true,
|
||||||
|
execute_keymap = "<leader>hr",
|
||||||
|
clear_keymap = "<leader>hc",
|
||||||
|
timeout_ms = 10000,
|
||||||
|
highlight_group = "Comment",
|
||||||
|
command_patterns = {
|
||||||
|
"^curl%s+",
|
||||||
|
"^http%s+",
|
||||||
|
"^wget%s+",
|
||||||
|
"^%u+%s+https?://",
|
||||||
|
"^https?://",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
<
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
COMMANDS *nvim-http-commands*
|
||||||
|
|
||||||
|
:NvimHttpRun
|
||||||
|
Run the HTTP command under cursor and open a Telescope window with:
|
||||||
|
- response_code
|
||||||
|
- a collapsed request block (url, headers, body)
|
||||||
|
- a collapsed headers block
|
||||||
|
- a collapsed result block (nested JSON tree when response is JSON)
|
||||||
|
|
||||||
|
:NvimHttpClear
|
||||||
|
Close and clear the HTTP result Telescope window.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
HTTP RUNNER *nvim-http-http*
|
||||||
|
|
||||||
|
Default mapping:
|
||||||
|
<leader>hr
|
||||||
|
|
||||||
|
Default clear mapping:
|
||||||
|
<leader>hc
|
||||||
|
|
||||||
|
Result window usage:
|
||||||
|
<CR> or <Tab> to open/close/toggle the selected node
|
||||||
|
q to close the result window
|
||||||
|
|
||||||
|
Supported line styles:
|
||||||
|
curl https://example.com
|
||||||
|
http GET https://example.com
|
||||||
|
wget https://example.com
|
||||||
|
GET https://example.com
|
||||||
|
https://example.com
|
||||||
|
|
||||||
|
Lines in markdown lists/quotes/inline code are normalized first, so these also
|
||||||
|
work:
|
||||||
|
- curl https://example.com
|
||||||
|
`GET https://example.com`
|
||||||
|
> https://example.com
|
||||||
|
|
||||||
|
YAML request blocks under request line:
|
||||||
|
POST https://jsonplaceholder.typicode.com/users
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer token
|
||||||
|
body:
|
||||||
|
name: Jane
|
||||||
|
username: jane1
|
||||||
|
|
||||||
|
Accepted block names:
|
||||||
|
header / headers
|
||||||
|
body / request
|
||||||
|
|
||||||
|
Parsing behavior:
|
||||||
|
- scans only directly-below lines with bounded lookahead (does not scan file)
|
||||||
|
- ignores leading whitespace
|
||||||
|
- uses only first header(s) block and first body/request block
|
||||||
|
- stops when both are found
|
||||||
|
- also stops when another top-level block is found after parsing started
|
||||||
|
- request blocks are currently applied to curl-style requests
|
||||||
|
- YAML block decoding requires the lyaml Lua rock
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
url
|
||||||
|
header:
|
||||||
|
X-Test: 1
|
||||||
|
body:
|
||||||
|
a: 1
|
||||||
|
-> uses both
|
||||||
|
|
||||||
|
url
|
||||||
|
header:
|
||||||
|
X-Test: 1
|
||||||
|
body:
|
||||||
|
a: 1
|
||||||
|
header2:
|
||||||
|
ignored: true
|
||||||
|
-> stops after body, ignores header2
|
||||||
|
|
||||||
|
url
|
||||||
|
header:
|
||||||
|
X-Test: 1
|
||||||
|
header2:
|
||||||
|
nope: 1
|
||||||
|
body:
|
||||||
|
ignored: true
|
||||||
|
-> stops at header2, ignores body
|
||||||
|
|
||||||
|
url
|
||||||
|
body:
|
||||||
|
a: 1
|
||||||
|
header:
|
||||||
|
X-Test: 1
|
||||||
|
-> body-first order works
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
vim:tw=78:ts=8:ft=help:norl:
|
||||||
6
doc/tags
Normal file
6
doc/tags
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
nvim-http-commands nvim-http.txt /*nvim-http-commands*
|
||||||
|
nvim-http-contents nvim-http.txt /*nvim-http-contents*
|
||||||
|
nvim-http-http nvim-http.txt /*nvim-http-http*
|
||||||
|
nvim-http-intro nvim-http.txt /*nvim-http-intro*
|
||||||
|
nvim-http-setup nvim-http.txt /*nvim-http-setup*
|
||||||
|
nvim-http.txt nvim-http.txt /*nvim-http.txt*
|
||||||
24
lua/nvim_http/config.lua
Normal file
24
lua/nvim_http/config.lua
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.defaults = {
|
||||||
|
http = {
|
||||||
|
enabled = true,
|
||||||
|
execute_keymap = "<leader>hr",
|
||||||
|
clear_keymap = "<leader>hc",
|
||||||
|
timeout_ms = 10000,
|
||||||
|
highlight_group = "Comment",
|
||||||
|
command_patterns = {
|
||||||
|
"^curl%s+",
|
||||||
|
"^http%s+",
|
||||||
|
"^wget%s+",
|
||||||
|
"^%u+%s+https?://",
|
||||||
|
"^https?://",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function M.merge(opts)
|
||||||
|
return vim.tbl_deep_extend("force", {}, M.defaults, opts or {})
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
767
lua/nvim_http/init.lua
Normal file
767
lua/nvim_http/init.lua
Normal file
@@ -0,0 +1,767 @@
|
|||||||
|
local config = require("nvim_http.config")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
local http_ns = vim.api.nvim_create_namespace("nvim_http")
|
||||||
|
local result_prompt_bufnr = nil
|
||||||
|
|
||||||
|
M.options = config.merge()
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.options = config.merge(opts)
|
||||||
|
|
||||||
|
if M.options.http.enabled and M.options.http.execute_keymap and M.options.http.execute_keymap ~= "" then
|
||||||
|
vim.keymap.set("n", M.options.http.execute_keymap, M.run_http_under_cursor, {
|
||||||
|
desc = "Run HTTP call under cursor",
|
||||||
|
silent = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
if M.options.http.enabled and M.options.http.clear_keymap and M.options.http.clear_keymap ~= "" then
|
||||||
|
vim.keymap.set("n", M.options.http.clear_keymap, M.clear_http_result, {
|
||||||
|
desc = "Clear HTTP call output",
|
||||||
|
silent = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.clear_http_result()
|
||||||
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
vim.api.nvim_buf_clear_namespace(bufnr, http_ns, 0, -1)
|
||||||
|
|
||||||
|
if result_prompt_bufnr and vim.api.nvim_buf_is_valid(result_prompt_bufnr) then
|
||||||
|
local ok_actions, actions = pcall(require, "telescope.actions")
|
||||||
|
if ok_actions then
|
||||||
|
pcall(actions.close, result_prompt_bufnr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result_prompt_bufnr = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function trim(text)
|
||||||
|
return text:match("^%s*(.-)%s*$")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function strip_markdown_prefixes(line)
|
||||||
|
local cleaned = trim(line)
|
||||||
|
cleaned = cleaned:gsub("^[-*+]%s+", "")
|
||||||
|
cleaned = cleaned:gsub("^%d+[.)]%s+", "")
|
||||||
|
cleaned = cleaned:gsub("^>%s*", "")
|
||||||
|
cleaned = cleaned:gsub("^`+", "")
|
||||||
|
cleaned = cleaned:gsub("`+$", "")
|
||||||
|
return trim(cleaned)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function normalize_http_command(line)
|
||||||
|
local command = strip_markdown_prefixes(line)
|
||||||
|
|
||||||
|
if command == "" or command:match("^```") then
|
||||||
|
return nil, "no command found on this line"
|
||||||
|
end
|
||||||
|
|
||||||
|
local method, url = command:match("^(%u+)%s+(https?://%S+)$")
|
||||||
|
if method and url then
|
||||||
|
return string.format("curl -sS -X %s %s", method, vim.fn.shellescape(url))
|
||||||
|
end
|
||||||
|
|
||||||
|
if command:match("^https?://") then
|
||||||
|
return string.format("curl -sS %s", vim.fn.shellescape(command))
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, pattern in ipairs(M.options.http.command_patterns or {}) do
|
||||||
|
if command:match(pattern) then
|
||||||
|
return command
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, "line is not an HTTP command"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function leading_spaces(text)
|
||||||
|
local _, finish = text:find("^%s*")
|
||||||
|
return finish or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function block_kind_from_key(key)
|
||||||
|
local lowered = key:lower()
|
||||||
|
if lowered == "header" or lowered == "headers" then
|
||||||
|
return "headers"
|
||||||
|
end
|
||||||
|
if lowered == "body" or lowered == "request" then
|
||||||
|
return "body"
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function decode_yaml_block(block_lines, root_key)
|
||||||
|
local ok_lyaml, lyaml = pcall(require, "lyaml")
|
||||||
|
if not ok_lyaml then
|
||||||
|
return nil, "lyaml is not installed"
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, decoded = pcall(lyaml.load, table.concat(block_lines, "\n"))
|
||||||
|
if not ok then
|
||||||
|
return nil, "invalid yaml block under request line: " .. tostring(decoded)
|
||||||
|
end
|
||||||
|
|
||||||
|
local doc = decoded
|
||||||
|
if type(decoded) == "table" and decoded[root_key] == nil and type(decoded[1]) == "table" then
|
||||||
|
doc = decoded[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(doc) ~= "table" or doc[root_key] == nil then
|
||||||
|
return nil, "invalid yaml block under request line"
|
||||||
|
end
|
||||||
|
|
||||||
|
return doc[root_key], nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_block(lines, start_idx)
|
||||||
|
local block_lines = { lines[start_idx] }
|
||||||
|
local base_indent = leading_spaces(lines[start_idx])
|
||||||
|
local index = start_idx + 1
|
||||||
|
|
||||||
|
while index <= #lines do
|
||||||
|
local line = lines[index]
|
||||||
|
local trimmed = trim(line)
|
||||||
|
if trimmed == "" then
|
||||||
|
block_lines[#block_lines + 1] = line
|
||||||
|
index = index + 1
|
||||||
|
else
|
||||||
|
local indent = leading_spaces(line)
|
||||||
|
if indent <= base_indent then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
block_lines[#block_lines + 1] = line
|
||||||
|
index = index + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return block_lines, index
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_request_blocks(bufnr, row)
|
||||||
|
local line_count = vim.api.nvim_buf_line_count(bufnr)
|
||||||
|
local start_line = row + 1
|
||||||
|
if start_line >= line_count then
|
||||||
|
return { headers = nil, body = nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
local end_line = math.min(line_count, start_line + 80)
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(bufnr, start_line, end_line, false)
|
||||||
|
|
||||||
|
local idx = 1
|
||||||
|
while idx <= #lines and trim(lines[idx]) == "" do
|
||||||
|
idx = idx + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local blocks = { headers = nil, body = nil }
|
||||||
|
local parse_error = nil
|
||||||
|
local found_any = false
|
||||||
|
|
||||||
|
while idx <= #lines do
|
||||||
|
local line = lines[idx]
|
||||||
|
local trimmed = trim(line)
|
||||||
|
if trimmed == "" then
|
||||||
|
idx = idx + 1
|
||||||
|
else
|
||||||
|
local key = trimmed:match("^([%w_-]+)%s*:")
|
||||||
|
if not key then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local kind = block_kind_from_key(key)
|
||||||
|
if not kind then
|
||||||
|
if found_any then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
return blocks
|
||||||
|
end
|
||||||
|
|
||||||
|
if blocks[kind] ~= nil then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local block_lines, next_idx = parse_block(lines, idx)
|
||||||
|
local value, err = decode_yaml_block(block_lines, key)
|
||||||
|
if err then
|
||||||
|
parse_error = err
|
||||||
|
vim.notify(err, vim.log.levels.WARN)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
blocks[kind] = value
|
||||||
|
found_any = true
|
||||||
|
idx = next_idx
|
||||||
|
|
||||||
|
if blocks.headers ~= nil and blocks.body ~= nil then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
blocks.parse_error = parse_error
|
||||||
|
return blocks
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_array(tbl)
|
||||||
|
if type(tbl) ~= "table" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
for key, _ in pairs(tbl) do
|
||||||
|
if type(key) ~= "number" or key < 1 or key % 1 ~= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return count == #tbl
|
||||||
|
end
|
||||||
|
|
||||||
|
local function headers_to_list(headers)
|
||||||
|
if type(headers) == "string" then
|
||||||
|
return { headers }
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(headers) ~= "table" then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local list = {}
|
||||||
|
if is_array(headers) then
|
||||||
|
for _, value in ipairs(headers) do
|
||||||
|
if type(value) == "string" then
|
||||||
|
list[#list + 1] = value
|
||||||
|
elseif type(value) == "table" then
|
||||||
|
for key, nested in pairs(value) do
|
||||||
|
list[#list + 1] = string.format("%s: %s", tostring(key), tostring(nested))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local keys = vim.tbl_keys(headers)
|
||||||
|
table.sort(keys, function(a, b)
|
||||||
|
return tostring(a) < tostring(b)
|
||||||
|
end)
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
list[#list + 1] = string.format("%s: %s", tostring(key), tostring(headers[key]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
|
local function body_to_payload(body)
|
||||||
|
if body == nil or body == vim.NIL then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(body) == "table" then
|
||||||
|
return vim.json.encode(body)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(body) == "string" then
|
||||||
|
return body
|
||||||
|
end
|
||||||
|
|
||||||
|
return tostring(body)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function extract_request_url(command)
|
||||||
|
local quoted = command:match("'(https?://[^']+)'") or command:match('"(https?://[^"]+)"')
|
||||||
|
if quoted then
|
||||||
|
return quoted
|
||||||
|
end
|
||||||
|
|
||||||
|
local bare = command:match("(https?://%S+)")
|
||||||
|
return bare or ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local function apply_request_blocks_to_command(command, blocks)
|
||||||
|
local request_details = {
|
||||||
|
url = extract_request_url(command),
|
||||||
|
headers = {},
|
||||||
|
body = "",
|
||||||
|
parse_error = blocks and blocks.parse_error or nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not blocks then
|
||||||
|
return command, request_details
|
||||||
|
end
|
||||||
|
|
||||||
|
local header_list = headers_to_list(blocks.headers)
|
||||||
|
local body_payload = body_to_payload(blocks.body)
|
||||||
|
request_details.headers = header_list
|
||||||
|
request_details.body = body_payload
|
||||||
|
|
||||||
|
if #header_list == 0 and body_payload == "" then
|
||||||
|
return command, request_details
|
||||||
|
end
|
||||||
|
|
||||||
|
if command:match("^curl%s+") == nil then
|
||||||
|
vim.notify("headers/body blocks are currently supported only for curl-style requests", vim.log.levels.WARN)
|
||||||
|
return command, request_details
|
||||||
|
end
|
||||||
|
|
||||||
|
local parts = {}
|
||||||
|
for _, header in ipairs(header_list) do
|
||||||
|
parts[#parts + 1] = "-H " .. vim.fn.shellescape(header)
|
||||||
|
end
|
||||||
|
|
||||||
|
if body_payload ~= "" then
|
||||||
|
local has_content_type = false
|
||||||
|
for _, header in ipairs(header_list) do
|
||||||
|
if header:lower():match("^%s*content%-type%s*:") then
|
||||||
|
has_content_type = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not has_content_type and type(blocks.body) == "table" then
|
||||||
|
local content_type = "Content-Type: application/json"
|
||||||
|
parts[#parts + 1] = "-H " .. vim.fn.shellescape(content_type)
|
||||||
|
request_details.headers[#request_details.headers + 1] = content_type
|
||||||
|
end
|
||||||
|
|
||||||
|
parts[#parts + 1] = "--data-binary " .. vim.fn.shellescape(body_payload)
|
||||||
|
end
|
||||||
|
|
||||||
|
if #parts == 0 then
|
||||||
|
return command, request_details
|
||||||
|
end
|
||||||
|
|
||||||
|
return command .. " " .. table.concat(parts, " "), request_details
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run_command(command)
|
||||||
|
local function normalize_newlines(text)
|
||||||
|
if not text or text == "" then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
return text:gsub("\r\n", "\n"):gsub("\r", "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
local is_curl = command:match("^curl%s+") ~= nil
|
||||||
|
|
||||||
|
if is_curl then
|
||||||
|
local function read_temp_file(path)
|
||||||
|
if vim.fn.filereadable(path) == 1 then
|
||||||
|
return table.concat(vim.fn.readfile(path, "b"), "\n")
|
||||||
|
end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local headers_file = vim.fn.tempname()
|
||||||
|
local body_file = vim.fn.tempname()
|
||||||
|
local wrapped_command = string.format(
|
||||||
|
"%s -D %s -o %s -w %s",
|
||||||
|
command,
|
||||||
|
vim.fn.shellescape(headers_file),
|
||||||
|
vim.fn.shellescape(body_file),
|
||||||
|
vim.fn.shellescape("__NVIM_HTTP_STATUS__:%{http_code}")
|
||||||
|
)
|
||||||
|
|
||||||
|
if vim.system then
|
||||||
|
local result = vim.system({ "sh", "-c", wrapped_command }, {
|
||||||
|
text = true,
|
||||||
|
timeout = M.options.http.timeout_ms,
|
||||||
|
}):wait()
|
||||||
|
|
||||||
|
local body = read_temp_file(body_file)
|
||||||
|
local headers = read_temp_file(headers_file)
|
||||||
|
vim.fn.delete(headers_file)
|
||||||
|
vim.fn.delete(body_file)
|
||||||
|
|
||||||
|
local status_code = nil
|
||||||
|
if result.stdout and result.stdout ~= "" then
|
||||||
|
status_code = result.stdout:match("__NVIM_HTTP_STATUS__:(%d%d%d)")
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
code = result.code or 0,
|
||||||
|
stdout = normalize_newlines(body),
|
||||||
|
stderr = normalize_newlines(result.stderr or ""),
|
||||||
|
headers = normalize_newlines(headers),
|
||||||
|
response_code = status_code,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local output = vim.fn.system(wrapped_command .. " 2>&1")
|
||||||
|
local code = vim.v.shell_error
|
||||||
|
local body = read_temp_file(body_file)
|
||||||
|
local headers = read_temp_file(headers_file)
|
||||||
|
vim.fn.delete(headers_file)
|
||||||
|
vim.fn.delete(body_file)
|
||||||
|
local status_code = output:match("__NVIM_HTTP_STATUS__:(%d%d%d)")
|
||||||
|
|
||||||
|
return {
|
||||||
|
code = code,
|
||||||
|
stdout = normalize_newlines(body),
|
||||||
|
stderr = normalize_newlines(output:gsub("__NVIM_HTTP_STATUS__:%d%d%d", "") or ""),
|
||||||
|
headers = normalize_newlines(headers),
|
||||||
|
response_code = status_code,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if vim.system then
|
||||||
|
local result = vim.system({ "sh", "-c", command }, {
|
||||||
|
text = true,
|
||||||
|
timeout = M.options.http.timeout_ms,
|
||||||
|
}):wait()
|
||||||
|
|
||||||
|
return {
|
||||||
|
code = result.code or 0,
|
||||||
|
stdout = normalize_newlines(result.stdout or ""),
|
||||||
|
stderr = normalize_newlines(result.stderr or ""),
|
||||||
|
headers = "",
|
||||||
|
response_code = nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local output = vim.fn.system(command .. " 2>&1")
|
||||||
|
return {
|
||||||
|
code = vim.v.shell_error,
|
||||||
|
stdout = normalize_newlines(output or ""),
|
||||||
|
stderr = "",
|
||||||
|
headers = "",
|
||||||
|
response_code = nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scalar_to_string(value)
|
||||||
|
if type(value) == "string" then
|
||||||
|
return string.format("%q", value)
|
||||||
|
end
|
||||||
|
|
||||||
|
if value == vim.NIL then
|
||||||
|
return "null"
|
||||||
|
end
|
||||||
|
|
||||||
|
return tostring(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function add_json_entries(entries, value, depth, label, path, expanded_nodes)
|
||||||
|
local indent = string.rep(" ", depth)
|
||||||
|
local value_type = type(value)
|
||||||
|
|
||||||
|
if value_type ~= "table" then
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = path,
|
||||||
|
expandable = false,
|
||||||
|
display = string.format("%s%s: %s", indent, label, scalar_to_string(value)),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local array = is_array(value)
|
||||||
|
local count = array and #value or vim.tbl_count(value)
|
||||||
|
local expanded = expanded_nodes[path] == true
|
||||||
|
local marker = expanded and "▼" or "▶"
|
||||||
|
local type_label = array and ("[" .. count .. "]") or ("{" .. count .. "}")
|
||||||
|
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = path,
|
||||||
|
expandable = true,
|
||||||
|
display = string.format("%s%s %s: %s", indent, marker, label, type_label),
|
||||||
|
}
|
||||||
|
|
||||||
|
if not expanded then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if array then
|
||||||
|
for index, child in ipairs(value) do
|
||||||
|
add_json_entries(entries, child, depth + 1, "[" .. index .. "]", path .. "/" .. index, expanded_nodes)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local keys = vim.tbl_keys(value)
|
||||||
|
table.sort(keys, function(a, b)
|
||||||
|
return tostring(a) < tostring(b)
|
||||||
|
end)
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
add_json_entries(entries, value[key], depth + 1, tostring(key), path .. "/" .. tostring(key), expanded_nodes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function build_picker_entries(result, state)
|
||||||
|
local entries = {}
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "response_code",
|
||||||
|
expandable = false,
|
||||||
|
display = "response_code: " .. (result.response_code or "n/a"),
|
||||||
|
}
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "",
|
||||||
|
expandable = false,
|
||||||
|
display = "",
|
||||||
|
}
|
||||||
|
|
||||||
|
local request_expanded = state.expanded_nodes["request"] == true
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "request",
|
||||||
|
expandable = true,
|
||||||
|
display = string.format("%s request", request_expanded and "▼" or "▶"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if request_expanded then
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "request/url",
|
||||||
|
expandable = false,
|
||||||
|
display = " url: " .. ((result.request and result.request.url ~= "") and result.request.url or "(unknown)"),
|
||||||
|
}
|
||||||
|
if result.request and result.request.parse_error then
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "request/parse_error",
|
||||||
|
expandable = false,
|
||||||
|
display = " parse_error: " .. result.request.parse_error,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local req_headers_expanded = state.expanded_nodes["request/headers"] == true
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "request/headers",
|
||||||
|
expandable = true,
|
||||||
|
display = string.format(" %s headers", req_headers_expanded and "▼" or "▶"),
|
||||||
|
}
|
||||||
|
if req_headers_expanded then
|
||||||
|
local req_headers = (result.request and result.request.headers) or {}
|
||||||
|
if #req_headers == 0 then
|
||||||
|
entries[#entries + 1] = { id = "request/headers/empty", expandable = false, display = " (empty)" }
|
||||||
|
else
|
||||||
|
for idx, header in ipairs(req_headers) do
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "request/headers/" .. idx,
|
||||||
|
expandable = false,
|
||||||
|
display = " " .. header,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local req_body_expanded = state.expanded_nodes["request/body"] == true
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "request/body",
|
||||||
|
expandable = true,
|
||||||
|
display = string.format(" %s body", req_body_expanded and "▼" or "▶"),
|
||||||
|
}
|
||||||
|
if req_body_expanded then
|
||||||
|
local req_body = (result.request and result.request.body) or ""
|
||||||
|
if req_body == "" then
|
||||||
|
entries[#entries + 1] = { id = "request/body/empty", expandable = false, display = " (empty)" }
|
||||||
|
else
|
||||||
|
local ok_decode, decoded = pcall(vim.json.decode, req_body)
|
||||||
|
if ok_decode and type(decoded) == "table" then
|
||||||
|
add_json_entries(entries, decoded, 2, "$", "request/body/json/$", state.expanded_nodes)
|
||||||
|
else
|
||||||
|
for idx, line in ipairs(vim.split(req_body, "\n", { plain = true })) do
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "request/body/raw/" .. idx,
|
||||||
|
expandable = false,
|
||||||
|
display = " " .. line,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "request/separator",
|
||||||
|
expandable = false,
|
||||||
|
display = "",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local headers_expanded = state.expanded_nodes["headers"] == true
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "headers",
|
||||||
|
expandable = true,
|
||||||
|
display = string.format("%s headers", headers_expanded and "▼" or "▶"),
|
||||||
|
}
|
||||||
|
if headers_expanded then
|
||||||
|
local header_lines = vim.split(result.headers or "", "\n", { plain = true })
|
||||||
|
if #header_lines == 0 or (#header_lines == 1 and header_lines[1] == "") then
|
||||||
|
entries[#entries + 1] = { id = "headers/empty", expandable = false, display = " (empty)" }
|
||||||
|
else
|
||||||
|
for idx, line in ipairs(header_lines) do
|
||||||
|
if line ~= "" then
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "headers/" .. idx,
|
||||||
|
expandable = false,
|
||||||
|
display = " " .. line,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local result_expanded = state.expanded_nodes["result"] == true
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "result",
|
||||||
|
expandable = true,
|
||||||
|
display = string.format("%s result", result_expanded and "▼" or "▶"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if result_expanded then
|
||||||
|
local ok_decode, decoded = pcall(vim.json.decode, result.stdout or "")
|
||||||
|
if ok_decode and type(decoded) == "table" then
|
||||||
|
add_json_entries(entries, decoded, 1, "$", "result/json/$", state.expanded_nodes)
|
||||||
|
else
|
||||||
|
local body_lines = vim.split(result.stdout or "", "\n", { plain = true })
|
||||||
|
if #body_lines == 0 or (#body_lines == 1 and body_lines[1] == "") then
|
||||||
|
entries[#entries + 1] = { id = "result/empty", expandable = false, display = " (empty)" }
|
||||||
|
else
|
||||||
|
for idx, line in ipairs(body_lines) do
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "result/raw/" .. idx,
|
||||||
|
expandable = false,
|
||||||
|
display = " " .. line,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if result.stderr and result.stderr ~= "" then
|
||||||
|
local stderr_expanded = state.expanded_nodes["stderr"] == true
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "stderr",
|
||||||
|
expandable = true,
|
||||||
|
display = string.format("%s stderr", stderr_expanded and "▼" or "▶"),
|
||||||
|
}
|
||||||
|
if stderr_expanded then
|
||||||
|
for idx, line in ipairs(vim.split(result.stderr, "\n", { plain = true })) do
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
id = "stderr/" .. idx,
|
||||||
|
expandable = false,
|
||||||
|
display = " " .. line,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
local function render_result_window(result, state)
|
||||||
|
local ok_telescope = pcall(require, "telescope")
|
||||||
|
if not ok_telescope then
|
||||||
|
vim.notify("nvim-http requires telescope.nvim to show results", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local pickers = require("telescope.pickers")
|
||||||
|
local finders = require("telescope.finders")
|
||||||
|
local conf = require("telescope.config").values
|
||||||
|
local themes = require("telescope.themes")
|
||||||
|
local actions = require("telescope.actions")
|
||||||
|
local action_state = require("telescope.actions.state")
|
||||||
|
|
||||||
|
if result_prompt_bufnr and vim.api.nvim_buf_is_valid(result_prompt_bufnr) then
|
||||||
|
pcall(actions.close, result_prompt_bufnr)
|
||||||
|
result_prompt_bufnr = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function make_finder(entries)
|
||||||
|
return finders.new_table({
|
||||||
|
results = entries,
|
||||||
|
entry_maker = function(item)
|
||||||
|
return {
|
||||||
|
value = item,
|
||||||
|
ordinal = item.display,
|
||||||
|
display = item.display,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local entries = build_picker_entries(result, state)
|
||||||
|
local picker
|
||||||
|
picker = pickers.new(themes.get_dropdown({
|
||||||
|
previewer = false,
|
||||||
|
layout_config = {
|
||||||
|
width = 0.9,
|
||||||
|
height = 0.9,
|
||||||
|
},
|
||||||
|
}), {
|
||||||
|
prompt_title = "HTTP Result",
|
||||||
|
results_title = "response tree",
|
||||||
|
selection_strategy = "row",
|
||||||
|
finder = make_finder(entries),
|
||||||
|
sorter = conf.generic_sorter({}),
|
||||||
|
attach_mappings = function(prompt_bufnr, map)
|
||||||
|
result_prompt_bufnr = prompt_bufnr
|
||||||
|
|
||||||
|
local function toggle_expand()
|
||||||
|
local current_picker = action_state.get_current_picker(prompt_bufnr)
|
||||||
|
local selection = action_state.get_selected_entry()
|
||||||
|
if not selection or not selection.value or not selection.value.expandable then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local current_row = current_picker:get_selection_row()
|
||||||
|
local id = selection.value.id
|
||||||
|
state.expanded_nodes[id] = not state.expanded_nodes[id]
|
||||||
|
|
||||||
|
local updated_entries = build_picker_entries(result, state)
|
||||||
|
local target_row = current_row
|
||||||
|
for idx, entry in ipairs(updated_entries) do
|
||||||
|
if entry.id == id then
|
||||||
|
target_row = idx - 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
current_picker:refresh(make_finder(updated_entries), { reset_prompt = false })
|
||||||
|
vim.defer_fn(function()
|
||||||
|
pcall(current_picker.set_selection, current_picker, target_row)
|
||||||
|
end, 10)
|
||||||
|
end
|
||||||
|
|
||||||
|
map("i", "<CR>", toggle_expand)
|
||||||
|
map("n", "<CR>", toggle_expand)
|
||||||
|
map("i", "<Tab>", toggle_expand)
|
||||||
|
map("n", "<Tab>", toggle_expand)
|
||||||
|
map("i", "q", actions.close)
|
||||||
|
map("n", "q", actions.close)
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
picker:find()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.run_http_under_cursor()
|
||||||
|
if not M.options.http.enabled then
|
||||||
|
vim.notify("http runner is disabled", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||||
|
local row = cursor[1] - 1
|
||||||
|
local line = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1] or ""
|
||||||
|
|
||||||
|
local command, err = normalize_http_command(line)
|
||||||
|
if not command then
|
||||||
|
vim.notify(err, vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local request_blocks = parse_request_blocks(bufnr, row)
|
||||||
|
local request_info
|
||||||
|
command, request_info = apply_request_blocks_to_command(command, request_blocks)
|
||||||
|
|
||||||
|
local result = run_command(command)
|
||||||
|
result.request = request_info or {
|
||||||
|
url = extract_request_url(command),
|
||||||
|
headers = {},
|
||||||
|
body = "",
|
||||||
|
}
|
||||||
|
vim.api.nvim_buf_clear_namespace(bufnr, http_ns, 0, -1)
|
||||||
|
render_result_window(result, { expanded_nodes = {} })
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
18
nvim-http-scm-1.rockspec
Normal file
18
nvim-http-scm-1.rockspec
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package = "nvim-http"
|
||||||
|
version = "scm-1"
|
||||||
|
rockspec_format = "3.0"
|
||||||
|
|
||||||
|
source = {
|
||||||
|
url = "git+https://github.com/macocianradu/nvim-http",
|
||||||
|
}
|
||||||
|
|
||||||
|
description = {
|
||||||
|
summary = "Neovim HTTP runner plugin",
|
||||||
|
license = "MIT",
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies = {
|
||||||
|
"lua >= 5.1",
|
||||||
|
"lyaml",
|
||||||
|
}
|
||||||
|
|
||||||
17
plugin/nvim-http.lua
Normal file
17
plugin/nvim-http.lua
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
local ok, nvim_http = pcall(require, "nvim_http")
|
||||||
|
if not ok then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_create_user_command("NvimHttpRun", function()
|
||||||
|
nvim_http.run_http_under_cursor()
|
||||||
|
end, {
|
||||||
|
desc = "Run HTTP command under cursor and open Telescope response tree",
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_create_user_command("NvimHttpClear", function()
|
||||||
|
nvim_http.clear_http_result()
|
||||||
|
end, {
|
||||||
|
desc = "Close and clear HTTP response tree",
|
||||||
|
})
|
||||||
|
|
||||||
Reference in New Issue
Block a user