Myo is a Neovim plugin written in Haskell with the plugin framework Ribosome.
It provides several interacting features that include:
- UI: automatic opening, closing and layouting of tmux panes
- Commands: Execution of configurable shell commands in tmux or subprocesses
- Output: Parsing, rendering and navigation of messages produced by commands
Myo benefits a lot from having per-project and per-project-type configuration for setting up language-specific commands. If you don't have a solution for this, the author also maintains proteome, a project management plugin.
The plugin can be loaded by specifying the repo to a package manager like any other, for example by cloning it in a
subdirectory of 'packpath'
or using one of the many plugin managers.
Plug 'tek/myo'
Since the plugin is written in Haskell, its executable has to be fetched or built on the first start.
- If the Nix package manager is available, the plugin will be fetched from the Nix cache (or built if the current commit isn't in the cache)
- Otherwise it will be downloaded from Github's releases.
- If the variable
g:myo_fetch_bin
is set to1
, Nix is ignored and the binary is downloaded from Github regardless.
There are a few settings that, when changing their variables, will trigger reconfiguration of the corresponding component:
myo_ui
myo_commands
Myo uses several autocommands to detect changes.
For the examples in this Readme, I will be using a setup for compiling and testing Scala with sbt.
This command will display some information about the plugin in a scratch buffer.
The purpose of the UI component is to allow creating tracked tmux panes with a fixed layout from mappings that are
cleaned up when vim quits.
The UI is configured through the variable myo_ui
whenever it is modified.
Its schema looks like this:
let s:sbt_pane =
\ {
\ 'layout': 'make',
\ 'ident': 'sbt',
\ 'minSize': 0.5,
\ 'maxSize': 35,
\ 'position': 8
\ }
let g:myo_ui = {
\ 'panes': [s:sbt_pane]
\ }
These settings will be combined with the built-in default layout that consists of determining the window and pane in
which Neovim has been started and a vertical layout named make
positioned to the right of the Neovim pane that
contains one pane, also called make
, which is used as the default target when executing system commands.
It is possible to set up much more complex layouts, with windows and sessions yet to come.
The myo_ui
variable may contain a key layouts
with the same schema as for panes.
The library used for interacting with tmux is chiasma.
layout
is the name of the parent layout of the pane.pin
causes this pane to be opened whenever its layout becomes visible, i.e. when another pane in it is opened. The defaultmake
pane has this set.position
is a value that orders panes in a layout. It can have any numerical value.fixed_size
tries to make a pane always have this number of lines or columns.minimized_size
, default2
, is the number of cells that the pane gets when minimized.weight
is used to distribute surplus space onto panes.
Panes open automatically when a command is executed in them, but there are functions for doing so manually.
:MyoTogglePane sbt
will open the pane sbt
in the lower right corner, or, if it is open already, minimize it to a size of two cells in the
direction of its parent layout (in this case, vertically).
:MyoToggleLayout make
will open the primary pane of the layout make
if no panes in the layout are open, or minimize the layout.
:MyoOpenPane sbt
:MyoOpenLayout make
will perform the same tasks, but won't minimize an already open view.
:MyoHidePane sbt
:MyoHideLayout make
will minimize the view if it is open, otherwise it will do nothing.
This command takes a pane name and will set it as the active pane in tmux.
:echo MyoPaneState('make')
will print the current target state of a pane (what is configured by Myo, not the actual tmux state).
:echo MyoLayoutState('make')
will print the same for a layout.
A command consists of a list of shell commands and some optional values:
- a tmux pane in which to execute it
- a shell command in which to execute it
- a runner (tmux or subprocess)
- a language for parsing its output
Commands are read from the myo_commands
variable:
let s:sbt_cmd = {
\ 'ident': 'sbt',
\ 'lines': ['sbt'],
\ 'target': 'sbt',
\ 'lang': 'scala',
\ }
let s:compile_cmd = {
\ 'ident': 'compile',
\ 'lines': ['test:compile'],
\ 'target': 'sbt',
\ 'lang': 'scala',
\ }
let g:myo_commands = { 'system': [s:sbt_cmd], 'shell': [s:compile_cmd] }
This snippet defines a system command (run directly in the tmux pane) with the command line sbt
that will be
executed in the previously created pane sbt
(defined by the target
field).
Next, it defines a shell command named compile
, which has as its target the name of a system command, here sbt
.
This means that when running this command, it will be sent to the tmux pane running sbt
(it will be started if
necessary) and some interactions with it, like output parsing, will be deferred to the system command.
Its command line is test:compile
, which is the text sent to sbt
.
ident
is the name of the command, to be used withMyoRun
andMyoParse
.lines
is a list of command lines that will be executed in tmux. For subprocess commands, only one line is valid.target
points to a pane or shell in which the command will be run.lang
is used to find the appropriate parser for command output. Currently supported:scala
,haskell
.displayName
is an optional override for the text to be displayed to the user instead of theident
.skipHistory
is a boolean that excludes the command from the command history.kill
is a boolean that causes a running command to be killed when another one is run in the same target.capture
is a boolean that changes how the command's output is parsed. If it istrue
, the currently visible output in a tmux pane is used; otherwise, the entire output since the start of execution.maxLogBytes
is a number that limits the size of the stored output.
Command lines may be template strings with references to parameters:
let ghc_test_cmd = {
\ 'ident': 'test',
\ 'lines': ['hadrian/build test {test_way:--test-way={test_way}} {test_options} {test_case:--only={test_case}}'],
\ 'params': { 'test_options': '-j6' }
\ 'target': 'make',
\ }
There are four variants of interpolations:
{param_name}
substitutes the value and terminates with error if it is undefined{param_name:}
substitutes the value or the empty string{param_name:expr}
processesexpr
as a template or substitutes the empty string if the parameter is undefined{param_name?expr}
processesexpr
as a template if the parameter is a boolean and true (v:true
or a nonzero number) or substitutes the empty string if the parameter is false or undefined
Parameter values are determined from several sources, in decreasing order of precedence:
- CLI-style options.
- Overrides from edits in a menu.
- Overrides from the command option callback.
- The nvim function
Myo_param_<name>
. For example, for the param{test_case}
, the functionMyo_param_test_case
is called. The return value must be a string or boolean, depending on the parameter type. Two special return values are recognized:{ 'ignore': 1 }
continues resolution with the next sources (essentially "fallback to default"){ 'error': 'Custom error message' }
will abort execution of the command and print the error message
- The nvim variable
<scope>:myo_param_<name>
from the scopesg:
,t:
,w:
,b:
in that order. For example,b:myo_param_test_case
. - The default values specified in the command config's attribute
params
. If this is specified, the interpolation styles{param_name:}
and{param_name}
behave identically and always substitute the default as fallback.
This Neovim command/function triggers the execution of a Myo command:
MyoRun compile
This invocation will:
- create the tmux panes for
sbt
(which will also createmake
since it is pinned). - run
sbt
in its pane - send
test:compile
to thesbt
pane
Subsequent invocations will check the stored process ID and skip the second step if it is alive.
The command's options may be overridden by a Neovim callback.
If the callback exists, Myo will call it whenever MyoRun
is executed.
The functions name has the schema MyoOverrides_<command-name>
, where the name is escaped by substituting
non-alphanumerical characters by _
.
The name is either the command's displayName
or, if that's undefined, its ident
.
For example, for a command named some test-command
, the function name will be MyoOverrides_some_test_command
.
The user may return arbitrary command options from that function, in particular a command line and a set of interpolation parameter values.
The purpose of this is to adapt options based on the current cursor context, to "run the test at the cursor position".
A special Neovim command, MyoTest
, uses an automatically created command to make this more explicit.
Importantly, this doesn't happen when the command is rerun from the history – the first invocation can lock in a certain
position, to be repeated later without changing to the new position of the cursor.
The function must take an argument, which will contain the following data:
{
"path": "/path/to/file",
"rel": "relative/path/to/file",
"line": 5,
"col": 10,
"text": "content of the cursor line",
"cword": "result of expand('<cword>')"
}
These denote the position of the cursor.
If the current buffer is not a file, the path
attribute will contain Neovim's current working directory and rel
will
be empty.
The indexes are zero-based.
The function's return value may either be a dict or a single command line string.
If the command
attribute is returned, the named command will be executed instead of the originally requested one.
This is primarily useful for MyoTest
.
If myo-test
isn't defined, a default configuration will be created.
This command can be used to set a template string for all test executions that is dynamically changed by parameter
values returned from MyoTestOverrides
.
The lines
attribute, or the returned string, overrides the command's default template, which may contain interpolation
placeholders that will be resolved as described above.
If the params
attribute is returned, it will be used to override the parameter values from the sources described
above.
If the shell
attribute is returned, it will set the command by that name as the shell in which the test will run;
otherwise, the target
attribute may set the tmux pane target.
The commands MyoRun
and MyoTest
allow an additional method for parameter overrides – CLI arguments:
MyoRun compile --debug=on --name='test name' --fast
These have the highest precedence.
The --param=value
variant works for string arguments, while --flag
enables booleans.
This function allows you to run commands from the history. For example, to run the previous command again:
:MyoReRun 0
This Neovim command/function runs the command myo-test
, which will be created with default settings if the user didn't
configure it.
It will call the function MyoTestOverrides
instead of the command name based function to determine the overrides.
This is mere convenience to ensure that a command for running the cursor test is always available.
This command displays the history in a scratch buffer with a prompt for substring searching.
Selecting one of the entries with <cr>
will rerun the command.
The prompt starts in normal mode, where you can cycle through the history with j
/k
, run a command with <cr>
,
and abort with <esc>
or <c-c>
.
Pressing e
will change to a different view, in which each of the command lines and parameters will be listed.
Pressing e
again on one of them will load its value into the prompt, where it can be modified and written back with
<esc>
.
When you are satisfied with the edits, you can press <cr>
to run the command with the modified values.
This command displays all defined commands in a menu like MyoHistory
.
Myo features support for vim-test, which is a plugin with many built-in heuristics for determining the appropriate command to execute something relating to the current cursor position. When running this function:
:MyoVimTest
Myo will call vim-test and execute its output in the pane or shell configured by g:myo_test_pane
or
g:myo_test_shell
(default being make
).
When a command is executed, its output is read into Myo's state. Running the command
:MyoParseLatest
Will run the appropriate parser, read from the command option pane
or the variable g:myo_test_lang
(currently only
haskell or ``scala
), and display its result in a scratch window, as showcased in the screenshot at the top of the
readme.
After parsing, the cursor will be set to the last of first error (depending on the values of g:myo_output_jump_first
and g:myo_output_auto_jump
).
Using the functions MyoPrev
and MyoNext
, you can cycle through the error list.
For some languages, a default layout and command set will be set up on startup. To prevent this, set:
let g:myo_default_commands = v:false