Skip to content

Commit a478ea7

Browse files
refactor: wip
Make whole result UI configurable. Fixes #492
1 parent d5228fe commit a478ea7

File tree

7 files changed

+316
-169
lines changed

7 files changed

+316
-169
lines changed

lua/rest-nvim/config/default.lua

+4
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ local default_config = {
8888
---@type string Mapping for cycle to next result pane
8989
next = "L",
9090
},
91+
-- TODO: add panes object as configurable pane list
92+
-- including raw or request log
93+
panes = require("rest-nvim.ui.panes.preset.browser"),
94+
-- panes = require("rest-nvim.ui.panes.preset.onepage"),
9195
},
9296
---@class rest.Config.Highlight
9397
highlight = {

lua/rest-nvim/request.lua

+13
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ local function run_request(req)
8686
})
8787
_G.rest_request = nil
8888
_G.rest_response = nil
89+
-- format body *after* RestResponse event
90+
local content_type = res.headers["content-type"]
91+
if content_type and config.response.hooks.format then
92+
local _, res_type = content_type[#content_type]:match("(.*)/([^;]+)")
93+
-- HACK: handle application/vnd.api+json style content types
94+
res_type = res_type:match(".+%+(.*)") or res_type
95+
local body, gq_ok = utils.gq_lines(vim.split(res.body, "\n"), res_type)
96+
if not gq_ok then
97+
res.body = "failed"
98+
else
99+
res.body = table.concat(body, "\n")
100+
end
101+
end
89102

90103
-- update cookie jar
91104
jar.update_jar(req.url, res)

lua/rest-nvim/ui/panes.lua lua/rest-nvim/ui/panes/init.lua

+10-5
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
---@field name string
55
---@field bufnr number
66
---@field group rest.ui.panes.PaneGroup
7-
---@field render fun(self:rest.ui.panes.Pane)
7+
---@field render fun(self:rest.ui.panes.Pane, state:any)
88

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

1414
---@class rest.ui.panes.PaneGroup
1515
---@field name string
1616
---@field panes rest.ui.panes.Pane[]
17+
---@field state any
1718
local RestUIPaneGroup = {}
1819
---@param direction number
1920
function RestUIPaneGroup:cycle(direction)
@@ -28,7 +29,7 @@ function RestUIPaneGroup:cycle(direction)
2829
end
2930
function RestUIPaneGroup:render()
3031
for _, pane in ipairs(self.panes) do
31-
pane:render()
32+
pane:render(self.state)
3233
end
3334
end
3435
---@param winnr integer
@@ -38,6 +39,10 @@ function RestUIPaneGroup:enter(winnr)
3839
end
3940
vim.api.nvim_win_set_buf(winnr, self.panes[1].bufnr)
4041
end
42+
function RestUIPaneGroup:set_state(state)
43+
self.state = state
44+
self:render()
45+
end
4146

4247
---@class rest.ui.panes.PaneGroupOpts
4348
---@field on_init? fun(self:rest.ui.panes.Pane)
@@ -65,7 +70,7 @@ function M.create_pane_group(name, pane_opts, opts)
6570
local pane = {
6671
name = pane_opt.name,
6772
group = group,
68-
render = function(self)
73+
render = function(self, state)
6974
if not self.bufnr or not vim.api.nvim_buf_is_loaded(self.bufnr) then
7075
self.bufnr = self.bufnr or vim.api.nvim_create_buf(false, false)
7176
-- small trick to ensure buffer is loaded before the `BufWinEnter` event
@@ -82,7 +87,7 @@ function M.create_pane_group(name, pane_opts, opts)
8287
end
8388
end
8489
vim.bo[self.bufnr].modifiable = true
85-
local modifiable = pane_opt.render(self) or false
90+
local modifiable = pane_opt.render(self, state) or false
8691
if not modifiable then
8792
vim.bo[self.bufnr].undolevels = -1
8893
else
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
local function set_lines(buffer, lines)
2+
vim.api.nvim_buf_set_lines(buffer, 0, -1, false, lines)
3+
end
4+
5+
---@type rest.ui.panes.PaneOpts[]
6+
return {
7+
{
8+
name = "Headers",
9+
render = function(self, state)
10+
if not state.request then
11+
vim.bo[self.bufnr].undolevels = -1
12+
set_lines(self.bufnr, { "No Request running" })
13+
return
14+
end
15+
vim.bo[self.bufnr].filetype = "rest_nvim_result"
16+
local lines = {
17+
"### Request: " .. state.request.name,
18+
table.concat({ state.request.method, state.request.url, state.request.http_version }, " "),
19+
""
20+
}
21+
if not state.response then
22+
table.insert(lines, "### Loading...")
23+
set_lines(self.bufnr, lines)
24+
return
25+
end
26+
table.insert(lines, "### Response")
27+
table.insert(
28+
lines,
29+
("%s %d %s"):format(
30+
state.response.status.version,
31+
state.response.status.code,
32+
state.response.status.text
33+
)
34+
)
35+
local headers = vim.iter(state.response.headers):totable()
36+
table.sort(headers, function(b, a)
37+
return a[1] > b[1]
38+
end)
39+
for _, header in ipairs(headers) do
40+
vim.list_extend(
41+
lines,
42+
vim.iter(header[2])
43+
:map(function(value)
44+
return header[1] .. ": " .. value
45+
end)
46+
:totable()
47+
)
48+
end
49+
set_lines(self.bufnr, lines)
50+
end,
51+
},
52+
{
53+
name = "Payload",
54+
render = function(self, state)
55+
if not state.request then
56+
set_lines(self.bufnr, { "No Request running" })
57+
return
58+
end
59+
-- TODO: render based on body types
60+
local body = state.request.body
61+
if body.__TYPE == "raw" or body.__TYPE == "json" or body.__TYPE == "xml" then
62+
set_lines(self.bufnr, vim.split(body.data, "\n"))
63+
if body.__TYPE ~= "raw" then
64+
vim.bo[self.bufnr].filetype = body.__TYPE
65+
end
66+
return
67+
end
68+
set_lines(self.bufnr, { "TODO" })
69+
end,
70+
},
71+
{
72+
name = "Response",
73+
render = function(self, state)
74+
if not state.response then
75+
set_lines(self.bufnr, { "Loading..." })
76+
return
77+
end
78+
---@type string[]
79+
local lines = {}
80+
local content_type = state.response.headers["content-type"]
81+
if content_type then
82+
local base_type, res_type = content_type[#content_type]:match("(.*)/([^;]+)")
83+
res_type = res_type:match(".+%+(.*)") or res_type
84+
if base_type == "image" then
85+
table.insert(lines, "Binary(image) response")
86+
elseif res_type == "octet_stream" then
87+
table.insert(lines, "Binary response")
88+
else
89+
vim.bo[self.bufnr].filetype = res_type
90+
end
91+
end
92+
if #lines == 0 then
93+
lines = vim.split(state.response.body, "\n")
94+
end
95+
set_lines(self.bufnr, lines)
96+
end,
97+
},
98+
{
99+
name = "Trace",
100+
render = function(self, _state)
101+
-- TODO:
102+
-- TODO: use nvim_buf_add_highlights to highlight traces
103+
set_lines(self.bufnr, { "TODO" })
104+
end,
105+
},
106+
}
+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
local logger = require("rest-nvim.logger")
2+
3+
local function set_lines(buffer, lines)
4+
vim.api.nvim_buf_set_lines(buffer, 0, -1, false, lines)
5+
end
6+
7+
---@param buffer integer
8+
---@param filetype string
9+
local function syntax_highlight(buffer, filetype)
10+
-- manually stop any attached tree-sitter parsers (#424, #429)
11+
vim.treesitter.stop(buffer)
12+
local lang = vim.treesitter.language.get_lang(filetype)
13+
local ok = pcall(vim.treesitter.start, buffer, lang)
14+
if not lang or not ok then
15+
vim.bo[buffer].syntax = filetype
16+
end
17+
end
18+
19+
---@type rest.ui.panes.PaneOpts[]
20+
return {
21+
{
22+
name = "Response",
23+
render = function(self, state)
24+
if not state.request then
25+
vim.bo[self.bufnr].undolevels = -1
26+
set_lines(self.bufnr, { "No Request running" })
27+
return
28+
end
29+
syntax_highlight(self.bufnr, "rest_nvim_result")
30+
local lines = {
31+
"### " .. state.request.name,
32+
table.concat({ state.request.method, state.request.url, state.request.http_version }, " "),
33+
}
34+
if state.response then
35+
logger.debug(state.response.status)
36+
table.insert(
37+
lines,
38+
("%s %d %s"):format(
39+
state.response.status.version,
40+
state.response.status.code,
41+
state.response.status.text
42+
)
43+
)
44+
local content_type = state.response.headers["content-type"]
45+
local body = vim.split(state.response.body, "\n")
46+
local body_meta = {}
47+
if content_type then
48+
local base_type, res_type = content_type[1]:match("(.*)/([^;]+)")
49+
-- HACK: handle application/vnd.api+json style content types
50+
res_type = res_type:match(".+%+(.*)") or res_type
51+
if base_type == "image" then
52+
body = { "Binary(image) answer" }
53+
elseif res_type == "octet_stream" then
54+
body = { "Binary answer" }
55+
-- elseif config.response.hooks.format then
56+
-- -- NOTE: format hook runs here because it should be done last.
57+
-- local ok
58+
-- body, ok = utils.gq_lines(body, res_type)
59+
-- if ok then
60+
-- table.insert(body_meta, "formatted")
61+
-- end
62+
end
63+
end
64+
local meta_str = ""
65+
if #body_meta > 0 then
66+
meta_str = " (" .. table.concat(body_meta, ",") .. ")"
67+
end
68+
table.insert(lines, "")
69+
table.insert(lines, "# @_RES" .. meta_str)
70+
vim.list_extend(lines, body)
71+
table.insert(lines, "# @_END")
72+
else
73+
vim.list_extend(lines, { "", "# Loading..." })
74+
end
75+
set_lines(self.bufnr, lines)
76+
return false
77+
end,
78+
},
79+
{
80+
name = "Headers",
81+
render = function(self, state)
82+
if not state.response then
83+
set_lines(self.bufnr, { "Loading..." })
84+
return
85+
end
86+
syntax_highlight(self.bufnr, "http_stat")
87+
local lines = {}
88+
logger.debug(state.response.headers)
89+
local headers = vim.iter(state.response.headers):totable()
90+
table.sort(headers, function(b, a)
91+
return a[1] > b[1]
92+
end)
93+
logger.debug(headers)
94+
for _, header in ipairs(headers) do
95+
if header[1] ~= "set-cookie" then
96+
vim.list_extend(
97+
lines,
98+
vim.iter(header[2])
99+
:map(function(value)
100+
return header[1] .. ": " .. value
101+
end)
102+
:totable()
103+
)
104+
end
105+
end
106+
set_lines(self.bufnr, lines)
107+
end,
108+
},
109+
{
110+
name = "Cookies",
111+
render = function(self, state)
112+
if not state.response then
113+
set_lines(self.bufnr, { "Loading..." })
114+
return
115+
end
116+
local lines = {}
117+
---@type string[]?
118+
local cookie_headers = vim.tbl_get(state.response, "headers", "set-cookie")
119+
if not cookie_headers then
120+
set_lines(self.bufnr, { "No Cookies" })
121+
return
122+
end
123+
syntax_highlight(self.bufnr, "http_stat")
124+
table.sort(cookie_headers)
125+
vim.list_extend(lines, cookie_headers)
126+
set_lines(self.bufnr, lines)
127+
end,
128+
},
129+
{
130+
name = "Statistics",
131+
render = function(self, state)
132+
if not state.response then
133+
set_lines(self.bufnr, { "Loading..." })
134+
return
135+
end
136+
local lines = {}
137+
if not state.response.statistics then
138+
set_lines(self.bufnr, { "No Statistics" })
139+
return
140+
end
141+
-- TODO: use manual highlighting instead
142+
syntax_highlight(self.bufnr, "http_stat")
143+
for _, style in ipairs(require("rest-nvim.config").clients.curl.statistics) do
144+
local title = style.title or style.id
145+
local value = state.response.statistics[style.id] or ""
146+
table.insert(lines, ("%s: %s"):format(title, value))
147+
end
148+
set_lines(self.bufnr, lines)
149+
end,
150+
},
151+
}

0 commit comments

Comments
 (0)