Advertisement
Guest User

Quarto utils

a guest
Oct 25th, 2022
124
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.68 KB | None | 0 0
  1. --
  2.  
  3. get_single_line = function(buffer, line)
  4.     return vim.api.nvim_buf_get_lines(buffer, line - 1, line, {})
  5. end
  6.  
  7. -- Form text into newline-separated string, adding
  8. -- extra to ensure last line is evaluated
  9. collapse_text = function(text)
  10.     table.insert(text, " ")
  11.     return table.concat(text, "\n")
  12. end
  13.  
  14. -- Compose functions to parse buffer text and to send it to a REPL
  15. create_send_function = function(parser, sender)
  16.     return function(...)
  17.         local result = parser(...)
  18.         return sender(result)
  19.     end
  20. end
  21.  
  22. -- Patterns to match chunk beginning and end lines
  23. chunk_start = "^%s*```{python.*$"
  24. -- Vim pattern, not Lua
  25. any_chunk_start = [[^\s*```{[a-z]\+]]
  26. chunk_end = "^%s*```%s*$"
  27.  
  28. -- Send a code chunk to a slime REPL
  29. run_current_chunk = function(buffer)
  30.     if buffer == nil then
  31.         buffer = 0
  32.     end
  33.     local buffer_name = vim.api.nvim_buf_get_name(buffer)
  34.  
  35.     -- 1-based index!
  36.     local current_line = vim.fn.line(".", vim.fn.bufwinid(buffer_name))
  37.     local last_line = vim.fn.line("$")
  38.     -- This function is 0-indexed
  39.     local buffer_lines = vim.api.nvim_buf_get_lines(buffer, 0, last_line, {})
  40.     -- Both loops include current line
  41.     -- Find nearest chunk start
  42.     local nearest_start = get_table_match_index(
  43.         buffer_lines,
  44.         current_line,
  45.         0,
  46.         -1,
  47.         chunk_start,
  48.         chunk_end
  49.     )
  50.     if nearest_start == nil then
  51.         print("Could not find chunk start from cursor position")
  52.         return
  53.     end
  54.  
  55.     local nearest_end = get_table_match_index(
  56.         buffer_lines,
  57.         current_line,
  58.         last_line + 1,
  59.         1,
  60.         chunk_end,
  61.         chunk_start
  62.     )
  63.     if nearest_end == nil then
  64.         print("Could not find chunk end from cursor position")
  65.         return
  66.     end
  67.     -- Fail silently  on empty chunk
  68.     if nearest_end - nearest_start < 2 then
  69.         return
  70.     end
  71.     -- Exclude start and end lines, which lack valid Python code
  72.     vim.fn["slime#send_range"](nearest_start + 1, nearest_end - 1)
  73.     return nearest_start + 1, nearest_end - 1
  74. end
  75.  
  76. -- Get index of first table element that matches pattern, or nil if an "abort" pattern is encountered before it
  77. get_table_match_index =
  78.     function(lines, start, stop, step, match_pattern, abort_pattern)
  79.         local line
  80.         for i = start, stop, step do
  81.             line = lines[i]
  82.             if string.match(line, match_pattern) then
  83.                 return i
  84.             elseif i ~= start and string.match(line, abort_pattern) then
  85.                 -- Indicates invalid arrangement of lines
  86.                 break
  87.             end
  88.         end
  89.         return nil
  90.     end
  91.  
  92. -- Extract code in chunks from buffer, then join into string
  93. function parse_code_chunks(buffer, stop_line)
  94.     -- Get lines from buffer
  95.     if buffer == nil then
  96.         buffer = 0
  97.     end
  98.     local buffer_lines =
  99.         vim.api.nvim_buf_get_lines(buffer, 0, vim.fn.line("$"), {})
  100.     if stop_line == nil then
  101.         stop_line = table.getn(buffer_lines)
  102.     end
  103.     local code = {}
  104.     local chunk_started, chunk_ended
  105.     local in_chunk = false
  106.     -- Iterate over whole table even if stop_line specified in order to run containing
  107.     -- chunk if stop_line part of a chunk
  108.     for i, line in ipairs(buffer_lines) do
  109.         chunk_started = string.match(line, chunk_start)
  110.         chunk_ended = string.match(line, chunk_end)
  111.         -- Case 1: in chunk in last iteration
  112.         if in_chunk then
  113.             if chunk_started then
  114.                 print("Invalid buffer: chunk start inside chunk at line" .. i)
  115.                 return {}
  116.             end
  117.             -- Reached chunk end
  118.             if chunk_ended then
  119.                 in_chunk = false
  120.                 -- Add code line to output
  121.             else
  122.                 table.insert(code, line)
  123.             end
  124.             -- Case 2: code inside chunk
  125.         else
  126.             -- Break early if stop line exceeded
  127.             if i >= stop_line then
  128.                 break
  129.             end
  130.             if chunk_ended then
  131.                 print("Invalid buffer: chunk end outside chunk at line " .. i)
  132.                 return {}
  133.             end
  134.             if chunk_started then
  135.                 in_chunk = true
  136.             end
  137.             -- If outside chunk and not start or end, do nothing
  138.         end
  139.         --local slime_config = vim.api.nvim_buf_get_var(buffer, "slime_config")
  140.     end
  141.     return code
  142. end
  143.  
  144. run_all_chunks = create_send_function(parse_code_chunks, function(text)
  145.     vim.fn["slime#send"](collapse_text(text))
  146. end)
  147.  
  148. -- Use special IPython escape magic to send Python code
  149. -- Currently broken by IPython update 8.2.0;
  150. -- see https://github.com/ipython/ipython/issues/13622
  151. run_all_chunks_ipython = create_send_function(parse_code_chunks, function(text)
  152.     vim.fn["slime#send"]("%cpaste -q\n")
  153.     vim.fn["slime#send"](collapse_text(text))
  154.     vim.fn["slime#send"]("\n--\n")
  155. end)
  156.  
  157. run_chunks_above = create_send_function(function()
  158.     return parse_code_chunks(
  159.         0,
  160.         vim.fn.line(".", vim.fn.bufwinid(vim.api.nvim_buf_get_name(0)))
  161.     )
  162. end, function(text)
  163.     vim.fn["slime#send"](collapse_text(text))
  164. end)
  165.  
  166. -- Run current line from a buffer
  167. run_line = function(buffer)
  168.     if buffer == nil then
  169.         buffer = 0
  170.     end
  171.     local buffer_name = vim.api.nvim_buf_get_name(buffer)
  172.     -- bufwinid takes a buffer name, not number
  173.     local current_line = vim.fn.line(".", vim.fn.bufwinid(buffer_name))
  174.     local line = get_single_line(buffer, current_line)
  175.     vim.fn["slime#send"](collapse_text(line))
  176. end
  177.  
  178. -- Yank a specified text object (e.g., "ip") and process for sending to REPL
  179. extract_text_object = function(buffer, text_object)
  180.     -- Prserve old register contents, to be restored
  181.     local old_register = vim.fn.getreg("z")
  182.     if old_register == nil then
  183.         old_register = ""
  184.     end
  185.  
  186.     if buffer == nil then
  187.         buffer = 0
  188.     end
  189.     local buf_win_id = vim.fn.bufwinid(vim.api.nvim_buf_get_name(buffer))
  190.  
  191.     local line = vim.fn.line(".", buf_win_id)
  192.     local column = vim.fn.col(".")
  193.  
  194.     vim.fn.setreg("z", "")
  195.     vim.cmd([[silent normal "zy]] .. text_object) -- "
  196.     -- Reset cursor (moved by yank)
  197.     vim.fn.cursor(line, column)
  198.     local text = string.gsub(vim.fn.getreg("z"), "```[^\n]*\n", "\n")
  199.     -- Yanking paragraphs gets chunk boundaries - remove them
  200.     vim.fn.setreg("z", old_register)
  201.     return text .. "\n "
  202. end
  203.  
  204. run_paragraph = create_send_function(function()
  205.     return extract_text_object(nil, "ip")
  206. end, vim.fn["slime#send"])
  207.  
  208. run_word = create_send_function(function()
  209.     return extract_text_object(nil, "iW")
  210. end, vim.fn["slime#send"])
  211.  
  212. -- Returns the most recent visual selection in a buffer, regardless of the
  213. -- mode used
  214. parse_visual_selection = function(buffer)
  215.     if buffer == nil then
  216.         buffer = 0
  217.     end
  218.     -- Define visual registers "'<", "'>" marking selection bounds
  219.     -- If either missing, return empty string
  220.     -- Get visual mode (char, line, block)
  221.     local visual_start = "'<"
  222.     local visual_end = "'>"
  223.     local start_line = vim.fn.line(visual_start)
  224.     local end_line = vim.fn.line(visual_end)
  225.     local start_col = vim.fn.col(visual_start)
  226.     local end_col = vim.fn.col(visual_end)
  227.     -- Change order if needed so start line is higher in buffer
  228.     if end_line < start_line then
  229.         start_line, end_line = end_line, start_line
  230.     end
  231.     local selected_lines =
  232.         vim.api.nvim_buf_get_lines(buffer, start_line - 1, end_line, {})
  233.     --print(vim.inspect(selected_lines))
  234.     local n_lines = table.getn(selected_lines)
  235.     -- If only one line selected, start and end col both occur on it, hence
  236.     -- special case
  237.     if n_lines == 1 then
  238.         selected_lines[1] = string.sub(selected_lines[1], start_col, end_col)
  239.     else
  240.         -- Trim non-selected parts of start and end lines
  241.         selected_lines[1] = string.sub(selected_lines[1], start_col)
  242.         selected_lines[n_lines] =
  243.             string.sub(selected_lines[n_lines], 1, end_col)
  244.     end
  245.     return selected_lines
  246. end
  247.  
  248. run_visual_selection = create_send_function(
  249.     parse_visual_selection,
  250.     function(text)
  251.         vim.fn["slime#send"](collapse_text(text))
  252.     end
  253. )
  254.  
  255. -- Move cursor `n_chunks` forward or backward
  256. find_chunk = function(n_chunks)
  257.     local match_line = nil
  258.     local i = 0
  259.     local flags = "W"
  260.     local step = 1
  261.     if n_chunks < 0 then
  262.         -- Needed to use < condition for loop for both positive and negative cases
  263.         n_chunks = n_chunks * -1
  264.         -- Add flag to search backward
  265.         flags = "b" .. flags
  266.     end
  267.  
  268.     while match_line ~= 0 and i < n_chunks do
  269.         -- Do not wrap around
  270.         match_line = vim.fn.search(any_chunk_start, flags)
  271.         i = i + step
  272.     end
  273.     return match_line
  274. end
  275.  
  276. -- Find chunk by chunk name
  277. -- If duplicate names, finds first, going down and wrapping
  278. find_named_chunk = function(name)
  279.     local pattern = [[^\s*```{.*\s\+]] .. name .. [[\s*}\s*$]]
  280.     local flags = "w" -- Wrap
  281.     return vim.fn.search(pattern, flags)
  282. end
  283.  
  284. find_nth_chunk = function(n_chunks)
  285.     if n_chunks < 1 then
  286.         print("Invalid chunk offset " .. n_chunks)
  287.         return
  288.     end
  289.  
  290.     -- If new chunk not found, restore old position
  291.     local old_pos = vim.fn.getpos(".")
  292.     local new_pos = find_chunk(n_chunks)
  293.     if new_pos == nil then
  294.         vim.fn.setpos(old_pos)
  295.     end
  296. end
  297.  
  298. run_next_chunk = function()
  299.     local next_chunk_start = find_chunk(1)
  300.  
  301.     -- Do not run if no next chunk found (i.e., cursor in last chunk in buffer or below)
  302.     if next_chunk_start ~= 0 then
  303.         run_current_chunk()
  304.     end
  305. end
  306. -- TODO parse specific languages
  307.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement