Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): make general UI configurable #522

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions doc/rest-nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,9 @@ rest.Opts.Env *rest.Opts.Env*
rest.Opts.UI *rest.Opts.UI*

Fields: ~
{winbar?} (boolean) Set winbar in result pane (Default: `true`)
{keybinds?} (rest.Opts.UI.Keybinds) Default mappings for result pane
{winbar?} (boolean) Set winbar in result pane (Default: `true`)
{keybinds?} (rest.Opts.UI.Keybinds) Default mappings for result pane
{panes?} (rest.ui.panes.PaneOpts[]) Result UI (Default: `rest-nvim.ui.panes.preset.legacy`)


rest.Opts.UI.Keybinds *rest.Opts.UI.Keybinds*
Expand Down
2 changes: 2 additions & 0 deletions lua/rest-nvim/config/default.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ local default_config = {
---@type string Mapping for cycle to next result pane
next = "L",
},
---@type rest.ui.panes.PaneOpts[]
panes = require("rest-nvim.ui.panes.preset.legacy"),
},
---@class rest.Config.Highlight
highlight = {
Expand Down
2 changes: 2 additions & 0 deletions lua/rest-nvim/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ local config
---@field winbar? boolean
--- Default mappings for result pane
---@field keybinds? rest.Opts.UI.Keybinds
--- Result UI (Default: `rest-nvim.ui.panes.preset.legacy`)
---@field panes? rest.ui.panes.PaneOpts[]

---@class rest.Opts.UI.Keybinds
--- Mapping for cycle to previous result pane (Default: `"H"`)
Expand Down
13 changes: 13 additions & 0 deletions lua/rest-nvim/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ local function run_request(req)
})
_G.rest_request = nil
_G.rest_response = nil
-- format body *after* RestResponse event
local content_type = res.headers["content-type"]
if content_type and config.response.hooks.format then
local _, res_type = content_type[#content_type]:match("(.*)/([^;]+)")
-- HACK: handle application/vnd.api+json style content types
res_type = res_type:match(".+%+(.*)") or res_type
local body, gq_ok = utils.gq_lines(vim.split(res.body, "\n"), res_type)
if not gq_ok then
res.body = "failed"
else
res.body = table.concat(body, "\n")
end
end

-- update cookie jar
jar.update_jar(req.url, res)
Expand Down
15 changes: 10 additions & 5 deletions lua/rest-nvim/ui/panes.lua → lua/rest-nvim/ui/panes/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
---@field name string
---@field bufnr number
---@field group rest.ui.panes.PaneGroup
---@field render fun(self:rest.ui.panes.Pane)
---@field render fun(self:rest.ui.panes.Pane, state:any)

---@class rest.ui.panes.PaneOpts
---@field name string
---@field on_init? fun(self:rest.ui.panes.Pane)
---@field render fun(self:rest.ui.panes.Pane):(modifiable:boolean?)
---@field render fun(self:rest.ui.panes.Pane, state:any):(modifiable:boolean?)

---@class rest.ui.panes.PaneGroup
---@field name string
---@field panes rest.ui.panes.Pane[]
---@field state any
local RestUIPaneGroup = {}
---@param direction number
function RestUIPaneGroup:cycle(direction)
Expand All @@ -28,7 +29,7 @@ function RestUIPaneGroup:cycle(direction)
end
function RestUIPaneGroup:render()
for _, pane in ipairs(self.panes) do
pane:render()
pane:render(self.state)
end
end
---@param winnr integer
Expand All @@ -38,6 +39,10 @@ function RestUIPaneGroup:enter(winnr)
end
vim.api.nvim_win_set_buf(winnr, self.panes[1].bufnr)
end
function RestUIPaneGroup:set_state(state)
self.state = state
self:render()
end

---@class rest.ui.panes.PaneGroupOpts
---@field on_init? fun(self:rest.ui.panes.Pane)
Expand Down Expand Up @@ -65,7 +70,7 @@ function M.create_pane_group(name, pane_opts, opts)
local pane = {
name = pane_opt.name,
group = group,
render = function(self)
render = function(self, state)
if not self.bufnr or not vim.api.nvim_buf_is_loaded(self.bufnr) then
self.bufnr = self.bufnr or vim.api.nvim_create_buf(false, false)
-- small trick to ensure buffer is loaded before the `BufWinEnter` event
Expand All @@ -82,7 +87,7 @@ function M.create_pane_group(name, pane_opts, opts)
end
end
vim.bo[self.bufnr].modifiable = true
local modifiable = pane_opt.render(self) or false
local modifiable = pane_opt.render(self, state) or false
if not modifiable then
vim.bo[self.bufnr].undolevels = -1
else
Expand Down
116 changes: 116 additions & 0 deletions lua/rest-nvim/ui/panes/preset/browser.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
local function set_lines(buffer, lines)
vim.api.nvim_buf_set_lines(buffer, 0, -1, false, lines)
end

---@type rest.ui.panes.PaneOpts[]
return {
{
name = "Headers",
render = function(self, state)
if not state.request then
vim.bo[self.bufnr].undolevels = -1
set_lines(self.bufnr, { "No Request running" })
return
end
vim.bo[self.bufnr].filetype = "rest_nvim_result"
local lines = {
"### Request: " .. state.request.name,
table.concat({ state.request.method, state.request.url, state.request.http_version }, " "),
""
}
if not state.response then
table.insert(lines, "### Loading...")
set_lines(self.bufnr, lines)
return
end
table.insert(lines, "### Response")
table.insert(
lines,
("%s %d %s"):format(
state.response.status.version,
state.response.status.code,
state.response.status.text
)
)
local headers = vim.iter(state.response.headers):totable()
table.sort(headers, function(b, a)
return a[1] > b[1]
end)
for _, header in ipairs(headers) do
vim.list_extend(
lines,
vim.iter(header[2])
:map(function(value)
return header[1] .. ": " .. value
end)
:totable()
)
end
set_lines(self.bufnr, lines)
end,
},
{
name = "Payload",
render = function(self, state)
if not state.request then
set_lines(self.bufnr, { "No Request running" })
return
end
-- TODO: render based on body types
local body = state.request.body
if not body then
set_lines(self.bufnr, {})
return
end
if vim.list_contains({ "json", "xml", "raw", "graphql" }, body.__TYPE) then
set_lines(self.bufnr, vim.split(body.data, "\n"))
if body.__TYPE == "graphql" then
vim.bo[self.bufnr].filetype = "json"
elseif body.__TYPE ~= "raw" then
vim.bo[self.bufnr].filetype = body.__TYPE
end
elseif body.__TYPE == "multiplart_form_data" then
-- TODO:
set_lines(self.bufnr, { "TODO: multipart-form-data" })
elseif body.__TYPE == "external" then
-- TODO:
set_lines(self.bufnr, { "TODO: external body" })
end
end,
},
{
name = "Response",
render = function(self, state)
if not state.response then
set_lines(self.bufnr, { "Loading..." })
return
end
---@type string[]
local lines = {}
local content_type = state.response.headers["content-type"]
if content_type then
local base_type, res_type = content_type[#content_type]:match("(.*)/([^;]+)")
res_type = res_type:match(".+%+(.*)") or res_type
if base_type == "image" then
table.insert(lines, "Binary(image) response")
elseif res_type == "octet_stream" then
table.insert(lines, "Binary response")
else
vim.bo[self.bufnr].filetype = res_type
end
end
if #lines == 0 then
lines = vim.split(state.response.body, "\n")
end
set_lines(self.bufnr, lines)
end,
},
{
name = "Trace",
render = function(self, _state)
-- TODO:
-- TODO: use nvim_buf_add_highlights to highlight traces
set_lines(self.bufnr, { "TODO" })
end,
},
}
151 changes: 151 additions & 0 deletions lua/rest-nvim/ui/panes/preset/legacy.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
local logger = require("rest-nvim.logger")

local function set_lines(buffer, lines)
vim.api.nvim_buf_set_lines(buffer, 0, -1, false, lines)
end

---@param buffer integer
---@param filetype string
local function syntax_highlight(buffer, filetype)
-- manually stop any attached tree-sitter parsers (#424, #429)
vim.treesitter.stop(buffer)
local lang = vim.treesitter.language.get_lang(filetype)
local ok = pcall(vim.treesitter.start, buffer, lang)
if not lang or not ok then
vim.bo[buffer].syntax = filetype
end
end

---@type rest.ui.panes.PaneOpts[]
return {
{
name = "Response",
render = function(self, state)
if not state.request then
vim.bo[self.bufnr].undolevels = -1
set_lines(self.bufnr, { "No Request running" })
return
end
syntax_highlight(self.bufnr, "rest_nvim_result")
local lines = {
"### " .. state.request.name,
table.concat({ state.request.method, state.request.url, state.request.http_version }, " "),
}
if state.response then
logger.debug(state.response.status)
table.insert(
lines,
("%s %d %s"):format(
state.response.status.version,
state.response.status.code,
state.response.status.text
)
)
local content_type = state.response.headers["content-type"]
local body = vim.split(state.response.body, "\n")
local body_meta = {}
if content_type then
local base_type, res_type = content_type[1]:match("(.*)/([^;]+)")
-- HACK: handle application/vnd.api+json style content types
res_type = res_type:match(".+%+(.*)") or res_type
if base_type == "image" then
body = { "Binary(image) answer" }
elseif res_type == "octet_stream" then
body = { "Binary answer" }
-- elseif config.response.hooks.format then
-- -- NOTE: format hook runs here because it should be done last.
-- local ok
-- body, ok = utils.gq_lines(body, res_type)
-- if ok then
-- table.insert(body_meta, "formatted")
-- end
end
end
local meta_str = ""
if #body_meta > 0 then
meta_str = " (" .. table.concat(body_meta, ",") .. ")"
end
table.insert(lines, "")
table.insert(lines, "# @_RES" .. meta_str)
vim.list_extend(lines, body)
table.insert(lines, "# @_END")
else
vim.list_extend(lines, { "", "# Loading..." })
end
set_lines(self.bufnr, lines)
return false
end,
},
{
name = "Headers",
render = function(self, state)
if not state.response then
set_lines(self.bufnr, { "Loading..." })
return
end
syntax_highlight(self.bufnr, "http_stat")
local lines = {}
logger.debug(state.response.headers)
local headers = vim.iter(state.response.headers):totable()
table.sort(headers, function(b, a)
return a[1] > b[1]
end)
logger.debug(headers)
for _, header in ipairs(headers) do
if header[1] ~= "set-cookie" then
vim.list_extend(
lines,
vim.iter(header[2])
:map(function(value)
return header[1] .. ": " .. value
end)
:totable()
)
end
end
set_lines(self.bufnr, lines)
end,
},
{
name = "Cookies",
render = function(self, state)
if not state.response then
set_lines(self.bufnr, { "Loading..." })
return
end
local lines = {}
---@type string[]?
local cookie_headers = vim.tbl_get(state.response, "headers", "set-cookie")
if not cookie_headers then
set_lines(self.bufnr, { "No Cookies" })
return
end
syntax_highlight(self.bufnr, "http_stat")
table.sort(cookie_headers)
vim.list_extend(lines, cookie_headers)
set_lines(self.bufnr, lines)
end,
},
{
name = "Statistics",
render = function(self, state)
if not state.response then
set_lines(self.bufnr, { "Loading..." })
return
end
local lines = {}
if not state.response.statistics then
set_lines(self.bufnr, { "No Statistics" })
return
end
-- TODO: use manual highlighting instead
syntax_highlight(self.bufnr, "http_stat")
for _, style in ipairs(require("rest-nvim.config").clients.curl.statistics) do
local title = style.title or style.id
local value = state.response.statistics[style.id] or ""
table.insert(lines, ("%s: %s"):format(title, value))
end
set_lines(self.bufnr, lines)
end,
},
}
Loading