all repos

curl.nvim @ 7c9dc5c60780a467d14f19c73d714a13765c2758

use curl within neovim
2 files changed, 254 insertions(+), 182 deletions(-)
updaties
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-04-19 22:44:58 +0300
Authored at: 2026-04-19 13:51:45 +0300
Change ID: pvumzrvzwmmzzqmwkvskqpwwvvullwyl
Parent: 7fbfcc5
M lua/curl.lua
···
        
        1
        +local curl = {}

      
        
        2
        +curl.config = {

      
        
        3
        +  curl = nil, -- path to binary

      
        
        4
        +  default_flags = { "-i" },

      
        
        5
        +  open_cmd = "vsplit",

      
        
        6
        +  map_execute = "<CR>", -- in query buffer

      
        
        7
        +}

      
        
        8
        +

      
        1
        9
         local H = {

      
        2
        10
           cache_dir = vim.fs.joinpath(vim.fn.stdpath "data", "curl_cache"),

      
        3
        11
           query_buf = nil,

      ···
        9
        17
           running_request = nil,

      
        10
        18
         }

      
        11
        19
         

      
        12
        
        -local curl = {}

      
        13
        
        -

      
        14
        
        -curl.config = {

      
        15
        
        -  curl = nil, -- path to binary

      
        16
        
        -  default_flags = { "-i" },

      
        17
        
        -  open_cmd = "vsplit",

      
        18
        
        -  map_execute = "<CR>", -- in query buffer

      
        19
        
        -}

      
        
        20
        +local function validate_config(config)

      
        
        21
        +  vim.validate("curl.config", config, "table")

      
        
        22
        +  vim.validate("curl.config.curl", config.curl, { "string", "nil" })

      
        
        23
        +  vim.validate("curl.config.default_flags", config.default_flags, "table")

      
        
        24
        +  vim.validate("curl.config.open_cmd", config.open_cmd, "string")

      
        
        25
        +  vim.validate("curl.config.map_execute", config.map_execute, "string")

      
        
        26
        +  for i, flag in ipairs(config.default_flags) do

      
        
        27
        +    vim.validate("curl.config.default_flags[" .. i .. "]", flag, "string")

      
        
        28
        +  end

      
        
        29
        +end

      
        20
        30
         

      
        21
        31
         local function is_valid_buf(buf)

      
        22
        32
           return type(buf) == "number" and buf > 0 and vim.api.nvim_buf_is_valid(buf)

      ···
        26
        36
           return type(win) == "number" and win > 0 and vim.api.nvim_win_is_valid(win)

      
        27
        37
         end

      
        28
        38
         

      
        
        39
        +local function set_buffer_option(buf, name, value)

      
        
        40
        +  vim.api.nvim_set_option_value(name, value, { buf = buf })

      
        
        41
        +end

      
        
        42
        +

      
        
        43
        +local function configure_scratch_buffer(buf)

      
        
        44
        +  set_buffer_option(buf, "buftype", "nofile")

      
        
        45
        +  set_buffer_option(buf, "bufhidden", "hide")

      
        
        46
        +  set_buffer_option(buf, "swapfile", false)

      
        
        47
        +end

      
        
        48
        +

      
        
        49
        +local function configure_query_buffer(buf)

      
        
        50
        +  set_buffer_option(buf, "bufhidden", "hide")

      
        
        51
        +  set_buffer_option(buf, "swapfile", false)

      
        
        52
        +  set_buffer_option(buf, "modifiable", true)

      
        
        53
        +end

      
        
        54
        +

      
        29
        55
         local function trim_lines(lines)

      
        30
        56
           while #lines > 1 and lines[#lines] == "" do

      
        31
        57
             table.remove(lines)

      
        32
        58
           end

      
        33
        59
           return lines

      
        
        60
        +end

      
        
        61
        +

      
        
        62
        +local function trim(s)

      
        
        63
        +  local from = s:match "^%s*()"

      
        
        64
        +  return from > #s and "" or s:match(".*%S", from)

      
        34
        65
         end

      
        35
        66
         

      
        36
        67
         local function is_query_start(line)

      ···
        48
        79
         

      
        49
        80
           local buf = vim.api.nvim_create_buf(false, true)

      
        50
        81
           vim.api.nvim_buf_set_name(buf, name)

      
        51
        
        -  vim.api.nvim_set_option_value("buftype", "nofile", { buf = buf })

      
        52
        
        -  vim.api.nvim_set_option_value("bufhidden", "hide", { buf = buf })

      
        53
        
        -  vim.api.nvim_set_option_value("swapfile", false, { buf = buf })

      
        
        82
        +  configure_scratch_buffer(buf)

      
        54
        83
           return buf

      
        55
        84
         end

      
        56
        85
         

      ···
        92
        121
           if not is_valid_buf(buf) then return end

      
        93
        122
           file = file or vim.api.nvim_buf_get_name(buf)

      
        94
        123
           if not is_curl_file(file) then return end

      
        
        124
        +

      
        95
        125
           local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)

      
        96
        126
           local ok = pcall(vim.fn.writefile, lines, file, "b")

      
        97
        127
           if ok then

      ···
        101
        131
           end

      
        102
        132
         end

      
        103
        133
         

      
        
        134
        +local function find_window_in_current_tab(bufnr)

      
        
        135
        +  for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do

      
        
        136
        +    if vim.api.nvim_win_get_buf(win) == bufnr then return win end

      
        
        137
        +  end

      
        
        138
        +  return nil

      
        
        139
        +end

      
        
        140
        +

      
        
        141
        +local function is_json_start_line(line)

      
        
        142
        +  return trim(line):match "^[%[%{]" ~= nil

      
        
        143
        +end

      
        
        144
        +

      
        
        145
        +local function schedule_output_write(text, is_json)

      
        
        146
        +  vim.schedule(function()

      
        
        147
        +    H.write_output(text, is_json)

      
        
        148
        +  end)

      
        
        149
        +end

      
        
        150
        +

      
        
        151
        +local function extract_headers_and_json(output)

      
        
        152
        +  local normalized = output:gsub("\r\n", "\n"):gsub("\r", "\n")

      
        
        153
        +  if is_json_start_line(normalized) then return {}, normalized end

      
        
        154
        +

      
        
        155
        +  local lines = vim.split(normalized, "\n", { plain = true })

      
        
        156
        +  local json_index = nil

      
        
        157
        +  for i, line in ipairs(lines) do

      
        
        158
        +    if is_json_start_line(line) then

      
        
        159
        +      json_index = i

      
        
        160
        +      break

      
        
        161
        +    end

      
        
        162
        +  end

      
        
        163
        +

      
        
        164
        +  if not json_index then return nil, nil end

      
        
        165
        +

      
        
        166
        +  local headers = {}

      
        
        167
        +  for i = 1, json_index - 1 do

      
        
        168
        +    table.insert(headers, trim(lines[i]))

      
        
        169
        +  end

      
        
        170
        +

      
        
        171
        +  local json_lines = {}

      
        
        172
        +  for i = json_index, #lines do

      
        
        173
        +    table.insert(json_lines, lines[i])

      
        
        174
        +  end

      
        
        175
        +

      
        
        176
        +  return headers, table.concat(json_lines, "\n")

      
        
        177
        +end

      
        
        178
        +

      
        
        179
        +local function merge_headers_and_json(headers, json)

      
        
        180
        +  if not headers or #headers == 0 then return json end

      
        
        181
        +  local merged = vim.deepcopy(headers)

      
        
        182
        +  vim.list_extend(merged, vim.split(json, "\n", { plain = true }))

      
        
        183
        +  return table.concat(merged, "\n")

      
        
        184
        +end

      
        
        185
        +

      
        
        186
        +local function shell_split(input)

      
        
        187
        +  local args = {}

      
        
        188
        +  local current = {}

      
        
        189
        +  local quote = nil

      
        
        190
        +  local i = 1

      
        
        191
        +

      
        
        192
        +  while i <= #input do

      
        
        193
        +    local ch = input:sub(i, i)

      
        
        194
        +

      
        
        195
        +    if quote == "'" then

      
        
        196
        +      if ch == "'" then

      
        
        197
        +        quote = nil

      
        
        198
        +      else

      
        
        199
        +        table.insert(current, ch)

      
        
        200
        +      end

      
        
        201
        +    elseif quote == '"' then

      
        
        202
        +      if ch == '"' then

      
        
        203
        +        quote = nil

      
        
        204
        +      elseif ch == "\\" and i < #input then

      
        
        205
        +        i = i + 1

      
        
        206
        +        table.insert(current, input:sub(i, i))

      
        
        207
        +      else

      
        
        208
        +        table.insert(current, ch)

      
        
        209
        +      end

      
        
        210
        +    else

      
        
        211
        +      if ch == "'" or ch == '"' then

      
        
        212
        +        quote = ch

      
        
        213
        +      elseif ch:match "%s" then

      
        
        214
        +        if #current > 0 then

      
        
        215
        +          table.insert(args, table.concat(current))

      
        
        216
        +          current = {}

      
        
        217
        +        end

      
        
        218
        +      elseif ch == "\\" and i < #input then

      
        
        219
        +        i = i + 1

      
        
        220
        +        table.insert(current, input:sub(i, i))

      
        
        221
        +      else

      
        
        222
        +        table.insert(current, ch)

      
        
        223
        +      end

      
        
        224
        +    end

      
        
        225
        +

      
        
        226
        +    i = i + 1

      
        
        227
        +  end

      
        
        228
        +

      
        
        229
        +  if quote then return nil, "unterminated quote in query" end

      
        
        230
        +  if #current > 0 then table.insert(args, table.concat(current)) end

      
        
        231
        +  return args

      
        
        232
        +end

      
        
        233
        +

      
        
        234
        +local function resolve_query_buffer()

      
        
        235
        +  if is_valid_buf(H.query_buf) then return H.query_buf end

      
        
        236
        +

      
        
        237
        +  local current = vim.api.nvim_get_current_buf()

      
        
        238
        +  local current_name = vim.api.nvim_buf_get_name(current)

      
        
        239
        +  if is_curl_file(current_name) then

      
        
        240
        +    H.set_query_context(current, current_name)

      
        
        241
        +    return current

      
        
        242
        +  end

      
        
        243
        +

      
        
        244
        +  return H.get_query_buffer()

      
        
        245
        +end

      
        
        246
        +

      
        
        247
        +local function stop_running_request()

      
        
        248
        +  if not H.running_request then return end

      
        
        249
        +  pcall(function()

      
        
        250
        +    H.running_request:kill(15)

      
        
        251
        +  end)

      
        
        252
        +  H.running_request = nil

      
        
        253
        +end

      
        
        254
        +

      
        
        255
        +local function result_to_output(result)

      
        
        256
        +  local chunks = {}

      
        
        257
        +  if result.stdout and result.stdout ~= "" then table.insert(chunks, result.stdout) end

      
        
        258
        +  if result.stderr and result.stderr ~= "" then table.insert(chunks, result.stderr) end

      
        
        259
        +

      
        
        260
        +  if #chunks == 0 then return "curl exited with code " .. result.code end

      
        
        261
        +

      
        
        262
        +  return table.concat(chunks, "\n\n")

      
        
        263
        +end

      
        
        264
        +

      
        104
        265
         function H.set_query_mapping(buf)

      
        105
        266
           if not is_valid_buf(buf) then return end

      
        
        267
        +

      
        106
        268
           vim.keymap.set("n", curl.config.map_execute, curl.execute, {

      
        107
        269
             buffer = buf,

      
        108
        270
             noremap = true,

      ···
        115
        277
           if not is_valid_buf(buf) then return false end

      
        116
        278
           local name = vim.api.nvim_buf_get_name(buf)

      
        117
        279
           if not is_curl_file(name) then return false end

      
        118
        
        -  vim.api.nvim_set_option_value("filetype", "curl", { buf = buf })

      
        119
        
        -  vim.api.nvim_set_option_value("syntax", "sh", { buf = buf })

      
        120
        
        -  vim.api.nvim_set_option_value("commentstring", "# %s", { buf = buf })

      
        
        280
        +

      
        
        281
        +  set_buffer_option(buf, "filetype", "curl")

      
        
        282
        +  set_buffer_option(buf, "syntax", "sh")

      
        
        283
        +  set_buffer_option(buf, "commentstring", "# %s")

      
        121
        284
           pcall(vim.treesitter.language.register, "bash", "curl")

      
        122
        285
           H.set_query_mapping(buf)

      
        123
        286
           H.query_buffers[name] = buf

      ···
        127
        290
         local function create_query_buffer(file)

      
        128
        291
           local buf = vim.fn.bufadd(file)

      
        129
        292
           vim.fn.bufload(buf)

      
        130
        
        -  vim.api.nvim_set_option_value("bufhidden", "hide", { buf = buf })

      
        131
        
        -  vim.api.nvim_set_option_value("swapfile", false, { buf = buf })

      
        132
        
        -  vim.api.nvim_set_option_value("modifiable", true, { buf = buf })

      
        
        293
        +  configure_query_buffer(buf)

      
        133
        294
           H.attach_curl_buffer(buf)

      
        134
        295
         

      
        135
        296
           local group = vim.api.nvim_create_augroup("curl_nvim_cache_" .. buf, { clear = true })

      ···
        144
        305
           return buf

      
        145
        306
         end

      
        146
        307
         

      
        147
        
        -local function find_window_in_current_tab(bufnr)

      
        148
        
        -  for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do

      
        149
        
        -    if vim.api.nvim_win_get_buf(win) == bufnr then return win end

      
        150
        
        -  end

      
        151
        
        -  return nil

      
        
        308
        +function H.set_query_context(buf, file)

      
        
        309
        +  if not is_valid_buf(buf) then return false end

      
        
        310
        +  file = file or vim.api.nvim_buf_get_name(buf)

      
        
        311
        +  if not is_curl_file(file) then return false end

      
        
        312
        +

      
        
        313
        +  H.query_buf = buf

      
        
        314
        +  H.query_file = file

      
        
        315
        +  H.query_buffers[file] = buf

      
        
        316
        +  return true

      
        152
        317
         end

      
        153
        318
         

      
        154
        319
         function H.setup_filetype()

      ···
        159
        324
         

      
        160
        325
         function H.setup_command()

      
        161
        326
           if H.command_created then return end

      
        
        327
        +

      
        162
        328
           vim.api.nvim_create_user_command("Curl", function()

      
        163
        329
             curl.open()

      
        164
        330
           end, { desc = "Open curl.nvim query buffer" })

      ···
        169
        335
           local file = get_cache_file()

      
        170
        336
           if not file then return nil end

      
        171
        337
         

      
        172
        
        -  H.query_file = file

      
        173
        338
           local existing = H.query_buffers[file]

      
        174
        
        -  if not is_valid_buf(existing) then

      
        175
        
        -    existing = create_query_buffer(file)

      
        176
        
        -    H.query_buffers[file] = existing

      
        177
        
        -  end

      
        178
        
        -  H.query_buf = existing

      
        179
        
        -

      
        180
        
        -  H.set_query_mapping(H.query_buf)

      
        
        339
        +  if not is_valid_buf(existing) then existing = create_query_buffer(file) end

      
        181
        340
         

      
        182
        
        -  return H.query_buf

      
        
        341
        +  H.query_buffers[file] = existing

      
        
        342
        +  H.set_query_context(existing, file)

      
        
        343
        +  H.set_query_mapping(existing)

      
        
        344
        +  return existing

      
        183
        345
         end

      
        184
        346
         

      
        185
        347
         function H.setup_query_file_autocmd()

      
        186
        348
           if H.query_autocmd_set then return end

      
        
        349
        +

      
        187
        350
           local group = vim.api.nvim_create_augroup("curl_nvim_query_files", { clear = true })

      
        188
        351
           vim.api.nvim_create_autocmd(

      
        189
        352
             { "BufReadPost", "BufNewFile", "BufEnter", "BufWinEnter", "BufFilePost" },

      ···
        195
        358
               end,

      
        196
        359
             }

      
        197
        360
           )

      
        
        361
        +

      
        198
        362
           for _, buf in ipairs(vim.api.nvim_list_bufs()) do

      
        199
        363
             H.attach_curl_buffer(buf)

      
        200
        364
           end

      
        
        365
        +

      
        201
        366
           H.query_autocmd_set = true

      
        202
        367
         end

      
        203
        368
         

      
        204
        369
         function H.get_output_buffer()

      
        205
        
        -  if not is_valid_buf(H.output_buf) then

      
        206
        
        -    H.output_buf = get_or_create_buffer "Curl output"

      
        207
        
        -    vim.api.nvim_set_option_value("filetype", "json", { buf = H.output_buf })

      
        208
        
        -    vim.api.nvim_set_option_value("modifiable", false, { buf = H.output_buf })

      
        209
        
        -  end

      
        
        370
        +  if is_valid_buf(H.output_buf) then return H.output_buf end

      
        
        371
        +

      
        
        372
        +  H.output_buf = get_or_create_buffer "Curl output"

      
        
        373
        +  set_buffer_option(H.output_buf, "filetype", "json")

      
        
        374
        +  set_buffer_option(H.output_buf, "modifiable", false)

      
        210
        375
           return H.output_buf

      
        211
        376
         end

      
        212
        377
         

      
        213
        378
         function H.open_output_window()

      
        214
        379
           local output_buf = H.get_output_buffer()

      
        215
        
        -  local query_buf = H.query_buf

      
        216
        
        -  if not is_valid_buf(query_buf) then query_buf = vim.api.nvim_get_current_buf() end

      
        217
        
        -  if not is_valid_buf(query_buf) then query_buf = H.get_query_buffer() end

      
        
        380
        +  local query_buf = resolve_query_buffer()

      
        218
        381
           if not query_buf then return nil end

      
        
        382
        +

      
        219
        383
           local query_win = find_window_in_current_tab(query_buf) or vim.api.nvim_get_current_win()

      
        220
        384
           local output_win = find_window_in_current_tab(output_buf)

      
        221
        385
         

      ···
        256
        420
           return start_line, end_line

      
        257
        421
         end

      
        258
        422
         

      
        259
        
        -local function shell_split(input)

      
        260
        
        -  local args = {}

      
        261
        
        -  local current = {}

      
        262
        
        -  local quote = nil

      
        263
        
        -  local i = 1

      
        264
        
        -

      
        265
        
        -  while i <= #input do

      
        266
        
        -    local ch = input:sub(i, i)

      
        267
        
        -

      
        268
        
        -    if quote == "'" then

      
        269
        
        -      if ch == "'" then

      
        270
        
        -        quote = nil

      
        271
        
        -      else

      
        272
        
        -        table.insert(current, ch)

      
        273
        
        -      end

      
        274
        
        -    elseif quote == '"' then

      
        275
        
        -      if ch == '"' then

      
        276
        
        -        quote = nil

      
        277
        
        -      elseif ch == "\\" and i < #input then

      
        278
        
        -        i = i + 1

      
        279
        
        -        table.insert(current, input:sub(i, i))

      
        280
        
        -      else

      
        281
        
        -        table.insert(current, ch)

      
        282
        
        -      end

      
        283
        
        -    else

      
        284
        
        -      if ch == "'" or ch == '"' then

      
        285
        
        -        quote = ch

      
        286
        
        -      elseif ch:match "%s" then

      
        287
        
        -        if #current > 0 then

      
        288
        
        -          table.insert(args, table.concat(current))

      
        289
        
        -          current = {}

      
        290
        
        -        end

      
        291
        
        -      elseif ch == "\\" and i < #input then

      
        292
        
        -        i = i + 1

      
        293
        
        -        table.insert(current, input:sub(i, i))

      
        294
        
        -      else

      
        295
        
        -        table.insert(current, ch)

      
        296
        
        -      end

      
        297
        
        -    end

      
        298
        
        -

      
        299
        
        -    i = i + 1

      
        300
        
        -  end

      
        301
        
        -

      
        302
        
        -  if quote then return nil, "unterminated quote in query" end

      
        303
        
        -  if #current > 0 then table.insert(args, table.concat(current)) end

      
        304
        
        -  return args

      
        305
        
        -end

      
        306
        
        -

      
        307
        423
         function H.build_command(query_lines)

      
        308
        424
           local binary = curl.config.curl or "curl"

      
        309
        425
           local body_parts = {}

      ···
        311
        427
           for _, line in ipairs(query_lines) do

      
        312
        428
             local trimmed = vim.trim(line)

      
        313
        429
             if trimmed ~= "" and not trimmed:match "^#" then

      
        314
        
        -      trimmed = trimmed:gsub("\\%s*$", "")

      
        315
        
        -      table.insert(body_parts, trimmed)

      
        
        430
        +      table.insert(body_parts, trimmed:gsub("\\%s*$", ""))

      
        316
        431
             end

      
        317
        432
           end

      
        318
        433
         

      ···
        321
        436
         

      
        322
        437
           local parsed, parse_err = shell_split(body)

      
        323
        438
           if not parsed or #parsed == 0 then return nil, parse_err end

      
        324
        
        -

      
        325
        439
           if parsed[1] == "curl" then table.remove(parsed, 1) end

      
        326
        440
         

      
        327
        
        -  local parts = { binary, "--silent", "--show-error" }

      
        328
        
        -  for _, flag in ipairs(curl.config.default_flags) do

      
        329
        
        -    table.insert(parts, flag)

      
        330
        
        -  end

      
        331
        
        -

      
        332
        
        -  for _, arg in ipairs(parsed) do

      
        333
        
        -    table.insert(parts, arg)

      
        334
        
        -  end

      
        335
        
        -

      
        336
        
        -  return parts, nil

      
        337
        
        -end

      
        338
        
        -

      
        339
        
        -local function trim(s)

      
        340
        
        -  local from = s:match "^%s*()"

      
        341
        
        -  return from > #s and "" or s:match(".*%S", from)

      
        342
        
        -end

      
        343
        
        -

      
        344
        
        -local function is_json_start_line(line)

      
        345
        
        -  return trim(line):match "^[%[%{]" ~= nil

      
        346
        
        -end

      
        347
        
        -

      
        348
        
        -local function extract_headers_and_json(output)

      
        349
        
        -  local normalized = output:gsub("\r\n", "\n"):gsub("\r", "\n")

      
        350
        
        -  local lines = vim.split(normalized, "\n", { plain = true })

      
        351
        
        -

      
        352
        
        -  if is_json_start_line(normalized) then return {}, normalized end

      
        353
        
        -

      
        354
        
        -  local json_index = nil

      
        355
        
        -  for i, line in ipairs(lines) do

      
        356
        
        -    if is_json_start_line(line) then

      
        357
        
        -      json_index = i

      
        358
        
        -      break

      
        359
        
        -    end

      
        360
        
        -  end

      
        361
        
        -

      
        362
        
        -  if not json_index then return nil, nil end

      
        363
        
        -

      
        364
        
        -  local headers = {}

      
        365
        
        -  for i = 1, json_index - 1 do

      
        366
        
        -    table.insert(headers, trim(lines[i]))

      
        367
        
        -  end

      
        368
        
        -

      
        369
        
        -  local json_lines = {}

      
        370
        
        -  for i = json_index, #lines do

      
        371
        
        -    table.insert(json_lines, lines[i])

      
        372
        
        -  end

      
        373
        
        -

      
        374
        
        -  return headers, table.concat(json_lines, "\n")

      
        
        441
        +  local command = { binary, "--silent", "--show-error" }

      
        
        442
        +  vim.list_extend(command, curl.config.default_flags)

      
        
        443
        +  vim.list_extend(command, parsed)

      
        
        444
        +  return command, nil

      
        375
        445
         end

      
        376
        446
         

      
        377
        447
         function H.write_formatted_output(output)

      
        378
        448
           if output == "" or vim.fn.executable "jq" ~= 1 then

      
        379
        
        -    vim.schedule(function()

      
        380
        
        -      H.write_output(output, false)

      
        381
        
        -    end)

      
        
        449
        +    schedule_output_write(output, false)

      
        382
        450
             return

      
        383
        451
           end

      
        384
        452
         

      
        385
        453
           local headers, json_candidate = extract_headers_and_json(output)

      
        386
        454
           if not json_candidate or json_candidate == "" then

      
        387
        
        -    vim.schedule(function()

      
        388
        
        -      H.write_output(output, false)

      
        389
        
        -    end)

      
        
        455
        +    schedule_output_write(output, false)

      
        390
        456
             return

      
        391
        457
           end

      
        392
        458
         

      ···
        396
        462
           }, function(result)

      
        397
        463
             local text = output

      
        398
        464
             local is_json = false

      
        
        465
        +

      
        399
        466
             if result.code == 0 and result.stdout and result.stdout ~= "" then

      
        400
        467
               is_json = true

      
        401
        
        -      if headers and #headers > 0 then

      
        402
        
        -        local merged = {}

      
        403
        
        -        for _, line in ipairs(headers) do

      
        404
        
        -          table.insert(merged, line)

      
        405
        
        -        end

      
        406
        
        -        local json_lines = vim.split(result.stdout, "\n", { plain = true })

      
        407
        
        -        for _, line in ipairs(json_lines) do

      
        408
        
        -          table.insert(merged, line)

      
        409
        
        -        end

      
        410
        
        -        text = table.concat(merged, "\n")

      
        411
        
        -      else

      
        412
        
        -        text = result.stdout

      
        413
        
        -      end

      
        
        468
        +      text = merge_headers_and_json(headers, result.stdout)

      
        414
        469
             end

      
        415
        
        -    vim.schedule(function()

      
        416
        
        -      H.write_output(text, is_json)

      
        417
        
        -    end)

      
        
        470
        +

      
        
        471
        +    schedule_output_write(text, is_json)

      
        418
        472
           end)

      
        419
        473
         end

      
        420
        474
         

      ···
        423
        477
           local lines = trim_lines(vim.split(text, "\n", { plain = true }))

      
        424
        478
           if #lines == 0 then lines = { "" } end

      
        425
        479
         

      
        426
        
        -  vim.api.nvim_set_option_value("modifiable", true, { buf = output_buf })

      
        
        480
        +  set_buffer_option(output_buf, "modifiable", true)

      
        427
        481
           vim.api.nvim_buf_set_lines(output_buf, 0, -1, false, lines)

      
        428
        
        -  vim.api.nvim_set_option_value("modifiable", false, { buf = output_buf })

      
        429
        
        -  vim.api.nvim_set_option_value("filetype", is_json and "json" or "text", { buf = output_buf })

      
        
        482
        +  set_buffer_option(output_buf, "modifiable", false)

      
        
        483
        +  set_buffer_option(output_buf, "filetype", is_json and "json" or "text")

      
        430
        484
         end

      
        431
        485
         

      
        432
        486
         function curl.setup(opts)

      
        433
        
        -  curl.config = vim.tbl_deep_extend("force", curl.config, opts or {})

      
        
        487
        +  local config = vim.tbl_deep_extend("force", curl.config, opts or {})

      
        
        488
        +  validate_config(config)

      
        
        489
        +  curl.config = config

      
        
        490
        +

      
        434
        491
           H.setup_filetype()

      
        435
        492
           H.setup_command()

      
        436
        493
           H.setup_query_file_autocmd()

      ···
        439
        496
         function curl.open()

      
        440
        497
           local query_buf = H.get_query_buffer()

      
        441
        498
           if not query_buf then return end

      
        
        499
        +

      
        442
        500
           local query_win = find_window_in_current_tab(query_buf)

      
        443
        501
           if is_valid_win(query_win) then

      
        444
        502
             vim.api.nvim_set_current_win(query_win)

      ···
        454
        512
             vim.notify("[curl.nvim] execute from a .curl request buffer", vim.log.levels.WARN)

      
        455
        513
             return

      
        456
        514
           end

      
        457
        
        -  H.query_buf = query_buf

      
        458
        
        -  H.query_file = query_file

      
        459
        
        -  H.query_buffers[query_file] = query_buf

      
        
        515
        +  H.set_query_context(query_buf, query_file)

      
        460
        516
         

      
        461
        517
           local lines = vim.api.nvim_buf_get_lines(query_buf, 0, -1, false)

      
        462
        518
           local cursor_line = vim.api.nvim_win_get_cursor(0)[1]

      ···
        475
        531
         

      
        476
        532
           save_query_buffer(query_buf, query_file)

      
        477
        533
           H.open_output_window()

      
        478
        
        -

      
        479
        
        -  if H.running_request then

      
        480
        
        -    pcall(function()

      
        481
        
        -      H.running_request:kill(15)

      
        482
        
        -    end)

      
        483
        
        -    H.running_request = nil

      
        484
        
        -  end

      
        
        534
        +  stop_running_request()

      
        485
        535
         

      
        486
        536
           H.running_request = vim.system(command, { text = true }, function(result)

      
        487
        537
             H.running_request = nil

      
        488
        
        -

      
        489
        
        -    local combined = result.stdout or ""

      
        490
        
        -    if result.stderr and result.stderr ~= "" then

      
        491
        
        -      combined = combined == "" and result.stderr or (combined .. "\n\n" .. result.stderr)

      
        492
        
        -    end

      
        493
        
        -    if combined == "" then combined = "curl exited with code " .. result.code end

      
        494
        
        -

      
        495
        
        -    H.write_formatted_output(combined)

      
        
        538
        +    H.write_formatted_output(result_to_output(result))

      
        496
        539
           end)

      
        497
        540
         end

      
        498
        541
         

      
