feat(lua)!: execute Lua with "nvim -l"

Problem:
Nvim has Lua but the "nvim" CLI can't easily be used to execute Lua
scripts, especially scripts that take arguments or produce output.

Solution:
- support "nvim -l [args...]" for running scripts. closes #15749
- exit without +q
- remove lua2dox_filter
- remove Doxyfile. This wasn't used anyway, because the doxygen config
  is inlined in gen_vimdoc.py (`Doxyfile` variable).
- use "nvim -l" in docs-gen CI job

Examples:

    $ nvim -l scripts/lua2dox.lua --help
    Lua2DoX (0.2 20130128)
    ...

    $ echo "print(vim.inspect(_G.arg))" | nvim -l - --arg1 --arg2
    $ echo 'print(vim.inspect(vim.api.nvim_buf_get_text(1,0,0,-1,-1,{})))' | nvim +"put ='text'" -l -

TODO?
  -e executes Lua code
  -l loads a module
  -i enters REPL _after running the other arguments_.
This commit is contained in:
Justin M. Keyes
2021-09-20 19:00:50 -07:00
parent 39d70fcafd
commit 7c94bcd2d7
16 changed files with 311 additions and 2784 deletions

View File

@@ -205,6 +205,8 @@ jobs:
env: env:
CC: ${{ matrix.cc }} CC: ${{ matrix.cc }}
CI_OS_NAME: ${{ matrix.os }} CI_OS_NAME: ${{ matrix.os }}
# TEST_FILE: test/functional/core/startup_spec.lua
# TEST_FILTER: foo
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -281,6 +283,8 @@ jobs:
DEPS_BUILD_DIR: ${{ github.workspace }}/nvim-deps DEPS_BUILD_DIR: ${{ github.workspace }}/nvim-deps
CACHE_NVIM_DEPS_DIR: ${{ github.workspace }}/nvim-deps CACHE_NVIM_DEPS_DIR: ${{ github.workspace }}/nvim-deps
DEPS_PREFIX: ${{ github.workspace }}/nvim-deps/usr DEPS_PREFIX: ${{ github.workspace }}/nvim-deps/usr
# TEST_FILE: test/functional/core/startup_spec.lua
# TEST_FILTER: foo
name: windows (MSVC_64) name: windows (MSVC_64)
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@@ -116,10 +116,7 @@ Each pull request must pass the automated builds on [Cirrus CI] and [GitHub Acti
- CI builds are compiled with [`-Werror`][gcc-warnings], so compiler warnings - CI builds are compiled with [`-Werror`][gcc-warnings], so compiler warnings
will fail the build. will fail the build.
- If any tests fail, the build will fail. - If any tests fail, the build will fail. See [test/README.md#running-tests][run-tests] to run tests locally.
See [test/README.md#running-tests][run-tests] to run tests locally.
Passing locally doesn't guarantee passing the CI build, because of the
different compilers and platforms tested against.
- CI runs [ASan] and other analyzers. - CI runs [ASan] and other analyzers.
- To run valgrind locally: `VALGRIND=1 make test` - To run valgrind locally: `VALGRIND=1 make test`
- To run Clang ASan/UBSan locally: `CC=clang make CMAKE_FLAGS="-DCLANG_ASAN_UBSAN=ON"` - To run Clang ASan/UBSan locally: `CC=clang make CMAKE_FLAGS="-DCLANG_ASAN_UBSAN=ON"`
@@ -127,6 +124,8 @@ Each pull request must pass the automated builds on [Cirrus CI] and [GitHub Acti
neighbors_, to encourage incrementally updating the legacy style to meet our neighbors_, to encourage incrementally updating the legacy style to meet our
[style](#style). (See [#3174][3174] for background.) [style](#style). (See [#3174][3174] for background.)
- CI for FreeBSD runs on [Cirrus CI]. - CI for FreeBSD runs on [Cirrus CI].
- To see CI results faster in your PR, you can temporarily set `TEST_FILE` in
[ci.yml](https://github.com/neovim/neovim/blob/e35b9020b16985eee26e942f9a3f6b045bc3809b/.github/workflows/ci.yml#L205).
### Clang scan-build ### Clang scan-build

View File

@@ -21,15 +21,19 @@ Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the
which can be used from Lua code (|lua-vimscript| |vim.api|). Together these which can be used from Lua code (|lua-vimscript| |vim.api|). Together these
"namespaces" form the Nvim programming interface. "namespaces" form the Nvim programming interface.
See the |lua-guide| for an introduction to using Lua in Neovim. Lua plugins and user config are automatically discovered and loaded, just like
Vimscript. See |lua-guide| for practical guidance.
You can also run Lua scripts from your shell using the |-l| argument: >
nvim -l foo.lua [args...]
<
*lua-compat* *lua-compat*
Lua 5.1 is the permanent interface for Nvim Lua. Plugins need only consider Lua 5.1 is the permanent interface for Nvim Lua. Plugins need only consider
Lua 5.1, not worry about forward-compatibility with future Lua versions. If Lua 5.1, not worry about forward-compatibility with future Lua versions. If
Nvim ever ships with Lua 5.4+, a Lua 5.1 compatibility shim will be provided Nvim ever ships with Lua 5.4+, a Lua 5.1 compatibility shim will be provided
so that old plugins continue to work transparently. so that old plugins continue to work transparently.
------------------------------------------------------------------------------ ==============================================================================
LUA CONCEPTS AND IDIOMS *lua-concepts* LUA CONCEPTS AND IDIOMS *lua-concepts*
Lua is very simple: this means that, while there are some quirks, once you Lua is very simple: this means that, while there are some quirks, once you
@@ -73,25 +77,24 @@ In Lua, any missing arguments are passed as `nil`. Example: >lua
Furthermore it is not an error if extra parameters are passed, they are just Furthermore it is not an error if extra parameters are passed, they are just
discarded. discarded.
It is also allowed to omit the parentheses (only) if the function takes *kwargs*
exactly one string (`"foo"`) or table literal (`{1,2,3}`). The latter is often When calling a function, you can omit the parentheses if the function takes
used to approximate the "named parameters" feature of languages like Python exactly one string literal (`"foo"`) or table literal (`{1,2,3}`). The latter
("kwargs" or "keyword args"). Example: >lua is often used to approximate "named parameters" ("kwargs" or "keyword args")
as in languages like Python and C#. Example: >lua
local func_with_opts = function(opts) local func_with_opts = function(opts)
local will_do_foo = opts.foo local will_do_foo = opts.foo
local filename = opts.filename local filename = opts.filename
... ...
end end
func_with_opts { foo = true, filename = "hello.world" } func_with_opts { foo = true, filename = "hello.world" }
< <
There is nothing special going on here except that parentheses are treated as There's nothing special going on here except that parentheses are treated as
whitespace. But visually, this small bit of sugar gets reasonably close to whitespace. But visually, this small bit of sugar gets reasonably close to
a "keyword args" interface. a "keyword args" interface.
It is of course also valid to call the function with parentheses: >lua It is of course also valid to call the function with parentheses: >lua
func_with_opts({ foo = true, filename = "hello.world" }) func_with_opts({ foo = true, filename = "hello.world" })
< <
Nvim tends to prefer the keyword args style. Nvim tends to prefer the keyword args style.
@@ -100,25 +103,20 @@ Nvim tends to prefer the keyword args style.
LUA PATTERNS *lua-patterns* LUA PATTERNS *lua-patterns*
Lua intentionally does not support regular expressions, instead it has limited Lua intentionally does not support regular expressions, instead it has limited
"patterns" which avoid the performance pitfalls of extended regex. "patterns" |luaref-patterns| which avoid the performance pitfalls of extended
|luaref-patterns| regex. Lua scripts can also use Vim regex via |vim.regex()|.
Examples using |string.match()|: >lua These examples use |string.match()| to demonstrate Lua patterns: >lua
print(string.match("foo123bar123", "%d+")) print(string.match("foo123bar123", "%d+"))
-- 123 -- 123
print(string.match("foo123bar123", "[^%d]+")) print(string.match("foo123bar123", "[^%d]+"))
-- foo -- foo
print(string.match("foo123bar123", "[abc]+")) print(string.match("foo123bar123", "[abc]+"))
-- ba -- ba
print(string.match("foo.bar", "%.bar")) print(string.match("foo.bar", "%.bar"))
-- .bar -- .bar
For more complex matching you can use Vim regex from Lua via |vim.regex()|.
============================================================================== ==============================================================================
IMPORTING LUA MODULES *require()* *lua-require* IMPORTING LUA MODULES *require()* *lua-require*
@@ -389,7 +387,7 @@ For example consider the following Lua omnifunc handler: >lua
vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omnifunc') vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omnifunc')
Note: The module ("mymod" in the above example) must either be a Lua global, Note: The module ("mymod" in the above example) must either be a Lua global,
or use the require syntax as specified above to access it from a package. or use require() as shown above to access it from a package.
Note: `v:lua` without a call is not allowed in a Vimscript expression: Note: `v:lua` without a call is not allowed in a Vimscript expression:
|Funcref|s cannot represent Lua functions. The following are errors: >vim |Funcref|s cannot represent Lua functions. The following are errors: >vim

View File

@@ -57,6 +57,11 @@ The following new APIs or features were added.
< <
(or the Vimscript equivalent) to their |config| file. (or the Vimscript equivalent) to their |config| file.
• Run Lua scripts from your shell using |-l|. >
nvim -l foo.lua --arg1 --arg2
< Also works with stdin: >
echo "print(42)" | nvim -l -
• Added a |vim.lsp.codelens.clear()| function to clear codelenses. • Added a |vim.lsp.codelens.clear()| function to clear codelenses.
• |vim.inspect_pos()|, |vim.show_pos()| and |:Inspect| allow a user to get or show items • |vim.inspect_pos()|, |vim.show_pos()| and |:Inspect| allow a user to get or show items

View File

@@ -9,7 +9,7 @@ Starting Vim *starting*
Type |gO| to see the table of contents. Type |gO| to see the table of contents.
============================================================================== ==============================================================================
Nvim arguments *vim-arguments* Nvim arguments *cli-arguments*
Most often, Nvim is started to edit a single file with the command: > Most often, Nvim is started to edit a single file with the command: >
@@ -31,8 +31,8 @@ filename One or more file names. The first one will be the current
To avoid a file name starting with a '-' being interpreted as To avoid a file name starting with a '-' being interpreted as
an option, precede the arglist with "--", e.g.: > an option, precede the arglist with "--", e.g.: >
nvim -- -filename nvim -- -filename
< All arguments after the "--" will be interpreted as file names, < All arguments after "--" are interpreted as file names, no
no other options or "+command" argument can follow. other options or "+command" argument can follow.
*--* *--*
`-` Alias for stdin (standard input). `-` Alias for stdin (standard input).
@@ -143,15 +143,13 @@ argument.
these commands, independently from "-c" commands. these commands, independently from "-c" commands.
*-S* *-S*
-S {file} Vimscript or Lua (".lua") {file} will be |:source|d after the -S [file] Vimscript or Lua (".lua") [file] will be |:source|d after the
first file has been read. Equivalent to: > first file has been read or "Session.vim" if [file] is not
given. Equivalent to: >
-c "source {file}" -c "source {file}"
< Can be repeated like "-c", subject to the same limit of 10 < Can be repeated like "-c", subject to the same limit of 10
"-c" arguments. {file} cannot start with a "-". "-c" arguments. {file} cannot start with a "-".
-S Works like "-S Session.vim". Only when used as the last
argument or when another "-" option follows.
-L *-L* *-r* -L *-L* *-r*
-r Recovery mode. Without a file name argument, a list of -r Recovery mode. Without a file name argument, a list of
existing swap files is given. With a file name, a swap file existing swap files is given. With a file name, a swap file
@@ -211,10 +209,30 @@ argument.
nvim -es +":verbose echo 'foo'" nvim -es +":verbose echo 'foo'"
nvim -V1 -es +foo nvim -V1 -es +foo
< User |config| is skipped (unless given with |-u|). < User |config| is skipped unless |-u| was given.
Swap file is skipped (like |-n|). Swap file is skipped (like |-n|).
User |shada| is loaded (unless "-i NONE" is given). User |shada| is loaded (unless "-i NONE" is given).
*-l*
-l {script} [args]
Executes Lua {script} file and exits. All [args] (up to "--"
|---|) are treated as {script} args, not Nvim args: by Lua
convention they are set in the `_G.arg` global table. *lua-args*
On {script} error, Nvim exits with code 1.
Sets 'verbose' to 1 (like "-V1"), so Lua `print()` writes to
output.
Any |cli-arguments| before "-l" are processed before executing
{script}. For example this quits before executing "foo.lua": >
nvim +q -l foo.lua
< This loads Lua module "bar" before executing "foo.lua": >
nvim +"lua require('bar')" -l foo.lua
<
User |config| is skipped unless |-u| was given.
User |shada| is skipped unless |-i| was given.
Swap file is skipped (like |-n|).
*-b* *-b*
-b Binary mode. File I/O will only recognize <NL> to separate -b Binary mode. File I/O will only recognize <NL> to separate
lines. The 'expandtab' option will be reset. The 'textwidth' lines. The 'expandtab' option will be reset. The 'textwidth'
@@ -222,9 +240,6 @@ argument.
is set. This is done after reading the |vimrc| but before is set. This is done after reading the |vimrc| but before
reading any file in the arglist. See also |edit-binary|. reading any file in the arglist. See also |edit-binary|.
*-l*
-l Lisp mode. Sets the 'lisp' and 'showmatch' options on.
*-A* *-A*
-A Arabic mode. Sets the 'arabic' option on. -A Arabic mode. Sets the 'arabic' option on.
@@ -239,10 +254,10 @@ argument.
Example: > Example: >
nvim -V8 nvim -V8
-V[N]{filename} -V[N]{file}
Like -V and set 'verbosefile' to {filename}. Messages are not Like -V and sets 'verbosefile' to {file} (must not start with
displayed; instead they are written to the file {filename}. a digit). Messages are not displayed; instead they are
{filename} must not start with a digit. written to {file}.
Example: > Example: >
nvim -V20vimlog nvim -V20vimlog
< <

View File

@@ -55,11 +55,19 @@ if sys.version_info < MIN_PYTHON_VERSION:
doxygen_version = tuple((int(i) for i in subprocess.check_output(["doxygen", "-v"], doxygen_version = tuple((int(i) for i in subprocess.check_output(["doxygen", "-v"],
universal_newlines=True).split()[0].split('.'))) universal_newlines=True).split()[0].split('.')))
# Until 0.9 is released, need this hacky way to check that "nvim -l foo.lua" works.
nvim_version = list(line for line in subprocess.check_output(['nvim', '-h'], universal_newlines=True).split('\n')
if '-l ' in line)
if doxygen_version < MIN_DOXYGEN_VERSION: if doxygen_version < MIN_DOXYGEN_VERSION:
print("\nRequires doxygen {}.{}.{}+".format(*MIN_DOXYGEN_VERSION)) print("\nRequires doxygen {}.{}.{}+".format(*MIN_DOXYGEN_VERSION))
print("Your doxygen version is {}.{}.{}\n".format(*doxygen_version)) print("Your doxygen version is {}.{}.{}\n".format(*doxygen_version))
sys.exit(1) sys.exit(1)
if len(nvim_version) == 0:
print("\nRequires 'nvim -l' feature, see https://github.com/neovim/neovim/pull/18706")
sys.exit(1)
# DEBUG = ('DEBUG' in os.environ) # DEBUG = ('DEBUG' in os.environ)
INCLUDE_C_DECL = ('INCLUDE_C_DECL' in os.environ) INCLUDE_C_DECL = ('INCLUDE_C_DECL' in os.environ)
INCLUDE_DEPRECATED = ('INCLUDE_DEPRECATED' in os.environ) INCLUDE_DEPRECATED = ('INCLUDE_DEPRECATED' in os.environ)
@@ -79,7 +87,7 @@ base_dir = os.path.dirname(os.path.dirname(script_path))
out_dir = os.path.join(base_dir, 'tmp-{target}-doc') out_dir = os.path.join(base_dir, 'tmp-{target}-doc')
filter_cmd = '%s %s' % (sys.executable, script_path) filter_cmd = '%s %s' % (sys.executable, script_path)
msgs = [] # Messages to show on exit. msgs = [] # Messages to show on exit.
lua2dox_filter = os.path.join(base_dir, 'scripts', 'lua2dox_filter') lua2dox = os.path.join(base_dir, 'scripts', 'lua2dox.lua')
CONFIG = { CONFIG = {
'api': { 'api': {
@@ -993,7 +1001,7 @@ def delete_lines_below(filename, tokenstr):
fp.writelines(lines[0:i]) fp.writelines(lines[0:i])
def main(config, args): def main(doxygen_config, args):
"""Generates: """Generates:
1. Vim :help docs 1. Vim :help docs
@@ -1021,7 +1029,7 @@ def main(config, args):
# runtime/lua/vim/lsp.lua:209: warning: argument 'foo' not found # runtime/lua/vim/lsp.lua:209: warning: argument 'foo' not found
stderr=(subprocess.STDOUT if debug else subprocess.DEVNULL)) stderr=(subprocess.STDOUT if debug else subprocess.DEVNULL))
p.communicate( p.communicate(
config.format( doxygen_config.format(
input=' '.join( input=' '.join(
[f'"{file}"' for file in CONFIG[target]['files']]), [f'"{file}"' for file in CONFIG[target]['files']]),
output=output_dir, output=output_dir,
@@ -1108,11 +1116,7 @@ def main(config, args):
fn_map_full.update(fn_map) fn_map_full.update(fn_map)
if len(sections) == 0: if len(sections) == 0:
if target == 'lua': fail(f'no sections for target: {target} (look for errors near "Preprocessing" log lines above)')
fail(f'no sections for target: {target} (this usually means'
+ ' "luajit" was not found by scripts/lua2dox_filter)')
else:
fail(f'no sections for target: {target}')
if len(sections) > len(CONFIG[target]['section_order']): if len(sections) > len(CONFIG[target]['section_order']):
raise RuntimeError( raise RuntimeError(
'found new modules "{}"; update the "section_order" map'.format( 'found new modules "{}"; update the "section_order" map'.format(
@@ -1159,7 +1163,7 @@ def main(config, args):
def filter_source(filename): def filter_source(filename):
name, extension = os.path.splitext(filename) name, extension = os.path.splitext(filename)
if extension == '.lua': if extension == '.lua':
p = subprocess.run([lua2dox_filter, filename], stdout=subprocess.PIPE) p = subprocess.run(['nvim', '-l', lua2dox, filename], stdout=subprocess.PIPE)
op = ('?' if 0 != p.returncode else p.stdout.decode('utf-8')) op = ('?' if 0 != p.returncode else p.stdout.decode('utf-8'))
print(op) print(op)
else: else:

View File

@@ -27,14 +27,13 @@ http://search.cpan.org/~alec/Doxygen-Lua-0.02/lib/Doxygen/Lua.pm
Running Running
------- -------
This file "lua2dox.lua" gets called by "lua2dox_filter" (bash). This script "lua2dox.lua" gets called by "gen_vimdoc.py".
Doxygen must be on your system. You can experiment like so: Doxygen must be on your system. You can experiment like so:
- Run "doxygen -g" to create a default Doxyfile. - Run "doxygen -g" to create a default Doxyfile.
- Then alter it to let it recognise lua. Add the two following lines: - Then alter it to let it recognise lua. Add the following line:
FILE_PATTERNS = *.lua FILE_PATTERNS = *.lua
FILTER_PATTERNS = *.lua=lua2dox_filter
- Then run "doxygen". - Then run "doxygen".
The core function reads the input file (filename or stdin) and outputs some pseudo C-ish language. The core function reads the input file (filename or stdin) and outputs some pseudo C-ish language.
@@ -117,26 +116,6 @@ local function string_split(Str, Pattern)
return splitStr return splitStr
end end
--! \class TCore_Commandline
--! \brief reads/parses commandline
local TCore_Commandline = class()
--! \brief constructor
function TCore_Commandline.init(this)
this.argv = arg
this.parsed = {}
this.params = {}
end
--! \brief get value
function TCore_Commandline.getRaw(this, Key, Default)
local val = this.argv[Key]
if not val then
val = Default
end
return val
end
------------------------------- -------------------------------
--! \brief file buffer --! \brief file buffer
--! --!
@@ -147,7 +126,7 @@ local TStream_Read = class()
--! --!
--! \param Filename name of file to read (or nil == stdin) --! \param Filename name of file to read (or nil == stdin)
function TStream_Read.getContents(this, Filename) function TStream_Read.getContents(this, Filename)
assert(Filename) assert(Filename, ('invalid file: %s'):format(Filename))
-- get lines from file -- get lines from file
-- syphon lines to our table -- syphon lines to our table
local filecontents = {} local filecontents = {}
@@ -548,15 +527,14 @@ end
local This_app = TApp() local This_app = TApp()
--main --main
local cl = TCore_Commandline()
local argv1 = cl:getRaw(2) local argv1 = arg[1]
if argv1 == '--help' then if argv1 == '--help' then
TCore_IO_writeln(This_app:getVersion()) TCore_IO_writeln(This_app:getVersion())
TCore_IO_writeln(This_app:getCopyright()) TCore_IO_writeln(This_app:getCopyright())
TCore_IO_writeln([[ TCore_IO_writeln([[
run as: run as:
lua2dox_filter <param> nvim -l scripts/lua2dox.lua <param>
-------------- --------------
Param: Param:
<filename> : interprets filename <filename> : interprets filename

View File

@@ -1,90 +0,0 @@
#!/usr/bin/env bash
###########################################################################
# Copyright (C) 2012 by Simon Dales #
# simon@purrsoft.co.uk #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; either version 2 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the #
# Free Software Foundation, Inc., #
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #
###########################################################################
LANG=""
##! \brief test executable to see if it exists
test_executable() {
P_EXE="$1"
#########
WHICH=$(which "$P_EXE")
if test -z "${WHICH}"; then
echo "not found \"${P_EXE}\""
else
EXE="${P_EXE}"
fi
}
##! \brief sets the lua interpreter
set_lua() {
if test -z "${EXE}"; then
test_executable '.deps/usr/bin/luajit'
fi
if test -z "${EXE}"; then
test_executable 'luajit'
fi
if test -z "${EXE}"; then
test_executable 'lua'
fi
}
##! \brief makes canonical name of file
##!
##! Note that "readlink -f" doesn't work in MacOSX
##!
do_readlink() {
pushd . > /dev/null
TARGET_FILE=$1
cd "$(dirname $TARGET_FILE)"
TARGET_FILE=$(basename "$TARGET_FILE")
# Iterate down a (possible) chain of symlinks
while [ -L "$TARGET_FILE" ]; do
TARGET_FILE=$(readlink "$TARGET_FILE")
cd $(dirname "$TARGET_FILE")
TARGET_FILE=$(basename "$TARGET_FILE")
done
PHYS_DIR=$(pwd -P)
RESULT=$PHYS_DIR
popd > /dev/null
}
##main
set_lua
if test -z "${EXE}"; then
echo "no lua interpreter found"
exit 1
else
BASENAME=$(basename "$0")
do_readlink "$0"
DIRNAME="${RESULT}"
LUASCRIPT="${DIRNAME}/lua2dox.lua ${BASENAME}"
#echo "lua[${LUASCRIPT}]"
${EXE} ${LUASCRIPT} $@
fi
##eof

File diff suppressed because it is too large Load Diff

View File

@@ -515,7 +515,7 @@ EXTERN int allbuf_lock INIT(= 0);
/// not allowed then. /// not allowed then.
EXTERN int sandbox INIT(= 0); EXTERN int sandbox INIT(= 0);
/// Batch-mode: "-es" or "-Es" commandline argument was given. /// Batch-mode: "-es", "-Es", "-l" commandline argument was given.
EXTERN int silent_mode INIT(= false); EXTERN int silent_mode INIT(= false);
/// Start position of active Visual selection. /// Start position of active Visual selection.

View File

@@ -323,6 +323,34 @@ static int nlua_thr_api_nvim__get_runtime(lua_State *lstate)
return 1; return 1;
} }
/// Copies all args into the Lua `arg` global.
///
/// Example:
/// nvim -l foo.lua -- -e "sin=math.sin" script a b
///
/// @note `lua` CLI sets _negative_ `arg` indices to the arguments upto "-e".
///
/// @see https://www.lua.org/pil/1.4.html
/// @see https://github.com/premake/premake-core/blob/1c1304637f4f5e50ba8c57aae8d1d80ec3b7aaf2/src/host/premake.c#L563-L594
///
/// @returns number of args (stops at "--")
int nlua_set_argv(char **argv, int argc)
{
lua_State *const L = global_lstate;
lua_newtable(L);
int i = 0;
for (; i < argc; i++) {
if (strequal("--", argv[i])) {
i--;
break;
}
lua_pushstring(L, argv[i]);
lua_rawseti(L, -2, i + 1);
}
lua_setglobal(L, "arg");
return i + 1;
}
static void nlua_schedule_event(void **argv) static void nlua_schedule_event(void **argv)
{ {
LuaRef cb = (LuaRef)(ptrdiff_t)argv[0]; LuaRef cb = (LuaRef)(ptrdiff_t)argv[0];

View File

@@ -275,14 +275,14 @@ int main(int argc, char **argv)
// Check if we have an interactive window. // Check if we have an interactive window.
check_and_set_isatty(&params); check_and_set_isatty(&params);
// TODO: should we try to keep param scan before this?
nlua_init();
TIME_MSG("init lua interpreter");
// Process the command line arguments. File names are put in the global // Process the command line arguments. File names are put in the global
// argument list "global_alist". // argument list "global_alist".
command_line_scan(&params); command_line_scan(&params);
nlua_init();
TIME_MSG("init lua interpreter");
if (embedded_mode) { if (embedded_mode) {
const char *err; const char *err;
if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) {
@@ -363,8 +363,7 @@ int main(int argc, char **argv)
debug_break_level = params.use_debug_break_level; debug_break_level = params.use_debug_break_level;
// Read ex-commands if invoked with "-es". // Read ex-commands if invoked with "-es".
if (!params.input_isatty && !params.input_neverscript if (!params.input_isatty && !params.input_istext && silent_mode && exmode_active) {
&& silent_mode && exmode_active) {
input_start(STDIN_FILENO); input_start(STDIN_FILENO);
} }
@@ -409,14 +408,12 @@ int main(int argc, char **argv)
init_default_autocmds(); init_default_autocmds();
TIME_MSG("init default autocommands"); TIME_MSG("init default autocommands");
bool vimrc_none = params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE"); bool vimrc_none = strequal(params.use_vimrc, "NONE");
// Reset 'loadplugins' for "-u NONE" before "--cmd" arguments. // Reset 'loadplugins' for "-u NONE" before "--cmd" arguments.
// Allows for setting 'loadplugins' there. // Allows for setting 'loadplugins' there.
if (vimrc_none) { // For --clean we still want to load plugins.
// When using --clean we still want to load plugins p_lpl = vimrc_none ? params.clean : p_lpl;
p_lpl = params.clean;
}
// Execute --cmd arguments. // Execute --cmd arguments.
exe_pre_commands(&params); exe_pre_commands(&params);
@@ -610,6 +607,11 @@ int main(int argc, char **argv)
(void)eval_has_provider("clipboard"); (void)eval_has_provider("clipboard");
} }
if (params.luaf != NULL) {
nlua_exec_file(params.luaf);
// return 0;
}
TIME_MSG("before starting main loop"); TIME_MSG("before starting main loop");
ILOG("starting main loop"); ILOG("starting main loop");
@@ -962,7 +964,7 @@ static bool edit_stdin(mparm_T *parmp)
{ {
bool implicit = !headless_mode bool implicit = !headless_mode
&& !(embedded_mode && stdin_fd <= 0) && !(embedded_mode && stdin_fd <= 0)
&& (!exmode_active || parmp->input_neverscript) && (!exmode_active || parmp->input_istext)
&& !parmp->input_isatty && !parmp->input_isatty
&& parmp->scriptin == NULL; // `-s -` was not given. && parmp->scriptin == NULL; // `-s -` was not given.
return parmp->had_stdin_file || implicit; return parmp->had_stdin_file || implicit;
@@ -993,9 +995,9 @@ static void command_line_scan(mparm_T *parmp)
} else { } else {
parmp->commands[parmp->n_commands++] = &(argv[0][1]); parmp->commands[parmp->n_commands++] = &(argv[0][1]);
} }
// Optional argument.
} else if (argv[0][0] == '-' && !had_minmin) { } else if (argv[0][0] == '-' && !had_minmin) {
// Optional argument.
want_argument = false; want_argument = false;
char c = argv[0][argv_idx++]; char c = argv[0][argv_idx++];
switch (c) { switch (c) {
@@ -1005,9 +1007,7 @@ static void command_line_scan(mparm_T *parmp)
silent_mode = true; silent_mode = true;
parmp->no_swap_file = true; parmp->no_swap_file = true;
} else { } else {
if (parmp->edit_type != EDIT_NONE if (parmp->edit_type > EDIT_STDIN) {
&& parmp->edit_type != EDIT_FILE
&& parmp->edit_type != EDIT_STDIN) {
mainerr(err_too_many_args, argv[0]); mainerr(err_too_many_args, argv[0]);
} }
parmp->had_stdin_file = true; parmp->had_stdin_file = true;
@@ -1015,7 +1015,7 @@ static void command_line_scan(mparm_T *parmp)
} }
argv_idx = -1; // skip to next argument argv_idx = -1; // skip to next argument
break; break;
case '-': // "--" don't take any more option arguments case '-': // "--" No more option arguments.
// "--help" give help message // "--help" give help message
// "--version" give version message // "--version" give version message
// "--noplugin[s]" skip plugins // "--noplugin[s]" skip plugins
@@ -1111,7 +1111,7 @@ static void command_line_scan(mparm_T *parmp)
break; break;
case 'E': // "-E" Ex mode case 'E': // "-E" Ex mode
exmode_active = true; exmode_active = true;
parmp->input_neverscript = true; parmp->input_istext = true;
break; break;
case 'f': // "-f" GUI: run in foreground. case 'f': // "-f" GUI: run in foreground.
break; break;
@@ -1123,10 +1123,6 @@ static void command_line_scan(mparm_T *parmp)
p_hkmap = true; p_hkmap = true;
set_option_value_give_err("rl", 1L, NULL, 0); set_option_value_give_err("rl", 1L, NULL, 0);
break; break;
case 'l': // "-l" lisp mode, 'lisp' and 'showmatch' on.
set_option_value_give_err("lisp", 1L, NULL, 0);
p_sm = true;
break;
case 'M': // "-M" no changes or writing of files case 'M': // "-M" no changes or writing of files
reset_modifiable(); reset_modifiable();
FALLTHROUGH; FALLTHROUGH;
@@ -1231,6 +1227,7 @@ static void command_line_scan(mparm_T *parmp)
FALLTHROUGH; FALLTHROUGH;
case 'S': // "-S {file}" execute Vim script case 'S': // "-S {file}" execute Vim script
case 'i': // "-i {shada}" use for ShaDa file case 'i': // "-i {shada}" use for ShaDa file
case 'l': // "-l {file}" Lua mode
case 'u': // "-u {vimrc}" vim inits file case 'u': // "-u {vimrc}" vim inits file
case 'U': // "-U {gvimrc}" gvim inits file case 'U': // "-U {gvimrc}" gvim inits file
case 'W': // "-W {scriptout}" overwrite case 'W': // "-W {scriptout}" overwrite
@@ -1311,6 +1308,27 @@ static void command_line_scan(mparm_T *parmp)
set_option_value_give_err("shadafile", 0L, argv[0], 0); set_option_value_give_err("shadafile", 0L, argv[0], 0);
break; break;
case 'l': // "-l" Lua script: args after "-l".
silent_mode = true;
p_verbose = 1;
parmp->no_swap_file = true;
parmp->use_vimrc = parmp->use_vimrc ? parmp->use_vimrc : "NONE";
if (p_shadafile == NULL || *p_shadafile == NUL) {
set_option_value_give_err("shadafile", 0L, "NONE", 0);
}
parmp->luaf = argv[0];
argc--;
argv++;
// Lua args after "-l <file>" (upto "--").
int l_argc = nlua_set_argv(argv, argc);
assert(l_argc >= 0);
argc = argc - l_argc;
if (argc > 0) { // Found "--".
argv = argv + l_argc;
had_minmin = true;
}
break;
case 's': // "-s {scriptin}" read from script file case 's': // "-s {scriptin}" read from script file
if (parmp->scriptin != NULL) { if (parmp->scriptin != NULL) {
scripterror: scripterror:
@@ -1354,9 +1372,7 @@ scripterror:
argv_idx = -1; // skip to next argument argv_idx = -1; // skip to next argument
// Check for only one type of editing. // Check for only one type of editing.
if (parmp->edit_type != EDIT_NONE if (parmp->edit_type > EDIT_STDIN) {
&& parmp->edit_type != EDIT_FILE
&& parmp->edit_type != EDIT_STDIN) {
mainerr(err_too_many_args, argv[0]); mainerr(err_too_many_args, argv[0]);
} }
parmp->edit_type = EDIT_FILE; parmp->edit_type = EDIT_FILE;
@@ -1421,6 +1437,7 @@ static void init_params(mparm_T *paramp, int argc, char **argv)
paramp->listen_addr = NULL; paramp->listen_addr = NULL;
paramp->server_addr = NULL; paramp->server_addr = NULL;
paramp->remote = 0; paramp->remote = 0;
paramp->luaf = NULL;
} }
/// Initialize global startuptime file if "--startuptime" passed as an argument. /// Initialize global startuptime file if "--startuptime" passed as an argument.
@@ -2154,6 +2171,7 @@ static void usage(void)
os_msg(_(" + Start at end of file\n")); os_msg(_(" + Start at end of file\n"));
os_msg(_(" --cmd <cmd> Execute <cmd> before any config\n")); os_msg(_(" --cmd <cmd> Execute <cmd> before any config\n"));
os_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n")); os_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n"));
os_msg(_(" -l <script> [args...] Execute Lua <script> (with optional args)\n"));
os_msg("\n"); os_msg("\n");
os_msg(_(" -b Binary mode\n")); os_msg(_(" -b Binary mode\n"));
os_msg(_(" -d Diff mode\n")); os_msg(_(" -d Diff mode\n"));

View File

@@ -23,15 +23,17 @@ typedef struct {
char cmds_tofree[MAX_ARG_CMDS]; // commands that need free() char cmds_tofree[MAX_ARG_CMDS]; // commands that need free()
int n_pre_commands; // no. of commands from --cmd int n_pre_commands; // no. of commands from --cmd
char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument
char *luaf; // Lua script filename from "-l"
int edit_type; // type of editing to do int edit_type; // type of editing to do
char *tagname; // tag from -t argument char *tagname; // tag from -t argument
char *use_ef; // 'errorfile' from -q argument char *use_ef; // 'errorfile' from -q argument
bool input_isatty; // stdin is a terminal bool input_isatty; // stdin is a terminal
bool input_istext; // stdin is text, not executable (-E/-Es)
bool output_isatty; // stdout is a terminal bool output_isatty; // stdout is a terminal
bool err_isatty; // stderr is a terminal bool err_isatty; // stderr is a terminal
bool input_neverscript; // never treat stdin as script (-E/-Es)
int no_swap_file; // "-n" argument used int no_swap_file; // "-n" argument used
int use_debug_break_level; int use_debug_break_level;
int window_count; // number of windows to use int window_count; // number of windows to use

View File

@@ -962,6 +962,7 @@ endfunc
" Test for enabling the lisp mode on startup " Test for enabling the lisp mode on startup
func Test_l_arg() func Test_l_arg()
throw 'Skipped: Nvim -l arg differs from Vim'
let after =<< trim [CODE] let after =<< trim [CODE]
let s = 'lisp=' .. &lisp .. ', showmatch=' .. &showmatch let s = 'lisp=' .. &lisp .. ', showmatch=' .. &showmatch
call writefile([s], 'Xtestout') call writefile([s], 'Xtestout')

View File

@@ -26,6 +26,7 @@ local write_file = helpers.write_file
local meths = helpers.meths local meths = helpers.meths
local alter_slashes = helpers.alter_slashes local alter_slashes = helpers.alter_slashes
local is_os = helpers.is_os local is_os = helpers.is_os
local dedent = helpers.dedent
local testfile = 'Xtest_startuptime' local testfile = 'Xtest_startuptime'
after_each(function() after_each(function()
@@ -47,6 +48,34 @@ describe('startup', function()
assert_log("require%('vim%._editor'%)", testfile, 100) assert_log("require%('vim%._editor'%)", testfile, 100)
end) end)
end) end)
it('-D does not hang #12647', function()
clear()
local screen
screen = Screen.new(60, 7)
screen:attach()
command([[let g:id = termopen('"]]..nvim_prog..
[[" -u NONE -i NONE --cmd "set noruler" -D')]])
screen:expect([[
^ |
|
Entering Debug mode. Type "cont" to continue. |
nvim_exec() |
cmd: aunmenu * |
> |
|
]])
command([[call chansend(g:id, "cont\n")]])
screen:expect([[
^ |
~ |
~ |
~ |
[No Name] |
|
|
]])
end)
end) end)
describe('startup', function() describe('startup', function()
@@ -58,6 +87,94 @@ describe('startup', function()
os.remove('Xtest_startup_ttyout') os.remove('Xtest_startup_ttyout')
end) end)
describe('-l Lua', function()
local function assert_l_out(expected, args_before, args_after)
local args = { nvim_prog, '--clean' }
vim.list_extend(args, args_before or {})
vim.list_extend(args, { '-l', 'test/functional/fixtures/startup.lua' })
vim.list_extend(args, args_after or {})
local out = funcs.system(args):gsub('\r\n', '\n')
return eq(dedent(expected), out)
end
it('failure modes', function()
-- nvim -l <missing file>
matches('nvim: Argument missing after: "%-l"', funcs.system({ nvim_prog, '-l' }))
eq(1, eval('v:shell_error'))
end)
it('os.exit() sets Nvim exitcode', function()
-- nvim -l foo.lua -arg1 -- a b c
assert_l_out([[
bufs:
args: { "-arg1", "--exitcode", "73", "--arg2" }]],
{},
{ '-arg1', "--exitcode", "73", '--arg2' }
)
eq(73, eval('v:shell_error'))
end)
it('sets _G.arg', function()
-- nvim -l foo.lua -arg1 -- a b c
assert_l_out([[
bufs:
args: { "-arg1", "--arg2", "arg3" }]],
{},
{ '-arg1', '--arg2', 'arg3' }
)
eq(0, eval('v:shell_error'))
-- nvim -l foo.lua --
assert_l_out([[
bufs:
args: {}]],
{},
{ '--' }
)
eq(0, eval('v:shell_error'))
-- nvim file1 file2 -l foo.lua -arg1 -- file3 file4
assert_l_out([[
bufs: file1 file2 file3 file4
args: { "-arg1", "arg 2" }]],
{ 'file1', 'file2', },
{ '-arg1', 'arg 2', '--', 'file3', 'file4' }
)
eq(0, eval('v:shell_error'))
-- nvim file1 file2 -l foo.lua -arg1 --
assert_l_out([[
bufs: file1 file2
args: { "-arg1" }]],
{ 'file1', 'file2', },
{ '-arg1', '--' }
)
eq(0, eval('v:shell_error'))
-- nvim -l foo.lua <vim args>
assert_l_out([[
bufs:
args: { "-c", "set wrap?" }]],
{},
{ '-c', 'set wrap?' }
)
eq(0, eval('v:shell_error'))
-- nvim <vim args> -l foo.lua <vim args>
assert_l_out(
-- luacheck: ignore 611 (Line contains only whitespaces)
[[
wrap
bufs:
args: { "-c", "set wrap?" }]],
{ '-c', 'set wrap?' },
{ '-c', 'set wrap?' }
)
eq(0, eval('v:shell_error'))
end)
end)
it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function() it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function()
-- system() puts a pipe at both ends. -- system() puts a pipe at both ends.
local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
@@ -66,6 +183,7 @@ describe('startup', function()
'+q' }) '+q' })
eq('0 0', out) eq('0 0', out)
end) end)
it('with --embed: has("ttyin")==0 has("ttyout")==0', function() it('with --embed: has("ttyin")==0 has("ttyout")==0', function()
local screen = Screen.new(25, 3) local screen = Screen.new(25, 3)
-- Remote UI connected by --embed. -- Remote UI connected by --embed.
@@ -77,6 +195,7 @@ describe('startup', function()
0 0 | 0 0 |
]]) ]])
end) end)
it('in a TTY: has("ttyin")==1 has("ttyout")==1', function() it('in a TTY: has("ttyin")==1 has("ttyout")==1', function()
local screen = Screen.new(25, 4) local screen = Screen.new(25, 4)
screen:attach() screen:attach()
@@ -95,6 +214,7 @@ describe('startup', function()
| |
]]) ]])
end) end)
it('output to pipe: has("ttyin")==1 has("ttyout")==0', function() it('output to pipe: has("ttyin")==1 has("ttyout")==0', function()
if is_os('win') then if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]]) command([[set shellcmdflag=/s\ /c shellxquote=\"]])
@@ -111,6 +231,7 @@ describe('startup', function()
read_file('Xtest_startup_ttyout')) read_file('Xtest_startup_ttyout'))
end) end)
end) end)
it('input from pipe: has("ttyin")==0 has("ttyout")==1', function() it('input from pipe: has("ttyin")==0 has("ttyout")==1', function()
if is_os('win') then if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]]) command([[set shellcmdflag=/s\ /c shellxquote=\"]])
@@ -128,6 +249,7 @@ describe('startup', function()
read_file('Xtest_startup_ttyout')) read_file('Xtest_startup_ttyout'))
end) end)
end) end)
it('input from pipe (implicit) #7679', function() it('input from pipe (implicit) #7679', function()
local screen = Screen.new(25, 4) local screen = Screen.new(25, 4)
screen:attach() screen:attach()
@@ -147,6 +269,7 @@ describe('startup', function()
| |
]]) ]])
end) end)
it('input from pipe + file args #7679', function() it('input from pipe + file args #7679', function()
eq('ohyeah\r\n0 0 bufs=3', eq('ohyeah\r\n0 0 bufs=3',
funcs.system({nvim_prog, '-n', '-u', 'NONE', '-i', 'NONE', '--headless', funcs.system({nvim_prog, '-n', '-u', 'NONE', '-i', 'NONE', '--headless',
@@ -532,32 +655,6 @@ describe('sysinit', function()
eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')) eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))'))
end) end)
it('fixed hang issue with -D (#12647)', function()
local screen
screen = Screen.new(60, 7)
screen:attach()
command([[let g:id = termopen('"]]..nvim_prog..
[[" -u NONE -i NONE --cmd "set noruler" -D')]])
screen:expect([[
^ |
Entering Debug mode. Type "cont" to continue. |
nvim_exec() |
cmd: aunmenu * |
> |
<" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All|
|
]])
command([[call chansend(g:id, "cont\n")]])
screen:expect([[
^ |
~ |
~ |
[No Name] |
|
<" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All|
|
]])
end)
end) end)
describe('user config init', function() describe('user config init', function()

View File

@@ -0,0 +1,34 @@
-- Test "nvim -l foo.lua …"
local function printbufs()
local bufs = ''
for _, v in ipairs(vim.api.nvim_list_bufs()) do
local b = vim.fn.bufname(v)
if b:len() > 0 then
bufs = ('%s %s'):format(bufs, b)
end
end
print(('bufs:%s'):format(bufs))
end
local function parseargs(args)
local exitcode = nil
for i = 1, #args do
if args[i] == '--exitcode' then
exitcode = tonumber(args[i + 1])
end
end
return exitcode
end
local function main()
printbufs()
print('args:', vim.inspect(_G.arg))
local exitcode = parseargs(_G.arg)
if type(exitcode) == 'number' then
os.exit(exitcode)
end
end
main()