Commit 5b82fa1e by HiPhish

Initial commit

parents
# Tagfile created by Vim
doc/tags
.. default-role:: code
###########################
Contributing to REPL.nvim
###########################
If you decide to contribute to the development of REPL.nvim please follow the
rules outlined in this file.
Project goals and non-goals
###########################
REPL.nvim aims to be a generic REPL plugin. We want to support as many REPL
types as possible, but we do not offer preferential treatment to any of them.
The main goal is to be a generic wrapper around Nvim‘s `:terminal` command.
Do:
- Add new REPL types
Do not:
- I don‘t know, this list will be filled out over time hopefully
Technical writing
#################
We use reStructureText_ (reST) for documenting the project. Please follow these
guidelines:
- Use proper quotes:
===== ================== =================================================
Glyph Unicode code point Name
===== ================== =================================================
``‘`` U+2018 Left single quotation mark
``’`` U+2019 Right single quotation mark
``“`` U+201C Left double quotation mark
``”`` U+201D Right double quotation mark
===== ================== =================================================
If you see wrong quotation marks (``'``, ``"``, ````` and ``´``) please fix
them.
- Annotate the file in `HACKING.rst`_ using a field list:
.. code-block:: rst
About the `foo` function
########################
:file: plugin/foo.vim
This makes it easy for readers to jump directly to that file.
- Annotate the `TODO.rst`_ file‘s document title with the date when you made
the change. I'm not sure if that‘s a good rule, might remove the rule later.
.. _reStructureText: http://docutils.sourceforge.net/rst.html
.. _HACKING.rst: HACKING.rst
.. _TODO.rst: TODO.rst
The MIT License (MIT)
Copyright (c) 2017 Alejandro "HiPhish" Sanchez
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
.. default-role:: code
######################
Working on REPL.nvim
######################
Code overview
#############
Here is the directory listing of important files::
├─ plugin
│ ├─ repl.vim
│ ├─ repl-settings.vim
│ └─ repl-mappings.vim
└─ test
└─ …
The `plugin/repl.vim` file contains all the important code. The `test`
directory is for testing_.
Building global REPL settings
#############################
:file: plugin/repl-settings.vim
REPL.nvim uses dictionaries for settings. All settings are in one dictionary
and the keys are the types of REPL. Here is an example:
.. code-block:: vim
let g:repl = {'python': '…', 'ruby': '…'}
Of course the values of these keys are not strings, they are dictionaries
themselves. Each dictionary can define a number of settings for that kind of
REPL, such as the binary to execute or the arguments to pass. The default
settings describe the standard settings.
For the future we should consider the possibility of allowing function
references as setting values, those functions would then return the value. The
function could then access information dynamically from the buffer or so.
Users can define a global REPL settings dictionary, as well as dictionaries
local to the tab, window or buffer. When the REPL command is invoked to actual
settings for spawning the REPL are assembled on the spot. Here is a possible
setup:
.. code-block:: vim
" Use GNU Guile as the Scheme interpreter every time
let g:repl = {'scheme' : {'binary': 'guile'}}
" Except for this buffer use Chibi-Scheme
let b:repl = {'scheme' : {'binary': 'chibi-scheme'}}
This assembly is performed inside the `s:repl()` function. When the script is
loaded only the global settings are assembled. Here is how it is done:
#) The user‘s `init.vim` is read, which might contain a `g:repl` variable with
some settings.
#) The plugin is read, which contain all default settings inside `s:repl`.
#) If no `g:repl` exists a new empty dictionary is created, otherwise the
existing one (from `init.vim`) is used.
#) Loop over all the keys in `s:repl` and copy the values to `g:repl` if they
do not exist.
The only restriction is that the user must not assign a value to `g:repl`
outside `init.vim`, otherwise the settings will be overwritten. Instead the
user must assign the values of the keys in question.
Defining a new REPL
===================
Defining a new REPL is the same as adding new settings: add a new key with the
name of the type to `s:repl` and fill in *all* the settings.
The inner workings of `plugin/repl.vim`
#######################################
:file: plugin/repl.vim
This is the file containing the bulk of the plugin logic. It defines the
`:Repl` command and sets up the instance management.
The `s:repl()` function
=======================
The function `s:repl()` is what is really behind the `:Repl` command, it
performs multiple steps, but its main responsibility is to handle spawning of
new REPL instances and hooking them up properly to the existing data
structures.
Determining the REPL type
-------------------------
If there is at least one argument passed the first argument is the type of the
REPL, except if the argument is `'-'`. If the first argument is `'-'` or there
are no arguments we have to guess the type based on the current file type.
We support the dotted file type syntax: first try all the dot-separated atomic
types from left to right. Later types override previous ones. Finally try the
whole file type. This means that if our file type is `scheme.guile` the types
tried are `scheme`, `guile` and `scheme.guile`, in that order.
If no type could be found an error is displayed and the function returns with
no value and no side effects.
Determining the options
-----------------------
As mentioned above, when the plugin was loaded the user‘s default options and
the plugin‘s default options had been combined into the global options. Now we
have to take local options into account as well.
We loop over the keys among the global options (for determined type of REPL)
and in each iteration we loop over the possible scopes. We create a copy of the
global option and if a local option exists we overwrite it.
.. code-block:: vim
for l:key in keys(g:repl[l:type])
silent execute 'let l:'.l:key.' = g:repl[l:type]["'.key.'"]'
for l:scope in ['t', 'w', 'b']
let l:entry = l:scope.':repl["'.l:type.'"]["'.l:key.'"]'
if exists(l:entry)
silent execute 'let l:'.l:key.' = '.l:entry
endif
endfor
endfor
Hooking up and managing REPL instances
======================================
Each REPL buffer has a `b:repl` dictionary with a `'-'` filed, containing
information about this particular instance. This `b:repl` variable can also
contain buffer-local settings, but since `'-'` is a reserved “type” there is no
danger of name collision.
For every type of REPL we have to keep track of running instances. Every entry
in `g:repl` can have an `'instances'` field which contains a list of running
instances, sorted by range from youngest to oldest. When a new REPL instance is
spawned it is added to the front of the list. When a REPL buffer is deleted the
instance is removed from the list using an autocommand.
Testing
#######
We use `Vader.vim`_ as our testing framework.
.. _Vader.vim: https://github.com/junegunn/vader.vim
.. default-role:: code
####################################################################
REPL.nvim - The universal, extendible and configurable REPL plugin
####################################################################
REPL.nvim bring REPL support to Nvim! Use the built-in REPLs, add your own, or
change the existing ones. Change settings in your `init.vim` or on the fly,
make them global or local, use the existing ones or make your own.
What is REPL.nvim?
##################
To put it simply, REPL.nvim adds a wrapper command that allows you to spawn a
REPL instance in a terminal buffer. What makes REPL.nvim stand out is the
amount of control it gives users. The plugin is designed to make configuration
as clean and simple as possible, to allow spawning any number of REPL instances
and add any type of REPL the user wishes with minimal effort.
Let‘s say you are working on a Python script and you want to try a snippet out
in the REPL. Without REPL.nvim you would have to execute `:new` followed by
`:terminal python` to get a REPL instance. This is not a big deal for one
instance, but it adds up over time.
With REPL.nvim on the other hand you only execute `:Repl` to spawn a new Python
REPL instance. What if you are not actually a Python file at the moment? You
can specify the REPL type as the first argument: `:Repl python`.
Setup and quick start
#####################
Installation
============
Instal REPL.nvim like any other plugin. You will also need to have the REPLs
you want to use installed on your system.
Starting a REPL
===============
A new REPL window is created by running the `:Repl` command. You can use the
same arguments you can also use with your binary. Example:
.. code-block:: vim
" Add the current working directory to the load path
:Repl guile -l my-file.scm
" Evaluate an expression and exit (escape the space after 'display')
:Repl guile -c '(display\ "Hello from Guile")'
See below for how to set the default arguments. The `:Repl` command also
accepts the usual modifiers like `:vert`:
.. code-block:: vim
" Open the REPL in a vertical split
:vert Repl
If the `:Repl` command is executed without arguments it will guess the type of
REPL based on the current file type. If you want to guess the type *and* pass
arguments use `-` as the first argument.
Configuration
#############
REPL settings
=============
All REPL configuration is held within the `g:repl` dictionary. You can read the
documentation for details; here is what the default configuration for Python
looks like:
.. code-block:: vim
let g:repl['python'] = {
\'binary': 'python',
\ 'args': [],
\ 'syntax': '',
\ 'title': 'Python REPL
\ }
To override the defaults create a new `g:repl` in your `init.vim` file
containing *only* options you want to change. REPL.nvim is smart enough to fill
in the rest.
.. code-block:: vim
" Add Python syntax highlighting
let g:repl['python'] = {'syntax': 'python'}
After Nvim has loaded you can the dictionary entries. If you wanted to turn
syntax highlighting back off after starting up Nvim you would execute
.. code-block:: vim
" Globally turn syntax highlighting back off
:let g:repl['python']['syntax'] = ''
You can also specify settings local to the current tab/window/buffer by using a
local dictionary:
.. code-block:: vim
" Turn on syntax highlighting for this tab only
let t:repl['python'] = {'syntax': 'python'}
Local dictionaries can be created at any time.
Key mappings
============
A new operator is available for sending text from the current buffer to the
REPL. You will have to remap the keys for the new operator:
.. code-block:: vim
" Send the text of a motion to the REPL
nmap <leader>rs <Plug>(ReplSend)
" Send the current line to the REPL
nmap <leader>rss <Plug>(ReplSendLine)
nmap <leader>rs_ <Plug>(ReplSendLine)
" Send the selected text to the REPL
vmap <leader>rs <Plug>(ReplSend)
With these mappings you could position your cursor inside a pair of
parentheses, press `<leader>rsa)` and your expression would be sent over to the
REPL with its parentheses.
Shortcomings
############
Since REPL.nvim is implemented on top of Nvim's terminal emulator it is also
bound to the same interface. You cannot use Vim's commands to edit text, you
instead have to enter terminal mode (insert mode for the terminal) to modify
text.
Syntax highlighting uses Vim's Scheme highlighting, but this might not always
be adequate. Highlighting the prompt or the backtrace as if it was regular
Scheme code is wrong.
License
#######
REPL.nvim is release under the terms of the MIT license. See the `LICENSE.txt`_
file for details.
.. _LICENSE.txt: LICENSE.txt
#############################
Things that need to be done
#############################
:date: 2017-04-03
.. default-role:: code
When adding to or removing anything from this file please adjust the date at
the top.
Improve support for dotted file type
Currently if the file type is `'a.b.c.d'` the plugin will loop over the list
`['a', 'b', 'c', 'd']` and finally try `'a.b.c.d'`. What it needs to do is
loop over all possible combinations of the list elements while preserving
order. In this example the combinations would be
#) `'a'`
#) `'a.b'`
#) `'a.b.c'`
#) `'a.b.c.d'`
#) `'b'`
#) `'b.c'`
#) `'b.c.d'`
#) `'c.d'`
#) `'d'`
Interface for new REPLs
Defining a new REPL is already possible, but due to loading order users
cannot change settings for it like they can for the built-in ones. What we
need is some sort of `repl#define_repl()` function:
.. code-block:: vim
let s:ruby = {
\ 'binary': 'irb',
\ 'args': [],
\ 'syntax': 'ruby',
\ 'title': 'Ruby REPL',
\ }
call repl#define_repl('ruby', ruby)
Document everything
That's self-evident.
Use a regular buffer
At the moment we are using a terminal buffer, but this is subpar because the
user cannot use regular Vim commands. A regular buffer that sends commands
from the buffer to the REPL process and puts responses into the buffer would
be a superior choice.
De-hardcode settings
Settings must be hard-coded by the user; for instance, this means the user
has to specify a particular string for the binary. It would be preferable if
the user could specify a function reference that returns the string instead.
Have functions in addition to the command
The `:Repl` command is a good interface for users, but other plugins could
make use of `repl#...()`-style functions. A file type plugin could then use
this plugin as a REPL framework.
Ideas for functions:
- `repl#determine_type()`: try to determine the type of the REPL based on
the current buffer
This diff is collapsed. Click to expand it.
" Author: Alejandro "HiPhish" Sanchez
" License: The MIT License (MIT) {{{
" Copyright (c) 2017 HiPhish
"
" Permission is hereby granted, free of charge, to any person obtaining a
" copy of this software and associated documentation files (the
" "Software"), to deal in the Software without restriction, including
" without limitation the rights to use, copy, modify, merge, publish,
" distribute, sublicense, and/or sell copies of the Software, and to permit
" persons to whom the Software is furnished to do so, subject to the
" following conditions:
"
" The above copyright notice and this permission notice shall be included
" in all copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
" NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
" DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
" OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
" USE OR OTHER DEALINGS IN THE SOFTWARE.
" }}}
if !has('nvim')
finish
endif
nnoremap <silent> <Plug>(ReplSend) :set opfunc=<SID>send_to_repl<CR>g@
nnoremap <silent> <Plug>(ReplSendLine) :set opfunc=<SID>send_to_repl<CR>g@_
vnoremap <silent> <Plug>(ReplSend) :<C-U>call <SID>send_to_repl(visualmode(), 1)<CR>
function! Send_to_repl(type, ...)
if a:0
call s:send_to_repl(a:type, a:0)
else
call s:send_to_repl(a:type)
endif
endfunction
function! s:send_to_repl(type, ...) range
if a:0
let l:visualmode = visualmode()
if l:visualmode == 'v'
let l:text = s:range_selection( "`<", "`>", 'v')
elseif l:visualmode == 'V'
let l:text = s:range_selection( "'<", "'>", 'V')
else
let l:text = s:range_selection( "`<", "`>", 'v')
endif
elseif a:type == 'line'
let l:text = s:range_selection( "'[", "']", 'V')
elseif a:type == 'char'
let l:text = s:range_selection( "`[", "`]", 'v')
endif
if empty(g:repl[&ft].instances)
Repl
endif
call jobsend(g:repl[&ft].instances[0].job_id, l:text)
Repl
startinsert
endfunction
function! s:range_selection(lower, upper, mod)
let l:reg = getreg('"')
let l:regtype = getregtype('"')
silent execute "normal! ".a:lower.a:mod.a:upper.'y'
let l:text = @"
call setreg('"', l:reg, l:regtype)
return l:text
endfunction
" Author: Alejandro "HiPhish" Sanchez
" License: The MIT License (MIT) {{{
" Copyright (c) 2017 HiPhish
"
" Permission is hereby granted, free of charge, to any person obtaining a
" copy of this software and associated documentation files (the
" "Software"), to deal in the Software without restriction, including
" without limitation the rights to use, copy, modify, merge, publish,
" distribute, sublicense, and/or sell copies of the Software, and to permit
" persons to whom the Software is furnished to do so, subject to the
" following conditions:
"
" The above copyright notice and this permission notice shall be included
" in all copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
" NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
" DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
" OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
" USE OR OTHER DEALINGS IN THE SOFTWARE.
" }}}
if !has('nvim')
finish
endif
" ----------------------------------------------------------------------------
" The default settings
" ----------------------------------------------------------------------------
" binary : Which REPL binary to execute
" args : Arguments to pass to every execution, come before user arguments
" syntax : Syntax highlighting to use for the REPL buffer
" title : Value of b:term_title
" ----------------------------------------------------------------------------
let s:repl = {
\ 'python' : {
\ 'binary': 'python',
\ 'args': [],
\ 'syntax': '',
\ 'title': 'Python REPL',
\ },
\ 'guile' : {
\ 'binary': 'guile',
\ 'args': ['-L', '.'],
\ 'syntax': 'scheme',
\ 'title': 'Guile REPL',
\ }
\ }
" ----------------------------------------------------------------------------
" Build a dictionary to hold global setting if there is none
" ----------------------------------------------------------------------------
if !exists('g:repl')
let g:repl = {}
endif
" Assign the default options, respect user settings
for s:type in keys(s:repl)
if !exists('g:repl["'.s:type.'"]')
let g:repl[s:type] = s:repl[s:type]
continue
endif
for s:option in keys(s:repl[s:type])
if !exists('g:repl["'.s:type.'"]["'.s:option.'"]')
let g:repl[s:type][s:option] = s:repl[s:type][s:option]
endif
endfor
endfor
" Author: Alejandro "HiPhish" Sanchez
" License: The MIT License (MIT) {{{
" Copyright (c) 2017 HiPhish
"
" Permission is hereby granted, free of charge, to any person obtaining a
" copy of this software and associated documentation files (the
" "Software"), to deal in the Software without restriction, including
" without limitation the rights to use, copy, modify, merge, publish,
" distribute, sublicense, and/or sell copies of the Software, and to permit
" persons to whom the Software is furnished to do so, subject to the
" following conditions:
"
" The above copyright notice and this permission notice shall be included
" in all copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
" NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
" DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
" OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
" USE OR OTHER DEALINGS IN THE SOFTWARE.
" }}}
if !has('nvim') || exists('g:repl_nvim')
finish
endif
let g:repl_nvim = 1
" ----------------------------------------------------------------------------
" The ':Repl' command
" ----------------------------------------------------------------------------
" The ':Repl' command is the public interface for end users. The user can
" pass any number of command-line arguments.
command! -complete=file -bang -nargs=* Repl call <SID>repl(<q-mods>, '<bang>', <f-args>)
" ----------------------------------------------------------------------------
" The 's:repl' function is what is actually called by the ':Repl' command
" ----------------------------------------------------------------------------
function! s:repl(mods, bang, ...)
" First we need to determine the REPL type. If there were no arguments
" passed or the first argument is '-' deduce the type from the current
" file type. Otherwise the type is the first argument.
let l:type = ''
if a:0 > 0 && a:1 !=? '-'
if exists('g:repl["'.a:1.'"]')
let l:type = a:1
else
echohl ErrorMsg
echom 'No REPL of type '''.a:1.''' defined'
echohl None
return
endif
else
for l:ft in split(&filetype, '\v\.')
if exists('g:repl["'.l:ft.'"]')
let l:type = l:ft
endif
endfor
if exists('g:repl["'.&filetype.'"]')
let l:type = l:ft
endif
if empty(l:type)
echohl ErrorMsg
echom 'No REPL for current file type defined'
echohl None
return
endif
endif
" If the '!' was not supplied and there is already an instance running
" jump to that instance.
if empty(a:bang) && exists('g:repl["'.l:type.'"].instances') && len(g:repl[l:type].instances) > 0
" Always use the youngest instance
let l:buffer = g:repl[l:type].instances[0].buffer
let l:windows = win_findbuf(l:buffer)
if empty(l:windows)
silent execute a:mods 'new'
silent execute 'buffer' l:buffer
else
call win_gotoid(l:windows[0])
endif
return
endif
" The actual option values to use are determined at runtime. Global
" settings take precedence, so we loop over the global dictionary and
" create local variants of every setting.
"
" After a local variable has been initialised with the global default we
" loop over the lower scopes in a given order. If we encounter the same
" setting it overwrites the previous values. The scopes are ordered by
" ascending significance, with the most significant being last.
for l:key in keys(g:repl[l:type])
silent execute 'let l:'.l:key.' = g:repl[l:type]["'.key.'"]'
for l:scope in ['t', 'w', 'b']
let l:entry = l:scope.':repl["'.l:type.'"]["'.l:key.'"]'
if exists(l:entry)
silent execute 'let l:'.l:key.' = '.l:entry
endif
endfor
endfor
" Append the argument to the command to the argument list (but skip the
" first argument, that is the file type)
let l:args = l:args + a:000[1:]
" Open a new buffer and launch the terminal
silent execute a:mods 'new'
silent execute 'terminal' l:binary join(l:args, ' ')
silent execute 'set syntax='.l:syntax
silent let b:term_title = l:title
" Collect information about this REPL instance
let b:repl = {
\ '-': {
\ 'type' : l:type,
\ 'binary' : l:binary,
\ 'args' : l:args,
\ 'job_id' : b:terminal_job_id,
\ 'buffer' : bufnr('%')
\ }
\ }
" Add This instance to the top of the list of instances
if exists('g:repl["'.l:type.'"].instances')
call insert(g:repl[l:type].instances, b:repl['-'])
else
let g:repl[l:type].instances = [b:repl['-']]
endif
" Hook up autocommand to clean up after the REPL terminates; the
" autocommand is not guaranteed to have access to the b:repl variable,
" that's why we instead use the literal job-id to identify this instance.
silent execute 'au BufDelete <buffer> call <SID>remove_instance('. b:repl['-'].job_id .', "'.l:type.'")'
endfunction