From d22b68384a001dba207137d46d07183dea0655fe Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 11:35:12 -0400 Subject: [PATCH 001/159] ref!: nuke previous codebase Add new modules: - `api` - `config`, `config.check` - `autocmds` - `commands` - `functions` - `utils` - `parser` (WIP) Note: this does not run requests yet, as we need to finish the `parser` module and make the curl module first --- lua/rest-nvim/api.lua | 33 ++ lua/rest-nvim/autocmds.lua | 60 +++ lua/rest-nvim/commands.lua | 174 +++++++ lua/rest-nvim/config/check.lua | 97 ++++ lua/rest-nvim/config/init.lua | 194 +++++--- lua/rest-nvim/curl/init.lua | 303 ------------- lua/rest-nvim/functions.lua | 79 ++++ lua/rest-nvim/init.lua | 251 +---------- lua/rest-nvim/parser.lua | 0 lua/rest-nvim/request/init.lua | 406 ----------------- lua/rest-nvim/utils.lua | 27 ++ lua/rest-nvim/utils/init.lua | 801 --------------------------------- 12 files changed, 623 insertions(+), 1802 deletions(-) create mode 100644 lua/rest-nvim/api.lua create mode 100644 lua/rest-nvim/autocmds.lua create mode 100644 lua/rest-nvim/commands.lua create mode 100644 lua/rest-nvim/config/check.lua delete mode 100644 lua/rest-nvim/curl/init.lua create mode 100644 lua/rest-nvim/functions.lua create mode 100644 lua/rest-nvim/parser.lua delete mode 100644 lua/rest-nvim/request/init.lua create mode 100644 lua/rest-nvim/utils.lua delete mode 100644 lua/rest-nvim/utils/init.lua diff --git a/lua/rest-nvim/api.lua b/lua/rest-nvim/api.lua new file mode 100644 index 00000000..b83fdd11 --- /dev/null +++ b/lua/rest-nvim/api.lua @@ -0,0 +1,33 @@ +---@mod rest-nvim.api rest.nvim Lua API +--- +---@brief [[ +--- +---The Lua API for rest.nvim +---Intended for use by third-party modules that extend its functionalities. +--- +---@brief ]] + +local api = {} + +local autocmds = require("rest-nvim.autocmds") +local commands = require("rest-nvim.commands") + +---Register a new autocommand in the `Rest` augroup +---@see vim.api.nvim_create_augroup +---@see vim.api.nvim_create_autocmd +--- +---@param events string[] Autocommand events, see `:h events` +---@param cb string|fun(args: table) Autocommand lua callback, runs a Vimscript command instead if it is a `string` +---@param description string Autocommand description +function api.register_rest_autocmd(events, cb, description) + autocmds.register_autocmd(events, cb, description) +end + +---Register a new `:Rest` subcommand +---@param name string The name of the subcommand to register +---@param cmd RestCmd +function api.register_rest_subcommand(name, cmd) + commands.register_subcommand(name, cmd) +end + +return api diff --git a/lua/rest-nvim/autocmds.lua b/lua/rest-nvim/autocmds.lua new file mode 100644 index 00000000..05f7e0fb --- /dev/null +++ b/lua/rest-nvim/autocmds.lua @@ -0,0 +1,60 @@ +---@mod rest-nvim.autocmds rest.nvim autocommands +--- +---@brief [[ +--- +--- rest.nvim autocommands +--- +---@brief ]] + +local autocmds = {} + +local commands = require("rest-nvim.commands") + +---Set up Rest autocommands group and set `:Rest` command on `*.http` files +function autocmds.setup() + local rest_nvim_augroup = vim.api.nvim_create_augroup("Rest", {}) + vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { + group = rest_nvim_augroup, + pattern = "*.http", + callback = function(args) + commands.init(args.buf) + end, + desc = "Set up rest.nvim commands", + }) +end + +---Register a new autocommand in the `Rest` augroup +---@see vim.api.nvim_create_augroup +---@see vim.api.nvim_create_autocmd +--- +---@param events string[] Autocommand events, see `:h events` +---@param cb string|fun(args: table) Autocommand lua callback, runs a Vimscript command instead if it is a `string` +---@param description string Autocommand description +---@package +function autocmds.register_autocmd(events, cb, description) + vim.validate({ + events = { events, "table" }, + cb = { cb, { "function", "string" } }, + description = { description, "string" }, + }) + + local autocmd_opts = { + group = "Rest", + pattern = "*.http", + desc = description, + } + + if type(cb) == "function" then + autocmd_opts = vim.tbl_deep_extend("force", autocmd_opts, { + callback = cb, + }) + elseif type(cb) == "string" then + autocmd_opts = vim.tbl_deep_extend("force", autocmd_opts, { + command = cb, + }) + end + + vim.api.nvim_create_autocmd(events, autocmd_opts) +end + +return autocmds diff --git a/lua/rest-nvim/commands.lua b/lua/rest-nvim/commands.lua new file mode 100644 index 00000000..0a2e0d07 --- /dev/null +++ b/lua/rest-nvim/commands.lua @@ -0,0 +1,174 @@ +---@mod rest-nvim.commands rest.nvim commands +--- +---@brief [[ +--- +--- `:Rest {command {args?}}` +--- +--- command action +--------------------------------------------------------------------------------- +--- +--- run {scope?} Execute one or several HTTP requests depending +--- on given `scope`. This scope can be either `last`, +--- `cursor` (default) or `document`. +--- last Re-run the last executed request, alias to `run last` +--- to retain backwards compatibility with the old keybinds +--- layout. +--- preview Preview the cURL command that is going to be ran while +--- executing the request (this does NOT run the request). +--- +---@brief ]] + +---@class RestCmd +---@field impl fun(args:string[], opts: vim.api.keyset.user_command) The command implementation +---@field complete? fun(subcmd_arg_lead: string): string[] Command completions callback, taking the lead of the subcommand's argument + +local commands = {} + +local functions = require("rest-nvim.functions") + +---@type { [string]: RestCmd } +local rest_command_tbl = { + run = { + impl = function(args) + local request_scope = #args == 0 and "cursor" or args[1] + functions.exec(request_scope, false) + end, + ---@return string[] + complete = function(args) + local scopes = { "last", "cursor", "document" } + if #args < 1 then + return scopes + end + + local match = vim.tbl_filter(function(scope) + if string.find(scope, "^" .. args) then + return scope + ---@diagnostic disable-next-line missing-return + end + end, scopes) + + return match + end, + }, + last = { + impl = function(_) + functions.exec("last", false) + end, + }, + preview = { + impl = function(_) + functions.exec("cursor", true) + end + }, + env = { + impl = function(args) + vim.print(args) + -- If there were no arguments for env then default to the `env("show", nil)` function behavior + if #args < 1 then + functions.env(nil, nil) + return + end + -- If there was only one argument and it is `set` then raise an error because we are also expecting for the env file path + if #args == 1 and args[1] == "set" then + vim.notify( + "Rest: Not enough arguments were passed to the 'env' command: 2 argument were expected, 1 was passed", + vim.log.levels.ERROR + ) + return + end + -- We do not need too many arguments here, complain about it please! + if #args > 3 then + vim.notify( + "Rest: Too many arguments were passed to the 'env' command: 2 arguments were expected, " .. #args .. " were passed" + ) + return + end + + functions.env(args[1], args[2]) + end, + ---@return string[] + complete = function(args) + local actions = { "set", "show" } + if #args < 1 then + return actions + end + + -- If the completion arguments have a whitespace then treat them as a table instead for easiness + if args:find(" ") then + args = vim.split(args, " ") + end + -- If the completion arguments is a table and `set` is the desired action then + -- return a list of files in the current working directory for completion + if type(args) == "table" and args[1]:match("set") then + -- We are currently looking for any ".*env*" file, e.g. ".env", ".env.json" + -- + -- This algorithm can be improved later on to search from a parent directory if the desired environment file + -- is somewhere else but in the current working directory. + local files = vim.fs.find(function(name, path) + return name:match(".*env.*$") + end, { limit = math.huge, type = "file", path = "./" }) + + return files + end + + local match = vim.tbl_filter(function(action) + if string.find(action, "^" .. args) then + return action + ---@diagnostic disable-next-line missing-return + end + end, actions) + + return match + end, + } +} + +local function rest(opts) + local fargs = opts.fargs + local cmd = fargs[1] + local args = #fargs > 1 and vim.list_slice(fargs, 2, #fargs) or {} + local command = rest_command_tbl[cmd] + if not command then + vim.notify("Rest: Unknown command: " .. cmd, vim.log.levels.ERROR) + return + end + + command.impl(args) +end + +---@package +function commands.init(bufnr) + vim.api.nvim_buf_create_user_command(bufnr, "Rest", rest, { + nargs = "+", + desc = "Run or preview your HTTP requests", + complete = function(arg_lead, cmdline, _) + local rest_commands = vim.tbl_keys(rest_command_tbl) + local subcmd, subcmd_arg_lead = cmdline:match("^Rest*%s(%S+)%s(.*)$") + if subcmd and subcmd_arg_lead and rest_command_tbl[subcmd] and rest_command_tbl[subcmd].complete then + return rest_command_tbl[subcmd].complete(subcmd_arg_lead) + end + if cmdline:match("^Rest*%s+%w*$") then + return vim.tbl_filter(function(cmd) + if string.find(cmd, "^" .. arg_lead) then + return cmd + ---@diagnostic disable-next-line missing-return + end + end, rest_commands) + end + end, + }) +end + +---Register a new `:Rest` subcommand +---@see vim.api.nvim_buf_create_user_command +---@param name string The name of the subcommand +---@param cmd RestCmd The implementation and optional completions +---@package +function commands.register_subcommand(name, cmd) + vim.validate({ name = { name, "string" } }) + vim.validate({ impl = { cmd.impl, "function" }, complete = { cmd.complete, "function", true } }) + + rest_command_tbl[name] = cmd +end + +return commands diff --git a/lua/rest-nvim/config/check.lua b/lua/rest-nvim/config/check.lua new file mode 100644 index 00000000..418c0c37 --- /dev/null +++ b/lua/rest-nvim/config/check.lua @@ -0,0 +1,97 @@ +---@mod rest-nvim.config.check rest.nvim config validation +--- +---@brief [[ +--- +--- rest.nvim config validation (internal) +--- +---@brief ]] + +local check = {} + +---@param tbl table The table to validate +---@see vim.validate +---@return boolean is_valid +---@return string|nil error_message +local function validate(tbl) + local ok, err = pcall(vim.validate, tbl) + return ok or false, "Rest: Invalid config" .. (err and ": " .. err or "") +end + +---Validates the configuration +---@param cfg RestConfig +---@return boolean is_valid +---@return string|nil error_message +function check.validate(cfg) + local ok, err = validate({ + runner = { cfg.runner, "string" }, + env_file = { cfg.env_file, "string" }, + encode_url = { cfg.encode_url, "boolean" }, + yank_dry_run = { cfg.yank_dry_run, "boolean" }, + skip_ssl_verification = { cfg.skip_ssl_verification, "boolean" }, + custom_dynamic_variables = { cfg.custom_dynamic_variables, "table" }, + keybinds = { cfg.keybinds, "table" }, + -- RestConfigResult + result = { cfg.result, "table" }, + -- RestConfigResultSplit + split = { cfg.result.split, "table" }, + horizontal = { cfg.result.split.horizontal, "boolean" }, + in_place = { cfg.result.split.in_place, "boolean" }, + stay_in_current_window_after_split = { cfg.result.split.stay_in_current_window_after_split, "boolean" }, + -- RestConfigResultBehavior + behavior = { cfg.result.behavior, "table" }, + -- RestConfigResultInfo + show_info = { cfg.result.behavior.show_info, "table" }, + url = { cfg.result.behavior.show_info.url, "boolean" }, + headers = { cfg.result.behavior.show_info.headers, "boolean" }, + http_info = { cfg.result.behavior.show_info.http_info, "boolean" }, + curl_command = { cfg.result.behavior.show_info.curl_command, "boolean" }, + -- RestConfigResultStats + statistics = { cfg.result.behavior.statistics, "table" }, + statistics_enable = { cfg.result.behavior.statistics.enable, "boolean" }, + stats = { cfg.result.behavior.statistics.stats, "table" }, + -- RestConfigResultFormatters + formatters = { cfg.result.behavior.formatters, "table" }, + json = { cfg.result.behavior.formatters.json, { "string", "function" } }, + html = { cfg.result.behavior.formatters.html, { "string", "function" } }, + -- RestConfigHighlight + highlight_enable = { cfg.highlight.enable, "boolean" }, + timeout = { cfg.highlight.timeout, "number" }, + }) + + if not ok then + return false, err + end + return true +end + +---Recursively check a table for unrecognized keys, +---using a default table as a reference +---@param tbl table +---@param default_tbl table +---@return string[] +function check.get_unrecognized_keys(tbl, default_tbl) + local unrecognized_keys = {} + for k, _ in pairs(tbl) do + unrecognized_keys[k] = true + end + for k, _ in pairs(default_tbl) do + unrecognized_keys[k] = false + end + + local ret = {} + for k, _ in pairs(unrecognized_keys) do + if unrecognized_keys[k] then + ret[k] = k + end + if type(default_tbl[k]) == "table" and tbl[k] then + for _, subk in pairs(check.get_unrecognized_keys(tbl[k], default_tbl[k])) do + local key = k .. "." .. subk + ret[key] = key + end + end + end + + return vim.tbl_keys(ret) +end + +return check diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index abf992f5..b1da6eaf 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -1,67 +1,151 @@ -local M = {} +---@mod rest-nvim.config rest.nvim configuration +--- +---@brief [[ +--- +--- rest.nvim configuration options +--- +---@brief ]] -local config = { - result_split_horizontal = false, - result_split_in_place = false, - skip_ssl_verification = false, +local config = {} + +---@class RestConfigDebug +---@field unrecognized_configs string[] Unrecognized configuration options + +---@class RestConfigResult +---@field split RestConfigResultSplit Result split window behavior +---@field behavior RestConfigResultBehavior Result buffer behavior + +---@class RestConfigResultSplit +---@field horizontal boolean Open request results in a horizontal split +---@field in_place boolean Keep the HTTP file buffer above|left when split horizontal|vertical +---@field stay_in_current_window_after_split boolean Stay in the current window (HTTP file) or change the focus to the results window + +---@class RestConfigResultBehavior +---@field show_info RestConfigResultInfo Request results information +---@field statistics RestConfigResultStats Request results statistics +---@field formatters RestConfigResultFormatters Formatters for the request results body + +---@class RestConfigResultInfo +---@field url boolean Display the request URL +---@field headers boolean Display the request headers +---@field http_info boolean Display the request HTTP information +---@field curl_command boolean Display the cURL command that was used for the request + +---@class RestConfigResultStats +---@field enable boolean Whether enable statistics or not +---@field stats string[]|{ [1]: string, title?: string, type: string }[] Statistics to be shown, takes cURL's `--write-out` options + +---@class RestConfigResultFormatters +---@field json string|fun(body: string): string JSON formatter +---@field html string|fun(body: string): string HTML formatter + +---@class RestConfigHighlight +---@field enable boolean Whether current request highlighting is enabled or not +---@field timeout number Duration time of the request highlighting in milliseconds + +---@class RestConfig +---@field runner string The HTTP client to be used when running requests, default is `curl` +---@field env_file string Environment variables file to be used for the request variables in the document +---@field encode_url boolean Encode URL before making request +---@field yank_dry_run boolean Whether to copy the request preview (cURL command) to the clipboard +---@field skip_ssl_verification boolean Skip SSL verification, useful for unknown certificates +---@field custom_dynamic_variables { [string]: fun(): string }[] Table of custom dynamic variables +---@field result RestConfigResult Request results buffer behavior +---@field highlight RestConfigHighlight Request highlighting +---@field keybinds { [1]: string, [2]: string, [3]: string }[] Keybindings list +---@field debug_info? RestConfigDebug Configurations debug information, set automatically + +---rest.nvim default configuration +---@type RestConfig +local default_config = { + runner = "curl", + env_file = ".env", encode_url = true, - highlight = { - enabled = true, - timeout = 150, - }, + yank_dry_run = true, + skip_ssl_verification = false, + custom_dynamic_variables = {}, result = { - show_curl_command = true, - show_url = true, - show_http_info = true, - show_headers = true, - show_statistics = false, - formatters = { - json = "jq", - html = function(body) - if vim.fn.executable("tidy") == 0 then - return body - end - -- stylua: ignore - return vim.fn.system({ - "tidy", "-i", "-q", - "--tidy-mark", "no", - "--show-body-only", "auto", - "--show-errors", "0", - "--show-warnings", "0", - "-", - }, body):gsub("\n$", "") - end, + split = { + horizontal = false, + in_place = false, + stay_in_current_window_after_split = true, + }, + behavior = { + show_info = { + url = true, + headers = true, + http_info = true, + curl_command = true, + }, + statistics = { + enable = true, + stats = { + { "time_total", title = "Total time: ", type = "time" }, + { "size_download", title = "Request download size: ", type = "byte" }, + }, + }, + formatters = { + json = "jq", + html = function(body) + if vim.fn.executable("tidy") == 0 then + return body + end + -- stylua: ignore + return vim.fn.system({ + "tidy", + "-i", + "-q", + "--tidy-mark", "no", + "--show-body-only", "auto", + "--show-errors", "0", + "--show-warnings", "0", + "-", + }, body):gsub("\n$", "") + end, + }, }, }, - jump_to_request = false, - env_file = ".env", - custom_dynamic_variables = {}, - yank_dry_run = true, + highlight = { + enable = true, + timeout = 150, + }, + ---Example: + --- + ---```lua + ---keybinds = { + --- "r", "(RestRun)", "Run request under the cursor", + ---} + --- + ---``` + ---@see vim.keymap.set + keybinds = {}, } ---- Get a configuration value ---- @param opt string ---- @return any -M.get = function(opt) - -- If an option was passed then - -- return the requested option. - -- Otherwise, return the entire - -- configurations. - if opt then - return config[opt] - end +---Set user-defined configurations for rest.nvim +---@param user_configs RestConfig User configurations +---@return RestConfig +function config.set(user_configs) + local check = require("rest-nvim.config.check") - return config -end + local conf = vim.tbl_deep_extend("force", { + debug_info = { + unrecognized_configs = check.get_unrecognized_keys(user_configs, default_config), + }, + }, default_config, user_configs) ---- Set user-defined configurations ---- @param user_configs table ---- @return table -M.set = function(user_configs) - vim.validate({ user_configs = { user_configs, "table" } }) + local ok, err = check.validate(conf) + if not ok then + vim.notify("Rest: " .. err, vim.log.levels.ERROR) + end + + if #conf.debug_info.unrecognized_configs > 0 then + vim.notify( + "Rest: Unrecognized configs found in setup: " .. vim.inspect(config.debug_info.unrecognized_configs), + vim.log.levels.WARN + ) + end - config = vim.tbl_deep_extend("force", config, user_configs) - return config + return conf end -return M +return config diff --git a/lua/rest-nvim/curl/init.lua b/lua/rest-nvim/curl/init.lua deleted file mode 100644 index 62008f1b..00000000 --- a/lua/rest-nvim/curl/init.lua +++ /dev/null @@ -1,303 +0,0 @@ -local utils = require("rest-nvim.utils") -local curl = require("plenary.curl") -local log = require("plenary.log").new({ plugin = "rest.nvim" }) -local config = require("rest-nvim.config") - -local M = {} --- checks if 'x' can be executed by system() -local function is_executable(x) - if type(x) == "string" and vim.fn.executable(x) == 1 then - return true - elseif vim.tbl_islist(x) and vim.fn.executable(x[1] or "") == 1 then - return true - end - - return false -end - -local function format_curl_cmd(res) - local cmd = "curl" - - for _, value in pairs(res) do - if string.sub(value, 1, 1) == "-" then - cmd = cmd .. " " .. value - else - cmd = cmd .. " '" .. value .. "'" - end - end - - -- remote -D option - cmd = string.gsub(cmd, "-D '%S+' ", "") - return cmd -end - -local function send_curl_start_event(data) - vim.api.nvim_exec_autocmds("User", { - pattern = "RestStartRequest", - modeline = false, - data = data, - }) -end - -local function send_curl_stop_event(data) - vim.api.nvim_exec_autocmds("User", { - pattern = "RestStopRequest", - modeline = false, - data = data, - }) -end - -local function create_error_handler(opts) - return function(err) - send_curl_stop_event(vim.tbl_extend("keep", { err = err }, opts)) - error(err.message) - end -end - -local function parse_headers(headers) - local parsed = {} - for _, header in ipairs(headers) do - if header ~= "" then - local key, value = header:match("([^:]+):%s*(.*)") - if key then - parsed[key] = value or "" - end - end - end - return parsed -end - --- get_or_create_buf checks if there is already a buffer with the rest run results --- and if the buffer does not exists, then create a new one -M.get_or_create_buf = function() - local tmp_name = "rest_nvim_results" - - -- Check if the file is already loaded in the buffer - local existing_bufnr = vim.fn.bufnr(tmp_name) - if existing_bufnr ~= -1 then - -- Set modifiable - vim.api.nvim_set_option_value("modifiable", true, { buf = existing_bufnr }) - -- Prevent modified flag - vim.api.nvim_set_option_value("buftype", "nofile", { buf = existing_bufnr }) - -- Delete buffer content - vim.api.nvim_buf_set_lines(existing_bufnr, 0, -1, false, {}) - - -- Make sure the filetype of the buffer is httpResult so it will be highlighted - vim.api.nvim_set_option_value("ft", "httpResult", { buf = existing_bufnr }) - - return existing_bufnr - end - - -- Create new buffer - local new_bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(new_bufnr, tmp_name) - vim.api.nvim_set_option_value("ft", "httpResult", { buf = new_bufnr }) - vim.api.nvim_set_option_value("buftype", "nofile", { buf = new_bufnr }) - - return new_bufnr -end - -local function create_callback(curl_cmd, opts) - local method = opts.method - local url = opts.url - local script_str = opts.script_str - - return function(res) - send_curl_stop_event(vim.tbl_extend("keep", { res = res }, opts)) - - if res.exit ~= 0 then - log.error("[rest.nvim] " .. utils.curl_error(res.exit)) - return - end - local res_bufnr = M.get_or_create_buf() - - local headers = utils.filter(res.headers, function(value) - return value ~= "" - end, false) - - headers = utils.map(headers, function(value) - local _, _, http, status = string.find(value, "^(HTTP.*)%s+(%d+)%s*$") - - if http and status then - return http .. " " .. utils.http_status(tonumber(status)) - end - - return value - end) - - headers = utils.split_list(headers, function(value) - return string.find(value, "^HTTP.*$") - end) - - res.headers = parse_headers(res.headers) - - local content_type = res.headers[utils.key(res.headers, "content-type")] - if content_type then - content_type = content_type:match("application/([-a-z]+)") or content_type:match("text/(%l+)") - end - - if script_str ~= nil then - local context = { - result = res, - pretty_print = vim.pretty_print, - json_decode = vim.fn.json_decode, - set_env = utils.set_env, - set = utils.set_context, - } - local env = { context = context } - setmetatable(env, { __index = _G }) - local f = load(script_str, nil, "bt", env) - if f ~= nil then - f() - end - end - - if config.get("result").show_url then - --- Add metadata into the created buffer (status code, date, etc) - -- Request statement (METHOD URL) - utils.write_block(res_bufnr, { method:upper() .. " " .. url }, false) - end - - -- This can be quite verbose so let user control it - if config.get("result").show_curl_command then - utils.write_block(res_bufnr, { "Command: " .. curl_cmd }, true) - end - - if config.get("result").show_http_info then - -- HTTP version, status code and its meaning, e.g. HTTP/1.1 200 OK - utils.write_block(res_bufnr, { "HTTP/1.1 " .. utils.http_status(res.status) }, false) - end - - if config.get("result").show_headers then - -- Headers, e.g. Content-Type: application/json - for _, header_block in ipairs(headers) do - utils.write_block(res_bufnr, header_block, true) - end - end - - if config.get("result").show_statistics then - -- Statistics, e.g. Total Time: 123.4 ms - local statistics - - res.body, statistics = utils.parse_statistics(res.body) - - utils.write_block(res_bufnr, statistics, true) - end - - --- Add the curl command results into the created buffer - local formatter = config.get("result").formatters[content_type] - -- format response body - if type(formatter) == "function" then - local ok, out = pcall(formatter, res.body) - -- check if formatter ran successfully - if ok and out then - res.body = out - else - vim.api.nvim_echo({ - { - string.format("Error calling formatter on response body:\n%s", out), - "Error", - }, - }, false, {}) - end - elseif is_executable(formatter) then - local stdout = vim.fn.system(formatter, res.body):gsub("\n$", "") - -- check if formatter ran successfully - if vim.v.shell_error == 0 then - res.body = stdout - else - vim.api.nvim_echo({ - { - string.format( - "Error running formatter %s on response body:\n%s", - vim.inspect(formatter), - stdout - ), - "Error", - }, - }, false, {}) - end - end - - -- append response container - local buf_content = "#+RESPONSE\n" - if utils.is_binary_content_type(content_type) then - buf_content = buf_content .. "Binary answer" - else - buf_content = buf_content .. res.body - end - buf_content = buf_content .. "\n#+END" - - local lines = utils.split(buf_content, "\n") - - utils.write_block(res_bufnr, lines) - - -- Only open a new split if the buffer is not loaded into the current window - if vim.fn.bufwinnr(res_bufnr) == -1 then - local cmd_split = [[vert sb]] - if config.get("result_split_horizontal") then - cmd_split = [[sb]] - end - if config.get("result_split_in_place") then - cmd_split = [[bel ]] .. cmd_split - end - vim.cmd(cmd_split .. res_bufnr) - -- Set unmodifiable state - vim.api.nvim_set_option_value("modifiable", false, { buf = res_bufnr }) - end - - -- Send cursor in response buffer to start - utils.move_cursor(res_bufnr, 1) - - -- add syntax highlights for response - local syntax_file = vim.fn.expand(string.format("$VIMRUNTIME/syntax/%s.vim", content_type)) - - if vim.fn.filereadable(syntax_file) == 1 then - vim.cmd(string.gsub( - [[ - if exists("b:current_syntax") - unlet b:current_syntax - endif - syn include @%s syntax/%s.vim - syn region %sBody matchgroup=Comment start=+\v^#\+RESPONSE$+ end=+\v^#\+END$+ contains=@%s - - let b:current_syntax = "httpResult" - ]], - "%%s", - content_type - )) - end - end -end - --- curl_cmd runs curl with the passed options, gets or creates a new buffer --- and then the results are printed to the recently obtained/created buffer --- @param opts (table) curl arguments: --- - yank_dry_run (boolean): displays the command --- - arguments are forwarded to plenary -M.curl_cmd = function(opts) - -- plenary's curl module is strange in the sense that with "dry_run" it returns the command - -- otherwise it starts the request :/ - local dry_run_opts = vim.tbl_extend("force", opts, { dry_run = true }) - local res = curl[opts.method](dry_run_opts) - local curl_cmd = format_curl_cmd(res) - - send_curl_start_event(opts) - - if opts.dry_run then - if config.get("yank_dry_run") then - vim.cmd("let @+=" .. string.format("%q", curl_cmd)) - end - - vim.api.nvim_echo({ { "[rest.nvim] Request preview:\n", "Comment" }, { curl_cmd } }, false, {}) - - send_curl_stop_event(opts) - return - else - opts.callback = vim.schedule_wrap(create_callback(curl_cmd, opts)) - opts.on_error = vim.schedule_wrap(create_error_handler(opts)) - curl[opts.method](opts) - end -end - -return M diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua new file mode 100644 index 00000000..fa32ec19 --- /dev/null +++ b/lua/rest-nvim/functions.lua @@ -0,0 +1,79 @@ +---@mod rest-nvim.functions rest.nvim functions +--- +---@brief [[ +--- +--- rest.nvim functions +--- +---@brief ]] + +local functions = {} + +local utils = require("rest-nvim.utils") + +---Execute or `preview` one or several HTTP requests depending on given `scope` +---and return request(s) results in a table that will be used to render results +---in a buffer. +---@param scope string Defines the request execution scope. Can be: `last`, `cursor` (default) or `document` +---@param preview boolean Whether execute the request or just preview the command that is going to be ran. Default is `false` +---@return table Request results (HTTP client output) +function functions.exec(scope, preview) + vim.validate({ + scope = { scope, "string" }, + preview = { preview, "boolean" }, + }) + + -- Fallback to 'cursor' if no scope was given + if not scope then + scope = "cursor" + end + + -- Raise an error if an invalid scope has been provided + if not vim.tbl_contains({ "last", "cursor", "document" }, scope) then + vim.notify( + "Rest: Invalid scope '" .. scope .. "'provided to the 'exec' function", + vim.log.levels.ERROR + ) + return {} + end + + print("WIP") +end + +---Manage the environment file that is currently in use while running requests +--- +---If you choose to `set` the environment, you must provide a `path` to the environment file. +---@param action string Determines the action to be taken. Can be: `set` or `show` (default) +function functions.env(action, path) + vim.validate({ + action = { action, { "string", "nil" } }, + path = { path, { "string", "nil" } }, + }) + + -- TODO: add a `select` action later to open some kind of prompt to select one of many detected "*env*" files + if not action then + action = "show" + end + + if not vim.tbl_contains({ "set", "show" }, action) then + vim.notify( + "Rest: Invalid action '" .. action .. "' provided to the 'env' function", + vim.log.levels.ERROR + ) + return + end + + vim.print(action) + if action == "set" then + -- TODO: check file path and some other goofy ahhh stuff + if utils.file_exists(path) then + _G._rest_nvim.env_file = path + vim.notify("Rest: Current env file has been changed to: " .. _G._rest_nvim.env_file) + else + vim.notify("Rest: Passed environment file '" .. path .. "' was not found", vim.log.levels.ERROR) + end + else + vim.notify("Rest: Current env file in use: " .. _G._rest_nvim.env_file) + end +end + +return functions diff --git a/lua/rest-nvim/init.lua b/lua/rest-nvim/init.lua index 7b9051dc..91313a31 100644 --- a/lua/rest-nvim/init.lua +++ b/lua/rest-nvim/init.lua @@ -1,245 +1,22 @@ -local backend = require("rest-nvim.request") -local config = require("rest-nvim.config") -local curl = require("rest-nvim.curl") -local log = require("plenary.log").new({ plugin = "rest.nvim" }) -local utils = require("rest-nvim.utils") -local path = require("plenary.path") +---@mod rest-nvim rest.nvim +--- +---@brief [[ +--- +--- A fast and asynchronous Neovim HTTP client written in Lua +--- +---@brief ]] local rest = {} -local Opts = {} -local defaultRequestOpts = { - verbose = false, - highlight = false, -} - -local LastOpts = {} - -rest.setup = function(user_configs) - config.set(user_configs or {}) -end - --- run will retrieve the required request information from the current buffer --- and then execute curl --- @param verbose toggles if only a dry run with preview should be executed (true = preview) -rest.run = function(verbose) - local ok, result = backend.get_current_request() - if not ok then - log.error("Failed to run the http request:") - log.error(result) - vim.api.nvim_err_writeln("[rest.nvim] Failed to get the current HTTP request: " .. result) - return - end - - return rest.run_request(result, { verbose = verbose }) -end - --- run will retrieve the required request information from the current buffer --- and then execute curl --- @param string filename to load --- @param opts table --- 1. keep_going boolean keep running even when last request failed --- 2. verbose boolean -rest.run_file = function(filename, opts) - log.info("Running file :" .. filename) - opts = vim.tbl_deep_extend( - "force", -- use value from rightmost map - defaultRequestOpts, - { highlight = config.get("highlight").enabled }, - opts or {} - ) - - -- 0 on error or buffer handle - local new_buf = vim.api.nvim_create_buf(true, false) - - vim.api.nvim_win_set_buf(0, new_buf) - vim.cmd.edit(filename) - - local requests = backend.buf_list_requests(new_buf) - for _, req in pairs(requests) do - rest.run_request(req, opts) - end - - return true -end - --- replace variables in header values -local function splice_headers(headers) - for name, value in pairs(headers) do - headers[name] = utils.replace_vars(value) - end - return headers -end - --- return the spliced/resolved filename --- @param string the filename w/o variables -local function load_external_payload(fileimport_string) - local fileimport_spliced = utils.replace_vars(fileimport_string) - if path:new(fileimport_spliced):is_absolute() then - return fileimport_spliced - else - local file_dirname = vim.fn.expand("%:p:h") - local file_name = path:new(path:new(file_dirname), fileimport_spliced) - return file_name:absolute() - end -end - - --- @param headers table HTTP headers --- @param payload table of the form { external = bool, filename_tpl= path, body_tpl = string } --- with body_tpl an array of lines -local function splice_body(headers, payload) - local external_payload = payload.external - local lines -- array of strings - if external_payload then - local importfile = load_external_payload(payload.filename_tpl) - if not utils.file_exists(importfile) then - error("import file " .. importfile .. " not found") - end - -- TODO we dont necessarily want to load the file, it can be slow - -- https://github.com/rest-nvim/rest.nvim/issues/203 - lines = utils.read_file(importfile) - else - lines = payload.body_tpl - end - local content_type = headers[utils.key(headers,"content-type")] or "" - local has_json = content_type:find("application/[^ ]*json") - - local body = "" - local vars = utils.read_variables() - -- nvim_buf_get_lines is zero based and end-exclusive - -- but start_line and stop_line are one-based and inclusive - -- magically, this fits :-) start_line is the CRLF between header and body - -- which should not be included in the body, stop_line is the last line of the body - for _, line in ipairs(lines) do - body = body .. utils.replace_vars(line, vars) - end - local is_json, json_body = pcall(vim.json.decode, body) - - if is_json and json_body then - if has_json then - -- convert entire json body to string. - return vim.fn.json_encode(json_body) - else - -- convert nested tables to string. - for key, val in pairs(json_body) do - if type(val) == "table" then - json_body[key] = vim.fn.json_encode(val) - end - end - return vim.fn.json_encode(json_body) - end - end -end - --- run will retrieve the required request information from the current buffer --- and then execute curl --- @param req table see validate_request to check the expected format --- @param opts table --- 1. keep_going boolean keep running even when last request failed -rest.run_request = function(req, opts) - -- TODO rename result to request - local result = req - local curl_raw_args = config.get("skip_ssl_verification") and vim.list_extend(result.raw, { "-k" }) - or result.raw - opts = vim.tbl_deep_extend( - "force", -- use value from rightmost map - defaultRequestOpts, - { highlight = config.get("highlight").enabled }, - opts or {} - ) - - -- if we want to pass as a file, we pass nothing to plenary - local spliced_body = nil - if not req.body.inline and req.body.filename_tpl then - curl_raw_args = vim.tbl_extend("force", curl_raw_args, { - '--data-binary', '@'..load_external_payload(req.body.filename_tpl)}) - else - spliced_body = splice_body(result.headers, result.body) - end - - if config.get("result").show_statistics then - local statistics_line = {} - - for _, tbl in ipairs(config.get("result").show_statistics) do - if type(tbl) == "string" then - tbl = { tbl } - end - - table.insert(statistics_line, tbl[1] .. "=%{" .. tbl[1] .. "}") - end - - curl_raw_args = vim.tbl_extend("force", curl_raw_args, { - "--write-out", - "\\n" .. table.concat(statistics_line, "&"), - }) - end - - Opts = { - request_id = vim.loop.now(), -- request id used to correlate RestStartRequest and RestStopRequest events - method = result.method:lower(), - url = result.url, - -- plenary.curl can't set http protocol version - -- http_version = result.http_version, - headers = splice_headers(result.headers), - raw = curl_raw_args, - body = spliced_body, - dry_run = opts.verbose, - bufnr = result.bufnr, - start_line = result.start_line, - end_line = result.end_line, - script_str = result.script_str, - } - - if not opts.verbose then - LastOpts = Opts - end - - if opts.highlight then - backend.highlight(result.bufnr, result.start_line, result.end_line) - end - - local success_req, req_err = pcall(curl.curl_cmd, Opts) - - if not success_req then - vim.api.nvim_err_writeln( - "[rest.nvim] Failed to perform the request.\nMake sure that you have entered the proper URL and the server is running.\n\nTraceback: " - .. req_err - ) - return false, req_err - end -end - --- last will run the last curl request, if available -rest.last = function() - if LastOpts.url == nil then - vim.api.nvim_err_writeln("[rest.nvim]: Last request not found") - return - end - - if config.get("highlight").enabled then - backend.highlight(LastOpts.bufnr, LastOpts.start_line, LastOpts.end_line) - end - - local success_req, req_err = pcall(curl.curl_cmd, LastOpts) - - if not success_req then - vim.api.nvim_err_writeln( - "[rest.nvim] Failed to perform the request.\nMake sure that you have entered the proper URL and the server is running.\n\nTraceback: " - .. req_err - ) - end -end +local config = require("rest-nvim.config") +local autocmds = require("rest-nvim.autocmds") -rest.request = backend +---Set up rest.nvim +---@param user_configs RestConfig User configurations +function rest.setup(user_configs) + _G._rest_nvim = config.set(user_configs or {}) -rest.select_env = function(env_file) - if path ~= nil then - vim.validate({ env_file = { env_file, "string" } }) - config.set({ env_file = env_file }) - else - print("No path given") - end + autocmds.setup() end return rest diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua new file mode 100644 index 00000000..e69de29b diff --git a/lua/rest-nvim/request/init.lua b/lua/rest-nvim/request/init.lua deleted file mode 100644 index 957fc6a7..00000000 --- a/lua/rest-nvim/request/init.lua +++ /dev/null @@ -1,406 +0,0 @@ -local utils = require("rest-nvim.utils") -local log = require("plenary.log").new({ plugin = "rest.nvim" }) -local config = require("rest-nvim.config") - --- get_importfile returns in case of an imported file the absolute filename --- @param bufnr Buffer number, a.k.a id --- @param stop_line Line to stop searching --- @return tuple filename and whether we should inline it when invoking curl -local function get_importfile_name(bufnr, start_line, stop_line) - -- store old cursor position - local oldpos = vim.fn.getcurpos() - utils.move_cursor(bufnr, start_line) - - local import_line = vim.fn.search("^<", "cn", stop_line) - -- restore old cursor position - utils.move_cursor(bufnr, oldpos[2]) - - if import_line > 0 then - local fileimport_string - local fileimport_line - local fileimport_inlined - fileimport_line = vim.api.nvim_buf_get_lines(bufnr, import_line - 1, import_line, false) - -- check second char against '@' (meaning "dont inline") - fileimport_inlined = string.sub(fileimport_line[1], 2, 2) ~= '@' - fileimport_string = string.gsub(fileimport_line[1], "<@?", "", 1):gsub("^%s+", ""):gsub("%s+$", "") - return fileimport_inlined, fileimport_string - - end - return nil -end - --- get_body retrieves the body lines in the buffer and then returns --- either a table if the body is a JSON or a raw string if it is a filename --- Plenary.curl allows a table or a raw string as body and can distinguish --- between strings with filenames and strings with the raw body --- @param bufnr Buffer number, a.k.a id --- @param start_line Line where body starts --- @param stop_line Line where body stops --- @return table { external = bool; filename_tpl or body_tpl; } -local function get_body(bufnr, start_line, stop_line) - -- first check if the body should be imported from an external file - local inline, importfile = get_importfile_name(bufnr, start_line, stop_line) - local lines -- an array of strings - if importfile ~= nil then - return { external = true; inline = inline; filename_tpl = importfile } - else - lines = vim.api.nvim_buf_get_lines(bufnr, start_line, stop_line, false) - end - - -- nvim_buf_get_lines is zero based and end-exclusive - -- but start_line and stop_line are one-based and inclusive - -- magically, this fits :-) start_line is the CRLF between header and body - -- which should not be included in the body, stop_line is the last line of the body - local lines2 = {} - for _, line in ipairs(lines) do - -- stop if a script opening tag is found - if line:find("{%%") then - break - end - -- Ignore commented lines with and without indent - if not utils.contains_comments(line) then - lines2[#lines2 + 1] = line - end - end - - return { external = false; inline = false; body_tpl = lines2 } -end - -local function get_response_script(bufnr, start_line, stop_line) - local all_lines = vim.api.nvim_buf_get_lines(bufnr, start_line, stop_line, false) - -- Check if there is a script - local script_start_rel - for i, line in ipairs(all_lines) do - -- stop if a script opening tag is found - if line:find("{%%") then - script_start_rel = i - break - end - end - - if script_start_rel == nil then - return nil - end - - -- Convert the relative script line number to the line number of the buffer - local script_start = start_line + script_start_rel - 1 - - local script_lines = vim.api.nvim_buf_get_lines(bufnr, script_start, stop_line, false) - local script_str = "" - - for _, line in ipairs(script_lines) do - script_str = script_str .. line .. "\n" - if line:find("%%}") then - break - end - end - - return script_str:match("{%%(.-)%%}") -end - --- is_request_line checks if the given line is a http request line according to RFC 2616 -local function is_request_line(line) - local http_methods = { "GET", "POST", "PUT", "PATCH", "DELETE" } - for _, method in ipairs(http_methods) do - if line:find("^" .. method) then - return true - end - end - return false -end - --- get_headers retrieves all the found headers and returns a lua table with them --- @param bufnr Buffer number, a.k.a id --- @param start_line Line where the request starts --- @param end_line Line where the request ends -local function get_headers(bufnr, start_line, end_line) - local headers = {} - local headers_end = end_line - - -- Iterate over all buffer lines starting after the request line - for line_number = start_line + 1, end_line do - local line_content = vim.fn.getbufline(bufnr, line_number)[1] - - -- message header and message body are separated by CRLF (see RFC 2616) - -- for our purpose also the next request line will stop the header search - if is_request_line(line_content) or line_content == "" then - headers_end = line_number - break - end - if not line_content:find(":") then - log.warn("Missing Key/Value pair in message header. Ignoring line: ", line_content) - goto continue - end - - local header_name, header_value = line_content:match("^(.-): ?(.*)$") - - if not utils.contains_comments(header_name) then - headers[header_name] = header_value - end - ::continue:: - end - - return headers, headers_end -end - --- get_curl_args finds command line flags and returns a lua table with them --- @param bufnr Buffer number, a.k.a id --- @param headers_end Line where the headers end --- @param end_line Line where the request ends -local function get_curl_args(bufnr, headers_end, end_line) - local curl_args = {} - local body_start = end_line - - log.debug("Getting curl args between lines", headers_end, " and ", end_line) - for line_number = headers_end, end_line do - local line_content = vim.fn.getbufline(bufnr, line_number)[1] - - if line_content:find("^ *%-%-?[a-zA-Z%-]+") then - local lc = vim.split(line_content, " ") - local x = "" - - for i, y in ipairs(lc) do - x = x .. y - - if #y:match("\\*$") % 2 == 1 and i ~= #lc then - -- insert space if there is an slash at end - x = x .. " " - else - -- insert 'x' into curl_args and reset it - table.insert(curl_args, x) - x = "" - end - end - elseif not line_content:find("^ *$") then - if line_number ~= end_line then - body_start = line_number - 1 - end - break - end - end - - return curl_args, body_start -end - --- start_request will find the request line (e.g. POST http://localhost:8081/foo) --- of the current request and returns the linenumber of this request line. --- The current request is defined as the next request line above the cursor --- @param bufnr The buffer number of the .http-file --- @param linenumber (number) From which line to start looking -local function start_request(bufnr, linenumber) - log.debug("Searching pattern starting from " .. linenumber) - - local oldlinenumber = linenumber - utils.move_cursor(bufnr, linenumber) - - local res = vim.fn.search("^GET\\|^POST\\|^PUT\\|^PATCH\\|^DELETE", "bcnW") - -- restore cursor position - utils.move_cursor(bufnr, oldlinenumber) - - return res -end - --- end_request will find the next request line (e.g. POST http://localhost:8081/foo) --- and returns the linenumber before this request line or the end of the buffer --- @param bufnr The buffer number of the .http-file -local function end_request(bufnr, linenumber) - -- store old cursor position - local oldlinenumber = linenumber - local last_line = vim.fn.line("$") - - -- start searching for next request from the next line - -- as the current line does contain the current, not the next request - if linenumber < last_line then - linenumber = linenumber + 1 - end - utils.move_cursor(bufnr, linenumber) - - local next = vim.fn.search("^GET\\|^POST\\|^PUT\\|^PATCH\\|^DELETE\\|^###\\", "cnW") - - -- restore cursor position - utils.move_cursor(bufnr, oldlinenumber) - - if next == 0 or (oldlinenumber == last_line) then - return last_line - else - -- skip comment lines above requests - while vim.fn.getline(next - 1):find("^ *#") do - next = next - 1 - end - - return next - 1 - end -end - --- parse_url returns a table with the method of the request and the URL --- @param stmt the request statement, e.g., POST http://localhost:3000/foo -local function parse_url(stmt) - -- remove HTTP - local parsed = utils.split(stmt, " HTTP/") - local http_version = nil - if parsed[2] ~= nil then - http_version = parsed[2] - end - parsed = utils.split(parsed[1], " ") - local http_method = parsed[1] - table.remove(parsed, 1) - local target_url = table.concat(parsed, " ") - - target_url = utils.replace_vars(target_url) - if config.get("encode_url") then - -- Encode URL - target_url = utils.encode_url(target_url) - end - - return { - method = http_method, - http_version = http_version, - url = target_url, - } -end - -local M = {} -M.get_current_request = function() - return M.buf_get_request(vim.api.nvim_win_get_buf(0), vim.fn.getcurpos()) -end - --- buf_get_request returns a table with all the request settings --- @param bufnr (number|nil) the buffer number --- @param curpos the cursor position --- @return (boolean, request or string) -M.buf_get_request = function(bufnr, curpos) - curpos = curpos or vim.fn.getcurpos() - bufnr = bufnr or vim.api.nvim_win_get_buf(0) - - local start_line = start_request(bufnr, curpos[2]) - - if start_line == 0 then - return false, "No request found" - end - local end_line = end_request(bufnr, start_line) - - local parsed_url = parse_url(vim.fn.getline(start_line)) - - local headers, headers_end = get_headers(bufnr, start_line, end_line) - - local curl_args, body_start = get_curl_args(bufnr, headers_end, end_line) - - local host = headers[utils.key(headers,"host")] or "" - parsed_url.url = host:gsub("%s+", "") .. parsed_url.url - headers[utils.key(headers,"host")] = nil - - local body = get_body(bufnr, body_start, end_line) - - local script_str = get_response_script(bufnr, headers_end, end_line) - - -- TODO this should just parse the request without modifying external state - -- eg move to run_request - if config.get("jump_to_request") then - utils.move_cursor(bufnr, start_line) - else - utils.move_cursor(bufnr, curpos[2], curpos[3]) - end - - local req = { - method = parsed_url.method, - url = parsed_url.url, - http_version = parsed_url.http_version, - headers = headers, - raw = curl_args, - body = body, - bufnr = bufnr, - start_line = start_line, - end_line = end_line, - script_str = script_str, - } - - return true, req -end - -M.print_request = function(req) - print(M.stringify_request(req)) -end - --- converts request into string, helpful for debug --- full_body boolean -M.stringify_request = function(req, opts) - opts = vim.tbl_deep_extend( - "force", -- use value from rightmost map - { full_body = false, headers = true }, -- defaults - opts or {} - ) - local str = [[ - url : ]] .. req.url .. [[\n - method: ]] .. req.method .. [[\n - range : ]] .. tostring(req.start_line) .. [[ -> ]] .. tostring(req.end_line) .. [[\n - ]] - - if req.http_version then - str = str .. "\nhttp_version: " .. req.http_version .. "\n" - end - - if opts.headers then - for name, value in pairs(req.headers) do - str = str .. "header '" .. name .. "'=" .. value .. "\n" - end - end - - if opts.full_body then - if req.body then - local res = req.body - str = str .. "body: " .. res .. "\n" - end - end - - -- here we should just display the beginning of the request - return str -end - -M.buf_list_requests = function(buf, _opts) - local last_line = vim.fn.line("$") - local requests = {} - - -- reset cursor position - vim.fn.cursor({ 1, 1 }) - local curpos = vim.fn.getcurpos() - log.debug("Listing requests for buf ", buf) - while curpos[2] <= last_line do - local ok, req = M.buf_get_request(buf, curpos) - if ok then - curpos[2] = req.end_line + 1 - requests[#requests + 1] = req - else - break - end - end - -- log.debug("found " , #requests , "requests") - return requests -end - -local select_ns = vim.api.nvim_create_namespace("rest-nvim") -M.highlight = function(bufnr, start_line, end_line) - local opts = config.get("highlight") or {} - local higroup = "IncSearch" - local timeout = opts.timeout or 150 - - vim.api.nvim_buf_clear_namespace(bufnr, select_ns, 0, -1) - - local end_column = string.len(vim.fn.getline(end_line)) - - vim.highlight.range( - bufnr, - select_ns, - higroup, - { start_line - 1, 0 }, - { end_line - 1, end_column }, - { regtype = "c"; inclusive = false } - ) - - vim.defer_fn(function() - if vim.api.nvim_buf_is_valid(bufnr) then - vim.api.nvim_buf_clear_namespace(bufnr, select_ns, 0, -1) - end - end, timeout) -end - -return M diff --git a/lua/rest-nvim/utils.lua b/lua/rest-nvim/utils.lua new file mode 100644 index 00000000..e37d0274 --- /dev/null +++ b/lua/rest-nvim/utils.lua @@ -0,0 +1,27 @@ +---@mod rest-nvim.utils rest.nvim utilities +--- +---@brief [[ +--- +--- rest.nvim utility functions +--- +---@brief ]] + +local utils = {} + +-- NOTE: vim.loop has been renamed to vim.uv in Neovim >= 0.10 and will be removed later +local uv = vim.uv or vim.loop + +---Check if a file exists in the given `path` +---@param path string file path +---@return boolean +function utils.file_exists(path) + local fd = uv.fs_open(path, "r", 438) + if fd then + uv.fs_close(fd) + return true + end + + return false +end + +return utils diff --git a/lua/rest-nvim/utils/init.lua b/lua/rest-nvim/utils/init.lua deleted file mode 100644 index 3867c20e..00000000 --- a/lua/rest-nvim/utils/init.lua +++ /dev/null @@ -1,801 +0,0 @@ -local config = require("rest-nvim.config") - -local random = math.random -math.randomseed(os.time()) - -local M = {} -local contexts = {} - -M.binary_content_types = { - "octet-stream", -} - -M.is_binary_content_type = function(content_type) - return vim.tbl_contains(M.binary_content_types, content_type) -end - --- move_cursor moves the cursor to the desired position in the provided buffer --- @param bufnr Buffer number, a.k.a id --- @param line the desired line --- @param column the desired column, defaults to 1 -M.move_cursor = function(bufnr, line, column) - column = column or 1 - vim.api.nvim_buf_call(bufnr, function() - vim.fn.cursor(line, column) - end) -end - -M.set_env = function(key, value) - local variables = M.get_env_variables() - variables[key] = value - M.write_env_file(variables) -end - --- set_context sets a context variable for the current file --- @param key The key to set --- @param value The value to set -M.set_context = function(key, value) - local env_file = "/" .. (config.get("env_file") or ".env") - local context = contexts[env_file] or {} - context[key] = value - contexts[env_file] = context -end - -M.write_env_file = function(variables) - local env_file = "/" .. (config.get("env_file") or ".env") - - -- Directories to search for env files - local env_file_paths = { - -- current working directory - vim.fn.getcwd() .. env_file, - -- directory of the currently opened file - vim.fn.expand("%:p:h") .. env_file, - } - - -- If there's an env file in the current working dir - for _, env_file_path in ipairs(env_file_paths) do - if M.file_exists(env_file_path) then - local file = io.open(env_file_path, "w+") - if file ~= nil then - if string.match(env_file_path, "(.-)%.json$") then - file:write(vim.fn.json_encode(variables)) - else - for key, value in pairs(variables) do - file:write(key .. "=" .. value .. "\n") - end - end - file:close() - end - end - end -end - -M.uuid = function() - local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" - return string.gsub(template, "[xy]", function(c) - local v = (c == "x") and random(0, 0xf) or random(8, 0xb) - return string.format("%x", v) - end) -end - --- file_exists checks if the provided file exists and returns a boolean --- @param file File to check -M.file_exists = function(file) - return vim.fn.filereadable(file) == 1 -end - --- read_file Reads all lines from a file and returns the content as a table --- returns empty table if file does not exist -M.read_file = function(file) - if not M.file_exists(file) then - return {} - end - local lines = {} - for line in io.lines(file) do - lines[#lines + 1] = line - end - return lines -end - --- reads the variables contained in the current file -M.get_file_variables = function() - local variables = {} - - -- If there is a line at the beginning with @ first - if vim.fn.search("^@", "cn") > 0 then - -- Read all lines of the file - local lines = vim.api.nvim_buf_get_lines(0, 0, -1, true) - - -- For each line - for _, line in pairs(lines) do - -- Get the name and value form lines that starts with @ - local name, val = line:match("^@([%w!@#$%^&*-_+?~]+)%s*=%s*([^=]+)") - if name then - -- Add to variables - variables[name] = val - end - end - end - return variables -end - --- Gets the variables from the currently selected env_file -M.get_env_variables = function() - local variables = {} - local env_file = "/" .. (config.get("env_file") or ".env") - - -- Directories to search for env files - local env_file_paths = { - -- current working directory - vim.fn.getcwd() .. env_file, - -- directory of the currently opened file - vim.fn.expand("%:p:h") .. env_file, - } - - -- If there's an env file in the current working dir - for _, env_file_path in ipairs(env_file_paths) do - if M.file_exists(env_file_path) then - if string.match(env_file_path, "(.-)%.json$") then - local f = io.open(env_file_path, "r") - if f ~= nil then - local json_vars = f:read("*all") - variables = vim.fn.json_decode(json_vars) - f:close() - end - else - for line in io.lines(env_file_path) do - local vars = M.split(line, "%s*=%s*", 1) - variables[vars[1]] = vars[2] - end - end - end - end - return variables -end - -M.get_context_variables = function() - local env_file = "/" .. (config.get("env_file") or ".env") - return contexts[env_file] or {} -end - --- get_variables Reads the environment variables found in the env_file option --- (default: .env) specified in configuration or from the files being read --- with variables beginning with @ and returns a table with the variables -M.get_variables = function() - local variables = {} - local file_variables = M.get_file_variables() - local env_variables = M.get_env_variables() - - for k, v in pairs(file_variables) do - variables[k] = v - end - - for k, v in pairs(env_variables) do - variables[k] = v - end - - -- For each variable name - for name, _ in pairs(variables) do - -- For each pair of variables - for oname, ovalue in pairs(variables) do - -- If a variable contains another variable - if variables[name]:match(oname) then - -- Add that into the variable - -- I.E if @url={{path}}:{{port}}/{{source}} - -- Substitute in path, port and source - variables[name] = variables[name]:gsub("{{" .. oname .. "}}", ovalue) - end - end - end - - return variables -end - -M.read_dynamic_variables = function() - local from_config = config.get("custom_dynamic_variables") or {} - local dynamic_variables = { - ["$uuid"] = M.uuid, - ["$timestamp"] = os.time, - ["$randomInt"] = function() - return math.random(0, 1000) - end, - } - for k, v in pairs(from_config) do - dynamic_variables[k] = v - end - return dynamic_variables -end - -M.get_node_value = function(node, bufnr) - local start_row, start_col, _, end_col = node:range() - local line = vim.api.nvim_buf_get_lines(bufnr, start_row, start_row + 1, false)[1] - return line and string.sub(line, start_col + 1, end_col):gsub("^[\"'](.*)[\"']$", "%1") or nil -end - -M.read_document_variables = function() - local variables = {} - local bufnr = vim.api.nvim_get_current_buf() - local parser = vim.treesitter.get_parser(bufnr) - if not parser then - return variables - end - - local first_tree = parser:trees()[1] - if not first_tree then - return variables - end - - local root = first_tree:root() - if not root then - return variables - end - - for node in root:iter_children() do - local type = node:type() - if type == "header" then - local name = node:named_child(0) - local value = node:named_child(1) - variables[M.get_node_value(name, bufnr)] = M.get_node_value(value, bufnr) - elseif type ~= "comment" then - break - end - end - return variables -end - -M.read_variables = function() - local first = M.get_variables() - local second = M.get_context_variables() - local third = M.read_dynamic_variables() - local fourth = M.read_document_variables() - - return vim.tbl_extend("force", first, second, third, fourth) -end - --- replace_vars replaces the env variables fields in the provided string --- with the env variable value --- @param str Where replace the placers for the env variables -M.replace_vars = function(str, vars) - if vars == nil then - vars = M.read_variables() - end - -- remove $dotenv tags, which are used by the vscode rest client for cross compatibility - str = str:gsub("%$dotenv ", ""):gsub("%$DOTENV ", "") - - for var in string.gmatch(str, "{{[^}]+}}") do - var = var:gsub("{", ""):gsub("}", "") - -- If the env variable wasn't found in the `.env` file or in the dynamic variables then search it - -- in the OS environment variables - if M.has_key(vars, var) then - str = type(vars[var]) == "function" and str:gsub("{{" .. var .. "}}", vars[var]()) - or str:gsub("{{" .. var .. "}}", vars[var]) - else - if os.getenv(var) then - str = str:gsub("{{" .. var .. "}}", os.getenv(var)) - else - error(string.format("Environment variable '%s' was not found.", var)) - end - end - end - return str -end - --- has_key checks if the provided table contains the provided key using a regex --- @param tbl Table to iterate over --- @param key The key to be searched in the table -M.has_key = function(tbl, key) - for tbl_key, _ in pairs(tbl) do - if string.find(key, tbl_key) then - return true - end - end - return false -end - --- has_value checks if the provided table contains the provided string using a regex --- @param tbl Table to iterate over --- @param str String to search in the table -M.has_value = function(tbl, str) - for _, element in ipairs(tbl) do - if string.find(str, element) then - return true - end - end - return false -end - --- key returns the provided table's key that matches the given case-insensitive pattern. --- if not found, return the given key. --- @param tbl Table to iterate over --- @param key The key to be searched in the table -M.key = function(tbl, key) - for tbl_key, _ in pairs(tbl) do - if string.lower(tbl_key) == string.lower(key) then - return tbl_key - end - end - return key -end - --- tbl_to_str recursively converts the provided table into a json string --- @param tbl Table to convert into a String --- @param json If the string should use a key:value syntax -M.tbl_to_str = function(tbl, json) - if not json then - json = false - end - local result = "{" - for k, v in pairs(tbl) do - -- Check the key type (ignore any numerical keys - assume its an array) - if type(k) == "string" then - result = result .. '"' .. k .. '"' .. ":" - end - -- Check the value type - if type(v) == "table" then - result = result .. M.tbl_to_str(v) - elseif type(v) == "boolean" then - result = result .. tostring(v) - elseif type(v) == "number" then - result = result .. v - else - result = result .. '"' .. v .. '"' - end - if json then - result = result .. ":" - else - result = result .. "," - end - end - -- Remove leading commas from the result - if result ~= "" then - result = result:sub(1, result:len() - 1) - end - return result .. "}" -end - --- Just a split function because Lua does not have this, nothing more --- @param str String to split --- @param sep Separator --- @param max_splits Number of times to split the string (optional) -M.split = function(str, sep, max_splits) - if sep == nil then - sep = "%s" - end - max_splits = max_splits or -1 - - local str_tbl = {} - local nField, nStart = 1, 1 - local nFirst, nLast = str:find(sep, nStart) - while nFirst and max_splits ~= 0 do - str_tbl[nField] = str:sub(nStart, nFirst - 1) - nField = nField + 1 - nStart = nLast + 1 - nFirst, nLast = str:find(sep, nStart) - max_splits = max_splits - 1 - end - str_tbl[nField] = str:sub(nStart) - - return str_tbl -end - --- iter_lines returns an iterator --- @param str String to iterate over -M.iter_lines = function(str) - -- If the string does not have a newline at the end then add it manually - if str:sub(-1) ~= "\n" then - str = str .. "\n" - end - - return str:gmatch("(.-)\n") -end - --- char_to_hex returns the provided character as its hex value, e.g., "[" is --- converted to "%5B" --- @param char The character to convert -M.char_to_hex = function(char) - return string.format("%%%02X", string.byte(char)) -end - --- encode_url encodes the given URL --- @param url The URL to encode -M.encode_url = function(url) - if url == nil then - error("You must need to provide an URL to encode") - end - - url = url:gsub("\n", "\r\n") - -- Encode characters but exclude `.`, `_`, `-`, `:`, `/`, `?`, `&`, `=`, `~`, `@` - url = string.gsub(url, "([^%w _ %- . : / ? & = ~ @])", M.char_to_hex) - url = url:gsub(" ", "+") - return url -end - --- contains_comments checks if the given string contains comments characters --- @param str The string that should be checked --- @return number -M.contains_comments = function(str) - return str:find("^#") or str:find("^%s+#") -end - ---- Filter a table and return filtered copy ---- ---- @param tbl table The table to filter ---- @param filter function The filtering function, parameters are value, key and table ---- @param preserve_keys boolean? Should the copied table preserve keys or not, default true ---- ---- @return List|table -M.filter = function(tbl, filter, preserve_keys) - local out = {} - - preserve_keys = preserve_keys and true - - for key, val in ipairs(tbl) do - if filter(val, key, tbl) then - if preserve_keys then - out[key] = val - else - table.insert(out, val) - end - end - end - - return out -end - ---- Make a copy of the table applying the transformation function to each element. ---- Does not preserve the keys of the original table. ---- ---- @param tbl table The table to filter ---- @param transform function The transformation function, parameters are value, key and table ---- ---- @return List -M.map = function(tbl, transform) - local out = {} - - for key, val in ipairs(tbl) do - table.insert(out, transform(val, key, tbl)) - end - - return out -end - ---- Wrapper around nvim_buf_set_lines ---- ---- @param buffer integer The target buffer ---- @param block List The list of lines to write ---- @param newline boolean? Add a newline to the end, default false ---- ---- @return nil -M.write_block = function(buffer, block, newline) - local content = vim.api.nvim_buf_get_lines(buffer, 0, -1, false) - local first_line = false - - if #content == 1 and content[1] == "" then - first_line = true - end - - vim.api.nvim_buf_set_lines(buffer, first_line and 0 or -1, -1, false, block) - - if newline then - vim.api.nvim_buf_set_lines(buffer, -1, -1, false, { "" }) - end -end - ---- Split table on the elements where the function returns true ---- ---- @param tbl List ---- @param index function ---- @param inclusive boolean? If true the split value is in the first table, default false ---- ---- @return List[] -M.split_list = function(tbl, index, inclusive) - local out = { {} } - - for key, val in ipairs(tbl) do - if index(val, key, tbl) then - table.insert(out, {}) - - if inclusive then - table.insert(out[#out - 1], val) - else - table.insert(out[#out], val) - end - else - table.insert(out[#out], val) - end - end - - return out -end - ---- Default transformers for statistics -local transform = { - time = function(time) - time = tonumber(time) - - if time >= 60 then - time = string.format("%.2f", time / 60) - - return time .. " min" - end - - local units = { "s", "ms", "µs", "ns" } - local unit = 1 - - while time < 1 and unit <= #units do - time = time * 1000 - unit = unit + 1 - end - - time = string.format("%.2f", time) - - return time .. " " .. units[unit] - end, - - size = function(bytes) - bytes = tonumber(bytes) - - local units = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" } - local unit = 1 - - while bytes >= 1024 and unit <= #units do - bytes = bytes / 1024 - unit = unit + 1 - end - - bytes = string.format("%.2f", bytes) - - return bytes .. " " .. units[unit] - end, -} - ---- Parse statistics line to a table with key, value pairs ---- ---- @param statistics_line string The statistics line from body ---- ---- @return string[] statistics -local get_parsed_statistics = function(statistics_line) - local out = {} - - for _, statistics_pair in ipairs(M.split(statistics_line, "&")) do - local value = M.split(statistics_pair, "=", 1) - - if #value == 1 then - table.insert(out, value[1]) - else - out[value[1]] = value[2] - end - end - - return out -end - ---- Parse and transform statistics line to a table of strings to be output. ---- Returns the body without statistics line and a table of statistics lines. ---- ---- @param body string Response body ---- ---- @return string body, string[] statistics -M.parse_statistics = function(body) - local _, _, statistics = string.find(body, "[%c%s]+([^%c]*)$") - local config_statistics = config.get("result").show_statistics - - body = string.gsub(body, "[%c%s]+([^%c]*)$", "") - local out = {} - - statistics = get_parsed_statistics(statistics) - - for _, tbl in ipairs(config_statistics) do - if type(tbl) == "string" then - tbl = { tbl } - end - - local value = statistics[tbl[1]] - - if tbl.type then - if type(tbl.type) == "string" then - value = transform[tbl.type](value) - end - - if type(tbl.type) == "function" then - value = tbl.type(value) - end - else - for key, fun in pairs(transform) do - if string.match(tbl[1], "^" .. key) then - value = fun(value) - end - end - end - - table.insert(out, (tbl.title or (tbl[1] .. " ")) .. value) - end - - return body, out -end - --- http_status returns the status code and the meaning, e.g. 200 OK --- see https://httpstatuses.com/ for reference --- @param code The request status code -M.http_status = function(code) - -- NOTE: this table does not cover all the statuses _yet_ - local status_meaning = { - -- 1xx codes (Informational) - [100] = "Continue", - [101] = "Switching Protocols", - [102] = "Processing", - - -- 2xx codes (Success) - [200] = "OK", - [201] = "Created", - [202] = "Accepted", - [203] = "Non-authoritative Information", - [204] = "No Content", - [205] = "Reset Content", - [206] = "Partial Content", - [207] = "Multi-Status", - [208] = "Already Reported", - [226] = "IM Used", - - -- 3xx codes (Redirection) - [300] = "Multiple Choices", - [301] = "Moved Permanently", - [302] = "Found", - [303] = "See Other", - [304] = "Not Modified", - [305] = "Use Proxy", - [307] = "Temporary Redirect", - [308] = "Permanent Redirect", - - -- 4xx codes (Client Error) - [400] = "Bad Request", - [401] = "Unauthorized", - [403] = "Forbidden", - [404] = "Not Found", - [405] = "Method Not Allowed", - [406] = "Not Acceptable", - [407] = "Proxy Authentication Required", - [408] = "Request Timeout", - [409] = "Conflict", - [410] = "Gone", - [411] = "Length Required", - [412] = "Precondition Failed", - [413] = "Payload Too Large", - [414] = "Request-URI Too Long", - [415] = "Unsupported Media Type", - [416] = "Requested Range Not Satisfiable", - [417] = "Expectation Failed", - [418] = "I'm a teapot", - [421] = "Misdirected Request", - [422] = "Unprocessable Entity", - [423] = "Locked", - [424] = "Failed Dependency", - [426] = "Upgrade Required", - [428] = "Precondition Required", - [429] = "Too Many Requests", - [431] = "Request Header Fields Too Large", - [444] = "Connection Closed Without Response", - [451] = "Unavailable For Legal Reasons", - [499] = "Client Closed Request", - - -- 5xx codes (Server Error) - [500] = "Internal Server Error", - [501] = "Not Implemented", - [502] = "Bad Gateway", - [503] = "Service Unavailable", - [504] = "Gateway Timeout", - [505] = "HTTP Version Not Supported", - [506] = "Variant Also Negotiates", - [507] = "Insufficient Storage", - [508] = "Loop Detected", - [510] = "Not Extended", - [511] = "Network Authentication Required", - [599] = "Network Connect Timeout Error", - } - - -- If the code is covered in the status_meaning table - if status_meaning[code] ~= nil then - return tostring(code) .. " " .. status_meaning[code] - end - - return tostring(code) .. " Unknown Status Meaning" -end - --- curl_error returns the status code and the meaning of an curl error --- see man curl for reference --- @param code The exit code of curl -M.curl_error = function(code) - local curl_error_dictionary = { - [1] = "Unsupported protocol. This build of curl has no support for this protocol.", - [2] = "Failed to initialize.", - [3] = "URL malformed. The syntax was not correct.", - [4] = "A feature or option that was needed to perform the desired request was not enabled or was explicitly disabled at build-time." - .. "To make curl able to do this, you probably need another build of libcurl!", - [5] = "Couldn't resolve proxy. The given proxy host could not be resolved.", - [6] = "Couldn't resolve host. The given remote host was not resolved.", - [7] = "Failed to connect to host.", - [8] = "Weird server reply. The server sent data curl couldn't parse.", - [9] = "FTP access denied. The server denied login or denied access to the particular resource or directory you wanted to reach. Most often you tried to change to a directory that doesn't exist on the server.", - [10] = "FTP accept failed. While waiting for the server to connect back when an active FTP session is used, an error code was sent over the control connection or similar.", - [11] = "FTP weird PASS reply. Curl couldn't parse the reply sent to the PASS request.", - [12] = "During an active FTP session while waiting for the server to connect back to curl, the timeout expired.", - [13] = "FTP weird PASV reply, Curl couldn't parse the reply sent to the PASV request.", - [14] = "FTP weird 227 format. Curl couldn't parse the 227-line the server sent.", - [15] = "FTP can't get host. Couldn't resolve the host IP we got in the 227-line.", - [16] = "HTTP/2 error. A problem was detected in the HTTP2 framing layer. This is somewhat generic and can be one out of several problems, see the error message for details.", - [17] = "FTP couldn't set binary. Couldn't change transfer method to binary.", - [18] = "Partial file. Only a part of the file was transferred.", - [19] = "FTP couldn't download/access the given file, the RETR (or similar) command failed.", - [21] = "FTP quote error. A quote command returned error from the server.", - [22] = "HTTP page not retrieved. The requested url was not found or returned another error with the HTTP error code being 400 or above. This return code only appears if -f, --fail is used.", - [23] = "Write error. Curl couldn't write data to a local filesystem or similar.", - [25] = "FTP couldn't STOR file. The server denied the STOR operation, used for FTP uploading.", - [26] = "Read error. Various reading problems.", - [27] = "Out of memory. A memory allocation request failed.", - [28] = "Operation timeout. The specified time-out period was reached according to the conditions.", - [30] = "FTP PORT failed. The PORT command failed. Not all FTP servers support the PORT command, try doing a transfer using PASV instead!", - [31] = "FTP couldn't use REST. The REST command failed. This command is used for resumed FTP transfers.", - [33] = 'HTTP range error. The range "command" didn\'t work.', - [34] = "HTTP post error. Internal post-request generation error.", - [35] = "SSL connect error. The SSL handshaking failed.", - [36] = "Bad download resume. Couldn't continue an earlier aborted download.", - [37] = "FILE couldn't read file. Failed to open the file. Permissions?", - [38] = "LDAP cannot bind. LDAP bind operation failed.", - [39] = "LDAP search failed.", - [41] = "Function not found. A required LDAP function was not found.", - [42] = "Aborted by callback. An application told curl to abort the operation.", - [43] = "Internal error. A function was called with a bad parameter.", - [45] = "Interface error. A specified outgoing interface could not be used.", - [47] = "Too many redirects. When following redirects, curl hit the maximum amount.", - [48] = "Unknown option specified to libcurl. This indicates that you passed a weird option to curl that was passed on to libcurl and rejected. Read up in the manual!", - [49] = "Malformed telnet option.", - [51] = "The peer's SSL certificate or SSH MD5 fingerprint was not OK.", - [52] = "The server didn't reply anything, which here is considered an error.", - [53] = "SSL crypto engine not found.", - [54] = "Cannot set SSL crypto engine as default.", - [55] = "Failed sending network data.", - [56] = "Failure in receiving network data.", - [58] = "Problem with the local certificate.", - [59] = "Couldn't use specified SSL cipher.", - [60] = "Peer certificate cannot be authenticated with known CA certificates.", - [61] = "Unrecognized transfer encoding.", - [62] = "Invalid LDAP URL.", - [63] = "Maximum file size exceeded.", - [64] = "Requested FTP SSL level failed.", - [65] = "Sending the data requires a rewind that failed.", - [66] = "Failed to initialize SSL Engine.", - [67] = "The user name, password, or similar was not accepted and curl failed to log in.", - [68] = "File not found on TFTP server.", - [69] = "Permission problem on TFTP server.", - [70] = "Out of disk space on TFTP server.", - [71] = "Illegal TFTP operation.", - [72] = "Unknown TFTP transfer ID.", - [73] = "File already exists (TFTP).", - [74] = "No such user (TFTP).", - [75] = "Character conversion failed.", - [76] = "Character conversion functions required.", - [77] = "Problem with reading the SSL CA cert (path? access rights?).", - [78] = "The resource referenced in the URL does not exist.", - [79] = "An unspecified error occurred during the SSH session.", - [80] = "Failed to shut down the SSL connection.", - [82] = "Could not load CRL file, missing or wrong format (added in 7.19.0).", - [83] = "Issuer check failed (added in 7.19.0).", - [84] = "The FTP PRET command failed", - [85] = "RTSP: mismatch of CSeq numbers", - [86] = "RTSP: mismatch of Session Identifiers", - [87] = "unable to parse FTP file list", - [88] = "FTP chunk callback reported error", - [89] = "No connection available, the session will be queued", - [90] = "SSL public key does not matched pinned public key", - [91] = "Invalid SSL certificate status.", - [92] = "Stream error in HTTP/2 framing layer.", - } - - if curl_error_dictionary[code] ~= nil then - return "curl error " .. tostring(code) .. ": " .. curl_error_dictionary[code] - end - - return "curl error " .. tostring(code) .. ": unknown curl error" -end - -return M From 6e29e65b026634f0702b543b2662d27e02046148 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 11:35:51 -0400 Subject: [PATCH 002/159] feat(queries): update bundled queries to use latest http parser rewrite changes from the `next` branch --- after/queries/http/highlights.scm | 25 +++++++++++++++++-------- after/queries/http/injections.scm | 16 +++++++++++++--- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/after/queries/http/highlights.scm b/after/queries/http/highlights.scm index 3b1ac926..5451e707 100644 --- a/after/queries/http/highlights.scm +++ b/after/queries/http/highlights.scm @@ -17,7 +17,17 @@ ; Variables -(identifier) @variable +(variable_declaration + name: (identifier) @variable) + +(variable_declaration + value: (number) @number) + +(variable_declaration + value: (boolean) @boolean) + +(variable_declaration + value: (string) @string) ; Fields @@ -39,20 +49,15 @@ "?" "&" "@" + "<" ] @operator ; Literals -(string) @string - (target_url) @text.uri (number) @number -; (boolean) @boolean - -(null) @constant.builtin - ; Punctuation [ "{{" "}}" ] @punctuation.bracket @@ -61,6 +66,11 @@ ":" ] @punctuation.delimiter +; external JSON body + +(external_body + file_path: (path) @text.uri) + ; Comments (comment) @comment @spell @@ -68,4 +78,3 @@ ; Errors (ERROR) @error - diff --git a/after/queries/http/injections.scm b/after/queries/http/injections.scm index 16bc175c..bc9d3c02 100644 --- a/after/queries/http/injections.scm +++ b/after/queries/http/injections.scm @@ -1,7 +1,17 @@ ; (comment) @comment -(json_body) @json +; Body -; (xml_body) @xml +((json_body) @injection.content + (#set! injection.language "json")) -; (graphql_body) @graphql Not used as of now.. +((xml_body) @injection.content + (#set! injection.language "xml")) + +((graphql_body) @injection.content + (#set! injection.language "graphql")) + +; Lua scripting + +((script_variable) @injection.content + (#set! injection.language "lua")) From 686317cdded68e335e04774a432850707c83034b Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 11:39:28 -0400 Subject: [PATCH 003/159] ref!: replace all old Vimscript files with Lua alternatives, bump required Neovim version to `>= 0.9` --- ftdetect/http.lua | 5 +++++ ftdetect/http.vim | 1 - ftplugin/http.lua | 1 + ftplugin/http.vim | 1 - plugin/rest-nvim.lua | 16 ++++++++++++++++ plugin/rest-nvim.vim | 27 --------------------------- 6 files changed, 22 insertions(+), 29 deletions(-) create mode 100644 ftdetect/http.lua delete mode 100644 ftdetect/http.vim create mode 100644 ftplugin/http.lua delete mode 100644 ftplugin/http.vim create mode 100644 plugin/rest-nvim.lua delete mode 100644 plugin/rest-nvim.vim diff --git a/ftdetect/http.lua b/ftdetect/http.lua new file mode 100644 index 00000000..bb600321 --- /dev/null +++ b/ftdetect/http.lua @@ -0,0 +1,5 @@ +vim.filetype.add({ + extension = { + http = "http", + } +}) diff --git a/ftdetect/http.vim b/ftdetect/http.vim deleted file mode 100644 index 02b25691..00000000 --- a/ftdetect/http.vim +++ /dev/null @@ -1 +0,0 @@ -au BufRead,BufNewFile *.http set ft=http diff --git a/ftplugin/http.lua b/ftplugin/http.lua new file mode 100644 index 00000000..ea55aa57 --- /dev/null +++ b/ftplugin/http.lua @@ -0,0 +1 @@ +vim.bo.commentstring = "# %s" diff --git a/ftplugin/http.vim b/ftplugin/http.vim deleted file mode 100644 index 9c2e80a0..00000000 --- a/ftplugin/http.vim +++ /dev/null @@ -1 +0,0 @@ -set commentstring=#\ %s diff --git a/plugin/rest-nvim.lua b/plugin/rest-nvim.lua new file mode 100644 index 00000000..ac507e45 --- /dev/null +++ b/plugin/rest-nvim.lua @@ -0,0 +1,16 @@ +if vim.fn.has("nvim-0.9.0") ~= 1 then + vim.notify_once("[rest.nvim] rest.nvim requires at least Neovim >= 0.9 in order to work") + return +end + +if vim.g.loaded_rest_nvim then + return +end + +-- NOTE: legacy code, needs an alternative later +-- +-- vim.api.nvim_create_user_command('RestLog', function() +-- vim.cmd(string.format('tabnew %s', vim.fn.stdpath('cache')..'/rest.nvim.log')) +-- end, { desc = 'Opens the rest.nvim log.', }) + +vim.g.loaded_rest_nvim = true diff --git a/plugin/rest-nvim.vim b/plugin/rest-nvim.vim deleted file mode 100644 index 21a2c32c..00000000 --- a/plugin/rest-nvim.vim +++ /dev/null @@ -1,27 +0,0 @@ -if !has('nvim-0.5') - echoerr 'Rest.nvim requires at least nvim-0.5. Please update or uninstall' - finish -endif - -if exists('g:loaded_rest_nvim') | finish | endif - -nnoremap RestNvim :lua require('rest-nvim').run() -nnoremap RestNvimPreview :lua require('rest-nvim').run(true) -nnoremap RestNvimLast :lua require('rest-nvim').last() -" nnoremap RestNvimSelectEnv :lua require('rest-nvim').last() - -command! -nargs=? -complete=file RestSelectEnv :lua require('rest-nvim').select_env() - -lua << EOF - vim.api.nvim_create_user_command('RestLog', function() - vim.cmd(string.format('tabnew %s', vim.fn.stdpath('cache')..'/rest.nvim.log')) -end, { desc = 'Opens the rest.nvim log.', }) -EOF - -let s:save_cpo = &cpo -set cpo&vim - -let &cpo = s:save_cpo -unlet s:save_cpo - -let g:loaded_rest_nvim = 1 From 577a3845bb83b160bfcb82582ae80693f6b7fdc6 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 11:41:21 -0400 Subject: [PATCH 004/159] ref(rockspec)!: downgrade versioning from `scm-3` to `scm-2`, as `scm-2` does not exist in `luarocks.org` yet. Also update description, dependencies and some other small adjustments to the rockspec --- rest.nvim-scm-2.rockspec | 43 ++++++++++++++++++++++++++++++++++++++++ rest.nvim-scm-3.rockspec | 38 ----------------------------------- 2 files changed, 43 insertions(+), 38 deletions(-) create mode 100644 rest.nvim-scm-2.rockspec delete mode 100644 rest.nvim-scm-3.rockspec diff --git a/rest.nvim-scm-2.rockspec b/rest.nvim-scm-2.rockspec new file mode 100644 index 00000000..bf9f0242 --- /dev/null +++ b/rest.nvim-scm-2.rockspec @@ -0,0 +1,43 @@ +local MAJOR, REV = "scm", "-2" +rockspec_format = "3.0" +package = "rest.nvim" +version = MAJOR .. REV + +description = { + summary = "A fast and asynchronous Neovim HTTP client written in Lua", + labels = { "neovim", "rest" }, + detailed = [[ + rest.nvim makes use of Lua cURL bindings to make HTTP requests so you don't have to leave Neovim to test your back-end codebase! + ]], + homepage = "https://github.com/rest-nvim/rest.nvim", + license = "GPLv3", +} + +dependencies = { + "lua >= 5.1, < 5.4", + "nvim-nio", + "Lua-cURL" +} + +source = { + url = "http://github.com/rest-nvim/rest.nvim/archive/" .. MAJOR .. ".zip", + dir = "rest.nvim-" .. MAJOR, +} + +if MAJOR == "scm" then + source = { + url = "git://github.com/rest-nvim/rest.nvim", + } +end + +build = { + type = "builtin", + copy_directories = { + "doc", + "after", + "plugin", + "syntax", + "ftdetect", + "ftplugin", + } +} diff --git a/rest.nvim-scm-3.rockspec b/rest.nvim-scm-3.rockspec deleted file mode 100644 index 9f432d00..00000000 --- a/rest.nvim-scm-3.rockspec +++ /dev/null @@ -1,38 +0,0 @@ -local MAJOR, REV = "scm", "-3" -rockspec_format = "3.0" -package = "rest.nvim" -version = MAJOR .. REV - -description = { - summary = "A fast Neovim http client written in Lua", - labels = { "neovim", "rest"}, - detailed = [[ - rest.nvim makes use of a curl wrapper implemented in pure Lua in plenary.nvim so, in other words, rest.nvim is a curl wrapper so you don't have to leave Neovim! - ]], - homepage = "https://github.com/rest-nvim/rest.nvim", - license = "MIT", -} - -dependencies = { - "lua >= 5.1, < 5.4", - "plenary.nvim", -} - -source = { - url = "http://github.com/rest-nvim/rest.nvim/archive/" .. MAJOR .. ".zip", - dir = "rest.nvim-" .. MAJOR, -} - -if MAJOR == "scm" then - source = { - url = "git://github.com/rest-nvim/rest.nvim", - } -end - -build = { - type = "builtin", - copy_directories = { - 'doc', - 'plugin' - } -} From a82cf558578a36135ba2f8a54f1258ded3c0a8c9 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 11:42:27 -0400 Subject: [PATCH 005/159] docs(LICENSE)!: re-license project under `GPLv3`. Goodbye `MIT`, nobody will ever miss you! --- LICENSE | 693 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 674 insertions(+), 19 deletions(-) diff --git a/LICENSE b/LICENSE index 7f2b3607..f288702d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,19 +1,674 @@ -Copyright (c) 2021 NTBBloodbath - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 58b6fef911e21cc8281cca894e98c413c6a6eda7 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 11:43:23 -0400 Subject: [PATCH 006/159] tests: update some tests to match the new tree-sitter HTTP parser syntax changes --- tests/post_create_user.http | 2 +- tests/script_vars/script_vars.http | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/post_create_user.http b/tests/post_create_user.http index 10a69a90..555cb518 100644 --- a/tests/post_create_user.http +++ b/tests/post_create_user.http @@ -7,5 +7,5 @@ Content-Type: application/json "array": ["a", "b", "c"], "object_ugly_closing": { "some_key": "some_value" - } + } } diff --git a/tests/script_vars/script_vars.http b/tests/script_vars/script_vars.http index 492b65de..82e970af 100644 --- a/tests/script_vars/script_vars.http +++ b/tests/script_vars/script_vars.http @@ -2,14 +2,14 @@ # Then the second request can be run GET https://jsonplaceholder.typicode.com/posts/3 -{% +--{% local body = context.json_decode(context.result.body) context.set_env("userId", body.userId) context.set_env("postId", body.id) -%} +--%} ### GET https://jsonplaceholder.typicode.com/posts/{{postId}} From 96ac2526061ab7dbdb95995cc2ed9f5a47e386e1 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 11:52:28 -0400 Subject: [PATCH 007/159] feat(queries): update `highlights.scm` --- after/queries/http/highlights.scm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/after/queries/http/highlights.scm b/after/queries/http/highlights.scm index 5451e707..7db1e559 100644 --- a/after/queries/http/highlights.scm +++ b/after/queries/http/highlights.scm @@ -56,8 +56,12 @@ (target_url) @text.uri +(string) @string + (number) @number +(boolean) @boolean + ; Punctuation [ "{{" "}}" ] @punctuation.bracket From e27bb347b358a2d48cc1a13ee07155eda6b826d7 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 11:56:54 -0400 Subject: [PATCH 008/159] ref(config)!: rename `runner` to `client` as it is more idiomatic --- lua/rest-nvim/config/check.lua | 2 +- lua/rest-nvim/config/init.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/rest-nvim/config/check.lua b/lua/rest-nvim/config/check.lua index 418c0c37..8dc87493 100644 --- a/lua/rest-nvim/config/check.lua +++ b/lua/rest-nvim/config/check.lua @@ -23,7 +23,7 @@ end ---@return string|nil error_message function check.validate(cfg) local ok, err = validate({ - runner = { cfg.runner, "string" }, + client = { cfg.client, "string" }, env_file = { cfg.env_file, "string" }, encode_url = { cfg.encode_url, "boolean" }, yank_dry_run = { cfg.yank_dry_run, "boolean" }, diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index b1da6eaf..6075e681 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -44,7 +44,7 @@ local config = {} ---@field timeout number Duration time of the request highlighting in milliseconds ---@class RestConfig ----@field runner string The HTTP client to be used when running requests, default is `curl` +---@field client string The HTTP client to be used when running requests, default is `curl` ---@field env_file string Environment variables file to be used for the request variables in the document ---@field encode_url boolean Encode URL before making request ---@field yank_dry_run boolean Whether to copy the request preview (cURL command) to the clipboard @@ -58,7 +58,7 @@ local config = {} ---rest.nvim default configuration ---@type RestConfig local default_config = { - runner = "curl", + client = "curl", env_file = ".env", encode_url = true, yank_dry_run = true, From 69bd4ee7a6af07044a9b23c770d8c757133fe8f5 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 13:30:31 -0400 Subject: [PATCH 009/159] feat: add `logger` module --- lua/rest-nvim/logger.lua | 162 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 lua/rest-nvim/logger.lua diff --git a/lua/rest-nvim/logger.lua b/lua/rest-nvim/logger.lua new file mode 100644 index 00000000..4cd8422e --- /dev/null +++ b/lua/rest-nvim/logger.lua @@ -0,0 +1,162 @@ +---@mod rest-nvim.logger rest.nvim logger +--- +---@brief [[ +--- +---Logging library for rest.nvim, slightly inspired by rmagatti/logger.nvim +---Intended for use by internal and third-party modules. +--- +--------------------------------------------------------------------------------- +--- +---Usage: +--- +---```lua +---local logger = require("rest-nvim.logger"):new({ level = "debug" }) +--- +---logger:set_log_level("info") +--- +---logger:info("This is an info log") +---``` +--- +---@brief ]] + +---@class logger +local logger = {} + +-- NOTE: vim.loop has been renamed to vim.uv in Neovim >= 0.10 and will be removed later +local uv = vim.uv or vim.loop + +---@see vim.log.levels +---@class LoggerLevels +local levels = { + trace = vim.log.levels.TRACE, + debug = vim.log.levels.DEBUG, + info = vim.log.levels.INFO, + warn = vim.log.levels.WARN, + error = vim.log.levels.ERROR, +} + +---@class LoggerConfig +---@field level_name string Logging level name. Default is `"info"` +---@field save_logs boolean Whether to save log messages into a `.log` file. Default is `true` +local default_config = { + level_name = "info", + save_logs = true, +} + +---Store the logger output in a file at `vim.fn.stdpath("log")` +---@see vim.fn.stdpath +---@param msg string Logger message to be saved +local function store_log(msg) + local date = os.date("%F %r") -- 2024-01-26 01:25:05 PM + local log_msg = date .. " | " .. msg + local log_path = vim.fs.joinpath(vim.fn.stdpath("log"), "rest.nvim.log") + + -- 644 sets read and write permissions for the owner, and it sets read-only + -- mode for the group and others + uv.fs_open(log_path, "a+", tonumber(644, 8), function(err, file) + if file and not err then + local file_pipe = uv.new_pipe(false) + ---@cast file_pipe uv_pipe_t + uv.pipe_open(file_pipe, file) + uv.write(file_pipe, log_msg) + uv.fs_close(file) + end + end) +end + +---Create a new logger instance +---@param opts LoggerConfig Logger configuration +---@return logger +function logger:new(opts) + local config = vim.tbl_deep_extend("force", default_config, opts or {}) + self.level = levels[config.level_name] + self.save_logs = config.save_logs + + self.__index = function(_, index) + if type(self[index]) == "function" then + return function(...) + -- Make any logger function call with "." access result in the syntactic sugar ":" access + self[index](self, ...) + end + else + return self[index] + end + end + setmetatable(config, self) + + return config +end + +---Set the log level for the logger +---@param level string New logging level +---@see vim.log.levels +function logger:set_log_level(level) + self.level = levels[level] +end + +---Log a trace message +---@param msg string Log message +function logger:trace(msg) + msg = "[rest.nvim] TRACE: " .. msg + if self.level == vim.log.levels.TRACE then + vim.notify(msg, levels.trace) + end + + if self.save_logs then + store_log(msg) + end +end + +---Log a debug message +---@param msg string Log message +function logger:debug(msg) + msg = "[rest.nvim] DEBUG: " .. msg + if self.level == vim.log.levels.DEBUG then + vim.notify(msg, levels.debug) + end + + if self.save_logs then + store_log(msg) + end +end + +---Log an info message +---@param msg string Log message +function logger:info(msg) + msg = "[rest.nvim] INFO: " .. msg + local valid_levels = { vim.log.levels.INFO, vim.log.levels.DEBUG } + if vim.tbl_contains(valid_levels, self.level) then + vim.notify(msg, levels.info) + end + + if self.save_logs then + store_log(msg) + end +end + +---Log a warning message +---@param msg string Log message +function logger:warn(msg) + msg = "[rest.nvim] WARN: " .. msg + local valid_levels = { vim.log.levels.INFO, vim.log.levels.DEBUG, vim.log.levels.WARN } + if vim.tbl_contains(valid_levels, self.level) then + vim.notify(msg, levels.warn) + end + + if self.save_logs then + store_log(msg) + end +end + +---Log an error message +---@param msg string Log message +function logger:error(msg) + msg = "[rest.nvim] ERROR: " .. msg + vim.notify(msg, levels.error) + + if self.save_logs then + store_log(msg) + end +end + +return logger From 35fdd33a46ffc010b4b766837a755dd65c9aded4 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 13:36:25 -0400 Subject: [PATCH 010/159] chore(logger): slightly improve documentation --- lua/rest-nvim/logger.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/rest-nvim/logger.lua b/lua/rest-nvim/logger.lua index 4cd8422e..3d1aebd5 100644 --- a/lua/rest-nvim/logger.lua +++ b/lua/rest-nvim/logger.lua @@ -15,6 +15,7 @@ ---logger:set_log_level("info") --- ---logger:info("This is an info log") +--- -- [rest.nvim] INFO: This is an info log ---``` --- ---@brief ]] From a5ef49d17ac025dc71fb086487a02ff9409c2db4 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 14:06:22 -0400 Subject: [PATCH 011/159] fix(logger): proper returning value for `logger:new()`, improve documentation --- lua/rest-nvim/logger.lua | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lua/rest-nvim/logger.lua b/lua/rest-nvim/logger.lua index 3d1aebd5..dfb3132d 100644 --- a/lua/rest-nvim/logger.lua +++ b/lua/rest-nvim/logger.lua @@ -5,6 +5,10 @@ ---Logging library for rest.nvim, slightly inspired by rmagatti/logger.nvim ---Intended for use by internal and third-party modules. --- +---Default logger instance is made during the `setup` and can be accessed +---by anyone through the `_G._rest_nvim.logger` configuration field +---that is set automatically. +--- --------------------------------------------------------------------------------- --- ---Usage: @@ -20,7 +24,7 @@ --- ---@brief ]] ----@class logger +---@class Logger local logger = {} -- NOTE: vim.loop has been renamed to vim.uv in Neovim >= 0.10 and will be removed later @@ -67,11 +71,12 @@ end ---Create a new logger instance ---@param opts LoggerConfig Logger configuration ----@return logger +---@return Logger function logger:new(opts) - local config = vim.tbl_deep_extend("force", default_config, opts or {}) - self.level = levels[config.level_name] - self.save_logs = config.save_logs + opts = opts or {} + local conf = vim.tbl_deep_extend("force", default_config, opts) + self.level = levels[conf.level_name] + self.save_logs = conf.save_logs self.__index = function(_, index) if type(self[index]) == "function" then @@ -83,9 +88,9 @@ function logger:new(opts) return self[index] end end - setmetatable(config, self) + setmetatable(opts, self) - return config + return self end ---Set the log level for the logger From 33b8083555c7216c9909d58f3b9d54f499a76df6 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 14:09:45 -0400 Subject: [PATCH 012/159] fix(logger): add a newline after each saved log --- lua/rest-nvim/logger.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/logger.lua b/lua/rest-nvim/logger.lua index dfb3132d..896f4678 100644 --- a/lua/rest-nvim/logger.lua +++ b/lua/rest-nvim/logger.lua @@ -53,7 +53,7 @@ local default_config = { ---@param msg string Logger message to be saved local function store_log(msg) local date = os.date("%F %r") -- 2024-01-26 01:25:05 PM - local log_msg = date .. " | " .. msg + local log_msg = date .. " | " .. msg .. "\n" local log_path = vim.fs.joinpath(vim.fn.stdpath("log"), "rest.nvim.log") -- 644 sets read and write permissions for the owner, and it sets read-only From e1f2eac4d5d6f5d9dea6818b76d14413aa644a49 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 14:11:04 -0400 Subject: [PATCH 013/159] feat(config): add `logs` configuration table, set up a default logger instance automatically on `rest.setup()` and start using it --- lua/rest-nvim/config/check.lua | 3 +++ lua/rest-nvim/config/init.lua | 28 +++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/lua/rest-nvim/config/check.lua b/lua/rest-nvim/config/check.lua index 8dc87493..c4257cf4 100644 --- a/lua/rest-nvim/config/check.lua +++ b/lua/rest-nvim/config/check.lua @@ -30,6 +30,9 @@ function check.validate(cfg) skip_ssl_verification = { cfg.skip_ssl_verification, "boolean" }, custom_dynamic_variables = { cfg.custom_dynamic_variables, "table" }, keybinds = { cfg.keybinds, "table" }, + -- RestConfigLogs + level = { cfg.logs.level, "string" }, + save = { cfg.logs.save, "boolean" }, -- RestConfigResult result = { cfg.result, "table" }, -- RestConfigResultSplit diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 6075e681..d80bd062 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -8,9 +8,15 @@ local config = {} +local logger = require("rest-nvim.logger") + ---@class RestConfigDebug ---@field unrecognized_configs string[] Unrecognized configuration options +---@class RestConfigLogs +---@field level string The logging level name, see `:h vim.log.levels`. Default is `"info"` +---@field save boolean Whether to save log messages into a `.log` file. Default is `true` + ---@class RestConfigResult ---@field split RestConfigResultSplit Result split window behavior ---@field behavior RestConfigResultBehavior Result buffer behavior @@ -44,16 +50,18 @@ local config = {} ---@field timeout number Duration time of the request highlighting in milliseconds ---@class RestConfig ----@field client string The HTTP client to be used when running requests, default is `curl` +---@field client string The HTTP client to be used when running requests, default is `"curl"` ---@field env_file string Environment variables file to be used for the request variables in the document ---@field encode_url boolean Encode URL before making request ---@field yank_dry_run boolean Whether to copy the request preview (cURL command) to the clipboard ---@field skip_ssl_verification boolean Skip SSL verification, useful for unknown certificates ---@field custom_dynamic_variables { [string]: fun(): string }[] Table of custom dynamic variables +---@field logs RestConfigLogs Logging system configuration ---@field result RestConfigResult Request results buffer behavior ---@field highlight RestConfigHighlight Request highlighting ---@field keybinds { [1]: string, [2]: string, [3]: string }[] Keybindings list ---@field debug_info? RestConfigDebug Configurations debug information, set automatically +---@field logger? Logger Logging system, set automatically ---rest.nvim default configuration ---@type RestConfig @@ -64,6 +72,10 @@ local default_config = { yank_dry_run = true, skip_ssl_verification = false, custom_dynamic_variables = {}, + logs = { + level = "info", + save = true, + }, result = { split = { horizontal = false, @@ -134,14 +146,20 @@ function config.set(user_configs) }, default_config, user_configs) local ok, err = check.validate(conf) + + -- We do not want to validate `logger` value so we are setting it after the validation + conf.logger = logger:new({ + level_name = conf.logs.level, + save_logs = conf.logs.save, + }) + if not ok then - vim.notify("Rest: " .. err, vim.log.levels.ERROR) + conf.logger:error(err) end if #conf.debug_info.unrecognized_configs > 0 then - vim.notify( - "Rest: Unrecognized configs found in setup: " .. vim.inspect(config.debug_info.unrecognized_configs), - vim.log.levels.WARN + conf.logger:warn( + "Unrecognized configs found in setup: " .. vim.inspect(config.debug_info.unrecognized_configs) ) end From 60a5ecc22ccd46242dad8ce694b57cb67ca172de Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 14:11:20 -0400 Subject: [PATCH 014/159] feat(rest.init): small docs adjustments --- lua/rest-nvim/init.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/rest-nvim/init.lua b/lua/rest-nvim/init.lua index 91313a31..b7922139 100644 --- a/lua/rest-nvim/init.lua +++ b/lua/rest-nvim/init.lua @@ -14,8 +14,10 @@ local autocmds = require("rest-nvim.autocmds") ---Set up rest.nvim ---@param user_configs RestConfig User configurations function rest.setup(user_configs) + -- Set up rest.nvim configurations _G._rest_nvim = config.set(user_configs or {}) + -- Set up rest.nvim autocommands and commands autocmds.setup() end From 37c425892ee9ff1617f1206c0bb434bf50f8d1df Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 26 Jan 2024 14:30:17 -0400 Subject: [PATCH 015/159] ref!: use the new `logger` module instead of plain `vim.notify` calls --- lua/rest-nvim/commands.lua | 23 ++++++++++++++++------- lua/rest-nvim/config/check.lua | 2 +- lua/rest-nvim/functions.lua | 29 +++++++++++++++++------------ 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/lua/rest-nvim/commands.lua b/lua/rest-nvim/commands.lua index 0a2e0d07..eaa719d3 100644 --- a/lua/rest-nvim/commands.lua +++ b/lua/rest-nvim/commands.lua @@ -62,7 +62,8 @@ local rest_command_tbl = { }, env = { impl = function(args) - vim.print(args) + local logger = _G._rest_nvim.logger + -- If there were no arguments for env then default to the `env("show", nil)` function behavior if #args < 1 then functions.env(nil, nil) @@ -70,16 +71,17 @@ local rest_command_tbl = { end -- If there was only one argument and it is `set` then raise an error because we are also expecting for the env file path if #args == 1 and args[1] == "set" then - vim.notify( - "Rest: Not enough arguments were passed to the 'env' command: 2 argument were expected, 1 was passed", - vim.log.levels.ERROR + ---@diagnostic disable-next-line need-check-nil + logger:error( + "Not enough arguments were passed to the 'env' command: 2 argument were expected, 1 was passed" ) return end -- We do not need too many arguments here, complain about it please! if #args > 3 then - vim.notify( - "Rest: Too many arguments were passed to the 'env' command: 2 arguments were expected, " .. #args .. " were passed" + ---@diagnostic disable-next-line need-check-nil + logger:error( + "Too many arguments were passed to the 'env' command: 2 arguments were expected, " .. #args .. " were passed" ) return end @@ -128,11 +130,18 @@ local function rest(opts) local cmd = fargs[1] local args = #fargs > 1 and vim.list_slice(fargs, 2, #fargs) or {} local command = rest_command_tbl[cmd] + + local logger = _G._rest_nvim.logger + if not command then - vim.notify("Rest: Unknown command: " .. cmd, vim.log.levels.ERROR) + ---@diagnostic disable-next-line need-check-nil + logger:error("Unknown command: " .. cmd) return end + -- NOTE: I do not know why lua lsp is complaining about a missing parameter here + -- when all the `command.impl` functions expect only one parameter? + ---@diagnostic disable-next-line missing-argument command.impl(args) end diff --git a/lua/rest-nvim/config/check.lua b/lua/rest-nvim/config/check.lua index c4257cf4..b51ae60a 100644 --- a/lua/rest-nvim/config/check.lua +++ b/lua/rest-nvim/config/check.lua @@ -14,7 +14,7 @@ local check = {} ---@return string|nil error_message local function validate(tbl) local ok, err = pcall(vim.validate, tbl) - return ok or false, "Rest: Invalid config" .. (err and ": " .. err or "") + return ok or false, "Invalid config" .. (err and ": " .. err or "") end ---Validates the configuration diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index fa32ec19..51565d01 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -22,6 +22,8 @@ function functions.exec(scope, preview) preview = { preview, "boolean" }, }) + local logger = _G._rest_nvim.logger + -- Fallback to 'cursor' if no scope was given if not scope then scope = "cursor" @@ -29,9 +31,9 @@ function functions.exec(scope, preview) -- Raise an error if an invalid scope has been provided if not vim.tbl_contains({ "last", "cursor", "document" }, scope) then - vim.notify( - "Rest: Invalid scope '" .. scope .. "'provided to the 'exec' function", - vim.log.levels.ERROR + ---@diagnostic disable-next-line need-check-nil + logger:error( + "Invalid scope '" .. scope .. "'provided to the 'exec' function" ) return {} end @@ -44,35 +46,38 @@ end ---If you choose to `set` the environment, you must provide a `path` to the environment file. ---@param action string Determines the action to be taken. Can be: `set` or `show` (default) function functions.env(action, path) + -- TODO: add a `select` action later to open some kind of prompt to select one of many detected "*env*" files vim.validate({ action = { action, { "string", "nil" } }, path = { path, { "string", "nil" } }, }) - -- TODO: add a `select` action later to open some kind of prompt to select one of many detected "*env*" files + local logger = _G._rest_nvim.logger + if not action then action = "show" end if not vim.tbl_contains({ "set", "show" }, action) then - vim.notify( - "Rest: Invalid action '" .. action .. "' provided to the 'env' function", - vim.log.levels.ERROR + ---@diagnostic disable-next-line need-check-nil + logger:error( + "Invalid action '" .. action .. "' provided to the 'env' function" ) return end - vim.print(action) if action == "set" then - -- TODO: check file path and some other goofy ahhh stuff if utils.file_exists(path) then _G._rest_nvim.env_file = path - vim.notify("Rest: Current env file has been changed to: " .. _G._rest_nvim.env_file) + ---@diagnostic disable-next-line need-check-nil + logger:info("Current env file has been changed to: " .. _G._rest_nvim.env_file) else - vim.notify("Rest: Passed environment file '" .. path .. "' was not found", vim.log.levels.ERROR) + ---@diagnostic disable-next-line need-check-nil + logger:error("Passed environment file '" .. path .. "' was not found") end else - vim.notify("Rest: Current env file in use: " .. _G._rest_nvim.env_file) + ---@diagnostic disable-next-line need-check-nil + logger:info("Current env file in use: " .. _G._rest_nvim.env_file) end end From 6ae1adb19b191eb64b579f121f4ce498ac36d45c Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 28 Jan 2024 21:00:30 -0400 Subject: [PATCH 016/159] feat: add `parser` module. Still a WIP! --- lua/rest-nvim/parser.lua | 204 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua index e69de29b..1ea6e15a 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser.lua @@ -0,0 +1,204 @@ +---@mod rest-nvim.parser rest.nvim tree-sitter parsing module +--- +---@brief [[ +--- +---Parsing module with tree-sitter, we use tree-sitter there to extract +---all the document nodes and their content from the HTTP files, then we +---start doing some other internal parsing like variables expansion and so on +--- +---@brief ]] + +local parser = {} + +local logger = _G._rest_nvim.logger + +---@alias NodesList { [string]: TSNode }[] +---@alias Variables { [string]: { type_: string, value: string|number|boolean } }[] + +---Check if a given `node` has a syntax error and throw an error log message if that is the case +---@param node TSNode Tree-sitter node +---@return boolean +local function check_syntax_error(node) + if node:has_error() then + ---Create a node range string á la `:InspectTree` view + ---@param n TSNode + ---@return string + local function create_node_range_str(n) + local s_row, s_col = n:start() + local e_row, e_col = n:end_() + local range = "[" + + if s_row == e_row then + range = range .. s_row .. ":" .. s_col .. " - " .. e_col + else + range = range .. s_row .. ":" .. s_col .. " - " .. e_row .. ":" .. e_col + end + range = range .. "]" + return range + end + + ---@diagnostic disable-next-line need-check-nil + logger:error("The tree-sitter node at the range " .. create_node_range_str(node) .. " has a syntax error and cannot be parsed") + return true + end + + return false +end + +---Get a tree-sitter node at the cursor position +---@return TSNode|nil Tree-sitter node +---@return string|nil Node type +function parser.get_node_at_cursor() + local node = assert(vim.treesitter.get_node()) + if check_syntax_error(node) then + return nil, nil + end + + return node, node:type() +end + +---Small wrapper around `vim.treesitter.get_node_text` because I do not want to +---write it every time +---@see vim.treesitter.get_node_text +---@param node TSNode Tree-sitter node +---@return string|nil +local function get_node_text(node) + if check_syntax_error(node) then + return nil + end + + return vim.treesitter.get_node_text(node, 0) +end + +---Recursively look behind `node` until `query` node type is found +---@param node TSNode|nil Tree-sitter node, defaults to the node at the cursor position if not passed +---@param query string The tree-sitter node type that we are looking for +---@return TSNode|nil +function parser.look_behind_until(node, query) + if not node then + node = parser.get_node_at_cursor() + end + + -- There are no more nodes behind the `document` one + ---@diagnostic disable-next-line need-check-nil + if node:type() == "document" then + ---@diagnostic disable-next-line need-check-nil + logger:debug("Current node is document, which does not have any parent nodes, returning it instead") + return node + end + + ---@cast node TSNode + if check_syntax_error(node) then + return nil + end + + ---@diagnostic disable-next-line need-check-nil + local parent = assert(node:parent()) + if parent:type() ~= query then + return parser.look_behind_until(parent, query) + end + + return parent +end + +---Traverse a request tree-sitter node and retrieve all its children nodes +---@param req_node TSNode Tree-sitter request node +---@return NodesList +local function traverse_request(req_node) + local child_nodes = {} + for child, _ in req_node:iter_children() do + local child_type = child:type() + child_nodes[child_type] = child + end + return child_nodes +end + +---Traverse the document tree-sitter node and retrieve all the `variable_declaration` nodes +---@param document_node TSNode Tree-sitter document node +---@return Variables +local function traverse_variables(document_node) + local variables = {} + for child, _ in document_node:iter_children() do + local child_type = child:type() + if child_type == "variable_declaration" then + local var_name = assert(get_node_text(child:field("name")[1])) + local var_value = child:field("value")[1] + local var_type = var_value:type() + variables[var_name] = { + type_ = var_type, + value = assert(get_node_text(var_value)), + } + end + end + return variables +end + +---Parse all the variable nodes in the document node +---@param children_nodes NodesList Tree-sitter nodes +---@return table +function parser.parse_variable(children_nodes) + local variables = {} + + return variables +end + +---Parse a request tree-sitter node +---@param children_nodes NodesList Tree-sitter nodes +---@param variables {} +---@return table +function parser.parse_request(children_nodes, variables) + local request = {} + for node_type, node in pairs(children_nodes) do + -- ast.request + if node_type == "method" then + request.method = assert(get_node_text(node)) + elseif node_type == "target_url" then + local url_node_text = assert(get_node_text(node)) + local url_variable = url_node_text:match("{{[%s]?%w+[%s]?}}") + end + + -- ast.headers + -- if node_type == "header" then + -- local header_name = assert(get_node_text(node:field("name")[1])) + -- local header_value = assert(get_node_text(node:field("value")[1])) + -- ast.headers[header_name] = vim.trim(header_value) + -- end + + -- ast.body + -- TODO: parse XML and GraphQL, how so? + -- if node_type == "json_body" then + -- local json_body_text = assert(get_node_text(node)) + -- local json_body = vim.json.decode(json_body_text, { + -- luanil = { + -- object = true, + -- array = true, + -- } + -- }) + -- + -- ast.body = json_body + -- end + end + + return request +end + +function parser.parse(req_node) + local ast = { + request = {}, + headers = {}, + body = {}, + } + local document_node = parser.look_behind_until(nil, "document") + + local request_children_nodes = traverse_request(req_node) + ---@cast document_node TSNode + local document_variables = traverse_variables(document_node) + + vim.print(document_variables) + + ast.request = parser.parse_request(request_children_nodes, document_variables) + + return ast +end + +return parser From 2259dc6ff648fa9e269025e8396e5dd507b146ab Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 29 Jan 2024 16:28:01 -0400 Subject: [PATCH 017/159] feat(parser): add initial variables expansion --- lua/rest-nvim/parser.lua | 116 +++++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 42 deletions(-) diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua index 1ea6e15a..ba4b5f74 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser.lua @@ -61,13 +61,15 @@ end ---write it every time ---@see vim.treesitter.get_node_text ---@param node TSNode Tree-sitter node +---@param source integer|string Buffer or string from which the `node` is extracted ---@return string|nil -local function get_node_text(node) +local function get_node_text(node, source) + source = source or 0 if check_syntax_error(node) then return nil end - return vim.treesitter.get_node_text(node, 0) + return vim.treesitter.get_node_text(node, source) end ---Recursively look behind `node` until `query` node type is found @@ -75,9 +77,7 @@ end ---@param query string The tree-sitter node type that we are looking for ---@return TSNode|nil function parser.look_behind_until(node, query) - if not node then - node = parser.get_node_at_cursor() - end + node = node or parser.get_node_at_cursor() -- There are no more nodes behind the `document` one ---@diagnostic disable-next-line need-check-nil @@ -121,67 +121,99 @@ local function traverse_variables(document_node) for child, _ in document_node:iter_children() do local child_type = child:type() if child_type == "variable_declaration" then - local var_name = assert(get_node_text(child:field("name")[1])) + local var_name = assert(get_node_text(child:field("name")[1], 0)) local var_value = child:field("value")[1] local var_type = var_value:type() variables[var_name] = { type_ = var_type, - value = assert(get_node_text(var_value)), + value = assert(get_node_text(var_value, 0)), } end end return variables end ----Parse all the variable nodes in the document node ----@param children_nodes NodesList Tree-sitter nodes ----@return table -function parser.parse_variable(children_nodes) - local variables = {} - - return variables +---Parse all the variable nodes in the given node and expand them to their values +---@param node TSNode Tree-sitter node +---@param tree string The text where variables should be looked for +---@param text string The text where variables should be expanded +---@param variables Variables Document variables list +---@return string The given `text` with expanded variables +local function parse_variables(node, tree, text, variables) + local variable_query = vim.treesitter.query.parse("http", "(variable name: (_) @name)") + ---@diagnostic disable-next-line missing-parameter + for _, nod, _ in variable_query:iter_captures(node:root(), tree) do + local variable_name = assert(get_node_text(nod, tree)) + local variable = variables[variable_name] + local variable_value = variable.value + if variable.type_ == "string" then + variable_value = variable_value:gsub('"', "") + end + text = text:gsub("{{[%s]?" .. variable_name .. "[%s]?}}", variable_value) + end + return text end ---Parse a request tree-sitter node ---@param children_nodes NodesList Tree-sitter nodes ----@param variables {} +---@param variables Variables ---@return table function parser.parse_request(children_nodes, variables) local request = {} for node_type, node in pairs(children_nodes) do - -- ast.request if node_type == "method" then - request.method = assert(get_node_text(node)) + request.method = assert(get_node_text(node, 0)) elseif node_type == "target_url" then - local url_node_text = assert(get_node_text(node)) - local url_variable = url_node_text:match("{{[%s]?%w+[%s]?}}") + request.url = assert(get_node_text(node, 0)) end - - -- ast.headers - -- if node_type == "header" then - -- local header_name = assert(get_node_text(node:field("name")[1])) - -- local header_value = assert(get_node_text(node:field("value")[1])) - -- ast.headers[header_name] = vim.trim(header_value) - -- end - - -- ast.body - -- TODO: parse XML and GraphQL, how so? - -- if node_type == "json_body" then - -- local json_body_text = assert(get_node_text(node)) - -- local json_body = vim.json.decode(json_body_text, { - -- luanil = { - -- object = true, - -- array = true, - -- } - -- }) - -- - -- ast.body = json_body - -- end end + -- Parse the request nodes again as a single string converted into a new AST Tree to expand the variables + local request_text = request.method .. " " .. request.url .. "\n" + local request_tree = vim.treesitter.get_string_parser(request_text, "http"):parse()[1] + request.url = parse_variables(request_tree:root(), request_text, request.url, variables) + return request end +---Parse request headers tree-sitter nodes +---@param children_nodes NodesList Tree-sitter nodes +---@param variables Variables +---@return table +function parser.parse_headers(children_nodes, variables) + local headers = {} + for node_type, node in pairs(children_nodes) do + if node_type == "header" then + local name = assert(get_node_text(node:field("name")[1], 0)) + local value = assert(get_node_text(node:field("value")[1], 0)) + headers[name] = vim.trim(value) + end + end + + return headers +end + +---Parse a request tree-sitter node body +---@param children_nodes NodesList Tree-sitter nodes +---@param variables Variables +---@return table +function parser.parse_body(children_nodes, variables) + local body = {} + for node_type, node in pairs(children_nodes) do + -- TODO: handle XML bodies by using xml2lua library from luarocks + if node_type == "json_body" then + -- TODO: expand variables + local json_body_text = assert(get_node_text(node, 0)) + local json_body = vim.json.decode(json_body_text, { + luanil = { object = true, array = true }, + }) + body = json_body + end + end + + return body +end + function parser.parse(req_node) local ast = { request = {}, @@ -194,9 +226,9 @@ function parser.parse(req_node) ---@cast document_node TSNode local document_variables = traverse_variables(document_node) - vim.print(document_variables) - ast.request = parser.parse_request(request_children_nodes, document_variables) + ast.headers = parser.parse_headers(request_children_nodes, document_variables) + ast.body = parser.parse_body(request_children_nodes, document_variables) return ast end From 9198b25df5b2dbb1012043c8f59e580714ec844b Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 29 Jan 2024 22:02:06 -0400 Subject: [PATCH 018/159] feat(parser): add variable expansion on header values --- lua/rest-nvim/parser.lua | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua index ba4b5f74..65586fc1 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser.lua @@ -105,10 +105,16 @@ end ---@param req_node TSNode Tree-sitter request node ---@return NodesList local function traverse_request(req_node) - local child_nodes = {} - for child, _ in req_node:iter_children() do + local child_nodes = { + headers = {}, + } + for child, field_name in req_node:iter_children() do local child_type = child:type() - child_nodes[child_type] = child + if child_type == "header" then + table.insert(child_nodes.headers, child) + else + child_nodes[child_type] = child + end end return child_nodes end @@ -177,17 +183,22 @@ function parser.parse_request(children_nodes, variables) end ---Parse request headers tree-sitter nodes ----@param children_nodes NodesList Tree-sitter nodes +---@param children_nodes TSNode[] Tree-sitter nodes ---@param variables Variables ---@return table function parser.parse_headers(children_nodes, variables) local headers = {} - for node_type, node in pairs(children_nodes) do - if node_type == "header" then - local name = assert(get_node_text(node:field("name")[1], 0)) - local value = assert(get_node_text(node:field("value")[1], 0)) - headers[name] = vim.trim(value) - end + for _, node in ipairs(children_nodes.headers) do + local name = assert(get_node_text(node:field("name")[1], 0)) + local value = vim.trim(assert(get_node_text(node:field("value")[1], 0))) + + -- This dummy request is just for the parser to be able to recognize the header node + -- so we can iterate over it to parse the variables + local dummy_request = "GET http://localhost:3333\n" + local header_text = name .. ": " .. value + local header_tree = vim.treesitter.get_string_parser(dummy_request .. header_text, "http"):parse()[1] + + headers[name] = parse_variables(header_tree:root(), dummy_request .. header_text, value, variables) end return headers From 48a3d01b840906e410b4262a69484ed1eca5f559 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 00:43:16 -0400 Subject: [PATCH 019/159] feat(parser): add variable expansion in body keys and values --- lua/rest-nvim/parser.lua | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua index 65586fc1..a9f6a9d7 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser.lua @@ -204,6 +204,41 @@ function parser.parse_headers(children_nodes, variables) return headers end +---Recursively traverse a body table and expand all the variables +---@param tbl table Request body +---@return table +local function traverse_body(tbl, variables) + for k, v in pairs(tbl) do + if type(v) == "table" then + traverse_body(v, variables) + end + + if type(k) == "string" and k:find("{{[%s]?%w+[%s?]}}") then + local variable_name = k:match("%w+") + local variable = variables[variable_name] + local variable_value = variable.value + if variable.type_ == "string" then + variable_value = variable_value:gsub('"', "") + end + + local key_value = tbl[k] + tbl[k] = nil + tbl[variable_value] = key_value + end + if type(v) == "string" and v:find("{{[%s]?%w+[%s?]}}") then + local variable_name = v:match("%w+") + local variable = variables[variable_name] + local variable_value = variable.value + if variable.type_ == "string" then + variable_value = variable_value:gsub('"', "") + end + + tbl[k] = variable_value + end + end + return tbl +end + ---Parse a request tree-sitter node body ---@param children_nodes NodesList Tree-sitter nodes ---@param variables Variables @@ -213,12 +248,13 @@ function parser.parse_body(children_nodes, variables) for node_type, node in pairs(children_nodes) do -- TODO: handle XML bodies by using xml2lua library from luarocks if node_type == "json_body" then - -- TODO: expand variables local json_body_text = assert(get_node_text(node, 0)) local json_body = vim.json.decode(json_body_text, { luanil = { object = true, array = true }, }) - body = json_body + body = traverse_body(json_body, variables) + -- This is some metadata to be used later on + body.__TYPE = "json" end end From bb87c8cbd848a7c0cf049575c358cc5fa73a34c5 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 13:53:28 -0400 Subject: [PATCH 020/159] feat(parser): add environment variables expansion as a fallback to document variables, small cleanup --- lua/rest-nvim/parser.lua | 77 ++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua index a9f6a9d7..5486e273 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser.lua @@ -143,17 +143,32 @@ end ---@param node TSNode Tree-sitter node ---@param tree string The text where variables should be looked for ---@param text string The text where variables should be expanded ----@param variables Variables Document variables list ----@return string The given `text` with expanded variables +---@param variables Variables HTTP document variables list +---@return string|nil The given `text` with expanded variables local function parse_variables(node, tree, text, variables) local variable_query = vim.treesitter.query.parse("http", "(variable name: (_) @name)") ---@diagnostic disable-next-line missing-parameter for _, nod, _ in variable_query:iter_captures(node:root(), tree) do local variable_name = assert(get_node_text(nod, tree)) + local variable_value local variable = variables[variable_name] - local variable_value = variable.value - if variable.type_ == "string" then - variable_value = variable_value:gsub('"', "") + -- If the variable was not found in the document then fallback to the shell environment + if not variable then + ---@diagnostic disable-next-line need-check-nil + logger:debug("The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ...") + local env_var = vim.env[variable_name] + if not env_var then + ---@diagnostic disable-next-line need-check-nil + logger:warn("The variable '" .. variable_name .. "' was not found in the document or in the environment. Returning the string as received ...") + return text + end + variable_value = env_var + else + variable_value = variable.value + if variable.type_ == "string" then + ---@cast variable_value string + variable_value = variable_value:gsub('"', "") + end end text = text:gsub("{{[%s]?" .. variable_name .. "[%s]?}}", variable_value) end @@ -162,7 +177,7 @@ end ---Parse a request tree-sitter node ---@param children_nodes NodesList Tree-sitter nodes ----@param variables Variables +---@param variables Variables HTTP document variables list ---@return table function parser.parse_request(children_nodes, variables) local request = {} @@ -184,7 +199,7 @@ end ---Parse request headers tree-sitter nodes ---@param children_nodes TSNode[] Tree-sitter nodes ----@param variables Variables +---@param variables Variables HTTP document variables list ---@return table function parser.parse_headers(children_nodes, variables) local headers = {} @@ -213,26 +228,44 @@ local function traverse_body(tbl, variables) traverse_body(v, variables) end - if type(k) == "string" and k:find("{{[%s]?%w+[%s?]}}") then - local variable_name = k:match("%w+") - local variable = variables[variable_name] - local variable_value = variable.value - if variable.type_ == "string" then - variable_value = variable_value:gsub('"', "") + ---Expand a variable in the given string + ---@param str string String where the variables are going to be expanded + ---@param vars Variables HTTP document variables list + ---@return string|number|boolean + local function expand_variable(str, vars) + local variable_name = str:gsub("{{[%s]?", ""):gsub("[%s]?}}", ""):match(".*") + local variable_value + local variable = vars[variable_name] + -- If the variable was not found in the document then fallback to the shell environment + if not variable then + ---@diagnostic disable-next-line need-check-nil + logger:debug("The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ...") + local env_var = vim.env[variable_name] + if not env_var then + ---@diagnostic disable-next-line need-check-nil + logger:warn("The variable '" .. variable_name .. "' was not found in the document or in the environment. Returning the string as received ...") + return str + end + variable_value = env_var + else + variable_value = variable.value + if variable.type_ == "string" then + ---@cast variable_value string + variable_value = variable_value:gsub('"', "") + end end + ---@cast variable_value string|number|boolean + return variable_value + end + if type(k) == "string" and k:find("{{[%s]?.*[%s?]}}") then + local variable_value = expand_variable(k, variables) local key_value = tbl[k] tbl[k] = nil tbl[variable_value] = key_value end - if type(v) == "string" and v:find("{{[%s]?%w+[%s?]}}") then - local variable_name = v:match("%w+") - local variable = variables[variable_name] - local variable_value = variable.value - if variable.type_ == "string" then - variable_value = variable_value:gsub('"', "") - end - + if type(v) == "string" and v:find("{{[%s]?.*[%s?]}}") then + local variable_value = expand_variable(v, variables) tbl[k] = variable_value end end @@ -241,7 +274,7 @@ end ---Parse a request tree-sitter node body ---@param children_nodes NodesList Tree-sitter nodes ----@param variables Variables +---@param variables Variables HTTP document variables list ---@return table function parser.parse_body(children_nodes, variables) local body = {} From 125b7f740568dd601cbb77c032174ccb34294204 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 14:20:00 -0400 Subject: [PATCH 021/159] fix(parser): self-contain logger in the functions where it is required to prevent `nil` errors on require --- lua/rest-nvim/parser.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua index 5486e273..4a9c9b69 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser.lua @@ -10,8 +10,6 @@ local parser = {} -local logger = _G._rest_nvim.logger - ---@alias NodesList { [string]: TSNode }[] ---@alias Variables { [string]: { type_: string, value: string|number|boolean } }[] @@ -20,6 +18,8 @@ local logger = _G._rest_nvim.logger ---@return boolean local function check_syntax_error(node) if node:has_error() then + local logger = _G._rest_nvim.logger + ---Create a node range string á la `:InspectTree` view ---@param n TSNode ---@return string @@ -77,6 +77,7 @@ end ---@param query string The tree-sitter node type that we are looking for ---@return TSNode|nil function parser.look_behind_until(node, query) + local logger = _G._rest_nvim.logger node = node or parser.get_node_at_cursor() -- There are no more nodes behind the `document` one @@ -146,6 +147,7 @@ end ---@param variables Variables HTTP document variables list ---@return string|nil The given `text` with expanded variables local function parse_variables(node, tree, text, variables) + local logger = _G._rest_nvim.logger local variable_query = vim.treesitter.query.parse("http", "(variable name: (_) @name)") ---@diagnostic disable-next-line missing-parameter for _, nod, _ in variable_query:iter_captures(node:root(), tree) do @@ -233,6 +235,8 @@ local function traverse_body(tbl, variables) ---@param vars Variables HTTP document variables list ---@return string|number|boolean local function expand_variable(str, vars) + local logger = _G._rest_nvim.logger + local variable_name = str:gsub("{{[%s]?", ""):gsub("[%s]?}}", ""):match(".*") local variable_value local variable = vars[variable_name] From 3783035b710f085d2b37698a767a2df95dc0f1be Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 17:02:15 -0400 Subject: [PATCH 022/159] feat(parser): support `(http_version)` node detection, improve documentation --- lua/rest-nvim/parser.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua index 4a9c9b69..6add58ec 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser.lua @@ -188,6 +188,9 @@ function parser.parse_request(children_nodes, variables) request.method = assert(get_node_text(node, 0)) elseif node_type == "target_url" then request.url = assert(get_node_text(node, 0)) + elseif node_type == "http_version" then + local http_version = assert(get_node_text(node, 0)) + request.http_version = http_version:gsub("HTTP/", "") end end @@ -298,6 +301,19 @@ function parser.parse_body(children_nodes, variables) return body end +---@class RequestReq +---@field method string The request method +---@field url string The request URL +---@field http_version? string The request HTTP protocol + +---@class Request +---@field request RequestReq +---@field headers { [string]: string|number|boolean }[] +---@field body table + +---Parse a request and return the request on itself, its headers and body +---@param req_node TSNode Tree-sitter request node +---@return Request function parser.parse(req_node) local ast = { request = {}, From 5e3a05a07a043eba443fbc9a7137946baed13ad4 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 17:06:19 -0400 Subject: [PATCH 023/159] feat(client)!: add initial client for `cURL` This is not using `plenary.nvim` anymore and `Lua-cURL` luarocks rock is required as a dependency to get it working. Note: there are some missing features yet like forms support, they are going to be added later on --- lua/rest-nvim/client/curl.lua | 87 +++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 lua/rest-nvim/client/curl.lua diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua new file mode 100644 index 00000000..1c446dcc --- /dev/null +++ b/lua/rest-nvim/client/curl.lua @@ -0,0 +1,87 @@ +---@mod rest-nvim.client.curl rest.nvim cURL client +--- +---@brief [[ +--- +--- rest.nvim cURL client implementation +--- +---@brief ]] + +local client = {} + +local curl = require("cURL.safe") + +-- TODO: add support for running multiple requests at once for `:Rest run document` +-- TODO: add support for submitting forms in the `client.request` function +-- TODO: add support for submitting XML bodies in the `client.request` function + +---@param request Request +function client.request(request) + local logger = _G._rest_nvim.logger + + -- We have to concat request headers to a single string, e.g. ["Content-Type"]: "application/json" -> "Content-Type: application/json" + local headers = {} + for name, value in pairs(request.headers) do + table.insert(headers, name .. ": " .. value) + end + + -- Whether to skip SSL host and peer verification + local skip_ssl_verification = _G._rest_nvim.skip_ssl_verification + local req = curl.easy_init() + req:setopt({ + url = request.request.url, + -- verbose = true, + httpheader = headers, + ssl_verifyhost = skip_ssl_verification, + ssl_verifypeer = skip_ssl_verification, + }) + + -- Set request HTTP version, defaults to HTTP/1.1 + if request.request.http_version then + local http_version = request.request.http_version:gsub("%.", "_") + req:setopt_http_version(curl["HTTP_VERSION_" .. http_version]) + else + req:setopt_http_version(curl.HTTP_VERSION_1_1) + end + + -- If the request method is not GET then we have to build the method in our own + -- See: https://github.com/Lua-cURL/Lua-cURLv3/issues/156 + local method = request.request.method + if vim.tbl_contains({ "POST", "PUT", "PATCH", "TRACE", "OPTIONS", "DELETE" }, method) then + req:setopt_post(true) + req:setopt_customrequest(method) + end + + -- Request body + if request.body.__TYPE == "json" then + -- Create a copy of the request body table to remove the unneeded `__TYPE` metadata field + local body = request.body + body.__TYPE = nil + + local json_body_string = vim.json.encode(request.body) + req:setopt_postfields(json_body_string) + end + + -- Request execution + local res_result = {} + local res_headers = {} + req:setopt_writefunction(table.insert, res_result) + req:setopt_headerfunction(table.insert, res_headers) + + local ok, err = req:perform() + if ok then + local url = req:getinfo_effective_url() + local code = req:getinfo_response_code() + local content = table.concat(res_result) + vim.print(url .. " - " .. code) + vim.print("Request headers:", table.concat(res_headers):gsub("\r", "")) + vim.print("Request content:\n\n" .. content) + vim.print("\nTime taken: " .. string.format("%.2fs", req:getinfo_total_time())) + else + ---@diagnostic disable-next-line need-check-nil + logger:error(err) + end + + req:close() +end + +return client From c7df42714622207b7eed844a79c48d2f14d9fbce Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 17:08:46 -0400 Subject: [PATCH 024/159] feat(functions): implement `:Rest run cursor` functionalities, add `nvim-nio` dependency There are also a few small adjustments in this commit regarding to documentation and error handling for the HTTP clients loading logic --- lua/rest-nvim/functions.lua | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index 51565d01..4a87f939 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -8,7 +8,9 @@ local functions = {} +local nio = require("nio") local utils = require("rest-nvim.utils") +local parser = require("rest-nvim.parser") ---Execute or `preview` one or several HTTP requests depending on given `scope` ---and return request(s) results in a table that will be used to render results @@ -23,6 +25,12 @@ function functions.exec(scope, preview) }) local logger = _G._rest_nvim.logger + local ok, client = pcall(require, "rest-nvim.client." .. _G._rest_nvim.client) + if not ok then + ---@diagnostic disable-next-line need-check-nil + logger:error("The client '" .. _G._rest_nvim.client .. "' could not be found. Maybe it is not installed?") + return {} + end -- Fallback to 'cursor' if no scope was given if not scope then @@ -33,12 +41,27 @@ function functions.exec(scope, preview) if not vim.tbl_contains({ "last", "cursor", "document" }, scope) then ---@diagnostic disable-next-line need-check-nil logger:error( - "Invalid scope '" .. scope .. "'provided to the 'exec' function" + "Invalid scope '" .. scope .. "' provided to the 'exec' function" ) return {} end - print("WIP") + -- TODO: implement `last` and `document` scopes. + -- + -- NOTE: The `document` scope may require some parser adjustments + if scope == "cursor" then + local req = parser.parse( + ---@diagnostic disable-next-line param-type-mismatch + parser.look_behind_until(parser.get_node_at_cursor(), "request") + ) + + local sleep = nio.wrap(function(ms, cb) + vim.defer_fn(cb, ms) + end, 2) + nio.run(function() + sleep(10, client.request(req)) + end) + end end ---Manage the environment file that is currently in use while running requests From 16009c267c9754f08ba10bf658bf7bf1d2782567 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 17:09:26 -0400 Subject: [PATCH 025/159] feat(queries): update bundled queries to use latest http parser rewrite changes from the `next` branch --- after/queries/http/highlights.scm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/after/queries/http/highlights.scm b/after/queries/http/highlights.scm index 7db1e559..bdda2977 100644 --- a/after/queries/http/highlights.scm +++ b/after/queries/http/highlights.scm @@ -56,6 +56,8 @@ (target_url) @text.uri +(http_version) @constant + (string) @string (number) @number From d5cbaa0058b076169e480ff52a52a75395692512 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 17:26:12 -0400 Subject: [PATCH 026/159] feat(functions): implement `:Rest run last`|`:Rest last` command functionality --- lua/rest-nvim/functions.lua | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index 4a87f939..6a0b5b2c 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -46,7 +46,7 @@ function functions.exec(scope, preview) return {} end - -- TODO: implement `last` and `document` scopes. + -- TODO: implement `document` scope. -- -- NOTE: The `document` scope may require some parser adjustments if scope == "cursor" then @@ -61,6 +61,24 @@ function functions.exec(scope, preview) nio.run(function() sleep(10, client.request(req)) end) + + ---Last HTTP request made by the user + ---@type Request + _G._rest_nvim_last_request = req + elseif scope == "last" then + local req = _G._rest_nvim_last_request + + if not req then + ---@diagnostic disable-next-line need-check-nil + logger:error("Rest run last: A previously made request was not found to be executed again") + else + local sleep = nio.wrap(function(ms, cb) + vim.defer_fn(cb, ms) + end, 2) + nio.run(function() + sleep(10, client.request(req)) + end) + end end end From a0f1274ebcce365ad950b8c78ee0f1a2b4a1c7e7 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 17:37:02 -0400 Subject: [PATCH 027/159] chore(parser): add some TODO comments --- lua/rest-nvim/parser.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua index 6add58ec..ac4246eb 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser.lua @@ -10,6 +10,9 @@ local parser = {} +-- TODO: parse and evaluate `(script_variable)` request node +-- TODO: parse and evaluate dynamic variables too + ---@alias NodesList { [string]: TSNode }[] ---@alias Variables { [string]: { type_: string, value: string|number|boolean } }[] From d68488fa63ab2f9ec3cc899f9186f5b95091b7bf Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 18:01:18 -0400 Subject: [PATCH 028/159] ref(syntax)!: rewrite syntax file for `httpResult` buffer in Lua with some new Lua APIs --- syntax/httpResult.lua | 47 +++++++++++++++++++++++++++++++++++++++++++ syntax/httpResult.vim | 35 -------------------------------- 2 files changed, 47 insertions(+), 35 deletions(-) create mode 100644 syntax/httpResult.lua delete mode 100644 syntax/httpResult.vim diff --git a/syntax/httpResult.lua b/syntax/httpResult.lua new file mode 100644 index 00000000..f87e3e69 --- /dev/null +++ b/syntax/httpResult.lua @@ -0,0 +1,47 @@ +if vim.fn.exists("b:current_syntax") == 1 then + return +end + +local function syntax(kind, group, rhs) + vim.cmd(string.format("syn %s %s %s", kind, group, rhs)) +end + +local function hl_link(lhs, rhs) + local ns = vim.api.nvim_create_namespace("rest.result") + vim.api.nvim_set_hl(ns, lhs, { link = rhs }) +end + +syntax("match", "httpResultComment", [[\v^#.*$"]]) +syntax("match", "httpResultPath", [[/.*$/ contained]]) +syntax("keyword", "httpResultTitle", [[GET POST PATCH PUT HEAD DELETE nextgroup=httpResultPath]]) + +syntax("match", "httpResultField", [[/^\(\w\)[^:]\+:/he=e-1]]) +syntax("match", "httpResultDateField", [[/^[Dd]ate:/he=e-1 nextgroup=httpResultDate]]) +syntax("match", "httpResultDateField", [[/^[Ee]xpires:/he=e-1 nextgroup=httpResultDate]]) +syntax("match", "httpResultDate", [[/.*$/ contained]]) + +syntax("match", "httpResult200", [[/2\d\d.*$/ contained]]) +syntax("match", "httpResult300", [[/3\d\d.*$/ contained]]) +syntax("match", "httpResult400", [[/4\d\d.*$/ contained]]) +syntax("match", "httpResult500", [[/5\d\d.*$/ contained]]) +syntax("region", "httpResultHeader", [[start=+^HTTP/+ end=+ + nextgroup=httpResult200,httpResult300,httpResult400,httpResult500]]) + +syntax("match", "httpResultNumber", [[/\v[ =]@1<=[0-9]*.?[0-9]+[ ,;&\n]/he=e-1]]) +syntax("region", "httpResultString", [[start=/\vr?"/ end=/\v"/]]) + + +hl_link("httpResultComment", "Comment") +hl_link("httpResultTitle", "Type") +hl_link("httpResultPath", "httpTSURI") +hl_link("httpResultField", "Identifier") +hl_link("httpResultDateField", "Identifier") +hl_link("httpResultDate", "String") +hl_link("httpResultString", "String") +hl_link("httpResultNumber", "Number") +hl_link("httpResultHeader", "Type") +hl_link("httpResult200", "String") +hl_link("httpResult300", "Function") +hl_link("httpResult400", "Number") +hl_link("httpResult500", "Number") + +vim.b.current_syntax = "httpResult" diff --git a/syntax/httpResult.vim b/syntax/httpResult.vim deleted file mode 100644 index 7b644c39..00000000 --- a/syntax/httpResult.vim +++ /dev/null @@ -1,35 +0,0 @@ -if exists("b:current_syntax") | finish | endif - -syn match httpResultComment "\v^#.*$" -syn keyword httpResultTitle GET POST PATCH PUT HEAD DELETE nextgroup=httpResultPath -syn match httpResultPath /.*$/ contained - -syn match httpResultField /^\(\w\)[^:]\+:/he=e-1 -syn match httpResultDateField /^[Dd]ate:/he=e-1 nextgroup=httpResultDate -syn match httpResultDateField /^[Ee]xpires:/he=e-1 nextgroup=httpResultDate -syn match httpResultDate /.*$/ contained - -syn region httpResultHeader start=+^HTTP/+ end=+ + nextgroup=httpResult200,httpResult300,httpResult400,httpResult500 -syn match httpResult200 /2\d\d.*$/ contained -syn match httpResult300 /3\d\d.*$/ contained -syn match httpResult400 /4\d\d.*$/ contained -syn match httpResult500 /5\d\d.*$/ contained - -syn region httpResultString start=/\vr?"/ end=/\v"/ -syn match httpResultNumber /\v[ =]@1<=[0-9]*.?[0-9]+[ ,;&\n]/he=e-1 - -hi link httpResultComment Comment -hi link httpResultTitle Type -hi link httpResultPath httpTSURI -hi link httpResultField Identifier -hi link httpResultDateField Identifier -hi link httpResultDate String -hi link httpResultString String -hi link httpResultNumber Number -hi link httpResultHeader Type -hi link httpResult200 String -hi link httpResult300 Function -hi link httpResult400 Number -hi link httpResult500 Number - -let b:current_syntax = "httpResult" From 1984c71fbc498f00d265fb540fa47845d4fc6aad Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 18:03:17 -0400 Subject: [PATCH 029/159] ref(logger)!: use `table.concat` instead of `vim.fs.joinpath` as it is only available in Neovim nightly releases --- lua/rest-nvim/logger.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/logger.lua b/lua/rest-nvim/logger.lua index 896f4678..e6f49d40 100644 --- a/lua/rest-nvim/logger.lua +++ b/lua/rest-nvim/logger.lua @@ -54,7 +54,7 @@ local default_config = { local function store_log(msg) local date = os.date("%F %r") -- 2024-01-26 01:25:05 PM local log_msg = date .. " | " .. msg .. "\n" - local log_path = vim.fs.joinpath(vim.fn.stdpath("log"), "rest.nvim.log") + local log_path = table.concat({ vim.fn.stdpath("log"), "rest.nvim.log" }, "/") -- 644 sets read and write permissions for the owner, and it sets read-only -- mode for the group and others From 752a15f372d426d69a0e0f44d75f5d15ae6420a6 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 18:43:38 -0400 Subject: [PATCH 030/159] ref(parser)!: set external body file as a table too This way we can set it a name when POST-ing the file in a request by using the optional `<@filename ./path/to/file.json` syntax from the tree-sitter parser --- lua/rest-nvim/parser.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua index ac4246eb..d33fcb53 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser.lua @@ -288,6 +288,7 @@ end ---@return table function parser.parse_body(children_nodes, variables) local body = {} + for node_type, node in pairs(children_nodes) do -- TODO: handle XML bodies by using xml2lua library from luarocks if node_type == "json_body" then @@ -298,6 +299,15 @@ function parser.parse_body(children_nodes, variables) body = traverse_body(json_body, variables) -- This is some metadata to be used later on body.__TYPE = "json" + elseif node_type == "external_body" then + -- < @ (identifier) (file_path name: (path)) + -- 0 1 2 3 + if node:child_count() > 2 then + body.name = assert(get_node_text(node:child(2), 0)) + end + body.path = assert(get_node_text(node:field("file_path")[1], 0)) + -- This is some metadata to be used later on + body.__TYPE = "external_file" end end From c3a91a22037c5b180769ef762e0de7e5757c9da8 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 18:45:36 -0400 Subject: [PATCH 031/159] feat(client.curl): add support for sending files in the requests (not tested) If the external file body tree-sitter node had a name identifier (`<@filename ./path/to/file.json`) that identifier is going to be the name of the file when sent to the request server. Otherwise, its name will fallback to `body`. --- lua/rest-nvim/client/curl.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 1c446dcc..cd175121 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -9,6 +9,7 @@ local client = {} local curl = require("cURL.safe") +local mimetypes = require("mimetypes") -- TODO: add support for running multiple requests at once for `:Rest run document` -- TODO: add support for submitting forms in the `client.request` function @@ -59,6 +60,15 @@ function client.request(request) local json_body_string = vim.json.encode(request.body) req:setopt_postfields(json_body_string) + elseif request.body.__TYPE == "external_file" then + local body_mimetype = mimetypes.guess(request.body.path) + local post_data = { + [request.body.name and request.body.name or "body"] = { + file = request.body.path, + type = body_mimetype, + } + } + req:post(post_data) end -- Request execution From 26e261a02ab2617d16883aec7655ee81f7f2e733 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 19:57:16 -0400 Subject: [PATCH 032/159] feat(utils): add `transform` table and functions --- lua/rest-nvim/utils.lua | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/lua/rest-nvim/utils.lua b/lua/rest-nvim/utils.lua index e37d0274..fd64eef4 100644 --- a/lua/rest-nvim/utils.lua +++ b/lua/rest-nvim/utils.lua @@ -24,4 +24,56 @@ function utils.file_exists(path) return false end +--- Default transformers for statistics +local transform = { + ---Transform `time` into a readable typed time (e.g. 200ms) + ---@param time string + ---@return string + time = function(time) + time = tonumber(time) + + if time >= 60 then + time = string.format("%.2f", time / 60) + + return time .. " min" + end + + local units = { "s", "ms", "µs", "ns" } + local unit = 1 + + while time < 1 and unit <= #units do + time = time * 1000 + unit = unit + 1 + end + + time = string.format("%.2f", time) + + return time .. " " .. units[unit] + end, + + ---Transform `bytes` into another bigger size type if needed + ---@param bytes string + ---@return string + size = function(bytes) + ---@diagnostic disable-next-line cast-local-type + bytes = tonumber(bytes) + + local units = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" } + local unit = 1 + + while bytes >= 1024 and unit <= #units do + ---@diagnostic disable-next-line cast-local-type + bytes = bytes / 1024 + unit = unit + 1 + end + + bytes = string.format("%.2f", bytes) + + return bytes .. " " .. units[unit] + end, +} + +utils.transform_time = transform.time +utils.transform_size = transform.size + return utils From a481aa7344bd41718fc64baad4a9ef0e5e4acdcd Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 19:58:45 -0400 Subject: [PATCH 033/159] ref(config)!: stats table now require a `title` field (no longer optional) and removed the `type` field --- lua/rest-nvim/config/init.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index d80bd062..63285bd0 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -39,7 +39,7 @@ local logger = require("rest-nvim.logger") ---@class RestConfigResultStats ---@field enable boolean Whether enable statistics or not ----@field stats string[]|{ [1]: string, title?: string, type: string }[] Statistics to be shown, takes cURL's `--write-out` options +---@field stats string[]|{ [1]: string, title: string }[] Statistics to be shown, takes cURL's easy getinfo constants name ---@class RestConfigResultFormatters ---@field json string|fun(body: string): string JSON formatter @@ -91,9 +91,10 @@ local default_config = { }, statistics = { enable = true, + ---@see https://curl.se/libcurl/c/curl_easy_getinfo.html stats = { - { "time_total", title = "Total time: ", type = "time" }, - { "size_download", title = "Request download size: ", type = "byte" }, + { "total_time", title = "Time taken:" }, + { "size_download_t", title = "Request download size:" }, }, }, formatters = { From 7565e32c59ad7dd59198490abcfd2e02bcd01592 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 20:01:22 -0400 Subject: [PATCH 034/159] feat(client.curl): add support for statistics again, improve error handling --- lua/rest-nvim/client/curl.lua | 72 ++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index cd175121..d280d37b 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -11,11 +11,57 @@ local client = {} local curl = require("cURL.safe") local mimetypes = require("mimetypes") +local utils = require("rest-nvim.utils") + -- TODO: add support for running multiple requests at once for `:Rest run document` -- TODO: add support for submitting forms in the `client.request` function -- TODO: add support for submitting XML bodies in the `client.request` function ----@param request Request +---Get request statistics +---@param req table cURL request class +---@param statistics_tbl RestConfigResultStats Statistics table +---@return table +local function get_stats(req, statistics_tbl) + local logger = _G._rest_nvim.logger + + local stats = {} + + local function get_stat(req_, stat_) + local curl_info = curl["INFO_" .. stat_:upper()] + if not curl_info then + ---@diagnostic disable-next-line need-check-nil + logger:error("The cURL request stat field '" .. stat_ "' was not found.\nPlease take a look at: https://curl.se/libcurl/c/curl_easy_getinfo.html") + return + end + local stat_info = req_:getinfo(curl_info) + + if stat_:find("size") then + stat_info = utils.transform_size(stat_info) + elseif stat_:find("time") then + stat_info = utils.transform_time(stat_info) + end + return stat_info + end + + local stat_title, stat_info + for _, stat in pairs(statistics_tbl) do + for k,v in pairs(stat) do + if type(k) == "string" and k == "title" then + stat_title = v + end + if type(k) == "number" then + stat_info = get_stat(req, v) + end + end + table.insert(stats, stat_title .. " " .. stat_info) + end + + return stats +end + +---Execute an HTTP request using cURL +---@param request Request Request data to be passed to cURL +---@return table function client.request(request) local logger = _G._rest_nvim.logger @@ -77,21 +123,27 @@ function client.request(request) req:setopt_writefunction(table.insert, res_result) req:setopt_headerfunction(table.insert, res_headers) + local ret = {} + local ok, err = req:perform() if ok then - local url = req:getinfo_effective_url() - local code = req:getinfo_response_code() - local content = table.concat(res_result) - vim.print(url .. " - " .. code) - vim.print("Request headers:", table.concat(res_headers):gsub("\r", "")) - vim.print("Request content:\n\n" .. content) - vim.print("\nTime taken: " .. string.format("%.2fs", req:getinfo_total_time())) + -- Get request statistics if they are enabled + local stats_config = _G._rest_nvim.result.behavior.statistics + if stats_config.enable then + local statistics = get_stats(req, stats_config.stats) + ret.statistics = statistics + end + + ret.url = req:getinfo_effective_url() + ret.headers = table.concat(res_headers):gsub("\r", "") + ret.result = table.concat(res_result) else ---@diagnostic disable-next-line need-check-nil - logger:error(err) + logger:error("Something went wrong when making the request with cURL: " .. err) end - req:close() + + return ret end return client From e61e5d6796c0486458a5aee1bf8acdbf037d1c5b Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 21:01:44 -0400 Subject: [PATCH 035/159] hotfix(syntax): add missing delimiter in `httpResultComment` match --- syntax/httpResult.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syntax/httpResult.lua b/syntax/httpResult.lua index f87e3e69..4a2918f3 100644 --- a/syntax/httpResult.lua +++ b/syntax/httpResult.lua @@ -11,7 +11,7 @@ local function hl_link(lhs, rhs) vim.api.nvim_set_hl(ns, lhs, { link = rhs }) end -syntax("match", "httpResultComment", [[\v^#.*$"]]) +syntax("match", "httpResultComment", [["\v^#.*$"]]) syntax("match", "httpResultPath", [[/.*$/ contained]]) syntax("keyword", "httpResultTitle", [[GET POST PATCH PUT HEAD DELETE nextgroup=httpResultPath]]) From 9152a01ed7b846511f7beaf07bb80ed2ffced9a6 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 21:17:26 -0400 Subject: [PATCH 036/159] revert: ref(syntax)!: rewrite syntax file for `httpResult` buffer in Lua with some new Lua APIs --- syntax/httpResult.lua | 47 ------------------------------------------- syntax/httpResult.vim | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 47 deletions(-) delete mode 100644 syntax/httpResult.lua create mode 100644 syntax/httpResult.vim diff --git a/syntax/httpResult.lua b/syntax/httpResult.lua deleted file mode 100644 index 4a2918f3..00000000 --- a/syntax/httpResult.lua +++ /dev/null @@ -1,47 +0,0 @@ -if vim.fn.exists("b:current_syntax") == 1 then - return -end - -local function syntax(kind, group, rhs) - vim.cmd(string.format("syn %s %s %s", kind, group, rhs)) -end - -local function hl_link(lhs, rhs) - local ns = vim.api.nvim_create_namespace("rest.result") - vim.api.nvim_set_hl(ns, lhs, { link = rhs }) -end - -syntax("match", "httpResultComment", [["\v^#.*$"]]) -syntax("match", "httpResultPath", [[/.*$/ contained]]) -syntax("keyword", "httpResultTitle", [[GET POST PATCH PUT HEAD DELETE nextgroup=httpResultPath]]) - -syntax("match", "httpResultField", [[/^\(\w\)[^:]\+:/he=e-1]]) -syntax("match", "httpResultDateField", [[/^[Dd]ate:/he=e-1 nextgroup=httpResultDate]]) -syntax("match", "httpResultDateField", [[/^[Ee]xpires:/he=e-1 nextgroup=httpResultDate]]) -syntax("match", "httpResultDate", [[/.*$/ contained]]) - -syntax("match", "httpResult200", [[/2\d\d.*$/ contained]]) -syntax("match", "httpResult300", [[/3\d\d.*$/ contained]]) -syntax("match", "httpResult400", [[/4\d\d.*$/ contained]]) -syntax("match", "httpResult500", [[/5\d\d.*$/ contained]]) -syntax("region", "httpResultHeader", [[start=+^HTTP/+ end=+ + nextgroup=httpResult200,httpResult300,httpResult400,httpResult500]]) - -syntax("match", "httpResultNumber", [[/\v[ =]@1<=[0-9]*.?[0-9]+[ ,;&\n]/he=e-1]]) -syntax("region", "httpResultString", [[start=/\vr?"/ end=/\v"/]]) - - -hl_link("httpResultComment", "Comment") -hl_link("httpResultTitle", "Type") -hl_link("httpResultPath", "httpTSURI") -hl_link("httpResultField", "Identifier") -hl_link("httpResultDateField", "Identifier") -hl_link("httpResultDate", "String") -hl_link("httpResultString", "String") -hl_link("httpResultNumber", "Number") -hl_link("httpResultHeader", "Type") -hl_link("httpResult200", "String") -hl_link("httpResult300", "Function") -hl_link("httpResult400", "Number") -hl_link("httpResult500", "Number") - -vim.b.current_syntax = "httpResult" diff --git a/syntax/httpResult.vim b/syntax/httpResult.vim new file mode 100644 index 00000000..7b644c39 --- /dev/null +++ b/syntax/httpResult.vim @@ -0,0 +1,35 @@ +if exists("b:current_syntax") | finish | endif + +syn match httpResultComment "\v^#.*$" +syn keyword httpResultTitle GET POST PATCH PUT HEAD DELETE nextgroup=httpResultPath +syn match httpResultPath /.*$/ contained + +syn match httpResultField /^\(\w\)[^:]\+:/he=e-1 +syn match httpResultDateField /^[Dd]ate:/he=e-1 nextgroup=httpResultDate +syn match httpResultDateField /^[Ee]xpires:/he=e-1 nextgroup=httpResultDate +syn match httpResultDate /.*$/ contained + +syn region httpResultHeader start=+^HTTP/+ end=+ + nextgroup=httpResult200,httpResult300,httpResult400,httpResult500 +syn match httpResult200 /2\d\d.*$/ contained +syn match httpResult300 /3\d\d.*$/ contained +syn match httpResult400 /4\d\d.*$/ contained +syn match httpResult500 /5\d\d.*$/ contained + +syn region httpResultString start=/\vr?"/ end=/\v"/ +syn match httpResultNumber /\v[ =]@1<=[0-9]*.?[0-9]+[ ,;&\n]/he=e-1 + +hi link httpResultComment Comment +hi link httpResultTitle Type +hi link httpResultPath httpTSURI +hi link httpResultField Identifier +hi link httpResultDateField Identifier +hi link httpResultDate String +hi link httpResultString String +hi link httpResultNumber Number +hi link httpResultHeader Type +hi link httpResult200 String +hi link httpResult300 Function +hi link httpResult400 Number +hi link httpResult500 Number + +let b:current_syntax = "httpResult" From c333b45878b0c08ce858214de9e3ddfae04eb7c0 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 21:30:04 -0400 Subject: [PATCH 037/159] feat(syntax): small improvements --- syntax/httpResult.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/syntax/httpResult.vim b/syntax/httpResult.vim index 7b644c39..a299f145 100644 --- a/syntax/httpResult.vim +++ b/syntax/httpResult.vim @@ -1,7 +1,7 @@ if exists("b:current_syntax") | finish | endif syn match httpResultComment "\v^#.*$" -syn keyword httpResultTitle GET POST PATCH PUT HEAD DELETE nextgroup=httpResultPath +syn keyword httpResultMethod OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT nextgroup=httpResultPath syn match httpResultPath /.*$/ contained syn match httpResultField /^\(\w\)[^:]\+:/he=e-1 @@ -19,8 +19,8 @@ syn region httpResultString start=/\vr?"/ end=/\v"/ syn match httpResultNumber /\v[ =]@1<=[0-9]*.?[0-9]+[ ,;&\n]/he=e-1 hi link httpResultComment Comment -hi link httpResultTitle Type -hi link httpResultPath httpTSURI +hi link httpResultMethod Type +hi link httpResultPath Link hi link httpResultField Identifier hi link httpResultDateField Identifier hi link httpResultDate String From dc4ed6edefa27705858da138618da794e52ff149 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 21:34:28 -0400 Subject: [PATCH 038/159] fix(syntax): do not highlight the whitespace between the HTTP method and the URL --- syntax/httpResult.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syntax/httpResult.vim b/syntax/httpResult.vim index a299f145..fe8271d9 100644 --- a/syntax/httpResult.vim +++ b/syntax/httpResult.vim @@ -2,7 +2,7 @@ if exists("b:current_syntax") | finish | endif syn match httpResultComment "\v^#.*$" syn keyword httpResultMethod OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT nextgroup=httpResultPath -syn match httpResultPath /.*$/ contained +syn match httpResultPath /.*$/hs=s+1 contained syn match httpResultField /^\(\w\)[^:]\+:/he=e-1 syn match httpResultDateField /^[Dd]ate:/he=e-1 nextgroup=httpResultDate From 684908028fe145f64c67073804e751987b4db369 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 21:35:14 -0400 Subject: [PATCH 039/159] feat: add `result` module to handle the HTTP request results buffer --- lua/rest-nvim/result.lua | 135 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 lua/rest-nvim/result.lua diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua new file mode 100644 index 00000000..32d7a8ee --- /dev/null +++ b/lua/rest-nvim/result.lua @@ -0,0 +1,135 @@ +---@mod rest-nvim.result rest.nvim result buffer +--- +---@brief [[ +--- +--- rest.nvim result buffer handling +--- +---@brief ]] + +local result = {} + +---Move the cursor to the desired position in the given buffer +---@param bufnr number Buffer handler number +---@param row number The desired line +---@param col number The desired column, defaults to `1` +local function move_cursor(bufnr, row, col) + col = col or 1 + vim.api.nvim_buf_call(bufnr, function() + vim.fn.cursor(row, col) + end) +end + +---Check if there is already a buffer with the rest run results +---and create the buffer if it does not exist +---@see vim.api.nvim_create_buf +---@return number Buffer handler number +function result.get_or_create_buf() + local tmp_name = "rest_nvim_results" + + -- Check if the file is already loaded in the buffer + local existing_buf, bufnr = false, nil + for _, id in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_get_name(id) == tmp_name then + existing_buf = true + bufnr = id + end + end + + if existing_buf then + -- Set modifiable + vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr }) + -- Prevent modified flag + vim.api.nvim_set_option_value("buftype", "nofile", { buf = bufnr }) + -- Delete buffer content + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {}) + + -- Make sure the filetype of the buffer is `httpResult` so it will be highlighted + vim.api.nvim_set_option_value("ft", "httpResult", { buf = bufnr }) + + return bufnr + end + + -- Create a new buffer + local new_bufnr = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_name(new_bufnr, tmp_name) + vim.api.nvim_set_option_value("ft", "httpResult", { buf = new_bufnr }) + vim.api.nvim_set_option_value("buftype", "nofile", { buf = new_bufnr }) + + return new_bufnr +end + +---Wrapper around `vim.api.nvim_buf_set_lines` +---@param bufnr number The target buffer +---@param block string[] The list of lines to write +---@param newline boolean? Add a newline to the end, defaults to `false` +---@see vim.api.nvim_buf_set_lines +function result.write_block(bufnr, block, newline) + newline = newline or false + + local content = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local first_line = false + + if #content == 1 and content[1] == "" then + first_line = true + end + + vim.api.nvim_buf_set_lines(bufnr, first_line and 0 or -1, -1, false, block) + + if newline then + vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, { "" }) + end +end + +function result.display_buf(bufnr) + local is_result_displayed = false + + for _, id in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_win_get_buf(id) == bufnr then + is_result_displayed = true + break + end + end + + if not is_result_displayed then + local cmd = "vert sb" + + local split_behavior = _G._rest_nvim.result.split + if split_behavior.horizontal then + cmd = "sb" + elseif split_behavior.in_place then + cmd = "bel " .. cmd + end + + if split_behavior.stay_in_current_window_after_split then + vim.cmd(cmd .. bufnr .. " | wincmd p") + else + vim.cmd(cmd .. bufnr) + end + + -- Set unmodifiable state + vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) + end + + move_cursor(bufnr, 1, 1) +end + +---Write request results in the given buffer and display it +---@param bufnr number The target buffer +---@ +function result.write_res(bufnr, res) + local headers = vim.tbl_filter(function(header) + if header ~= "" then + return header + ---@diagnostic disable-next-line missing-return + end + end, vim.split(res.headers, "\n")) + + result.write_block(bufnr, { res.method .. " " .. res.url }, true) + result.write_block(bufnr, headers, true) + -- TODO: write request body + result.write_block(bufnr, res.statistics, false) + + result.display_buf(bufnr) +end + +return result From 5a5ee4fa199a24c3464297b1e8606e56c2f81289 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 21:35:44 -0400 Subject: [PATCH 040/159] feat(client.curl): also return the used HTTP method --- lua/rest-nvim/client/curl.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index d280d37b..14b30f90 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -135,6 +135,7 @@ function client.request(request) end ret.url = req:getinfo_effective_url() + ret.method = req:getinfo_effective_method() ret.headers = table.concat(res_headers):gsub("\r", "") ret.result = table.concat(res_result) else From 8f7f7a0162ccedfc68d9b6ff05f1feaa3e63237d Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 21:36:38 -0400 Subject: [PATCH 041/159] feat(functions): refactor `nio` code to properly return the request results table, open results buffer --- lua/rest-nvim/functions.lua | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index 6a0b5b2c..48340065 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -11,13 +11,13 @@ local functions = {} local nio = require("nio") local utils = require("rest-nvim.utils") local parser = require("rest-nvim.parser") +local result = require("rest-nvim.result") ---Execute or `preview` one or several HTTP requests depending on given `scope` ---and return request(s) results in a table that will be used to render results ---in a buffer. ---@param scope string Defines the request execution scope. Can be: `last`, `cursor` (default) or `document` ---@param preview boolean Whether execute the request or just preview the command that is going to be ran. Default is `false` ----@return table Request results (HTTP client output) function functions.exec(scope, preview) vim.validate({ scope = { scope, "string" }, @@ -49,18 +49,17 @@ function functions.exec(scope, preview) -- TODO: implement `document` scope. -- -- NOTE: The `document` scope may require some parser adjustments + local req_results = {} + if scope == "cursor" then local req = parser.parse( ---@diagnostic disable-next-line param-type-mismatch parser.look_behind_until(parser.get_node_at_cursor(), "request") ) - local sleep = nio.wrap(function(ms, cb) - vim.defer_fn(cb, ms) - end, 2) - nio.run(function() - sleep(10, client.request(req)) - end) + req_results = nio.run(function() + return client.request(req) + end):wait() ---Last HTTP request made by the user ---@type Request @@ -72,14 +71,14 @@ function functions.exec(scope, preview) ---@diagnostic disable-next-line need-check-nil logger:error("Rest run last: A previously made request was not found to be executed again") else - local sleep = nio.wrap(function(ms, cb) - vim.defer_fn(cb, ms) - end, 2) - nio.run(function() - sleep(10, client.request(req)) - end) + req_results = nio.run(function() + return client.request(req) + end):wait() end end + + local result_buf = result.get_or_create_buf() + result.write_res(result_buf, req_results) end ---Manage the environment file that is currently in use while running requests From 36915baff6169b50a3320a37335b8764bbc0e6e4 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 23:46:46 -0400 Subject: [PATCH 042/159] chore(client.curl): remove newline --- lua/rest-nvim/client/curl.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 14b30f90..64b5ef23 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -124,7 +124,6 @@ function client.request(request) req:setopt_headerfunction(table.insert, res_headers) local ret = {} - local ok, err = req:perform() if ok then -- Get request statistics if they are enabled From 78bf6acaa4f493f865386e2135ca903b625b5454 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 23:47:46 -0400 Subject: [PATCH 043/159] feat(result): write request result body to results buffer, improve docs and fix some issues --- lua/rest-nvim/result.lua | 95 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua index 32d7a8ee..ad842ac8 100644 --- a/lua/rest-nvim/result.lua +++ b/lua/rest-nvim/result.lua @@ -8,6 +8,8 @@ local result = {} +local nio = require("nio") + ---Move the cursor to the desired position in the given buffer ---@param bufnr number Buffer handler number ---@param row number The desired line @@ -29,7 +31,7 @@ function result.get_or_create_buf() -- Check if the file is already loaded in the buffer local existing_buf, bufnr = false, nil for _, id in ipairs(vim.api.nvim_list_bufs()) do - if vim.api.nvim_buf_get_name(id) == tmp_name then + if vim.api.nvim_buf_get_name(id):find(tmp_name) then existing_buf = true bufnr = id end @@ -115,18 +117,103 @@ end ---Write request results in the given buffer and display it ---@param bufnr number The target buffer ----@ +---@param res table Request results function result.write_res(bufnr, res) + local logger = _G._rest_nvim.logger + local headers = vim.tbl_filter(function(header) if header ~= "" then return header - ---@diagnostic disable-next-line missing-return + ---@diagnostic disable-next-line missing-return end end, vim.split(res.headers, "\n")) + -- METHOD http://foo.bar/api/endpoint result.write_block(bufnr, { res.method .. " " .. res.url }, true) + -- ...... + -- Content-Type: application/json + -- ...... result.write_block(bufnr, headers, true) - -- TODO: write request body + + nio.run(function() + ---@type string + local res_type + for _, header in ipairs(headers) do + if header:find("Content%-Type") then + local content_type = vim.trim(vim.split(header, ":")[2]) + -- We need to remove the leading charset if we are getting a JSON + res_type = vim.split(content_type, "/")[2]:gsub(";.*", "") + end + end + + -- Do not try to format binary content + if res_type == "octet-stream" then + -- That empty value is a newline + local body = { "", "Binary answer" } + result.write_block(bufnr, body, true) + else + local formatters = _G._rest_nvim.result.behavior.formatters + local filetypes = vim.tbl_keys(formatters) + + -- If there is a formatter for the content type filetype then + -- format the request result body, otherwise return it as we got it + if vim.tbl_contains(filetypes, res_type) then + local fmt = formatters[res_type] + if type(fmt) == "function" then + local ok, out = pcall(fmt, res.result) + if ok and out then + res.result = out + else + ---@diagnostic disable-next-line need-check-nil + logger:error("Error calling formatter on response body:\n" .. out) + end + elseif vim.fn.executable(fmt) == 1 then + local stdout = vim.fn.system(fmt, res.result):gsub("\n$", "") + -- Check if formatter ran successfully + if vim.v.shell_error == 0 then + res.result = stdout + else + ---@diagnostic disable-next-line need-check-nil + logger:error( + "Error running formatter '" .. fmt .. "' on response body:\n" .. stdout + ) + end + end + else + ---@diagnostic disable-next-line need-check-nil + logger:info( + "Could not find a formatter for the body type " .. res_type .. " returned in the request, the results will not be formatted" + ) + end + local body = vim.split(res.result, "\n") + table.insert(body, 1, "") + table.insert(body, 2, "#+RES") + table.insert(body, "#+END") + result.write_block(bufnr, body, true) + + -- add syntax highlights for response + vim.api.nvim_buf_call(bufnr, function() + local syntax_file = vim.fn.expand(string.format("$VIMRUNTIME/syntax/%s.vim", res_type)) + if vim.fn.filereadable(syntax_file) == 1 then + vim.cmd(string.gsub( + [[ + if exists("b:current_syntax") + unlet b:current_syntax + endif + syn include @%s syntax/%s.vim + syn region %sBody matchgroup=Comment start=+\v^#\+RES$+ end=+\v^#\+END$+ contains=@%s + + let b:current_syntax = "httpResult" + ]], + "%%s", + res_type + )) + end + end) + end + end) + + table.insert(res.statistics, 1, "") result.write_block(bufnr, res.statistics, false) result.display_buf(bufnr) From 80f15e98bfca1b25c455deaf872971fa6e22706d Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 23:53:03 -0400 Subject: [PATCH 044/159] feat(result): disable concealing --- lua/rest-nvim/result.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua index ad842ac8..c6f6ddb5 100644 --- a/lua/rest-nvim/result.lua +++ b/lua/rest-nvim/result.lua @@ -85,6 +85,7 @@ end function result.display_buf(bufnr) local is_result_displayed = false + -- Check if the results buffer is already displayed for _, id in ipairs(vim.api.nvim_list_wins()) do if vim.api.nvim_win_get_buf(id) == bufnr then is_result_displayed = true @@ -108,6 +109,17 @@ function result.display_buf(bufnr) vim.cmd(cmd .. bufnr) end + -- Get the ID of the window that contains the results buffer + local winnr + for _, id in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_win_get_buf(id) == bufnr then + winnr = id + end + end + + -- Disable concealing for the results buffer window + vim.api.nvim_set_option_value("conceallevel", 0, { win = winnr }) + -- Set unmodifiable state vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) end From 7a418af8d079f47771101d8cbf04c2f4400624b2 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 23:55:34 -0400 Subject: [PATCH 045/159] feat(luarocks/rockspec): update plugin dependencies --- .github/workflows/luarocks.yml | 7 ++++++- rest.nvim-scm-2.rockspec | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/luarocks.yml b/.github/workflows/luarocks.yml index 8f19fa52..6639d245 100644 --- a/.github/workflows/luarocks.yml +++ b/.github/workflows/luarocks.yml @@ -12,6 +12,11 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Luarocks Upload - uses: mrcjkb/luarocks-tag-release@master + uses: mrcjkb/luarocks-tag-release@v5 + with: + dependencies: | + nvim-nio + Lua-cURL + mimetypes env: LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} diff --git a/rest.nvim-scm-2.rockspec b/rest.nvim-scm-2.rockspec index bf9f0242..932c79cb 100644 --- a/rest.nvim-scm-2.rockspec +++ b/rest.nvim-scm-2.rockspec @@ -16,7 +16,8 @@ description = { dependencies = { "lua >= 5.1, < 5.4", "nvim-nio", - "Lua-cURL" + "Lua-cURL", + "mimetypes", } source = { From 1360f1c66d2c0aa0fec0c5dc46928dba2888836e Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 30 Jan 2024 23:58:08 -0400 Subject: [PATCH 046/159] chore: format source code --- lua/rest-nvim/client/curl.lua | 9 ++++++--- lua/rest-nvim/commands.lua | 14 ++++++-------- lua/rest-nvim/config/init.lua | 4 +--- lua/rest-nvim/functions.lua | 24 ++++++++++++------------ lua/rest-nvim/parser.lua | 24 +++++++++++++++++++----- lua/rest-nvim/result.lua | 10 +++++----- 6 files changed, 49 insertions(+), 36 deletions(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 64b5ef23..c77a3f06 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -30,7 +30,10 @@ local function get_stats(req, statistics_tbl) local curl_info = curl["INFO_" .. stat_:upper()] if not curl_info then ---@diagnostic disable-next-line need-check-nil - logger:error("The cURL request stat field '" .. stat_ "' was not found.\nPlease take a look at: https://curl.se/libcurl/c/curl_easy_getinfo.html") + logger:error( + "The cURL request stat field '" + .. stat_("' was not found.\nPlease take a look at: https://curl.se/libcurl/c/curl_easy_getinfo.html") + ) return end local stat_info = req_:getinfo(curl_info) @@ -45,7 +48,7 @@ local function get_stats(req, statistics_tbl) local stat_title, stat_info for _, stat in pairs(statistics_tbl) do - for k,v in pairs(stat) do + for k, v in pairs(stat) do if type(k) == "string" and k == "title" then stat_title = v end @@ -112,7 +115,7 @@ function client.request(request) [request.body.name and request.body.name or "body"] = { file = request.body.path, type = body_mimetype, - } + }, } req:post(post_data) end diff --git a/lua/rest-nvim/commands.lua b/lua/rest-nvim/commands.lua index eaa719d3..4b3b8c18 100644 --- a/lua/rest-nvim/commands.lua +++ b/lua/rest-nvim/commands.lua @@ -43,7 +43,7 @@ local rest_command_tbl = { local match = vim.tbl_filter(function(scope) if string.find(scope, "^" .. args) then return scope - ---@diagnostic disable-next-line missing-return + ---@diagnostic disable-next-line missing-return end end, scopes) @@ -58,7 +58,7 @@ local rest_command_tbl = { preview = { impl = function(_) functions.exec("cursor", true) - end + end, }, env = { impl = function(args) @@ -72,9 +72,7 @@ local rest_command_tbl = { -- If there was only one argument and it is `set` then raise an error because we are also expecting for the env file path if #args == 1 and args[1] == "set" then ---@diagnostic disable-next-line need-check-nil - logger:error( - "Not enough arguments were passed to the 'env' command: 2 argument were expected, 1 was passed" - ) + logger:error("Not enough arguments were passed to the 'env' command: 2 argument were expected, 1 was passed") return end -- We do not need too many arguments here, complain about it please! @@ -116,13 +114,13 @@ local rest_command_tbl = { local match = vim.tbl_filter(function(action) if string.find(action, "^" .. args) then return action - ---@diagnostic disable-next-line missing-return + ---@diagnostic disable-next-line missing-return end end, actions) return match end, - } + }, } local function rest(opts) @@ -160,7 +158,7 @@ function commands.init(bufnr) return vim.tbl_filter(function(cmd) if string.find(cmd, "^" .. arg_lead) then return cmd - ---@diagnostic disable-next-line missing-return + ---@diagnostic disable-next-line missing-return end end, rest_commands) end diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 63285bd0..4acc093d 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -159,9 +159,7 @@ function config.set(user_configs) end if #conf.debug_info.unrecognized_configs > 0 then - conf.logger:warn( - "Unrecognized configs found in setup: " .. vim.inspect(config.debug_info.unrecognized_configs) - ) + conf.logger:warn("Unrecognized configs found in setup: " .. vim.inspect(config.debug_info.unrecognized_configs)) end return conf diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index 48340065..3652d282 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -40,9 +40,7 @@ function functions.exec(scope, preview) -- Raise an error if an invalid scope has been provided if not vim.tbl_contains({ "last", "cursor", "document" }, scope) then ---@diagnostic disable-next-line need-check-nil - logger:error( - "Invalid scope '" .. scope .. "' provided to the 'exec' function" - ) + logger:error("Invalid scope '" .. scope .. "' provided to the 'exec' function") return {} end @@ -57,9 +55,11 @@ function functions.exec(scope, preview) parser.look_behind_until(parser.get_node_at_cursor(), "request") ) - req_results = nio.run(function() - return client.request(req) - end):wait() + req_results = nio + .run(function() + return client.request(req) + end) + :wait() ---Last HTTP request made by the user ---@type Request @@ -71,9 +71,11 @@ function functions.exec(scope, preview) ---@diagnostic disable-next-line need-check-nil logger:error("Rest run last: A previously made request was not found to be executed again") else - req_results = nio.run(function() - return client.request(req) - end):wait() + req_results = nio + .run(function() + return client.request(req) + end) + :wait() end end @@ -100,9 +102,7 @@ function functions.env(action, path) if not vim.tbl_contains({ "set", "show" }, action) then ---@diagnostic disable-next-line need-check-nil - logger:error( - "Invalid action '" .. action .. "' provided to the 'env' function" - ) + logger:error("Invalid action '" .. action .. "' provided to the 'env' function") return end diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser.lua index d33fcb53..1ca410db 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser.lua @@ -41,7 +41,9 @@ local function check_syntax_error(node) end ---@diagnostic disable-next-line need-check-nil - logger:error("The tree-sitter node at the range " .. create_node_range_str(node) .. " has a syntax error and cannot be parsed") + logger:error( + "The tree-sitter node at the range " .. create_node_range_str(node) .. " has a syntax error and cannot be parsed" + ) return true end @@ -160,11 +162,17 @@ local function parse_variables(node, tree, text, variables) -- If the variable was not found in the document then fallback to the shell environment if not variable then ---@diagnostic disable-next-line need-check-nil - logger:debug("The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ...") + logger:debug( + "The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ..." + ) local env_var = vim.env[variable_name] if not env_var then ---@diagnostic disable-next-line need-check-nil - logger:warn("The variable '" .. variable_name .. "' was not found in the document or in the environment. Returning the string as received ...") + logger:warn( + "The variable '" + .. variable_name + .. "' was not found in the document or in the environment. Returning the string as received ..." + ) return text end variable_value = env_var @@ -249,11 +257,17 @@ local function traverse_body(tbl, variables) -- If the variable was not found in the document then fallback to the shell environment if not variable then ---@diagnostic disable-next-line need-check-nil - logger:debug("The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ...") + logger:debug( + "The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ..." + ) local env_var = vim.env[variable_name] if not env_var then ---@diagnostic disable-next-line need-check-nil - logger:warn("The variable '" .. variable_name .. "' was not found in the document or in the environment. Returning the string as received ...") + logger:warn( + "The variable '" + .. variable_name + .. "' was not found in the document or in the environment. Returning the string as received ..." + ) return str end variable_value = env_var diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua index c6f6ddb5..ea1acbfa 100644 --- a/lua/rest-nvim/result.lua +++ b/lua/rest-nvim/result.lua @@ -136,7 +136,7 @@ function result.write_res(bufnr, res) local headers = vim.tbl_filter(function(header) if header ~= "" then return header - ---@diagnostic disable-next-line missing-return + ---@diagnostic disable-next-line missing-return end end, vim.split(res.headers, "\n")) @@ -186,15 +186,15 @@ function result.write_res(bufnr, res) res.result = stdout else ---@diagnostic disable-next-line need-check-nil - logger:error( - "Error running formatter '" .. fmt .. "' on response body:\n" .. stdout - ) + logger:error("Error running formatter '" .. fmt .. "' on response body:\n" .. stdout) end end else ---@diagnostic disable-next-line need-check-nil logger:info( - "Could not find a formatter for the body type " .. res_type .. " returned in the request, the results will not be formatted" + "Could not find a formatter for the body type " + .. res_type + .. " returned in the request, the results will not be formatted" ) end local body = vim.split(res.result, "\n") From e1561fec27f24af0b7e76da954756bb579cf8ae0 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 00:08:16 -0400 Subject: [PATCH 047/159] ref!: remove `yank_dry_run` configuration option and `preview` command It's not really something I like to do, but with the new implementation we have with `cURL` this is no longer possible. Before we used the `curl` binary wrapper that had `plenary.nvim` which allowed us to shape the command and interact with it, however, now we use native Lua bindings for `libcurl` so we interact directly with cURL without having to use the `curl` binary --- lua/rest-nvim/commands.lua | 9 +-------- lua/rest-nvim/config/check.lua | 1 - lua/rest-nvim/config/init.lua | 2 -- lua/rest-nvim/functions.lua | 6 ++---- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/lua/rest-nvim/commands.lua b/lua/rest-nvim/commands.lua index 4b3b8c18..8bdd72ca 100644 --- a/lua/rest-nvim/commands.lua +++ b/lua/rest-nvim/commands.lua @@ -13,8 +13,6 @@ --- last Re-run the last executed request, alias to `run last` --- to retain backwards compatibility with the old keybinds --- layout. ---- preview Preview the cURL command that is going to be ran while ---- executing the request (this does NOT run the request). --- ---@brief ]] @@ -55,11 +53,6 @@ local rest_command_tbl = { functions.exec("last", false) end, }, - preview = { - impl = function(_) - functions.exec("cursor", true) - end, - }, env = { impl = function(args) local logger = _G._rest_nvim.logger @@ -147,7 +140,7 @@ end function commands.init(bufnr) vim.api.nvim_buf_create_user_command(bufnr, "Rest", rest, { nargs = "+", - desc = "Run or preview your HTTP requests", + desc = "Run your HTTP requests", complete = function(arg_lead, cmdline, _) local rest_commands = vim.tbl_keys(rest_command_tbl) local subcmd, subcmd_arg_lead = cmdline:match("^Rest*%s(%S+)%s(.*)$") diff --git a/lua/rest-nvim/config/check.lua b/lua/rest-nvim/config/check.lua index b51ae60a..65fcd88a 100644 --- a/lua/rest-nvim/config/check.lua +++ b/lua/rest-nvim/config/check.lua @@ -26,7 +26,6 @@ function check.validate(cfg) client = { cfg.client, "string" }, env_file = { cfg.env_file, "string" }, encode_url = { cfg.encode_url, "boolean" }, - yank_dry_run = { cfg.yank_dry_run, "boolean" }, skip_ssl_verification = { cfg.skip_ssl_verification, "boolean" }, custom_dynamic_variables = { cfg.custom_dynamic_variables, "table" }, keybinds = { cfg.keybinds, "table" }, diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 4acc093d..6f2a0425 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -53,7 +53,6 @@ local logger = require("rest-nvim.logger") ---@field client string The HTTP client to be used when running requests, default is `"curl"` ---@field env_file string Environment variables file to be used for the request variables in the document ---@field encode_url boolean Encode URL before making request ----@field yank_dry_run boolean Whether to copy the request preview (cURL command) to the clipboard ---@field skip_ssl_verification boolean Skip SSL verification, useful for unknown certificates ---@field custom_dynamic_variables { [string]: fun(): string }[] Table of custom dynamic variables ---@field logs RestConfigLogs Logging system configuration @@ -69,7 +68,6 @@ local default_config = { client = "curl", env_file = ".env", encode_url = true, - yank_dry_run = true, skip_ssl_verification = false, custom_dynamic_variables = {}, logs = { diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index 3652d282..424ec6a6 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -13,15 +13,13 @@ local utils = require("rest-nvim.utils") local parser = require("rest-nvim.parser") local result = require("rest-nvim.result") ----Execute or `preview` one or several HTTP requests depending on given `scope` +---Execute one or several HTTP requests depending on given `scope` ---and return request(s) results in a table that will be used to render results ---in a buffer. ---@param scope string Defines the request execution scope. Can be: `last`, `cursor` (default) or `document` ----@param preview boolean Whether execute the request or just preview the command that is going to be ran. Default is `false` -function functions.exec(scope, preview) +function functions.exec(scope) vim.validate({ scope = { scope, "string" }, - preview = { preview, "boolean" }, }) local logger = _G._rest_nvim.logger From 05794ffe9b6676f345d8547e90f1a9bdfcb29543 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 00:13:49 -0400 Subject: [PATCH 048/159] feat(luarocks/rockspec): add `xml2lua` dependency --- .github/workflows/luarocks.yml | 1 + rest.nvim-scm-2.rockspec | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/luarocks.yml b/.github/workflows/luarocks.yml index 6639d245..c74b01df 100644 --- a/.github/workflows/luarocks.yml +++ b/.github/workflows/luarocks.yml @@ -18,5 +18,6 @@ jobs: nvim-nio Lua-cURL mimetypes + xml2lua env: LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} diff --git a/rest.nvim-scm-2.rockspec b/rest.nvim-scm-2.rockspec index 932c79cb..b248ae86 100644 --- a/rest.nvim-scm-2.rockspec +++ b/rest.nvim-scm-2.rockspec @@ -18,6 +18,7 @@ dependencies = { "nvim-nio", "Lua-cURL", "mimetypes", + "xml2lua", } source = { From 779ac3e6c2d7853c53f22717c10c5f28e3ee7874 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 01:25:54 -0400 Subject: [PATCH 049/159] ref(syntax): use `tree-sitter` highlighting groups --- syntax/httpResult.vim | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/syntax/httpResult.vim b/syntax/httpResult.vim index fe8271d9..0c296fea 100644 --- a/syntax/httpResult.vim +++ b/syntax/httpResult.vim @@ -18,18 +18,18 @@ syn match httpResult500 /5\d\d.*$/ contained syn region httpResultString start=/\vr?"/ end=/\v"/ syn match httpResultNumber /\v[ =]@1<=[0-9]*.?[0-9]+[ ,;&\n]/he=e-1 -hi link httpResultComment Comment -hi link httpResultMethod Type -hi link httpResultPath Link -hi link httpResultField Identifier -hi link httpResultDateField Identifier -hi link httpResultDate String -hi link httpResultString String -hi link httpResultNumber Number -hi link httpResultHeader Type -hi link httpResult200 String -hi link httpResult300 Function -hi link httpResult400 Number -hi link httpResult500 Number +hi link httpResultComment @comment +hi link httpResultMethod @type +hi link httpResultPath @text.uri +hi link httpResultField @constant +hi link httpResultDateField @constant +hi link httpResultDate @attribute +hi link httpResultString @string +hi link httpResultNumber @number +hi link httpResultHeader @constant +hi link httpResult200 Msg +hi link httpResult300 MoreMsg +hi link httpResult400 WarningMsg +hi link httpResult500 ErrorMsg let b:current_syntax = "httpResult" From 90e32c910d34b26fb09944bcf56f9b7fc26f5e3d Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 02:11:01 -0400 Subject: [PATCH 050/159] feat(parser): parse dynamic variables, add `parser.dynamic_vars` module --- lua/rest-nvim/parser/dynamic_vars.lua | 61 ++++++++++ lua/rest-nvim/{parser.lua => parser/init.lua} | 104 +++++++++++------- 2 files changed, 125 insertions(+), 40 deletions(-) create mode 100644 lua/rest-nvim/parser/dynamic_vars.lua rename lua/rest-nvim/{parser.lua => parser/init.lua} (82%) diff --git a/lua/rest-nvim/parser/dynamic_vars.lua b/lua/rest-nvim/parser/dynamic_vars.lua new file mode 100644 index 00000000..377862aa --- /dev/null +++ b/lua/rest-nvim/parser/dynamic_vars.lua @@ -0,0 +1,61 @@ +---@mod rest-nvim.parser.dynamic_vars rest.nvim parsing module dynamic variables +--- +---@brief [[ +--- +--- rest.nvim dynamic variables +--- +---@brief ]] + +local dynamic_vars = {} + +local random = math.random +math.randomseed(os.time()) + +---Generate a random uuid +---@return string +local function uuid() + local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" + return string.gsub(template, "[xy]", function(c) + local v = (c == "x") and random(0, 0xf) or random(8, 0xb) + return string.format("%x", v) + end) +end + +---Retrieve all dynamic variables from both rest.nvim and the ones declared by +---the user on his configuration +---@return { [string]: fun():string }[] +function dynamic_vars.retrieve_all() + local user_variables = _G._rest_nvim.custom_dynamic_variables or {} + local rest_variables = { + ["$uuid"] = uuid, + ["$date"] = function() + return os.date("%Y-%m-%d") + end, + ["$timestamp"] = os.time, + ["$randomInt"] = function() + return math.random(0, 1000) + end, + } + + return vim.tbl_deep_extend("force", rest_variables, user_variables) +end + +---Look for a dynamic variable and evaluate it +---@param name string The dynamic variable name +---@return string|nil +function dynamic_vars.read(name) + local logger = _G._rest_nvim.logger + + local vars = dynamic_vars.retrieve_all() + if not vim.tbl_contains(vim.tbl_keys(vars), name) then + ---@diagnostic disable-next-line need-check-nil + logger:error( + "The dynamic variable '" .. name .. "' was not found. Maybe it's written wrong or doesn't exist?" + ) + return nil + end + + return vars[name]() +end + +return dynamic_vars diff --git a/lua/rest-nvim/parser.lua b/lua/rest-nvim/parser/init.lua similarity index 82% rename from lua/rest-nvim/parser.lua rename to lua/rest-nvim/parser/init.lua index 1ca410db..3f1943b4 100644 --- a/lua/rest-nvim/parser.lua +++ b/lua/rest-nvim/parser/init.lua @@ -10,8 +10,12 @@ local parser = {} +local xml2lua = require("xml2lua") + +local dynamic_vars = require("rest-nvim.parser.dynamic_vars") + -- TODO: parse and evaluate `(script_variable)` request node --- TODO: parse and evaluate dynamic variables too +-- TODO: parse and evaluate environment files too ---@alias NodesList { [string]: TSNode }[] ---@alias Variables { [string]: { type_: string, value: string|number|boolean } }[] @@ -158,7 +162,16 @@ local function parse_variables(node, tree, text, variables) for _, nod, _ in variable_query:iter_captures(node:root(), tree) do local variable_name = assert(get_node_text(nod, tree)) local variable_value - local variable = variables[variable_name] + + -- If the variable name contains a `$` symbol then try to parse it as a dynamic variable + if variable_name:find("^%$") then + variable_value = dynamic_vars.read(variable_name) + if variable_value then + return variable_value + end + end + + local variable = vars[variable_name] -- If the variable was not found in the document then fallback to the shell environment if not variable then ---@diagnostic disable-next-line need-check-nil @@ -239,60 +252,70 @@ end ---@param tbl table Request body ---@return table local function traverse_body(tbl, variables) - for k, v in pairs(tbl) do - if type(v) == "table" then - traverse_body(v, variables) + ---Expand a variable in the given string + ---@param str string String where the variables are going to be expanded + ---@param vars Variables HTTP document variables list + ---@return string|number|boolean + local function expand_variable(str, vars) + local logger = _G._rest_nvim.logger + + local variable_name = str:gsub("{{[%s]?", ""):gsub("[%s]?}}", ""):match(".*") + local variable_value + + -- If the variable name contains a `$` symbol then try to parse it as a dynamic variable + if variable_name:find("^%$") then + variable_value = dynamic_vars.read(variable_name) + if variable_value then + return variable_value + end end - ---Expand a variable in the given string - ---@param str string String where the variables are going to be expanded - ---@param vars Variables HTTP document variables list - ---@return string|number|boolean - local function expand_variable(str, vars) - local logger = _G._rest_nvim.logger - - local variable_name = str:gsub("{{[%s]?", ""):gsub("[%s]?}}", ""):match(".*") - local variable_value - local variable = vars[variable_name] - -- If the variable was not found in the document then fallback to the shell environment - if not variable then + local variable = vars[variable_name] + -- If the variable was not found in the document then fallback to the shell environment + if not variable then + ---@diagnostic disable-next-line need-check-nil + logger:debug( + "The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ..." + ) + local env_var = vim.env[variable_name] + if not env_var then ---@diagnostic disable-next-line need-check-nil - logger:debug( - "The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ..." + logger:warn( + "The variable '" + .. variable_name + .. "' was not found in the document or in the environment. Returning the string as received ..." ) - local env_var = vim.env[variable_name] - if not env_var then - ---@diagnostic disable-next-line need-check-nil - logger:warn( - "The variable '" - .. variable_name - .. "' was not found in the document or in the environment. Returning the string as received ..." - ) - return str - end - variable_value = env_var - else - variable_value = variable.value - if variable.type_ == "string" then - ---@cast variable_value string - variable_value = variable_value:gsub('"', "") - end + return str + end + variable_value = env_var + else + variable_value = variable.value + if variable.type_ == "string" then + ---@cast variable_value string + variable_value = variable_value:gsub('"', "") end - ---@cast variable_value string|number|boolean - return variable_value + end + ---@cast variable_value string|number|boolean + return variable_value + end + + for k, v in pairs(tbl) do + if type(v) == "table" then + traverse_body(v, variables) end - if type(k) == "string" and k:find("{{[%s]?.*[%s?]}}") then + if type(k) == "string" and k:find("{{[%s]?.*[%s]?}}") then local variable_value = expand_variable(k, variables) local key_value = tbl[k] tbl[k] = nil tbl[variable_value] = key_value end - if type(v) == "string" and v:find("{{[%s]?.*[%s?]}}") then + if type(v) == "string" and v:find("{{[%s]?.*[%s]?}}") then local variable_value = expand_variable(v, variables) tbl[k] = variable_value end end + return tbl end @@ -303,6 +326,7 @@ end function parser.parse_body(children_nodes, variables) local body = {} + -- TODO: handle GraphQL bodies by using a graphql parser library from luarocks for node_type, node in pairs(children_nodes) do -- TODO: handle XML bodies by using xml2lua library from luarocks if node_type == "json_body" then From 6905450d1411efc7c4f3593984e0c80108586fdc Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 02:17:41 -0400 Subject: [PATCH 051/159] ref!: move `:Rest env set` completion logic into its own function `functions.find_env_files()` --- lua/rest-nvim/commands.lua | 10 +--------- lua/rest-nvim/functions.lua | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lua/rest-nvim/commands.lua b/lua/rest-nvim/commands.lua index 8bdd72ca..71107b0e 100644 --- a/lua/rest-nvim/commands.lua +++ b/lua/rest-nvim/commands.lua @@ -93,15 +93,7 @@ local rest_command_tbl = { -- If the completion arguments is a table and `set` is the desired action then -- return a list of files in the current working directory for completion if type(args) == "table" and args[1]:match("set") then - -- We are currently looking for any ".*env*" file, e.g. ".env", ".env.json" - -- - -- This algorithm can be improved later on to search from a parent directory if the desired environment file - -- is somewhere else but in the current working directory. - local files = vim.fs.find(function(name, path) - return name:match(".*env.*$") - end, { limit = math.huge, type = "file", path = "./" }) - - return files + return functions.find_env_files() end local match = vim.tbl_filter(function(action) diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index 424ec6a6..b53f6afb 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -81,6 +81,20 @@ function functions.exec(scope) result.write_res(result_buf, req_results) end +---Find a list of environment files starting from the current directory +---@return string[] +function functions.find_env_files() + -- We are currently looking for any ".*env*" file, e.g. ".env", ".env.json" + -- + -- This algorithm can be improved later on to search from a parent directory if the desired environment file + -- is somewhere else but in the current working directory. + local files = vim.fs.find(function(name, path) + return name:match(".*env.*$") + end, { limit = math.huge, type = "file", path = "./" }) + + return files +end + ---Manage the environment file that is currently in use while running requests --- ---If you choose to `set` the environment, you must provide a `path` to the environment file. From 7c396bf7f58eb2c9d37ffc60262e3cac95337a25 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 02:55:55 -0400 Subject: [PATCH 052/159] feat(parser): add `env_vars` module to parse environment variables files --- lua/rest-nvim/parser/env_vars.lua | 64 +++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 lua/rest-nvim/parser/env_vars.lua diff --git a/lua/rest-nvim/parser/env_vars.lua b/lua/rest-nvim/parser/env_vars.lua new file mode 100644 index 00000000..06e86f62 --- /dev/null +++ b/lua/rest-nvim/parser/env_vars.lua @@ -0,0 +1,64 @@ +---@mod rest-nvim.parser.env_vars rest.nvim parsing module environment variables +--- +---@brief [[ +--- +--- rest.nvim environment variables +--- +---@brief ]] + +local env_vars = {} + +local utils = require("rest-nvim.utils") + +-- NOTE: vim.loop has been renamed to vim.uv in Neovim >= 0.10 and will be removed later +local uv = vim.uv or vim.loop + +---Get the environment variables file filetype +---@param env_file string The environment file path +---@return string|nil +local function get_env_filetype(env_file) + local ext = vim.fn.fnamemodify(env_file, ":e") + return ext == "" and nil or ext +end + +---Read the environment variables file from the rest.nvim configuration +---and store all the environment variables in the `vim.env` metatable +---@see vim.env +function env_vars.read_file() + local path = _G._rest_nvim.env_file + local logger = _G._rest_nvim.logger + + if utils.file_exists(path) then + local env_ext = get_env_filetype(path) + local file_contents = utils.read_file(path) + + local variables = {} + if env_ext == "json" then + variables = vim.json.decode(file_contents) + else + local vars_tbl = vim.split(file_contents, "\n") + table.remove(vars_tbl, #vars_tbl) + for _, var in ipairs(vars_tbl) do + local variable = vim.split(var, "=") + local variable_name = variable[1] + -- In case some weirdo adds a `=` character to his ENV value + if #variable > 2 then + table.remove(variable, 1) + variable_value = table.concat(variable, "=") + else + variable_value = variable[2] + end + variables[variable_name] = variable_value + end + end + + for k, v in pairs(variables) do + vim.env[k] = v + end + else + ---@diagnostic disable-next-line need-check-nil + logger:error("Current environment file '" .. path .. "' was not found in the current working directory") + end +end + +return env_vars From 199abb8645b07664dc99201849199185e66fe169 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 02:56:26 -0400 Subject: [PATCH 053/159] feat(utils): add `read_file(path)` function --- lua/rest-nvim/utils.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lua/rest-nvim/utils.lua b/lua/rest-nvim/utils.lua index fd64eef4..c6c72952 100644 --- a/lua/rest-nvim/utils.lua +++ b/lua/rest-nvim/utils.lua @@ -24,6 +24,31 @@ function utils.file_exists(path) return false end +---Read a file if it exists +---@param path string file path +---@return string +function utils.read_file(path) + local logger = _G._rest_nvim.logger + + ---@cast content string + local content + if utils.file_exists(path) then + local file = uv.fs_open(path, "r", 438) + ---@cast file number + local stat = uv.fs_fstat(file) + ---@cast stat uv.aliases.fs_stat_table + content = uv.fs_read(file, stat.size, 0) + ---@cast content string + uv.fs_close(file) + else + ---@diagnostic disable-next-line need-check-nil + logger:error("Failed to read file '" .. path .. "'") + return "" + end + + return content +end + --- Default transformers for statistics local transform = { ---Transform `time` into a readable typed time (e.g. 200ms) From f26a1532c0b333a791fa0d923a4f9be126188867 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 02:57:12 -0400 Subject: [PATCH 054/159] feat(parser): also parse environment variables from `.env` files --- lua/rest-nvim/parser/init.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/rest-nvim/parser/init.lua b/lua/rest-nvim/parser/init.lua index 3f1943b4..f1249a4b 100644 --- a/lua/rest-nvim/parser/init.lua +++ b/lua/rest-nvim/parser/init.lua @@ -12,10 +12,10 @@ local parser = {} local xml2lua = require("xml2lua") +local env_vars = require("rest-nvim.parser.env_vars") local dynamic_vars = require("rest-nvim.parser.dynamic_vars") -- TODO: parse and evaluate `(script_variable)` request node --- TODO: parse and evaluate environment files too ---@alias NodesList { [string]: TSNode }[] ---@alias Variables { [string]: { type_: string, value: string|number|boolean } }[] @@ -178,6 +178,7 @@ local function parse_variables(node, tree, text, variables) logger:debug( "The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ..." ) + env_vars.read_file() local env_var = vim.env[variable_name] if not env_var then ---@diagnostic disable-next-line need-check-nil @@ -277,6 +278,7 @@ local function traverse_body(tbl, variables) logger:debug( "The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ..." ) + env_vars.read_file() local env_var = vim.env[variable_name] if not env_var then ---@diagnostic disable-next-line need-check-nil From c71e394ee76416ad19c9769a549dea27f1f27c81 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 12:27:34 -0400 Subject: [PATCH 055/159] feat(client.curl): add `curl_error(code)` function for better error handling --- lua/rest-nvim/client/curl.lua | 100 +++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index c77a3f06..82c1611a 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -17,6 +17,103 @@ local utils = require("rest-nvim.utils") -- TODO: add support for submitting forms in the `client.request` function -- TODO: add support for submitting XML bodies in the `client.request` function +---Return the status code and the meaning of an curl error +---see man curl for reference +---@param code number The exit code of curl +---@return string +local function curl_error(code) + local curl_error_dictionary = { + [1] = "Unsupported protocol. This build of curl has no support for this protocol.", + [2] = "Failed to initialize.", + [3] = "URL malformed. The syntax was not correct.", + [4] = "A feature or option that was needed to perform the desired request was not enabled or was explicitly disabled at build-time." + .. "To make curl able to do this, you probably need another build of libcurl!", + [5] = "Couldn't resolve proxy. The given proxy host could not be resolved.", + [6] = "Couldn't resolve host. The given remote host was not resolved.", + [7] = "Failed to connect to host.", + [8] = "Weird server reply. The server sent data curl couldn't parse.", + [9] = "FTP access denied. The server denied login or denied access to the particular resource or directory you wanted to reach. Most often you tried to change to a directory that doesn't exist on the server.", + [10] = "FTP accept failed. While waiting for the server to connect back when an active FTP session is used, an error code was sent over the control connection or similar.", + [11] = "FTP weird PASS reply. Curl couldn't parse the reply sent to the PASS request.", + [12] = "During an active FTP session while waiting for the server to connect back to curl, the timeout expired.", + [13] = "FTP weird PASV reply, Curl couldn't parse the reply sent to the PASV request.", + [14] = "FTP weird 227 format. Curl couldn't parse the 227-line the server sent.", + [15] = "FTP can't get host. Couldn't resolve the host IP we got in the 227-line.", + [16] = "HTTP/2 error. A problem was detected in the HTTP2 framing layer. This is somewhat generic and can be one out of several problems, see the error message for details.", + [17] = "FTP couldn't set binary. Couldn't change transfer method to binary.", + [18] = "Partial file. Only a part of the file was transferred.", + [19] = "FTP couldn't download/access the given file, the RETR (or similar) command failed.", + [21] = "FTP quote error. A quote command returned error from the server.", + [22] = "HTTP page not retrieved. The requested url was not found or returned another error with the HTTP error code being 400 or above. This return code only appears if -f, --fail is used.", + [23] = "Write error. Curl couldn't write data to a local filesystem or similar.", + [25] = "FTP couldn't STOR file. The server denied the STOR operation, used for FTP uploading.", + [26] = "Read error. Various reading problems.", + [27] = "Out of memory. A memory allocation request failed.", + [28] = "Operation timeout. The specified time-out period was reached according to the conditions.", + [30] = "FTP PORT failed. The PORT command failed. Not all FTP servers support the PORT command, try doing a transfer using PASV instead!", + [31] = "FTP couldn't use REST. The REST command failed. This command is used for resumed FTP transfers.", + [33] = 'HTTP range error. The range "command" didn\'t work.', + [34] = "HTTP post error. Internal post-request generation error.", + [35] = "SSL connect error. The SSL handshaking failed.", + [36] = "Bad download resume. Couldn't continue an earlier aborted download.", + [37] = "FILE couldn't read file. Failed to open the file. Permissions?", + [38] = "LDAP cannot bind. LDAP bind operation failed.", + [39] = "LDAP search failed.", + [41] = "Function not found. A required LDAP function was not found.", + [42] = "Aborted by callback. An application told curl to abort the operation.", + [43] = "Internal error. A function was called with a bad parameter.", + [45] = "Interface error. A specified outgoing interface could not be used.", + [47] = "Too many redirects. When following redirects, curl hit the maximum amount.", + [48] = "Unknown option specified to libcurl. This indicates that you passed a weird option to curl that was passed on to libcurl and rejected. Read up in the manual!", + [49] = "Malformed telnet option.", + [51] = "The peer's SSL certificate or SSH MD5 fingerprint was not OK.", + [52] = "The server didn't reply anything, which here is considered an error.", + [53] = "SSL crypto engine not found.", + [54] = "Cannot set SSL crypto engine as default.", + [55] = "Failed sending network data.", + [56] = "Failure in receiving network data.", + [58] = "Problem with the local certificate.", + [59] = "Couldn't use specified SSL cipher.", + [60] = "Peer certificate cannot be authenticated with known CA certificates.", + [61] = "Unrecognized transfer encoding.", + [62] = "Invalid LDAP URL.", + [63] = "Maximum file size exceeded.", + [64] = "Requested FTP SSL level failed.", + [65] = "Sending the data requires a rewind that failed.", + [66] = "Failed to initialize SSL Engine.", + [67] = "The user name, password, or similar was not accepted and curl failed to log in.", + [68] = "File not found on TFTP server.", + [69] = "Permission problem on TFTP server.", + [70] = "Out of disk space on TFTP server.", + [71] = "Illegal TFTP operation.", + [72] = "Unknown TFTP transfer ID.", + [73] = "File already exists (TFTP).", + [74] = "No such user (TFTP).", + [75] = "Character conversion failed.", + [76] = "Character conversion functions required.", + [77] = "Problem with reading the SSL CA cert (path? access rights?).", + [78] = "The resource referenced in the URL does not exist.", + [79] = "An unspecified error occurred during the SSH session.", + [80] = "Failed to shut down the SSL connection.", + [82] = "Could not load CRL file, missing or wrong format (added in 7.19.0).", + [83] = "Issuer check failed (added in 7.19.0).", + [84] = "The FTP PRET command failed", + [85] = "RTSP: mismatch of CSeq numbers", + [86] = "RTSP: mismatch of Session Identifiers", + [87] = "unable to parse FTP file list", + [88] = "FTP chunk callback reported error", + [89] = "No connection available, the session will be queued", + [90] = "SSL public key does not matched pinned public key", + [91] = "Invalid SSL certificate status.", + [92] = "Stream error in HTTP/2 framing layer.", + } + + if not curl_error_dictionary[code] then + return "cURL error " .. tostring(code) .. ": Unknown curl error" + end + return "cURL error " .. tostring(code) .. ": " .. curl_error_dictionary[code] +end + ---Get request statistics ---@param req table cURL request class ---@param statistics_tbl RestConfigResultStats Statistics table @@ -142,7 +239,8 @@ function client.request(request) ret.result = table.concat(res_result) else ---@diagnostic disable-next-line need-check-nil - logger:error("Something went wrong when making the request with cURL: " .. err) + logger:error("Something went wrong when making the request with cURL:\n" .. curl_error(err:no())) + return {} end req:close() From ffadec039fee7a4dd92f976e37b75b26b5def496 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 12:30:29 -0400 Subject: [PATCH 056/159] fix(functions): do not try to show results if the request failed --- lua/rest-nvim/functions.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index b53f6afb..f652e11f 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -77,8 +77,11 @@ function functions.exec(scope) end end - local result_buf = result.get_or_create_buf() - result.write_res(result_buf, req_results) + -- We should not be trying to show a result if the request failed + if #req_results > 0 then + local result_buf = result.get_or_create_buf() + result.write_res(result_buf, req_results) + end end ---Find a list of environment files starting from the current directory From 8f6e8e0cf06f6062c41cea948e57d0e1bab5a5da Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 12:37:59 -0400 Subject: [PATCH 057/159] fix(functions): use `vim.tbl_isempty` instead of `#tbl > 0` --- lua/rest-nvim/functions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index f652e11f..9bd0a85a 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -78,7 +78,7 @@ function functions.exec(scope) end -- We should not be trying to show a result if the request failed - if #req_results > 0 then + if not vim.tbl_isempty(req_results) then local result_buf = result.get_or_create_buf() result.write_res(result_buf, req_results) end From f17c568372683e9315df4e0a676efbf2452d4e04 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 13:29:57 -0400 Subject: [PATCH 058/159] feat(client.curl): also return the response status `code` --- lua/rest-nvim/client/curl.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 82c1611a..d1071592 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -234,6 +234,7 @@ function client.request(request) end ret.url = req:getinfo_effective_url() + ret.code = req:getinfo_response_code() ret.method = req:getinfo_effective_method() ret.headers = table.concat(res_headers):gsub("\r", "") ret.result = table.concat(res_result) From 7d66c3100c05b1fac9c6040478239843c622a3d8 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 13:50:13 -0400 Subject: [PATCH 059/159] ref(config): change default request download size stat title --- lua/rest-nvim/config/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 6f2a0425..95a1dd48 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -92,7 +92,7 @@ local default_config = { ---@see https://curl.se/libcurl/c/curl_easy_getinfo.html stats = { { "total_time", title = "Time taken:" }, - { "size_download_t", title = "Request download size:" }, + { "size_download_t", title = "Download size:" }, }, }, formatters = { From 733ba3484463c5cf54a31f9797a3cce9d75b352a Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 15:34:52 -0400 Subject: [PATCH 060/159] ref!: use a winbar + virtual panes to set the results buffer contents This code is highly inspired by `tamton-aquib/stuff.nvim`, thanks to him for letting me take the idea <3 Co-authored-by: tamton-aquib <77913442+tamton-aquib@users.noreply.github.com> --- lua/rest-nvim/client/curl.lua | 8 +- lua/rest-nvim/result.lua | 179 ++++++++++++++++++++++++++++++---- 2 files changed, 166 insertions(+), 21 deletions(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index d1071592..75faae21 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -143,17 +143,18 @@ local function get_stats(req, statistics_tbl) return stat_info end - local stat_title, stat_info + local stat_key, stat_title, stat_info for _, stat in pairs(statistics_tbl) do for k, v in pairs(stat) do if type(k) == "string" and k == "title" then stat_title = v end if type(k) == "number" then + stat_key = v stat_info = get_stat(req, v) end end - table.insert(stats, stat_title .. " " .. stat_info) + stats[stat_key] = stat_title .. " " .. stat_info end return stats @@ -229,8 +230,7 @@ function client.request(request) -- Get request statistics if they are enabled local stats_config = _G._rest_nvim.result.behavior.statistics if stats_config.enable then - local statistics = get_stats(req, stats_config.stats) - ret.statistics = statistics + ret.statistics = get_stats(req, stats_config.stats) end ret.url = req:getinfo_effective_url() diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua index ea1acbfa..bde87f1d 100644 --- a/lua/rest-nvim/result.lua +++ b/lua/rest-nvim/result.lua @@ -10,6 +10,86 @@ local result = {} local nio = require("nio") +---Results buffer handler number +---@type number|nil +result.bufnr = nil + +---Current pane index in the results window winbar +---@type number +result.current_pane_index = 1 + +---@class ResultPane +---@field name string Pane name +---@field contents string[] Pane contents + +---Results window winbar panes list +---@type { [number]: ResultPane }[] +result.pane_map = { + [1] = { name = "Response", contents = { "Fetching ..." } }, + [2] = { name = "Headers", contents = { "Fetching ..." } }, + [3] = { name = "Cookies", contents = { "Fetching ..." } }, +} + +local function get_hl_group_fg(name) + -- If the HEX color has a zero as the first character, `string.format` will skip it + -- so we have to add it manually later + local hl_fg = string.format("%02X", vim.api.nvim_get_hl(0, { name = name, link = false }).fg) + if #hl_fg == 5 then + hl_fg = "0" .. hl_fg + end + hl_fg = "#" .. hl_fg + return hl_fg +end + +local function set_winbar_hl() + -- Set highlighting for the winbar panes name + local textinfo_fg = get_hl_group_fg("TextInfo") + for i, pane in ipairs(result.pane_map) do + ---@diagnostic disable-next-line undefined-field + vim.api.nvim_set_hl(0, pane.name .. "Highlight", { + fg = textinfo_fg, + bold = (i == result.current_pane_index), + underline = (i == result.current_pane_index), + }) + end + + -- Set highlighting for the winbar stats + local textmuted_fg = get_hl_group_fg("TextMuted") + for _, hl in ipairs({ "Code", "Size", "Time" }) do + vim.api.nvim_set_hl(0, "Rest" .. hl, { + fg = textmuted_fg, + }) + end + + -- Set highlighting for the winbar status code + local moremsg = get_hl_group_fg("MoreMsg") + local errormsg = get_hl_group_fg("ErrorMsg") + local warningmsg = get_hl_group_fg("WarningMsg") + vim.api.nvim_set_hl(0, "RestCode200", { fg = moremsg }) + vim.api.nvim_set_hl(0, "RestCode300", { fg = warningmsg }) + vim.api.nvim_set_hl(0, "RestCodexxx", { fg = errormsg }) +end + +local function rest_winbar(selected) + if type(selected) == "number" then + result.current_pane_index = selected + end + + -- Cycle through the panes + if result.current_pane_index > 3 then + result.current_pane_index = 1 + end + if result.current_pane_index < 1 then + result.current_pane_index = 3 + end + + set_winbar_hl() + -- Set winbar pane contents + ---@diagnostic disable-next-line undefined-field + result.write_block(result.bufnr, result.pane_map[result.current_pane_index].contents, true, false) +end +_G._rest_nvim_winbar = rest_winbar + ---Move the cursor to the desired position in the given buffer ---@param bufnr number Buffer handler number ---@param row number The desired line @@ -48,6 +128,7 @@ function result.get_or_create_buf() -- Make sure the filetype of the buffer is `httpResult` so it will be highlighted vim.api.nvim_set_option_value("ft", "httpResult", { buf = bufnr }) + result.bufnr = bufnr return bufnr end @@ -57,32 +138,45 @@ function result.get_or_create_buf() vim.api.nvim_set_option_value("ft", "httpResult", { buf = new_bufnr }) vim.api.nvim_set_option_value("buftype", "nofile", { buf = new_bufnr }) + result.bufnr = new_bufnr return new_bufnr end ---Wrapper around `vim.api.nvim_buf_set_lines` ---@param bufnr number The target buffer ---@param block string[] The list of lines to write +---@param rewrite boolean? Rewrite the buffer content, defaults to `true` ---@param newline boolean? Add a newline to the end, defaults to `false` ---@see vim.api.nvim_buf_set_lines -function result.write_block(bufnr, block, newline) +function result.write_block(bufnr, block, rewrite, newline) + rewrite = rewrite or true newline = newline or false local content = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local first_line = false - if #content == 1 and content[1] == "" then + if (#content == 1 and content[1] == "") or rewrite then first_line = true end + if rewrite then + -- Set modifiable state + vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr }) + -- Delete buffer content + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {}) + end + vim.api.nvim_buf_set_lines(bufnr, first_line and 0 or -1, -1, false, block) if newline then vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, { "" }) end + + -- Set unmodifiable state + vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) end -function result.display_buf(bufnr) +function result.display_buf(bufnr, res_code, stats) local is_result_displayed = false -- Check if the results buffer is already displayed @@ -120,8 +214,45 @@ function result.display_buf(bufnr) -- Disable concealing for the results buffer window vim.api.nvim_set_option_value("conceallevel", 0, { win = winnr }) + -- Set winbar pane contents + ---@diagnostic disable-next-line undefined-field + result.write_block(bufnr, result.pane_map[result.current_pane_index].contents, true, false) + -- Set unmodifiable state vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) + + -- Set winbar + -- + -- winbar panes + local winbar = [[%#Normal# %1@v:lua._G._rest_nvim_winbar@%#ResponseHighlight#Response%X%#Normal# %2@v:lua._G._rest_nvim_winbar@%#HeadersHighlight#Headers%X%#Normal# %3@v:lua._G._rest_nvim_winbar@%#CookiesHighlight#Cookies%X%#Normal#%=%<]] + + -- winbar status code + winbar = winbar .. "%#RestCode#" .. "status: " + if res_code >= 200 and res_code < 300 then + winbar = winbar .. "%#RestCode200#" + elseif res_code >= 300 and res_code < 400 then + winbar = winbar .. "%#RestCode300#" + elseif res_code >= 400 then + winbar = winbar .. "%#RestCodexxx#" + end + winbar = winbar .. tostring(res_code) + + -- winbar statistics + for stat_name, stat_value in pairs(stats) do + local val = vim.split(stat_value, ": ") + if stat_name:find("total_time") then + winbar = winbar .. "%#RestTime#, " .. val[1]:lower() .. ": " + local value, representation = vim.split(val[2], " ")[1], vim.split(val[2], " ")[2] + winbar = winbar .. "%#Number#" .. value .. " %#Normal#" .. representation + elseif stat_name:find("size_download") then + winbar = winbar .. "%#RestSize#, " .. val[1]:lower() .. ": " + local value, representation = vim.split(val[2], " ")[1], vim.split(val[2], " ")[2] + winbar = winbar .. "%#Number#" .. value .. " %#Normal#" .. representation + end + end + winbar = winbar .. " " + set_winbar_hl() -- Set initial highlighting before displaying winbar + vim.wo[winnr].winbar = winbar end move_cursor(bufnr, 1, 1) @@ -140,12 +271,21 @@ function result.write_res(bufnr, res) end end, vim.split(res.headers, "\n")) - -- METHOD http://foo.bar/api/endpoint - result.write_block(bufnr, { res.method .. " " .. res.url }, true) + local cookies = vim.tbl_filter(function(header) + if header:find("set%-cookie") then + return header + ---@diagnostic disable-next-line missing-return + end + end, headers) + -- ...... -- Content-Type: application/json -- ...... - result.write_block(bufnr, headers, true) + ---@diagnostic disable-next-line inject-field + result.pane_map[2].contents = headers + + ---@diagnostic disable-next-line inject-field + result.pane_map[3].contents = vim.tbl_isempty(cookies) and { "No cookies" } or cookies nio.run(function() ---@type string @@ -159,10 +299,9 @@ function result.write_res(bufnr, res) end -- Do not try to format binary content + local body = {} if res_type == "octet-stream" then - -- That empty value is a newline - local body = { "", "Binary answer" } - result.write_block(bufnr, body, true) + body = { "Binary answer" } else local formatters = _G._rest_nvim.result.behavior.formatters local filetypes = vim.tbl_keys(formatters) @@ -197,11 +336,18 @@ function result.write_res(bufnr, res) .. " returned in the request, the results will not be formatted" ) end - local body = vim.split(res.result, "\n") - table.insert(body, 1, "") - table.insert(body, 2, "#+RES") + body = vim.split(res.result, "\n") + table.insert(body, 1, res.method .. " " .. res.url) + table.insert(body, 2, "") + table.insert(body, 3, "#+RES") table.insert(body, "#+END") - result.write_block(bufnr, body, true) + table.insert(body, "") + + -- Add statistics to the response + table.sort(res.statistics) + for _, stat in pairs(res.statistics) do + table.insert(body, stat) + end -- add syntax highlights for response vim.api.nvim_buf_call(bufnr, function() @@ -223,12 +369,11 @@ function result.write_res(bufnr, res) end end) end + ---@diagnostic disable-next-line inject-field + result.pane_map[1].contents = body end) - table.insert(res.statistics, 1, "") - result.write_block(bufnr, res.statistics, false) - - result.display_buf(bufnr) + result.display_buf(bufnr, res.code, res.statistics) end return result From 486fc2ee97f235afdfc0e8cb176bea7b6f8af232 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 15:40:05 -0400 Subject: [PATCH 061/159] docs(result): add documentation for some new functions --- lua/rest-nvim/result.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua index bde87f1d..81b2145d 100644 --- a/lua/rest-nvim/result.lua +++ b/lua/rest-nvim/result.lua @@ -30,6 +30,9 @@ result.pane_map = { [3] = { name = "Cookies", contents = { "Fetching ..." } }, } +---Get the foreground value of a highlighting group +---@param name string Highlighting group name +---@return string local function get_hl_group_fg(name) -- If the HEX color has a zero as the first character, `string.format` will skip it -- so we have to add it manually later @@ -41,6 +44,7 @@ local function get_hl_group_fg(name) return hl_fg end +---Set the results window winbar highlighting groups local function set_winbar_hl() -- Set highlighting for the winbar panes name local textinfo_fg = get_hl_group_fg("TextInfo") @@ -70,6 +74,11 @@ local function set_winbar_hl() vim.api.nvim_set_hl(0, "RestCodexxx", { fg = errormsg }) end +---Select the winbar panel based on the pane index and set the pane contents +--- +---If the pane index is higher than 3 or lower than 1, it will cycle through +---the panes, e.g. >= 4 gets converted to 1 and <= 0 gets converted to 3 +---@param selected number winbar pane index local function rest_winbar(selected) if type(selected) == "number" then result.current_pane_index = selected From 0c8e442556600401b33c79237183b43506b670d9 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 16:28:01 -0400 Subject: [PATCH 062/159] feat(syntax): improve HTTP status highlighting --- syntax/httpResult.vim | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/syntax/httpResult.vim b/syntax/httpResult.vim index 0c296fea..2898ed85 100644 --- a/syntax/httpResult.vim +++ b/syntax/httpResult.vim @@ -10,10 +10,12 @@ syn match httpResultDateField /^[Ee]xpires:/he=e-1 nextgroup=httpResultDate syn match httpResultDate /.*$/ contained syn region httpResultHeader start=+^HTTP/+ end=+ + nextgroup=httpResult200,httpResult300,httpResult400,httpResult500 -syn match httpResult200 /2\d\d.*$/ contained -syn match httpResult300 /3\d\d.*$/ contained -syn match httpResult400 /4\d\d.*$/ contained -syn match httpResult500 /5\d\d.*$/ contained +syn match httpResult200 /2\d\d/ nextgroup=httpResultStatus contained +syn match httpResult300 /3\d\d/ nextgroup=httpResultstatus contained +syn match httpResult400 /4\d\d/ nextgroup=httpResultstatus contained +syn match httpResult500 /5\d\d/ nextgroup=httpResultstatus contained + +syn match httpResultStatus /.*$/ contained syn region httpResultString start=/\vr?"/ end=/\v"/ syn match httpResultNumber /\v[ =]@1<=[0-9]*.?[0-9]+[ ,;&\n]/he=e-1 From 69802eeaad98601ede90179578fdc38aaa10df04 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 16:40:02 -0400 Subject: [PATCH 063/159] feat(result): add `H` and `L` keybinds to the results buffer to cycle through the winbar panes Also get rid of the stats in the winbar right side as we are already rendering the request statistics in the `Request` winbar pane anyway --- lua/rest-nvim/autocmds.lua | 15 ++++++++++ lua/rest-nvim/commands.lua | 34 +++++++++++++++++++++++ lua/rest-nvim/functions.lua | 15 ++++++++++ lua/rest-nvim/result.lua | 55 ++++++++----------------------------- 4 files changed, 76 insertions(+), 43 deletions(-) diff --git a/lua/rest-nvim/autocmds.lua b/lua/rest-nvim/autocmds.lua index 05f7e0fb..7b94024b 100644 --- a/lua/rest-nvim/autocmds.lua +++ b/lua/rest-nvim/autocmds.lua @@ -9,6 +9,7 @@ local autocmds = {} local commands = require("rest-nvim.commands") +local functions = require("rest-nvim.functions") ---Set up Rest autocommands group and set `:Rest` command on `*.http` files function autocmds.setup() @@ -21,6 +22,20 @@ function autocmds.setup() end, desc = "Set up rest.nvim commands", }) + + vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { + group = rest_nvim_augroup, + pattern = "rest_nvim_results", + callback = function(_) + vim.keymap.set("n", "H", function() + functions.cycle_result_pane("prev") + end) + vim.keymap.set("n", "L", function() + functions.cycle_result_pane("next") + end) + end, + desc = "Set up rest.nvim results buffer keybinds" + }) end ---Register a new autocommand in the `Rest` augroup diff --git a/lua/rest-nvim/commands.lua b/lua/rest-nvim/commands.lua index 71107b0e..f03fd1c8 100644 --- a/lua/rest-nvim/commands.lua +++ b/lua/rest-nvim/commands.lua @@ -103,6 +103,40 @@ local rest_command_tbl = { end end, actions) + return match + end, + }, + result = { + impl = function(args) + local logger = _G._rest_nvim.logger + + if #args > 1 then + ---@diagnostic disable-next-line need-check-nil + logger:error("Too many arguments were passed to the 'result' command: 1 argument was expected, " .. #args .. " were passed") + return + end + if not vim.tbl_contains({ "next", "prev" }, args[1]) then + ---@diagnostic disable-next-line need-check-nil + logger:error("Unknown argument was passed to the 'result' command: 'next' or 'prev' were expected") + return + end + + functions.cycle_result_pane(args[1]) + end, + ---@return string[] + complete = function(args) + local cycles = { "next", "prev" } + if #args < 1 then + return cycles + end + + local match = vim.tbl_filter(function(cycle) + if string.find(cycle, "^" .. args) then + return cycle + ---@diagnostic disable-next-line missing-return + end + end, cycles) + return match end, }, diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index 9bd0a85a..c16c5cc9 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -136,4 +136,19 @@ function functions.env(action, path) end end +---Cycle through the results buffer winbar panes +---@param cycle string Cycle direction, can be: `"next"` or `"prev"` +function functions.cycle_result_pane(cycle) + ---@type number + local idx = result.current_pane_index + + if cycle == "next" then + idx = idx + 1 + elseif cycle == "prev" then + idx = idx - 1 + end + + _G._rest_nvim_winbar(idx) +end + return functions diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua index 81b2145d..b6d0e836 100644 --- a/lua/rest-nvim/result.lua +++ b/lua/rest-nvim/result.lua @@ -57,21 +57,9 @@ local function set_winbar_hl() }) end - -- Set highlighting for the winbar stats + -- Set highlighting for the winbar hints local textmuted_fg = get_hl_group_fg("TextMuted") - for _, hl in ipairs({ "Code", "Size", "Time" }) do - vim.api.nvim_set_hl(0, "Rest" .. hl, { - fg = textmuted_fg, - }) - end - - -- Set highlighting for the winbar status code - local moremsg = get_hl_group_fg("MoreMsg") - local errormsg = get_hl_group_fg("ErrorMsg") - local warningmsg = get_hl_group_fg("WarningMsg") - vim.api.nvim_set_hl(0, "RestCode200", { fg = moremsg }) - vim.api.nvim_set_hl(0, "RestCode300", { fg = warningmsg }) - vim.api.nvim_set_hl(0, "RestCodexxx", { fg = errormsg }) + vim.api.nvim_set_hl(0, "RestText", { fg = textmuted_fg }) end ---Select the winbar panel based on the pane index and set the pane contents @@ -233,33 +221,9 @@ function result.display_buf(bufnr, res_code, stats) -- Set winbar -- -- winbar panes - local winbar = [[%#Normal# %1@v:lua._G._rest_nvim_winbar@%#ResponseHighlight#Response%X%#Normal# %2@v:lua._G._rest_nvim_winbar@%#HeadersHighlight#Headers%X%#Normal# %3@v:lua._G._rest_nvim_winbar@%#CookiesHighlight#Cookies%X%#Normal#%=%<]] - - -- winbar status code - winbar = winbar .. "%#RestCode#" .. "status: " - if res_code >= 200 and res_code < 300 then - winbar = winbar .. "%#RestCode200#" - elseif res_code >= 300 and res_code < 400 then - winbar = winbar .. "%#RestCode300#" - elseif res_code >= 400 then - winbar = winbar .. "%#RestCodexxx#" - end - winbar = winbar .. tostring(res_code) - - -- winbar statistics - for stat_name, stat_value in pairs(stats) do - local val = vim.split(stat_value, ": ") - if stat_name:find("total_time") then - winbar = winbar .. "%#RestTime#, " .. val[1]:lower() .. ": " - local value, representation = vim.split(val[2], " ")[1], vim.split(val[2], " ")[2] - winbar = winbar .. "%#Number#" .. value .. " %#Normal#" .. representation - elseif stat_name:find("size_download") then - winbar = winbar .. "%#RestSize#, " .. val[1]:lower() .. ": " - local value, representation = vim.split(val[2], " ")[1], vim.split(val[2], " ")[2] - winbar = winbar .. "%#Number#" .. value .. " %#Normal#" .. representation - end - end - winbar = winbar .. " " + local winbar = [[%#Normal# %1@v:lua._G._rest_nvim_winbar@%#ResponseHighlight#Response%X%#Normal# %#RestText#|%#Normal# %2@v:lua._G._rest_nvim_winbar@%#HeadersHighlight#Headers%X%#Normal# %#RestText#|%#Normal# %3@v:lua._G._rest_nvim_winbar@%#CookiesHighlight#Cookies%X%#Normal#%=%<]] + + winbar = winbar .. "%#RestText#Press %#Keyword#H%#RestText# for the prev pane or %#Keyword#L%#RestText# for the next pane%#Normal# " set_winbar_hl() -- Set initial highlighting before displaying winbar vim.wo[winnr].winbar = winbar end @@ -347,11 +311,16 @@ function result.write_res(bufnr, res) end body = vim.split(res.result, "\n") table.insert(body, 1, res.method .. " " .. res.url) - table.insert(body, 2, "") - table.insert(body, 3, "#+RES") + table.insert(body, 2, headers[1]) -- HTTP/X and status code + meaning + table.insert(body, 3, "") + table.insert(body, 4, "#+RES") table.insert(body, "#+END") table.insert(body, "") + -- Remove the HTTP/X and status code + meaning from here to avoid duplicates + ---@diagnostic disable-next-line undefined-field + table.remove(result.pane_map[2].contents, 1) + -- Add statistics to the response table.sort(res.statistics) for _, stat in pairs(res.statistics) do From c5a5753f591ad362e7a9d75431e953f86aa20d60 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 16:43:30 -0400 Subject: [PATCH 064/159] feat(result): some configuration adjustments to the results window --- lua/rest-nvim/result.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua index b6d0e836..e58f278a 100644 --- a/lua/rest-nvim/result.lua +++ b/lua/rest-nvim/result.lua @@ -211,6 +211,14 @@ function result.display_buf(bufnr, res_code, stats) -- Disable concealing for the results buffer window vim.api.nvim_set_option_value("conceallevel", 0, { win = winnr }) + -- Disable numbering for the results buffer window + vim.api.nvim_set_option_value("number", false, { win = winnr }) + vim.api.nvim_set_option_value("relativenumber", false, { win = winnr }) + + -- Enable wrapping and smart indent on break + vim.api.nvim_set_option_value("wrap", true, { win = winnr }) + vim.api.nvim_set_option_value("breakindent", true, { win = winnr }) + -- Set winbar pane contents ---@diagnostic disable-next-line undefined-field result.write_block(bufnr, result.pane_map[result.current_pane_index].contents, true, false) From 4a49dc025bd6d8743e1c5b5b732b07c2141677c4 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 17:18:39 -0400 Subject: [PATCH 065/159] feat(parser): add XML body support --- lua/rest-nvim/parser/init.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lua/rest-nvim/parser/init.lua b/lua/rest-nvim/parser/init.lua index f1249a4b..a798b5be 100644 --- a/lua/rest-nvim/parser/init.lua +++ b/lua/rest-nvim/parser/init.lua @@ -11,6 +11,7 @@ local parser = {} local xml2lua = require("xml2lua") +local xml_handler = require("xmlhandler.tree") local env_vars = require("rest-nvim.parser.env_vars") local dynamic_vars = require("rest-nvim.parser.dynamic_vars") @@ -339,6 +340,14 @@ function parser.parse_body(children_nodes, variables) body = traverse_body(json_body, variables) -- This is some metadata to be used later on body.__TYPE = "json" + elseif node_type == "xml_body" then + local body_handler = xml_handler:new() + local xml_parser = xml2lua.parser(body_handler) + local xml_body_text = assert(get_node_text(node, 0)) + xml_parser:parse(xml_body_text) + body = traverse_body(body_handler.root, variables) + -- This is some metadata to be used later on + body.__TYPE = "xml" elseif node_type == "external_body" then -- < @ (identifier) (file_path name: (path)) -- 0 1 2 3 From 3e2dca80b3df1bd5951afa83dc6587615a6704c1 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 17:24:38 -0400 Subject: [PATCH 066/159] hotfix(result): fix result pane content render in subsequent requests when not closing the results window, small cleanup --- lua/rest-nvim/result.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua index e58f278a..56727460 100644 --- a/lua/rest-nvim/result.lua +++ b/lua/rest-nvim/result.lua @@ -173,7 +173,7 @@ function result.write_block(bufnr, block, rewrite, newline) vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) end -function result.display_buf(bufnr, res_code, stats) +function result.display_buf(bufnr) local is_result_displayed = false -- Check if the results buffer is already displayed @@ -236,6 +236,9 @@ function result.display_buf(bufnr, res_code, stats) vim.wo[winnr].winbar = winbar end + -- Set winbar pane contents + ---@diagnostic disable-next-line undefined-field + result.write_block(bufnr, result.pane_map[result.current_pane_index].contents, true, false) move_cursor(bufnr, 1, 1) end @@ -359,7 +362,7 @@ function result.write_res(bufnr, res) result.pane_map[1].contents = body end) - result.display_buf(bufnr, res.code, res.statistics) + result.display_buf(bufnr) end return result From b5a766c900835416b002a68d35fc17191b2904e9 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 18:27:49 -0400 Subject: [PATCH 067/159] hotfix(result): proper pattern checking for `Content-Type` header --- lua/rest-nvim/result.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua index 56727460..5d8284c2 100644 --- a/lua/rest-nvim/result.lua +++ b/lua/rest-nvim/result.lua @@ -275,7 +275,7 @@ function result.write_res(bufnr, res) ---@type string local res_type for _, header in ipairs(headers) do - if header:find("Content%-Type") then + if header:find("^Content%-Type") then local content_type = vim.trim(vim.split(header, ":")[2]) -- We need to remove the leading charset if we are getting a JSON res_type = vim.split(content_type, "/")[2]:gsub(";.*", "") From 13cef10ccbed5a33b1c7fb5587d5811d8ed47ee3 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 20:53:55 -0400 Subject: [PATCH 068/159] feat(parser): add support for script_variables, create `script_vars` parser module --- lua/rest-nvim/client/curl.lua | 7 +++++- lua/rest-nvim/parser/env_vars.lua | 8 +++++++ lua/rest-nvim/parser/init.lua | 26 +++++++++++++++++----- lua/rest-nvim/parser/script_vars.lua | 32 ++++++++++++++++++++++++++++ lua/rest-nvim/result.lua | 10 ++++----- 5 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 lua/rest-nvim/parser/script_vars.lua diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 75faae21..fe90f832 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -12,6 +12,7 @@ local curl = require("cURL.safe") local mimetypes = require("mimetypes") local utils = require("rest-nvim.utils") +local script_vars = require("rest-nvim.parser.script_vars") -- TODO: add support for running multiple requests at once for `:Rest run document` -- TODO: add support for submitting forms in the `client.request` function @@ -237,7 +238,11 @@ function client.request(request) ret.code = req:getinfo_response_code() ret.method = req:getinfo_effective_method() ret.headers = table.concat(res_headers):gsub("\r", "") - ret.result = table.concat(res_result) + ret.body = table.concat(res_result) + + if request.script ~= nil or not request.script == "" then + script_vars.load(request.script, ret) + end else ---@diagnostic disable-next-line need-check-nil logger:error("Something went wrong when making the request with cURL:\n" .. curl_error(err:no())) diff --git a/lua/rest-nvim/parser/env_vars.lua b/lua/rest-nvim/parser/env_vars.lua index 06e86f62..6295a1cb 100644 --- a/lua/rest-nvim/parser/env_vars.lua +++ b/lua/rest-nvim/parser/env_vars.lua @@ -21,6 +21,14 @@ local function get_env_filetype(env_file) return ext == "" and nil or ext end +---Set an environment variable for the current Neovim session +---@param name string Variable name +---@param value string|number|boolean Variable value +---@see vim.env +function env_vars.set_var(name, value) + vim.env[name] = value +end + ---Read the environment variables file from the rest.nvim configuration ---and store all the environment variables in the `vim.env` metatable ---@see vim.env diff --git a/lua/rest-nvim/parser/init.lua b/lua/rest-nvim/parser/init.lua index a798b5be..4e32b42d 100644 --- a/lua/rest-nvim/parser/init.lua +++ b/lua/rest-nvim/parser/init.lua @@ -16,8 +16,6 @@ local xml_handler = require("xmlhandler.tree") local env_vars = require("rest-nvim.parser.env_vars") local dynamic_vars = require("rest-nvim.parser.dynamic_vars") --- TODO: parse and evaluate `(script_variable)` request node - ---@alias NodesList { [string]: TSNode }[] ---@alias Variables { [string]: { type_: string, value: string|number|boolean } }[] @@ -25,7 +23,7 @@ local dynamic_vars = require("rest-nvim.parser.dynamic_vars") ---@param node TSNode Tree-sitter node ---@return boolean local function check_syntax_error(node) - if node:has_error() then + if node and node:has_error() then local logger = _G._rest_nvim.logger ---Create a node range string á la `:InspectTree` view @@ -172,7 +170,7 @@ local function parse_variables(node, tree, text, variables) end end - local variable = vars[variable_name] + local variable = variables[variable_name] -- If the variable was not found in the document then fallback to the shell environment if not variable then ---@diagnostic disable-next-line need-check-nil @@ -331,7 +329,6 @@ function parser.parse_body(children_nodes, variables) -- TODO: handle GraphQL bodies by using a graphql parser library from luarocks for node_type, node in pairs(children_nodes) do - -- TODO: handle XML bodies by using xml2lua library from luarocks if node_type == "json_body" then local json_body_text = assert(get_node_text(node, 0)) local json_body = vim.json.decode(json_body_text, { @@ -363,6 +360,22 @@ function parser.parse_body(children_nodes, variables) return body end +---Get a script variable node and return +---@param req_node TSNode Tree-sitter request node +---@return string +function parser.parse_script(req_node) + -- Get the next named sibling of the current request node, + -- if the request does not have any sibling or if it is not + -- a script_variable node then early return an empty string + local next_sibling = req_node:next_named_sibling() + ---@diagnostic disable-next-line need-check-nil + if not next_sibling or next_sibling and next_sibling:type() ~= "script_variable" then + return "" + end + + return assert(get_node_text(next_sibling, 0)) +end + ---@class RequestReq ---@field method string The request method ---@field url string The request URL @@ -372,6 +385,7 @@ end ---@field request RequestReq ---@field headers { [string]: string|number|boolean }[] ---@field body table +---@field script? string ---Parse a request and return the request on itself, its headers and body ---@param req_node TSNode Tree-sitter request node @@ -381,6 +395,7 @@ function parser.parse(req_node) request = {}, headers = {}, body = {}, + script = "", } local document_node = parser.look_behind_until(nil, "document") @@ -391,6 +406,7 @@ function parser.parse(req_node) ast.request = parser.parse_request(request_children_nodes, document_variables) ast.headers = parser.parse_headers(request_children_nodes, document_variables) ast.body = parser.parse_body(request_children_nodes, document_variables) + ast.script = parser.parse_script(req_node) return ast end diff --git a/lua/rest-nvim/parser/script_vars.lua b/lua/rest-nvim/parser/script_vars.lua new file mode 100644 index 00000000..777c4b94 --- /dev/null +++ b/lua/rest-nvim/parser/script_vars.lua @@ -0,0 +1,32 @@ +---@mod rest-nvim.parser.script_vars rest.nvim parsing module script variables +--- +---@brief [[ +--- +--- rest.nvim script variables +--- +---@brief ]] + +local script_vars = {} + +local env_vars = require("rest-nvim.parser.env_vars") + +---Load a script_variable content and evaluate it +---@param script_str string The script variable content +---@param res table Request response body +function script_vars.load(script_str, res) + local context = { + result = res, + print = vim.print, + json_decode = vim.json.decode, + set_env = env_vars.set_var, + } + local env = { context = context } + setmetatable(env, { __index = _G }) + + local f = load(script_str, "script_variable", "bt", env) + if f then + f() + end +end + +return script_vars diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result.lua index 5d8284c2..c190ff25 100644 --- a/lua/rest-nvim/result.lua +++ b/lua/rest-nvim/result.lua @@ -295,18 +295,18 @@ function result.write_res(bufnr, res) if vim.tbl_contains(filetypes, res_type) then local fmt = formatters[res_type] if type(fmt) == "function" then - local ok, out = pcall(fmt, res.result) + local ok, out = pcall(fmt, res.body) if ok and out then - res.result = out + res.body = out else ---@diagnostic disable-next-line need-check-nil logger:error("Error calling formatter on response body:\n" .. out) end elseif vim.fn.executable(fmt) == 1 then - local stdout = vim.fn.system(fmt, res.result):gsub("\n$", "") + local stdout = vim.fn.system(fmt, res.body):gsub("\n$", "") -- Check if formatter ran successfully if vim.v.shell_error == 0 then - res.result = stdout + res.body = stdout else ---@diagnostic disable-next-line need-check-nil logger:error("Error running formatter '" .. fmt .. "' on response body:\n" .. stdout) @@ -320,7 +320,7 @@ function result.write_res(bufnr, res) .. " returned in the request, the results will not be formatted" ) end - body = vim.split(res.result, "\n") + body = vim.split(res.body, "\n") table.insert(body, 1, res.method .. " " .. res.url) table.insert(body, 2, headers[1]) -- HTTP/X and status code + meaning table.insert(body, 3, "") From 2b57d64d4c63485c081555598dcfd46c2ef17b15 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 21:20:34 -0400 Subject: [PATCH 069/159] ref!: load and evaluate script variables after getting the results of the HTTP client, it is not something the HTTP client should deal with --- lua/rest-nvim/client/curl.lua | 7 ++----- lua/rest-nvim/functions.lua | 8 +++++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index fe90f832..3ee12070 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -12,7 +12,6 @@ local curl = require("cURL.safe") local mimetypes = require("mimetypes") local utils = require("rest-nvim.utils") -local script_vars = require("rest-nvim.parser.script_vars") -- TODO: add support for running multiple requests at once for `:Rest run document` -- TODO: add support for submitting forms in the `client.request` function @@ -239,10 +238,8 @@ function client.request(request) ret.method = req:getinfo_effective_method() ret.headers = table.concat(res_headers):gsub("\r", "") ret.body = table.concat(res_result) - - if request.script ~= nil or not request.script == "" then - script_vars.load(request.script, ret) - end + -- We are returning the request script variable as it + ret.script = request.script else ---@diagnostic disable-next-line need-check-nil logger:error("Something went wrong when making the request with cURL:\n" .. curl_error(err:no())) diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index c16c5cc9..0819a977 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -12,6 +12,7 @@ local nio = require("nio") local utils = require("rest-nvim.utils") local parser = require("rest-nvim.parser") local result = require("rest-nvim.result") +local script_vars = require("rest-nvim.parser.script_vars") ---Execute one or several HTTP requests depending on given `scope` ---and return request(s) results in a table that will be used to render results @@ -77,10 +78,15 @@ function functions.exec(scope) end end - -- We should not be trying to show a result if the request failed if not vim.tbl_isempty(req_results) then + -- We should not be trying to show a result if the request failed local result_buf = result.get_or_create_buf() result.write_res(result_buf, req_results) + + -- Load the script variables + if req_results.script ~= nil or not req_results.script == "" then + script_vars.load(req_results.script, req_results) + end end end From 7a3fde3322cf7093a1ca51bb4e50516296b4bc7a Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 21:21:49 -0400 Subject: [PATCH 070/159] ref: move `result.lua` to `result/init.lua` --- lua/rest-nvim/{result.lua => result/init.lua} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lua/rest-nvim/{result.lua => result/init.lua} (100%) diff --git a/lua/rest-nvim/result.lua b/lua/rest-nvim/result/init.lua similarity index 100% rename from lua/rest-nvim/result.lua rename to lua/rest-nvim/result/init.lua From d48ba4a6049c3ee00ec1218a3123d46a91f62150 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 31 Jan 2024 21:46:24 -0400 Subject: [PATCH 071/159] ref!: move `winbar` logic from `result.init` to `result.winbar` module --- lua/rest-nvim/functions.lua | 7 ++- lua/rest-nvim/result/init.lua | 94 +++++----------------------- lua/rest-nvim/result/winbar.lua | 107 ++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 81 deletions(-) create mode 100644 lua/rest-nvim/result/winbar.lua diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index 0819a977..acae7835 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -9,11 +9,14 @@ local functions = {} local nio = require("nio") + local utils = require("rest-nvim.utils") local parser = require("rest-nvim.parser") -local result = require("rest-nvim.result") local script_vars = require("rest-nvim.parser.script_vars") +local result = require("rest-nvim.result") +local winbar = require("rest-nvim.result.winbar") + ---Execute one or several HTTP requests depending on given `scope` ---and return request(s) results in a table that will be used to render results ---in a buffer. @@ -146,7 +149,7 @@ end ---@param cycle string Cycle direction, can be: `"next"` or `"prev"` function functions.cycle_result_pane(cycle) ---@type number - local idx = result.current_pane_index + local idx = winbar.current_pane_index if cycle == "next" then idx = idx + 1 diff --git a/lua/rest-nvim/result/init.lua b/lua/rest-nvim/result/init.lua index c190ff25..f9ab2056 100644 --- a/lua/rest-nvim/result/init.lua +++ b/lua/rest-nvim/result/init.lua @@ -10,82 +10,23 @@ local result = {} local nio = require("nio") +local winbar = require("rest-nvim.result.winbar") + ---Results buffer handler number ---@type number|nil result.bufnr = nil ----Current pane index in the results window winbar ----@type number -result.current_pane_index = 1 - ----@class ResultPane ----@field name string Pane name ----@field contents string[] Pane contents - ----Results window winbar panes list ----@type { [number]: ResultPane }[] -result.pane_map = { - [1] = { name = "Response", contents = { "Fetching ..." } }, - [2] = { name = "Headers", contents = { "Fetching ..." } }, - [3] = { name = "Cookies", contents = { "Fetching ..." } }, -} - ----Get the foreground value of a highlighting group ----@param name string Highlighting group name ----@return string -local function get_hl_group_fg(name) - -- If the HEX color has a zero as the first character, `string.format` will skip it - -- so we have to add it manually later - local hl_fg = string.format("%02X", vim.api.nvim_get_hl(0, { name = name, link = false }).fg) - if #hl_fg == 5 then - hl_fg = "0" .. hl_fg - end - hl_fg = "#" .. hl_fg - return hl_fg -end - ----Set the results window winbar highlighting groups -local function set_winbar_hl() - -- Set highlighting for the winbar panes name - local textinfo_fg = get_hl_group_fg("TextInfo") - for i, pane in ipairs(result.pane_map) do - ---@diagnostic disable-next-line undefined-field - vim.api.nvim_set_hl(0, pane.name .. "Highlight", { - fg = textinfo_fg, - bold = (i == result.current_pane_index), - underline = (i == result.current_pane_index), - }) - end - - -- Set highlighting for the winbar hints - local textmuted_fg = get_hl_group_fg("TextMuted") - vim.api.nvim_set_hl(0, "RestText", { fg = textmuted_fg }) -end - ---Select the winbar panel based on the pane index and set the pane contents --- ---If the pane index is higher than 3 or lower than 1, it will cycle through ---the panes, e.g. >= 4 gets converted to 1 and <= 0 gets converted to 3 ---@param selected number winbar pane index -local function rest_winbar(selected) - if type(selected) == "number" then - result.current_pane_index = selected - end - - -- Cycle through the panes - if result.current_pane_index > 3 then - result.current_pane_index = 1 - end - if result.current_pane_index < 1 then - result.current_pane_index = 3 - end - - set_winbar_hl() +_G._rest_nvim_winbar = function(selected) + winbar.set_pane(selected) -- Set winbar pane contents ---@diagnostic disable-next-line undefined-field - result.write_block(result.bufnr, result.pane_map[result.current_pane_index].contents, true, false) + result.write_block(result.bufnr, winbar.pane_map[winbar.current_pane_index].contents, true, false) end -_G._rest_nvim_winbar = rest_winbar ---Move the cursor to the desired position in the given buffer ---@param bufnr number Buffer handler number @@ -173,7 +114,7 @@ function result.write_block(bufnr, block, rewrite, newline) vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) end -function result.display_buf(bufnr) +function result.display_buf(bufnr, stats) local is_result_displayed = false -- Check if the results buffer is already displayed @@ -221,24 +162,19 @@ function result.display_buf(bufnr) -- Set winbar pane contents ---@diagnostic disable-next-line undefined-field - result.write_block(bufnr, result.pane_map[result.current_pane_index].contents, true, false) + result.write_block(bufnr, winbar.pane_map[winbar.current_pane_index].contents, true, false) -- Set unmodifiable state vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) -- Set winbar - -- - -- winbar panes - local winbar = [[%#Normal# %1@v:lua._G._rest_nvim_winbar@%#ResponseHighlight#Response%X%#Normal# %#RestText#|%#Normal# %2@v:lua._G._rest_nvim_winbar@%#HeadersHighlight#Headers%X%#Normal# %#RestText#|%#Normal# %3@v:lua._G._rest_nvim_winbar@%#CookiesHighlight#Cookies%X%#Normal#%=%<]] - - winbar = winbar .. "%#RestText#Press %#Keyword#H%#RestText# for the prev pane or %#Keyword#L%#RestText# for the next pane%#Normal# " - set_winbar_hl() -- Set initial highlighting before displaying winbar - vim.wo[winnr].winbar = winbar + winbar.set_hl() -- Set initial highlighting before displaying winbar + vim.wo[winnr].winbar = winbar.get_content(stats) end -- Set winbar pane contents ---@diagnostic disable-next-line undefined-field - result.write_block(bufnr, result.pane_map[result.current_pane_index].contents, true, false) + result.write_block(bufnr, winbar.pane_map[winbar.current_pane_index].contents, true, false) move_cursor(bufnr, 1, 1) end @@ -266,10 +202,10 @@ function result.write_res(bufnr, res) -- Content-Type: application/json -- ...... ---@diagnostic disable-next-line inject-field - result.pane_map[2].contents = headers + winbar.pane_map[2].contents = headers ---@diagnostic disable-next-line inject-field - result.pane_map[3].contents = vim.tbl_isempty(cookies) and { "No cookies" } or cookies + winbar.pane_map[3].contents = vim.tbl_isempty(cookies) and { "No cookies" } or cookies nio.run(function() ---@type string @@ -330,7 +266,7 @@ function result.write_res(bufnr, res) -- Remove the HTTP/X and status code + meaning from here to avoid duplicates ---@diagnostic disable-next-line undefined-field - table.remove(result.pane_map[2].contents, 1) + table.remove(winbar.pane_map[2].contents, 1) -- Add statistics to the response table.sort(res.statistics) @@ -359,10 +295,10 @@ function result.write_res(bufnr, res) end) end ---@diagnostic disable-next-line inject-field - result.pane_map[1].contents = body + winbar.pane_map[1].contents = body end) - result.display_buf(bufnr) + result.display_buf(bufnr, res.statistics) end return result diff --git a/lua/rest-nvim/result/winbar.lua b/lua/rest-nvim/result/winbar.lua new file mode 100644 index 00000000..a671f80e --- /dev/null +++ b/lua/rest-nvim/result/winbar.lua @@ -0,0 +1,107 @@ +---@mod rest-nvim.result.winbar rest.nvim result buffer winbar add-on +--- +---@brief [[ +--- +--- rest.nvim result buffer winbar +--- +---@brief ]] + +local winbar = {} + +---Current pane index in the results window winbar +---@type number +winbar.current_pane_index = 1 + +---Create the winbar contents and return them +---@param stats table Request statistics +---@return string +function winbar.get_content(stats) + -- winbar panes + local content = [[%#Normal# %1@v:lua._G._rest_nvim_winbar@%#ResponseHighlight#Response%X%#Normal# %#RestText#|%#Normal# %2@v:lua._G._rest_nvim_winbar@%#HeadersHighlight#Headers%X%#Normal# %#RestText#|%#Normal# %3@v:lua._G._rest_nvim_winbar@%#CookiesHighlight#Cookies%X%#Normal# %=%<]] + + -- winbar statistics + for stat_name, stat_value in pairs(stats) do + local val = vim.split(stat_value, ": ") + if stat_name:find("total_time") then + content = content .. "%#RestText# " .. val[1]:lower() .. ": " + local value, representation = vim.split(val[2], " ")[1], vim.split(val[2], " ")[2] + content = content .. "%#Number#" .. value .. " %#Normal#" .. representation + elseif stat_name:find("size_download") then + content = content .. "%#RestText#, " .. val[1]:lower() .. ": " + local value, representation = vim.split(val[2], " ")[1], vim.split(val[2], " ")[2] + content = content .. "%#Number#" .. value .. " %#Normal#" .. representation + end + end + -- content = content .. "%#RestText#Press %#Keyword#H%#RestText# for the prev pane or %#Keyword#L%#RestText# for the next pane%#Normal# " + content = content .. "%#RestText# | Press %#Keyword#?%#RestText# for help%#Normal# " + content = content .. " " + + return content +end + +---@class ResultPane +---@field name string Pane name +---@field contents string[] Pane contents + +---Results window winbar panes list +---@type { [number]: ResultPane }[] +winbar.pane_map = { + [1] = { name = "Response", contents = { "Fetching ..." } }, + [2] = { name = "Headers", contents = { "Fetching ..." } }, + [3] = { name = "Cookies", contents = { "Fetching ..." } }, +} + +---Get the foreground value of a highlighting group +---@param name string Highlighting group name +---@return string +local function get_hl_group_fg(name) + -- If the HEX color has a zero as the first character, `string.format` will skip it + -- so we have to add it manually later + local hl_fg = string.format("%02X", vim.api.nvim_get_hl(0, { name = name, link = false }).fg) + if #hl_fg == 5 then + hl_fg = "0" .. hl_fg + end + hl_fg = "#" .. hl_fg + return hl_fg +end + +---Set the results window winbar highlighting groups +function winbar.set_hl() + -- Set highlighting for the winbar panes name + local textinfo_fg = get_hl_group_fg("TextInfo") + for i, pane in ipairs(winbar.pane_map) do + ---@diagnostic disable-next-line undefined-field + vim.api.nvim_set_hl(0, pane.name .. "Highlight", { + fg = textinfo_fg, + bold = (i == winbar.current_pane_index), + underline = (i == winbar.current_pane_index), + }) + end + + -- Set highlighting for the winbar text + local textmuted_fg = get_hl_group_fg("TextMuted") + vim.api.nvim_set_hl(0, "RestText", { fg = textmuted_fg }) +end + +---Select the winbar panel based on the pane index and set the pane contents +--- +---If the pane index is higher than 3 or lower than 1, it will cycle through +---the panes, e.g. >= 4 gets converted to 1 and <= 0 gets converted to 3 +---@param selected number winbar pane index +function winbar.set_pane(selected) + if type(selected) == "number" then + winbar.current_pane_index = selected + end + + -- Cycle through the panes + if winbar.current_pane_index > 3 then + winbar.current_pane_index = 1 + end + if winbar.current_pane_index < 1 then + winbar.current_pane_index = 3 + end + + winbar.set_hl() +end + +return winbar From 79a4481fa95bf803d44536884811c7eebc775168 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Thu, 1 Feb 2024 03:40:55 -0400 Subject: [PATCH 072/159] ref(result)!: move request statistics to its own winbar pane --- lua/rest-nvim/result/init.lua | 17 ++++++++------- lua/rest-nvim/result/winbar.lua | 37 ++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/lua/rest-nvim/result/init.lua b/lua/rest-nvim/result/init.lua index f9ab2056..ab3b5c34 100644 --- a/lua/rest-nvim/result/init.lua +++ b/lua/rest-nvim/result/init.lua @@ -262,18 +262,11 @@ function result.write_res(bufnr, res) table.insert(body, 3, "") table.insert(body, 4, "#+RES") table.insert(body, "#+END") - table.insert(body, "") -- Remove the HTTP/X and status code + meaning from here to avoid duplicates ---@diagnostic disable-next-line undefined-field table.remove(winbar.pane_map[2].contents, 1) - -- Add statistics to the response - table.sort(res.statistics) - for _, stat in pairs(res.statistics) do - table.insert(body, stat) - end - -- add syntax highlights for response vim.api.nvim_buf_call(bufnr, function() local syntax_file = vim.fn.expand(string.format("$VIMRUNTIME/syntax/%s.vim", res_type)) @@ -298,6 +291,16 @@ function result.write_res(bufnr, res) winbar.pane_map[1].contents = body end) + -- Add statistics to the response + local stats = {} + table.sort(res.statistics) + for _, stat in pairs(res.statistics) do + table.insert(stats, stat) + end + table.sort(stats) + ---@diagnostic disable-next-line inject-field + winbar.pane_map[4].contents = stats + result.display_buf(bufnr, res.statistics) end diff --git a/lua/rest-nvim/result/winbar.lua b/lua/rest-nvim/result/winbar.lua index a671f80e..f590e4a6 100644 --- a/lua/rest-nvim/result/winbar.lua +++ b/lua/rest-nvim/result/winbar.lua @@ -17,24 +17,26 @@ winbar.current_pane_index = 1 ---@return string function winbar.get_content(stats) -- winbar panes - local content = [[%#Normal# %1@v:lua._G._rest_nvim_winbar@%#ResponseHighlight#Response%X%#Normal# %#RestText#|%#Normal# %2@v:lua._G._rest_nvim_winbar@%#HeadersHighlight#Headers%X%#Normal# %#RestText#|%#Normal# %3@v:lua._G._rest_nvim_winbar@%#CookiesHighlight#Cookies%X%#Normal# %=%<]] + local content = [[%#Normal# %1@v:lua._G._rest_nvim_winbar@%#ResponseHighlight#Response%X%#Normal# %#RestText#|%#Normal# %2@v:lua._G._rest_nvim_winbar@%#HeadersHighlight#Headers%X%#Normal# %#RestText#|%#Normal# %3@v:lua._G._rest_nvim_winbar@%#CookiesHighlight#Cookies%X%#Normal# %#RestText#|%#Normal# %4@v:lua._G._rest_nvim_winbar@%#StatsHighlight#Stats%X%#Normal# %=%<]] -- winbar statistics - for stat_name, stat_value in pairs(stats) do - local val = vim.split(stat_value, ": ") - if stat_name:find("total_time") then - content = content .. "%#RestText# " .. val[1]:lower() .. ": " - local value, representation = vim.split(val[2], " ")[1], vim.split(val[2], " ")[2] - content = content .. "%#Number#" .. value .. " %#Normal#" .. representation - elseif stat_name:find("size_download") then - content = content .. "%#RestText#, " .. val[1]:lower() .. ": " - local value, representation = vim.split(val[2], " ")[1], vim.split(val[2], " ")[2] - content = content .. "%#Number#" .. value .. " %#Normal#" .. representation + if not vim.tbl_isempty(stats) then + for stat_name, stat_value in pairs(stats) do + local val = vim.split(stat_value, ": ") + if stat_name:find("total_time") then + content = content .. " %#RestText# " .. val[1]:lower() .. ": " + local value, representation = vim.split(val[2], " ")[1], vim.split(val[2], " ")[2] + content = content .. "%#Number#" .. value .. " %#Normal#" .. representation + elseif stat_name:find("size_download") then + content = content .. " %#RestText#" .. val[1]:lower() .. ": " + local value, representation = vim.split(val[2], " ")[1], vim.split(val[2], " ")[2] + content = content .. "%#Number#" .. value .. " %#Normal#" .. representation + end end + content = content .. " %#RestText#|%#Normal# " end -- content = content .. "%#RestText#Press %#Keyword#H%#RestText# for the prev pane or %#Keyword#L%#RestText# for the next pane%#Normal# " - content = content .. "%#RestText# | Press %#Keyword#?%#RestText# for help%#Normal# " - content = content .. " " + content = content .. "%#RestText#Press %#Keyword#?%#RestText# for help%#Normal# " return content end @@ -49,6 +51,7 @@ winbar.pane_map = { [1] = { name = "Response", contents = { "Fetching ..." } }, [2] = { name = "Headers", contents = { "Fetching ..." } }, [3] = { name = "Cookies", contents = { "Fetching ..." } }, + [4] = { name = "Stats", contents = { "Fetching ..." } }, } ---Get the foreground value of a highlighting group @@ -85,8 +88,8 @@ end ---Select the winbar panel based on the pane index and set the pane contents --- ----If the pane index is higher than 3 or lower than 1, it will cycle through ----the panes, e.g. >= 4 gets converted to 1 and <= 0 gets converted to 3 +---If the pane index is higher than 4 or lower than 1, it will cycle through +---the panes, e.g. >= 5 gets converted to 1 and <= 0 gets converted to 4 ---@param selected number winbar pane index function winbar.set_pane(selected) if type(selected) == "number" then @@ -94,11 +97,11 @@ function winbar.set_pane(selected) end -- Cycle through the panes - if winbar.current_pane_index > 3 then + if winbar.current_pane_index > 4 then winbar.current_pane_index = 1 end if winbar.current_pane_index < 1 then - winbar.current_pane_index = 3 + winbar.current_pane_index = 4 end winbar.set_hl() From ca5980cbef0f19e4148d524f43e5541442810311 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sat, 3 Feb 2024 16:11:41 -0400 Subject: [PATCH 073/159] feat(init): also setup the tree-sitter branch on `setup()` --- lua/rest-nvim/init.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lua/rest-nvim/init.lua b/lua/rest-nvim/init.lua index b7922139..53c273e4 100644 --- a/lua/rest-nvim/init.lua +++ b/lua/rest-nvim/init.lua @@ -19,6 +19,18 @@ function rest.setup(user_configs) -- Set up rest.nvim autocommands and commands autocmds.setup() + + -- Set up tree-sitter HTTP parser branch + -- NOTE: remove this piece of code once rest.nvim v2 has been pushed + -- and tree-sitter-http `next` branch has been merged + local ok, treesitter_parsers = pcall(require, "nvim-treesitter.parsers") + if ok then + local parser_config = treesitter_parsers.get_parser_configs() + + parser_config.http = vim.tbl_deep_extend("force", parser_config.http, { + install_info = { branch = "next" }, + }) + end end return rest From a3e08b4fc3a08efbd95a28ad871deab938a8495a Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sat, 3 Feb 2024 16:12:28 -0400 Subject: [PATCH 074/159] feat(rockspec): use `dev` branch on `scm-2` --- rest.nvim-scm-2.rockspec | 1 + 1 file changed, 1 insertion(+) diff --git a/rest.nvim-scm-2.rockspec b/rest.nvim-scm-2.rockspec index b248ae86..c6955888 100644 --- a/rest.nvim-scm-2.rockspec +++ b/rest.nvim-scm-2.rockspec @@ -29,6 +29,7 @@ source = { if MAJOR == "scm" then source = { url = "git://github.com/rest-nvim/rest.nvim", + branch = "dev", } end From 06a0ad3ff771111e3279a7d0bf2a656da5d4ca0a Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sat, 3 Feb 2024 16:15:21 -0400 Subject: [PATCH 075/159] fix(ci): run luarocks CI on new releases too and let us run the CI manually --- .github/workflows/luarocks.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/luarocks.yml b/.github/workflows/luarocks.yml index c74b01df..5d51c3ad 100644 --- a/.github/workflows/luarocks.yml +++ b/.github/workflows/luarocks.yml @@ -1,8 +1,12 @@ name: Luarocks release on: push: + releases: + types: + - created tags: - "*" + workflow_dispatch: jobs: luarocks-release: @@ -11,6 +15,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + with: + fetch-depth: 0 # Required to count the commits - name: Luarocks Upload uses: mrcjkb/luarocks-tag-release@v5 with: From 51f352cbe2a06fe0a69960671d0cedc4eee2c9d3 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sat, 3 Feb 2024 16:21:28 -0400 Subject: [PATCH 076/159] fix(luarocks/rockspec): lowercase `Lua-cURL` dependency --- .github/workflows/luarocks.yml | 2 +- rest.nvim-scm-2.rockspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/luarocks.yml b/.github/workflows/luarocks.yml index 5d51c3ad..078e92ac 100644 --- a/.github/workflows/luarocks.yml +++ b/.github/workflows/luarocks.yml @@ -22,7 +22,7 @@ jobs: with: dependencies: | nvim-nio - Lua-cURL + lua-curl mimetypes xml2lua env: diff --git a/rest.nvim-scm-2.rockspec b/rest.nvim-scm-2.rockspec index c6955888..762de9a3 100644 --- a/rest.nvim-scm-2.rockspec +++ b/rest.nvim-scm-2.rockspec @@ -16,7 +16,7 @@ description = { dependencies = { "lua >= 5.1, < 5.4", "nvim-nio", - "Lua-cURL", + "lua-curl", "mimetypes", "xml2lua", } From 36ce0cd8968f525e714b7e7a385a115e1115408a Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sat, 3 Feb 2024 16:39:36 -0400 Subject: [PATCH 077/159] fix(ci): install build dependencies for `lua-curl` when uploading to luarocks --- .github/workflows/luarocks.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/luarocks.yml b/.github/workflows/luarocks.yml index 078e92ac..68fe3a2a 100644 --- a/.github/workflows/luarocks.yml +++ b/.github/workflows/luarocks.yml @@ -17,6 +17,8 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 # Required to count the commits + - name: Install build dependencies + run: apt-get install -y libcurl4-gnutls-dev - name: Luarocks Upload uses: mrcjkb/luarocks-tag-release@v5 with: From 71ce34c06fb5db14433f059d54d7fdc9b7351a29 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sat, 3 Feb 2024 16:42:37 -0400 Subject: [PATCH 078/159] fix(ci): use `sudo` while installing build dependencies for `lua-curl` (I really hate GitHub CI) --- .github/workflows/luarocks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/luarocks.yml b/.github/workflows/luarocks.yml index 68fe3a2a..3d1eb9fd 100644 --- a/.github/workflows/luarocks.yml +++ b/.github/workflows/luarocks.yml @@ -18,7 +18,7 @@ jobs: with: fetch-depth: 0 # Required to count the commits - name: Install build dependencies - run: apt-get install -y libcurl4-gnutls-dev + run: sudo apt-get install -y libcurl4-gnutls-dev - name: Luarocks Upload uses: mrcjkb/luarocks-tag-release@v5 with: From 0091266d813a6ca773063252bed5c264b00a25f2 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Fri, 9 Feb 2024 06:15:37 -0400 Subject: [PATCH 079/159] chore(tests): small cleanup --- tests/script_vars/script_vars.http | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/script_vars/script_vars.http b/tests/script_vars/script_vars.http index 82e970af..9e21b1c8 100644 --- a/tests/script_vars/script_vars.http +++ b/tests/script_vars/script_vars.http @@ -11,5 +11,4 @@ context.set_env("postId", body.id) --%} -### GET https://jsonplaceholder.typicode.com/posts/{{postId}} From 5dc380589443e1e1e3f3d06cdc69d678fd865f89 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 12 Feb 2024 07:26:14 -0400 Subject: [PATCH 080/159] feat(client.curl): add support for submitting XML --- lua/rest-nvim/client/curl.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 3ee12070..13813ef5 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -10,12 +10,12 @@ local client = {} local curl = require("cURL.safe") local mimetypes = require("mimetypes") +local xml2lua = require("xml2lua") local utils = require("rest-nvim.utils") -- TODO: add support for running multiple requests at once for `:Rest run document` -- TODO: add support for submitting forms in the `client.request` function --- TODO: add support for submitting XML bodies in the `client.request` function ---Return the status code and the meaning of an curl error ---see man curl for reference @@ -200,13 +200,19 @@ function client.request(request) end -- Request body + -- + -- Create a copy of the request body table to remove the unneeded `__TYPE` metadata field later + local body = request.body if request.body.__TYPE == "json" then - -- Create a copy of the request body table to remove the unneeded `__TYPE` metadata field - local body = request.body body.__TYPE = nil - local json_body_string = vim.json.encode(request.body) + local json_body_string = vim.json.encode(body) req:setopt_postfields(json_body_string) + elseif request.body.__TYPE == "xml" then + body.__TYPE = nil + + local xml_body_string = xml2lua.toXml(body) + req:setopt_postfields(xml_body_string) elseif request.body.__TYPE == "external_file" then local body_mimetype = mimetypes.guess(request.body.path) local post_data = { From 0218dfbd908474886777bc55df18d2087d6853e9 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 12 Feb 2024 07:59:29 -0400 Subject: [PATCH 081/159] feat: improve annotations docs, code cleanup and fixes --- lua/rest-nvim/client/curl.lua | 6 +++--- lua/rest-nvim/commands.lua | 4 ++-- lua/rest-nvim/config/init.lua | 4 +++- lua/rest-nvim/functions.lua | 6 ++++-- lua/rest-nvim/parser/dynamic_vars.lua | 5 +++-- lua/rest-nvim/parser/env_vars.lua | 1 + lua/rest-nvim/parser/init.lua | 26 ++++++++++++++------------ lua/rest-nvim/result/init.lua | 7 +++++-- lua/rest-nvim/utils.lua | 5 ++++- 9 files changed, 39 insertions(+), 25 deletions(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 13813ef5..6ce887ff 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -20,7 +20,7 @@ local utils = require("rest-nvim.utils") ---Return the status code and the meaning of an curl error ---see man curl for reference ---@param code number The exit code of curl ----@return string +---@return string The curl error message local function curl_error(code) local curl_error_dictionary = { [1] = "Unsupported protocol. This build of curl has no support for this protocol.", @@ -117,7 +117,7 @@ end ---Get request statistics ---@param req table cURL request class ---@param statistics_tbl RestConfigResultStats Statistics table ----@return table +---@return table Request statistics local function get_stats(req, statistics_tbl) local logger = _G._rest_nvim.logger @@ -162,7 +162,7 @@ end ---Execute an HTTP request using cURL ---@param request Request Request data to be passed to cURL ----@return table +---@return table The request information (url, method, headers, body, etc) function client.request(request) local logger = _G._rest_nvim.logger diff --git a/lua/rest-nvim/commands.lua b/lua/rest-nvim/commands.lua index f03fd1c8..e881976a 100644 --- a/lua/rest-nvim/commands.lua +++ b/lua/rest-nvim/commands.lua @@ -29,7 +29,7 @@ local rest_command_tbl = { run = { impl = function(args) local request_scope = #args == 0 and "cursor" or args[1] - functions.exec(request_scope, false) + functions.exec(request_scope) end, ---@return string[] complete = function(args) @@ -50,7 +50,7 @@ local rest_command_tbl = { }, last = { impl = function(_) - functions.exec("last", false) + functions.exec("last") end, }, env = { diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 95a1dd48..98cc3692 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -102,6 +102,7 @@ local default_config = { return body end -- stylua: ignore + ---@diagnostic disable-next-line redundant-return-value return vim.fn.system({ "tidy", "-i", @@ -134,7 +135,7 @@ local default_config = { ---Set user-defined configurations for rest.nvim ---@param user_configs RestConfig User configurations ----@return RestConfig +---@return RestConfig rest.nvim configuration table function config.set(user_configs) local check = require("rest-nvim.config.check") @@ -153,6 +154,7 @@ function config.set(user_configs) }) if not ok then + ---@cast err string conf.logger:error(err) end diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index acae7835..b9ff1de9 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -94,7 +94,7 @@ function functions.exec(scope) end ---Find a list of environment files starting from the current directory ----@return string[] +---@return string[] Environment variable files path function functions.find_env_files() -- We are currently looking for any ".*env*" file, e.g. ".env", ".env.json" -- @@ -110,7 +110,8 @@ end ---Manage the environment file that is currently in use while running requests --- ---If you choose to `set` the environment, you must provide a `path` to the environment file. ----@param action string Determines the action to be taken. Can be: `set` or `show` (default) +---@param action string|nil Determines the action to be taken. Can be: `set` or `show` (default) +---@param path string|nil Path to the environment variables file function functions.env(action, path) -- TODO: add a `select` action later to open some kind of prompt to select one of many detected "*env*" files vim.validate({ @@ -131,6 +132,7 @@ function functions.env(action, path) end if action == "set" then + ---@cast path string if utils.file_exists(path) then _G._rest_nvim.env_file = path ---@diagnostic disable-next-line need-check-nil diff --git a/lua/rest-nvim/parser/dynamic_vars.lua b/lua/rest-nvim/parser/dynamic_vars.lua index 377862aa..1d40d249 100644 --- a/lua/rest-nvim/parser/dynamic_vars.lua +++ b/lua/rest-nvim/parser/dynamic_vars.lua @@ -15,6 +15,7 @@ math.randomseed(os.time()) ---@return string local function uuid() local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" + ---@diagnostic disable-next-line redundant-return-value return string.gsub(template, "[xy]", function(c) local v = (c == "x") and random(0, 0xf) or random(8, 0xb) return string.format("%x", v) @@ -23,7 +24,7 @@ end ---Retrieve all dynamic variables from both rest.nvim and the ones declared by ---the user on his configuration ----@return { [string]: fun():string }[] +---@return { [string]: fun():string }[] An array-like table of tables which contains dynamic variables definition function dynamic_vars.retrieve_all() local user_variables = _G._rest_nvim.custom_dynamic_variables or {} local rest_variables = { @@ -42,7 +43,7 @@ end ---Look for a dynamic variable and evaluate it ---@param name string The dynamic variable name ----@return string|nil +---@return string|nil The dynamic variable value or `nil` if the dynamic variable was not found function dynamic_vars.read(name) local logger = _G._rest_nvim.logger diff --git a/lua/rest-nvim/parser/env_vars.lua b/lua/rest-nvim/parser/env_vars.lua index 6295a1cb..3417c566 100644 --- a/lua/rest-nvim/parser/env_vars.lua +++ b/lua/rest-nvim/parser/env_vars.lua @@ -49,6 +49,7 @@ function env_vars.read_file() for _, var in ipairs(vars_tbl) do local variable = vim.split(var, "=") local variable_name = variable[1] + local variable_value -- In case some weirdo adds a `=` character to his ENV value if #variable > 2 then table.remove(variable, 1) diff --git a/lua/rest-nvim/parser/init.lua b/lua/rest-nvim/parser/init.lua index 4e32b42d..e3e72386 100644 --- a/lua/rest-nvim/parser/init.lua +++ b/lua/rest-nvim/parser/init.lua @@ -112,12 +112,12 @@ end ---Traverse a request tree-sitter node and retrieve all its children nodes ---@param req_node TSNode Tree-sitter request node ----@return NodesList +---@return { headers: TSNode[], [string]: TSNode }[] local function traverse_request(req_node) local child_nodes = { headers = {}, } - for child, field_name in req_node:iter_children() do + for child, _ in req_node:iter_children() do local child_type = child:type() if child_type == "header" then table.insert(child_nodes.headers, child) @@ -204,7 +204,7 @@ end ---Parse a request tree-sitter node ---@param children_nodes NodesList Tree-sitter nodes ---@param variables Variables HTTP document variables list ----@return table +---@return table A table containing the request target `url` and `method` to be used function parser.parse_request(children_nodes, variables) local request = {} for node_type, node in pairs(children_nodes) do @@ -227,12 +227,12 @@ function parser.parse_request(children_nodes, variables) end ---Parse request headers tree-sitter nodes ----@param children_nodes TSNode[] Tree-sitter nodes +---@param header_nodes NodesList Tree-sitter nodes ---@param variables Variables HTTP document variables list ----@return table -function parser.parse_headers(children_nodes, variables) +---@return table Table containing the headers in a key-value style +function parser.parse_headers(header_nodes, variables) local headers = {} - for _, node in ipairs(children_nodes.headers) do + for _, node in ipairs(header_nodes) do local name = assert(get_node_text(node:field("name")[1], 0)) local value = vim.trim(assert(get_node_text(node:field("value")[1], 0))) @@ -323,7 +323,7 @@ end ---Parse a request tree-sitter node body ---@param children_nodes NodesList Tree-sitter nodes ---@param variables Variables HTTP document variables list ----@return table +---@return table Decoded body table function parser.parse_body(children_nodes, variables) local body = {} @@ -360,9 +360,9 @@ function parser.parse_body(children_nodes, variables) return body end ----Get a script variable node and return +---Get a script variable node and return its content ---@param req_node TSNode Tree-sitter request node ----@return string +---@return string Script variables content function parser.parse_script(req_node) -- Get the next named sibling of the current request node, -- if the request does not have any sibling or if it is not @@ -389,7 +389,7 @@ end ---Parse a request and return the request on itself, its headers and body ---@param req_node TSNode Tree-sitter request node ----@return Request +---@return Request Table containing the request data function parser.parse(req_node) local ast = { request = {}, @@ -404,7 +404,9 @@ function parser.parse(req_node) local document_variables = traverse_variables(document_node) ast.request = parser.parse_request(request_children_nodes, document_variables) - ast.headers = parser.parse_headers(request_children_nodes, document_variables) + -- TODO: make parse_headers use a traverse_headers function to avoid these diagnostic warnings + ---@diagnostic disable-next-line undefined-field + ast.headers = parser.parse_headers(request_children_nodes.headers, document_variables) ast.body = parser.parse_body(request_children_nodes, document_variables) ast.script = parser.parse_script(req_node) diff --git a/lua/rest-nvim/result/init.lua b/lua/rest-nvim/result/init.lua index ab3b5c34..1f4c61d2 100644 --- a/lua/rest-nvim/result/init.lua +++ b/lua/rest-nvim/result/init.lua @@ -114,6 +114,9 @@ function result.write_block(bufnr, block, rewrite, newline) vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) end +---Display results buffer window +---@param bufnr number The target buffer +---@param stats table Request statistics function result.display_buf(bufnr, stats) local is_result_displayed = false @@ -198,9 +201,9 @@ function result.write_res(bufnr, res) end end, headers) - -- ...... + -- -- Content-Type: application/json - -- ...... + -- ---@diagnostic disable-next-line inject-field winbar.pane_map[2].contents = headers diff --git a/lua/rest-nvim/utils.lua b/lua/rest-nvim/utils.lua index c6c72952..a0b69030 100644 --- a/lua/rest-nvim/utils.lua +++ b/lua/rest-nvim/utils.lua @@ -30,7 +30,7 @@ end function utils.read_file(path) local logger = _G._rest_nvim.logger - ---@cast content string + ---@type string|uv_fs_t|nil local content if utils.file_exists(path) then local file = uv.fs_open(path, "r", 438) @@ -46,6 +46,7 @@ function utils.read_file(path) return "" end + ---@cast content string return content end @@ -55,6 +56,7 @@ local transform = { ---@param time string ---@return string time = function(time) + ---@diagnostic disable-next-line cast-local-type time = tonumber(time) if time >= 60 then @@ -67,6 +69,7 @@ local transform = { local unit = 1 while time < 1 and unit <= #units do + ---@diagnostic disable-next-line cast-local-type time = time * 1000 unit = unit + 1 end From 582bc4d7289f47b966d50d6a0637698a73eacfa1 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 12 Feb 2024 08:09:07 -0400 Subject: [PATCH 082/159] ref(parser)!: decouple headers traverse logic from the `traverse_request` function into a dedicated `traverse_headers` function --- lua/rest-nvim/parser/init.lua | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/lua/rest-nvim/parser/init.lua b/lua/rest-nvim/parser/init.lua index e3e72386..2d9456b8 100644 --- a/lua/rest-nvim/parser/init.lua +++ b/lua/rest-nvim/parser/init.lua @@ -112,22 +112,33 @@ end ---Traverse a request tree-sitter node and retrieve all its children nodes ---@param req_node TSNode Tree-sitter request node ----@return { headers: TSNode[], [string]: TSNode }[] +---@return NodesList local function traverse_request(req_node) - local child_nodes = { - headers = {}, - } + local child_nodes = {} for child, _ in req_node:iter_children() do local child_type = child:type() - if child_type == "header" then - table.insert(child_nodes.headers, child) - else + if child_type ~= "header" then child_nodes[child_type] = child end end return child_nodes end +---Traverse a request tree-sitter node and retrieve all its children header nodes +---@param req_node TSNode Tree-sitter request node +---@return NodesList An array-like table containing the request header nodes +local function traverse_headers(req_node) + local headers = {} + for child, _ in req_node:iter_children() do + local child_type = child:type() + if child_type == "header" then + table.insert(headers, child) + end + end + + return headers +end + ---Traverse the document tree-sitter node and retrieve all the `variable_declaration` nodes ---@param document_node TSNode Tree-sitter document node ---@return Variables @@ -229,7 +240,7 @@ end ---Parse request headers tree-sitter nodes ---@param header_nodes NodesList Tree-sitter nodes ---@param variables Variables HTTP document variables list ----@return table Table containing the headers in a key-value style +---@return table A table containing the headers in a key-value style function parser.parse_headers(header_nodes, variables) local headers = {} for _, node in ipairs(header_nodes) do @@ -400,13 +411,13 @@ function parser.parse(req_node) local document_node = parser.look_behind_until(nil, "document") local request_children_nodes = traverse_request(req_node) + local request_header_nodes = traverse_headers(req_node) + ---@cast document_node TSNode local document_variables = traverse_variables(document_node) ast.request = parser.parse_request(request_children_nodes, document_variables) - -- TODO: make parse_headers use a traverse_headers function to avoid these diagnostic warnings - ---@diagnostic disable-next-line undefined-field - ast.headers = parser.parse_headers(request_children_nodes.headers, document_variables) + ast.headers = parser.parse_headers(request_header_nodes, document_variables) ast.body = parser.parse_body(request_children_nodes, document_variables) ast.script = parser.parse_script(req_node) From 2347230a72a2185f21a24d38b4ce0413dcf090d8 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 12 Feb 2024 08:10:09 -0400 Subject: [PATCH 083/159] feat(doc): add new technical documentation generated by using `lemmy-help` --- doc/rest-nvim-api.txt | 275 +++++++++++++++++++++++++++++++++++++++ doc/rest-nvim-config.txt | 87 +++++++++++++ doc/rest-nvim-curl.txt | 18 +++ doc/rest-nvim-parser.txt | 175 +++++++++++++++++++++++++ 4 files changed, 555 insertions(+) create mode 100644 doc/rest-nvim-api.txt create mode 100644 doc/rest-nvim-config.txt create mode 100644 doc/rest-nvim-curl.txt create mode 100644 doc/rest-nvim-parser.txt diff --git a/doc/rest-nvim-api.txt b/doc/rest-nvim-api.txt new file mode 100644 index 00000000..4c0d9483 --- /dev/null +++ b/doc/rest-nvim-api.txt @@ -0,0 +1,275 @@ +============================================================================== +rest.nvim Lua API *rest-nvim.api* + + +The Lua API for rest.nvim +Intended for use by third-party modules that extend its functionalities. + + + *api.register_rest_autocmd* +api.register_rest_autocmd({events}, {cb}, {description}) + + + Parameters: ~ + {events} (string[]) Autocommand events, see `:h events` + {cb} (string|fun(args:table)) Autocommand lua callback, runs a Vimscript command instead if it is a `string` + {description} (string) Autocommand description + + + *api.register_rest_subcommand* +api.register_rest_subcommand({name}, {cmd}) + Register a new `:Rest` subcommand + + Parameters: ~ + {name} (string) The name of the subcommand to register + {cmd} (RestCmd) + + +============================================================================== +rest.nvim utilities *rest-nvim.utils* + + + rest.nvim utility functions + + +utils.file_exists({path}) *utils.file_exists* + Check if a file exists in the given `path` + + Parameters: ~ + {path} (string) file path + + Returns: ~ + (boolean) + + +utils.read_file({path}) *utils.read_file* + Read a file if it exists + + Parameters: ~ + {path} (string) file path + + Returns: ~ + (string) + + +============================================================================== +rest.nvim functions *rest-nvim.functions* + + + rest.nvim functions + + +functions.exec({scope}) *functions.exec* + Execute one or several HTTP requests depending on given `scope` + and return request(s) results in a table that will be used to render results + in a buffer. + + Parameters: ~ + {scope} (string) Defines the request execution scope. Can be: `last`, `cursor` (default) or `document` + + +functions.find_env_files() *functions.find_env_files* + Find a list of environment files starting from the current directory + + Returns: ~ + (string[]) variable files path + + +functions.env({action}, {path}) *functions.env* + Manage the environment file that is currently in use while running requests + + If you choose to `set` the environment, you must provide a `path` to the environment file. + + Parameters: ~ + {action} (string|nil) Determines the action to be taken. Can be: `set` or `show` (default) + {path} (string|nil) Path to the environment variables file + + +functions.cycle_result_pane({cycle}) *functions.cycle_result_pane* + Cycle through the results buffer winbar panes + + Parameters: ~ + {cycle} (string) Cycle direction, can be: `"next"` or `"prev"` + + +============================================================================== +rest.nvim logger *rest-nvim.logger* + + +Logging library for rest.nvim, slightly inspired by rmagatti/logger.nvim +Intended for use by internal and third-party modules. + +Default logger instance is made during the `setup` and can be accessed +by anyone through the `_G._rest_nvim.logger` configuration field +that is set automatically. + +------------------------------------------------------------------------------ + +Usage: + +```lua +local logger = require("rest-nvim.logger"):new({ level = "debug" }) + +logger:set_log_level("info") + +logger:info("This is an info log") + -- [rest.nvim] INFO: This is an info log +``` + + +Logger *Logger* + + +LoggerLevels *LoggerLevels* + + +LoggerConfig *LoggerConfig* + + Fields: ~ + {level_name} (string) Logging level name. Default is `"info"` + {save_logs} (boolean) Whether to save log messages into a `.log` file. Default is `true` + + +logger:new({opts}) *logger:new* + Create a new logger instance + + Parameters: ~ + {opts} (LoggerConfig) Logger configuration + + Returns: ~ + (Logger) + + +logger:set_log_level({level}) *logger:set_log_level* + Set the log level for the logger + + Parameters: ~ + {level} (string) New logging level + + See: ~ + |vim.log.levels| + + +logger:trace({msg}) *logger:trace* + Log a trace message + + Parameters: ~ + {msg} (string) Log message + + +logger:debug({msg}) *logger:debug* + Log a debug message + + Parameters: ~ + {msg} (string) Log message + + +logger:info({msg}) *logger:info* + Log an info message + + Parameters: ~ + {msg} (string) Log message + + +logger:warn({msg}) *logger:warn* + Log a warning message + + Parameters: ~ + {msg} (string) Log message + + +logger:error({msg}) *logger:error* + Log an error message + + Parameters: ~ + {msg} (string) Log message + + +============================================================================== +rest.nvim result buffer *rest-nvim.result* + + + rest.nvim result buffer handling + + +result.bufnr *result.bufnr* + Results buffer handler number + + Type: ~ + (number|nil) + + +result.get_or_create_buf() *result.get_or_create_buf* + + Returns: ~ + (number) handler number + + +result.write_block() *result.write_block* + + See: ~ + |vim.api.nvim_buf_set_lines| + + +result.display_buf({bufnr}, {stats}) *result.display_buf* + Display results buffer window + + Parameters: ~ + {bufnr} (number) The target buffer + {stats} (table) Request statistics + + +result.write_res({bufnr}, {res}) *result.write_res* + Write request results in the given buffer and display it + + Parameters: ~ + {bufnr} (number) The target buffer + {res} (table) Request results + + +============================================================================== +rest.nvim result buffer winbar add-on *rest-nvim.result.winbar* + + + rest.nvim result buffer winbar + + +winbar.current_pane_index *winbar.current_pane_index* + Current pane index in the results window winbar + + Type: ~ + (number) + + +winbar.get_content({stats}) *winbar.get_content* + Create the winbar contents and return them + + Parameters: ~ + {stats} (table) Request statistics + + Returns: ~ + (string) + + +ResultPane *ResultPane* + + Fields: ~ + {name} (string) Pane name + {contents} (string[]) Pane contents + + +winbar.set_hl() *winbar.set_hl* + Set the results window winbar highlighting groups + + +winbar.set_pane({selected}) *winbar.set_pane* + Select the winbar panel based on the pane index and set the pane contents + + If the pane index is higher than 4 or lower than 1, it will cycle through + the panes, e.g. >= 5 gets converted to 1 and <= 0 gets converted to 4 + + Parameters: ~ + {selected} (number) winbar pane index + + +vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/doc/rest-nvim-config.txt b/doc/rest-nvim-config.txt new file mode 100644 index 00000000..3880ce8e --- /dev/null +++ b/doc/rest-nvim-config.txt @@ -0,0 +1,87 @@ +============================================================================== +rest.nvim configuration *rest-nvim.config* + + + rest.nvim configuration options + + +RestConfigDebug *RestConfigDebug* + + Fields: ~ + {unrecognized_configs} (string[]) Unrecognized configuration options + + +RestConfigLogs *RestConfigLogs* + + Fields: ~ + {level} (string) The logging level name, see `:h vim.log.levels`. Default is `"info"` + {save} (boolean) Whether to save log messages into a `.log` file. Default is `true` + + +RestConfigResult *RestConfigResult* + + Fields: ~ + {split} (RestConfigResultSplit) Result split window behavior + {behavior} (RestConfigResultBehavior) Result buffer behavior + + +RestConfigResultSplit *RestConfigResultSplit* + + Fields: ~ + {horizontal} (boolean) Open request results in a horizontal split + {in_place} (boolean) Keep the HTTP file buffer above|left when split horizontal|vertical + {stay_in_current_window_after_split} (boolean) Stay in the current window (HTTP file) or change the focus to the results window + + +RestConfigResultBehavior *RestConfigResultBehavior* + + Fields: ~ + {show_info} (RestConfigResultInfo) Request results information + {statistics} (RestConfigResultStats) Request results statistics + {formatters} (RestConfigResultFormatters) Formatters for the request results body + + +RestConfigResultInfo *RestConfigResultInfo* + + Fields: ~ + {url} (boolean) Display the request URL + {headers} (boolean) Display the request headers + {http_info} (boolean) Display the request HTTP information + {curl_command} (boolean) Display the cURL command that was used for the request + + +RestConfigResultStats *RestConfigResultStats* + + Fields: ~ + {enable} (boolean) Whether enable statistics or not + {stats} (string[]|) + + +RestConfigResultFormatters *RestConfigResultFormatters* + + Fields: ~ + {json} (string|fun(body:string):string) JSON formatter + {html} (string|fun(body:string):string) HTML formatter + + +RestConfigHighlight *RestConfigHighlight* + + Fields: ~ + {enable} (boolean) Whether current request highlighting is enabled or not + {timeout} (number) Duration time of the request highlighting in milliseconds + + +RestConfig *RestConfig* + + Fields: ~ + {client} (string) The HTTP client to be used when running requests, default is `"curl"` + {env_file} (string) Environment variables file to be used for the request variables in the document + {encode_url} (boolean) Encode URL before making request + {skip_ssl_verification} (boolean) Skip SSL verification, useful for unknown certificates + {custom_dynamic_variables} () + + +config.set() *config.set* + + +vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/doc/rest-nvim-curl.txt b/doc/rest-nvim-curl.txt new file mode 100644 index 00000000..230cde05 --- /dev/null +++ b/doc/rest-nvim-curl.txt @@ -0,0 +1,18 @@ +============================================================================== +rest.nvim cURL client *rest-nvim.client.curl* + + + rest.nvim cURL client implementation + + +client.request({request}) *client.request* + Execute an HTTP request using cURL + + Parameters: ~ + {request} (Request) Request data to be passed to cURL + + Returns: ~ + (table) request information (url, method, headers, body, etc) + + +vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/doc/rest-nvim-parser.txt b/doc/rest-nvim-parser.txt new file mode 100644 index 00000000..23b43915 --- /dev/null +++ b/doc/rest-nvim-parser.txt @@ -0,0 +1,175 @@ +============================================================================== +rest.nvim parsing module dynamic variables *rest-nvim.parser.dynamic_vars* + + + rest.nvim dynamic variables + + +dynamic_vars.retrieve_all() *dynamic_vars.retrieve_all* + Retrieve all dynamic variables from both rest.nvim and the ones declared by + the user on his configuration + @return { [string]: fun():string }[] An array-like table of tables which contains dynamic variables definition + + +dynamic_vars.read({name}) *dynamic_vars.read* + Look for a dynamic variable and evaluate it + + Parameters: ~ + {name} (string) The dynamic variable name + + Returns: ~ + (string|nil) dynamic variable value or `nil` if the dynamic variable was not found + + +============================================================================== +rest.nvim parsing module environment variables *rest-nvim.parser.env_vars* + + + rest.nvim environment variables + + +env_vars.set_var({name}, {value}) *env_vars.set_var* + Set an environment variable for the current Neovim session + + Parameters: ~ + {name} (string) Variable name + {value} (string|number|boolean) Variable value + + See: ~ + |vim.env| + + +env_vars.read_file() *env_vars.read_file* + Read the environment variables file from the rest.nvim configuration + and store all the environment variables in the `vim.env` metatable + + See: ~ + |vim.env| + + +============================================================================== +rest.nvim parsing module script variables *rest-nvim.parser.script_vars* + + + rest.nvim script variables + + +script_vars.load({script_str}, {res}) *script_vars.load* + Load a script_variable content and evaluate it + + Parameters: ~ + {script_str} (string) The script variable content + {res} (table) Request response body + + +============================================================================== +rest.nvim tree-sitter parsing module *rest-nvim.parser* + + +Parsing module with tree-sitter, we use tree-sitter there to extract +all the document nodes and their content from the HTTP files, then we +start doing some other internal parsing like variables expansion and so on + + +NodesList *NodesList* + + Type: ~ + + + +Variables *Variables* + + Type: ~ + + + +parser.get_node_at_cursor() *parser.get_node_at_cursor* + + Returns: ~ + (string|nil) type + + + *parser.look_behind_until* +parser.look_behind_until({node}, {query}) + Recursively look behind `node` until `query` node type is found + + Parameters: ~ + {node} (TSNode|nil) Tree-sitter node, defaults to the node at the cursor position if not passed + {query} (string) The tree-sitter node type that we are looking for + + Returns: ~ + (TSNode|nil) + + + *parser.parse_request* +parser.parse_request({children_nodes}, {variables}) + Parse a request tree-sitter node + + Parameters: ~ + {children_nodes} (NodesList) Tree-sitter nodes + {variables} (Variables) HTTP document variables list + + Returns: ~ + (table) table containing the request target `url` and `method` to be used + + + *parser.parse_headers* +parser.parse_headers({header_nodes}, {variables}) + Parse request headers tree-sitter nodes + + Parameters: ~ + {header_nodes} (NodesList) Tree-sitter nodes + {variables} (Variables) HTTP document variables list + + Returns: ~ + (table) table containing the headers in a key-value style + + + *parser.parse_body* +parser.parse_body({children_nodes}, {variables}) + Parse a request tree-sitter node body + + Parameters: ~ + {children_nodes} (NodesList) Tree-sitter nodes + {variables} (Variables) HTTP document variables list + + Returns: ~ + (table) body table + + +parser.parse_script({req_node}) *parser.parse_script* + Get a script variable node and return its content + + Parameters: ~ + {req_node} (TSNode) Tree-sitter request node + + Returns: ~ + (string) variables content + + +RequestReq *RequestReq* + + Fields: ~ + {method} (string) The request method + {url} (string) The request URL + {http_version?} (string) The request HTTP protocol + + +Request *Request* + + Fields: ~ + {request} (RequestReq) + {headers} () + + +parser.parse({req_node}) *parser.parse* + Parse a request and return the request on itself, its headers and body + + Parameters: ~ + {req_node} (TSNode) Tree-sitter request node + + Returns: ~ + (Request) containing the request data + + +vim:tw=78:ts=8:noet:ft=help:norl: From f6411f383793bab51819ae784328cb872ad37a32 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 12 Feb 2024 08:21:54 -0400 Subject: [PATCH 084/159] feat(commands): add `:Rest logs` command, improve documentation --- lua/rest-nvim/commands.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lua/rest-nvim/commands.lua b/lua/rest-nvim/commands.lua index e881976a..f88ea89e 100644 --- a/lua/rest-nvim/commands.lua +++ b/lua/rest-nvim/commands.lua @@ -10,10 +10,22 @@ --- run {scope?} Execute one or several HTTP requests depending --- on given `scope`. This scope can be either `last`, --- `cursor` (default) or `document`. +--- --- last Re-run the last executed request, alias to `run last` --- to retain backwards compatibility with the old keybinds --- layout. --- +--- logs Open the rest.nvim logs file in a new tab. +--- +--- env {action?} {path?} Manage the environment file that is currently in use while +--- running requests. If you choose to `set` the environment, +--- you must provide a `path` to the environment file. The +--- default action is `show`, which displayes the current +--- environment file path. +--- +--- result {direction?} Cycle through the results buffer winbar panes. The cycle +--- direction can be either `next` or `prev`. +--- ---@brief ]] ---@class RestCmd @@ -53,6 +65,12 @@ local rest_command_tbl = { functions.exec("last") end, }, + logs = { + impl = function(_) + local logs_path = table.concat({ vim.fn.stdpath("log"), "rest.nvim.log" }, "/") + vim.cmd("tabedit " .. logs_path) + end, + }, env = { impl = function(args) local logger = _G._rest_nvim.logger From 83424e35d5ce0e0921d66f7cce5a08f66b1fa9b4 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 12 Feb 2024 08:22:16 -0400 Subject: [PATCH 085/159] docs(doc): update tags file --- doc/tags | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/doc/tags b/doc/tags index e1967e62..71591196 100644 --- a/doc/tags +++ b/doc/tags @@ -1,3 +1,47 @@ +Logger rest-nvim-api.txt /*Logger* +LoggerConfig rest-nvim-api.txt /*LoggerConfig* +LoggerLevels rest-nvim-api.txt /*LoggerLevels* +NodesList rest-nvim-parser.txt /*NodesList* +Request rest-nvim-parser.txt /*Request* +RequestReq rest-nvim-parser.txt /*RequestReq* +RestConfig rest-nvim-config.txt /*RestConfig* +RestConfigDebug rest-nvim-config.txt /*RestConfigDebug* +RestConfigHighlight rest-nvim-config.txt /*RestConfigHighlight* +RestConfigLogs rest-nvim-config.txt /*RestConfigLogs* +RestConfigResult rest-nvim-config.txt /*RestConfigResult* +RestConfigResultBehavior rest-nvim-config.txt /*RestConfigResultBehavior* +RestConfigResultFormatters rest-nvim-config.txt /*RestConfigResultFormatters* +RestConfigResultInfo rest-nvim-config.txt /*RestConfigResultInfo* +RestConfigResultSplit rest-nvim-config.txt /*RestConfigResultSplit* +RestConfigResultStats rest-nvim-config.txt /*RestConfigResultStats* +ResultPane rest-nvim-api.txt /*ResultPane* +Variables rest-nvim-parser.txt /*Variables* +api.register_rest_autocmd rest-nvim-api.txt /*api.register_rest_autocmd* +api.register_rest_subcommand rest-nvim-api.txt /*api.register_rest_subcommand* +client.request rest-nvim-curl.txt /*client.request* +config.set rest-nvim-config.txt /*config.set* +dynamic_vars.read rest-nvim-parser.txt /*dynamic_vars.read* +dynamic_vars.retrieve_all rest-nvim-parser.txt /*dynamic_vars.retrieve_all* +env_vars.read_file rest-nvim-parser.txt /*env_vars.read_file* +env_vars.set_var rest-nvim-parser.txt /*env_vars.set_var* +functions.cycle_result_pane rest-nvim-api.txt /*functions.cycle_result_pane* +functions.env rest-nvim-api.txt /*functions.env* +functions.exec rest-nvim-api.txt /*functions.exec* +functions.find_env_files rest-nvim-api.txt /*functions.find_env_files* +logger:debug rest-nvim-api.txt /*logger:debug* +logger:error rest-nvim-api.txt /*logger:error* +logger:info rest-nvim-api.txt /*logger:info* +logger:new rest-nvim-api.txt /*logger:new* +logger:set_log_level rest-nvim-api.txt /*logger:set_log_level* +logger:trace rest-nvim-api.txt /*logger:trace* +logger:warn rest-nvim-api.txt /*logger:warn* +parser.get_node_at_cursor rest-nvim-parser.txt /*parser.get_node_at_cursor* +parser.look_behind_until rest-nvim-parser.txt /*parser.look_behind_until* +parser.parse rest-nvim-parser.txt /*parser.parse* +parser.parse_body rest-nvim-parser.txt /*parser.parse_body* +parser.parse_headers rest-nvim-parser.txt /*parser.parse_headers* +parser.parse_request rest-nvim-parser.txt /*parser.parse_request* +parser.parse_script rest-nvim-parser.txt /*parser.parse_script* rest-nvim rest-nvim.txt /*rest-nvim* rest-nvim-contents rest-nvim.txt /*rest-nvim-contents* rest-nvim-contributing rest-nvim.txt /*rest-nvim-contributing* @@ -14,4 +58,28 @@ rest-nvim-usage-dynamic-variables rest-nvim.txt /*rest-nvim-usage-dynamic-variab rest-nvim-usage-environment-variables rest-nvim.txt /*rest-nvim-usage-environment-variables* rest-nvim-usage-external-files rest-nvim.txt /*rest-nvim-usage-external-files* rest-nvim-usage-requests rest-nvim.txt /*rest-nvim-usage-requests* +rest-nvim.api rest-nvim-api.txt /*rest-nvim.api* +rest-nvim.client.curl rest-nvim-curl.txt /*rest-nvim.client.curl* +rest-nvim.config rest-nvim-config.txt /*rest-nvim.config* +rest-nvim.functions rest-nvim-api.txt /*rest-nvim.functions* +rest-nvim.logger rest-nvim-api.txt /*rest-nvim.logger* +rest-nvim.parser rest-nvim-parser.txt /*rest-nvim.parser* +rest-nvim.parser.dynamic_vars rest-nvim-parser.txt /*rest-nvim.parser.dynamic_vars* +rest-nvim.parser.env_vars rest-nvim-parser.txt /*rest-nvim.parser.env_vars* +rest-nvim.parser.script_vars rest-nvim-parser.txt /*rest-nvim.parser.script_vars* +rest-nvim.result rest-nvim-api.txt /*rest-nvim.result* +rest-nvim.result.winbar rest-nvim-api.txt /*rest-nvim.result.winbar* rest-nvim.txt rest-nvim.txt /*rest-nvim.txt* +rest-nvim.utils rest-nvim-api.txt /*rest-nvim.utils* +result.bufnr rest-nvim-api.txt /*result.bufnr* +result.display_buf rest-nvim-api.txt /*result.display_buf* +result.get_or_create_buf rest-nvim-api.txt /*result.get_or_create_buf* +result.write_block rest-nvim-api.txt /*result.write_block* +result.write_res rest-nvim-api.txt /*result.write_res* +script_vars.load rest-nvim-parser.txt /*script_vars.load* +utils.file_exists rest-nvim-api.txt /*utils.file_exists* +utils.read_file rest-nvim-api.txt /*utils.read_file* +winbar.current_pane_index rest-nvim-api.txt /*winbar.current_pane_index* +winbar.get_content rest-nvim-api.txt /*winbar.get_content* +winbar.set_hl rest-nvim-api.txt /*winbar.set_hl* +winbar.set_pane rest-nvim-api.txt /*winbar.set_pane* From fc813e8b13cd054e151e90bb9dae1903bb459a8e Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 12 Feb 2024 08:24:08 -0400 Subject: [PATCH 086/159] fix(commands): typo in documentation --- lua/rest-nvim/commands.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/commands.lua b/lua/rest-nvim/commands.lua index f88ea89e..f05e6988 100644 --- a/lua/rest-nvim/commands.lua +++ b/lua/rest-nvim/commands.lua @@ -20,7 +20,7 @@ --- env {action?} {path?} Manage the environment file that is currently in use while --- running requests. If you choose to `set` the environment, --- you must provide a `path` to the environment file. The ---- default action is `show`, which displayes the current +--- default action is `show`, which displays the current --- environment file path. --- --- result {direction?} Cycle through the results buffer winbar panes. The cycle From 5d220e8b2fe553b08ac879d15a78ac0adc9a09a8 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 12 Feb 2024 08:24:40 -0400 Subject: [PATCH 087/159] feat(doc): add `:Rest` command documentation --- doc/rest-nvim-commands.txt | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 doc/rest-nvim-commands.txt diff --git a/doc/rest-nvim-commands.txt b/doc/rest-nvim-commands.txt new file mode 100644 index 00000000..fa13bc8c --- /dev/null +++ b/doc/rest-nvim-commands.txt @@ -0,0 +1,37 @@ +============================================================================== +rest.nvim commands *rest-nvim.commands* + + + `:Rest {command {args?}}` + + command action +------------------------------------------------------------------------------ + + run {scope?} Execute one or several HTTP requests depending + on given `scope`. This scope can be either `last`, + `cursor` (default) or `document`. + + last Re-run the last executed request, alias to `run last` + to retain backwards compatibility with the old keybinds + layout. + + logs Open the rest.nvim logs file in a new tab. + + env {action?} {path?} Manage the environment file that is currently in use while + running requests. If you choose to `set` the environment, + you must provide a `path` to the environment file. The + default action is `show`, which displays the current + environment file path. + + result {direction?} Cycle through the results buffer winbar panes. The cycle + direction can be either `next` or `prev`. + + +RestCmd *RestCmd* + + Fields: ~ + {impl} (fun(args:string[],opts:vim.api.keyset.user_command)) The command implementation + {complete?} (fun(subcmd_arg_lead:string):string[]) Command completions callback, taking the lead of the subcommand's argument + + +vim:tw=78:ts=8:noet:ft=help:norl: From f3c57a063aa5e4a654d752eeda585e22b2ee2f29 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 12 Feb 2024 08:24:48 -0400 Subject: [PATCH 088/159] docs(doc): update tags file --- doc/tags | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/tags b/doc/tags index 71591196..a73cec35 100644 --- a/doc/tags +++ b/doc/tags @@ -4,6 +4,7 @@ LoggerLevels rest-nvim-api.txt /*LoggerLevels* NodesList rest-nvim-parser.txt /*NodesList* Request rest-nvim-parser.txt /*Request* RequestReq rest-nvim-parser.txt /*RequestReq* +RestCmd rest-nvim-commands.txt /*RestCmd* RestConfig rest-nvim-config.txt /*RestConfig* RestConfigDebug rest-nvim-config.txt /*RestConfigDebug* RestConfigHighlight rest-nvim-config.txt /*RestConfigHighlight* @@ -60,6 +61,7 @@ rest-nvim-usage-external-files rest-nvim.txt /*rest-nvim-usage-external-files* rest-nvim-usage-requests rest-nvim.txt /*rest-nvim-usage-requests* rest-nvim.api rest-nvim-api.txt /*rest-nvim.api* rest-nvim.client.curl rest-nvim-curl.txt /*rest-nvim.client.curl* +rest-nvim.commands rest-nvim-commands.txt /*rest-nvim.commands* rest-nvim.config rest-nvim-config.txt /*rest-nvim.config* rest-nvim.functions rest-nvim-api.txt /*rest-nvim.functions* rest-nvim.logger rest-nvim-api.txt /*rest-nvim.logger* From 89637a61763fa42beeec0bd0d7bf324badd1f92b Mon Sep 17 00:00:00 2001 From: bingcoke <248677315@qq.com> Date: Wed, 31 Jan 2024 12:27:17 +0800 Subject: [PATCH 089/159] feat: add telescope extension (#278) --- README.md | 23 +++++++++++ lua/rest-nvim/config/check.lua | 2 + lua/rest-nvim/config/init.lua | 4 ++ lua/telescope/_extensions/rest.lua | 65 ++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 lua/telescope/_extensions/rest.lua diff --git a/README.md b/README.md index 15d85a9b..ac3972fd 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,9 @@ use { -- Jump to request line on run jump_to_request = false, env_file = '.env', + -- for telescope select + env_pattern = "\\.env$", + env_edit_command = "tabedit", custom_dynamic_variables = {}, yank_dry_run = true, }) @@ -186,6 +189,26 @@ request method (e.g. `GET`) and run `rest.nvim`. Run `export DEBUG_PLENARY="debug"` before starting nvim. Logs will appear most likely in ~/.cache/nvim/rest.nvim.log +## Telescope + +```lua + +-- first load extension +require("telescope").load_extension("rest") +-- then use telescope +require("telescope").extensions.rest.select_env() + +``` + +### Mappings + +- Enter: Select Env file +- Ctrl+O: Edit Env file + +### Config + +- env_pattern: For env file pattern +- env_edit_command: For env file edit command ## Contribute diff --git a/lua/rest-nvim/config/check.lua b/lua/rest-nvim/config/check.lua index 65fcd88a..60532b19 100644 --- a/lua/rest-nvim/config/check.lua +++ b/lua/rest-nvim/config/check.lua @@ -25,6 +25,8 @@ function check.validate(cfg) local ok, err = validate({ client = { cfg.client, "string" }, env_file = { cfg.env_file, "string" }, + env_pattern = { cfg.env_pattern, "string" }, + env_edit_command = { cfg.env_edit_command, "string" }, encode_url = { cfg.encode_url, "boolean" }, skip_ssl_verification = { cfg.skip_ssl_verification, "boolean" }, custom_dynamic_variables = { cfg.custom_dynamic_variables, "table" }, diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 98cc3692..293bcbf9 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -52,6 +52,8 @@ local logger = require("rest-nvim.logger") ---@class RestConfig ---@field client string The HTTP client to be used when running requests, default is `"curl"` ---@field env_file string Environment variables file to be used for the request variables in the document +---@field env_pattern string Environment variables file pattern for telescope.nvim +---@field env_edit_command string Neovim command to edit an environment file, default is `"tabedit"` ---@field encode_url boolean Encode URL before making request ---@field skip_ssl_verification boolean Skip SSL verification, useful for unknown certificates ---@field custom_dynamic_variables { [string]: fun(): string }[] Table of custom dynamic variables @@ -67,6 +69,8 @@ local logger = require("rest-nvim.logger") local default_config = { client = "curl", env_file = ".env", + env_pattern = "\\.env$", + env_edit_command = "tabedit", encode_url = true, skip_ssl_verification = false, custom_dynamic_variables = {}, diff --git a/lua/telescope/_extensions/rest.lua b/lua/telescope/_extensions/rest.lua new file mode 100644 index 00000000..eaeeebda --- /dev/null +++ b/lua/telescope/_extensions/rest.lua @@ -0,0 +1,65 @@ +local has_telescope, telescope = pcall(require, "telescope") + +if not has_telescope then + return +end + +local rest = require("rest-nvim") + +local state = require("telescope.actions.state") + +local action_state = require("telescope.actions.state") +local actions = require("telescope.actions") +local finders = require("telescope.finders") +local pickers = require("telescope.pickers") +local conf = require("telescope.config").values + +local config = require("rest-nvim.config") + +local function rest_env_select(opt) + local pattern = config.get("env_pattern") + local edit = config.get("env_edit_command") + + local command = string.format("fd -H '%s'", pattern) + local result = io.popen(command):read("*a") + + local lines = {} + for line in result:gmatch("[^\r\n]+") do + table.insert(lines, line) + end + + pickers + .new({}, { + prompt_title = "Select Evn", + finder = finders.new_table({ + results = lines, + }), + attach_mappings = function(prompt_bufnr, map) + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() + if selection == nil then + return + end + actions.close(prompt_bufnr) + rest.select_env(selection[1]) + end) + map("i", "", function() + actions.close(prompt_bufnr) + local selection = state.get_selected_entry(prompt_bufnr) + if selection == nil then + return + end + vim.api.nvim_command(edit .. " " .. selection[1]) + end) + return true + end, + previewer = conf.grep_previewer({}), + }) + :find() +end + +return telescope.register_extension({ + exports = { + select_env = rest_env_select, + }, +}) From 83bdcf82121a54aa634d7989def281bd2449b56b Mon Sep 17 00:00:00 2001 From: bingcoke <248677315@qq.com> Date: Wed, 31 Jan 2024 12:48:22 +0800 Subject: [PATCH 090/159] fix(telescope): doc and check select is nil --- README.md | 4 ++-- lua/telescope/_extensions/rest.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ac3972fd..f566f090 100644 --- a/README.md +++ b/README.md @@ -189,13 +189,13 @@ request method (e.g. `GET`) and run `rest.nvim`. Run `export DEBUG_PLENARY="debug"` before starting nvim. Logs will appear most likely in ~/.cache/nvim/rest.nvim.log -## Telescope +## Telescope Extension ```lua -- first load extension require("telescope").load_extension("rest") --- then use telescope +-- then use it require("telescope").extensions.rest.select_env() ``` diff --git a/lua/telescope/_extensions/rest.lua b/lua/telescope/_extensions/rest.lua index eaeeebda..05636243 100644 --- a/lua/telescope/_extensions/rest.lua +++ b/lua/telescope/_extensions/rest.lua @@ -30,17 +30,17 @@ local function rest_env_select(opt) pickers .new({}, { - prompt_title = "Select Evn", + prompt_title = "Select Env File", finder = finders.new_table({ results = lines, }), attach_mappings = function(prompt_bufnr, map) actions.select_default:replace(function() local selection = action_state.get_selected_entry() + actions.close(prompt_bufnr) if selection == nil then return end - actions.close(prompt_bufnr) rest.select_env(selection[1]) end) map("i", "", function() From a2ca4d6efa163f3c229a8b5cf1bc3e6636b61121 Mon Sep 17 00:00:00 2001 From: BingCoke <81607010+BingCoke@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:34:55 +0800 Subject: [PATCH 091/159] feat: add Lualine component (#279) chore: autoformat with stylua --- README.md | 29 +++++++++++++++++++++++++++++ lua/lualine/components/rest.lua | 31 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 lua/lualine/components/rest.lua diff --git a/README.md b/README.md index f566f090..5182815f 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,35 @@ require("telescope").extensions.rest.select_env() - env_pattern: For env file pattern - env_edit_command: For env file edit command +## Lualine + +We also have lualine component to get what env file you select! +And dont't worry, it will only show up under http files. + +```lua +-- Juse add a component in your lualine config +{ + sections = { + lualine_x = { + "rest" + } + } +} + +-- To custom icon and color +{ + sections = { + lualine_x = { + { + "rest", + icon = "", + fg = "#428890" + } + } + } +} +``` + ## Contribute 1. Fork it (https://github.com/rest-nvim/rest.nvim/fork) diff --git a/lua/lualine/components/rest.lua b/lua/lualine/components/rest.lua new file mode 100644 index 00000000..934e7c04 --- /dev/null +++ b/lua/lualine/components/rest.lua @@ -0,0 +1,31 @@ +local lualine_require = require("lualine_require") +local M = lualine_require.require("lualine.component"):extend() +local config = require("rest-nvim.config") + +local default_options = { + fg = "#428890", + icon = "", +} + +function M:init(options) + M.super.init(self, options) + self.options = vim.tbl_deep_extend("keep", self.options or {}, default_options) + self.icon = self.options.icon + + self.highlight_color = self:create_hl({ fg = self.options.fg }, "Rest") +end + +function M:apply_icon() + local default_highlight = self:get_default_hl() + self.status = self:format_hl(self.highlight_color) .. self.icon .. " " .. default_highlight .. self.status +end + +function M.update_status() + local current_filetype = vim.bo.filetype + if current_filetype == "http" then + return config.get("env_file") + end + return "" +end + +return M From 0f8a81b44d41a175b7e559ee98744e8daa76782c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:47:58 +0000 Subject: [PATCH 092/159] chore(main): release 1.1.0 --- CHANGELOG.md | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..cb826316 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,122 @@ +# Changelog + +## [1.1.0](https://github.com/rest-nvim/rest.nvim/compare/v1.0.1...v1.1.0) (2024-02-12) + + +### Features + +* add Lualine component ([#279](https://github.com/rest-nvim/rest.nvim/issues/279)) ([3e0d86d](https://github.com/rest-nvim/rest.nvim/commit/3e0d86d66db8858d7e847e7ad495274d6663c985)) +* add telescope extension ([#278](https://github.com/rest-nvim/rest.nvim/issues/278)) ([de3c0fd](https://github.com/rest-nvim/rest.nvim/commit/de3c0fd6130def3dea2ef3809dfbf4458a0946fc)) + + +### Bug Fixes + +* add host before url only if it starts with '/', otherwise it probably starts with 'http' ([f3d319f](https://github.com/rest-nvim/rest.nvim/commit/f3d319f4567d253977217963c1910e83eeb8c0af)) +* return non json body ([543b64c](https://github.com/rest-nvim/rest.nvim/commit/543b64cc639c01db319f00ba6a2b0767d0c8e8c1)) +* **telescope:** doc and check select is nil ([e862e72](https://github.com/rest-nvim/rest.nvim/commit/e862e725ba483b8c48585b44738767c86668d49e)) + +## [1.0.1](https://github.com/rest-nvim/rest.nvim/compare/v1.0.0...v1.0.1) (2024-01-24) + + +### Bug Fixes + +* **ci:** run luarocks CI on new releases too and let us run the CI manually ([6ce1f76](https://github.com/rest-nvim/rest.nvim/commit/6ce1f763247fb218ecf0fba749145c9688797afa)) + +## 1.0.0 (2023-11-29) + + +### Features + +* add callbacks ([dcea003](https://github.com/rest-nvim/rest.nvim/commit/dcea003675f4bb2445276654c89ea3d5f335cd26)) +* add config option for custom dynamic variables ([3da902d](https://github.com/rest-nvim/rest.nvim/commit/3da902dd2304a813eec321e6ff2bcf8e6c60ff7c)) +* add config.show_curl_command ([aea7c64](https://github.com/rest-nvim/rest.nvim/commit/aea7c64bdff1073beed9bd7fddb60cce7796d7ff)) +* add formatting CI so I don't have to care about formatting myself ([28a146d](https://github.com/rest-nvim/rest.nvim/commit/28a146d73a29072ae915da490f8cf99650b19b7e)) +* add luacheck (WIP) ([64eb52f](https://github.com/rest-nvim/rest.nvim/commit/64eb52fee8f0fc6a5f9b67f7d99cce67098b7e7a)) +* add selene linter CI ([28eaf15](https://github.com/rest-nvim/rest.nvim/commit/28eaf15db2f74c11863a4663022db60f1a1f3945)) +* add support for custom formatters ([b98ee9e](https://github.com/rest-nvim/rest.nvim/commit/b98ee9e7e3b0110c064903e81d2c2ed8b200013f)) +* add support for passing flags to curl ([3c46649](https://github.com/rest-nvim/rest.nvim/commit/3c46649aa2fec8282518e0743eff9d8305193d42)) +* add tree-sitter parser instructions, remove syntax highlighting lazy-loading. Closes [#30](https://github.com/rest-nvim/rest.nvim/issues/30) ([1831a5f](https://github.com/rest-nvim/rest.nvim/commit/1831a5faad9f76cf9cb2d1517b4719743abbcd20)) +* add verbose command to preview cURL requests ([f04b205](https://github.com/rest-nvim/rest.nvim/commit/f04b2051e9594f1fe1538cf397cea68709e2ebd4)) +* adding a RestLog command ([f1597ab](https://github.com/rest-nvim/rest.nvim/commit/f1597ab6df09b0f04196fa7d516a1b20c91f292e)) +* adding rockspec ([f87117d](https://github.com/rest-nvim/rest.nvim/commit/f87117d6a0d5bee54832c6e2962c369061484655)) +* allow selecting of env files with command ([090e253](https://github.com/rest-nvim/rest.nvim/commit/090e253c114b6d5448bac5869a28a6623c195e3a)) +* allow skipping SSL verification, closes [#46](https://github.com/rest-nvim/rest.nvim/issues/46) ([#50](https://github.com/rest-nvim/rest.nvim/issues/50)) ([cd6815d](https://github.com/rest-nvim/rest.nvim/commit/cd6815d1a04021e0ff206fc02c3b67d2e06d8a44)) +* apply `jq` formatting on any JSON content-type, see [#61](https://github.com/rest-nvim/rest.nvim/issues/61) ([155816b](https://github.com/rest-nvim/rest.nvim/commit/155816b52c1efa58a06c8c91d0339d32f1ed0e2e)) +* be able to use environment variables in headers ([b44e602](https://github.com/rest-nvim/rest.nvim/commit/b44e602b9f94beb77573fc83b42dd55802c530d5)) +* better syntax highlights in http result ([5b21f91](https://github.com/rest-nvim/rest.nvim/commit/5b21f91b462d224d6a2c2dc6cb74d1796dae20d0)) +* **ci:** add releaser CI to automate semver ([e0ca3ef](https://github.com/rest-nvim/rest.nvim/commit/e0ca3ef7f567e9829997ad006f8b1c255fbf8773)) +* config option for custom environment variables file ([#83](https://github.com/rest-nvim/rest.nvim/issues/83)) ([2542929](https://github.com/rest-nvim/rest.nvim/commit/254292969c7bb052d0123ceba5af0382cc4cb6c0)) +* defer splicing as late as possible ([f811cfe](https://github.com/rest-nvim/rest.nvim/commit/f811cfebf830040f43edfec9604c56028ab2f7db)) +* **doc:** use treesitter injection ([#243](https://github.com/rest-nvim/rest.nvim/issues/243)) ([8b62563](https://github.com/rest-nvim/rest.nvim/commit/8b62563cfb19ffe939a260504944c5975796a682)) +* dont display binary output in answer ([5ebe35f](https://github.com/rest-nvim/rest.nvim/commit/5ebe35f4d1a0a841ef231e03ce446e884c1651bf)) +* implement document vars, fix [#68](https://github.com/rest-nvim/rest.nvim/issues/68) ([7e45caf](https://github.com/rest-nvim/rest.nvim/commit/7e45cafe9c3d00cc40df80272a041c242f52b393)) +* implementing backend-agnostic run_request ([87941ab](https://github.com/rest-nvim/rest.nvim/commit/87941abfc7096669d3283131a023ab1f387629d7)) +* initial statistics implementation ([091d160](https://github.com/rest-nvim/rest.nvim/commit/091d16083092c8c8ee6033b8c35037a5b9a01e12)) +* introduce a testing framework ([d34e1b8](https://github.com/rest-nvim/rest.nvim/commit/d34e1b80ad47fe8818dea179bd7562fb7472b0b3)) +* introduce stringify_request ([7b21a36](https://github.com/rest-nvim/rest.nvim/commit/7b21a361982a5261f2eb187e8e99761a63f772cd)) +* log failed request in logfile ([8373dcd](https://github.com/rest-nvim/rest.nvim/commit/8373dcd31338d42ec2b4d3975bdc6e07d9cf5937)) +* make it possible to not inline external file ([248a07c](https://github.com/rest-nvim/rest.nvim/commit/248a07ca3f65c13417b66255c2a3bad1e8341827)) +* move highlight option to per-query options ([09143ad](https://github.com/rest-nvim/rest.nvim/commit/09143adbef84b8376eb7a703f187cd5d058d15fa)) +* refactor writing to buffer ([e326b56](https://github.com/rest-nvim/rest.nvim/commit/e326b5641ec94faf59db80553c82250bf1223b6f)) +* running a response script to set env ([090e253](https://github.com/rest-nvim/rest.nvim/commit/090e253c114b6d5448bac5869a28a6623c195e3a)) +* support variables in path towards the file to load as well ([ef06dae](https://github.com/rest-nvim/rest.nvim/commit/ef06daed22b992c3e0b6cbd017bd752c2c0eb1b2)) +* update highlights query ([713ba63](https://github.com/rest-nvim/rest.nvim/commit/713ba63cb1d3be15a7aada3f65a3252c60c59383)) +* update tree-sitter setup, http parser got merged into nvim-treesitter ([759bf5b](https://github.com/rest-nvim/rest.nvim/commit/759bf5b1a8cd15ecf6ecf2407a826d4be6ec3414)) +* use size and time default transformers ([d60951c](https://github.com/rest-nvim/rest.nvim/commit/d60951c969170443be95b48cc57387a78501e211)) +* yank executable curl cmd ([b635ff2](https://github.com/rest-nvim/rest.nvim/commit/b635ff2cfd3471b3b5b0dbb85121bc8ac94877c8)) + + +### Bug Fixes + +* `get_auth` was always returning nil ([d6aeecb](https://github.com/rest-nvim/rest.nvim/commit/d6aeecbae7229fa5f4f1e495a9b398140b807f53)) +* 51: body_start is calculated correctly even if the last request has ([52448f5](https://github.com/rest-nvim/rest.nvim/commit/52448f50c7ff0ca1206e7e142cb204e9e4422290)) +* add http highlights file to repository ([da70732](https://github.com/rest-nvim/rest.nvim/commit/da707323197c0b4c116a60c4799f59ba73d21dbf)) +* add ignore http version ([383af39](https://github.com/rest-nvim/rest.nvim/commit/383af397082ec47310d0f074910f5465ffd0ecc7)) +* **ci/formatter:** remove init.lua reference ([ca6ba49](https://github.com/rest-nvim/rest.nvim/commit/ca6ba49ad9dbe969436df75ba03c2dc30b0aea1f)) +* **ci:** add missing stylua version field to formatter ([9515f59](https://github.com/rest-nvim/rest.nvim/commit/9515f597b9d31f2ccc4994fb1038ca81f0b476d6)) +* **ci:** change linter job name to luacheck ([b971076](https://github.com/rest-nvim/rest.nvim/commit/b9710762747a9021bd8c029319d0e541f450692e)) +* **curl:** check if `b:current_syntax` exists before trying to unlet it ([ad7ac9f](https://github.com/rest-nvim/rest.nvim/commit/ad7ac9fb765659b59349b66b390ac67727f6a295)) +* **curl:** return a better error message, see [#16](https://github.com/rest-nvim/rest.nvim/issues/16) and [#17](https://github.com/rest-nvim/rest.nvim/issues/17) ([2ed39f1](https://github.com/rest-nvim/rest.nvim/commit/2ed39f16c565cd21542f51c9d148118d55f7ff8e)) +* disable interpretation of backslash escapes in response body ([d5a4022](https://github.com/rest-nvim/rest.nvim/commit/d5a40221cbf9437e0542c36f62ed803bff65b311)) +* disable interpretation of backslash escapes in response body rendering ([275c713](https://github.com/rest-nvim/rest.nvim/commit/275c713f1bf16f4c58462ef92a17e0fa20a245bd)) +* document renamed variable ([6e73def](https://github.com/rest-nvim/rest.nvim/commit/6e73defb2ec6aeb4d4bf060f798277f58ebafa0f)) +* env var interpolation regex expression ([#53](https://github.com/rest-nvim/rest.nvim/issues/53)) ([e0f023e](https://github.com/rest-nvim/rest.nvim/commit/e0f023e30c6b1267f15c1316ef2bd70fd7dae41c)) +* error handling for formatters ([dc10994](https://github.com/rest-nvim/rest.nvim/commit/dc10994afe07f75aaf6ea9f1cb71c00d0e3a11f2)) +* error when run_request is run without { verbose = XXX } ([25761da](https://github.com/rest-nvim/rest.nvim/commit/25761da6d7bbe16a7fcb2850d05ce1ccba53cfaf)) +* escape single quotes in response body ([59d5331](https://github.com/rest-nvim/rest.nvim/commit/59d53311c8dc36ebe356bc74fc36ae402272a5cd)) +* escape single quotes in response body ([da30eef](https://github.com/rest-nvim/rest.nvim/commit/da30eef5a13f5f0af0ed52d1ae4f2a609b1dd4f3)) +* find next request line when the verb is 'DELETE' ([3ee124d](https://github.com/rest-nvim/rest.nvim/commit/3ee124d0b1de4bba25f5185a3b50828ac8743a97)) +* formatter for html ([e5e364f](https://github.com/rest-nvim/rest.nvim/commit/e5e364f44489c660a1cbe1e7f97b0d7097e7988e)) +* hardcoded lower casing of content-type ([17ab07d](https://github.com/rest-nvim/rest.nvim/commit/17ab07d72a3931718d88826253452d2fed0cdc3f)), closes [#157](https://github.com/rest-nvim/rest.nvim/issues/157) +* httpResultPath match ([#245](https://github.com/rest-nvim/rest.nvim/issues/245)) ([5bd5713](https://github.com/rest-nvim/rest.nvim/commit/5bd5713a8c261b3544039fc3ffb9cee04e8938d8)) +* ignore commented lines as it should be ([0096462](https://github.com/rest-nvim/rest.nvim/commit/0096462d0f83f1e93f70ea65c55817403353820b)) +* include documentation on rockspec ([c63c780](https://github.com/rest-nvim/rest.nvim/commit/c63c7807f834339c140dbdf014f0f3bec353cb17)) +* inconsistency in rockspec ([8f0232b](https://github.com/rest-nvim/rest.nvim/commit/8f0232b674fffb7ba8380eca763442722582925e)) +* **init:** squashed some luacheck errors ([d5a0b5e](https://github.com/rest-nvim/rest.nvim/commit/d5a0b5ec7a8571126e886cbc091f5f3b7cc20c7c)) +* **log:** dont hardcode log level ([867cde3](https://github.com/rest-nvim/rest.nvim/commit/867cde3f6895dc9c81dffc8b99a6f51d1b61ed95)) +* missing argument to end_request ([2826f69](https://github.com/rest-nvim/rest.nvim/commit/2826f6960fbd9adb1da9ff0d008aa2819d2d06b3)), closes [#94](https://github.com/rest-nvim/rest.nvim/issues/94) +* parsing of curl arguments ([48a7c85](https://github.com/rest-nvim/rest.nvim/commit/48a7c8564b8ee3e0eaafa094d911699d92a89a09)) +* parsing of headers and json body ([88ff794](https://github.com/rest-nvim/rest.nvim/commit/88ff794eb323843e138fa75b7c30bb994ba4135f)) +* parsing of nested tables in request body ([46441b6](https://github.com/rest-nvim/rest.nvim/commit/46441b631c398924b84e141940415947d1a6cb0b)) +* proper comments parsing regex. Closes [#29](https://github.com/rest-nvim/rest.nvim/issues/29) ([fb35b85](https://github.com/rest-nvim/rest.nvim/commit/fb35b85bd8cca26c1bd807cf3af313b3fbcc6a86)) +* proper regex for env variables and json body detecting ([850f01e](https://github.com/rest-nvim/rest.nvim/commit/850f01e6738affde079ed38d9b52484e2100ad4a)) +* proper search regex for json body start/end ([2d970d0](https://github.com/rest-nvim/rest.nvim/commit/2d970d01716ecd03f3f35340f6edbd0b40b878bf)) +* quotes ([761bd07](https://github.com/rest-nvim/rest.nvim/commit/761bd0747613f680021167a31eba08600a06bbf5)) +* readme table layout ([7184d14](https://github.com/rest-nvim/rest.nvim/commit/7184d14f8176d412b26d32da8e9ca6c813d9711c)) +* **README:** set proper repository for license badge ([de5cb9e](https://github.com/rest-nvim/rest.nvim/commit/de5cb9e76f278e002a3a7f1818bdb74d162b9d69)) +* **README:** use a consistent coding style in packer example ([36351d8](https://github.com/rest-nvim/rest.nvim/commit/36351d887b91a6486477bb6692a0c24e32d3b43e)) +* remove stylua edits ([0d22f4a](https://github.com/rest-nvim/rest.nvim/commit/0d22f4a22b17a2dde63ecc8d5541e101e739511e)) +* remove unnecessary edits ([042d6d2](https://github.com/rest-nvim/rest.nvim/commit/042d6d25fe81ea83fe1e1002a844e06025d464b5)) +* Removed forgotten print ([e1482ea](https://github.com/rest-nvim/rest.nvim/commit/e1482ea4333a019ec8ef6cf43f406d52e9782800)) +* result_split_horizontal config getter ([948cf14](https://github.com/rest-nvim/rest.nvim/commit/948cf14cad1e8b01f50d72b7859df1ae6f35b290)) +* tidy adding meta tags ([8b7d9df](https://github.com/rest-nvim/rest.nvim/commit/8b7d9df9fbf71efd074fcb21916c70fff44d9af2)) +* typo in lua docstring ([aa63d33](https://github.com/rest-nvim/rest.nvim/commit/aa63d336723378c1f95dbfba029f387b9ee46459)) +* undefined api ([4b1ae8a](https://github.com/rest-nvim/rest.nvim/commit/4b1ae8abfefbbc66c783c343d3d5b13265deb3ce)) +* use config.get to read skip_ssl_verification setting ([4b8608e](https://github.com/rest-nvim/rest.nvim/commit/4b8608e6633ab1c555b9afd6c1724ca7f2ebcde5)) +* yank curl command which includes double quotes ([1d76b3a](https://github.com/rest-nvim/rest.nvim/commit/1d76b3ac9d4f9d04026f24851828040809cd48d5)) + + +### Reverts + +* removed error logging from curl ([877291e](https://github.com/rest-nvim/rest.nvim/commit/877291e3996964ba198d7b20ebbd8222b9f262b8)) +* temporarily revert stylua changes ([91795ef](https://github.com/rest-nvim/rest.nvim/commit/91795ef796455eb4c237880a5969143434495d3f)) From e2cf88ed1ffb7a010abea51a3d264609c4597802 Mon Sep 17 00:00:00 2001 From: Lyle Hanson Date: Sat, 17 Feb 2024 19:07:42 -0500 Subject: [PATCH 093/159] fix(telescope): find `.gitignore`'d `.env` files --- lua/telescope/_extensions/rest.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/telescope/_extensions/rest.lua b/lua/telescope/_extensions/rest.lua index 05636243..f2a0f1f6 100644 --- a/lua/telescope/_extensions/rest.lua +++ b/lua/telescope/_extensions/rest.lua @@ -20,7 +20,7 @@ local function rest_env_select(opt) local pattern = config.get("env_pattern") local edit = config.get("env_edit_command") - local command = string.format("fd -H '%s'", pattern) + local command = string.format("fd -HI '%s'", pattern) local result = io.popen(command):read("*a") local lines = {} From 3eebe4f28d2f74f9e323d3fdd5c067cf12e878a2 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 12:04:25 -0400 Subject: [PATCH 094/159] feat(plugin): check for dependencies on plugin startup --- plugin/rest-nvim.lua | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/plugin/rest-nvim.lua b/plugin/rest-nvim.lua index ac507e45..157972b5 100644 --- a/plugin/rest-nvim.lua +++ b/plugin/rest-nvim.lua @@ -7,10 +7,44 @@ if vim.g.loaded_rest_nvim then return end --- NOTE: legacy code, needs an alternative later --- --- vim.api.nvim_create_user_command('RestLog', function() --- vim.cmd(string.format('tabnew %s', vim.fn.stdpath('cache')..'/rest.nvim.log')) --- end, { desc = 'Opens the rest.nvim log.', }) +-- Locate dependencies +local dependencies = { + ["nvim-nio"] = "rest.nvim will not work asynchronously.", + ["lua-curl"] = "Default HTTP client won't work.", + xml2lua = "rest.nvim will be completely unable to use XML bodies in your requests.", + mimetypes = "rest.nvim will be completely unable to recognize the file type of external bodies.", +} +for dep, err in pairs(dependencies) do + local found_dep + -- Both nvim-nio and lua-curl has a different Lua module name + if dep == "nvim-nio" then + found_dep = package.searchpath("nio", package.path) + elseif dep == "lua-curl" then + found_dep = package.searchpath("cURL.safe", package.path) + else + found_dep = package.searchpath(dep, package.path) + end + + -- If the dependency could not be find in the Lua package.path then try to load it using pcall + -- in case it has been installed through a regular plugin manager and not rocks.nvim + if not found_dep then + local found_dep2 + -- Both nvim-nio and lua-curl has a different Lua module name + if dep == "nvim-nio" then + found_dep2 = pcall(require, "nio") + elseif dep == "lua-curl" then + found_dep2 = pcall(require, "cURL.safe") + else + found_dep2 = pcall(require, dep) + end + + if not found_dep2 then + vim.notify( + "[rest.nvim] Dependency '" .. dep .. "' was not found. " .. err, + vim.log.levels.ERROR + ) + end + end +end vim.g.loaded_rest_nvim = true From c581425b2be7c11fdbe22a7a23d6be67369229d4 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 12:20:23 -0400 Subject: [PATCH 095/159] ref!: make all dependencies "opt-in" so we can fail gracefully if some of them were not found This helps: - If a user has just updated `rest.nvim` and is not aware of the new dependencies, they will receive a nice error instead of a bunch of errors about modules not found during runtime - If a user does not have all the dependencies installed, the plugin will continue to work WITHOUT the features that the dependency provides us, since not all of them are mandatory and the user could forget some of them --- lua/rest-nvim/client/curl.lua | 170 ++++++++++++++++--------------- lua/rest-nvim/functions.lua | 32 +++--- lua/rest-nvim/parser/init.lua | 19 ++-- lua/rest-nvim/result/init.lua | 184 ++++++++++++++++++---------------- 4 files changed, 220 insertions(+), 185 deletions(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 6ce887ff..9dc27ed8 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -8,9 +8,7 @@ local client = {} -local curl = require("cURL.safe") -local mimetypes = require("mimetypes") -local xml2lua = require("xml2lua") +local found_curl, curl = pcall(require, "cURL.safe") local utils = require("rest-nvim.utils") @@ -164,94 +162,108 @@ end ---@param request Request Request data to be passed to cURL ---@return table The request information (url, method, headers, body, etc) function client.request(request) + local ret = {} local logger = _G._rest_nvim.logger - -- We have to concat request headers to a single string, e.g. ["Content-Type"]: "application/json" -> "Content-Type: application/json" - local headers = {} - for name, value in pairs(request.headers) do - table.insert(headers, name .. ": " .. value) - end - - -- Whether to skip SSL host and peer verification - local skip_ssl_verification = _G._rest_nvim.skip_ssl_verification - local req = curl.easy_init() - req:setopt({ - url = request.request.url, - -- verbose = true, - httpheader = headers, - ssl_verifyhost = skip_ssl_verification, - ssl_verifypeer = skip_ssl_verification, - }) - - -- Set request HTTP version, defaults to HTTP/1.1 - if request.request.http_version then - local http_version = request.request.http_version:gsub("%.", "_") - req:setopt_http_version(curl["HTTP_VERSION_" .. http_version]) + if not found_curl then + ---@diagnostic disable-next-line need-check-nil + logger:error("lua-curl could not be found, therefore the cURL client will not work.") else - req:setopt_http_version(curl.HTTP_VERSION_1_1) - end + -- We have to concat request headers to a single string, e.g. ["Content-Type"]: "application/json" -> "Content-Type: application/json" + local headers = {} + for name, value in pairs(request.headers) do + table.insert(headers, name .. ": " .. value) + end - -- If the request method is not GET then we have to build the method in our own - -- See: https://github.com/Lua-cURL/Lua-cURLv3/issues/156 - local method = request.request.method - if vim.tbl_contains({ "POST", "PUT", "PATCH", "TRACE", "OPTIONS", "DELETE" }, method) then - req:setopt_post(true) - req:setopt_customrequest(method) - end + -- Whether to skip SSL host and peer verification + local skip_ssl_verification = _G._rest_nvim.skip_ssl_verification + local req = curl.easy_init() + req:setopt({ + url = request.request.url, + -- verbose = true, + httpheader = headers, + ssl_verifyhost = skip_ssl_verification, + ssl_verifypeer = skip_ssl_verification, + }) - -- Request body - -- - -- Create a copy of the request body table to remove the unneeded `__TYPE` metadata field later - local body = request.body - if request.body.__TYPE == "json" then - body.__TYPE = nil + -- Set request HTTP version, defaults to HTTP/1.1 + if request.request.http_version then + local http_version = request.request.http_version:gsub("%.", "_") + req:setopt_http_version(curl["HTTP_VERSION_" .. http_version]) + else + req:setopt_http_version(curl.HTTP_VERSION_1_1) + end - local json_body_string = vim.json.encode(body) - req:setopt_postfields(json_body_string) - elseif request.body.__TYPE == "xml" then - body.__TYPE = nil + -- If the request method is not GET then we have to build the method in our own + -- See: https://github.com/Lua-cURL/Lua-cURLv3/issues/156 + local method = request.request.method + if vim.tbl_contains({ "POST", "PUT", "PATCH", "TRACE", "OPTIONS", "DELETE" }, method) then + req:setopt_post(true) + req:setopt_customrequest(method) + end - local xml_body_string = xml2lua.toXml(body) - req:setopt_postfields(xml_body_string) - elseif request.body.__TYPE == "external_file" then - local body_mimetype = mimetypes.guess(request.body.path) - local post_data = { - [request.body.name and request.body.name or "body"] = { - file = request.body.path, - type = body_mimetype, - }, - } - req:post(post_data) - end + -- Request body + -- + -- Create a copy of the request body table to remove the unneeded `__TYPE` metadata field later + local body = request.body + if request.body.__TYPE == "json" then + body.__TYPE = nil - -- Request execution - local res_result = {} - local res_headers = {} - req:setopt_writefunction(table.insert, res_result) - req:setopt_headerfunction(table.insert, res_headers) + local json_body_string = vim.json.encode(body) + req:setopt_postfields(json_body_string) + elseif request.body.__TYPE == "xml" then + local ok, xml2lua = pcall(require, "xml2lua") + body.__TYPE = nil - local ret = {} - local ok, err = req:perform() - if ok then - -- Get request statistics if they are enabled - local stats_config = _G._rest_nvim.result.behavior.statistics - if stats_config.enable then - ret.statistics = get_stats(req, stats_config.stats) + -- Send an empty table if xml2lua is not installed + if ok then + local xml_body_string = xml2lua.toXml(body) + req:setopt_postfields(xml_body_string) + else + req:setopt_postfields({}) + end + elseif request.body.__TYPE == "external_file" then + local ok, mimetypes = pcall(require, "mimetypes") + if ok then + local body_mimetype = mimetypes.guess(request.body.path) + local post_data = { + [request.body.name and request.body.name or "body"] = { + file = request.body.path, + type = body_mimetype, + }, + } + req:post(post_data) + end end - ret.url = req:getinfo_effective_url() - ret.code = req:getinfo_response_code() - ret.method = req:getinfo_effective_method() - ret.headers = table.concat(res_headers):gsub("\r", "") - ret.body = table.concat(res_result) - -- We are returning the request script variable as it - ret.script = request.script - else - ---@diagnostic disable-next-line need-check-nil - logger:error("Something went wrong when making the request with cURL:\n" .. curl_error(err:no())) - return {} + -- Request execution + local res_result = {} + local res_headers = {} + req:setopt_writefunction(table.insert, res_result) + req:setopt_headerfunction(table.insert, res_headers) + + local ok, err = req:perform() + if ok then + -- Get request statistics if they are enabled + local stats_config = _G._rest_nvim.result.behavior.statistics + if stats_config.enable then + ret.statistics = get_stats(req, stats_config.stats) + end + + ret.url = req:getinfo_effective_url() + ret.code = req:getinfo_response_code() + ret.method = req:getinfo_effective_method() + ret.headers = table.concat(res_headers):gsub("\r", "") + ret.body = table.concat(res_result) + -- We are returning the request script variable as it + ret.script = request.script + else + ---@diagnostic disable-next-line need-check-nil + logger:error("Something went wrong when making the request with cURL:\n" .. curl_error(err:no())) + return {} + end + req:close() end - req:close() return ret end diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index b9ff1de9..3457692b 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -8,7 +8,7 @@ local functions = {} -local nio = require("nio") +local found_nio, nio = pcall(require, "nio") local utils = require("rest-nvim.utils") local parser = require("rest-nvim.parser") @@ -57,11 +57,15 @@ function functions.exec(scope) parser.look_behind_until(parser.get_node_at_cursor(), "request") ) - req_results = nio - .run(function() - return client.request(req) - end) - :wait() + if found_nio then + req_results = nio + .run(function() + return client.request(req) + end) + :wait() + else + req_results = client.request(req) + end ---Last HTTP request made by the user ---@type Request @@ -73,11 +77,15 @@ function functions.exec(scope) ---@diagnostic disable-next-line need-check-nil logger:error("Rest run last: A previously made request was not found to be executed again") else - req_results = nio - .run(function() - return client.request(req) - end) - :wait() + if found_nio then + req_results = nio + .run(function() + return client.request(req) + end) + :wait() + else + req_results = client.request(req) + end end end @@ -100,7 +108,7 @@ function functions.find_env_files() -- -- This algorithm can be improved later on to search from a parent directory if the desired environment file -- is somewhere else but in the current working directory. - local files = vim.fs.find(function(name, path) + local files = vim.fs.find(function(name, _) return name:match(".*env.*$") end, { limit = math.huge, type = "file", path = "./" }) diff --git a/lua/rest-nvim/parser/init.lua b/lua/rest-nvim/parser/init.lua index 2d9456b8..64122101 100644 --- a/lua/rest-nvim/parser/init.lua +++ b/lua/rest-nvim/parser/init.lua @@ -10,9 +10,6 @@ local parser = {} -local xml2lua = require("xml2lua") -local xml_handler = require("xmlhandler.tree") - local env_vars = require("rest-nvim.parser.env_vars") local dynamic_vars = require("rest-nvim.parser.dynamic_vars") @@ -336,6 +333,7 @@ end ---@param variables Variables HTTP document variables list ---@return table Decoded body table function parser.parse_body(children_nodes, variables) + local logger = _G._rest_nvim.logger local body = {} -- TODO: handle GraphQL bodies by using a graphql parser library from luarocks @@ -349,11 +347,16 @@ function parser.parse_body(children_nodes, variables) -- This is some metadata to be used later on body.__TYPE = "json" elseif node_type == "xml_body" then - local body_handler = xml_handler:new() - local xml_parser = xml2lua.parser(body_handler) - local xml_body_text = assert(get_node_text(node, 0)) - xml_parser:parse(xml_body_text) - body = traverse_body(body_handler.root, variables) + local found_xml2lua, xml2lua = pcall(require, "xml2lua") + if found_xml2lua then + local xml_handler = require("xmlhandler.tree") + + local body_handler = xml_handler:new() + local xml_parser = xml2lua.parser(body_handler) + local xml_body_text = assert(get_node_text(node, 0)) + xml_parser:parse(xml_body_text) + body = traverse_body(body_handler.root, variables) + end -- This is some metadata to be used later on body.__TYPE = "xml" elseif node_type == "external_body" then diff --git a/lua/rest-nvim/result/init.lua b/lua/rest-nvim/result/init.lua index 1f4c61d2..24b638b7 100644 --- a/lua/rest-nvim/result/init.lua +++ b/lua/rest-nvim/result/init.lua @@ -8,7 +8,7 @@ local result = {} -local nio = require("nio") +local found_nio, nio = pcall(require, "nio") local winbar = require("rest-nvim.result.winbar") @@ -181,12 +181,100 @@ function result.display_buf(bufnr, stats) move_cursor(bufnr, 1, 1) end ----Write request results in the given buffer and display it +---Format the result body ---@param bufnr number The target buffer +---@param headers table Request headers ---@param res table Request results -function result.write_res(bufnr, res) +local function format_body(bufnr, headers, res) local logger = _G._rest_nvim.logger + ---@type string + local res_type + for _, header in ipairs(headers) do + if header:find("^Content%-Type") then + local content_type = vim.trim(vim.split(header, ":")[2]) + -- We need to remove the leading charset if we are getting a JSON + res_type = vim.split(content_type, "/")[2]:gsub(";.*", "") + end + end + + -- Do not try to format binary content + local body = {} + if res_type == "octet-stream" then + body = { "Binary answer" } + else + local formatters = _G._rest_nvim.result.behavior.formatters + local filetypes = vim.tbl_keys(formatters) + + -- If there is a formatter for the content type filetype then + -- format the request result body, otherwise return it as we got it + if vim.tbl_contains(filetypes, res_type) then + local fmt = formatters[res_type] + if type(fmt) == "function" then + local ok, out = pcall(fmt, res.body) + if ok and out then + res.body = out + else + ---@diagnostic disable-next-line need-check-nil + logger:error("Error calling formatter on response body:\n" .. out) + end + elseif vim.fn.executable(fmt) == 1 then + local stdout = vim.fn.system(fmt, res.body):gsub("\n$", "") + -- Check if formatter ran successfully + if vim.v.shell_error == 0 then + res.body = stdout + else + ---@diagnostic disable-next-line need-check-nil + logger:error("Error running formatter '" .. fmt .. "' on response body:\n" .. stdout) + end + end + else + ---@diagnostic disable-next-line need-check-nil + logger:info( + "Could not find a formatter for the body type " + .. res_type + .. " returned in the request, the results will not be formatted" + ) + end + body = vim.split(res.body, "\n") + table.insert(body, 1, res.method .. " " .. res.url) + table.insert(body, 2, headers[1]) -- HTTP/X and status code + meaning + table.insert(body, 3, "") + table.insert(body, 4, "#+RES") + table.insert(body, "#+END") + + -- Remove the HTTP/X and status code + meaning from here to avoid duplicates + ---@diagnostic disable-next-line undefined-field + table.remove(winbar.pane_map[2].contents, 1) + + -- add syntax highlights for response + vim.api.nvim_buf_call(bufnr, function() + local syntax_file = vim.fn.expand(string.format("$VIMRUNTIME/syntax/%s.vim", res_type)) + if vim.fn.filereadable(syntax_file) == 1 then + vim.cmd(string.gsub( + [[ + if exists("b:current_syntax") + unlet b:current_syntax + endif + syn include @%s syntax/%s.vim + syn region %sBody matchgroup=Comment start=+\v^#\+RES$+ end=+\v^#\+END$+ contains=@%s + + let b:current_syntax = "httpResult" + ]], + "%%s", + res_type + )) + end + end) + end + ---@diagnostic disable-next-line inject-field + winbar.pane_map[1].contents = body +end + +---Write request results in the given buffer and display it +---@param bufnr number The target buffer +---@param res table Request results +function result.write_res(bufnr, res) local headers = vim.tbl_filter(function(header) if header ~= "" then return header @@ -210,89 +298,13 @@ function result.write_res(bufnr, res) ---@diagnostic disable-next-line inject-field winbar.pane_map[3].contents = vim.tbl_isempty(cookies) and { "No cookies" } or cookies - nio.run(function() - ---@type string - local res_type - for _, header in ipairs(headers) do - if header:find("^Content%-Type") then - local content_type = vim.trim(vim.split(header, ":")[2]) - -- We need to remove the leading charset if we are getting a JSON - res_type = vim.split(content_type, "/")[2]:gsub(";.*", "") - end - end - - -- Do not try to format binary content - local body = {} - if res_type == "octet-stream" then - body = { "Binary answer" } - else - local formatters = _G._rest_nvim.result.behavior.formatters - local filetypes = vim.tbl_keys(formatters) - - -- If there is a formatter for the content type filetype then - -- format the request result body, otherwise return it as we got it - if vim.tbl_contains(filetypes, res_type) then - local fmt = formatters[res_type] - if type(fmt) == "function" then - local ok, out = pcall(fmt, res.body) - if ok and out then - res.body = out - else - ---@diagnostic disable-next-line need-check-nil - logger:error("Error calling formatter on response body:\n" .. out) - end - elseif vim.fn.executable(fmt) == 1 then - local stdout = vim.fn.system(fmt, res.body):gsub("\n$", "") - -- Check if formatter ran successfully - if vim.v.shell_error == 0 then - res.body = stdout - else - ---@diagnostic disable-next-line need-check-nil - logger:error("Error running formatter '" .. fmt .. "' on response body:\n" .. stdout) - end - end - else - ---@diagnostic disable-next-line need-check-nil - logger:info( - "Could not find a formatter for the body type " - .. res_type - .. " returned in the request, the results will not be formatted" - ) - end - body = vim.split(res.body, "\n") - table.insert(body, 1, res.method .. " " .. res.url) - table.insert(body, 2, headers[1]) -- HTTP/X and status code + meaning - table.insert(body, 3, "") - table.insert(body, 4, "#+RES") - table.insert(body, "#+END") - - -- Remove the HTTP/X and status code + meaning from here to avoid duplicates - ---@diagnostic disable-next-line undefined-field - table.remove(winbar.pane_map[2].contents, 1) - - -- add syntax highlights for response - vim.api.nvim_buf_call(bufnr, function() - local syntax_file = vim.fn.expand(string.format("$VIMRUNTIME/syntax/%s.vim", res_type)) - if vim.fn.filereadable(syntax_file) == 1 then - vim.cmd(string.gsub( - [[ - if exists("b:current_syntax") - unlet b:current_syntax - endif - syn include @%s syntax/%s.vim - syn region %sBody matchgroup=Comment start=+\v^#\+RES$+ end=+\v^#\+END$+ contains=@%s - - let b:current_syntax = "httpResult" - ]], - "%%s", - res_type - )) - end - end) - end - ---@diagnostic disable-next-line inject-field - winbar.pane_map[1].contents = body - end) + if found_nio then + nio.run(function() + format_body(bufnr, headers, res) + end) + else + format_body(bufnr, headers, res) + end -- Add statistics to the response local stats = {} From 23365a547d9edb8db7cae4e7b7c36bc1ac8f61b8 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 13:10:18 -0400 Subject: [PATCH 096/159] hotfix(config): proper check for `debug_info` --- lua/rest-nvim/config/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 293bcbf9..a73dd144 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -163,7 +163,7 @@ function config.set(user_configs) end if #conf.debug_info.unrecognized_configs > 0 then - conf.logger:warn("Unrecognized configs found in setup: " .. vim.inspect(config.debug_info.unrecognized_configs)) + conf.logger:warn("Unrecognized configs found in setup: " .. vim.inspect(conf.debug_info.unrecognized_configs)) end return conf From a7709278027af5a49e94386ffb42cfc7351e9d27 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 13:14:10 -0400 Subject: [PATCH 097/159] fix(plugin): slightly improve error message for `mimetypes` dependency --- plugin/rest-nvim.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rest-nvim.lua b/plugin/rest-nvim.lua index 157972b5..0db2c017 100644 --- a/plugin/rest-nvim.lua +++ b/plugin/rest-nvim.lua @@ -12,7 +12,7 @@ local dependencies = { ["nvim-nio"] = "rest.nvim will not work asynchronously.", ["lua-curl"] = "Default HTTP client won't work.", xml2lua = "rest.nvim will be completely unable to use XML bodies in your requests.", - mimetypes = "rest.nvim will be completely unable to recognize the file type of external bodies.", + mimetypes = "rest.nvim will be completely unable to recognize the file type of external body files.", } for dep, err in pairs(dependencies) do local found_dep From 0d231660b733320c170ab3c464beafaf9992f31d Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 13:15:07 -0400 Subject: [PATCH 098/159] feat(plugin): add dependencies state into a `vim.g.rest_nvim_deps` table This is going to be useful for the `rest-nvim/health.lua` module --- plugin/rest-nvim.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugin/rest-nvim.lua b/plugin/rest-nvim.lua index 0db2c017..8298f9ce 100644 --- a/plugin/rest-nvim.lua +++ b/plugin/rest-nvim.lua @@ -7,6 +7,13 @@ if vim.g.loaded_rest_nvim then return end + +--- Dependencies management --- +------------------------------- +-- This variable is going to hold the dependencies state (whether they are found or not), +-- to be used later by the `health.lua` module +local rest_nvim_deps = {} + -- Locate dependencies local dependencies = { ["nvim-nio"] = "rest.nvim will not work asynchronously.", @@ -38,13 +45,28 @@ for dep, err in pairs(dependencies) do found_dep2 = pcall(require, dep) end + rest_nvim_deps[dep] = { + found = false, + error = err, + } if not found_dep2 then vim.notify( "[rest.nvim] Dependency '" .. dep .. "' was not found. " .. err, vim.log.levels.ERROR ) + else + rest_nvim_deps[dep] = { + found = true, + error = err, + } end + else + rest_nvim_deps[dep] = { + found = true, + error = err, + } end end +vim.g.rest_nvim_deps = rest_nvim_deps vim.g.loaded_rest_nvim = true From e8d8a505392b7fcf4d2eeeb30d1d4612bed86645 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 13:22:10 -0400 Subject: [PATCH 099/159] feat(plugin): add `nvim-treesitter` as a dependency --- plugin/rest-nvim.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugin/rest-nvim.lua b/plugin/rest-nvim.lua index 8298f9ce..40934e5d 100644 --- a/plugin/rest-nvim.lua +++ b/plugin/rest-nvim.lua @@ -16,10 +16,11 @@ local rest_nvim_deps = {} -- Locate dependencies local dependencies = { - ["nvim-nio"] = "rest.nvim will not work asynchronously.", - ["lua-curl"] = "Default HTTP client won't work.", - xml2lua = "rest.nvim will be completely unable to use XML bodies in your requests.", - mimetypes = "rest.nvim will be completely unable to recognize the file type of external body files.", + ["nvim-nio"] = "rest.nvim will not work asynchronously", + ["nvim-treesitter"] = "rest.nvim parsing will not work", + ["lua-curl"] = "Default HTTP client won't work", + xml2lua = "rest.nvim will be completely unable to use XML bodies in your requests", + mimetypes = "rest.nvim will be completely unable to recognize the file type of external body files", } for dep, err in pairs(dependencies) do local found_dep From ac3e7bbeb24b0dda4d965108b5902cfd31d04427 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 13:28:25 -0400 Subject: [PATCH 100/159] ref(config)!: if the formatter is a function then it should also return a boolean indicating whether the formatter executable has been found or not --- lua/rest-nvim/config/init.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index a73dd144..1ec49a69 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -29,7 +29,7 @@ local logger = require("rest-nvim.logger") ---@class RestConfigResultBehavior ---@field show_info RestConfigResultInfo Request results information ---@field statistics RestConfigResultStats Request results statistics ----@field formatters RestConfigResultFormatters Formatters for the request results body +---@field formatters RestConfigResultFormatters Formatters for the request results body. If the formatter is a function it should return two values, the formatted body and a boolean whether the formatter has been found or not ---@class RestConfigResultInfo ---@field url boolean Display the request URL @@ -42,8 +42,8 @@ local logger = require("rest-nvim.logger") ---@field stats string[]|{ [1]: string, title: string }[] Statistics to be shown, takes cURL's easy getinfo constants name ---@class RestConfigResultFormatters ----@field json string|fun(body: string): string JSON formatter ----@field html string|fun(body: string): string HTML formatter +---@field json string|fun(body: string): string,boolean JSON formatter +---@field html string|fun(body: string): string,boolean HTML formatter ---@class RestConfigHighlight ---@field enable boolean Whether current request highlighting is enabled or not @@ -103,11 +103,10 @@ local default_config = { json = "jq", html = function(body) if vim.fn.executable("tidy") == 0 then - return body + return body, false end -- stylua: ignore - ---@diagnostic disable-next-line redundant-return-value - return vim.fn.system({ + local fmt_body = vim.fn.system({ "tidy", "-i", "-q", @@ -117,6 +116,8 @@ local default_config = { "--show-warnings", "0", "-", }, body):gsub("\n$", "") + + return fmt_body, true end, }, }, From 374309109d58b56dc5b1b20bc5519f2ff8ed02ed Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 13:37:13 -0400 Subject: [PATCH 101/159] ref(config)!: if the formatter is a function then it should also return a table indicating the formatter `name` and whether it has been `found` or not --- lua/rest-nvim/config/init.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 1ec49a69..709f076c 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -29,7 +29,7 @@ local logger = require("rest-nvim.logger") ---@class RestConfigResultBehavior ---@field show_info RestConfigResultInfo Request results information ---@field statistics RestConfigResultStats Request results statistics ----@field formatters RestConfigResultFormatters Formatters for the request results body. If the formatter is a function it should return two values, the formatted body and a boolean whether the formatter has been found or not +---@field formatters RestConfigResultFormatters Formatters for the request results body. If the formatter is a function it should return two values, the formatted body and a table containing two values `found` (whether the formatter has been found or not) and `name` (the formatter name) ---@class RestConfigResultInfo ---@field url boolean Display the request URL @@ -42,8 +42,8 @@ local logger = require("rest-nvim.logger") ---@field stats string[]|{ [1]: string, title: string }[] Statistics to be shown, takes cURL's easy getinfo constants name ---@class RestConfigResultFormatters ----@field json string|fun(body: string): string,boolean JSON formatter ----@field html string|fun(body: string): string,boolean HTML formatter +---@field json string|fun(body: string): string,table JSON formatter +---@field html string|fun(body: string): string,table HTML formatter ---@class RestConfigHighlight ---@field enable boolean Whether current request highlighting is enabled or not @@ -103,7 +103,7 @@ local default_config = { json = "jq", html = function(body) if vim.fn.executable("tidy") == 0 then - return body, false + return body, { found = false, name = "tidy" } end -- stylua: ignore local fmt_body = vim.fn.system({ @@ -117,7 +117,7 @@ local default_config = { "-", }, body):gsub("\n$", "") - return fmt_body, true + return fmt_body, { found = true, name = "tidy" } end, }, }, From b6d5705a830993715459ef701c4e5fa40a7a9d1b Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 13:42:21 -0400 Subject: [PATCH 102/159] feat: add `health` module, currently checks for Luarocks dependencies, configuration correctness and body formatters --- lua/rest-nvim/health.lua | 106 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 lua/rest-nvim/health.lua diff --git a/lua/rest-nvim/health.lua b/lua/rest-nvim/health.lua new file mode 100644 index 00000000..eea76afe --- /dev/null +++ b/lua/rest-nvim/health.lua @@ -0,0 +1,106 @@ +---@mod rest-nvim.health rest.nvim healthcheck +--- +---@brief [[ +--- +---Healthcheck module for rest.nvim +--- +---@brief ]] + +local health = {} + +local function install_health() + vim.health.start("Installation") + + -- Luarocks installed + -- we check for either luarocks system-wide or rocks.nvim as rocks.nvim can manage Luarocks installation + if vim.fn.executable("luarocks") ~= 1 and not vim.g.rocks_nvim_loaded then + vim.health.error("`Luarocks` is not installed in your system") + else + vim.health.ok("Found `luarocks` installed in your system") + end + + -- Luarocks in `package.path` + local found_luarocks_in_path = string.find(package.path, "rocks") + if not found_luarocks_in_path then + vim.health.error( + "Luarocks PATHs were not found in your Neovim's Lua `package.path`", + "Check rest.nvim README to know how to add your luarocks PATHs to Neovim" + ) + else + vim.health.ok("Found Luarocks PATHs in your Neovim's Lua `package.path`") + end + + -- Luarocks dependencies existence checking + for dep, dep_info in pairs(vim.g.rest_nvim_deps) do + if not dep_info.found then + local err_advice = "Install it through `luarocks --local install " .. dep .. "`" + if dep:find("nvim") then + err_advice = "Install it through your preferred plugins manager" + end + + vim.health.error( + "Dependency `" .. dep .. "` was not found (" .. dep_info.error .. ")", + err_advice + ) + else + vim.health.ok("Dependency `" .. dep .. "` was found") + end + end + + -- Tree-sitter and HTTP parser + local found_treesitter, ts_info = pcall(require, "nvim-treesitter.info") + if not found_treesitter then + vim.health.warn("Could not check for tree-sitter `http` parser existence because `nvim-treesitter` is not installed") + else + local is_http_parser_installed = vim.tbl_contains(ts_info.installed_parsers(), "http") + if not is_http_parser_installed then + vim.health.error( + "Tree-sitter `http` parser is not installed (rest.nvim parsing will not work.)", + "Install it through `:TSInstall http` or add it to your `nvim-treesitter`'s `ensure_installed` table." + ) + else + vim.health.ok("Tree-sitter `http` parser is installed") + end + end + +end + +local function configuration_health() + vim.health.start("Configuration") + + -- Configuration options + local unrecognized_configs = _G._rest_nvim.debug_info.unrecognized_configs + if not vim.tbl_isempty(unrecognized_configs) then + for _, config_key in ipairs(unrecognized_configs) do + vim.health.error("Unrecognized configuration option `" .. config_key .. "` found") + end + else + vim.health.ok("No unrecognized configuration options were found") + end + + -- Formatters + local formatters = _G._rest_nvim.result.behavior.formatters + for ft, formatter in pairs(formatters) do + if type(formatter) == "string" then + if vim.fn.executable(formatter) ~= 1 then + vim.health.error("Formatter for `" .. ft .. "` is set to `" .. formatter .. "`, however, rest.nvim could not find it in your system") + else + vim.health.ok("Formatter for `" .. ft .. "` is set to `" .. formatter .. "` and rest.nvim found it in your system") + end + elseif type(formatter) == "function" then + local _, fmt_meta = formatter() + if not fmt_meta.found then + vim.health.error("Formatter for `" .. ft .. "` is set to `" .. fmt_meta.name .. "`, however, rest.nvim could not find it in your system") + else + vim.health.ok("Formatter for `" .. ft .. "` is set to `" .. fmt_meta.name .. "` and rest.nvim found it in your system") + end + end + end +end + +function health.check() + install_health() + configuration_health() +end + +return health From 94a61f65e0cd8a1249fb588d1a0515850c63cc86 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 13:48:07 -0400 Subject: [PATCH 103/159] feat(config): small documentation adjustments --- lua/rest-nvim/config/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 709f076c..1a918e42 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -130,7 +130,7 @@ local default_config = { --- ---```lua ---keybinds = { - --- "r", "(RestRun)", "Run request under the cursor", + --- "r", ":Rest run", "Run request under the cursor", ---} --- ---``` From bd7f357c4a58f7cf1af3e5affe689f2811fac779 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 13:50:41 -0400 Subject: [PATCH 104/159] feat(api): add a `VERSION` constant --- lua/rest-nvim/api.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/rest-nvim/api.lua b/lua/rest-nvim/api.lua index b83fdd11..a32158bb 100644 --- a/lua/rest-nvim/api.lua +++ b/lua/rest-nvim/api.lua @@ -12,6 +12,11 @@ local api = {} local autocmds = require("rest-nvim.autocmds") local commands = require("rest-nvim.commands") +---rest.nvim API version, equals to the current rest.nvim version. Meant to be used by modules later +---@type string +---@see vim.version +api.VERSION = "2.0.0" + ---Register a new autocommand in the `Rest` augroup ---@see vim.api.nvim_create_augroup ---@see vim.api.nvim_create_autocmd From 4573062dcb02f4b9e10f8ff4908b657d2facba25 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 14:27:06 -0400 Subject: [PATCH 105/159] ref(config)!: set a saner highlight timeout value (`750` ms) --- lua/rest-nvim/config/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 1a918e42..25b3d70c 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -124,7 +124,7 @@ local default_config = { }, highlight = { enable = true, - timeout = 150, + timeout = 750, }, ---Example: --- From 8d160ae9f3e79b2271883a386e6ba64534f8e2c4 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 14:28:53 -0400 Subject: [PATCH 106/159] feat(api): add a `namespace` variable, which holds `rest.nvim` namespace generated with `nvim_create_namespace` --- lua/rest-nvim/api.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/rest-nvim/api.lua b/lua/rest-nvim/api.lua index a32158bb..ef49a484 100644 --- a/lua/rest-nvim/api.lua +++ b/lua/rest-nvim/api.lua @@ -17,6 +17,11 @@ local commands = require("rest-nvim.commands") ---@see vim.version api.VERSION = "2.0.0" +---rest.nvim namespace used for buffer highlights +---@type number +---@see vim.api.nvim_create_namespace +api.namespace = vim.api.nvim_create_namespace("rest-nvim") + ---Register a new autocommand in the `Rest` augroup ---@see vim.api.nvim_create_augroup ---@see vim.api.nvim_create_autocmd From 2426a79387e63c7a2147d87e35dafc60f741739b Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 14:31:22 -0400 Subject: [PATCH 107/159] feat(parser): `parse` function will also return the `request` node `start` and `end_` --- lua/rest-nvim/parser/init.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lua/rest-nvim/parser/init.lua b/lua/rest-nvim/parser/init.lua index 64122101..1558e977 100644 --- a/lua/rest-nvim/parser/init.lua +++ b/lua/rest-nvim/parser/init.lua @@ -400,6 +400,8 @@ end ---@field headers { [string]: string|number|boolean }[] ---@field body table ---@field script? string +---@field start number +---@field end_ number ---Parse a request and return the request on itself, its headers and body ---@param req_node TSNode Tree-sitter request node @@ -424,6 +426,10 @@ function parser.parse(req_node) ast.body = parser.parse_body(request_children_nodes, document_variables) ast.script = parser.parse_script(req_node) + -- Request node range + ast.start = req_node:start() + ast.end_ = req_node:end_() + return ast end From bc6be4c3b5ab85f62dcb20fc303b35a04c701799 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 14:31:47 -0400 Subject: [PATCH 108/159] feat: re-implement the request highlighting --- lua/rest-nvim/functions.lua | 5 +++++ lua/rest-nvim/utils.lua | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index 3457692b..484ddf38 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -26,6 +26,7 @@ function functions.exec(scope) scope = { scope, "string" }, }) + local api = require("rest-nvim.api") local logger = _G._rest_nvim.logger local ok, client = pcall(require, "rest-nvim.client." .. _G._rest_nvim.client) if not ok then @@ -57,6 +58,8 @@ function functions.exec(scope) parser.look_behind_until(parser.get_node_at_cursor(), "request") ) + utils.highlight(0, req.start, req.end_, api.namespace) + if found_nio then req_results = nio .run(function() @@ -77,6 +80,8 @@ function functions.exec(scope) ---@diagnostic disable-next-line need-check-nil logger:error("Rest run last: A previously made request was not found to be executed again") else + utils.highlight(0, req.start, req.end_, api.namespace) + if found_nio then req_results = nio .run(function() diff --git a/lua/rest-nvim/utils.lua b/lua/rest-nvim/utils.lua index a0b69030..052a73ec 100644 --- a/lua/rest-nvim/utils.lua +++ b/lua/rest-nvim/utils.lua @@ -104,4 +104,36 @@ local transform = { utils.transform_time = transform.time utils.transform_size = transform.size +---Highlight a request +---@param bufnr number Buffer handler ID +---@param start number Request tree-sitter node start +---@param end_ number Request tree-sitter node end +---@param ns number rest.nvim Neovim namespace +function utils.highlight(bufnr, start, end_, ns) + local highlight = _G._rest_nvim.highlight + local higroup = "IncSearch" + local timeout = highlight.timeout + + -- Clear buffer highlights + vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) + + -- Highlight request + vim.highlight.range( + bufnr, + ns, + higroup, + { start, 0 }, + { end_, string.len(vim.fn.getline(end_)) }, + { regtype = "c", inclusive = false } + ) + + -- Clear buffer highlights again after timeout + vim.defer_fn(function() + vim.notify("Cleaning highlights") + if vim.api.nvim_buf_is_valid(bufnr) then + vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) + end + end, timeout) +end + return utils From 1e7d212ea457f966665e08890b5f78b645155562 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 14:47:23 -0400 Subject: [PATCH 109/159] fix(config): better example for `keybinds` option --- lua/rest-nvim/config/init.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 25b3d70c..f9a2c927 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -130,7 +130,12 @@ local default_config = { --- ---```lua ---keybinds = { - --- "r", ":Rest run", "Run request under the cursor", + --- { + --- "rr", ":Rest run", "Run request under the cursor", + --- }, + --- { + --- "rl", ":Rest run last", "Re-run latest request", + --- }, ---} --- ---``` From d7ba4b041b3c26076530a20a69983559a6403b2b Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 14:49:00 -0400 Subject: [PATCH 110/159] feat: implement `config.keybinds`, add `rest-nvim.keybinds` module --- lua/rest-nvim/init.lua | 4 ++++ lua/rest-nvim/keybinds.lua | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 lua/rest-nvim/keybinds.lua diff --git a/lua/rest-nvim/init.lua b/lua/rest-nvim/init.lua index 53c273e4..0c6eb7f1 100644 --- a/lua/rest-nvim/init.lua +++ b/lua/rest-nvim/init.lua @@ -9,6 +9,7 @@ local rest = {} local config = require("rest-nvim.config") +local keybinds = require("rest-nvim.keybinds") local autocmds = require("rest-nvim.autocmds") ---Set up rest.nvim @@ -17,6 +18,9 @@ function rest.setup(user_configs) -- Set up rest.nvim configurations _G._rest_nvim = config.set(user_configs or {}) + -- Set up rest.nvim keybinds + keybinds.apply() + -- Set up rest.nvim autocommands and commands autocmds.setup() diff --git a/lua/rest-nvim/keybinds.lua b/lua/rest-nvim/keybinds.lua new file mode 100644 index 00000000..bedf3c05 --- /dev/null +++ b/lua/rest-nvim/keybinds.lua @@ -0,0 +1,48 @@ +---@mod rest-nvim.autocmds rest.nvim autocommands +--- +---@brief [[ +--- +--- rest.nvim autocommands +--- +---@brief ]] + +local keybinds = {} + +---Apply user-defined keybinds in the rest.nvim configuration +function keybinds.apply() + local keybindings = _G._rest_nvim.keybinds + for _, keybind in ipairs(keybindings) do + local lhs = keybind[1] + local cmd = keybind[2] + local desc = keybind[3] + + vim.validate({ + lhs = { lhs, "string" }, + cmd = { cmd, "string" }, + desc = { desc, "string" }, + }) + + vim.keymap.set("n", lhs, cmd, { desc = desc }) + end +end + +---Register a new keybinding +---@see vim.keymap.set +--- +---@param mode string Keybind mode +---@param lhs string Keybind trigger +---@param cmd string Command to be run +---@param opts table Keybind options +---@package +function keybinds.register_keybind(mode, lhs, cmd, opts) + vim.validate({ + mode = { mode, "string" }, + lhs = { lhs, "string" }, + cmd = { cmd, "string" }, + opts = { opts, "table" }, + }) + + vim.keymap.set(mode, lhs, cmd, opts) +end + +return keybinds From 8bbd2fb53ffb177ae134a523631ec6c33f294190 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 14:49:33 -0400 Subject: [PATCH 111/159] feat(api): add `register_rest_keybind` function to add new keybinds, wrapper around `vim.keymap.set` --- lua/rest-nvim/api.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lua/rest-nvim/api.lua b/lua/rest-nvim/api.lua index ef49a484..c16702ec 100644 --- a/lua/rest-nvim/api.lua +++ b/lua/rest-nvim/api.lua @@ -9,6 +9,7 @@ local api = {} +local keybinds = require("rest-nvim.keybinds") local autocmds = require("rest-nvim.autocmds") local commands = require("rest-nvim.commands") @@ -40,4 +41,15 @@ function api.register_rest_subcommand(name, cmd) commands.register_subcommand(name, cmd) end +---Register a new keybinding +---@see vim.keymap.set +--- +---@param mode string Keybind mode +---@param lhs string Keybind trigger +---@param cmd string Command to be run +---@param opts table Keybind options +function api.register_rest_keybind(mode, lhs, cmd, opts) + keybinds.register_keybind(mode, lhs, cmd, opts) +end + return api From 1017924b2d7d18c428d77af5b54a164fda8ac802 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 15:06:57 -0400 Subject: [PATCH 112/159] feat(health): improve instructions for `nvim-nio` and `nvim-treesitter` when they are not installed --- lua/rest-nvim/health.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/rest-nvim/health.lua b/lua/rest-nvim/health.lua index eea76afe..c7188b2d 100644 --- a/lua/rest-nvim/health.lua +++ b/lua/rest-nvim/health.lua @@ -35,7 +35,11 @@ local function install_health() if not dep_info.found then local err_advice = "Install it through `luarocks --local install " .. dep .. "`" if dep:find("nvim") then - err_advice = "Install it through your preferred plugins manager" + err_advice = "Install it through your preferred plugins manager or luarocks by using `luarocks --local install " .. dep .. "`" + -- NOTE: nvim-treesitter has a weird bug in luarocks due to the parsers installation logic so let's mark it as not recommended + if dep == "nvim-treesitter" then + err_advice = err_advice .. " (not recommended yet!)" + end end vim.health.error( From 72145522d94597919fb9eefe745efd368844707f Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 15:07:22 -0400 Subject: [PATCH 113/159] ref(health): convert some errors into warnings --- lua/rest-nvim/health.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/rest-nvim/health.lua b/lua/rest-nvim/health.lua index c7188b2d..61c62911 100644 --- a/lua/rest-nvim/health.lua +++ b/lua/rest-nvim/health.lua @@ -76,7 +76,7 @@ local function configuration_health() local unrecognized_configs = _G._rest_nvim.debug_info.unrecognized_configs if not vim.tbl_isempty(unrecognized_configs) then for _, config_key in ipairs(unrecognized_configs) do - vim.health.error("Unrecognized configuration option `" .. config_key .. "` found") + vim.health.warn("Unrecognized configuration option `" .. config_key .. "` found") end else vim.health.ok("No unrecognized configuration options were found") @@ -87,14 +87,14 @@ local function configuration_health() for ft, formatter in pairs(formatters) do if type(formatter) == "string" then if vim.fn.executable(formatter) ~= 1 then - vim.health.error("Formatter for `" .. ft .. "` is set to `" .. formatter .. "`, however, rest.nvim could not find it in your system") + vim.health.warn("Formatter for `" .. ft .. "` is set to `" .. formatter .. "`, however, rest.nvim could not find it in your system") else vim.health.ok("Formatter for `" .. ft .. "` is set to `" .. formatter .. "` and rest.nvim found it in your system") end elseif type(formatter) == "function" then local _, fmt_meta = formatter() if not fmt_meta.found then - vim.health.error("Formatter for `" .. ft .. "` is set to `" .. fmt_meta.name .. "`, however, rest.nvim could not find it in your system") + vim.health.warn("Formatter for `" .. ft .. "` is set to `" .. fmt_meta.name .. "`, however, rest.nvim could not find it in your system") else vim.health.ok("Formatter for `" .. ft .. "` is set to `" .. fmt_meta.name .. "` and rest.nvim found it in your system") end From 7bd3a5d4ea37965086cc61a3750e94bb407f1aec Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 17:28:45 -0400 Subject: [PATCH 114/159] feat(client.curl): support `Host` header --- lua/rest-nvim/client/curl.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 9dc27ed8..59655c27 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -169,6 +169,17 @@ function client.request(request) ---@diagnostic disable-next-line need-check-nil logger:error("lua-curl could not be found, therefore the cURL client will not work.") else + -- If Host header exists then we need to tweak the request url + if vim.tbl_contains(vim.tbl_keys(request.headers), "Host") then + ---@diagnostic disable-next-line inject-field + request.request.url = request.headers["Host"] .. request.request.url + request.headers["Host"] = nil + elseif vim.tbl_contains(vim.tbl_keys(request.headers), "host") then + ---@diagnostic disable-next-line inject-field + request.request.url = request.headers["host"] .. request.request.url + request.headers["host"] = nil + end + -- We have to concat request headers to a single string, e.g. ["Content-Type"]: "application/json" -> "Content-Type: application/json" local headers = {} for name, value in pairs(request.headers) do From eb4fbf26aabd8b96985173bb1910d4a48ac54b61 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 18:15:47 -0400 Subject: [PATCH 115/159] feat: add support for form data (`application/x-www-form-urlencoded`), not tested but it should work! --- lua/rest-nvim/client/curl.lua | 8 ++++++++ lua/rest-nvim/parser/init.lua | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 59655c27..7d06359c 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -245,6 +245,14 @@ function client.request(request) } req:post(post_data) end + elseif request.body.__TYPE == "form_data" then + body.__TYPE = nil + + local form = curl.form() + for k, v in pairs(body) do + form:add_content(k, v) + end + req:setopt_httppost(form) end -- Request execution diff --git a/lua/rest-nvim/parser/init.lua b/lua/rest-nvim/parser/init.lua index 1558e977..7f750fb7 100644 --- a/lua/rest-nvim/parser/init.lua +++ b/lua/rest-nvim/parser/init.lua @@ -333,7 +333,6 @@ end ---@param variables Variables HTTP document variables list ---@return table Decoded body table function parser.parse_body(children_nodes, variables) - local logger = _G._rest_nvim.logger local body = {} -- TODO: handle GraphQL bodies by using a graphql parser library from luarocks @@ -368,6 +367,22 @@ function parser.parse_body(children_nodes, variables) body.path = assert(get_node_text(node:field("file_path")[1], 0)) -- This is some metadata to be used later on body.__TYPE = "external_file" + elseif node_type == "form_data" then + local names = node:field("name") + local values = node:field("value") + if vim.tbl_count(names) > 1 then + for idx, name in ipairs(names) do + ---@type string|number|boolean + local value = assert(get_node_text(values[idx], 0)):gsub('"', "") + body[assert(get_node_text(name, 0))] = value + end + else + ---@type string|number|boolean + local value = assert(get_node_text(values[1], 0)):gsub('"', "") + body[assert(get_node_text(names[1], 0))] = value + end + -- This is some metadata to be used later on + body.__TYPE = "form" end end From 7f751915d1a902895f5eb98336121262695f4b4e Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 19 Feb 2024 18:27:29 -0400 Subject: [PATCH 116/159] feat: temporarily re-implement `RestNvimX` keybinds with a deprecation notice for backward mappings support --- lua/rest-nvim/keybinds.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lua/rest-nvim/keybinds.lua b/lua/rest-nvim/keybinds.lua index bedf3c05..85dc9b6b 100644 --- a/lua/rest-nvim/keybinds.lua +++ b/lua/rest-nvim/keybinds.lua @@ -8,8 +8,24 @@ local keybinds = {} +local function legacy_keybinds() + -- NOTE: RestNvimPreview no longer exists + vim.keymap.set("n", "RestNvim", function() + vim.deprecate("`RestNvim` mapping", "`:Rest run`", "2.1.0", "rest.nvim", false) + vim.cmd("Rest run") + end) + vim.keymap.set("n", "RestNvimLast", function() + vim.deprecate("`RestNvimLast` mapping", "`:Rest run last`", "2.1.0", "rest.nvim", false) + vim.cmd("Rest run") + end) +end + ---Apply user-defined keybinds in the rest.nvim configuration function keybinds.apply() + -- Temporarily apply legacy keybinds + legacy_keybinds() + + -- User-defined keybinds local keybindings = _G._rest_nvim.keybinds for _, keybind in ipairs(keybindings) do local lhs = keybind[1] From 69434598018a47d3bddc6c17c27b87f15fb9b700 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Thu, 22 Feb 2024 12:05:35 -0400 Subject: [PATCH 117/159] feat(ci): tell `luarocks-tag-release` where to find cURL headers --- .github/workflows/luarocks.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/luarocks.yml b/.github/workflows/luarocks.yml index 3d1eb9fd..f576088c 100644 --- a/.github/workflows/luarocks.yml +++ b/.github/workflows/luarocks.yml @@ -27,5 +27,7 @@ jobs: lua-curl mimetypes xml2lua + extra_luarocks_args: | + CURL_INCDIR=/usr/include/x86_64-linux-gnu env: LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} From efa097ea23f3489137a1b9b4b9214a5caef9e6ad Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 17:59:48 -0400 Subject: [PATCH 118/159] cleanup(utils): remove debugging print --- lua/rest-nvim/utils.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/rest-nvim/utils.lua b/lua/rest-nvim/utils.lua index 052a73ec..70e50d1f 100644 --- a/lua/rest-nvim/utils.lua +++ b/lua/rest-nvim/utils.lua @@ -129,7 +129,6 @@ function utils.highlight(bufnr, start, end_, ns) -- Clear buffer highlights again after timeout vim.defer_fn(function() - vim.notify("Cleaning highlights") if vim.api.nvim_buf_is_valid(bufnr) then vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) end From e0efa3053486cf9b03fe692ab2052b6169045159 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 18:18:43 -0400 Subject: [PATCH 119/159] fix(result): fix LSP type annotations warning --- lua/rest-nvim/result/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/rest-nvim/result/init.lua b/lua/rest-nvim/result/init.lua index 24b638b7..6898b2fd 100644 --- a/lua/rest-nvim/result/init.lua +++ b/lua/rest-nvim/result/init.lua @@ -61,6 +61,7 @@ function result.get_or_create_buf() -- Prevent modified flag vim.api.nvim_set_option_value("buftype", "nofile", { buf = bufnr }) -- Delete buffer content + ---@cast bufnr number vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {}) -- Make sure the filetype of the buffer is `httpResult` so it will be highlighted From 19e122aa7deb280c1a3913c6581f0086261754a5 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 19:37:20 -0400 Subject: [PATCH 120/159] feat(config): improve `env_pattern` regex to match any `env` file, e.g. `.env` and `.env.json` --- lua/rest-nvim/config/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index f9a2c927..769f9486 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -69,7 +69,7 @@ local logger = require("rest-nvim.logger") local default_config = { client = "curl", env_file = ".env", - env_pattern = "\\.env$", + env_pattern = ".*env.*$", env_edit_command = "tabedit", encode_url = true, skip_ssl_verification = false, From 5337d082abb4e40d16edbf1479d80dca052a22b9 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 19:39:20 -0400 Subject: [PATCH 121/159] ref(functions): use `env_pattern` configuration option in the `find_env_files` function --- lua/rest-nvim/functions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index 484ddf38..b646e49f 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -114,7 +114,7 @@ function functions.find_env_files() -- This algorithm can be improved later on to search from a parent directory if the desired environment file -- is somewhere else but in the current working directory. local files = vim.fs.find(function(name, _) - return name:match(".*env.*$") + return name:match(_G._rest_nvim.env_pattern) end, { limit = math.huge, type = "file", path = "./" }) return files From 072c6662eef12ffc3598b4a7e2631dd7561fea03 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 19:40:00 -0400 Subject: [PATCH 122/159] fix(telescope): adapt rest extension to `rest.nvim` v2 changes --- lua/telescope/_extensions/rest.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lua/telescope/_extensions/rest.lua b/lua/telescope/_extensions/rest.lua index f2a0f1f6..a69c3a9a 100644 --- a/lua/telescope/_extensions/rest.lua +++ b/lua/telescope/_extensions/rest.lua @@ -4,7 +4,7 @@ if not has_telescope then return end -local rest = require("rest-nvim") +local rest_functions = require("rest-nvim.functions") local state = require("telescope.actions.state") @@ -14,11 +14,9 @@ local finders = require("telescope.finders") local pickers = require("telescope.pickers") local conf = require("telescope.config").values -local config = require("rest-nvim.config") - -local function rest_env_select(opt) - local pattern = config.get("env_pattern") - local edit = config.get("env_edit_command") +local function rest_env_select(_) + local pattern = _G._rest_nvim.env_pattern + local edit = _G._rest_nvim.env_edit_command local command = string.format("fd -HI '%s'", pattern) local result = io.popen(command):read("*a") @@ -41,7 +39,7 @@ local function rest_env_select(opt) if selection == nil then return end - rest.select_env(selection[1]) + rest_functions.env("set", selection[1]) end) map("i", "", function() actions.close(prompt_bufnr) From 17dd4098d589d884c06ba47e62baf8655df251f8 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 19:40:29 -0400 Subject: [PATCH 123/159] fix(tests): use a comment as a separator to make http parser happy --- tests/env_vars/post_create_user.http | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/env_vars/post_create_user.http b/tests/env_vars/post_create_user.http index 1b89860a..ada24aa6 100644 --- a/tests/env_vars/post_create_user.http +++ b/tests/env_vars/post_create_user.http @@ -16,7 +16,7 @@ Authorization: Bearer {{TOKEN}} "id" : "{{$uuid}}" } ----- +# ----- POST {{URL}} Content-Type: application/json From 3f667b12c38f4be7c041f9c0510a154e2f356842 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 19:53:20 -0400 Subject: [PATCH 124/159] fix(lualine): adapt rest component to `rest.nvim` v2 changes --- lua/lualine/components/rest.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/lualine/components/rest.lua b/lua/lualine/components/rest.lua index 934e7c04..0513668e 100644 --- a/lua/lualine/components/rest.lua +++ b/lua/lualine/components/rest.lua @@ -1,6 +1,5 @@ local lualine_require = require("lualine_require") local M = lualine_require.require("lualine.component"):extend() -local config = require("rest-nvim.config") local default_options = { fg = "#428890", @@ -23,7 +22,7 @@ end function M.update_status() local current_filetype = vim.bo.filetype if current_filetype == "http" then - return config.get("env_file") + return _G._rest_nvim.env_file end return "" end From 6fee1d5ae8dabe44fb24d4047b846e34d1c5f9ec Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 21:29:09 -0400 Subject: [PATCH 125/159] ref!: remove Nix flake, test CI and logic The reason behind this is that I do not really know anything about Nix and I am not willing to learn it to fix the existing flake --- .github/workflows/test.yml | 14 ------- Makefile | 5 --- flake.lock | 43 ------------------- flake.nix | 84 -------------------------------------- 4 files changed, 146 deletions(-) delete mode 100644 .github/workflows/test.yml delete mode 100644 flake.lock delete mode 100644 flake.nix diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 06659264..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: test - -on: [push, pull_request] - -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v21 - with: - nix_path: nixpkgs=channel:nixos-unstable - - run: | - nix develop .#ci -c make test diff --git a/Makefile b/Makefile index 3a526153..4fd4be3b 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,3 @@ lint: format: stylua . -test: - # possible args to test_directory: sequential=true,keep_going=false - # minimal.vim is generated when entering the flake, aka `nix develop ./contrib` - nvim --headless -u minimal.vim -c "lua require('plenary.test_harness').test_directory('.', {minimal_init='minimal.vim'})" - diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 11ae34c1..00000000 --- a/flake.lock +++ /dev/null @@ -1,43 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1670751203, - "narHash": "sha256-XdoH1v3shKDGlrwjgrNX/EN8s3c+kQV7xY6cLCE8vcI=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "64e0bf055f9d25928c31fb12924e59ff8ce71e60", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index 84538380..00000000 --- a/flake.nix +++ /dev/null @@ -1,84 +0,0 @@ -{ - description = "rest.nvim: A fast Neovim http client written in Lua"; - - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - }; - - outputs = { self, nixpkgs, flake-utils, ... }: - flake-utils.lib.eachSystem [ "x86_64-linux" ] (system: - let - pkgs = nixpkgs.legacyPackages.${system}; - - mkDevShell = luaVersion: - let - luaEnv = pkgs."lua${luaVersion}".withPackages (lp: with lp; [ - busted - luacheck - luarocks - ]); - in - pkgs.mkShell { - name = "rest-nvim"; - buildInputs = [ - pkgs.sumneko-lua-language-server - luaEnv - pkgs.stylua - ]; - - shellHook = let - myVimPackage = with pkgs.vimPlugins; { - start = [ plenary-nvim (nvim-treesitter.withPlugins ( - plugins: with plugins; [ - tree-sitter-lua - tree-sitter-http - tree-sitter-json - ] - ))]; - }; - packDirArgs.myNeovimPackages = myVimPackage; - in - '' - export DEBUG_PLENARY="debug" - cat <<-EOF > minimal.vim - set rtp+=. - set packpath^=${pkgs.vimUtils.packDir packDirArgs} - EOF - ''; - }; - - in - { - - devShells = { - default = self.devShells.${system}.luajit; - ci = let - neovimConfig = pkgs.neovimUtils.makeNeovimConfig { - plugins = with pkgs.vimPlugins; [ - { plugin = (nvim-treesitter.withPlugins ( - plugins: with plugins; [ - tree-sitter-lua - tree-sitter-http - tree-sitter-json - ] - )); - } - { plugin = plenary-nvim; } - ]; - customRC = ""; - wrapRc = false; - }; - myNeovim = pkgs.wrapNeovimUnstable pkgs.neovim-unwrapped neovimConfig; - in - (mkDevShell "jit").overrideAttrs(oa: { - buildInputs = oa.buildInputs ++ [ myNeovim ]; - }); - - luajit = mkDevShell "jit"; - lua-51 = mkDevShell "5_1"; - lua-52 = mkDevShell "5_2"; - }; - }); -} - From 870240a4ba416d034a1ab9b23c1684c93cfcd29a Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 21:32:40 -0400 Subject: [PATCH 126/159] chore: format using stylua --- ftdetect/http.lua | 2 +- lua/rest-nvim/autocmds.lua | 2 +- lua/rest-nvim/commands.lua | 4 ++- lua/rest-nvim/health.lua | 38 +++++++++++++++++++-------- lua/rest-nvim/parser/dynamic_vars.lua | 4 +-- lua/rest-nvim/result/winbar.lua | 9 ++++--- plugin/rest-nvim.lua | 6 +---- 7 files changed, 39 insertions(+), 26 deletions(-) diff --git a/ftdetect/http.lua b/ftdetect/http.lua index bb600321..4cd204f0 100644 --- a/ftdetect/http.lua +++ b/ftdetect/http.lua @@ -1,5 +1,5 @@ vim.filetype.add({ extension = { http = "http", - } + }, }) diff --git a/lua/rest-nvim/autocmds.lua b/lua/rest-nvim/autocmds.lua index 7b94024b..5c14084c 100644 --- a/lua/rest-nvim/autocmds.lua +++ b/lua/rest-nvim/autocmds.lua @@ -34,7 +34,7 @@ function autocmds.setup() functions.cycle_result_pane("next") end) end, - desc = "Set up rest.nvim results buffer keybinds" + desc = "Set up rest.nvim results buffer keybinds", }) end diff --git a/lua/rest-nvim/commands.lua b/lua/rest-nvim/commands.lua index f05e6988..39b28e82 100644 --- a/lua/rest-nvim/commands.lua +++ b/lua/rest-nvim/commands.lua @@ -130,7 +130,9 @@ local rest_command_tbl = { if #args > 1 then ---@diagnostic disable-next-line need-check-nil - logger:error("Too many arguments were passed to the 'result' command: 1 argument was expected, " .. #args .. " were passed") + logger:error( + "Too many arguments were passed to the 'result' command: 1 argument was expected, " .. #args .. " were passed" + ) return end if not vim.tbl_contains({ "next", "prev" }, args[1]) then diff --git a/lua/rest-nvim/health.lua b/lua/rest-nvim/health.lua index 61c62911..f424548b 100644 --- a/lua/rest-nvim/health.lua +++ b/lua/rest-nvim/health.lua @@ -35,17 +35,16 @@ local function install_health() if not dep_info.found then local err_advice = "Install it through `luarocks --local install " .. dep .. "`" if dep:find("nvim") then - err_advice = "Install it through your preferred plugins manager or luarocks by using `luarocks --local install " .. dep .. "`" + err_advice = "Install it through your preferred plugins manager or luarocks by using `luarocks --local install " + .. dep + .. "`" -- NOTE: nvim-treesitter has a weird bug in luarocks due to the parsers installation logic so let's mark it as not recommended if dep == "nvim-treesitter" then err_advice = err_advice .. " (not recommended yet!)" end end - vim.health.error( - "Dependency `" .. dep .. "` was not found (" .. dep_info.error .. ")", - err_advice - ) + vim.health.error("Dependency `" .. dep .. "` was not found (" .. dep_info.error .. ")", err_advice) else vim.health.ok("Dependency `" .. dep .. "` was found") end @@ -54,7 +53,9 @@ local function install_health() -- Tree-sitter and HTTP parser local found_treesitter, ts_info = pcall(require, "nvim-treesitter.info") if not found_treesitter then - vim.health.warn("Could not check for tree-sitter `http` parser existence because `nvim-treesitter` is not installed") + vim.health.warn( + "Could not check for tree-sitter `http` parser existence because `nvim-treesitter` is not installed" + ) else local is_http_parser_installed = vim.tbl_contains(ts_info.installed_parsers(), "http") if not is_http_parser_installed then @@ -66,7 +67,6 @@ local function install_health() vim.health.ok("Tree-sitter `http` parser is installed") end end - end local function configuration_health() @@ -87,16 +87,32 @@ local function configuration_health() for ft, formatter in pairs(formatters) do if type(formatter) == "string" then if vim.fn.executable(formatter) ~= 1 then - vim.health.warn("Formatter for `" .. ft .. "` is set to `" .. formatter .. "`, however, rest.nvim could not find it in your system") + vim.health.warn( + "Formatter for `" + .. ft + .. "` is set to `" + .. formatter + .. "`, however, rest.nvim could not find it in your system" + ) else - vim.health.ok("Formatter for `" .. ft .. "` is set to `" .. formatter .. "` and rest.nvim found it in your system") + vim.health.ok( + "Formatter for `" .. ft .. "` is set to `" .. formatter .. "` and rest.nvim found it in your system" + ) end elseif type(formatter) == "function" then local _, fmt_meta = formatter() if not fmt_meta.found then - vim.health.warn("Formatter for `" .. ft .. "` is set to `" .. fmt_meta.name .. "`, however, rest.nvim could not find it in your system") + vim.health.warn( + "Formatter for `" + .. ft + .. "` is set to `" + .. fmt_meta.name + .. "`, however, rest.nvim could not find it in your system" + ) else - vim.health.ok("Formatter for `" .. ft .. "` is set to `" .. fmt_meta.name .. "` and rest.nvim found it in your system") + vim.health.ok( + "Formatter for `" .. ft .. "` is set to `" .. fmt_meta.name .. "` and rest.nvim found it in your system" + ) end end end diff --git a/lua/rest-nvim/parser/dynamic_vars.lua b/lua/rest-nvim/parser/dynamic_vars.lua index 1d40d249..4bf2cce1 100644 --- a/lua/rest-nvim/parser/dynamic_vars.lua +++ b/lua/rest-nvim/parser/dynamic_vars.lua @@ -50,9 +50,7 @@ function dynamic_vars.read(name) local vars = dynamic_vars.retrieve_all() if not vim.tbl_contains(vim.tbl_keys(vars), name) then ---@diagnostic disable-next-line need-check-nil - logger:error( - "The dynamic variable '" .. name .. "' was not found. Maybe it's written wrong or doesn't exist?" - ) + logger:error("The dynamic variable '" .. name .. "' was not found. Maybe it's written wrong or doesn't exist?") return nil end diff --git a/lua/rest-nvim/result/winbar.lua b/lua/rest-nvim/result/winbar.lua index f590e4a6..a2be8439 100644 --- a/lua/rest-nvim/result/winbar.lua +++ b/lua/rest-nvim/result/winbar.lua @@ -17,7 +17,8 @@ winbar.current_pane_index = 1 ---@return string function winbar.get_content(stats) -- winbar panes - local content = [[%#Normal# %1@v:lua._G._rest_nvim_winbar@%#ResponseHighlight#Response%X%#Normal# %#RestText#|%#Normal# %2@v:lua._G._rest_nvim_winbar@%#HeadersHighlight#Headers%X%#Normal# %#RestText#|%#Normal# %3@v:lua._G._rest_nvim_winbar@%#CookiesHighlight#Cookies%X%#Normal# %#RestText#|%#Normal# %4@v:lua._G._rest_nvim_winbar@%#StatsHighlight#Stats%X%#Normal# %=%<]] + local content = + [[%#Normal# %1@v:lua._G._rest_nvim_winbar@%#ResponseHighlight#Response%X%#Normal# %#RestText#|%#Normal# %2@v:lua._G._rest_nvim_winbar@%#HeadersHighlight#Headers%X%#Normal# %#RestText#|%#Normal# %3@v:lua._G._rest_nvim_winbar@%#CookiesHighlight#Cookies%X%#Normal# %#RestText#|%#Normal# %4@v:lua._G._rest_nvim_winbar@%#StatsHighlight#Stats%X%#Normal# %=%<]] -- winbar statistics if not vim.tbl_isempty(stats) then @@ -49,9 +50,9 @@ end ---@type { [number]: ResultPane }[] winbar.pane_map = { [1] = { name = "Response", contents = { "Fetching ..." } }, - [2] = { name = "Headers", contents = { "Fetching ..." } }, - [3] = { name = "Cookies", contents = { "Fetching ..." } }, - [4] = { name = "Stats", contents = { "Fetching ..." } }, + [2] = { name = "Headers", contents = { "Fetching ..." } }, + [3] = { name = "Cookies", contents = { "Fetching ..." } }, + [4] = { name = "Stats", contents = { "Fetching ..." } }, } ---Get the foreground value of a highlighting group diff --git a/plugin/rest-nvim.lua b/plugin/rest-nvim.lua index 40934e5d..491d663e 100644 --- a/plugin/rest-nvim.lua +++ b/plugin/rest-nvim.lua @@ -7,7 +7,6 @@ if vim.g.loaded_rest_nvim then return end - --- Dependencies management --- ------------------------------- -- This variable is going to hold the dependencies state (whether they are found or not), @@ -51,10 +50,7 @@ for dep, err in pairs(dependencies) do error = err, } if not found_dep2 then - vim.notify( - "[rest.nvim] Dependency '" .. dep .. "' was not found. " .. err, - vim.log.levels.ERROR - ) + vim.notify("[rest.nvim] Dependency '" .. dep .. "' was not found. " .. err, vim.log.levels.ERROR) else rest_nvim_deps[dep] = { found = true, From 02fdadb2f73159ddf2c108a674b426f1d966cec5 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 21:46:34 -0400 Subject: [PATCH 127/159] feat(ci): update actions versioning, add missing token in release-please action --- .github/workflows/format.yml | 10 +++++++--- .github/workflows/lint.yml | 4 ++-- .github/workflows/luarocks.yml | 2 +- .github/workflows/release.yml | 2 ++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index efbf7ef1..355087da 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -7,19 +7,23 @@ on: - ".github/**" - "**.md" +permissions: + contents: write + pull-requests: write + jobs: stylua: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup and run stylua - uses: JohnnyMorganz/stylua-action@v3 + uses: JohnnyMorganz/stylua-action@v4 with: token: ${{ secrets.GITHUB_TOKEN }} version: v0.19.1 args: --config-path=stylua.toml . - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4 + uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "chore: autoformat with stylua" branch: ${{ github.ref }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 89fe097f..b91f6916 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,8 +10,8 @@ jobs: luacheck: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: nebularg/actions-luacheck@v1.1.0 + - uses: actions/checkout@v4 + - uses: nebularg/actions-luacheck@v1.1.2 with: files: 'lua/' config: 'https://raw.githubusercontent.com/NTBBloodbath/rest.nvim/main/.luacheckrc' diff --git a/.github/workflows/luarocks.yml b/.github/workflows/luarocks.yml index f576088c..3b048e50 100644 --- a/.github/workflows/luarocks.yml +++ b/.github/workflows/luarocks.yml @@ -14,7 +14,7 @@ jobs: name: Luarocks upload steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required to count the commits - name: Install build dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 067c97c4..9a9d9d1e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,7 @@ on: push: branches: - main + workflow_dispatch: permissions: contents: write @@ -16,5 +17,6 @@ jobs: steps: - uses: google-github-actions/release-please-action@v3 with: + token: ${{ secrets.CI_TOKEN }} release-type: simple package-name: rest.nvim From 532a47d196d9b03b21e17258d5ff56a9a6214e65 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 21:46:59 -0400 Subject: [PATCH 128/159] tests(script_vars): improve documentation --- tests/script_vars/script_vars.http | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/script_vars/script_vars.http b/tests/script_vars/script_vars.http index 9e21b1c8..f7c3ed7f 100644 --- a/tests/script_vars/script_vars.http +++ b/tests/script_vars/script_vars.http @@ -6,6 +6,7 @@ GET https://jsonplaceholder.typicode.com/posts/3 local body = context.json_decode(context.result.body) +-- These environment variables are stored in 'vim.env' context.set_env("userId", body.userId) context.set_env("postId", body.id) From 8b4d97dfba67836ceac3c2430e9d0c76e564c7dd Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Tue, 5 Mar 2024 21:49:18 -0400 Subject: [PATCH 129/159] docs(README): overhaul README for v2 release --- README.md | 326 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 185 insertions(+), 141 deletions(-) diff --git a/README.md b/README.md index 5182815f..46a69c71 100644 --- a/README.md +++ b/README.md @@ -3,220 +3,265 @@ # rest.nvim ![License](https://img.shields.io/github/license/NTBBloodbath/rest.nvim?style=for-the-badge) -![Neovim version](https://img.shields.io/badge/Neovim-0.5-5ba246?style=for-the-badge&logo=neovim) +![Neovim version](https://img.shields.io/badge/Neovim-0.9.2-5ba246?style=for-the-badge&logo=neovim) +[![LuaRocks](https://img.shields.io/luarocks/v/teto/rest.nvim?style=for-the-badge&logo=lua&color=blue)](https://luarocks.org/modules/teto/rest.nvim) ![Matrix](https://img.shields.io/matrix/rest.nvim%3Amatrix.org?server_fqdn=matrix.org&style=for-the-badge&logo=element&label=Matrix&color=55b394&link=https%3A%2F%2Fmatrix.to%2F%23%2F%23rest.nvim%3Amatrix.org) [Features](#features) • [Install](#install) • [Usage](#usage) • [Contribute](#contribute) -![Demo](./assets/demo.png) +![Demo](https://github.com/rest-nvim/rest.nvim/assets/36456999/e9b536a5-f7b2-4cd8-88fb-fdc5409dd2a4) --- -A fast Neovim http client written in Lua. +A very fast, powerful, extensible and asynchronous Neovim HTTP client written in Lua. -`rest.nvim` makes use of a curl wrapper made in pure Lua by [tami5] and implemented -in `plenary.nvim` so, in other words, `rest.nvim` is a curl wrapper so you don't -have to leave Neovim! +`rest.nvim` by default makes use of native [cURL](https://curl.se/) bindings. In this way, you get +absolutely all the power that cURL provides from the comfort of our editor just by using a keybind +and without wasting the precious resources of your machine. -> **IMPORTANT:** If you are facing issues, please [report them](https://github.com/rest-nvim/rest.nvim/issues/new) +In addition to this, you can also write integrations with external HTTP clients, such as the postman +CLI. For more information on this, please see this [blog post](https://amartin.codeberg.page/posts/first-look-at-thunder-rest/#third-party-clients). -## Notices - -- **2023-07-12**: tagged 0.2 release before changes for 0.10 compatibility -- **2021-11-04**: HTTP Tree-Sitter parser now depends on JSON parser for the JSON bodies detection, - please install it too. -- **2021-08-26**: We have deleted the syntax file for HTTP files to start using the tree-sitter parser instead, - please see [Tree-Sitter parser](#tree-sitter-parser) section for more information. -- **2021-07-01**: Now for getting syntax highlighting in http files you should - add a `require('rest-nvim').setup()` to your `rest.nvim` setup, refer to [packer.nvim](#packernvim). - This breaking change should allow lazy-loading of `rest.nvim`. +> [!IMPORTANT] +> +> If you are facing issues, please [report them](https://github.com/rest-nvim/rest.nvim/issues/new) so we can work in a fix together :) ## Features - Easy to use -- Fast execution time -- Run request under cursor -- Syntax highlight for http files and output -- Possibility of using environment variables in http files +- Friendly and organized request results window +- Fast runtime with statistics about your request +- Tree-sitter based parsing and syntax highlighting for speed and perfect accuracy +- Possibility of using dynamic/environment variables and Lua scripting in HTTP files ## Install -> **WARNING:** rest.nvim requires Neovim >= 0.5 to work. +> [!NOTE] +> +> rest.nvim requires Neovim >= 0.9.2 to work. ### Dependencies - System-wide - - curl -- Optional [can be changed, see config below] - - jq (to format JSON output) - - tidy (to format HTML output) -- Other plugins - - [plenary.nvim](https://github.com/nvim-lua/plenary.nvim) + - `Python` (only if you are using `packer.nvim` or `lazy.nvim` plus `luarocks.nvim` for the installation) + - `cURL` development headers (usually called `libcurl-dev` or `libcurl-devel` depending on your Linux distribution) +- Optional [can be changed, see config below](#default-configuration) + - `jq` (to format JSON output) + - `tidy` (to format HTML output) + +### [rocks.nvim](https://github.com/nvim-neorocks/rocks.nvim) (recommended) + +```vim +:Rocks install rest.nvim +``` -### packer.nvim +### [packer.nvim](https://github.com/wbthomason/packer.nvim) ```lua use { "rest-nvim/rest.nvim", - requires = { "nvim-lua/plenary.nvim" }, + rocks = { "lua-curl", "nvim-nio", "mimetypes", "xml2lua" }, config = function() - require("rest-nvim").setup({ - -- Open request results in a horizontal split - result_split_horizontal = false, - -- Keep the http file buffer above|left when split horizontal|vertical - result_split_in_place = false, - -- Skip SSL verification, useful for unknown certificates - skip_ssl_verification = false, - -- Encode URL before making request - encode_url = true, - -- Highlight request on run - highlight = { - enabled = true, - timeout = 150, - }, - result = { - -- toggle showing URL, HTTP info, headers at top the of result window - show_url = true, - -- show the generated curl command in case you want to launch - -- the same request via the terminal (can be verbose) - show_curl_command = false, - show_http_info = true, - show_headers = true, - -- table of curl `--write-out` variables or false if disabled - -- for more granular control see Statistics Spec - show_statistics = false, - -- executables or functions for formatting response body [optional] - -- set them to false if you want to disable them - formatters = { - json = "jq", - html = function(body) - return vim.fn.system({"tidy", "-i", "-q", "-"}, body) - end - }, - }, - -- Jump to request line on run - jump_to_request = false, - env_file = '.env', - -- for telescope select - env_pattern = "\\.env$", - env_edit_command = "tabedit", - custom_dynamic_variables = {}, - yank_dry_run = true, - }) - end + require("rest-nvim").setup() + end, } ``` ### [lazy.nvim](https://github.com/folke/lazy.nvim) ```lua --- plugins/rest.lua -return { - "rest-nvim/rest.nvim", - dependencies = { { "nvim-lua/plenary.nvim" } }, - config = function() - require("rest-nvim").setup({ - --- Get the same options from Packer setup - }) - end +{ + "vhyrro/luarocks.nvim", + branch = "more-fixes", + config = function() + require("luarocks").setup({}) + end, +}, +{ + "rest-nvim/rest.nvim", + dependencies = { "luarocks.nvim" }, + config = function() + require("rest-nvim").setup() + end, +} +``` + +### Default configuration + +This is the default configuration of `rest.nvim`, it is fully documented and typed internally so you +get a good experience during autocompletion :) + +> [!NOTE] +> +> You can also check out `:h rest-nvim.config` for documentation. + +```lua +local default_config = { + client = "curl", + env_file = ".env", + env_pattern = "\\.env$", + env_edit_command = "tabedit", + encode_url = true, + skip_ssl_verification = false, + custom_dynamic_variables = {}, + logs = { + level = "info", + save = true, + }, + result = { + split = { + horizontal = false, + in_place = false, + stay_in_current_window_after_split = true, + }, + behavior = { + show_info = { + url = true, + headers = true, + http_info = true, + curl_command = true, + }, + statistics = { + enable = true, + ---@see https://curl.se/libcurl/c/curl_easy_getinfo.html + stats = { + { "total_time", title = "Time taken:" }, + { "size_download_t", title = "Download size:" }, + }, + }, + formatters = { + json = "jq", + html = function(body) + if vim.fn.executable("tidy") == 0 then + return body, { found = false, name = "tidy" } + end + local fmt_body = vim.fn.system({ + "tidy", + "-i", + "-q", + "--tidy-mark", "no", + "--show-body-only", "auto", + "--show-errors", "0", + "--show-warnings", "0", + "-", + }, body):gsub("\n$", "") + + return fmt_body, { found = true, name = "tidy" } + end, + }, + }, + }, + highlight = { + enable = true, + timeout = 750, + }, + ---Example: + --- + ---```lua + ---keybinds = { + --- { + --- "rr", ":Rest run", "Run request under the cursor", + --- }, + --- { + --- "rl", ":Rest run last", "Re-run latest request", + --- }, + ---} + --- + ---``` + ---@see vim.keymap.set + keybinds = {}, } ``` -### Tree-Sitter parser +### Tree-Sitter parsing -We are using a Tree-Sitter parser for our HTTP files, in order to get the correct syntax highlighting -for HTTP files (including JSON bodies) you should add the following into your `ensure_installed` table -in your tree-sitter setup. +`rest.nvim` uses tree-sitter as a first-class citizen, so it will not work if the required parsers are +not installed. These parsers are as follows and you can add them to your `ensure_installed` table +in your `nvim-treesitter` configuration. ```lua -ensure_installed = { "http", "json" } +ensure_installed = { "lua", "xml", "http", "json", "graphql" } ``` -Or manually run `:TSInstall http json`. +Or manually run `:TSInstall lua xml http json graphql`. ## Keybindings By default `rest.nvim` does not have any key mappings so you will not have conflicts with any of your existing ones. -To run `rest.nvim` you should map the following commands: +However, `rest.nvim` exposes a `:Rest` command in HTTP files that you can use to create your +keybinds easily. For example: + +```lua +keybinds = { + { + "rr", ":Rest run", "Run request under the cursor", + }, + { + "rl", ":Rest run last", "Re-run latest request", + }, +} +``` + +You can still also use the legacy `RestNvim` commands for mappings: - `RestNvim`, run the request under the cursor -- `RestNvimPreview`, preview the request cURL command - `RestNvimLast`, re-run the last request -## Settings - -- `result_split_horizontal` opens result on a horizontal split (default opens - on vertical) -- `result_split_in_place` opens result below|right on horizontal|vertical split - (default opens top|left on horizontal|vertical split) -- `skip_ssl_verification` passes the `-k` flag to cURL in order to skip SSL verification, - useful when using unknown certificates -- `encode_url` flag to encode the URL before making request -- `highlight` allows to enable and configure the highlighting of the selected request when send, -- `jump_to_request` moves the cursor to the selected request line when send, -- `env_file` specifies file name that consist environment variables (default: .env) -- `custom_dynamic_variables` allows to extend or overwrite built-in dynamic variable functions - (default: {}) - -### Statistics Spec - -| Property | Type | Description | -| :------- | :----------------- | :----------------------------------------------------- | -| [1] | string | `--write-out` variable name, see `man curl`. Required. | -| title | string | Replaces the variable name in the output if defined. | -| type | string or function | Specifies type transformation for the output value. Default transformers are `time` and `size`. Can also be a function which takes the value as a parameter and returns a string. | +> [!NOTE] +> +> 1. `RestNvimPreview` has been removed, as we can no longer implement it with the current +> cURL implementation. +> +> 2. The legacy `` mappings will raise a deprecation warning suggesting you to switch to +> the `:Rest` command, as they are going to be completely removed in the next version. ## Usage Create a new http file or open an existing one and place the cursor over the -request method (e.g. `GET`) and run `rest.nvim`. +request and run the :Rest run command. -> **NOTES**: +> [!NOTE] > -> 1. `rest.nvim` follows the RFC 2616 request format so any other -> http file should work without problems. -> -> 2. You can find examples of use in [tests](./tests) +> You can find examples of use in the [tests](./tests) directory. --- -### Debug - - -Run `export DEBUG_PLENARY="debug"` before starting nvim. Logs will appear most -likely in ~/.cache/nvim/rest.nvim.log +### Telescope Extension -## Telescope Extension +`rest.nvim` provides a [telescope.nvim] extension to select the environment variables file, +you can load and use it with the following snippet: ```lua - -- first load extension require("telescope").load_extension("rest") --- then use it +-- then use it, you can also use the `:Telescope rest select_env` command require("telescope").extensions.rest.select_env() - ``` +Here is a preview of the extension working :) + +![telescope rest extension demo](https://github.com/rest-nvim/rest.nvim/assets/36456999/a810954f-b45c-44ee-854d-94039de8e2fc) ### Mappings -- Enter: Select Env file -- Ctrl+O: Edit Env file +- Enter: Select Env file +- Ctrl + O: Edit Env file ### Config -- env_pattern: For env file pattern -- env_edit_command: For env file edit command +- `env_pattern`: For env file pattern +- `env_edit_command`: For env file edit command ## Lualine We also have lualine component to get what env file you select! -And dont't worry, it will only show up under http files. + +And dont't worry, it will only show up under HTTP files. ```lua --- Juse add a component in your lualine config +-- Just add a component in your lualine config { sections = { lualine_x = { @@ -225,7 +270,7 @@ And dont't worry, it will only show up under http files. } } --- To custom icon and color +-- To use a custom icon and color { sections = { lualine_x = { @@ -239,6 +284,10 @@ And dont't worry, it will only show up under http files. } ``` +Here is a preview of the component working :) + +![lualine component demo](https://github.com/rest-nvim/rest.nvim/assets/81607010/cf4bb327-61aa-494c-84a5-82f5ee21004f) + ## Contribute 1. Fork it (https://github.com/rest-nvim/rest.nvim/fork) @@ -247,9 +296,6 @@ And dont't worry, it will only show up under http files. 4. Push to the branch (git push origin my-new-feature) 5. Create a new Pull Request -To run the tests, enter a nix shell with `nix develop ./contrib`, then run `make -test`. - ## Related software - [vim-rest-console](https://github.com/diepm/vim-rest-console) @@ -259,6 +305,4 @@ test`. ## License -rest.nvim is [MIT Licensed](./LICENSE). - -[tami5]: https://github.com/tami5 +rest.nvim is [GPLv3 Licensed](./LICENSE). From 1dd7a7ff2bb20825588e4f0332f1216374e0f7f2 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 00:33:03 -0400 Subject: [PATCH 130/159] feat(result): add `help` window module --- lua/rest-nvim/result/help.lua | 110 ++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 lua/rest-nvim/result/help.lua diff --git a/lua/rest-nvim/result/help.lua b/lua/rest-nvim/result/help.lua new file mode 100644 index 00000000..557dd39b --- /dev/null +++ b/lua/rest-nvim/result/help.lua @@ -0,0 +1,110 @@ +---@mod rest-nvim.result.help rest.nvim result buffer help +--- +---@brief [[ +--- +--- rest.nvim result buffer help window handling +--- +---@brief ]] + +local help = {} + +local result = require("rest-nvim.result") + +---Get or create a new request window help buffer +local function get_or_create_buf() + local tmp_name = "rest_winbar_help" + local existing_buf, help_bufnr = false, nil + + -- Check if the help buffer is already loaded + for _, id in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_get_name(id):find(tmp_name) then + existing_buf = true + help_bufnr = id + end + end + + if not existing_buf then + -- Create a new buffer + local new_bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_name(new_bufnr, tmp_name) + vim.api.nvim_set_option_value("ft", "markdown", { buf = new_bufnr }) + vim.api.nvim_set_option_value("buftype", "nofile", { buf = new_bufnr }) + + -- Write to buffer + local buf_content = { + "**`rest.nvim` results window help**", + "", + "**Keybinds**:", + " - `H`: go to previous pane", + " - `L`: go to next pane", + " - `q`: close results window", + "", + "**Press `q` to close this help window**", + } + result.write_block(new_bufnr, buf_content, false, false) + + return new_bufnr + end + + return help_bufnr +end + +---Open the request results help window +function help.open() + local help_bufnr = get_or_create_buf() + + -- Get the results buffer window ID + local winnr + for _, id in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(id)):find("rest_nvim_results") then + winnr = id + end + end + + -- Help window sizing and positioning + local width = vim.api.nvim_win_get_width(winnr) / 2 + local height = 8 + + local col = vim.api.nvim_win_get_width(winnr) - width - 4 + local row = vim.api.nvim_win_get_height(winnr) - height - 4 + + -- Display the help buffer window + ---@cast help_bufnr number + local help_win = vim.api.nvim_open_win(help_bufnr, true, { + style = "minimal", + border = "single", + win = winnr, + relative = "win", + width = width, + height = height, + row = row, + col = col, + }) + + -- Always conceal the markdown content + vim.api.nvim_set_option_value("conceallevel", 2, { win = help_win }) + vim.api.nvim_set_option_value("concealcursor", "n", { win = help_win }) +end + +---Close the request results help window +function help.close() + local logger = _G._rest_nvim.logger + + -- Get the help buffer ID + local winnr + for _, id in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(id)):find("rest_winbar_help") then + winnr = id + end + end + + if not winnr then + ---@diagnostic disable-next-line need-check-nil + logger:error("Could not find a help window to close") + return + end + + vim.api.nvim_win_close(winnr, false) +end + +return help From 53f4e96d7e493d820a275d264b800b6e2df5b2bf Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 00:34:32 -0400 Subject: [PATCH 131/159] feat(autocmds): add `?` keybind for the results buffer keybinds help and `q` to close, some keybinds improvements --- lua/rest-nvim/autocmds.lua | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lua/rest-nvim/autocmds.lua b/lua/rest-nvim/autocmds.lua index 5c14084c..2b5fc200 100644 --- a/lua/rest-nvim/autocmds.lua +++ b/lua/rest-nvim/autocmds.lua @@ -10,6 +10,7 @@ local autocmds = {} local commands = require("rest-nvim.commands") local functions = require("rest-nvim.functions") +local result_help = require("rest-nvim.result.help") ---Set up Rest autocommands group and set `:Rest` command on `*.http` files function autocmds.setup() @@ -26,16 +27,33 @@ function autocmds.setup() vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { group = rest_nvim_augroup, pattern = "rest_nvim_results", - callback = function(_) + callback = function(args) vim.keymap.set("n", "H", function() functions.cycle_result_pane("prev") - end) + end, { desc = "Go to previous winbar pane" }) vim.keymap.set("n", "L", function() functions.cycle_result_pane("next") - end) + end, { desc = "Go to next winbar pane" }) + vim.keymap.set("n", "?", result_help.open, { + desc = "Open rest.nvim request results help window", + buffer = args.buf, + }) + vim.keymap.set("n", "q", function() + vim.api.nvim_buf_delete(args.buf, { unload = true }) + end, { desc = "Close rest.nvim results buffer", buffer = args.buf }) end, desc = "Set up rest.nvim results buffer keybinds", }) + vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { + group = rest_nvim_augroup, + pattern = "rest_winbar_help", + callback = function(args) + vim.keymap.set("n", "q", result_help.close, { + desc = "Close rest.nvim request results help window", + buffer = args.buf, + }) + end + }) end ---Register a new autocommand in the `Rest` augroup From f9fdd9c39d99d5af7da7cbe985de2ae698e75da6 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 00:37:34 -0400 Subject: [PATCH 132/159] chore: small docs adjustments in setup function --- lua/rest-nvim/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/rest-nvim/init.lua b/lua/rest-nvim/init.lua index 0c6eb7f1..05c1be80 100644 --- a/lua/rest-nvim/init.lua +++ b/lua/rest-nvim/init.lua @@ -25,8 +25,9 @@ function rest.setup(user_configs) autocmds.setup() -- Set up tree-sitter HTTP parser branch - -- NOTE: remove this piece of code once rest.nvim v2 has been pushed + -- NOTE: remove this piece of code once rest.nvim v2 has been pushed, -- and tree-sitter-http `next` branch has been merged + -- and nvim-treesitter http is up-to-date local ok, treesitter_parsers = pcall(require, "nvim-treesitter.parsers") if ok then local parser_config = treesitter_parsers.get_parser_configs() From ecf678080c224fd742219bbe32fdbb4e9cc36f35 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 00:52:05 -0400 Subject: [PATCH 133/159] feat(doc): update user documentation --- doc/rest-nvim-api.txt | 57 ++++++++++++++++++++++++++++++++++++++++ doc/rest-nvim-config.txt | 8 +++--- doc/tags | 7 +++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/doc/rest-nvim-api.txt b/doc/rest-nvim-api.txt index 4c0d9483..33d84781 100644 --- a/doc/rest-nvim-api.txt +++ b/doc/rest-nvim-api.txt @@ -6,6 +6,26 @@ The Lua API for rest.nvim Intended for use by third-party modules that extend its functionalities. +api.VERSION *api.VERSION* + rest.nvim API version, equals to the current rest.nvim version. Meant to be used by modules later + + Type: ~ + (string) + + See: ~ + |vim.version| + + +api.namespace *api.namespace* + rest.nvim namespace used for buffer highlights + + Type: ~ + (number) + + See: ~ + |vim.api.nvim_create_namespace| + + *api.register_rest_autocmd* api.register_rest_autocmd({events}, {cb}, {description}) @@ -25,6 +45,17 @@ api.register_rest_subcommand({name}, {cmd}) {cmd} (RestCmd) + *api.register_rest_keybind* +api.register_rest_keybind({mode}, {lhs}, {cmd}, {opts}) + + + Parameters: ~ + {mode} (string) Keybind mode + {lhs} (string) Keybind trigger + {cmd} (string) Command to be run + {opts} (table) Keybind options + + ============================================================================== rest.nvim utilities *rest-nvim.utils* @@ -52,6 +83,17 @@ utils.read_file({path}) *utils.read_file* (string) + *utils.highlight* +utils.highlight({bufnr}, {start}, {end_}, {ns}) + Highlight a request + + Parameters: ~ + {bufnr} (number) Buffer handler ID + {start} (number) Request tree-sitter node start + {end_} (number) Request tree-sitter node end + {ns} (number) rest.nvim Neovim namespace + + ============================================================================== rest.nvim functions *rest-nvim.functions* @@ -272,4 +314,19 @@ winbar.set_pane({selected}) *winbar.set_pane* {selected} (number) winbar pane index +============================================================================== +rest.nvim result buffer help *rest-nvim.result.help* + + + rest.nvim result buffer help window handling + + +help.open() *help.open* + Open the request results help window + + +help.close() *help.close* + Close the request results help window + + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/doc/rest-nvim-config.txt b/doc/rest-nvim-config.txt index 3880ce8e..bb2fb871 100644 --- a/doc/rest-nvim-config.txt +++ b/doc/rest-nvim-config.txt @@ -38,7 +38,7 @@ RestConfigResultBehavior *RestConfigResultBehavior* Fields: ~ {show_info} (RestConfigResultInfo) Request results information {statistics} (RestConfigResultStats) Request results statistics - {formatters} (RestConfigResultFormatters) Formatters for the request results body + {formatters} (RestConfigResultFormatters) Formatters for the request results body. If the formatter is a function it should return two values, the formatted body and a table containing two values `found` (whether the formatter has been found or not) and `name` (the formatter name) RestConfigResultInfo *RestConfigResultInfo* @@ -60,8 +60,8 @@ RestConfigResultStats *RestConfigResultStats* RestConfigResultFormatters *RestConfigResultFormatters* Fields: ~ - {json} (string|fun(body:string):string) JSON formatter - {html} (string|fun(body:string):string) HTML formatter + {json} (string|fun(body:string):string,table) JSON formatter + {html} (string|fun(body:string):string,table) HTML formatter RestConfigHighlight *RestConfigHighlight* @@ -76,6 +76,8 @@ RestConfig *RestConfig* Fields: ~ {client} (string) The HTTP client to be used when running requests, default is `"curl"` {env_file} (string) Environment variables file to be used for the request variables in the document + {env_pattern} (string) Environment variables file pattern for telescope.nvim + {env_edit_command} (string) Neovim command to edit an environment file, default is `"tabedit"` {encode_url} (boolean) Encode URL before making request {skip_ssl_verification} (boolean) Skip SSL verification, useful for unknown certificates {custom_dynamic_variables} () diff --git a/doc/tags b/doc/tags index a73cec35..29dd0659 100644 --- a/doc/tags +++ b/doc/tags @@ -17,7 +17,10 @@ RestConfigResultSplit rest-nvim-config.txt /*RestConfigResultSplit* RestConfigResultStats rest-nvim-config.txt /*RestConfigResultStats* ResultPane rest-nvim-api.txt /*ResultPane* Variables rest-nvim-parser.txt /*Variables* +api.VERSION rest-nvim-api.txt /*api.VERSION* +api.namespace rest-nvim-api.txt /*api.namespace* api.register_rest_autocmd rest-nvim-api.txt /*api.register_rest_autocmd* +api.register_rest_keybind rest-nvim-api.txt /*api.register_rest_keybind* api.register_rest_subcommand rest-nvim-api.txt /*api.register_rest_subcommand* client.request rest-nvim-curl.txt /*client.request* config.set rest-nvim-config.txt /*config.set* @@ -29,6 +32,8 @@ functions.cycle_result_pane rest-nvim-api.txt /*functions.cycle_result_pane* functions.env rest-nvim-api.txt /*functions.env* functions.exec rest-nvim-api.txt /*functions.exec* functions.find_env_files rest-nvim-api.txt /*functions.find_env_files* +help.close rest-nvim-api.txt /*help.close* +help.open rest-nvim-api.txt /*help.open* logger:debug rest-nvim-api.txt /*logger:debug* logger:error rest-nvim-api.txt /*logger:error* logger:info rest-nvim-api.txt /*logger:info* @@ -70,6 +75,7 @@ rest-nvim.parser.dynamic_vars rest-nvim-parser.txt /*rest-nvim.parser.dynamic_va rest-nvim.parser.env_vars rest-nvim-parser.txt /*rest-nvim.parser.env_vars* rest-nvim.parser.script_vars rest-nvim-parser.txt /*rest-nvim.parser.script_vars* rest-nvim.result rest-nvim-api.txt /*rest-nvim.result* +rest-nvim.result.help rest-nvim-api.txt /*rest-nvim.result.help* rest-nvim.result.winbar rest-nvim-api.txt /*rest-nvim.result.winbar* rest-nvim.txt rest-nvim.txt /*rest-nvim.txt* rest-nvim.utils rest-nvim-api.txt /*rest-nvim.utils* @@ -80,6 +86,7 @@ result.write_block rest-nvim-api.txt /*result.write_block* result.write_res rest-nvim-api.txt /*result.write_res* script_vars.load rest-nvim-parser.txt /*script_vars.load* utils.file_exists rest-nvim-api.txt /*utils.file_exists* +utils.highlight rest-nvim-api.txt /*utils.highlight* utils.read_file rest-nvim-api.txt /*utils.read_file* winbar.current_pane_index rest-nvim-api.txt /*winbar.current_pane_index* winbar.get_content rest-nvim-api.txt /*winbar.get_content* From 3288d414a6f1107556b0752ec21085a474a77866 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 01:04:46 -0400 Subject: [PATCH 134/159] feat(Makefile): add `docgen` target, QoL improvements --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile b/Makefile index 4fd4be3b..56b63322 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,15 @@ +.PHONY: lint format docgen +.SILENT: docgen + lint: luacheck . format: stylua . +docgen: + lemmy-help lua/rest-nvim/client/curl.lua > doc/rest-nvim-curl.txt + lemmy-help lua/rest-nvim/commands.lua > doc/rest-nvim-commands.txt + lemmy-help lua/rest-nvim/config/init.lua > doc/rest-nvim-config.txt + lemmy-help lua/rest-nvim/parser/dynamic_vars.lua lua/rest-nvim/parser/env_vars.lua lua/rest-nvim/parser/script_vars.lua lua/rest-nvim/parser/init.lua > doc/rest-nvim-parser.txt + lemmy-help lua/rest-nvim/api.lua lua/rest-nvim/utils.lua lua/rest-nvim/functions.lua lua/rest-nvim/logger.lua lua/rest-nvim/result/init.lua lua/rest-nvim/result/winbar.lua lua/rest-nvim/result/help.lua > doc/rest-nvim-api.txt From e0a60198dd1dd2cc8afc27494a4f8bfc869b2a38 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 01:05:06 -0400 Subject: [PATCH 135/159] feat(ci): add a `docgen` workflow --- .github/workflows/docgen.yml | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/docgen.yml diff --git a/.github/workflows/docgen.yml b/.github/workflows/docgen.yml new file mode 100644 index 00000000..1c3095e6 --- /dev/null +++ b/.github/workflows/docgen.yml @@ -0,0 +1,38 @@ +name: format + +on: + push: + branches: [main] + paths-ignore: + - ".github/**" + - "**.md" + +permissions: + contents: write + pull-requests: read + +jobs: + docgen: + runs-on: ubuntu-latest + name: Generate documentation + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up lemmy-help + uses: supplypike/setup-bin@v3 + with: + uri: "https://github.com/numToStr/lemmy-help/releases/download/v0.11.0/lemmy-help-x86_64-unknown-linux-gnu.tar.gz" + name: lemmy-help + version: "0.11.0" + - name: Generate docs + run: "make docgen" + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: regenerate documentation" + branch: ${{ github.ref }} + - name: Push formatted files + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} From 75e18d96fafaf54a87a09e98fbcb12c96d45aa5c Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 01:06:10 -0400 Subject: [PATCH 136/159] docs: re-add `ft` to `lazy.nvim` config example --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 46a69c71..8ddcbb52 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ use { }, { "rest-nvim/rest.nvim", + ft = "http", dependencies = { "luarocks.nvim" }, config = function() require("rest-nvim").setup() From 33576051ce6aec39ed14f81747dfa5bafd8cf1b2 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 01:25:37 -0400 Subject: [PATCH 137/159] docs(README): update project features --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8ddcbb52..5d81386d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ CLI. For more information on this, please see this [blog post](https://amartin.c - Easy to use - Friendly and organized request results window - Fast runtime with statistics about your request +- Easily set environment variables based on the response to re-use the data later - Tree-sitter based parsing and syntax highlighting for speed and perfect accuracy - Possibility of using dynamic/environment variables and Lua scripting in HTTP files From f40db49c05d008edb3099e8e5c2089a006defda3 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 01:55:08 -0400 Subject: [PATCH 138/159] docs(README): improve usage notes --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d81386d..715948c2 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,12 @@ request and run the :Rest run command. > [!NOTE] > -> You can find examples of use in the [tests](./tests) directory. +> 1. You can find examples of use in the [tests](./tests) directory. +> +> 2. `rest.nvim` supports multiple HTTP requests in one file. It selects the +> request in the current cursor line, no matters the position as long as +> the cursor is on a request tree-sitter node. + --- From a127173020a53557be0df42dbbd2820959b500c5 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 02:01:36 -0400 Subject: [PATCH 139/159] feat(doc): update `:h rest-nvim` documentation --- doc/rest-nvim.txt | 289 +++++++++++++++++++++++++++------------------- doc/tags | 3 +- 2 files changed, 173 insertions(+), 119 deletions(-) diff --git a/doc/rest-nvim.txt b/doc/rest-nvim.txt index 204c3dca..41fa3a20 100644 --- a/doc/rest-nvim.txt +++ b/doc/rest-nvim.txt @@ -1,11 +1,4 @@ -*rest-nvim.txt* A fast Neovim http client written in Lua based on curl - - ______ _ ~ - (_____ \ _ (_) ~ - _____) )_____ ___ _| |_ ____ _ _ _ ____ ~ - | __ /| ___ |/___|_ _) | _ \ | | | | \ ~ - | | \ \| ____|___ | | |_ _| | | \ V /| | | | | ~ - |_| |_|_____|___/ \__|_)_| |_|\_/ |_|_|_|_| ~ +*rest-nvim.txt* A very fast, powerful, extensible and asynchronous Neovim HTTP client. NTBBloodbath *rest-nvim* @@ -21,8 +14,8 @@ CONTENTS *rest-nvim-contents* 3. Import body from external file......|rest-nvim-usage-external-files| 4. Environment Variables........|rest-nvim-usage-environment-variables| 5. Dynamic Variables................|rest-nvim-usage-dynamic-variables| + 6. Script Variables..................|rest-nvim-usage-script-variables| 6. Callbacks................................|rest-nvim-usage-callbacks| - 5. Known issues..........................................|rest-nvim-issues| 6. License..............................................|rest-nvim-license| 7. Contributing....................................|rest-nvim-contributing| @@ -30,57 +23,116 @@ CONTENTS *rest-nvim-contents* =============================================================================== INTRODUCTION *rest-nvim-intro* -`rest.nvim` is a fast Neovim http client written in Lua which makes use of a -curl wrapper made in pure Lua by github.com/tami5 and implemented in the -plugin `plenary.nvim` so, in other words, `rest.nvim` is a curl wrapper so you -don't have to leave Neovim! - +`rest.nvim` by default makes use of native `cURL` bindings. In this way, you +get absolutely all the power that cURL provides from the comfort of our editor +just by using a keybind and without wasting the precious resources of your +machine. =============================================================================== FEATURES *rest-nvim-features* - Easy to use -- Fast execution time -- Run request under cursor -- Syntax highlight for http files and output -- Possibility of using environment variables in http files -- Set environment variables based on the response +- Friendly and organized request results window +- Fast runtime with statistics about your request +- Easily set environment variables based on the response to re-use the data later +- Tree-sitter based parsing and syntax highlighting for speed and perfect accuracy +- Possibility of using dynamic/environment variables and Lua scripting in HTTP files =============================================================================== QUICK START *rest-nvim-quick-start* After installing `rest.nvim` you will need to configure it using a `setup` -function, it looks like this by default: >lua - - require("rest-nvim").setup({ - -- Open request results in a horizontal split - result_split_horizontal = false, - -- Keep the http file buffer above|left when split horizontal|vertical - result_split_in_place = false, - -- Skip SSL verification, useful for unknown certificates - skip_ssl_verification = false, - -- Highlight request on run - highlight = { - enabled = true, - timeout = 150, +function, it looks like this by default: +>lua + require("rest-nvim").setup({ + client = "curl", + env_file = ".env", + env_pattern = "\\.env$", + env_edit_command = "tabedit", + encode_url = true, + skip_ssl_verification = false, + custom_dynamic_variables = {}, + logs = { + level = "info", + save = true, + }, + result = { + split = { + horizontal = false, + in_place = false, + stay_in_current_window_after_split = true, }, - -- Jump to request line on run - jump_to_request = false, - env_file = '.env', - yank_dry_run = true, - }) + behavior = { + show_info = { + url = true, + headers = true, + http_info = true, + curl_command = true, + }, + statistics = { + enable = true, + ---@see https://curl.se/libcurl/c/curl_easy_getinfo.html + stats = { + { "total_time", title = "Time taken:" }, + { "size_download_t", title = "Download size:" }, + }, + }, + formatters = { + json = "jq", + html = function(body) + if vim.fn.executable("tidy") == 0 then + return body, { found = false, name = "tidy" } + end + local fmt_body = vim.fn.system({ + "tidy", + "-i", + "-q", + "--tidy-mark", "no", + "--show-body-only", "auto", + "--show-errors", "0", + "--show-warnings", "0", + "-", + }, body):gsub("\n$", "") + + return fmt_body, { found = true, name = "tidy" } + end, + }, + }, + }, + highlight = { + enable = true, + timeout = 750, + }, + ---Example: + --- + ---```lua + ---keybinds = { + --- { + --- "rr", ":Rest run", "Run request under the cursor", + --- }, + --- { + --- "rl", ":Rest run last", "Re-run latest request", + --- }, + ---} + --- + ---``` + ---@see vim.keymap.set + keybinds = {}, + }) +< +Please refer to |rest-nvim.config| for more information and documentation. In this section we will be using `https://reqres.in/` for requests. Let's say we want to create a new user and send our body as a JSON, so we will do the following: - 1. We declare the HTTP method to use followed by the URL. >lua + 1. We declare the HTTP method to use followed by the URL. >http POST https://reqres.in/api/users < 2. Since we want to send our body as a JSON object, we set the - Content-Type header. >http + Content-Type header. > Content-Type: application/json < 3. Now, we set the body of our request. >json @@ -89,43 +141,57 @@ will do the following: "job": "leader" } < - 4. Finally, we place the cursor over or below the method of our request - and call `rest.nvim` with `:lua require('rest-nvim').run()`. - -Since the way to call rest.nvim with Lua is not comfortable, rest.nvim -exposes a command to be mapped. See |rest-nvim-usage-commands| + 4. Finally, we place the cursor over or below the method of our request + and call `rest.nvim` with the following command: >vim + :Rest run +< +To get a better understanding of the `:Rest` command, please see |rest-nvim.commands| =============================================================================== USAGE *rest-nvim-usage* -Create a new http file or open an existing one and place the cursor over the -request line (e.g. `GET http://localhost:3000/foo`) or below and run `rest.nvim` -(see |rest-nvim-usage-commands|). +Create a new HTTP file or open an existing one and place the cursor over the +request and run the `:Rest run` command (see |rest-nvim.commands|). Notes: - - `rest.nvim` follows the RFC 2616 request format so any other http file - should work without problems. - - `rest.nvim` supports multiple http requests in one file. It selects the - nearest request in or above the current cursor line. + - You can find examples of use in the `tests` directory in the GitHub + repository. + - `rest.nvim` supports multiple HTTP requests in one file. It selects the + request in the current cursor line, no matters the position as long as + the cursor is on a request tree-sitter node. =============================================================================== COMMANDS *rest-nvim-usage-commands* -- `RestNvim` - Run `rest.nvim` in the current cursor position. +`rest.nvim` exposes a `:Rest` command that is available only in HTTP buffers, +this command has very useful subcommands for you (see |rest-nvim.commands|). +Some of these commands are the following: + +- `:Rest run` + Execute one or several HTTP requests depending on the given `scope`. + Defaults to the request under the cursor (`:Rest run cursor`). + +- `:Rest last` + Re-run the last executed request, alias to `:Rest run last`. + +- `:Rest env` + Manage the environment file that is currently in use while running requests. + +If you have used `rest.nvim` before v2 (aka `Thunder Rest`), you will know that +before we used `` commands that had to be used in keybinds. This has +changed, and in order not to completely break your workflow, these +commands (`RestNvim` and `RestNvimLast`) will still work until +the next version, but it is highly recommended to update your setup. + +Unfortunately, the following command has had to be removed as it cannot be +re-implemented with the `rest.nvim` v2 architecture and workflow: - `RestNvimPreview` Same as `RestNvim` but it returns the cURL command without executing the request. Intended for debugging purposes. -- `:RestLog` - Shows `rest.nvim` logs (export DEBUG_PLENARY=debug for more logs). - -- `:RestSelectEnv path/to/env` - Set the path to an env file. - =============================================================================== REQUESTS *rest-nvim-usage-requests* @@ -144,8 +210,12 @@ IMPORT BODY FROM EXTERNAL FILE *rest-nvim-usage-external-files* `rest.nvim` allows the http file to import the body from an external file. -The syntax is `< path/to/file.json`. `rest.nvim` supports absolute and relative -paths to the external file. +The syntax is to append an external body from a file is `< path/to/file.json`. + +You can also choose a name for the file sent to the backend using +the syntax `<@user.json path/to/file.json`. + +`rest.nvim` supports absolute and relative paths to the external file. =============================================================================== @@ -153,62 +223,66 @@ ENVIRONMENT VARIABLES *rest-nvim-usage-environment-variables* `rest.nvim` allows the use of environment variables in requests. -To use environment variables, the following syntax is used: `{{VARIABLE_NAME}}` +To use environment variables, the following syntax is used: `{{VARIABLE_NAME}}`. These environment variables can be obtained from: - - File in the current working directory (env_file in config or '.env') - - System + - Your current Neovim session (`vim.env`) + - Your system shell environment. + - File in the current working directory (`env_file` in config, `.env` by default). -Environment variables can be set in .env format or in json. +Environment variables can be set in `.env` format or in `json`. -To change the environment for the session use :RestSelectEnv path/to/environment +To change the environment for the session use `:Rest env set path/to/environment` -Environment variables can be set dynamically from the response body. -(see rest-nvim-usage-dynamic-variables) +Environment variables can be set dynamically from the response body. +(see |rest-nvim-usage-script-variables|) =============================================================================== -RESPONSE SCRIPT *rest-nvim-response-script* +SCRIPT VARIABLES *rest-nvim-script-variables* -A lua script can be run after a request has completed. This script must below -the body and wrapped in {% script %}. A context table is avaliable in the +A Lua script can be run after a request has completed. This script must below +the body and wrapped in `--{% script --%}`. A context table is avaliable in the response script. The context table can be used to read the response and set -environment variables. +environment variables. -The context table: >lua +The context table: +>lua { result = res, - pretty_print = vim.pretty_print, - json_decode = vim.fn.json_decode, + pretty_print = vim.print, + json_decode = vim.json.decode, set_env = utils.set_env, } < Now environment variables can be set like so: -> +>http GET https://jsonplaceholder.typicode.com/posts/3 - - {% - + + --{% + local body = context.json_decode(context.result.body) context.set_env("postId", body.id) - - %} + + --%} < + + =============================================================================== DYNAMIC VARIABLES *rest-nvim-usage-dynamic-variables* `rest.nvim` allows the use of dynamic variables in requests. The following dynamic variables are currently supported: - - $uuid: generates a universally unique identifier (UUID-v4) - - $timestamp: generates the current UNIX timestamp (seconds since epoch) - - $randomInt: generates a random integer between 0 and 1000 + - `$uuid`: generates a universally unique identifier (UUID-v4) + - `$timestamp`: generates the current UNIX timestamp (seconds since epoch) + - `$randomInt`: generates a random integer between 0 and 1000 -To use dynamic variables, the following syntax is used: `{{DYNAMIC_VARIABLE}}`, +To use dynamic variables, the following syntax is used: `{{$DYNAMIC_VARIABLE}}`, e.g. `{{$uuid}}` -You can extend or overwrite built-in dynamic variables, with the config key >lua - +You can extend or overwrite built-in dynamic variables, with the config key +>lua -- custom_dynamic_variables: require("rest-nvim").setup({ custom_dynamic_variables = { @@ -223,53 +297,33 @@ You can extend or overwrite built-in dynamic variables, with the config key >lua end, }, }) +< + =============================================================================== CALLBACKS *rest-nvim-usage-callbacks* -rest.nvim fires different events upon requests: - - a User RestStartRequest event when launching the request - - a User RestStopRequest event when the requests finishes or errors out >lua +`rest.nvim` fires different events upon requests: + - a User `RestStartRequest` event when launching the request + - a User `RestStopRequest` event when the requests finishes or errors out +>lua vim.api.nvim_create_autocmd("User", { pattern = "RestStartRequest", once = true, callback = function(opts) - print("IT STARTED") - vim.pretty_print(opts) + print("IT STARTED") + vim.pretty_print(opts) end, }) < -=============================================================================== -KNOWN ISSUES *rest-nvim-issues* - - - Nothing here at the moment :) =============================================================================== LICENSE *rest-nvim-license* -rest.nvim is distributed under MIT License. - -Copyright (c) 2021 NTBBloodbath - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +`rest.nvim` is distributed under GPLv3 License, please read the LICENSE file +in the GitHub repository (`rest-nvim/rest.nvim`). =============================================================================== @@ -281,4 +335,5 @@ CONTRIBUTING *rest-nvim-contributing* 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/doc/tags b/doc/tags index 29dd0659..9330b08a 100644 --- a/doc/tags +++ b/doc/tags @@ -53,10 +53,9 @@ rest-nvim-contents rest-nvim.txt /*rest-nvim-contents* rest-nvim-contributing rest-nvim.txt /*rest-nvim-contributing* rest-nvim-features rest-nvim.txt /*rest-nvim-features* rest-nvim-intro rest-nvim.txt /*rest-nvim-intro* -rest-nvim-issues rest-nvim.txt /*rest-nvim-issues* rest-nvim-license rest-nvim.txt /*rest-nvim-license* rest-nvim-quick-start rest-nvim.txt /*rest-nvim-quick-start* -rest-nvim-response-script rest-nvim.txt /*rest-nvim-response-script* +rest-nvim-script-variables rest-nvim.txt /*rest-nvim-script-variables* rest-nvim-usage rest-nvim.txt /*rest-nvim-usage* rest-nvim-usage-callbacks rest-nvim.txt /*rest-nvim-usage-callbacks* rest-nvim-usage-commands rest-nvim.txt /*rest-nvim-usage-commands* From 3492f1a83ca71a5fa8406e72dccb5549f861f237 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 02:04:20 -0400 Subject: [PATCH 140/159] fix(doc): fix indentation in `:h rest-nvim` callbacks section --- doc/rest-nvim.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/rest-nvim.txt b/doc/rest-nvim.txt index 41fa3a20..35e111ed 100644 --- a/doc/rest-nvim.txt +++ b/doc/rest-nvim.txt @@ -308,14 +308,14 @@ CALLBACKS *rest-nvim-usage-callbacks - a User `RestStopRequest` event when the requests finishes or errors out >lua - vim.api.nvim_create_autocmd("User", { + vim.api.nvim_create_autocmd("User", { pattern = "RestStartRequest", once = true, - callback = function(opts) - print("IT STARTED") - vim.pretty_print(opts) - end, - }) + callback = function(opts) + print("IT STARTED") + vim.pretty_print(opts) + end, + }) < From c2011fb4a7b8dfa6f70e263951b78c631a87c386 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Wed, 6 Mar 2024 04:07:37 -0400 Subject: [PATCH 141/159] feat: add `build.lua` to be able to install `rest.nvim` by using lazy --- build.lua | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 build.lua diff --git a/build.lua b/build.lua new file mode 100644 index 00000000..c7c80ed0 --- /dev/null +++ b/build.lua @@ -0,0 +1,20 @@ +-- This build.lua exists to bridge luarocks installation for lazy.nvim users. +-- It's main purposes are: +-- - Shelling out to luarocks.nvim for installation +-- - Installing rest's dependencies as rocks +-- +-- Important note: we execute the build code in a vim.schedule +-- to defer the execution and ensure that the runtimepath is appropriately set. + +vim.schedule(function() + local ok, luarocks = pcall(require, "luarocks.rocks") + + assert(ok, "Unable to install rest.nvim: required dependency `vhyrro/luarocks.nvim` not found!") + + luarocks.ensure({ + "nvim-nio ~> 1.7", + "lua-curl ~> 0.3", + "mimetypes ~> 1.0", + "xml2lua ~> 1.5", + }) +end) From ab72d0c5055f901f3f6fbb1177e769e2c33b21f5 Mon Sep 17 00:00:00 2001 From: patricklamar Date: Sat, 9 Mar 2024 16:06:16 +0800 Subject: [PATCH 142/159] fix: ignore error log and adding syntax highlights for result when can not get the res_type --- lua/rest-nvim/result/init.lua | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lua/rest-nvim/result/init.lua b/lua/rest-nvim/result/init.lua index 6898b2fd..9b401121 100644 --- a/lua/rest-nvim/result/init.lua +++ b/lua/rest-nvim/result/init.lua @@ -229,7 +229,7 @@ local function format_body(bufnr, headers, res) logger:error("Error running formatter '" .. fmt .. "' on response body:\n" .. stdout) end end - else + elseif res_type ~= nil then ---@diagnostic disable-next-line need-check-nil logger:info( "Could not find a formatter for the body type " @@ -237,6 +237,7 @@ local function format_body(bufnr, headers, res) .. " returned in the request, the results will not be formatted" ) end + body = vim.split(res.body, "\n") table.insert(body, 1, res.method .. " " .. res.url) table.insert(body, 2, headers[1]) -- HTTP/X and status code + meaning @@ -249,11 +250,12 @@ local function format_body(bufnr, headers, res) table.remove(winbar.pane_map[2].contents, 1) -- add syntax highlights for response - vim.api.nvim_buf_call(bufnr, function() - local syntax_file = vim.fn.expand(string.format("$VIMRUNTIME/syntax/%s.vim", res_type)) - if vim.fn.filereadable(syntax_file) == 1 then - vim.cmd(string.gsub( - [[ + if res_type ~= nil then + vim.api.nvim_buf_call(bufnr, function() + local syntax_file = vim.fn.expand(string.format("$VIMRUNTIME/syntax/%s.vim", res_type)) + if vim.fn.filereadable(syntax_file) == 1 then + vim.cmd(string.gsub( + [[ if exists("b:current_syntax") unlet b:current_syntax endif @@ -262,11 +264,12 @@ local function format_body(bufnr, headers, res) let b:current_syntax = "httpResult" ]], - "%%s", - res_type - )) - end - end) + "%%s", + res_type + )) + end + end) + end end ---@diagnostic disable-next-line inject-field winbar.pane_map[1].contents = body From ffa3ce1e2a0cc599fd0931403d90d135b0082e77 Mon Sep 17 00:00:00 2001 From: Zie Mcdowell Date: Mon, 11 Mar 2024 21:15:37 +0000 Subject: [PATCH 143/159] docs: added doc for telescope ext (#296) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 715948c2..882f9fc0 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,9 @@ require("telescope").load_extension("rest") -- then use it, you can also use the `:Telescope rest select_env` command require("telescope").extensions.rest.select_env() ``` + +If running Ubuntu or Debian based systems you might need to run `ln -s $(which fdfind) ~/.local/bin/fd` to get extension to work. This is becuase extension runs the [fd](https://github.com/sharkdp/fd?tab=readme-ov-file#installation) command. + Here is a preview of the extension working :) ![telescope rest extension demo](https://github.com/rest-nvim/rest.nvim/assets/36456999/a810954f-b45c-44ee-854d-94039de8e2fc) From 4a508d65ccf2effe1365d9c8bb63296e2e50fdb8 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sat, 16 Mar 2024 14:39:41 -0400 Subject: [PATCH 144/159] ref(winbar): use more common highlighting groups, fixes #290 --- lua/rest-nvim/result/winbar.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/rest-nvim/result/winbar.lua b/lua/rest-nvim/result/winbar.lua index a2be8439..30464712 100644 --- a/lua/rest-nvim/result/winbar.lua +++ b/lua/rest-nvim/result/winbar.lua @@ -72,7 +72,7 @@ end ---Set the results window winbar highlighting groups function winbar.set_hl() -- Set highlighting for the winbar panes name - local textinfo_fg = get_hl_group_fg("TextInfo") + local textinfo_fg = get_hl_group_fg("Statement") for i, pane in ipairs(winbar.pane_map) do ---@diagnostic disable-next-line undefined-field vim.api.nvim_set_hl(0, pane.name .. "Highlight", { @@ -83,7 +83,7 @@ function winbar.set_hl() end -- Set highlighting for the winbar text - local textmuted_fg = get_hl_group_fg("TextMuted") + local textmuted_fg = get_hl_group_fg("Comment") vim.api.nvim_set_hl(0, "RestText", { fg = textmuted_fg }) end From 2e67ee17744d06e2de136d2fba655d80b143f14a Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sat, 16 Mar 2024 16:25:20 -0400 Subject: [PATCH 145/159] docs(README): add links for Matrix and Discord, small adjustments --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 882f9fc0..2da55eea 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ ![License](https://img.shields.io/github/license/NTBBloodbath/rest.nvim?style=for-the-badge) ![Neovim version](https://img.shields.io/badge/Neovim-0.9.2-5ba246?style=for-the-badge&logo=neovim) [![LuaRocks](https://img.shields.io/luarocks/v/teto/rest.nvim?style=for-the-badge&logo=lua&color=blue)](https://luarocks.org/modules/teto/rest.nvim) -![Matrix](https://img.shields.io/matrix/rest.nvim%3Amatrix.org?server_fqdn=matrix.org&style=for-the-badge&logo=element&label=Matrix&color=55b394&link=https%3A%2F%2Fmatrix.to%2F%23%2F%23rest.nvim%3Amatrix.org) +[![Discord](https://img.shields.io/badge/discord-join-7289da?style=for-the-badge&logo=discord)](https://discord.gg/AcXkuXKj7C) +[![Matrix](https://img.shields.io/matrix/rest.nvim%3Amatrix.org?server_fqdn=matrix.org&style=for-the-badge&logo=element&label=Matrix&color=55b394&link=https%3A%2F%2Fmatrix.to%2F%23%2F%23rest.nvim%3Amatrix.org)](https://matrix.to/#/#rest.nvim:matrix.org) [Features](#features) • [Install](#install) • [Usage](#usage) • [Contribute](#contribute) @@ -75,7 +76,6 @@ use { ```lua { "vhyrro/luarocks.nvim", - branch = "more-fixes", config = function() require("luarocks").setup({}) end, @@ -90,6 +90,11 @@ use { } ``` +> [!NOTE] +> +> There's a `build.lua` file in the repository that `lazy.nvim` will find and source to install the +> luarocks dependencies for you by using `luarocks.nvim`. + ### Default configuration This is the default configuration of `rest.nvim`, it is fully documented and typed internally so you From 16284ba6c127c4bfa0468b3e9406595d93fe1a48 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 04:48:24 -0400 Subject: [PATCH 146/159] feat(curl): encode URL query parameters using cURL flags --- lua/rest-nvim/client/curl.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 7d06359c..23790f1c 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -197,6 +197,18 @@ function client.request(request) ssl_verifypeer = skip_ssl_verification, }) + -- Encode URL query parameters and set the request URL again with the encoded values + local should_encode_url = _G._rest_nvim.encode_url + if should_encode_url then + -- Create a new URL as we cannot extract the URL from the req object + local _url = curl.url() + _url:set_url(request.request.url) + -- Re-add the request query with the encoded parameters + _url:set_query(_url:get_query(), curl.U_URLENCODE) + -- Re-add the request URL to the req object + req:setopt_url(_url:get_url()) + end + -- Set request HTTP version, defaults to HTTP/1.1 if request.request.http_version then local http_version = request.request.http_version:gsub("%.", "_") @@ -269,7 +281,14 @@ function client.request(request) ret.statistics = get_stats(req, stats_config.stats) end - ret.url = req:getinfo_effective_url() + -- Returns the decoded URL if the request URL was encoded by cURL to improve the results + -- buffer output readability. + -- NOTE: perhaps make this configurable in case someone wants to see the encoded URL instead? + if should_encode_url then + ret.url = request.request.url + else + ret.url = req:getinfo_effective_url() + end ret.code = req:getinfo_response_code() ret.method = req:getinfo_effective_method() ret.headers = table.concat(res_headers):gsub("\r", "") From c3dca4ac73269f5bf8b8be7a424b8eea640da159 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 04:50:13 -0400 Subject: [PATCH 147/159] feat(utils): expose a `escape` function to encode strings, meant to be used by extensions to encode URLs in case their clients does not provide an encode utility --- lua/rest-nvim/utils.lua | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lua/rest-nvim/utils.lua b/lua/rest-nvim/utils.lua index 70e50d1f..ad3ddd36 100644 --- a/lua/rest-nvim/utils.lua +++ b/lua/rest-nvim/utils.lua @@ -11,12 +11,26 @@ local utils = {} -- NOTE: vim.loop has been renamed to vim.uv in Neovim >= 0.10 and will be removed later local uv = vim.uv or vim.loop +---Encodes a string into its escaped hexadecimal representation +---taken from Lua Socket and added underscore to ignore +---@param str string Binary string to be encoded +---@return string +function utils.escape(str) + local encoded = string.gsub(str, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end) + + return encoded +end + ---Check if a file exists in the given `path` ---@param path string file path ---@return boolean function utils.file_exists(path) + ---@diagnostic disable-next-line undefined-field local fd = uv.fs_open(path, "r", 438) if fd then + ---@diagnostic disable-next-line undefined-field uv.fs_close(fd) return true end @@ -30,15 +44,16 @@ end function utils.read_file(path) local logger = _G._rest_nvim.logger - ---@type string|uv_fs_t|nil + ---@type string|nil local content if utils.file_exists(path) then + ---@diagnostic disable-next-line undefined-field local file = uv.fs_open(path, "r", 438) - ---@cast file number + ---@diagnostic disable-next-line undefined-field local stat = uv.fs_fstat(file) - ---@cast stat uv.aliases.fs_stat_table + ---@diagnostic disable-next-line undefined-field content = uv.fs_read(file, stat.size, 0) - ---@cast content string + ---@diagnostic disable-next-line undefined-field uv.fs_close(file) else ---@diagnostic disable-next-line need-check-nil From ce7fa41b80752c772a9f70056cd687c46f8ab972 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 05:00:25 -0400 Subject: [PATCH 148/159] chore: regenerate documentation --- doc/rest-nvim-api.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/rest-nvim-api.txt b/doc/rest-nvim-api.txt index 33d84781..d709b031 100644 --- a/doc/rest-nvim-api.txt +++ b/doc/rest-nvim-api.txt @@ -63,6 +63,17 @@ rest.nvim utilities *rest-nvim.utils* rest.nvim utility functions +utils.escape({str}) *utils.escape* + Encodes a string into its escaped hexadecimal representation + taken from Lua Socket and added underscore to ignore + + Parameters: ~ + {str} (string) Binary string to be encoded + + Returns: ~ + (string) + + utils.file_exists({path}) *utils.file_exists* Check if a file exists in the given `path` From 070660bfe00d06d4ba01434be12e666860175985 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 05:01:46 -0400 Subject: [PATCH 149/159] feat(config): add `decode_url` configuration option to the `result.behavior` table --- README.md | 1 + doc/rest-nvim-config.txt | 1 + lua/rest-nvim/client/curl.lua | 5 ++--- lua/rest-nvim/config/check.lua | 1 + lua/rest-nvim/config/init.lua | 2 ++ 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2da55eea..736b486e 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ local default_config = { stay_in_current_window_after_split = true, }, behavior = { + decode_url = true, show_info = { url = true, headers = true, diff --git a/doc/rest-nvim-config.txt b/doc/rest-nvim-config.txt index bb2fb871..b529557d 100644 --- a/doc/rest-nvim-config.txt +++ b/doc/rest-nvim-config.txt @@ -37,6 +37,7 @@ RestConfigResultBehavior *RestConfigResultBehavior* Fields: ~ {show_info} (RestConfigResultInfo) Request results information + {decode_url} (boolean) Whether to decode the request URL query parameters to improve readability {statistics} (RestConfigResultStats) Request results statistics {formatters} (RestConfigResultFormatters) Formatters for the request results body. If the formatter is a function it should return two values, the formatted body and a table containing two values `found` (whether the formatter has been found or not) and `name` (the formatter name) diff --git a/lua/rest-nvim/client/curl.lua b/lua/rest-nvim/client/curl.lua index 23790f1c..c48f6bd8 100644 --- a/lua/rest-nvim/client/curl.lua +++ b/lua/rest-nvim/client/curl.lua @@ -282,9 +282,8 @@ function client.request(request) end -- Returns the decoded URL if the request URL was encoded by cURL to improve the results - -- buffer output readability. - -- NOTE: perhaps make this configurable in case someone wants to see the encoded URL instead? - if should_encode_url then + -- buffer output readability + if should_encode_url and _G._rest_nvim.result.behavior.decode_url then ret.url = request.request.url else ret.url = req:getinfo_effective_url() diff --git a/lua/rest-nvim/config/check.lua b/lua/rest-nvim/config/check.lua index 60532b19..4fedae1d 100644 --- a/lua/rest-nvim/config/check.lua +++ b/lua/rest-nvim/config/check.lua @@ -43,6 +43,7 @@ function check.validate(cfg) stay_in_current_window_after_split = { cfg.result.split.stay_in_current_window_after_split, "boolean" }, -- RestConfigResultBehavior behavior = { cfg.result.behavior, "table" }, + decode_url = { cfg.result.behavior.decode_url, "boolean" }, -- RestConfigResultInfo show_info = { cfg.result.behavior.show_info, "table" }, url = { cfg.result.behavior.show_info.url, "boolean" }, diff --git a/lua/rest-nvim/config/init.lua b/lua/rest-nvim/config/init.lua index 769f9486..cc90eac1 100644 --- a/lua/rest-nvim/config/init.lua +++ b/lua/rest-nvim/config/init.lua @@ -28,6 +28,7 @@ local logger = require("rest-nvim.logger") ---@class RestConfigResultBehavior ---@field show_info RestConfigResultInfo Request results information +---@field decode_url boolean Whether to decode the request URL query parameters to improve readability ---@field statistics RestConfigResultStats Request results statistics ---@field formatters RestConfigResultFormatters Formatters for the request results body. If the formatter is a function it should return two values, the formatted body and a table containing two values `found` (whether the formatter has been found or not) and `name` (the formatter name) @@ -85,6 +86,7 @@ local default_config = { stay_in_current_window_after_split = true, }, behavior = { + decode_url = true, show_info = { url = true, headers = true, From ee3f047a34961b4fbf203559253578dadfc1a31b Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 06:17:06 -0400 Subject: [PATCH 150/159] feat(env_vars): add a `quiet` parameter to `read_file` to decide whether to fail silently if an environment file is not found, some cleanups --- lua/rest-nvim/parser/env_vars.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lua/rest-nvim/parser/env_vars.lua b/lua/rest-nvim/parser/env_vars.lua index 3417c566..5a0d2cac 100644 --- a/lua/rest-nvim/parser/env_vars.lua +++ b/lua/rest-nvim/parser/env_vars.lua @@ -10,9 +10,6 @@ local env_vars = {} local utils = require("rest-nvim.utils") --- NOTE: vim.loop has been renamed to vim.uv in Neovim >= 0.10 and will be removed later -local uv = vim.uv or vim.loop - ---Get the environment variables file filetype ---@param env_file string The environment file path ---@return string|nil @@ -31,8 +28,10 @@ end ---Read the environment variables file from the rest.nvim configuration ---and store all the environment variables in the `vim.env` metatable +---@param quiet boolean Whether to fail silently if an environment file is not found, defaults to `false` ---@see vim.env -function env_vars.read_file() +function env_vars.read_file(quiet) + quiet = quiet or false local path = _G._rest_nvim.env_file local logger = _G._rest_nvim.logger @@ -65,8 +64,10 @@ function env_vars.read_file() vim.env[k] = v end else - ---@diagnostic disable-next-line need-check-nil - logger:error("Current environment file '" .. path .. "' was not found in the current working directory") + if not quiet then + ---@diagnostic disable-next-line need-check-nil + logger:error("Current environment file '" .. path .. "' was not found in the current working directory") + end end end From b8cfe071ede11988d19c785d9cccc8bf721aab06 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 06:23:35 -0400 Subject: [PATCH 151/159] feat: re-implement pre and post request hooks, load env variables from environment file before running the requests --- lua/rest-nvim/api.lua | 24 ++++++++++++++++++++++++ lua/rest-nvim/functions.lua | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lua/rest-nvim/api.lua b/lua/rest-nvim/api.lua index c16702ec..5638d112 100644 --- a/lua/rest-nvim/api.lua +++ b/lua/rest-nvim/api.lua @@ -52,4 +52,28 @@ function api.register_rest_keybind(mode, lhs, cmd, opts) keybinds.register_keybind(mode, lhs, cmd, opts) end +---Execute all the pre-request hooks, functions that are meant to run before executing a request +--- +---This function is called automatically during the execution of the requests, invoking it again could cause inconveniences +---@see vim.api.nvim_exec_autocmds +---@package +function api.exec_pre_request_hooks() + vim.api.nvim_exec_autocmds("User", { + pattern = "RestStartRequest", + modeline = false, + }) +end + +---Execute all the post-request hooks, functions that are meant to run after executing a request +--- +---This function is called automatically during the execution of the requests, invoking it again could cause inconveniences +---@see vim.api.nvim_exec_autocmds +---@package +function api.exec_post_request_hooks() + vim.api.nvim_exec_autocmds("User", { + pattern = "RestStopRequest", + modeline = false, + }) +end + return api diff --git a/lua/rest-nvim/functions.lua b/lua/rest-nvim/functions.lua index b646e49f..fd111bb7 100644 --- a/lua/rest-nvim/functions.lua +++ b/lua/rest-nvim/functions.lua @@ -27,6 +27,8 @@ function functions.exec(scope) }) local api = require("rest-nvim.api") + local env_vars = require("rest-nvim.parser.env_vars") + local logger = _G._rest_nvim.logger local ok, client = pcall(require, "rest-nvim.client." .. _G._rest_nvim.client) if not ok then @@ -60,6 +62,17 @@ function functions.exec(scope) utils.highlight(0, req.start, req.end_, api.namespace) + -- Set up a _rest_nvim_req_data Lua global table that holds the parsed request + -- so the values can be modified from the pre-request hooks + _G._rest_nvim_req_data = req + -- Load environment variables from the env file + env_vars.read_file(true) + -- Run pre-request hooks + api.exec_pre_request_hooks() + -- Clean the _rest_nvim_req_data global after running the pre-request hooks + -- as the req table will remain modified + _G._rest_nvim_req_data = nil + if found_nio then req_results = nio .run(function() @@ -82,6 +95,17 @@ function functions.exec(scope) else utils.highlight(0, req.start, req.end_, api.namespace) + -- Set up a _rest_nvim_req_data Lua global table that holds the parsed request + -- so the values can be modified from the pre-request hooks + _G._rest_nvim_req_data = req + -- Load environment variables from the env file + env_vars.read_file(true) + -- Run pre-request hooks + api.exec_pre_request_hooks() + -- Clean the _rest_nvim_req_data global after running the pre-request hooks + -- as the req table will remain modified + _G._rest_nvim_req_data = nil + if found_nio then req_results = nio .run(function() @@ -94,8 +118,8 @@ function functions.exec(scope) end end + -- We should not be trying to show a result or evaluate code if the request failed if not vim.tbl_isempty(req_results) then - -- We should not be trying to show a result if the request failed local result_buf = result.get_or_create_buf() result.write_res(result_buf, req_results) @@ -103,6 +127,15 @@ function functions.exec(scope) if req_results.script ~= nil or not req_results.script == "" then script_vars.load(req_results.script, req_results) end + + -- Set up a _rest_nvim_res_data Lua global table that holds the request results + -- so the values can be modified from the post-request hooks + _G._rest_nvim_res_data = req_results + -- Run post-request hooks + api.exec_post_request_hooks() + -- Clean the _rest_nvim_res_data global after running the post-request hooks + -- as the req_results table will remain modified + _G._rest_nvim_res_data = nil end end From 24a3169ebdce556d699b7db380bc9c3439d89972 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 06:30:26 -0400 Subject: [PATCH 152/159] chore: regenerate documentation --- doc/rest-nvim.txt | 22 ++++++++++++++-------- doc/tags | 3 ++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/doc/rest-nvim.txt b/doc/rest-nvim.txt index 35e111ed..7dac3d46 100644 --- a/doc/rest-nvim.txt +++ b/doc/rest-nvim.txt @@ -15,7 +15,7 @@ CONTENTS *rest-nvim-contents* 4. Environment Variables........|rest-nvim-usage-environment-variables| 5. Dynamic Variables................|rest-nvim-usage-dynamic-variables| 6. Script Variables..................|rest-nvim-usage-script-variables| - 6. Callbacks................................|rest-nvim-usage-callbacks| + 7. Hooks........................................|rest-nvim-usage-hooks| 6. License..............................................|rest-nvim-license| 7. Contributing....................................|rest-nvim-contributing| @@ -64,6 +64,7 @@ function, it looks like this by default: stay_in_current_window_after_split = true, }, behavior = { + decode_url = true, show_info = { url = true, headers = true, @@ -301,19 +302,24 @@ You can extend or overwrite built-in dynamic variables, with the config key =============================================================================== -CALLBACKS *rest-nvim-usage-callbacks* +HOOKS *rest-nvim-usage-hooks* `rest.nvim` fires different events upon requests: - - a User `RestStartRequest` event when launching the request - - a User `RestStopRequest` event when the requests finishes or errors out + - a User `RestStartRequest` event when launching the request. + - a User `RestStopRequest` event when the requests finishes. >lua vim.api.nvim_create_autocmd("User", { pattern = "RestStartRequest", - once = true, - callback = function(opts) - print("IT STARTED") - vim.pretty_print(opts) + once = true, -- This is optional, only if you want the hook to run once + callback = function() + print("Started request") + -- You can access and modify the request data (body, headers, etc) by + -- using the following temporal global variable + vim.print(_G._rest_nvim_req_data) + -- You can also access environment variables from both your current + -- shell session and your environment file by using 'vim.env' + _G._rest_nvim_req_data.headers["USER"] = vim.env.USERNAME end, }) < diff --git a/doc/tags b/doc/tags index 9330b08a..f12ab59a 100644 --- a/doc/tags +++ b/doc/tags @@ -57,11 +57,11 @@ rest-nvim-license rest-nvim.txt /*rest-nvim-license* rest-nvim-quick-start rest-nvim.txt /*rest-nvim-quick-start* rest-nvim-script-variables rest-nvim.txt /*rest-nvim-script-variables* rest-nvim-usage rest-nvim.txt /*rest-nvim-usage* -rest-nvim-usage-callbacks rest-nvim.txt /*rest-nvim-usage-callbacks* rest-nvim-usage-commands rest-nvim.txt /*rest-nvim-usage-commands* rest-nvim-usage-dynamic-variables rest-nvim.txt /*rest-nvim-usage-dynamic-variables* rest-nvim-usage-environment-variables rest-nvim.txt /*rest-nvim-usage-environment-variables* rest-nvim-usage-external-files rest-nvim.txt /*rest-nvim-usage-external-files* +rest-nvim-usage-hooks rest-nvim.txt /*rest-nvim-usage-hooks* rest-nvim-usage-requests rest-nvim.txt /*rest-nvim-usage-requests* rest-nvim.api rest-nvim-api.txt /*rest-nvim.api* rest-nvim.client.curl rest-nvim-curl.txt /*rest-nvim.client.curl* @@ -84,6 +84,7 @@ result.get_or_create_buf rest-nvim-api.txt /*result.get_or_create_buf* result.write_block rest-nvim-api.txt /*result.write_block* result.write_res rest-nvim-api.txt /*result.write_res* script_vars.load rest-nvim-parser.txt /*script_vars.load* +utils.escape rest-nvim-api.txt /*utils.escape* utils.file_exists rest-nvim-api.txt /*utils.file_exists* utils.highlight rest-nvim-api.txt /*utils.highlight* utils.read_file rest-nvim-api.txt /*utils.read_file* From 4f41d09e06cbac00a33162797a7c711be674051b Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 06:32:18 -0400 Subject: [PATCH 153/159] docs: update project features --- README.md | 1 + doc/rest-nvim.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 736b486e..aceb300d 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ CLI. For more information on this, please see this [blog post](https://amartin.c - Easy to use - Friendly and organized request results window - Fast runtime with statistics about your request +- Set custom pre-request and post-request hooks to dynamically interact with the data - Easily set environment variables based on the response to re-use the data later - Tree-sitter based parsing and syntax highlighting for speed and perfect accuracy - Possibility of using dynamic/environment variables and Lua scripting in HTTP files diff --git a/doc/rest-nvim.txt b/doc/rest-nvim.txt index 7dac3d46..db41d669 100644 --- a/doc/rest-nvim.txt +++ b/doc/rest-nvim.txt @@ -34,6 +34,7 @@ FEATURES *rest-nvim-features* - Easy to use - Friendly and organized request results window - Fast runtime with statistics about your request +- Set custom pre-request and post-request hooks to dynamically interact with the data - Easily set environment variables based on the response to re-use the data later - Tree-sitter based parsing and syntax highlighting for speed and perfect accuracy - Possibility of using dynamic/environment variables and Lua scripting in HTTP files From 5c34314cdc086a8a068458b68e752325980fb37f Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 06:33:28 -0400 Subject: [PATCH 154/159] ref(parser)!: do not read environment files during the parsing process --- lua/rest-nvim/parser/init.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/lua/rest-nvim/parser/init.lua b/lua/rest-nvim/parser/init.lua index 7f750fb7..e4555cbc 100644 --- a/lua/rest-nvim/parser/init.lua +++ b/lua/rest-nvim/parser/init.lua @@ -10,7 +10,6 @@ local parser = {} -local env_vars = require("rest-nvim.parser.env_vars") local dynamic_vars = require("rest-nvim.parser.dynamic_vars") ---@alias NodesList { [string]: TSNode }[] @@ -185,7 +184,6 @@ local function parse_variables(node, tree, text, variables) logger:debug( "The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ..." ) - env_vars.read_file() local env_var = vim.env[variable_name] if not env_var then ---@diagnostic disable-next-line need-check-nil @@ -285,7 +283,6 @@ local function traverse_body(tbl, variables) logger:debug( "The variable '" .. variable_name .. "' was not found in the document, falling back to the environment ..." ) - env_vars.read_file() local env_var = vim.env[variable_name] if not env_var then ---@diagnostic disable-next-line need-check-nil From 7340994517dc8cb362de4369467f622098ecc4e0 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 06:39:19 -0400 Subject: [PATCH 155/159] chore: regenerate documentation --- doc/rest-nvim-parser.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/rest-nvim-parser.txt b/doc/rest-nvim-parser.txt index 23b43915..d4ec8d5e 100644 --- a/doc/rest-nvim-parser.txt +++ b/doc/rest-nvim-parser.txt @@ -39,10 +39,13 @@ env_vars.set_var({name}, {value}) *env_vars.set_var* |vim.env| -env_vars.read_file() *env_vars.read_file* +env_vars.read_file({quiet}) *env_vars.read_file* Read the environment variables file from the rest.nvim configuration and store all the environment variables in the `vim.env` metatable + Parameters: ~ + {quiet} (boolean) Whether to fail silently if an environment file is not found, defaults to `false` + See: ~ |vim.env| From d17e9d5c14ca9c7a40787a2dfc693de636a66a10 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 06:39:57 -0400 Subject: [PATCH 156/159] chore: autoformat with stylua --- lua/rest-nvim/autocmds.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rest-nvim/autocmds.lua b/lua/rest-nvim/autocmds.lua index 2b5fc200..5a6751f1 100644 --- a/lua/rest-nvim/autocmds.lua +++ b/lua/rest-nvim/autocmds.lua @@ -52,7 +52,7 @@ function autocmds.setup() desc = "Close rest.nvim request results help window", buffer = args.buf, }) - end + end, }) end From b43d338a86b232456b85a735b26fc01aa81efc20 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Sun, 17 Mar 2024 08:56:31 -0400 Subject: [PATCH 157/159] chore: update queries --- after/queries/http/highlights.scm | 49 +++++++++++++------------------ after/queries/http/injections.scm | 14 ++++----- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/after/queries/http/highlights.scm b/after/queries/http/highlights.scm index bdda2977..dfb5a5e9 100644 --- a/after/queries/http/highlights.scm +++ b/after/queries/http/highlights.scm @@ -1,22 +1,17 @@ ; Keywords - -(scheme) @namespace +(scheme) @module ; Methods - -(method) @method +(method) @function.method ; Constants - (const_spec) @constant ; Headers - (header name: (name) @constant) ; Variables - (variable_declaration name: (identifier) @variable) @@ -30,20 +25,23 @@ value: (string) @string) ; Fields - -(pair name: (identifier) @field) +(pair + name: (identifier) @variable.member) ; URL / Host -(host) @text.uri -(host (identifier) @text.uri) -(path (identifier) @text.uri) +(host) @string.special.url -; Parameters +(host + (identifier) @string.special.url) -(query_param (key) @parameter) +(path + (identifier) @string.special.url) -; Operators +; Parameters +(query_param + (key) @variable.parameter) +; Operators [ "=" "?" @@ -53,8 +51,7 @@ ] @operator ; Literals - -(target_url) @text.uri +(target_url) @string.special.url (http_version) @constant @@ -65,22 +62,16 @@ (boolean) @boolean ; Punctuation - -[ "{{" "}}" ] @punctuation.bracket - [ - ":" -] @punctuation.delimiter + "{{" + "}}" +] @punctuation.bracket -; external JSON body +":" @punctuation.delimiter +; external JSON body (external_body - file_path: (path) @text.uri) + file_path: (path) @string.special.path) ; Comments - (comment) @comment @spell - -; Errors - -(ERROR) @error diff --git a/after/queries/http/injections.scm b/after/queries/http/injections.scm index bc9d3c02..68268afc 100644 --- a/after/queries/http/injections.scm +++ b/after/queries/http/injections.scm @@ -1,17 +1,17 @@ -; (comment) @comment +; Comments +((comment) @injection.content + (#set! injection.language "comment")) ; Body - ((json_body) @injection.content - (#set! injection.language "json")) + (#set! injection.language "json")) ((xml_body) @injection.content - (#set! injection.language "xml")) + (#set! injection.language "xml")) ((graphql_body) @injection.content - (#set! injection.language "graphql")) + (#set! injection.language "graphql")) ; Lua scripting - ((script_variable) @injection.content - (#set! injection.language "lua")) + (#set! injection.language "lua")) From 2809184ea8ec9b8d44a5f131770e4c1f13cd383f Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 18 Mar 2024 03:48:25 -0400 Subject: [PATCH 158/159] docs: update README.md --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aceb300d..951a2cee 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,13 @@ CLI. For more information on this, please see this [blog post](https://amartin.c - `jq` (to format JSON output) - `tidy` (to format HTML output) +> [!NOTE] +> +> 1. Python will be unnecessary once `luarocks.nvim` gets rid of it as a dependency in the `go-away-python` branch. +> +> 2. I will be working on making a binary rock of `Lua-cURL` so that the `cURL` development headers are not +> necessary for the installation process. + ### [rocks.nvim](https://github.com/nvim-neorocks/rocks.nvim) (recommended) ```vim @@ -309,10 +316,16 @@ Here is a preview of the component working :) 1. Fork it (https://github.com/rest-nvim/rest.nvim/fork) 2. Create your feature branch (git checkout -b my-new-feature) -3. Commit your changes (git commit -am 'Add some feature') -4. Push to the branch (git push origin my-new-feature) +3. Commit your changes (git commit -am 'feat: add some feature') +4. Push to the branch (git push -u origin my-new-feature) 5. Create a new Pull Request +> [!IMPORTANT] +> +> rest.nvim uses [semantic commits](https://www.conventionalcommits.org/en/v1.0.0/) that adhere to +> semantic versioning and these help with automatic releases, please use this type of convention +> when submitting changes to the project. + ## Related software - [vim-rest-console](https://github.com/diepm/vim-rest-console) From f09e2259a280857e8fefedccf987034c0abafb37 Mon Sep 17 00:00:00 2001 From: NTBBloodbath Date: Mon, 18 Mar 2024 03:49:11 -0400 Subject: [PATCH 159/159] cleanup: remove nvim-treesitter HTTP parser branch modifications code --- lua/rest-nvim/init.lua | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lua/rest-nvim/init.lua b/lua/rest-nvim/init.lua index 05c1be80..2b76bc2e 100644 --- a/lua/rest-nvim/init.lua +++ b/lua/rest-nvim/init.lua @@ -23,19 +23,6 @@ function rest.setup(user_configs) -- Set up rest.nvim autocommands and commands autocmds.setup() - - -- Set up tree-sitter HTTP parser branch - -- NOTE: remove this piece of code once rest.nvim v2 has been pushed, - -- and tree-sitter-http `next` branch has been merged - -- and nvim-treesitter http is up-to-date - local ok, treesitter_parsers = pcall(require, "nvim-treesitter.parsers") - if ok then - local parser_config = treesitter_parsers.get_parser_configs() - - parser_config.http = vim.tbl_deep_extend("force", parser_config.http, { - install_info = { branch = "next" }, - }) - end end return rest