Commit ce0a5268 by HiPhish

Introduce REPL.nvim API functions

Two functions are defined for starters.
parent b50258fb
......@@ -4,26 +4,6 @@
.. default-role:: code
When adding to or removing anything from this file please adjust the date at
the top.
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)
Use a regular buffer
At the moment we are using a terminal buffer, but this is subpar because the
......@@ -36,8 +16,3 @@ 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
" 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.
" }}}
" ----------------------------------------------------------------------------
" Guess the file type based on a file type string.
"
" Arguments:
" a:ft File type string, such as 'python' or 'scheme.guile'
"
" Returns:
" The guessed type
"
" Throws:
" 'nomatch' No matching type was found
" ----------------------------------------------------------------------------
function! repl#guess_type(ft)
let l:fts = split(a:ft, '\v\.')
let l:type = ''
" Start with single types and gradually move to longer types, i.e.
" 'a', 'b', 'c', 'a.b', 'b.c', 'a.b.c'
for l:i in range(0, len(l:fts) - 1)
for l:j in range(0, len(l:fts) - l:i -1)
let l:ft = join(l:fts[l:j : l:j + l:i], '.')
if has_key(g:repl, l:ft)
let l:type = l:ft
endif
endfor
endfor
if empty(l:type)
throw 'nomatch'
endif
return l:type
endfunction
" ----------------------------------------------------------------------------
" Define a new REPL for a given type.
"
" Arguments:
" a:type Type of the REPL to define
" a:repl Dictionary containing the repl information
" a:force Either "keep", "force" or "error"; see third arg of extend()
"
" If the REPL does not exist it is added as a new REPL. If it does exists its
" settings are merged with the new one according to 'a:force'.
" ----------------------------------------------------------------------------
function! repl#define_repl(type, repl, force)
if !has_key(g:repl, a:type)
let g:repl[a:type] = a:repl
return
endif
call extend(g:repl[a:type], a:repl, a:force)
endf
......@@ -15,6 +15,7 @@ License: MIT License
==============================================================================
TABLE OF CONTENTS *repl.nvim-contents*
Part I - User manual~
1. Introduction ...................................... |repl.nvim-intro|
2. Setup ............................................. |repl.nvim-setup|
3. Running a REPL .................................... |repl.nvim-running|
......@@ -26,6 +27,10 @@ TABLE OF CONTENTS *repl.nvim-contents*
7.2 GNU Guile ..................................... |repl.nvim-guile|
7.3 Python ........................................ |repl.nvim-python|
8. Defining new REPLs ................................ |repl.nvim-defining|
9. API ............................................... |repl.nvim-api|
Part II - API reference~
guess_type ........................................... |repl#guess_type()|
==============================================================================
INTRODUCTION *repl.nvim-introduction*
......@@ -49,9 +54,10 @@ RUNNING A REPL *repl.nvim-setup*
:Repl [{type} [{arg} ...]] *:Repl*
To spawn a new REPL instance run the `:Repl` command. The first argument is
the type of REPL, if it is omitted the type will be guessed based on your file
type. For instance, if you are editing a Python file a `'python'` REPL will be
spawned. You can also use modifiers like `:vert`
the type of REPL, if it is omitted the type will be guessed based on the
current |'filetype'| according to |repl#guess_type()|. For instance, if you
are editing a Python file a `'python'` REPL will be spawned. You can also use
modifiers like `:vert`.
If a {type} argument is given that type will be used. However, if the {type}
is `'-'` the type will be guessed again. This allows you to pass arguments to
......@@ -67,12 +73,6 @@ split with arguments you would run
To terminate a REPL either delete the buffer or terminate the REPL the same
way you would if it was a standalone process.
REPL.nvim supports detection of dotted file types like `scheme.guile`. The
individual types are tried from left to right with later matches overriding
earlier ones. Following the individual types, larger compound types are tried
as well from left to right and increasing it size. Example: if the file type
is `a.b.c` the types tried are `a`, `b`, `c`, `a.b`, `b.c` and `a.b.c`.
------------------------------------------------------------------------------
:Repl! [{type} [{arg} ...]] *:Repl!*
......@@ -140,6 +140,9 @@ It is also possible to specify local setting by using one of the scopes `t:`, `w
or `b:`, with later ones taking precedence. The following is a listing of the
default settings.
Note The |repl#define_repl()| function provides a safe way of defining or
modifying the entries of `g:repl`.
------------------------------------------------------------------------------
g:repl['type']['binary'] *g:repl['type']['binary']*
......@@ -248,19 +251,86 @@ environments where `python` defaults to the binary of the environment.
DEFINING NEW REPL TYPES *repl.nvim-setup*
Plugin authors can define new types of REPL to integrate with REPL.nvim as
standalone plugins. You must define a dictionary entry for the new REPL type
that specifies all the necessary information (see |repl.nvim-builtin| for
comaprison). As an example, here is how we can define a Ruby REPL:
standalone plugins. You must define a dictionary for the new REPL type that
specifies all the necessary information (see |repl.nvim-builtin| for
comaprison) and pass it as an argument to |repl#define_repl|. As an example,
here is how we can define a Ruby REPL:
>
let g:repl['ruby'] = {
let ruby_repl = {
\ 'binary': 'irb',
\ 'args': [],
\ 'syntax': 'ruby',
\ 'title': 'Ruby REPL',
\ }
" Throw an error if there is already a Ruby REPL defined
call repl#define_repl('ruby', ruby_repl, 'error')
<
If you want to place this definition inside a file it must be source after the
normal plugin files. One easy way is to put it inside the |after-directory|.
If you want to place this definition inside a file it must be sourced after
the normal plugin files, e.g. by putting it inside the |after-directory|.
==============================================================================
API *repl.nvim-api*
The API provides users with a public interface to control REPL.nvim from a
higher level. You can use the API with your own configuration or to provide
REPL integration with other plugins.
------------------------------------------------------------------------------
repl#guess_type({ft}) *repl#guess_type()*
Tries to guess the type of the REPL based on the file type string {ft}.
Arguments:~
{ft} File type string according to |filetype|
Returns:~
A matching REPL type if one was found.
Throws:~
`'nomatch'` No matching REPL type was found
For atomic types this means looking up {ft} as the key into `g:repl`. For
compound file types (see |'ft'|) a number of combinations are tried; first
{ft} is split on the dots and all the atomic types are tried from left to
right. Then progressively larger combinations of the atomic types are tried
with the complete {ft} string being last. The last successfully matching
combination is returned.
Example If {ft} is `'a.b.c'` the following combinations are tried in this
order: `'a'`, `'b'`, `'c'`, `'a.b'`, `'b.c'`, `'a.b.c'`
As a rule of thumb remember: the more to the right, the more specific the
type, and more complex types take precedence over simpler ones.
------------------------------------------------------------------------------
repl#define_repl({type}, {repl}, {force}) *repl#define_repl()*
Defines a new REPL or extends and existing one. If the {repl} has not yet be
defined it is added to |g:repl| as a new entry. Otherwise the behaviour
depends on the value of the {force} argument.
Arguments:~
{type} The type of the REPL, it will be used as a key into |g:repl|
{repl} Dictionary of settings for the new REPL
{force} Either 'keep', 'force' or 'error'; this argument tells the function
what to do if {type} is already in |g:repl|. The behaviour is the
same as that of the third argument to |extend()|.
Use this function to define a new REPL instead of assigning entries to
|g:repl| manually. A value of 'force' is particularly useful because it allows
overriding existing options:
>
call repl#define_repl('python', {'binary': 'python3'}, 'force')
<
This will change the binary, but leave other options as they are.
------------------------------------------------------------------------------
==============================================================================
vim:tw=78:ts=8:ft=help:norl:
......
......@@ -55,24 +55,15 @@ let s:repl = {
\ 'title': 'Bourne Shell',
\ },
\ }
" ----------------------------------------------------------------------------
" ----------------------------------------------------------------------------
" 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
call repl#define_repl(s:type, s:repl[s:type], 'keep')
endfor
......@@ -29,15 +29,20 @@ let g:repl_nvim = 1
" ----------------------------------------------------------------------------
" The ':Repl' command
" ----------------------------------------------------------------------------
" Definition of 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
"
" a:mods Modifiers to the command like ':vert'
" a:bang Bang attribute to the command, will be empty of no bang
" a:000 Arguments manually passed to the command
" ----------------------------------------------------------------------------
function! s:repl(mods, bang, ...)
" First we need to determine the REPL type. If there were no arguments
......@@ -45,7 +50,7 @@ function! s:repl(mods, bang, ...)
" file type. Otherwise the type is the first argument.
let l:type = ''
if a:0 > 0 && a:1 !=? '-'
if exists('g:repl["'.a:1.'"]')
if has_key(g:repl, a:1)
let l:type = a:1
else
echohl ErrorMsg
......@@ -54,31 +59,19 @@ function! s:repl(mods, bang, ...)
return
endif
else
" First split the file type at the dots, then loop over the list
let l:fts = split(&filetype, '\v\.')
" Start with single types and gradually move to longer types, i.e.
" 'a', 'b', 'c', 'a.b', 'b.c', 'a.b.c'
for l:i in range(0, len(l:fts) - 1)
for l:j in range(0, len(l:fts) - l:i -1)
let l:ft = join(l:fts[l:j : l:j + l:i], '.')
if exists('g:repl["'.l:ft.'"]')
let l:type = l:ft
endif
endfor
endfor
if empty(l:type)
try
let l:type = repl#guess_type(&filetype)
catch
echohl ErrorMsg
echom 'No REPL for current file type defined'
echohl None
return
endif
endtry
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
if empty(a:bang) && has_key(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)
......@@ -93,23 +86,19 @@ function! s:repl(mods, bang, ...)
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.
" The actual option values to use are determined at runtime. Local
" settings take precedence, so we loop over the local scope from lowest to
" highest precedence.
let l:repl = copy(g:repl[l:type])
for l:scope in ['t', 'w', 'b']
let l:local_settings = l:scope.':repl["'.l:type.'"]'
if exists(l:local_settings)
silent execut 'call extend(l:repl, '.l:local_settings.', "force")'
endif
endfor
" If an option is a function reference call it.
for l:key in keys(l:repl)
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:repl["'.l:key.'"] = '.l:entry
endif
endfor
" If the option is a function reference call it
if type(l:repl[l:key]) == type(function('type'))
let l:repl[l:key] = l:repl[l:key]()
endif
......@@ -137,7 +126,7 @@ function! s:repl(mods, bang, ...)
\ }
" Add This instance to the top of the list of instances
if exists('g:repl["'.l:type.'"].instances')
if has_key(g:repl[l:type], 'instances')
call insert(g:repl[l:type].instances, b:repl['-'])
else
let g:repl[l:type].instances = [b:repl['-']]
......@@ -149,7 +138,13 @@ function! s:repl(mods, bang, ...)
silent execute 'au BufDelete <buffer> call <SID>remove_instance('. b:repl['-'].job_id .', "'.l:type.'")'
endfunction
" ----------------------------------------------------------------------------
" Remove an instance from the global list of instances
"
" - a:job_id Job ID of the REPL process, used to find the REPL instance
" - a:type The type of REPL
" ----------------------------------------------------------------------------
function! s:remove_instance(job_id, type)
for i in range(len(g:repl[a:type].instances))
if g:repl[a:type].instances[i].job_id == a:job_id
......
################
# The REPL API #
################
# This test file is intended for testing the REPL.nvim API in isolation from
# the ':Repl' command.
# Cleaning up the global settings ensures that user-specific settings do not
# interfere with testing.
Before (Clean up the g:repl variable):
let g:repl = {}
#--- repl#guess_type ---------------------------------------------------------
Execute (Dotted file type, single type):
let g:repl['c'] = {'binary': '', 'args': [], 'syntax': '', 'title': ''}
Then (Type 'c' is recognised):
AssertEqual repl#guess_type('a.b.c'), 'c'
Execute (Dotted file type, compound type):
let g:repl['b.c'] = {'binary': '', 'args': [], 'syntax': '', 'title': ''}
Then (Type 'b.c' is recognised):
AssertEqual repl#guess_type('a.b.c'), 'b.c'
Execute (Non-existing type):
Then (An error is thrown):
AssertThrows call repl#guess_type('foo')
#-- repl#define_repl ---------------------------------------------------------
Execute (Defining a new REPL):
let foo = {'binary': '', 'args': [], 'syntax': '', 'title': ''}
call repl#define_repl('foo', foo, 'keep')
Then (The new entry is in g:repl):
AssertEqual g:repl.foo, foo
Execute (Keeping settings of an existing REPL):
let g:repl['foo'] = {'binary': '', 'args': [], 'syntax': '', 'title': ''}
call repl#define_repl('foo', {'binary': 'bar'}, 'keep')
Then (The settings are unchanged):
AssertEqual g:repl.foo.binary, ''
Execute (Forcing new settings onto an existing REPL):
let g:repl['foo'] = {'binary': '', 'args': [], 'syntax': '', 'title': ''}
call repl#define_repl('foo', {'binary': 'bar'}, 'force')
Then (The settings are unchanged):
AssertEqual g:repl.foo.binary, 'bar'
Execute (Throwing an error when re-defining a REPL):
let g:repl['foo'] = {'binary': '', 'args': [], 'syntax': '', 'title': ''}
Then (The settings are unchanged):
AssertThrows call repl#define_repl('foo', {'binary', ''}, 'error')
......@@ -21,8 +21,8 @@
# USE OR OTHER DEALINGS IN THE SOFTWARE.
# This is a mock version of Guile. It is not interactive, it only serves for
# testing the Guile-REPL plugin.
# This is a mock version of a REPL. It is not interactive, it only serves for
# testing the REPL.nvim plugin.
EOF=false
......
......@@ -31,39 +31,6 @@ Then (A terminal buffer with settings set):
#-----------------------------------------------------------------------------
Execute (Dotted file type, single type):
set ft=a.b.c
" Remove false positives just in case they exist
if exists('g:repl["a"]')
remove(g:repl, 'a')
endif
if exists('g:repl["b"]')
remove(g:repl, 'b')
endif
let g:repl['c'] = {'binary': '', 'args': [], 'syntax': '', 'title': ''}
silent Repl
Then (Type 'c' is recognised):
AssertEqual b:repl['-'].type, 'c'
quit
#-----------------------------------------------------------------------------
Execute (Dotted file type, compound type):
set ft=a.b.c
" Remove false positives just in case they exist
if exists('g:repl["a.b"]')
remove(g:repl, 'a.b')
endif
let g:repl['b.c'] = {'binary': '', 'args': [], 'syntax': '', 'title': ''}
silent Repl
Then (Type 'b.c' is recognised):
AssertEqual b:repl['-'].type, 'b.c'
quit
#-----------------------------------------------------------------------------
Execute (User-supplied arguments):
silent Repl - foo bar
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment