Skip to content

Commit

Permalink
feat: introduce curl filetype
Browse files Browse the repository at this point in the history
Can now use plugin features from any file with the .curl extension
Also contains some bugfixes and refactoring
  • Loading branch information
oysandvik94 committed Jul 13, 2024
1 parent 7a0ea74 commit 2484e51
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 80 deletions.
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@ https://github.com/oysandvik94/curl.nvim/assets/25078429/9c25d289-c293-41c4-9d8d

curl.nvim allows you to run HTTP requests with curl from a scratchpad, and display the formatted output

The scratch buffer can be persisted on a per-directory basis, globally, or in named files.
- Introduces the ".curl" filetype, where pressing enter will execute a curl request under the cursor
- Quality of life formatting features, so that writing out curl commands is a *little* less tedious
- Output is formatted using JQ
- Persist a collections of curl request using various methods of persistence, such as a global file,
named files, or a file that is stored per working directory
- It's just curl, so all the headers and auth flags you already know works

See [the features section](<README#✨ Features>) for more information.

The plugin aims to be 100% compatible with curl; if a curl command can execute in your shell,
you will be able to paste it in to the scratch buffer and run it.
Because of this, the plugin attempts to get the balance of being ergonomic and convenient, while
still using the knowledge of curl you already have.

However, there are a few quality of life features enabled in the scratch buffer, so that you
dont have to write out curl commands as if they were valid bash.

## Installation and requirements

The plugin requires you to have curl on your system, which you most likely have.
Expand Down Expand Up @@ -100,6 +104,13 @@ See examples under [Features](<README#✨ Features>) for more information

## ✨ Features

### 💪 .curl filetype

Opening any file with the ".curl" file extension will activate this plugins features.
You will get some syntax highlighting and the ability to execute curl commands from you buffer.
Since any ".curl" file will work, you can manage your own collection instead of using the builtin
system, and even check in files to your repository.

### 💪 Formatting

#### No quotes needed
Expand Down
15 changes: 15 additions & 0 deletions after/syntax/curlse.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
runtime! syntax/sh.vim

syn match curlFlag "-[a-zA-Z0-9-]\+"
hi def link curlFlag Type

syn match curlMethod "\v-X (GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT)"hs=s+3
hi def link curlMethod Function

syn match curlUrl "\v(https?://)[^\s]+" contains=curlProtocol
syn match curlProtocol "\v^(https?://)" contained
hi def link curlUrl String
hi def link curlProtocol Type

syn match curlCommand "\vcurl"
hi def link curlCommand
68 changes: 68 additions & 0 deletions ftplugin/curl.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
vim.bo.syntax = "sh"
vim.bo.commentstring = "# %s"

vim.treesitter.language.register("bash", "curl")

-- Custom highlights for curl-specific elements
local query = vim.treesitter.query.parse(
"bash",
[[
;; Highlight the 'curl' command
((command_name) @curl (#eq? @curl "curl"))
;; Highlight curl options
((word) @curl_option (#match? @curl_option "^-"))
;; Highlight HTTP methods
((word) @http_method (#match? @http_method "^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT)$"))
;; Highlight URLs
((word) @url (#match? @url "^https?://"))
]]
)

local bufnr = 0
local parser = vim.treesitter.get_parser(bufnr, "bash")
local tree = parser:parse()[1]
local root = tree:root()

for id, node in query:iter_captures(root, bufnr, 0, -1) do
local name = query.captures[id]
local start_row, start_col, end_row, end_col = node:range()

if name == "curl" then
vim.api.nvim_buf_add_highlight(bufnr, -1, "CurlKeyword", start_row, start_col, end_col)
elseif name == "curl_option" or name == "http_method" then
vim.api.nvim_buf_add_highlight(bufnr, -1, "CurlFunction", start_row, start_col, end_col)
elseif name == "url" then
vim.api.nvim_buf_add_highlight(bufnr, -1, "CurlUrl", start_row, start_col, end_col)
end
end

vim.api.nvim_set_hl(0, "CurlUrl", {
link = "@text",
})

vim.api.nvim_set_hl(0, "CurlFunction", {
link = "@function",
})

vim.api.nvim_set_hl(0, "CurlKeyword", {
link = "@operator",
})

local config = require("curl.config")
local execute_mapping = config.get("mappings")["execute_curl"]
vim.api.nvim_buf_set_keymap(
0,
"n",
execute_mapping,
"<cmd>lua require('curl.api').execute_curl()<CR>",
{ noremap = true, silent = true }
)

vim.api.nvim_create_autocmd({ "InsertLeave", "TextChanged" }, {
command = "silent w",
buffer = 0,
nested = true,
})
24 changes: 5 additions & 19 deletions lua/curl/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,17 @@ local parser = require("curl.parser")
local buffers = require("curl.buffers")
local output_parser = require("curl.output_parser")
local notify = require("curl.notifications")
local config = require("curl.config")

local create_execute_mapping = function(buffer)
local execute_mapping = config.get("mappings")["execute_curl"]
vim.api.nvim_buf_set_keymap(
buffer,
"n",
execute_mapping,
"<cmd>lua require('curl.api').execute_curl()<CR>",
{ noremap = true, silent = true }
)
end

M.open_custom_tab = function(custom_buf_name)
buffers.open_custom_curl_tab(custom_buf_name)
local curl_buffer = buffers.open_curl_tab()
create_execute_mapping(curl_buffer)
end

M.open_global_tab = function()
local curl_buffer = buffers.open_global_curl_tab()
create_execute_mapping(curl_buffer)
buffers.open_global_curl_tab()
end

M.open_curl_tab = function()
local curl_buffer = buffers.open_curl_tab()
create_execute_mapping(curl_buffer)
buffers.open_curl_tab()
end

---comment
Expand All @@ -50,7 +34,9 @@ M.execute_curl = function()

local output = ""
local error = ""
local output_bufnr = OUTPUT_BUF_ID

local output_bufnr = buffers.open_result_buffer()
OUTPUT_BUF_ID = output_bufnr
local _ = vim.fn.jobstart(curl_command, {
on_exit = function(_, exit_code, _)
if exit_code ~= 0 then
Expand Down
126 changes: 74 additions & 52 deletions lua/curl/buffers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,92 +3,114 @@ local cache = require("curl.cache")

OUTPUT_BUF_ID = -1
COMMAND_BUF_ID = -1
GLOBAL_COMMAND_BUF_ID = -1
CURL_WINDOW_ID = -1
TAB_ID = "curl.nvim.tab"

local function open_curl_tab_if_created(global)
local curl_tab_id = global and "curl.nvim.global" or "curl.nvim"
local close_curl_buffer = function(buffer, force)
if buffer == -1 then
return
end
vim.api.nvim_buf_delete(buffer, { force = force })
end

local function find_curl_tab_windid()
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
local success, tab_id = pcall(function() ---@type any,integer
return vim.api.nvim_tabpage_get_var(tab, "id")
end)

if success and tab_id == curl_tab_id then
if success and tab_id == TAB_ID then
vim.api.nvim_set_current_tabpage(tab)
vim.api.nvim_set_current_win(vim.api.nvim_tabpage_get_win(tab))
return true
local win_id = vim.api.nvim_tabpage_get_win(tab)
vim.api.nvim_set_current_win(win_id)
return win_id
end
end
end
local function open_curl_tab_if_created()
local tab_win_id = find_curl_tab_windid()
if tab_win_id ~= nil then
return tab_win_id
end

vim.cmd("tabnew")
vim.api.nvim_tabpage_set_var(0, "id", curl_tab_id)
vim.api.nvim_tabpage_set_var(0, "id", TAB_ID)
return vim.api.nvim_tabpage_get_win(0)
end

local open_command_buffer = function(command_file)
local bufnr = vim.fn.bufadd(command_file)
vim.api.nvim_set_option_value("filetype", "sh", { buf = bufnr })
vim.api.nvim_win_set_buf(0, bufnr)
local bufnr = vim.fn.bufnr(command_file, false)

return bufnr
end
if bufnr == -1 then
vim.cmd.edit(command_file)
local new_bufnr = vim.fn.bufnr(command_file, false)

local open_result_buffer = function()
OUTPUT_BUF_ID = vim.api.nvim_create_buf(false, true)
vim.api.nvim_set_option_value("filetype", "json", { buf = OUTPUT_BUF_ID })
vim.cmd("vert belowright sb" .. OUTPUT_BUF_ID .. " | wincmd p")
end
if COMMAND_BUF_ID ~= new_bufnr then
close_curl_buffer(COMMAND_BUF_ID, false)
end

M.open_global_curl_tab = function()
if open_curl_tab_if_created(true) then
return 0
return new_bufnr
end

local file = cache.load_global_command_file()
GLOBAL_COMMAND_BUF_ID = open_command_buffer(file)
vim.api.nvim_win_set_buf(CURL_WINDOW_ID, bufnr)

open_result_buffer()

return GLOBAL_COMMAND_BUF_ID
return bufnr
end

M.open_curl_tab = function()
if open_curl_tab_if_created() then
return 0
end

local file = cache.load_command_file()
COMMAND_BUF_ID = open_command_buffer(file)
M.open_result_buffer = function()
local bufnr = vim.fn.bufnr("Curl output", false)

open_result_buffer()
if bufnr ~= -1 then
local curl_tab_winid = vim.fn.win_findbuf(bufnr)
local open_windows = vim.api.nvim_tabpage_list_wins(0)
if vim.tbl_contains(open_windows, function(v)
return vim.tbl_contains(curl_tab_winid, v)
end, { predicate = true }) == false then
vim.cmd("vert belowright sb" .. bufnr .. " | wincmd p")
end
return bufnr
end

return COMMAND_BUF_ID
local new_bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(new_bufnr, "Curl output")
vim.api.nvim_set_option_value("filetype", "json", { buf = new_bufnr })
vim.api.nvim_set_option_value("buftype", "nofile", { buf = new_bufnr })
vim.cmd("vert belowright sb" .. new_bufnr .. " | wincmd p")
OUTPUT_BUF_ID = new_bufnr
return new_bufnr
end

M.open_custom_curl_tab = function(curl_buf_name)
if open_curl_tab_if_created() then
return 0
end
local setup_curl_tab_for_file = function(filename)
CURL_WINDOW_ID = open_curl_tab_if_created()

local file = cache.load_custom_command_file(curl_buf_name)
COMMAND_BUF_ID = open_command_buffer(file)
COMMAND_BUF_ID = open_command_buffer(filename)

open_result_buffer()
M.open_result_buffer()
end

return COMMAND_BUF_ID
M.open_global_curl_tab = function()
local filename = cache.load_global_command_file()
setup_curl_tab_for_file(filename)
end

---@param buffer number
local close_curl_window = function(buffer, force)
if buffer == -1 then
return
end
vim.api.nvim_buf_delete(buffer, { force = force })
M.open_curl_tab = function()
local filename = cache.load_command_file()
setup_curl_tab_for_file(filename)
end

M.open_custom_curl_tab = function(curl_buf_name)
local filename = cache.load_custom_command_file(curl_buf_name)
setup_curl_tab_for_file(filename)
end

M.close_curl_tab = function(force)
close_curl_window(GLOBAL_COMMAND_BUF_ID, force)
close_curl_window(COMMAND_BUF_ID, force)
close_curl_window(OUTPUT_BUF_ID, force)
GLOBAL_COMMAND_BUF_ID, COMMAND_BUF_ID, OUTPUT_BUF_ID = -1, -1, -1
if CURL_WINDOW_ID ~= -1 then
vim.api.nvim_win_close(CURL_WINDOW_ID, force)
end

close_curl_buffer(COMMAND_BUF_ID, force)
close_curl_buffer(OUTPUT_BUF_ID, force)
CURL_WINDOW_ID, COMMAND_BUF_ID, OUTPUT_BUF_ID = -1, -1, -1
end

M.get_command_buffer_and_pos = function()
Expand Down
21 changes: 17 additions & 4 deletions lua/curl/cache.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ M.load_custom_command_file = function(filename)
local custom_cache_dir = cache_dir / curl_filename ---@type Path
return custom_cache_dir:absolute()
end
---

---@return string
M.load_global_command_file = function()
local cache_dir = Path:new(vim.fn.stdpath("data")) / "curl_cache" ---@type Path
Expand All @@ -27,9 +27,22 @@ M.load_command_file = function()
local cache_dir = Path:new(vim.fn.stdpath("data")) / "curl_cache" ---@type Path
cache_dir:mkdir({ parents = true, exists_ok = true })

local cwd_cache_dir = cache_dir / (vim.fn.sha256(workspace_path)) ---@type Path
return cwd_cache_dir:absolute()
local unique_id = vim.fn.fnamemodify(workspace_path, ":t") .. "_" .. vim.fn.sha256(workspace_path):sub(1, 8) ---@type string
local new_file_name = unique_id .. ".curl"

local old_cache_file = cache_dir / vim.fn.sha256(workspace_path) ---@type Path
local new_cache_file = cache_dir / new_file_name ---@type Path

if old_cache_file:exists() then
if not new_cache_file:exists() then
old_cache_file:rename({ new_name = new_cache_file:absolute() })
else
local archive_file = cache_dir / (vim.fn.sha256(workspace_path) .. ".archive") ---@type Path
old_cache_file:rename({ new_name = archive_file:absolute() })
end
end

return new_cache_file:absolute()
end
---

return M
9 changes: 9 additions & 0 deletions lua/curl/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,18 @@ local create_curl_open = function()
})
end

local create_filetype = function()
vim.filetype.add({
extension = {
curl = "curl",
},
})
end
function M.setup(opts)
create_curl_open()

create_filetype()

vim.api.nvim_create_user_command("CurlClose", function()
require("curl.api").close_curl_tab()
end, { desc = "Close tab for curl.nvim" })
Expand Down
Loading

0 comments on commit 2484e51

Please sign in to comment.