aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/builtin.txt63
-rw-r--r--runtime/doc/news.txt5
-rw-r--r--runtime/doc/ui.txt5
-rw-r--r--src/nvim/api/ui.c15
-rw-r--r--src/nvim/eval.c56
-rw-r--r--src/nvim/eval.lua2
-rw-r--r--src/nvim/eval/funcs.c32
-rw-r--r--src/nvim/testdir/test_blob.vim31
-rw-r--r--src/nvim/ui.c21
-rw-r--r--src/nvim/ui.h7
-rw-r--r--src/nvim/ui_client.c2
-rw-r--r--test/functional/api/vim_spec.lua18
-rw-r--r--test/functional/terminal/tui_spec.lua39
-rw-r--r--test/functional/vimscript/has_spec.lua21
-rw-r--r--test/helpers.lua2
15 files changed, 246 insertions, 73 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index b74e30db09..568b73ddfa 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -359,7 +359,8 @@ pyxeval({expr}) any evaluate |python_x| expression
rand([{expr}]) Number get pseudo-random number
range({expr} [, {max} [, {stride}]])
List items from {expr} to {max}
-readblob({fname}) Blob read a |Blob| from {fname}
+readblob({fname} [, {offset} [, {size}]])
+ Blob read a |Blob| from {fname}
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
readfile({fname} [, {type} [, {max}]])
List get list of lines from file {fname}
@@ -3867,6 +3868,7 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The
clipboard |clipboard| provider is available.
fname_case Case in file names matters (for Darwin and MS-Windows
this is not present).
+ gui_running Nvim has a GUI.
iconv Can use |iconv()| for conversion.
linux Linux system.
mac MacOS system.
@@ -6109,6 +6111,25 @@ pyxeval({expr}) *pyxeval()*
Can also be used as a |method|: >
GetExpr()->pyxeval()
<
+rand([{expr}]) *rand()*
+ Return a pseudo-random Number generated with an xoshiro128**
+ algorithm using seed {expr}. The returned number is 32 bits,
+ also on 64 bits systems, for consistency.
+ {expr} can be initialized by |srand()| and will be updated by
+ rand(). If {expr} is omitted, an internal seed value is used
+ and updated.
+ Returns -1 if {expr} is invalid.
+
+ Examples: >
+ :echo rand()
+ :let seed = srand()
+ :echo rand(seed)
+ :echo rand(seed) % 16 " random number 0 - 15
+<
+ Can also be used as a |method|: >
+ seed->rand()
+<
+
*E726* *E727*
range({expr} [, {max} [, {stride}]]) *range()*
Returns a |List| with Numbers:
@@ -6131,29 +6152,29 @@ range({expr} [, {max} [, {stride}]]) *range()*
Can also be used as a |method|: >
GetExpr()->range()
<
-rand([{expr}]) *rand()*
- Return a pseudo-random Number generated with an xoshiro128**
- algorithm using seed {expr}. The returned number is 32 bits,
- also on 64 bits systems, for consistency.
- {expr} can be initialized by |srand()| and will be updated by
- rand(). If {expr} is omitted, an internal seed value is used
- and updated.
- Returns -1 if {expr} is invalid.
- Examples: >
- :echo rand()
- :let seed = srand()
- :echo rand(seed)
- :echo rand(seed) % 16 " random number 0 - 15
-<
- Can also be used as a |method|: >
- seed->rand()
-<
-
-readblob({fname}) *readblob()*
+readblob({fname} [, {offset} [, {size}]]) *readblob()*
Read file {fname} in binary mode and return a |Blob|.
- When the file can't be opened an error message is given and
+ If {offset} is specified, read the file from the specified
+ offset. If it is a negative value, it is used as an offset
+ from the end of the file. E.g., to read the last 12 bytes: >
+ readblob('file.bin', -12)
+< If {size} is specified, only the specified size will be read.
+ E.g. to read the first 100 bytes of a file: >
+ readblob('file.bin', 0, 100)
+< If {size} is -1 or omitted, the whole data starting from
+ {offset} will be read.
+ This can be also used to read the data from a character device
+ on Unix when {size} is explicitly set. Only if the device
+ supports seeking {offset} can be used. Otherwise it should be
+ zero. E.g. to read 10 bytes from a serial console: >
+ readblob('/dev/ttyS0', 0, 10)
+< When the file can't be opened an error message is given and
the result is an empty |Blob|.
+ When the offset is beyond the end of the file the result is an
+ empty blob.
+ When trying to read more bytes than are available the result
+ is truncated.
Also see |readfile()| and |writefile()|.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index c5261b739d..90e17d3678 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -179,6 +179,11 @@ The following new APIs or features were added.
Additionally |TSNode:range()| now takes an optional {include_bytes} argument.
+• |nvim_list_uis()| reports all |ui-option| fields.
+
+• Vim's `has('gui_running')` is now supported as a way for plugins to check if
+ a GUI (not the |TUI|) is attached to Nvim. |has()|
+
==============================================================================
CHANGED FEATURES *news-changes*
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index bb567e021e..e706e36374 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -53,9 +53,8 @@ with these (optional) keys:
- `term_name` Sets the name of the terminal 'term'.
- `term_colors` Sets the number of supported colors 't_Co'.
- `term_background` Sets the default value of 'background'.
-- `stdin_fd` Read buffer from `fd` as if it was a stdin pipe.
- This option can only used by |--embed| ui on startup.
- See |ui-startup-stdin|.
+- `stdin_fd` Read buffer 1 from this fd as if it were stdin |--|.
+ Only from |--embed| UI on startup. |ui-startup-stdin|
- `stdin_tty` Tells if `stdin` is a `tty` or not.
- `stdout_tty` Tells if `stdout` is a `tty` or not.
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index f6dee066dc..a8f5d2e070 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -113,6 +113,10 @@ void remote_ui_disconnect(uint64_t channel_id)
kv_destroy(data->call_buf);
pmap_del(uint64_t)(&connected_uis, channel_id);
ui_detach_impl(ui, channel_id);
+
+ // Destroy `ui`.
+ XFREE_CLEAR(ui->term_name);
+ XFREE_CLEAR(ui->term_background);
xfree(ui);
}
@@ -163,15 +167,9 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
- ui->pum_nlines = 0;
- ui->pum_pos = false;
- ui->pum_width = 0.0;
- ui->pum_height = 0.0;
ui->pum_row = -1.0;
ui->pum_col = -1.0;
ui->rgb = true;
- ui->override = false;
-
CLEAR_FIELD(ui->ui_ext);
for (size_t i = 0; i < options.size; i++) {
@@ -320,6 +318,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
return;
});
set_tty_option("term", string_to_cstr(value.data.string));
+ ui->term_name = string_to_cstr(value.data.string);
return;
}
@@ -328,6 +327,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
return;
});
t_colors = (int)value.data.integer;
+ ui->term_colors = (int)value.data.integer;
return;
}
@@ -336,6 +336,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
return;
});
set_tty_background(value.data.string.data);
+ ui->term_background = string_to_cstr(value.data.string);
return;
}
@@ -359,6 +360,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
return;
});
stdin_isatty = value.data.boolean;
+ ui->stdin_tty = value.data.boolean;
return;
}
@@ -367,6 +369,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
return;
});
stdout_isatty = value.data.boolean;
+ ui->stdout_tty = value.data.boolean;
return;
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 698172442e..b81384266c 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5859,27 +5859,63 @@ write_blob_error:
return false;
}
-/// Read a blob from a file `fd`.
+/// Read blob from file "fd".
+/// Caller has allocated a blob in "rettv".
///
/// @param[in] fd File to read from.
-/// @param[in,out] blob Blob to write to.
+/// @param[in,out] rettv Blob to write to.
+/// @param[in] offset Read the file from the specified offset.
+/// @param[in] size Read the specified size, or -1 if no limit.
///
-/// @return true on success, or false on failure.
-bool read_blob(FILE *const fd, blob_T *const blob)
+/// @return OK on success, or FAIL on failure.
+int read_blob(FILE *const fd, typval_T *rettv, off_T offset, off_T size_arg)
FUNC_ATTR_NONNULL_ALL
{
+ blob_T *const blob = rettv->vval.v_blob;
FileInfo file_info;
if (!os_fileinfo_fd(fileno(fd), &file_info)) {
- return false;
+ return FAIL; // can't read the file, error
}
- const int size = (int)os_fileinfo_size(&file_info);
- ga_grow(&blob->bv_ga, size);
- blob->bv_ga.ga_len = size;
+
+ int whence;
+ off_T size = size_arg;
+ const off_T file_size = (off_T)os_fileinfo_size(&file_info);
+ if (offset >= 0) {
+ // The size defaults to the whole file. If a size is given it is
+ // limited to not go past the end of the file.
+ if (size == -1 || (size > file_size - offset && !S_ISCHR(file_info.stat.st_mode))) {
+ // size may become negative, checked below
+ size = (off_T)os_fileinfo_size(&file_info) - offset;
+ }
+ whence = SEEK_SET;
+ } else {
+ // limit the offset to not go before the start of the file
+ if (-offset > file_size && !S_ISCHR(file_info.stat.st_mode)) {
+ offset = -file_size;
+ }
+ // Size defaults to reading until the end of the file.
+ if (size == -1 || size > -offset) {
+ size = -offset;
+ }
+ whence = SEEK_END;
+ }
+ if (size <= 0) {
+ return OK;
+ }
+ if (offset != 0 && vim_fseek(fd, offset, whence) != 0) {
+ return OK;
+ }
+
+ ga_grow(&blob->bv_ga, (int)size);
+ blob->bv_ga.ga_len = (int)size;
if (fread(blob->bv_ga.ga_data, 1, (size_t)blob->bv_ga.ga_len, fd)
< (size_t)blob->bv_ga.ga_len) {
- return false;
+ // An empty blob is returned on error.
+ tv_blob_free(rettv->vval.v_blob);
+ rettv->vval.v_blob = NULL;
+ return FAIL;
}
- return true;
+ return OK;
}
/// Saves a typval_T as a string.
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index a476e20339..24263d9aee 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -296,7 +296,7 @@ return {
perleval={args=1, base=1},
rand={args={0, 1}, base=1},
range={args={1, 3}, base=1},
- readblob={args=1, base=1},
+ readblob={args={1, 3}, base=1},
readdir={args={1, 2}, base=1},
readfile={args={1, 3}, base=1},
reduce={args={2, 3}, base=1},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 20e590a30c..5da6233e38 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3228,7 +3228,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (!n) {
- if (STRNICMP(name, "patch", 5) == 0) {
+ if (STRNICMP(name, "gui_running", 11) == 0) {
+ n = ui_gui_attached();
+ } else if (STRNICMP(name, "patch", 5) == 0) {
if (name[5] == '-'
&& strlen(name) >= 11
&& ascii_isdigit(name[6])
@@ -5590,15 +5592,24 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
ptrdiff_t prevlen = 0; // length of data in prev
ptrdiff_t prevsize = 0; // size of prev buffer
int64_t maxline = MAXLNUM;
+ off_T offset = 0;
+ off_T size = -1;
if (argvars[1].v_type != VAR_UNKNOWN) {
- if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
- binary = true;
- } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
- blob = true;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- maxline = tv_get_number(&argvars[2]);
+ if (always_blob) {
+ offset = (off_T)tv_get_number(&argvars[1]);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ size = (off_T)tv_get_number(&argvars[2]);
+ }
+ } else {
+ if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
+ binary = true;
+ } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
+ blob = true;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ maxline = tv_get_number(&argvars[2]);
+ }
}
}
@@ -5617,11 +5628,8 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
if (blob) {
tv_blob_alloc_ret(rettv);
- if (!read_blob(fd, rettv->vval.v_blob)) {
+ if (read_blob(fd, rettv, offset, size) == FAIL) {
semsg(_(e_notread), fname);
- // An empty blob is returned on error.
- tv_blob_free(rettv->vval.v_blob);
- rettv->vval.v_blob = NULL;
}
fclose(fd);
return;
diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim
index b1859f9dfe..b01d9309fa 100644
--- a/src/nvim/testdir/test_blob.vim
+++ b/src/nvim/testdir/test_blob.vim
@@ -439,10 +439,41 @@ func Test_blob_read_write()
call writefile(b, 'Xblob')
VAR br = readfile('Xblob', 'B')
call assert_equal(b, br)
+ VAR br2 = readblob('Xblob')
+ call assert_equal(b, br2)
+ VAR br3 = readblob('Xblob', 1)
+ call assert_equal(b[1 :], br3)
+ VAR br4 = readblob('Xblob', 1, 2)
+ call assert_equal(b[1 : 2], br4)
+ VAR br5 = readblob('Xblob', -3)
+ call assert_equal(b[-3 :], br5)
+ VAR br6 = readblob('Xblob', -3, 2)
+ call assert_equal(b[-3 : -2], br6)
+
+ #" reading past end of file, empty result
+ VAR br1e = readblob('Xblob', 10000)
+ call assert_equal(0z, br1e)
+
+ #" reading too much, result is truncated
+ VAR blong = readblob('Xblob', -1000)
+ call assert_equal(b, blong)
+ LET blong = readblob('Xblob', -10, 8)
+ call assert_equal(b, blong)
+ LET blong = readblob('Xblob', 0, 10)
+ call assert_equal(b, blong)
+
call delete('Xblob')
END
call CheckLegacyAndVim9Success(lines)
+ if filereadable('/dev/random')
+ let b = readblob('/dev/random', 0, 10)
+ call assert_equal(10, len(b))
+ endif
+
+ call assert_fails("call readblob('notexist')", 'E484:')
+ " TODO: How do we test for the E485 error?
+
" This was crashing when calling readfile() with a directory.
call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory')
endfunc
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 73545441d3..ce1a57350a 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -134,6 +134,7 @@ void ui_free_all_mem(void)
}
#endif
+/// Returns true if any `rgb=true` UI is attached.
bool ui_rgb_attached(void)
{
if (!headless_mode && p_tgc) {
@@ -147,6 +148,18 @@ bool ui_rgb_attached(void)
return false;
}
+/// Returns true if a GUI is attached.
+bool ui_gui_attached(void)
+{
+ for (size_t i = 0; i < ui_count; i++) {
+ bool tui = uis[i]->stdin_tty || uis[i]->stdout_tty;
+ if (!tui) {
+ return true;
+ }
+ }
+ return false;
+}
+
/// Returns true if any UI requested `override=true`.
bool ui_override(void)
{
@@ -598,6 +611,14 @@ Array ui_array(void)
PUT(info, "height", INTEGER_OBJ(ui->height));
PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb));
PUT(info, "override", BOOLEAN_OBJ(ui->override));
+
+ // TUI fields. (`stdin_fd` is intentionally omitted.)
+ PUT(info, "term_name", STRING_OBJ(cstr_to_string(ui->term_name)));
+ PUT(info, "term_background", STRING_OBJ(cstr_to_string(ui->term_background)));
+ PUT(info, "term_colors", INTEGER_OBJ(ui->term_colors));
+ PUT(info, "stdin_tty", BOOLEAN_OBJ(ui->stdin_tty));
+ PUT(info, "stdout_tty", BOOLEAN_OBJ(ui->stdout_tty));
+
for (UIExtension j = 0; j < kUIExtCount; j++) {
if (ui_ext_names[j][0] != '_' || ui->ui_ext[j]) {
PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index e83f93eb07..dc0ccc73ea 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -103,6 +103,13 @@ struct ui_t {
double pum_height;
double pum_width;
+ // TUI fields.
+ char *term_name;
+ char *term_background;
+ int term_colors;
+ bool stdin_tty;
+ bool stdout_tty;
+
// TODO(bfredl): integrate into struct!
UIData data[1];
};
diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c
index 8262293ec5..25ff63ea2d 100644
--- a/src/nvim/ui_client.c
+++ b/src/nvim/ui_client.c
@@ -1,6 +1,8 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+/// Nvim's own UI client, which attaches to a child or remote Nvim server.
+
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 3e40967dd5..08abf82f47 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -2549,20 +2549,26 @@ describe('API', function()
{
chan = 1,
ext_cmdline = false,
- ext_popupmenu = false,
- ext_tabline = false,
- ext_wildmenu = false,
+ ext_hlstate = false,
ext_linegrid = screen._options.ext_linegrid or false,
+ ext_messages = false,
ext_multigrid = false,
- ext_hlstate = false,
+ ext_popupmenu = false,
+ ext_tabline = false,
ext_termcolors = false,
- ext_messages = false,
+ ext_wildmenu = false,
height = 4,
- rgb = true,
override = true,
+ rgb = true,
+ stdin_tty = false,
+ stdout_tty = false,
+ term_background = '',
+ term_colors = 0,
+ term_name = '',
width = 20,
}
}
+
eq(expected, nvim("list_uis"))
screen:detach()
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index bbcf5ecad7..069fbad803 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -1398,17 +1398,34 @@ describe('TUI', function()
]]}
end)
- it('is included in nvim_list_uis()', function()
- feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(filter(v, {k,v -> k[:3] !=# "ext_" })))})\r')
- screen:expect([=[
- |
- {4:~ }|
- {5: }|
- [[['chan', 1], ['height', 6], ['override', v:false|
- ], ['rgb', v:false], ['width', 50]]] |
- {10:Press ENTER or type command to continue}{1: } |
- {3:-- TERMINAL --} |
- ]=])
+ it('in nvim_list_uis()', function()
+ -- $TERM in :terminal.
+ local exp_term = is_os('bsd') and 'builtin_xterm' or 'xterm-256color'
+ local expected = {
+ {
+ chan = 1,
+ ext_cmdline = false,
+ ext_hlstate = false,
+ ext_linegrid = true,
+ ext_messages = false,
+ ext_multigrid = false,
+ ext_popupmenu = false,
+ ext_tabline = false,
+ ext_termcolors = true,
+ ext_wildmenu = false,
+ height = 6,
+ override = false,
+ rgb = false,
+ stdin_tty = true,
+ stdout_tty = true,
+ term_background = '',
+ term_colors = 256,
+ term_name = exp_term,
+ width = 50
+ },
+ }
+ local _, rv = child_session:request('nvim_list_uis')
+ eq(expected, rv)
end)
it('allows grid to assume wider ambiguous-width characters than host terminal #19686', function()
diff --git a/test/functional/vimscript/has_spec.lua b/test/functional/vimscript/has_spec.lua
index 2e26d603b3..78a761d370 100644
--- a/test/functional/vimscript/has_spec.lua
+++ b/test/functional/vimscript/has_spec.lua
@@ -1,8 +1,11 @@
local helpers = require('test.functional.helpers')(after_each)
-local eq = helpers.eq
+local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
+local connect = helpers.connect
+local eq = helpers.eq
local funcs = helpers.funcs
local is_os = helpers.is_os
+local nvim_prog = helpers.nvim_prog
describe('has()', function()
before_each(clear)
@@ -69,8 +72,22 @@ describe('has()', function()
end
end)
+ it('"gui_running"', function()
+ eq(0, funcs.has('gui_running'))
+ local tui = Screen.new(50,15)
+ local gui_session = connect(funcs.serverstart())
+ local gui = Screen.new(50,15)
+ eq(0, funcs.has('gui_running'))
+ tui:attach({ext_linegrid=true, rgb=true, stdin_tty=true, stdout_tty=true})
+ gui:attach({ext_multigrid=true, rgb=true}, gui_session)
+ eq(1, funcs.has('gui_running'))
+ tui:detach()
+ eq(1, funcs.has('gui_running'))
+ gui:detach()
+ eq(0, funcs.has('gui_running'))
+ end)
+
it('does not change v:shell_error', function()
- local nvim_prog = helpers.nvim_prog
funcs.system({nvim_prog, '-es', '+73cquit'})
funcs.has('python3') -- use a call whose implementation shells out
eq(73, funcs.eval('v:shell_error'))
diff --git a/test/helpers.lua b/test/helpers.lua
index d45536b42b..117b6b4aaa 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -312,7 +312,7 @@ function module.is_os(s)
or s == 'bsd') then
error('unknown platform: '..tostring(s))
end
- return ((s == 'win' and module.sysname():find('windows'))
+ return not not ((s == 'win' and module.sysname():find('windows'))
or (s == 'mac' and module.sysname() == 'darwin')
or (s == 'freebsd' and module.sysname() == 'freebsd')
or (s == 'openbsd' and module.sysname() == 'openbsd')