M lua/curl/health.lua
···
        
        1
        +local function check_binary(bin, msg, optional)

      
        
        2
        +  if vim.fn.executable(bin) == 1 then

      
        
        3
        +    vim.health.ok(bin .. " found on PATH: `" .. vim.fn.exepath(bin) .. "`")

      
        
        4
        +    return

      
        
        5
        +  end

      
        
        6
        +

      
        
        7
        +  if optional then

      
        
        8
        +    vim.health.warn(bin .. " not found on PATH, " .. msg)

      
        
        9
        +  else

      
        
        10
        +    vim.health.error(bin .. " not found on PATH, " .. msg)

      
        
        11
        +  end

      
        
        12
        +end

      
        
        13
        +

      
        
        14
        +return {

      
        
        15
        +  check = function()

      
        
        16
        +    vim.health.start "Neovim version"

      
        
        17
        +    if vim.fn.has "nvim-0.12" == 1 then

      
        
        18
        +      vim.health.ok "Neovim version is compatible"

      
        
        19
        +    else

      
        
        20
        +      vim.health.error "nvim-0.12 or newer is required"

      
        
        21
        +    end

      
        
        22
        +

      
        
        23
        +    vim.health.start "Required dependencies"

      
        
        24
        +    check_binary("curl", "required to run requests", false)

      
        
        25
        +

      
        
        26
        +    vim.health.start "Optional dependencies"

      
        
        27
        +    check_binary("jq", "used for JSON formatting in output buffers", true)

      
        
        28
        +  end,

      
        
        29
        +}