This project has been cryogenically frozen (i.e. I'm not working on it) until I start actively using Neorg again. I appreciate the plugin's got broken features, and it's likely more broken than when I left it, because Neorg itself is changing.
Sorry :(
My reasoning:
- I haven't been using this project or even using Neorg for some time - since early August 2023.
- Neorg itself just isn't ready yet, for me to use it how I'd like.
- Specific issues with the current norg parser have made it hard to develop this plugin.
- Mainly, it's really hard to concurrently output text correctly inside a ranged tag, given the current AST. It's really hard to avoid writing text out beyond the tag's end marker.
- The plugin ecosystem isn't ready (for me at least).
- Mainly, there's no built-in test runner. This plugin is quite ambitious and complex, and it was maddening to rely on manual testing for it.
So, these things which would need to happen before I'd personally begin working on this again:
- Usability: I'm waitng for GTD. (I'm mainly just hoping for a cohesive capture/refile/agenda UI, and I think GTD will include that + much more). This is on its way.
- Bugfixes: I believe this plugin will be much easier to work on with the new version of the norg parser. i.e. once ranged tags are represented more cleanly in the AST. Vhyrro has indicated that this will be fixed in the new parser.
- Developer experience: I'd like a basic test framework for plugin authors to run tests. Vhyrro has expressed this as a likely future feature, but it's far from the most-urgent thing.
Please don't take this as a dig at Neorg or the project team, they're great. Neorg's a great project and I'm still keen to contribute in future.
When Neorg's moved along somewhat, I'll take another look and look to pick this up again.
In the meantime, PRs are welcome, and I'd be happy to link from this page to an active fork (or alternative plugin), if someone wants to move this forwards.
Ta
**PRE-ALPHA** - breaking changes incoming soon. See Planning, below
@code
block execution for neorg,
similar to Org Mode's 'eval'.
This code began with tamton-aquib's PR - thanks to @tamton-aquib.
In a norg file, move the cursor into the code block and execute :Neorg exec cursor
.
The lua code will run and the @result
tag will be [re]-generated.
@code lua
print('hello, neorg')
@end
@result
hello, neorg
@end
This project is super early in development, and working out what it wants to be.
Conceptually, it would be nice to reproduce some of the success of org-babel
,
but the scope should be limited.
Eventually, Neorg will have its own native core.exec
module.
Maybe some of this code will be used, maybe not.
For now I'm taking advice from the Neorg team to try and ensure this fits reasonably
into the ecosystem.
- Goal: Support a literate programming use case, but don't try to solve all its problems.
- Goal: execute norg
@code
blocks according to per-language configurations. - Goal: capture results into
@result
tags, which are themselves 'verbatim ranged tags'. - Goal: schedule execution appropriately, such that code executions don't interfere with one another.
- Goal: support extension via public functions.
- Goal: make use of existing modules like
core.tangle
, where appropriate. - Goal: use a sensible set of tags for affecting the behaviour of code execution.
- Goal: an API to allow for adding runners for different languages.
- Goal: provide some runtime flexibility.
- Provide support for environment variables, arguments, compilation options, some basic error handling.
- Output handling options - to current-file, an external file, virtual lines.
- Either execute a code block in its own process, or (for debugging), a long-running REPL-style session.
These non-goals are useful to help define the API, so that people can extend with their own modules.
- Non-goal: tangling (exporting code to files). See
core.tangle
. - Non-goal: processing results. This should be done with macros. Maybe another module.
- Non-goal:
org-babel
style interopability between languages and data sources. Another module might look to reproduce these amazing features. - Non-goal: a comprehensive platform of language & OS code runners, containerised runners, all that jazz.
First, make sure to pull this plugin down. This plugin does not run any code in of itself.
It requires Neorg to load it first:
You can install it through your favorite plugin manager:
-
packer.nvim
use { "nvim-neorg/neorg", config = function() require('neorg').setup { load = { ["core.defaults"] = {}, ... ["external.exec"] = {}, }, } end, requires = { "nvim-lua/plenary.nvim", "laher/neorg-exec" }, }
-
vim-plug
Plug 'nvim-neorg/neorg' | Plug 'nvim-lua/plenary.nvim' | Plug 'laher/neorg-exec'
You can then put this initial configuration in your
init.vim
file:lua << EOF require('neorg').setup { load = { ["core.defaults"] = {}, ... ["external.exec"] = {}, }, } EOF
-
lazy.nvim
require("lazy").setup({ { "nvim-neorg/neorg", opts = { load = { ["core.defaults"] = {}, ... ["external.exec"] = {}, }, }, dependencies = { { "nvim-lua/plenary.nvim" }, { "laher/neorg-exec" } }, } })
Given a norg file containing a code block like this:
@code bash
print hello
@end
You can exec
the code block under the cursor with an ex command:
:Neorg exec cursor
cursor
also works on headings. It will execute all code blocks within that
heading section.
To run all blocks in the file, you can use current-file
.
:Neorg exec current-file
Or you can bind a key like this:
vim.keymap.set('n', '<localleader>x', ':Neorg exec cursor<CR>', {silent = true}) -- just this block or blocks within heading section
vim.keymap.set('n', '<localleader>X', ':Neorg exec current-file<CR>', {silent = true}) -- whole file
Note: localleader
is like leader
but intended more for specific filetypes, such as .norg. You can use leader
if you prefer).
By default, the result will be written into the buffer, directly below the code
tag's @end
tag:
#result.start
#result.exit 0.01s 0
@result
hello
@end
Provide some tags to specify how to run the code.
exec.name {name}
(or justname {name}
) - names the blockexec.out {virtual|inplace}
(or justout {virtual|inplace}
) - specifies how to render the output. (default isout inplace
).exec.session {sessionid}
(orsession {sessionid}
) - indicates that this block can be run in a named session. In this way you can run or re-run blocks in the same interpreter. NOTE that sessions can only apply to languages which are configured with arepl_cmd
.exec.env.KEY val
(orenv.KEY val
) - set an environment variable.exec.enabled false
can be used to disable code execution.
#exec.name helloworld
#exec.out virtual
#exec.env.NAME neorg
@code bash
print hello $NAME
@end
After running a code block with virtual
rendering, you can use two other subcommands:
materialize
to write all virtual text to the file, 'in place'.- or
clear
to delete the virtual text.clear
also clears@result
blocks from the file.
Use the exec
metadata tag, to configure metadata for the whole file.
Note that carrover tags override document metadata.
For example:
@document.meta
exec: {
out: virtual
env: {
MYVAR: val
}
}
@end
Default configuration settings can be seen in config.lua.
When you configure neorg
, you can override some of these settings, like so:
["external.exec"] = {
config = {
default_metadata = {
enabled = false,
env = {
NEORG: "rocks"
},
},
lang_cmds = {
lua = {
cmd = "luajit ${0}", -- use a different command for running lua
type = "interpreted",
repl = nil, -- disable sessions
},
},
}
},
- The
default_metadata
section serves as global defaults forexec
metadata.default_metadata
is overridden by document metadata and carryover tags.- Note how we set
enabled = false,
. If you set this, you'll need to enable@code
blocks (or whole documents) explicitly. - You could also e.g. set some default
env
variables andout = "virtual"
.
- See the
lang_cmds
section for per-language runtime configuration. A language type should be either"interpreted"
or"compiled"
.
An example of an interpreted language which supports session
s via a repl:
lang_cmds = {
lua = {
cmd = "lua ${0}",
type = "interpreted",
repl = "lua -i",
},
...
}
An example for a compiled language, which supports wrapping into a main function. This example executes some steps before & after running the binary.
lang_cmds = {
cpp = {
cmd = "g++ ${0} && ./a.out && rm ./a.out",
type = "compiled",
main_wrap = [[
#include <iostream>
int main() {
${1}
}
]],
},
...
}
Not really planning as such. More of a rambling list.
- After an invocation fails, there's sometimes an index-out-of-bounds when you retry.
- Rendering quirks:
- Results rendering can be a bit unpredictable. Sometimes it gets a bit mangled, sometimes it can duplicate the results section. * I addressed a lot of the quirks by locating the @result block with treesitter.
- Spinner is also a bit funky.
-
virtual
mode does some weird stuff affecting navigation around the file. * seems better now. As good as it can be.
-
Much of the original PR checklist - see below
-
Scheduling:
- One queue (try plenary.async), one consumer. Single thread executing code.
- Usually one session & one process at a time, but support multiple workers for 'session' support a la org-mode
-
UI:
-
Render 'virtual lines' into a popup instead of the buffer.Doesn't suit multiple blocks - Maybe spinner could go into the gutter.
- output handling: 'replace' @result block (instead of 'prepend' another @result block)
-
-
Code block tagging, for indicating how to run the code.
- Similar to org-mode's tagging for code block environment and result handling.
- Options:
- [-]
Consider tags above code blocks like#exec cache=5m pwd=.. result.tagtype=@
- Could instead be individual tags per item <- This is @vhyrro's preference.
- [-]
Or, possibly even merge it into the@code
line ...@code bash cache=5m
- [-]
- env support.
- try plenary.job for easy env support. Maybe there are some other benefits too.
- Caching - similar to org-mode but with cache timeout (plus the hash in the result block)
- Named blocks.
- Handling options for stderr, etc. Needs thought - do we want nested tags?
- Output type? e.g.
json
. Then results could be syntax-hightlighted just like code blocks.
-
Results
- Render in a ranged tag? like
@result\ndone...\n@end
(or optionally|result\n** some norg-formatted output\n|end
)- verbatim only, for now. It seems like Macros could fulfil generation
of norg markup <- @vhyrro's recommendation.
-
out=file filename.out
-
out=silent
- What to do about stderr vs stdout? prefixes?
-
- verbatim only, for now. It seems like Macros could fulfil generation
of norg markup <- @vhyrro's recommendation.
- Tag with start time? like
#exec.start 2020-01-01T00:11:22.123Z
- Then maybe at the end ... (insert above the @result tag)
- duration -
#exec.duration_s 1.23s
- exit code
- duration -
- Render in a ranged tag? like
-
A way to assess whether a compiler/interpreter is available & feasible. e.g.
type -p gcc
. Seems related to cross-platform support. -
Run multiple blocks at once, within a node, etc. Caching, env variables, macros?
- whole buffer
- all blocks under a heading
-
Hopefully, tangle integration.
-
file-level tagging (similar to
@code
block tagging) -
Subcommand changes:
- rename
view
tovirtual
. - Restructure, maybe:
:Neorg exec cursor [normal|virtual]
:Neorg exec buf [normal|virtual]
... not sure yet how to make this extendable. - Cursor mode to support 'all code blocks within current norg object'
- rename
-
Macro support:
-
.exec.call named-block arg arg
-
.exec.result named-result
- some way to address code blocks across files.
- some way to chain things together? IDK if this is a good idea, but maybe worth thinking about.
-
-
virtual-mode: keep or not keep?
- Options:
- Keep
- it's kinda cool.
- maybe more suitable for some use cases? like literate programming? Hard to assess
Not keep... keeping it.- a little bit memory hungry (we need to keep all the lines in a table aswell as the virtual lines).
- The code is a bit more complicated & stateful because of this.
- Workflow is a bit non-obvious.
- We could retain virtual lines for stats? and progress reporting?
- Keep
- Options:
-
Security:
-
Safety options:
- don't run multiple without confirm?
-
chroot jails?out of scope -
docker-based runners?out of scope - memory limits, timeouts, etc?
- killing processes? esp sessions.
- enabled false, can be configured as a default or a file/block lebel.
- (could be the default, maybe?)
-
Integration with
core.tangle
:- Respect
core.tangle
tags somehow?- Or at least follow the naming conventions. (e.g.
current-file
)
- Or at least follow the naming conventions. (e.g.
- Use
core.tangle
to pre-process code blocks? Is that even feasible?
- Respect
Some examples of what I think a nice tagged code block + results block could look like ... feedback welcome.
Most of these carryover tags haven't been implemented. But they could be.
#exec.cache 5m
#exec.pwd=..
#exec.results=replace
@code bash
ls
@end
#exec.start 2020-01-01T00:11:22.123Z hash=0000deadbeef1234
#exec.exit 0 1.23s
@result
dir/
file1.txt
file2.txt
@end
See [https://github.com/nvim-neorg/neorg/pull/618#issue-1402358683](Pull Request)
- cross platform support.
- spinner for running block.
- run for
- compiled (c, cpp, rust, etc)
- check extra parameters for wrapping in main function? (+ named blocks)
- interpreted (python, lua, bash, etc)
- compiled (c, cpp, rust, etc)
- subcommands
-
view(virtual_lines) renamed tovirtual
- normal (maybe a better command name, normal lines added to buffer.)
-
- add logging?
- check for timeout / limit number of output lines.
- make non-blockable if possible (weird python behaviour)
- user config options. (done now IMO)
- set lines to separate files and run.
- panic messages instead of empty returns.
- assign state for each running block instead of tracking the current one. (will fix spinners and re-execution bugs)
- code cleanup. (done now IMO)