Skip to content

Commit

Permalink
feat: configurable code runner with molten-nvim integration (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
benlubas authored Nov 18, 2023
1 parent 14a80ff commit eacd8ff
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 144 deletions.
68 changes: 62 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ It will be merged with the default options, which are shown below in the example
If you want to use the defaults, simply call `setup` without arguments or with an empty table.

```lua
require'quarto'.setup{
require('quarto').setup({
debug = false,
closePreviewOnExit = true,
lspFeatures = {
Expand All @@ -77,6 +77,13 @@ require'quarto'.setup{
enabled = true,
triggers = { "BufWritePost" }
},
codeRunner = {
enabled = false,
default_method = nil, -- 'molten' or 'slime'
ft_runners = {}, -- filetype to runner, ie. `{ python = "molten" }`.
-- Takes precedence over `default_method`
never_run = { "yaml" }, -- filetypes which are never sent to a code runner
},
completion = {
enabled = true,
},
Expand All @@ -87,7 +94,7 @@ require'quarto'.setup{
rename = '<leader>lR',
references = 'gr',
}
}
})
```

### Preview
Expand All @@ -101,9 +108,9 @@ QuartoPreview
or access the function from lua, e.g. to create a keybinding:

```lua
local quarto = require'quarto'
local quarto = require('quarto')
quarto.setup()
vim.keymap.set('n', '<leader>qp', quarto.quartoPreview, {silent = true, noremap = true})
vim.keymap.set('n', '<leader>qp', quarto.quartoPreview, { silent = true, noremap = true })
```

Then use the keyboard shortcut to open `quarto preview` for the current file or project in the active working directory in the neovim integrated terminal in a new tab.
Expand Down Expand Up @@ -154,13 +161,59 @@ You can now also enable other lsp features, such as the show hover function
and shortcut, independent of showing diagnostics by enabling lsp features
but not enabling diagnostics.

### Other edgecases
### Other Edge Cases

Other languages might have similar issues (e.g. I see a lot of warnings about whitespace when activating diagnostics with `lua`).
If you come across them and have a fix, I will be very happy about a pull request!
Or, what might ultimately be the cleaner way of documenting language specific issues, an entry in the [wiki](https://github.com/quarto-dev/quarto-nvim/wiki).

## Available Commnds
## Running Code

Quarto-nvim doesn't run code for you, instead, it will interface with existing code running
plugins and tell them what to run. There are currently two such code running plugins that quarto
will work with:
1. [molten-nvim](https://github.com/benlubas/molten-nvim) - a code runner that supports the jupyter
kernel, renders output below each code cell, and optionally renders images in the terminal.
2. [vim-slime](https://github.com/jpalardy/vim-slime) - a general purpose code runner with support
for sending code to integrated nvim terminals, tmux panes, and many others.

I recommend picking a code runner, setting it up based on its README, and then coming back
to this point to learn how Quarto will augment that code runner.

This plugin enables easily sending code cells to your code runner. There are two different ways to
do this: commands, covered below; and lua functions, covered right here. *By default these functions
will only run cells that are the same language as the current cell.*

Quarto exposes code running functions through to runner module: `require('quarto.runner')`. Those
functions are:
- `run_cell()` - runs the current cell
- `run_above(multi_lang)` - runs all the cells above the current one, **and** the current one, in order
- `run_below(multi_lang)` - runs all the cells below the current one, **and** the current one, in order
- `run_all(multi_lang)` - runs all the cells in the document
- `run_line(multi_lang)` - runs the line of code at your cursor
- `run_range()` - run code inside the visual range

Each function that takes the optional `multi_lang` argument will run cells of all languages when
called with the value `true`, and will only run cells that match the language of the current cell
otherwise. As a result, just calling `run_all()` will run all cells that match the language of the
current cell.


Here are some example run mappings:
```lua
local runner = require("quarto.runner")
vim.keymap.set("n", "<localleader>rc", runner.run_cell, { desc = "run cell", silent = true })
vim.keymap.set("n", "<localleader>ra", runner.run_above, { desc = "run cell and above", silent = true })
vim.keymap.set("n", "<localleader>rA", runner.run_all, { desc = "run all cells", silent = true })
vim.keymap.set("n", "<localleader>rl", runner.run_line, { desc = "run line", silent = true })
vim.keymap.set("v", "<localleader>r", runner.run_range, { desc = "run line", silent = true })
vim.keymap.set("n", "<localleader>RA", function()
runner.run_all(true)
end, { desc = "run all cells of all languages", silent = true })
```


## Available Commands

```vim
QuartoPreview
Expand All @@ -169,8 +222,11 @@ QuartoHelp <..>
QuartoActivate
QuartoDiagnostics
QuartoHover
QuartoSend
QuartoSendAbove
QuartoSendBelow
QuartoSendAll
QuartoSendLine
```

## Recommended Plugins
Expand Down
23 changes: 12 additions & 11 deletions ftplugin/quarto.lua
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
vim.b.slime_cell_delimiter = "```"

local quarto = require 'quarto'
local config = require("quarto.config").config
local quarto = require("quarto")

local function set_keymaps()
local b = vim.api.nvim_get_current_buf()
local function set(lhs, rhs)
vim.api.nvim_buf_set_keymap(b, 'n', lhs, rhs, { silent = true, noremap = true })
vim.api.nvim_buf_set_keymap(b, "n", lhs, rhs, { silent = true, noremap = true })
end
set(quarto.config.keymap.definition, ":lua require'otter'.ask_definition()<cr>")
set(quarto.config.keymap.type_definition, ":lua require'otter'.ask_type_definition()<cr>")
set(quarto.config.keymap.hover, ":lua require'otter'.ask_hover()<cr>")
set(quarto.config.keymap.rename, ":lua require'otter'.ask_rename()<cr>")
set(quarto.config.keymap.references, ":lua require'otter'.ask_references()<cr>")
set(quarto.config.keymap.document_symbols, ":lua require'otter'.ask_document_symbols()<cr>")
set(quarto.config.keymap.format, ":lua require'otter'.ask_format()<cr>")
set(config.keymap.definition, ":lua require'otter'.ask_definition()<cr>")
set(config.keymap.type_definition, ":lua require'otter'.ask_type_definition()<cr>")
set(config.keymap.hover, ":lua require'otter'.ask_hover()<cr>")
set(config.keymap.rename, ":lua require'otter'.ask_rename()<cr>")
set(config.keymap.references, ":lua require'otter'.ask_references()<cr>")
set(config.keymap.document_symbols, ":lua require'otter'.ask_document_symbols()<cr>")
set(config.keymap.format, ":lua require'otter'.ask_format()<cr>")
end

if quarto.config.lspFeatures.enabled then
if config.lspFeatures.enabled then
quarto.activate()
set_keymaps()
-- set the keymap again if a language server attaches
Expand All @@ -31,7 +32,7 @@ if quarto.config.lspFeatures.enabled then
-- <https://github.com/neovim/neovim/blob/d0d132fbd055834cbecb3d4e3a123a6ea8f099ec/runtime/lua/vim/lsp.lua#L1702-L1711>
vim.api.nvim_create_autocmd("LspAttach", {
buffer = vim.api.nvim_get_current_buf(),
group = vim.api.nvim_create_augroup('QuartoKeymapSetup', {}),
group = vim.api.nvim_create_augroup("QuartoKeymapSetup", {}),
callback = set_keymaps,
})
end
39 changes: 39 additions & 0 deletions lua/quarto/config.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
local M = {}

M.defaultConfig = {
debug = false,
closePreviewOnExit = true,
lspFeatures = {
enabled = true,
chunks = "curly",
languages = { "r", "python", "julia", "bash", "html" },
diagnostics = {
enabled = true,
triggers = { "BufWritePost" },
},
completion = {
enabled = true,
},
},
codeRunner = {
enabled = false,
default_method = nil, -- "molten" or "slime"
ft_runners = {}, -- filetype to runner, ie. `{ python = "molten" }`.
-- Takes precedence over `default_method`
never_run = { "yaml" }, -- filetypes which are never sent to a code runner
},
keymap = {
hover = "K",
definition = "gd",
type_definition = "gD",
rename = "<leader>lR",
format = "<leader>lf",
references = "gr",
document_symbols = "gS",
},
}

-- use defaultConfig if not setup
M.config = M.config or M.defaultConfig

return M
140 changes: 26 additions & 114 deletions lua/quarto/init.lua
Original file line number Diff line number Diff line change
@@ -1,39 +1,10 @@
local M = {}
local api = vim.api
local cfg = require("quarto.config")
local otter = require("otter")
local otterkeeper = require("otter.keeper")
local tools = require("quarto.tools")
local util = require("lspconfig.util")

M.defaultConfig = {
debug = false,
closePreviewOnExit = true,
lspFeatures = {
enabled = true,
chunks = "curly",
languages = { "r", "python", "julia", "bash", "html" },
diagnostics = {
enabled = true,
triggers = { "BufWritePost" },
},
completion = {
enabled = true,
},
},
keymap = {
hover = "K",
definition = "gd",
type_definition = "gD",
rename = "<leader>lR",
format = "<leader>lf",
references = "gr",
document_symbols = "gS",
},
}

-- use defaultConfig if not setup
M.config = M.defaultConfig

function M.quartoPreview(opts)
opts = opts or {}
local args = opts.args or ""
Expand Down Expand Up @@ -73,12 +44,12 @@ function M.quartoPreview(opts)
vim.cmd("tabprevious")
api.nvim_buf_set_var(0, "quartoOutputBuf", quartoOutputBuf)

if not M.config then
if not cfg.config then
return
end

-- close preview terminal on exit of the quarto buffer
if M.config.closePreviewOnExit then
if cfg.config.closePreviewOnExit then
api.nvim_create_autocmd({ "QuitPre", "WinClosed" }, {
buffer = api.nvim_get_current_buf(),
group = api.nvim_create_augroup("quartoPreview", {}),
Expand Down Expand Up @@ -121,7 +92,7 @@ end

M.activate = function()
local tsquery = nil
if M.config.lspFeatures.chunks == "curly" then
if cfg.config.lspFeatures.chunks == "curly" then
tsquery = [[
(fenced_code_block
(info_string
Expand All @@ -138,95 +109,36 @@ M.activate = function()
]]
end
otter.activate(
M.config.lspFeatures.languages,
M.config.lspFeatures.completion.enabled,
M.config.lspFeatures.diagnostics.enabled,
cfg.config.lspFeatures.languages,
cfg.config.lspFeatures.completion.enabled,
cfg.config.lspFeatures.diagnostics.enabled,
tsquery
)
end

-- setup
M.setup = function(opt)
M.config = vim.tbl_deep_extend("force", M.defaultConfig, opt or {})
end

local function concat(ls)
if not (type(ls) == "table") then
return ls .. "\n\n"
end
local s = ""
for _, l in ipairs(ls) do
if l ~= "" then
s = s .. "\n" .. l
end
end
return s .. "\n"
end

local function send(lines)
lines = concat(lines)
local success, yarepl = pcall(require, "yarepl")
if success then
yarepl._send_strings(0)
else
vim.fn["slime#send"](lines)
if success then
vim.fn.notify("Install a REPL code sending plugin to use this feature. Options are yarepl.nvim and vim-slim.")
end
cfg.config = vim.tbl_deep_extend("force", cfg.defaultConfig, opt or {})

if cfg.config.codeRunner.enabled then
-- setup top level run functions
local runner = require("quarto.runner")
M.quartoSend = runner.run_cell
M.quartoSendAbove = runner.run_above
M.quartoSendBelow = runner.run_below
M.quartoSendAll = runner.run_all
M.quartoSendRange = runner.run_range
M.quartoSendLine = runner.run_line

-- setup run user commands
api.nvim_create_user_command("QuartoSend", runner.run_cell, {})
api.nvim_create_user_command("QuartoSendAbove", runner.run_above, {})
api.nvim_create_user_command("QuartoSendBelow", runner.run_below, {})
api.nvim_create_user_command("QuartoSendAll", runner.run_all, {})
api.nvim_create_user_command("QuartoSendRange", runner.run_range, { range = 2 })
api.nvim_create_user_command("QuartoSendLine", runner.run_line, {})
end
end

M.quartoSend = function()
local lines = otterkeeper.get_language_lines_around_cursor()
if lines == nil then
print("No code chunk detected around cursor")
return
end
send(lines)
end

M.quartoSendAbove = function()
local lines = otterkeeper.get_language_lines_to_cursor(true)
if lines == nil then
print(
"No code chunks found for the current language, which is detected based on the current code block. Is your cursor in a code block?"
)
return
end
send(lines)
end

M.quartoSendBelow = function()
local lines = otterkeeper.get_language_lines_from_cursor(true)
if lines == nil then
print(
"No code chunks found for the current language, which is detected based on the current code block. Is your cursor in a code block?"
)
return
end
send(lines)
end

M.quartoSendAll = function()
local lines = otterkeeper.get_language_lines(true)
if lines == nil then
print(
"No code chunks found for the current language, which is detected based on the current code block. Is your cursor in a code block?"
)
return
end
send(lines)
end

M.quartoSendRange = function()
local lines = otterkeeper.get_language_lines_in_visual_selection(true)
if lines == nil then
print(
"No code chunks found for the current language, which is detected based on the current code block. Is your cursor in a code block?"
)
return
end
send(lines)
end

return M
Loading

0 comments on commit eacd8ff

Please sign in to comment.