diff options
37 files changed, 791 insertions, 1063 deletions
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 4ddf82706b..ca9dfd0350 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1983,7 +1983,9 @@ locations_to_items({locations}, {offset_encoding}) (`table[]`) A list of objects with the following fields: • {filename} (`string`) • {lnum} (`integer`) 1-indexed line number + • {end_lnum} (`integer`) 1-indexed end line number • {col} (`integer`) 1-indexed column + • {end_col} (`integer`) 1-indexed end column • {text} (`string`) • {user_data} (`lsp.Location|lsp.LocationLink`) diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt index 0a7e0baad3..a8c5670352 100644 --- a/runtime/doc/mbyte.txt +++ b/runtime/doc/mbyte.txt @@ -686,7 +686,7 @@ You might want to select the font used for the menus. Unfortunately this doesn't always work. See the system specific remarks below, and 'langmenu'. -USING UTF-8 IN X-Windows *utf-8-in-xwindows* +USING UTF-8 IN X-WINDOWS *utf-8-in-xwindows* You need to specify a font to be used. For double-wide characters another font is required, which is exactly twice as wide. There are three ways to do diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index b316cc7b04..2ff6b0302c 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -113,6 +113,7 @@ LSP • Completion side effects (including snippet expansion, execution of commands and application of additional text edits) is now built-in. +• |vim.lsp.util.locations_to_items()| sets `end_col` and `end_lnum` fields. LUA diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index 5d3c0cbdc2..897e503fc4 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -1296,7 +1296,7 @@ passed to make, say :make html or :make pdf. Additional arguments can be passed to pandoc: - either by appending them to make, say `:make html --self-contained` . -- or setting them in `b:pandoc_compiler_args` or `g:pandoc_compiler_args` +- or setting them in `b:pandoc_compiler_args` or `g:pandoc_compiler_args`. PERL *quickfix-perl* *compiler-perl* diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 1e3dfe1e32..61028c791d 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -466,14 +466,14 @@ ASTRO *astro.vim* *ft-astro-syntax* Configuration The following variables control certain syntax highlighting features. -You can add them to your .vimrc: > +You can add them to your .vimrc. + +To enables TypeScript and TSX for ".astro" files (default "disable"): > let g:astro_typescript = "enable" < -Enables TypeScript and TSX for ".astro" files. Default Value: "disable" > +To enables Stylus for ".astro" files (default "disable"): > let g:astro_stylus = "enable" < -Enables Stylus for ".astro" files. Default Value: "disable" - NOTE: You need to install an external plugin to support stylus in astro files. @@ -1437,7 +1437,7 @@ Note: Syntax folding might slow down syntax highlighting significantly, especially for large files. -HTML/OS (by Aestiva) *htmlos.vim* *ft-htmlos-syntax* +HTML/OS (BY AESTIVA) *htmlos.vim* *ft-htmlos-syntax* The coloring scheme for HTML/OS works as follows: diff --git a/runtime/ftplugin/glsl.lua b/runtime/ftplugin/glsl.lua new file mode 100644 index 0000000000..f398d66a63 --- /dev/null +++ b/runtime/ftplugin/glsl.lua @@ -0,0 +1 @@ +vim.bo.commentstring = '// %s' diff --git a/runtime/lua/vim/_comment.lua b/runtime/lua/vim/_comment.lua index 044cd69716..efe289b3e1 100644 --- a/runtime/lua/vim/_comment.lua +++ b/runtime/lua/vim/_comment.lua @@ -194,14 +194,9 @@ local function toggle_lines(line_start, line_end, ref_position) -- - Debatable for highlighting in text area (like LSP semantic tokens). -- Mostly because it causes flicker as highlighting is preserved during -- comment toggling. - package.loaded['vim._comment']._lines = vim.tbl_map(f, lines) - local lua_cmd = string.format( - 'vim.api.nvim_buf_set_lines(0, %d, %d, false, package.loaded["vim._comment"]._lines)', - line_start - 1, - line_end - ) - vim.cmd.lua({ lua_cmd, mods = { lockmarks = true } }) - package.loaded['vim._comment']._lines = nil + vim._with({ lockmarks = true }, function() + vim.api.nvim_buf_set_lines(0, line_start - 1, line_end, false, vim.tbl_map(f, lines)) + end) end --- Operator which toggles user-supplied range of lines diff --git a/runtime/lua/vim/glob.lua b/runtime/lua/vim/glob.lua index ad4a915a94..6de2bc3e94 100644 --- a/runtime/lua/vim/glob.lua +++ b/runtime/lua/vim/glob.lua @@ -29,8 +29,10 @@ function M.to_lpeg(pattern) return patt end - local function add(acc, a) - return acc + a + local function condlist(conds, after) + return vim.iter(conds):fold(P(false), function(acc, cond) + return acc + cond * after + end) end local function mul(acc, m) @@ -63,15 +65,14 @@ function M.to_lpeg(pattern) * C(P('!') ^ -1) * Ct(Ct(C(P(1)) * P('-') * C(P(1) - P(']'))) ^ 1 * P(']')) / class, - CondList = P('{') * Cf(V('Cond') * (P(',') * V('Cond')) ^ 0, add) * P('}'), + CondList = P('{') * Ct(V('Cond') * (P(',') * V('Cond')) ^ 0) * P('}') * V('Pattern') / condlist, -- TODO: '*' inside a {} condition is interpreted literally but should probably have the same -- wildcard semantics it usually has. -- Fixing this is non-trivial because '*' should match non-greedily up to "the rest of the -- pattern" which in all other cases is the entire succeeding part of the pattern, but at the end of a {} -- condition means "everything after the {}" where several other options separated by ',' may -- exist in between that should not be matched by '*'. - Cond = Cf((V('Ques') + V('Class') + V('CondList') + (V('Literal') - S(',}'))) ^ 1, mul) - + Cc(P(0)), + Cond = Cf((V('Ques') + V('Class') + V('Literal') - S(',}')) ^ 1, mul) + Cc(P(0)), Literal = P(1) / P, End = P(-1) * Cc(P(-1)), }) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 3d61af8b15..088d57b6d6 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1722,7 +1722,9 @@ end) ---@inlinedoc ---@field filename string ---@field lnum integer 1-indexed line number +---@field end_lnum integer 1-indexed end line number ---@field col integer 1-indexed column +---@field end_col integer 1-indexed end column ---@field text string ---@field user_data lsp.Location|lsp.LocationLink @@ -1749,7 +1751,7 @@ function M.locations_to_items(locations, offset_encoding) end local items = {} - ---@type table<string, {start: lsp.Position, location: lsp.Location|lsp.LocationLink}[]> + ---@type table<string, {start: lsp.Position, end: lsp.Position, location: lsp.Location|lsp.LocationLink}[]> local grouped = setmetatable({}, { __index = function(t, k) local v = {} @@ -1761,7 +1763,7 @@ function M.locations_to_items(locations, offset_encoding) -- locations may be Location or LocationLink local uri = d.uri or d.targetUri local range = d.range or d.targetSelectionRange - table.insert(grouped[uri], { start = range.start, location = d }) + table.insert(grouped[uri], { start = range.start, ['end'] = range['end'], location = d }) end ---@type string[] @@ -1776,6 +1778,9 @@ function M.locations_to_items(locations, offset_encoding) local line_numbers = {} for _, temp in ipairs(rows) do table.insert(line_numbers, temp.start.line) + if temp.start.line ~= temp['end'].line then + table.insert(line_numbers, temp['end'].line) + end end -- get all the lines for this uri @@ -1783,13 +1788,18 @@ function M.locations_to_items(locations, offset_encoding) for _, temp in ipairs(rows) do local pos = temp.start + local end_pos = temp['end'] local row = pos.line + local end_row = end_pos.line local line = lines[row] or '' local col = M._str_byteindex_enc(line, pos.character, offset_encoding) + local end_col = M._str_byteindex_enc(lines[end_row] or '', end_pos.character, offset_encoding) table.insert(items, { filename = filename, lnum = row + 1, + end_lnum = end_row + 1, col = col + 1, + end_col = end_col + 1, text = line, user_data = temp.location, }) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 9d54a80144..7fd29d5f7b 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -1139,4 +1139,82 @@ function vim._defer_require(root, mod) }) end +--- @nodoc +--- @class vim.context.mods +--- @field buf? integer +--- @field emsg_silent? boolean +--- @field hide? boolean +--- @field horizontal? boolean +--- @field keepalt? boolean +--- @field keepjumps? boolean +--- @field keepmarks? boolean +--- @field keeppatterns? boolean +--- @field lockmarks? boolean +--- @field noautocmd? boolean +--- @field options? table<string, any> +--- @field sandbox? boolean +--- @field silent? boolean +--- @field unsilent? boolean +--- @field win? integer + +--- Executes function `f` with the given context specification. +--- +--- @param context vim.context.mods +function vim._with(context, f) + vim.validate('context', context, 'table') + vim.validate('f', f, 'function') + + vim.validate('context.buf', context.buf, 'number', true) + vim.validate('context.emsg_silent', context.emsg_silent, 'boolean', true) + vim.validate('context.hide', context.hide, 'boolean', true) + vim.validate('context.horizontal', context.horizontal, 'boolean', true) + vim.validate('context.keepalt', context.keepalt, 'boolean', true) + vim.validate('context.keepjumps', context.keepjumps, 'boolean', true) + vim.validate('context.keepmarks', context.keepmarks, 'boolean', true) + vim.validate('context.keeppatterns', context.keeppatterns, 'boolean', true) + vim.validate('context.lockmarks', context.lockmarks, 'boolean', true) + vim.validate('context.noautocmd', context.noautocmd, 'boolean', true) + vim.validate('context.options', context.options, 'table', true) + vim.validate('context.sandbox', context.sandbox, 'boolean', true) + vim.validate('context.silent', context.silent, 'boolean', true) + vim.validate('context.unsilent', context.unsilent, 'boolean', true) + vim.validate('context.win', context.win, 'number', true) + + -- Check buffer exists + if context.buf then + if not vim.api.nvim_buf_is_valid(context.buf) then + error('Invalid buffer id: ' .. context.buf) + end + end + + -- Check window exists + if context.win then + if not vim.api.nvim_win_is_valid(context.win) then + error('Invalid window id: ' .. context.win) + end + end + + -- Store original options + local previous_options ---@type table<string, any> + if context.options then + previous_options = {} + for k, v in pairs(context.options) do + previous_options[k] = + vim.api.nvim_get_option_value(k, { win = context.win, buf = context.buf }) + vim.api.nvim_set_option_value(k, v, { win = context.win, buf = context.buf }) + end + end + + local retval = { vim._with_c(context, f) } + + -- Restore original options + if previous_options then + for k, v in pairs(previous_options) do + vim.api.nvim_set_option_value(k, v, { win = context.win, buf = context.buf }) + end + end + + return unpack(retval) +end + return vim diff --git a/runtime/syntax/java.vim b/runtime/syntax/java.vim index 5ba724d24e..f5910a8557 100644 --- a/runtime/syntax/java.vim +++ b/runtime/syntax/java.vim @@ -3,7 +3,7 @@ " Maintainer: Aliaksei Budavei <0x000c70 AT gmail DOT com> " Former Maintainer: Claudio Fleiner <claudio@fleiner.com> " Repository: https://github.com/zzzyxwvut/java-vim.git -" Last Change: 2024 May 30 +" Last Change: 2024 Jun 08 " Please check :help java.vim for comments on some of the options available. @@ -215,7 +215,7 @@ syn keyword javaLabelVarType contained var syn keyword javaLabelCastType contained char byte short int " Allow for the contingency of the enclosing region not being able to " _keep_ its _end_, e.g. case ':':. -syn region javaLabelWhenClause contained transparent matchgroup=javaLabel start="\<when\>" matchgroup=NONE end=":"me=e-1 end="->"me=e-2 contains=TOP,javaExternal +syn region javaLabelWhenClause contained transparent matchgroup=javaLabel start="\<when\>" matchgroup=NONE end=":"me=e-1 end="->"me=e-2 contains=TOP,javaExternal,javaLambdaDef syn match javaLabelNumber contained "\<0\>[lL]\@!" syn match javaLabelNumber contained "\<\%(0\%([xX]\x\%(_*\x\)*\|_*\o\%(_*\o\)*\|[bB][01]\%(_*[01]\)*\)\|[1-9]\%(_*\d\)*\)\>[lL]\@!" hi def link javaLabelDefault javaLabel diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index d9bc0ccc92..5adaff8449 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -54,6 +54,10 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } if (HAS_KEY_X(opts, buf)) { + VALIDATE(!(HAS_KEY_X(opts, scope) && *scope == OPT_GLOBAL), "%s", + "cannot use both global 'scope' and 'buf'", { + return FAIL; + }); *scope = OPT_LOCAL; *req_scope = kOptReqBuf; *from = find_buffer_by_handle(opts->buf, err); @@ -68,11 +72,6 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * return FAIL; }); - VALIDATE((!HAS_KEY_X(opts, scope) || !HAS_KEY_X(opts, buf)), "%s", - "cannot use both 'scope' and 'buf'", { - return FAIL; - }); - VALIDATE((!HAS_KEY_X(opts, win) || !HAS_KEY_X(opts, buf)), "%s", "cannot use both 'buf' and 'win'", { return FAIL; diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index 6fbcb2dbb8..5522ab1ca3 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -1066,7 +1066,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en bool whole = (start == 1 && end == buf->b_ml.ml_line_count); bool write_undo_file = false; context_sha256_T sha_ctx; - unsigned bkc = get_bkc_value(buf); + unsigned bkc = get_bkc_flags(buf); if (fname == NULL || *fname == NUL) { // safety check return FAIL; diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 05225cecd0..5f9bfc3a73 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -39,8 +39,6 @@ #include "nvim/os/os_defs.h" #include "nvim/os/shell.h" #include "nvim/path.h" -#include "nvim/rbuffer.h" -#include "nvim/rbuffer_defs.h" #include "nvim/terminal.h" #include "nvim/types_defs.h" @@ -432,7 +430,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s wstream_init(&proc->in, 0); } if (has_out) { - rstream_init(&proc->out, 0); + rstream_init(&proc->out); } if (rpc) { @@ -447,7 +445,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s if (has_err) { callback_reader_start(&chan->on_stderr, "stderr"); - rstream_init(&proc->err, 0); + rstream_init(&proc->err); rstream_start(&proc->err, on_job_stderr, chan); } @@ -484,7 +482,7 @@ uint64_t channel_connect(bool tcp, const char *address, bool rpc, CallbackReader channel->stream.socket.s.internal_close_cb = close_cb; channel->stream.socket.s.internal_data = channel; wstream_init(&channel->stream.socket.s, 0); - rstream_init(&channel->stream.socket, 0); + rstream_init(&channel->stream.socket); if (rpc) { rpc_start(channel); @@ -509,7 +507,7 @@ void channel_from_connection(SocketWatcher *watcher) channel->stream.socket.s.internal_close_cb = close_cb; channel->stream.socket.s.internal_data = channel; wstream_init(&channel->stream.socket.s, 0); - rstream_init(&channel->stream.socket, 0); + rstream_init(&channel->stream.socket); rpc_start(channel); channel_create_event(channel, watcher->addr); } @@ -554,7 +552,7 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, const char **err dup2(STDERR_FILENO, STDIN_FILENO); } #endif - rstream_init_fd(&main_loop, &channel->stream.stdio.in, stdin_dup_fd, 0); + rstream_init_fd(&main_loop, &channel->stream.stdio.in, stdin_dup_fd); wstream_init_fd(&main_loop, &channel->stream.stdio.out, stdout_dup_fd, 0); if (rpc) { @@ -648,51 +646,38 @@ static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len) return l; } -void on_channel_data(RStream *stream, RBuffer *buf, size_t count, void *data, bool eof) +size_t on_channel_data(RStream *stream, const char *buf, size_t count, void *data, bool eof) { Channel *chan = data; - on_channel_output(stream, chan, buf, eof, &chan->on_data); + return on_channel_output(stream, chan, buf, count, eof, &chan->on_data); } -void on_job_stderr(RStream *stream, RBuffer *buf, size_t count, void *data, bool eof) +size_t on_job_stderr(RStream *stream, const char *buf, size_t count, void *data, bool eof) { Channel *chan = data; - on_channel_output(stream, chan, buf, eof, &chan->on_stderr); + return on_channel_output(stream, chan, buf, count, eof, &chan->on_stderr); } -static void on_channel_output(RStream *stream, Channel *chan, RBuffer *buf, bool eof, - CallbackReader *reader) +static size_t on_channel_output(RStream *stream, Channel *chan, const char *buf, size_t count, + bool eof, CallbackReader *reader) { - size_t count; - char *output = rbuffer_read_ptr(buf, &count); - if (chan->term) { - if (!eof) { - char *p = output; - char *end = output + count; + if (count) { + const char *p = buf; + const char *end = buf + count; while (p < end) { // Don't pass incomplete UTF-8 sequences to libvterm. #16245 // Composing chars can be passed separately, so utf_ptr2len_len() is enough. int clen = utf_ptr2len_len(p, (int)(end - p)); if (clen > end - p) { - count = (size_t)(p - output); + count = (size_t)(p - buf); break; } p += clen; } } - terminal_receive(chan->term, output, count); - } - - if (count) { - rbuffer_consumed(buf, count); - } - // Move remaining data to start of buffer, so the buffer can never wrap around. - rbuffer_reset(buf); - - if (callback_reader_set(*reader)) { - ga_concat_len(&reader->buffer, output, count); + terminal_receive(chan->term, buf, count); } if (eof) { @@ -700,8 +685,11 @@ static void on_channel_output(RStream *stream, Channel *chan, RBuffer *buf, bool } if (callback_reader_set(*reader)) { + ga_concat_len(&reader->buffer, buf, count); schedule_channel_event(chan); } + + return count; } /// schedule the necessary callbacks to be invoked as a deferred event diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h index 8563006159..41690ead88 100644 --- a/src/nvim/event/defs.h +++ b/src/nvim/event/defs.h @@ -6,7 +6,6 @@ #include <uv.h> #include "nvim/eval/typval_defs.h" -#include "nvim/rbuffer_defs.h" #include "nvim/types_defs.h" enum { EVENT_HANDLER_MAX_ARGC = 10, }; @@ -59,11 +58,13 @@ typedef struct rstream RStream; /// Type of function called when the RStream buffer is filled with data /// /// @param stream The Stream instance -/// @param buf The associated RBuffer instance +/// @param read_data data that was read /// @param count Number of bytes that was read. /// @param data User-defined data /// @param eof If the stream reached EOF. -typedef void (*stream_read_cb)(RStream *stream, RBuffer *buf, size_t count, void *data, bool eof); +/// @return number of bytes which were consumed +typedef size_t (*stream_read_cb)(RStream *stream, const char *read_data, size_t count, void *data, + bool eof); /// Type of function called when the Stream has information about a write /// request. @@ -102,11 +103,16 @@ struct stream { struct rstream { Stream s; bool did_eof; - RBuffer *buffer; + bool want_read; + bool pending_read; + bool paused_full; + char *buffer; // ARENA_BLOCK_SIZE + char *read_pos; + char *write_pos; uv_buf_t uvbuf; stream_read_cb read_cb; size_t num_bytes; - size_t fpos; + int64_t fpos; }; #define ADDRESS_MAX_SIZE 256 diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 710376cd62..70fc31ba21 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -18,7 +18,6 @@ #include "nvim/os/pty_process.h" #include "nvim/os/shell.h" #include "nvim/os/time.h" -#include "nvim/rbuffer_defs.h" #include "nvim/ui_client.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -355,7 +354,7 @@ static void flush_stream(Process *proc, RStream *stream) int err = uv_recv_buffer_size((uv_handle_t *)&stream->s.uv.pipe, &system_buffer_size); if (err) { - system_buffer_size = (int)rbuffer_capacity(stream->buffer); + system_buffer_size = ARENA_BLOCK_SIZE; } size_t max_bytes = stream->num_bytes + (size_t)system_buffer_size; diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index 6c7fa20bd8..71290d0c0d 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -11,38 +11,44 @@ #include "nvim/macros_defs.h" #include "nvim/main.h" #include "nvim/os/os_defs.h" -#include "nvim/rbuffer.h" -#include "nvim/rbuffer_defs.h" #include "nvim/types_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/rstream.c.generated.h" #endif -void rstream_init_fd(Loop *loop, RStream *stream, int fd, size_t bufsize) +void rstream_init_fd(Loop *loop, RStream *stream, int fd) FUNC_ATTR_NONNULL_ARG(1, 2) { stream_init(loop, &stream->s, fd, NULL); - rstream_init(stream, bufsize); + rstream_init(stream); } -void rstream_init_stream(RStream *stream, uv_stream_t *uvstream, size_t bufsize) +void rstream_init_stream(RStream *stream, uv_stream_t *uvstream) FUNC_ATTR_NONNULL_ARG(1, 2) { stream_init(NULL, &stream->s, -1, uvstream); - rstream_init(stream, bufsize); + rstream_init(stream); } -void rstream_init(RStream *stream, size_t bufsize) +void rstream_init(RStream *stream) FUNC_ATTR_NONNULL_ARG(1) { stream->fpos = 0; stream->read_cb = NULL; stream->num_bytes = 0; - stream->buffer = rbuffer_new(bufsize); - stream->buffer->data = stream; - stream->buffer->full_cb = on_rbuffer_full; - stream->buffer->nonfull_cb = on_rbuffer_nonfull; + stream->buffer = alloc_block(); + stream->read_pos = stream->write_pos = stream->buffer; +} + +void rstream_start_inner(RStream *stream) + FUNC_ATTR_NONNULL_ARG(1) +{ + if (stream->s.uvstream) { + uv_read_start(stream->s.uvstream, alloc_cb, read_cb); + } else { + uv_idle_start(&stream->s.uv.idle, fread_idle_cb); + } } /// Starts watching for events from a `Stream` instance. @@ -53,17 +59,16 @@ void rstream_start(RStream *stream, stream_read_cb cb, void *data) { stream->read_cb = cb; stream->s.cb_data = data; - if (stream->s.uvstream) { - uv_read_start(stream->s.uvstream, alloc_cb, read_cb); - } else { - uv_idle_start(&stream->s.uv.idle, fread_idle_cb); + stream->want_read = true; + if (!stream->paused_full) { + rstream_start_inner(stream); } } /// Stops watching for events from a `Stream` instance. /// /// @param stream The `Stream` instance -void rstream_stop(RStream *stream) +void rstream_stop_inner(RStream *stream) FUNC_ATTR_NONNULL_ALL { if (stream->s.uvstream) { @@ -73,16 +78,14 @@ void rstream_stop(RStream *stream) } } -static void on_rbuffer_full(RBuffer *buf, void *data) -{ - rstream_stop(data); -} - -static void on_rbuffer_nonfull(RBuffer *buf, void *data) +/// Stops watching for events from a `Stream` instance. +/// +/// @param stream The `Stream` instance +void rstream_stop(RStream *stream) + FUNC_ATTR_NONNULL_ALL { - RStream *stream = data; - assert(stream->read_cb); - rstream_start(stream, stream->read_cb, stream->s.cb_data); + rstream_stop_inner(stream); + stream->want_read = false; } // Callbacks used by libuv @@ -91,10 +94,9 @@ static void on_rbuffer_nonfull(RBuffer *buf, void *data) static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) { RStream *stream = handle->data; - // `uv_buf_t.len` happens to have different size on Windows. - size_t write_count; - buf->base = rbuffer_write_ptr(stream->buffer, &write_count); - buf->len = UV_BUF_LEN(write_count); + buf->base = stream->write_pos; + // `uv_buf_t.len` happens to have different size on Windows (as a treat) + buf->len = UV_BUF_LEN(rstream_space(stream)); } /// Callback invoked by libuv after it copies the data into the buffer provided @@ -108,21 +110,21 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) // cnt == 0 means libuv asked for a buffer and decided it wasn't needed: // http://docs.libuv.org/en/latest/stream.html#c.uv_read_start. // - // We don't need to do anything with the RBuffer because the next call + // We don't need to do anything with the buffer because the next call // to `alloc_cb` will return the same unused pointer (`rbuffer_produced` // won't be called) if (cnt == UV_ENOBUFS || cnt == 0) { return; } else if (cnt == UV_EOF && uvstream->type == UV_TTY) { // The TTY driver might signal EOF without closing the stream - invoke_read_cb(stream, 0, true); + invoke_read_cb(stream, true); } else { DLOG("closing Stream (%p): %s (%s)", (void *)stream, uv_err_name((int)cnt), os_strerror((int)cnt)); // Read error or EOF, either way stop the stream and invoke the callback // with eof == true uv_read_stop(uvstream); - invoke_read_cb(stream, 0, true); + invoke_read_cb(stream, true); } return; } @@ -130,10 +132,13 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) // at this point we're sure that cnt is positive, no error occurred size_t nread = (size_t)cnt; stream->num_bytes += nread; - // Data was already written, so all we need is to update 'wpos' to reflect - // the space actually used in the buffer. - rbuffer_produced(stream->buffer, nread); - invoke_read_cb(stream, nread, false); + stream->write_pos += cnt; + invoke_read_cb(stream, false); +} + +static size_t rstream_space(RStream *stream) +{ + return (size_t)((stream->buffer + ARENA_BLOCK_SIZE) - stream->write_pos); } /// Called by the by the 'idle' handle to emulate a reading event @@ -146,52 +151,37 @@ static void fread_idle_cb(uv_idle_t *handle) uv_fs_t req; RStream *stream = handle->data; + stream->uvbuf.base = stream->write_pos; // `uv_buf_t.len` happens to have different size on Windows. - size_t write_count; - stream->uvbuf.base = rbuffer_write_ptr(stream->buffer, &write_count); - stream->uvbuf.len = UV_BUF_LEN(write_count); - - // the offset argument to uv_fs_read is int64_t, could someone really try - // to read more than 9 quintillion (9e18) bytes? - // upcast is meant to avoid tautological condition warning on 32 bits - uintmax_t fpos_intmax = stream->fpos; - if (fpos_intmax > INT64_MAX) { - ELOG("stream offset overflow"); - preserve_exit("stream offset overflow"); - } + stream->uvbuf.len = UV_BUF_LEN(rstream_space(stream)); // Synchronous read - uv_fs_read(handle->loop, - &req, - stream->s.fd, - &stream->uvbuf, - 1, - (int64_t)stream->fpos, - NULL); + uv_fs_read(handle->loop, &req, stream->s.fd, &stream->uvbuf, 1, stream->fpos, NULL); uv_fs_req_cleanup(&req); if (req.result <= 0) { uv_idle_stop(&stream->s.uv.idle); - invoke_read_cb(stream, 0, true); + invoke_read_cb(stream, true); return; } - // no errors (req.result (ssize_t) is positive), it's safe to cast. - size_t nread = (size_t)req.result; - rbuffer_produced(stream->buffer, nread); - stream->fpos += nread; - invoke_read_cb(stream, nread, false); + // no errors (req.result (ssize_t) is positive), it's safe to use. + stream->write_pos += req.result; + stream->fpos += req.result; + invoke_read_cb(stream, false); } static void read_event(void **argv) { RStream *stream = argv[0]; + stream->pending_read = false; if (stream->read_cb) { - size_t count = (uintptr_t)argv[1]; - bool eof = (uintptr_t)argv[2]; - stream->did_eof = eof; - stream->read_cb(stream, stream->buffer, count, stream->s.cb_data, eof); + size_t available = rstream_available(stream); + size_t consumed = stream->read_cb(stream, stream->read_pos, available, stream->s.cb_data, + stream->did_eof); + assert(consumed <= available); + rstream_consume(stream, consumed); } stream->s.pending_reqs--; if (stream->s.closed && !stream->s.pending_reqs) { @@ -199,13 +189,48 @@ static void read_event(void **argv) } } -static void invoke_read_cb(RStream *stream, size_t count, bool eof) +size_t rstream_available(RStream *stream) { + return (size_t)(stream->write_pos - stream->read_pos); +} + +void rstream_consume(RStream *stream, size_t consumed) +{ + stream->read_pos += consumed; + size_t remaining = (size_t)(stream->write_pos - stream->read_pos); + if (remaining > 0 && stream->read_pos > stream->buffer) { + memmove(stream->buffer, stream->read_pos, remaining); + stream->read_pos = stream->buffer; + stream->write_pos = stream->buffer + remaining; + } else if (remaining == 0) { + stream->read_pos = stream->write_pos = stream->buffer; + } + + if (stream->want_read && stream->paused_full && rstream_space(stream)) { + assert(stream->read_cb); + stream->paused_full = false; + rstream_start_inner(stream); + } +} + +static void invoke_read_cb(RStream *stream, bool eof) +{ + stream->did_eof |= eof; + + if (!rstream_space(stream)) { + rstream_stop_inner(stream); + stream->paused_full = true; + } + + // we cannot use pending_reqs as a socket can have both pending reads and writes + if (stream->pending_read) { + return; + } + // Don't let the stream be closed before the event is processed. stream->s.pending_reqs++; - - CREATE_EVENT(stream->s.events, read_event, - stream, (void *)(uintptr_t *)count, (void *)(uintptr_t)eof); + stream->pending_read = true; + CREATE_EVENT(stream->s.events, read_event, stream); } void rstream_may_close(RStream *stream) diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index 3d26dd868f..bc1b503f4c 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -8,7 +8,6 @@ #include "nvim/event/loop.h" #include "nvim/event/stream.h" #include "nvim/log.h" -#include "nvim/rbuffer.h" #include "nvim/types_defs.h" #ifdef MSWIN # include "nvim/os/os_win_console.h" @@ -149,7 +148,7 @@ static void rstream_close_cb(uv_handle_t *handle) { RStream *stream = handle->data; if (stream->buffer) { - rbuffer_free(stream->buffer); + free_block(stream->buffer); } close_cb(handle); } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 2f54aa511b..1e2c515195 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1729,12 +1729,6 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) } const char *errormsg = NULL; -#undef ERROR -#define ERROR(msg) \ - do { \ - errormsg = msg; \ - goto end; \ - } while (0) cmdmod_T save_cmdmod = cmdmod; cmdmod = cmdinfo->cmdmod; @@ -1745,16 +1739,19 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) if (!MODIFIABLE(curbuf) && (eap->argt & EX_MODIFY) // allow :put in terminals && !(curbuf->terminal && eap->cmdidx == CMD_put)) { - ERROR(_(e_modifiable)); + errormsg = _(e_modifiable); + goto end; } if (!IS_USER_CMDIDX(eap->cmdidx)) { if (cmdwin_type != 0 && !(eap->argt & EX_CMDWIN)) { // Command not allowed in the command line window - ERROR(_(e_cmdwin)); + errormsg = _(e_cmdwin); + goto end; } if (text_locked() && !(eap->argt & EX_LOCK_OK)) { // Command not allowed when text is locked - ERROR(_(get_text_locked_msg())); + errormsg = _(get_text_locked_msg()); + goto end; } } // Disallow editing another buffer when "curbuf->b_ro_locked" is set. @@ -1802,7 +1799,6 @@ end: do_cmdline_end(); return retv; -#undef ERROR } static void profile_cmd(const exarg_T *eap, cstack_T *cstack, LineGetter fgetline, void *cookie) @@ -2696,7 +2692,7 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod, /// Apply the command modifiers. Saves current state in "cmdmod", call /// undo_cmdmod() later. -static void apply_cmdmod(cmdmod_T *cmod) +void apply_cmdmod(cmdmod_T *cmod) { if ((cmod->cmod_flags & CMOD_SANDBOX) && !cmod->cmod_did_sandbox) { sandbox++; diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 22ee0a1c98..ee0eabbebb 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -17,10 +17,13 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" +#include "nvim/autocmd.h" #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/eval/window.h" +#include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/fold.h" #include "nvim/globals.h" @@ -40,6 +43,7 @@ #include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/types_defs.h" +#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/stdlib.c.generated.h" @@ -568,6 +572,99 @@ static int nlua_foldupdate(lua_State *lstate) return 0; } +static int nlua_with(lua_State *L) +{ + int flags = 0; + buf_T *buf = NULL; + win_T *win = NULL; + +#define APPLY_FLAG(key, flag) \ + if (strequal((key), k) && (v)) { \ + flags |= (flag); \ + } + + luaL_argcheck(L, lua_istable(L, 1), 1, "table expected"); + lua_pushnil(L); // [dict, ..., nil] + while (lua_next(L, 1)) { + // [dict, ..., key, value] + if (lua_type(L, -2) == LUA_TSTRING) { + const char *k = lua_tostring(L, -2); + bool v = lua_toboolean(L, -1); + if (strequal("buf", k)) { \ + buf = handle_get_buffer((int)luaL_checkinteger(L, -1)); + } else if (strequal("win", k)) { \ + win = handle_get_window((int)luaL_checkinteger(L, -1)); + } else { + APPLY_FLAG("sandbox", CMOD_SANDBOX); + APPLY_FLAG("silent", CMOD_SILENT); + APPLY_FLAG("emsg_silent", CMOD_ERRSILENT); + APPLY_FLAG("unsilent", CMOD_UNSILENT); + APPLY_FLAG("noautocmd", CMOD_NOAUTOCMD); + APPLY_FLAG("hide", CMOD_HIDE); + APPLY_FLAG("keepalt", CMOD_KEEPALT); + APPLY_FLAG("keepmarks", CMOD_KEEPMARKS); + APPLY_FLAG("keepjumps", CMOD_KEEPJUMPS); + APPLY_FLAG("lockmarks", CMOD_LOCKMARKS); + APPLY_FLAG("keeppatterns", CMOD_KEEPPATTERNS); + } + } + // pop the value; lua_next will pop the key. + lua_pop(L, 1); // [dict, ..., key] + } + int status = 0; + int rets = 0; + + cmdmod_T save_cmdmod = cmdmod; + cmdmod.cmod_flags = flags; + apply_cmdmod(&cmdmod); + + if (buf || win) { + try_start(); + } + + aco_save_T aco; + win_execute_T win_execute_args; + Error err = ERROR_INIT; + + if (win) { + tabpage_T *tabpage = win_find_tabpage(win); + if (!win_execute_before(&win_execute_args, win, tabpage)) { + goto end; + } + } else if (buf) { + aucmd_prepbuf(&aco, buf); + } + + int s = lua_gettop(L); + lua_pushvalue(L, 2); + status = lua_pcall(L, 0, LUA_MULTRET, 0); + rets = lua_gettop(L) - s; + + if (win) { + win_execute_after(&win_execute_args); + } else if (buf) { + aucmd_restbuf(&aco); + } + +end: + if (buf || win) { + try_end(&err); + } + + undo_cmdmod(&cmdmod); + cmdmod = save_cmdmod; + + if (status) { + return lua_error(L); + } else if (ERROR_SET(&err)) { + nlua_push_errstr(L, "%s", err.msg); + api_clear_error(&err); + return lua_error(L); + } + + return rets; +} + // Access to internal functions. For use in runtime/ static void nlua_state_add_internal(lua_State *const lstate) { @@ -582,6 +679,9 @@ static void nlua_state_add_internal(lua_State *const lstate) // _updatefolds lua_pushcfunction(lstate, &nlua_foldupdate); lua_setfield(lstate, -2, "_foldupdate"); + + lua_pushcfunction(lstate, &nlua_with); + lua_setfield(lstate, -2, "_with_c"); } void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 98d5d8c6cb..6a0dc10214 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -31,8 +31,6 @@ #include "nvim/msgpack_rpc/packer.h" #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/os/input.h" -#include "nvim/rbuffer.h" -#include "nvim/rbuffer_defs.h" #include "nvim/types_defs.h" #include "nvim/ui.h" #include "nvim/ui_client.h" @@ -202,10 +200,25 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem return frame.errored ? NIL : frame.result; } -static void receive_msgpack(RStream *stream, RBuffer *rbuf, size_t c, void *data, bool eof) +static size_t receive_msgpack(RStream *stream, const char *rbuf, size_t c, void *data, bool eof) { Channel *channel = data; channel_incref(channel); + size_t consumed = 0; + + DLOG("ch %" PRIu64 ": parsing %zu bytes from msgpack Stream: %p", + channel->id, c, (void *)stream); + + if (c > 0) { + Unpacker *p = channel->rpc.unpacker; + p->read_ptr = rbuf; + p->read_size = c; + parse_msgpack(channel); + + if (!unpacker_closed(p)) { + consumed = c - p->read_size; + } + } if (eof) { channel_close(channel->id, kChannelPartRpc, NULL); @@ -213,25 +226,10 @@ static void receive_msgpack(RStream *stream, RBuffer *rbuf, size_t c, void *data snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client", channel->id); chan_close_with_error(channel, buf, LOGLVL_INF); - goto end; - } - - DLOG("ch %" PRIu64 ": parsing %zu bytes from msgpack Stream: %p", - channel->id, rbuffer_size(rbuf), (void *)stream); - - Unpacker *p = channel->rpc.unpacker; - size_t size = 0; - p->read_ptr = rbuffer_read_ptr(rbuf, &size); - p->read_size = size; - parse_msgpack(channel); - - if (!unpacker_closed(p)) { - size_t consumed = size - p->read_size; - rbuffer_consumed_compact(rbuf, consumed); } -end: channel_decref(channel); + return consumed; } static ChannelCallFrame *find_call_frame(RpcState *rpc, uint32_t request_id) diff --git a/src/nvim/option.c b/src/nvim/option.c index 41b9aaf9a7..301e6e9cea 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -6192,10 +6192,10 @@ bool can_bs(int what) return vim_strchr(p_bs, what) != NULL; } -/// Get the local or global value of 'backupcopy'. +/// Get the local or global value of 'backupcopy' flags. /// /// @param buf The buffer. -unsigned get_bkc_value(buf_T *buf) +unsigned get_bkc_flags(buf_T *buf) { return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags; } @@ -6211,7 +6211,7 @@ char *get_flp_value(buf_T *buf) return buf->b_p_flp; } -/// Get the local or global value of the 'virtualedit' flags. +/// Get the local or global value of 'virtualedit' flags. unsigned get_ve_flags(win_T *wp) { return (wp->w_ve_flags ? wp->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU); diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index 5f372b2376..585c4964e2 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -21,8 +21,6 @@ #include "nvim/os/fileio.h" #include "nvim/os/fs.h" #include "nvim/os/os_defs.h" -#include "nvim/rbuffer.h" -#include "nvim/rbuffer_defs.h" #include "nvim/types_defs.h" #ifdef HAVE_SYS_UIO_H diff --git a/src/nvim/os/fileio_defs.h b/src/nvim/os/fileio_defs.h index 63b6f51c17..0f76fdb2aa 100644 --- a/src/nvim/os/fileio_defs.h +++ b/src/nvim/os/fileio_defs.h @@ -4,7 +4,6 @@ #include <stdint.h> #include "nvim/func_attr.h" -#include "nvim/rbuffer_defs.h" /// Structure used to read from/write to file typedef struct { diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 63eca0b6da..ea21a32230 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -27,8 +27,6 @@ #include "nvim/os/os_defs.h" #include "nvim/os/time.h" #include "nvim/profile.h" -#include "nvim/rbuffer.h" -#include "nvim/rbuffer_defs.h" #include "nvim/state.h" #include "nvim/state_defs.h" @@ -62,7 +60,7 @@ void input_start(void) } used_stdin = true; - rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO, READ_BUFFER_SIZE); + rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO); rstream_start(&read_stream, input_read_cb, NULL); } @@ -157,7 +155,7 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e if (maxlen && input_available()) { restart_cursorhold_wait(tb_change_cnt); - // Safe to convert rbuffer_read to int, it will never overflow since + // Safe to convert `to_read` to int, it will never overflow since // INPUT_BUFFER_SIZE fits in an int size_t to_read = MIN((size_t)maxlen, input_available()); memcpy(buf, input_read_pos, to_read); @@ -497,17 +495,15 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events) return input_eof ? kInputEof : kInputNone; } -static void input_read_cb(RStream *stream, RBuffer *buf, size_t c, void *data, bool at_eof) +static size_t input_read_cb(RStream *stream, const char *buf, size_t c, void *data, bool at_eof) { if (at_eof) { input_eof = true; } - assert(input_space() >= rbuffer_size(buf)); - RBUFFER_UNTIL_EMPTY(buf, ptr, len) { - input_enqueue_raw(ptr, len); - rbuffer_consumed(buf, len); - } + assert(input_space() >= c); + input_enqueue_raw(buf, c); + return c; } static void process_ctrl_c(void) diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 026f14ebc8..b66faa2285 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -40,8 +40,6 @@ #include "nvim/path.h" #include "nvim/pos_defs.h" #include "nvim/profile.h" -#include "nvim/rbuffer.h" -#include "nvim/rbuffer_defs.h" #include "nvim/state_defs.h" #include "nvim/strings.h" #include "nvim/tag.h" @@ -49,17 +47,11 @@ #include "nvim/ui.h" #include "nvim/vim_defs.h" -#define DYNAMIC_BUFFER_INIT { NULL, 0, 0 } #define NS_1_SECOND 1000000000U // 1 second, in nanoseconds #define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data. #define SHELL_SPECIAL "\t \"&'$;<>()\\|" -typedef struct { - char *data; - size_t cap, len; -} DynamicBuffer; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/shell.c.generated.h" #endif @@ -669,7 +661,7 @@ char *shell_argv_to_str(char **const argv) /// @return shell command exit code int os_call_shell(char *cmd, ShellOpts opts, char *extra_args) { - DynamicBuffer input = DYNAMIC_BUFFER_INIT; + StringBuilder input = KV_INITIAL_VALUE; char *output = NULL; char **output_ptr = NULL; int current_state = State; @@ -698,9 +690,9 @@ int os_call_shell(char *cmd, ShellOpts opts, char *extra_args) size_t nread; int exitcode = do_os_system(shell_build_argv(cmd, extra_args), - input.data, input.len, output_ptr, &nread, + input.items, input.size, output_ptr, &nread, emsg_silent, forward_output); - xfree(input.data); + kv_destroy(input); if (output) { write_output(output, nread, true); @@ -864,7 +856,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu bool has_input = (input != NULL && input[0] != NUL); // the output buffer - DynamicBuffer buf = DYNAMIC_BUFFER_INIT; + StringBuilder buf = KV_INITIAL_VALUE; stream_read_cb data_cb = system_data_cb; if (nread) { *nread = 0; @@ -907,9 +899,9 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu if (has_input) { wstream_init(&proc->in, 0); } - rstream_init(&proc->out, 0); + rstream_init(&proc->out); rstream_start(&proc->out, data_cb, &buf); - rstream_init(&proc->err, 0); + rstream_init(&proc->err); rstream_start(&proc->err, data_cb, &buf); // write the input, if any @@ -952,18 +944,17 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu // prepare the out parameters if requested if (output) { - if (buf.len == 0) { + assert(nread); + if (buf.size == 0) { // no data received from the process, return NULL *output = NULL; - xfree(buf.data); + *nread = 0; + kv_destroy(buf); } else { + *nread = buf.size; // NUL-terminate to make the output directly usable as a C string - buf.data[buf.len] = NUL; - *output = buf.data; - } - - if (nread) { - *nread = buf.len; + kv_push(buf, NUL); + *output = buf.items; } } @@ -973,29 +964,11 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu return exitcode; } -/// - ensures at least `desired` bytes in buffer -/// -/// TODO(aktau): fold with kvec/garray -static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired) -{ - if (buf->cap >= desired) { - assert(buf->data); - return; - } - - buf->cap = desired; - kv_roundup32(buf->cap); - buf->data = xrealloc(buf->data, buf->cap); -} - -static void system_data_cb(RStream *stream, RBuffer *buf, size_t count, void *data, bool eof) +static size_t system_data_cb(RStream *stream, const char *buf, size_t count, void *data, bool eof) { - DynamicBuffer *dbuf = data; - - size_t nread = buf->size; - dynamic_buffer_ensure(dbuf, dbuf->len + nread + 1); - rbuffer_read(buf, dbuf->data + dbuf->len, nread); - dbuf->len += nread; + StringBuilder *dbuf = data; + kv_concat_len(*dbuf, buf, count); + return count; } /// Tracks output received for the current executing shell command, and displays @@ -1078,7 +1051,7 @@ static bool out_data_decide_throttle(size_t size) /// /// @param output Data to save, or NULL to invoke a special mode. /// @param size Length of `output`. -static void out_data_ring(char *output, size_t size) +static void out_data_ring(const char *output, size_t size) { #define MAX_CHUNK_SIZE (OUT_DATA_THRESHOLD / 2) static char last_skipped[MAX_CHUNK_SIZE]; // Saved output. @@ -1120,11 +1093,11 @@ static void out_data_ring(char *output, size_t size) /// @param output Data to append to screen lines. /// @param count Size of data. /// @param eof If true, there will be no more data output. -static void out_data_append_to_screen(char *output, size_t *count, bool eof) +static void out_data_append_to_screen(const char *output, size_t *count, bool eof) FUNC_ATTR_NONNULL_ALL { - char *p = output; - char *end = output + *count; + const char *p = output; + const char *end = output + *count; while (p < end) { if (*p == '\n' || *p == '\r' || *p == TAB || *p == BELL) { msg_putchar_attr((uint8_t)(*p), 0); @@ -1152,25 +1125,16 @@ end: ui_flush(); } -static void out_data_cb(RStream *stream, RBuffer *buf, size_t count, void *data, bool eof) +static size_t out_data_cb(RStream *stream, const char *ptr, size_t count, void *data, bool eof) { - size_t cnt; - char *ptr = rbuffer_read_ptr(buf, &cnt); - - if (ptr != NULL && cnt > 0 - && out_data_decide_throttle(cnt)) { // Skip output above a threshold. + if (count > 0 && out_data_decide_throttle(count)) { // Skip output above a threshold. // Save the skipped output. If it is the final chunk, we display it later. - out_data_ring(ptr, cnt); - } else if (ptr != NULL) { - out_data_append_to_screen(ptr, &cnt, eof); - } - - if (cnt) { - rbuffer_consumed(buf, cnt); + out_data_ring(ptr, count); + } else if (count > 0) { + out_data_append_to_screen(ptr, &count, eof); } - // Move remaining data to start of buffer, so the buffer can never wrap around. - rbuffer_reset(buf); + return count; } /// Parses a command string into a sequence of words, taking quotes into @@ -1234,7 +1198,7 @@ static size_t word_length(const char *str) /// event loop starts. If we don't (by writing in chunks returned by `ml_get`) /// the buffer being modified might get modified by reading from the process /// before we finish writing. -static void read_input(DynamicBuffer *buf) +static void read_input(StringBuilder *buf) { size_t written = 0; size_t len = 0; @@ -1248,14 +1212,11 @@ static void read_input(DynamicBuffer *buf) } else if (lp[written] == NL) { // NL -> NUL translation len = 1; - dynamic_buffer_ensure(buf, buf->len + len); - buf->data[buf->len++] = NUL; + kv_push(*buf, NUL); } else { char *s = vim_strchr(lp + written, NL); len = s == NULL ? l : (size_t)(s - (lp + written)); - dynamic_buffer_ensure(buf, buf->len + len); - memcpy(buf->data + buf->len, lp + written, len); - buf->len += len; + kv_concat_len(*buf, lp + written, len); } if (len == l) { @@ -1264,8 +1225,7 @@ static void read_input(DynamicBuffer *buf) || (!curbuf->b_p_bin && curbuf->b_p_fixeol) || (lnum != curbuf->b_no_eol_lnum && (lnum != curbuf->b_ml.ml_line_count || curbuf->b_p_eol))) { - dynamic_buffer_ensure(buf, buf->len + 1); - buf->data[buf->len++] = NL; + kv_push(*buf, NL); } lnum++; if (lnum > curbuf->b_op_end.lnum) { diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c deleted file mode 100644 index 493c079d4c..0000000000 --- a/src/nvim/rbuffer.c +++ /dev/null @@ -1,230 +0,0 @@ -#include <assert.h> -#include <stdbool.h> -#include <stddef.h> -#include <string.h> - -#include "nvim/macros_defs.h" -#include "nvim/memory.h" -#include "nvim/rbuffer.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "rbuffer.c.generated.h" -#endif - -/// Creates a new `RBuffer` instance. -RBuffer *rbuffer_new(size_t capacity) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET -{ - if (!capacity) { - capacity = 0x10000; - } - - RBuffer *rv = xcalloc(1, sizeof(RBuffer) + capacity); - rv->full_cb = rv->nonfull_cb = NULL; - rv->data = NULL; - rv->size = 0; - rv->write_ptr = rv->read_ptr = rv->start_ptr; - rv->end_ptr = rv->start_ptr + capacity; - rv->temp = NULL; - return rv; -} - -void rbuffer_free(RBuffer *buf) FUNC_ATTR_NONNULL_ALL -{ - xfree(buf->temp); - xfree(buf); -} - -/// Return a pointer to a raw buffer containing the first empty slot available -/// for writing. The second argument is a pointer to the maximum number of -/// bytes that could be written. -/// -/// It is necessary to call this function twice to ensure all empty space was -/// used. See RBUFFER_UNTIL_FULL for a macro that simplifies this task. -char *rbuffer_write_ptr(RBuffer *buf, size_t *write_count) FUNC_ATTR_NONNULL_ALL -{ - if (buf->size == rbuffer_capacity(buf)) { - *write_count = 0; - return NULL; - } - - if (buf->write_ptr >= buf->read_ptr) { - *write_count = (size_t)(buf->end_ptr - buf->write_ptr); - } else { - *write_count = (size_t)(buf->read_ptr - buf->write_ptr); - } - - return buf->write_ptr; -} - -// Reset an RBuffer so read_ptr is at the beginning of the memory. If -// necessary, this moves existing data by allocating temporary memory. -void rbuffer_reset(RBuffer *buf) FUNC_ATTR_NONNULL_ALL -{ - size_t temp_size; - if ((temp_size = rbuffer_size(buf))) { - if (buf->temp == NULL) { - buf->temp = xcalloc(1, rbuffer_capacity(buf)); - } - rbuffer_read(buf, buf->temp, buf->size); - } - buf->read_ptr = buf->write_ptr = buf->start_ptr; - if (temp_size) { - rbuffer_write(buf, buf->temp, temp_size); - } -} - -/// Adjust `rbuffer` write pointer to reflect produced data. This is called -/// automatically by `rbuffer_write`, but when using `rbuffer_write_ptr` -/// directly, this needs to called after the data was copied to the internal -/// buffer. The write pointer will be wrapped if required. -void rbuffer_produced(RBuffer *buf, size_t count) FUNC_ATTR_NONNULL_ALL -{ - assert(count && count <= rbuffer_space(buf)); - - buf->write_ptr += count; - if (buf->write_ptr >= buf->end_ptr) { - // wrap around - buf->write_ptr -= rbuffer_capacity(buf); - } - - buf->size += count; - if (buf->full_cb && !rbuffer_space(buf)) { - buf->full_cb(buf, buf->data); - } -} - -/// Return a pointer to a raw buffer containing the first byte available -/// for reading. The second argument is a pointer to the maximum number of -/// bytes that could be read. -/// -/// It is necessary to call this function twice to ensure all available bytes -/// were read. See RBUFFER_UNTIL_EMPTY for a macro that simplifies this task. -char *rbuffer_read_ptr(RBuffer *buf, size_t *read_count) FUNC_ATTR_NONNULL_ALL -{ - if (!buf->size) { - *read_count = 0; - return buf->read_ptr; - } - - if (buf->read_ptr < buf->write_ptr) { - *read_count = (size_t)(buf->write_ptr - buf->read_ptr); - } else { - *read_count = (size_t)(buf->end_ptr - buf->read_ptr); - } - - return buf->read_ptr; -} - -/// Adjust `rbuffer` read pointer to reflect consumed data. This is called -/// automatically by `rbuffer_read`, but when using `rbuffer_read_ptr` -/// directly, this needs to called after the data was copied from the internal -/// buffer. The read pointer will be wrapped if required. -void rbuffer_consumed(RBuffer *buf, size_t count) - FUNC_ATTR_NONNULL_ALL -{ - if (count == 0) { - return; - } - assert(count <= buf->size); - - buf->read_ptr += count; - if (buf->read_ptr >= buf->end_ptr) { - buf->read_ptr -= rbuffer_capacity(buf); - } - - bool was_full = buf->size == rbuffer_capacity(buf); - buf->size -= count; - if (buf->nonfull_cb && was_full) { - buf->nonfull_cb(buf, buf->data); - } -} - -/// Use instead of rbuffer_consumed to use rbuffer in a linear, non-cyclic fashion. -/// -/// This is generally useful if we can guarantee to parse all input -/// except some small incomplete token, like when parsing msgpack. -void rbuffer_consumed_compact(RBuffer *buf, size_t count) - FUNC_ATTR_NONNULL_ALL -{ - assert(buf->read_ptr <= buf->write_ptr); - rbuffer_consumed(buf, count); - if (buf->read_ptr > buf->start_ptr) { - assert((size_t)(buf->write_ptr - buf->read_ptr) == buf->size - || buf->write_ptr == buf->start_ptr); - memmove(buf->start_ptr, buf->read_ptr, buf->size); - buf->read_ptr = buf->start_ptr; - buf->write_ptr = buf->read_ptr + buf->size; - } -} - -// Higher level functions for copying from/to RBuffer instances and data -// pointers -size_t rbuffer_write(RBuffer *buf, const char *src, size_t src_size) - FUNC_ATTR_NONNULL_ALL -{ - size_t size = src_size; - - RBUFFER_UNTIL_FULL(buf, wptr, wcnt) { - size_t copy_count = MIN(src_size, wcnt); - memcpy(wptr, src, copy_count); - rbuffer_produced(buf, copy_count); - - if (!(src_size -= copy_count)) { - return size; - } - - src += copy_count; - } - - return size - src_size; -} - -size_t rbuffer_read(RBuffer *buf, char *dst, size_t dst_size) - FUNC_ATTR_NONNULL_ALL -{ - size_t size = dst_size; - - RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) { - size_t copy_count = MIN(dst_size, rcnt); - memcpy(dst, rptr, copy_count); - rbuffer_consumed(buf, copy_count); - - if (!(dst_size -= copy_count)) { - return size; - } - - dst += copy_count; - } - - return size - dst_size; -} - -char *rbuffer_get(RBuffer *buf, size_t index) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET -{ - assert(index < buf->size); - char *rptr = buf->read_ptr + index; - if (rptr >= buf->end_ptr) { - rptr -= rbuffer_capacity(buf); - } - return rptr; -} - -int rbuffer_cmp(RBuffer *buf, const char *str, size_t count) - FUNC_ATTR_NONNULL_ALL -{ - assert(count <= buf->size); - size_t rcnt; - rbuffer_read_ptr(buf, &rcnt); - size_t n = MIN(count, rcnt); - int rv = memcmp(str, buf->read_ptr, n); - count -= n; - size_t remaining = buf->size - rcnt; - - if (rv || !count || !remaining) { - return rv; - } - - return memcmp(str + n, buf->start_ptr, count); -} diff --git a/src/nvim/rbuffer.h b/src/nvim/rbuffer.h deleted file mode 100644 index 942e1f2365..0000000000 --- a/src/nvim/rbuffer.h +++ /dev/null @@ -1,71 +0,0 @@ -// Specialized ring buffer. This is basically an array that wraps read/write -// pointers around the memory region. It should be more efficient than the old -// RBuffer which required memmove() calls to relocate read/write positions. -// -// The main purpose of RBuffer is simplify memory management when reading from -// uv_stream_t instances: -// -// - The event loop writes data to a RBuffer, advancing the write pointer -// - The main loop reads data, advancing the read pointer -// - If the buffer becomes full(size == capacity) the rstream is temporarily -// stopped(automatic backpressure handling) -// -// Reference: http://en.wikipedia.org/wiki/Circular_buffer -#pragma once - -#include <stddef.h> -#include <stdint.h> - -#include "nvim/rbuffer_defs.h" // IWYU pragma: keep - -// Macros that simplify working with the read/write pointers directly by hiding -// ring buffer wrap logic. Some examples: -// -// - Pass the write pointer to a function(write_data) that incrementally -// produces data, returning the number of bytes actually written to the -// ring buffer: -// -// RBUFFER_UNTIL_FULL(rbuf, ptr, cnt) -// rbuffer_produced(rbuf, write_data(state, ptr, cnt)); -// -// - Pass the read pointer to a function(read_data) that incrementally -// consumes data, returning the number of bytes actually read from the -// ring buffer: -// -// RBUFFER_UNTIL_EMPTY(rbuf, ptr, cnt) -// rbuffer_consumed(rbuf, read_data(state, ptr, cnt)); -// -// Note that the rbuffer_{produced,consumed} calls are necessary or these macros -// create infinite loops -#define RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) \ - for (size_t rcnt = 0, _r = 1; _r; _r = 0) \ - for (char *rptr = rbuffer_read_ptr(buf, &rcnt); \ - buf->size; \ - rptr = rbuffer_read_ptr(buf, &rcnt)) - -#define RBUFFER_UNTIL_FULL(buf, wptr, wcnt) \ - for (size_t wcnt = 0, _r = 1; _r; _r = 0) \ - for (char *wptr = rbuffer_write_ptr(buf, &wcnt); \ - rbuffer_space(buf); \ - wptr = rbuffer_write_ptr(buf, &wcnt)) - -// Iteration -#define RBUFFER_EACH(buf, c, i) \ - for (size_t i = 0; \ - i < buf->size; \ - i = buf->size) \ - for (char c = 0; \ - i < buf->size ? ((int)(c = *rbuffer_get(buf, i))) || 1 : 0; \ - i++) - -#define RBUFFER_EACH_REVERSE(buf, c, i) \ - for (size_t i = buf->size; \ - i != SIZE_MAX; \ - i = SIZE_MAX) \ - for (char c = 0; \ - i-- > 0 ? ((int)(c = *rbuffer_get(buf, i))) || 1 : 0; \ - ) - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "rbuffer.h.generated.h" -#endif diff --git a/src/nvim/rbuffer_defs.h b/src/nvim/rbuffer_defs.h deleted file mode 100644 index 51dc349846..0000000000 --- a/src/nvim/rbuffer_defs.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include <stddef.h> - -#include "nvim/func_attr.h" - -typedef struct rbuffer RBuffer; -/// Type of function invoked during certain events: -/// - When the RBuffer switches to the full state -/// - When the RBuffer switches to the non-full state -typedef void (*rbuffer_callback)(RBuffer *buf, void *data); - -struct rbuffer { - rbuffer_callback full_cb, nonfull_cb; - void *data; - size_t size; - // helper memory used to by rbuffer_reset if required - char *temp; - char *end_ptr, *read_ptr, *write_ptr; - char start_ptr[]; -}; - -static inline size_t rbuffer_size(RBuffer *buf) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; - -static inline size_t rbuffer_size(RBuffer *buf) -{ - return buf->size; -} - -static inline size_t rbuffer_capacity(RBuffer *buf) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; - -static inline size_t rbuffer_capacity(RBuffer *buf) -{ - return (size_t)(buf->end_ptr - buf->start_ptr); -} - -static inline size_t rbuffer_space(RBuffer *buf) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; - -static inline size_t rbuffer_space(RBuffer *buf) -{ - return rbuffer_capacity(buf) - buf->size; -} diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 5130678a81..a5768cfc06 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -15,7 +15,6 @@ #include "nvim/option_vars.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" -#include "nvim/rbuffer.h" #include "nvim/strings.h" #include "nvim/tui/input.h" #include "nvim/tui/input_defs.h" @@ -153,7 +152,7 @@ void tinput_init(TermInput *input, Loop *loop) termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS); // setup input handle - rstream_init_fd(loop, &input->read_stream, input->in_fd, READ_STREAM_SIZE); + rstream_init_fd(loop, &input->read_stream, input->in_fd); // initialize a timer handle for handling ESC with libtermkey uv_timer_init(&loop->uv, &input->timer_handle); @@ -211,9 +210,9 @@ static void tinput_flush(TermInput *input) input->key_buffer_len = 0; } -static void tinput_enqueue(TermInput *input, char *buf, size_t size) +static void tinput_enqueue(TermInput *input, const char *buf, size_t size) { - if (input->key_buffer_len > KEY_BUFFER_SIZE - 0xff) { + if (input->key_buffer_len > KEY_BUFFER_SIZE - size) { // don't ever let the buffer get too full or we risk putting incomplete keys into it tinput_flush(input); } @@ -463,8 +462,10 @@ static void tinput_timer_cb(uv_timer_t *handle) TermInput *input = handle->data; // If the raw buffer is not empty, process the raw buffer first because it is // processing an incomplete bracketed paster sequence. - if (rbuffer_size(input->read_stream.buffer)) { - handle_raw_buffer(input, true); + size_t size = rstream_available(&input->read_stream); + if (size) { + size_t consumed = handle_raw_buffer(input, true, input->read_stream.read_pos, size); + rstream_consume(&input->read_stream, consumed); } tk_getkeys(input, true); tinput_flush(input); @@ -478,39 +479,37 @@ static void tinput_timer_cb(uv_timer_t *handle) /// /// @param input the input stream /// @return true iff handle_focus_event consumed some input -static bool handle_focus_event(TermInput *input) +static size_t handle_focus_event(TermInput *input, const char *ptr, size_t size) { - if (rbuffer_size(input->read_stream.buffer) > 2 - && (!rbuffer_cmp(input->read_stream.buffer, "\x1b[I", 3) - || !rbuffer_cmp(input->read_stream.buffer, "\x1b[O", 3))) { - bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I'; - // Advance past the sequence - rbuffer_consumed(input->read_stream.buffer, 3); + if (size >= 3 + && (!memcmp(ptr, "\x1b[I", 3) + || !memcmp(ptr, "\x1b[O", 3))) { + bool focus_gained = ptr[2] == 'I'; MAXSIZE_TEMP_ARRAY(args, 1); ADD_C(args, BOOLEAN_OBJ(focus_gained)); rpc_send_event(ui_client_channel_id, "nvim_ui_set_focus", args); - return true; + return 3; // Advance past the sequence } - return false; + return 0; } #define START_PASTE "\x1b[200~" #define END_PASTE "\x1b[201~" -static HandleState handle_bracketed_paste(TermInput *input) +static size_t handle_bracketed_paste(TermInput *input, const char *ptr, size_t size, + bool *incomplete) { - size_t buf_size = rbuffer_size(input->read_stream.buffer); - if (buf_size > 5 - && (!rbuffer_cmp(input->read_stream.buffer, START_PASTE, 6) - || !rbuffer_cmp(input->read_stream.buffer, END_PASTE, 6))) { - bool enable = *rbuffer_get(input->read_stream.buffer, 4) == '0'; + if (size >= 6 + && (!memcmp(ptr, START_PASTE, 6) + || !memcmp(ptr, END_PASTE, 6))) { + bool enable = ptr[4] == '0'; if (input->paste && enable) { - return kNotApplicable; // Pasting "start paste" code literally. + return 0; // Pasting "start paste" code literally. } + // Advance past the sequence - rbuffer_consumed(input->read_stream.buffer, 6); if (!!input->paste == enable) { - return kComplete; // Spurious "disable paste" code. + return 6; // Spurious "disable paste" code. } if (enable) { @@ -525,15 +524,15 @@ static HandleState handle_bracketed_paste(TermInput *input) // Paste phase: "disabled". input->paste = 0; } - return kComplete; - } else if (buf_size < 6 - && (!rbuffer_cmp(input->read_stream.buffer, START_PASTE, buf_size) - || !rbuffer_cmp(input->read_stream.buffer, - END_PASTE, buf_size))) { + return 6; + } else if (size < 6 + && (!memcmp(ptr, START_PASTE, size) + || !memcmp(ptr, END_PASTE, size))) { // Wait for further input, as the sequence may be split. - return kIncomplete; + *incomplete = true; + return 0; } - return kNotApplicable; + return 0; } /// Handle an OSC or DCS response sequence from the terminal. @@ -644,20 +643,31 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key) } } -static void handle_raw_buffer(TermInput *input, bool force) +static size_t handle_raw_buffer(TermInput *input, bool force, const char *data, size_t size) { - HandleState is_paste = kNotApplicable; + const char *ptr = data; do { - if (!force - && (handle_focus_event(input) - || (is_paste = handle_bracketed_paste(input)) != kNotApplicable)) { - if (is_paste == kIncomplete) { + if (!force) { + size_t consumed = handle_focus_event(input, ptr, size); + if (consumed) { + ptr += consumed; + size -= consumed; + continue; + } + + bool incomplete = false; + consumed = handle_bracketed_paste(input, ptr, size, &incomplete); + if (incomplete) { + assert(consumed == 0); // Wait for the next input, leaving it in the raw buffer due to an // incomplete sequence. - return; + return (size_t)(ptr - data); + } else if (consumed) { + ptr += consumed; + size -= consumed; + continue; } - continue; } // @@ -666,55 +676,47 @@ static void handle_raw_buffer(TermInput *input, bool force) // calls (above) depend on this. // size_t count = 0; - RBUFFER_EACH(input->read_stream.buffer, c, i) { + for (size_t i = 0; i < size; i++) { count = i + 1; - if (c == '\x1b' && count > 1) { + if (ptr[i] == '\x1b' && count > 1) { count--; break; } } // Push bytes directly (paste). if (input->paste) { - RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) { - size_t consumed = MIN(count, len); - assert(consumed <= input->read_stream.buffer->size); - tinput_enqueue(input, ptr, consumed); - rbuffer_consumed(input->read_stream.buffer, consumed); - if (!(count -= consumed)) { - break; - } - } + tinput_enqueue(input, ptr, count); + ptr += count; + size -= count; continue; } + // Push through libtermkey (translates to "<keycode>" strings, etc.). - RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) { - const size_t size = MIN(count, len); - if (size > termkey_get_buffer_remaining(input->tk)) { + { + const size_t to_use = MIN(count, size); + if (to_use > termkey_get_buffer_remaining(input->tk)) { // We are processing a very long escape sequence. Increase termkey's // internal buffer size. We don't handle out of memory situations so // abort if it fails - const size_t delta = size - termkey_get_buffer_remaining(input->tk); + const size_t delta = to_use - termkey_get_buffer_remaining(input->tk); const size_t bufsize = termkey_get_buffer_size(input->tk); if (!termkey_set_buffer_size(input->tk, MAX(bufsize + delta, bufsize * 2))) { abort(); } } - size_t consumed = termkey_push_bytes(input->tk, ptr, size); + size_t consumed = termkey_push_bytes(input->tk, ptr, to_use); // We resize termkey's buffer when it runs out of space, so this should // never happen - assert(consumed <= rbuffer_size(input->read_stream.buffer)); - rbuffer_consumed(input->read_stream.buffer, consumed); + assert(consumed <= to_use); + ptr += consumed; + size -= consumed; // Process the input buffer now for any keys tk_getkeys(input, false); - - if (!(count -= consumed)) { - break; - } } - } while (rbuffer_size(input->read_stream.buffer)); + } while (size); const size_t tk_size = termkey_get_buffer_size(input->tk); const size_t tk_remaining = termkey_get_buffer_remaining(input->tk); @@ -726,23 +728,25 @@ static void handle_raw_buffer(TermInput *input, bool force) abort(); } } + + return (size_t)(ptr - data); } -static void tinput_read_cb(RStream *stream, RBuffer *buf, size_t count_, void *data, bool eof) +static size_t tinput_read_cb(RStream *stream, const char *buf, size_t count_, void *data, bool eof) { TermInput *input = data; + size_t consumed = handle_raw_buffer(input, false, buf, count_); + tinput_flush(input); + if (eof) { loop_schedule_fast(&main_loop, event_create(tinput_done_event, NULL)); - return; + return consumed; } - handle_raw_buffer(input, false); - tinput_flush(input); - // An incomplete sequence was found. Leave it in the raw buffer and wait for // the next input. - if (rbuffer_size(input->read_stream.buffer)) { + if (consumed < count_) { // If 'ttimeout' is not set, start the timer with a timeout of 0 to process // the next input. int64_t ms = input->ttimeout @@ -750,11 +754,7 @@ static void tinput_read_cb(RStream *stream, RBuffer *buf, size_t count_, void *d // Stop the current timer if already running uv_timer_stop(&input->timer_handle); uv_timer_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0); - return; } - // Make sure the next input escape sequence fits into the ring buffer without - // wraparound, else it could be misinterpreted (because rbuffer_read_ptr() - // exposes the underlying buffer to callers unaware of the wraparound). - rbuffer_reset(input->read_stream.buffer); + return consumed; } diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index c594228c07..8d0c0c20e9 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -5,7 +5,6 @@ #include <uv.h> #include "nvim/event/defs.h" -#include "nvim/rbuffer_defs.h" #include "nvim/tui/input_defs.h" // IWYU pragma: keep #include "nvim/tui/tui_defs.h" #include "nvim/types_defs.h" @@ -17,7 +16,7 @@ typedef enum { kKeyEncodingXterm, ///< Xterm's modifyOtherKeys encoding (XTMODKEYS) } KeyEncoding; -#define KEY_BUFFER_SIZE 0xfff +#define KEY_BUFFER_SIZE 0x1000 typedef struct { int in_fd; // Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk @@ -40,12 +39,6 @@ typedef struct { size_t key_buffer_len; } TermInput; -typedef enum { - kIncomplete = -1, - kNotApplicable = 0, - kComplete = 1, -} HandleState; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/input.h.generated.h" #endif diff --git a/test/functional/lua/glob_spec.lua b/test/functional/lua/glob_spec.lua index 56cd4c9bb5..b3e1b79ee7 100644 --- a/test/functional/lua/glob_spec.lua +++ b/test/functional/lua/glob_spec.lua @@ -161,7 +161,7 @@ describe('glob', function() eq(false, match('{ab,cd}', 'a')) eq(true, match('{ab,cd}', 'cd')) eq(true, match('{a,b,c}', 'c')) - eq(true, match('{a,{b,c}}', 'c')) + eq(false, match('{a,{b,c}}', 'c')) -- {} cannot nest end) it('should match [] groups', function() @@ -223,6 +223,17 @@ describe('glob', function() eq(true, match('{[0-9],[a-z]}', '0')) eq(true, match('{[0-9],[a-z]}', 'a')) eq(false, match('{[0-9],[a-z]}', 'A')) + + -- glob is from willRename filter in typescript-language-server + -- https://github.com/typescript-language-server/typescript-language-server/blob/b224b878652438bcdd639137a6b1d1a6630129e4/src/lsp-server.ts#L266 + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.js')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.ts')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.mts')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.mjs')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.cjs')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.cts')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.jsx')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.tsx')) end) end) end) diff --git a/test/functional/lua/with_spec.lua b/test/functional/lua/with_spec.lua new file mode 100644 index 0000000000..36dee9630a --- /dev/null +++ b/test/functional/lua/with_spec.lua @@ -0,0 +1,292 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') + +local fn = n.fn +local api = n.api +local command = n.command +local eq = t.eq +local exec_lua = n.exec_lua +local matches = t.matches +local pcall_err = t.pcall_err + +before_each(function() + n.clear() +end) + +describe('vim._with {buf = }', function() + it('does not trigger autocmd', function() + exec_lua [[ + local new = vim.api.nvim_create_buf(false, true) + vim.api.nvim_create_autocmd( { 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave' }, { + callback = function() _G.n = (_G.n or 0) + 1 end + }) + vim._with({buf = new}, function() + end) + assert(_G.n == nil) + ]] + end) + + it('trigger autocmd if changed within context', function() + exec_lua [[ + local new = vim.api.nvim_create_buf(false, true) + vim.api.nvim_create_autocmd( { 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave' }, { + callback = function() _G.n = (_G.n or 0) + 1 end + }) + vim._with({}, function() + vim.api.nvim_set_current_buf(new) + assert(_G.n ~= nil) + end) + ]] + end) + + it('can access buf options', function() + local buf1 = api.nvim_get_current_buf() + local buf2 = exec_lua [[ + buf2 = vim.api.nvim_create_buf(false, true) + return buf2 + ]] + + eq(false, api.nvim_get_option_value('autoindent', { buf = buf1 })) + eq(false, api.nvim_get_option_value('autoindent', { buf = buf2 })) + + local val = exec_lua [[ + return vim._with({buf = buf2}, function() + vim.cmd "set autoindent" + return vim.api.nvim_get_current_buf() + end) + ]] + + eq(false, api.nvim_get_option_value('autoindent', { buf = buf1 })) + eq(true, api.nvim_get_option_value('autoindent', { buf = buf2 })) + eq(buf1, api.nvim_get_current_buf()) + eq(buf2, val) + end) + + it('does not cause ml_get errors with invalid visual selection', function() + exec_lua [[ + local api = vim.api + local t = function(s) return api.nvim_replace_termcodes(s, true, true, true) end + api.nvim_buf_set_lines(0, 0, -1, true, {"a", "b", "c"}) + api.nvim_feedkeys(t "G<C-V>", "txn", false) + vim._with({buf = api.nvim_create_buf(false, true)}, function() vim.cmd "redraw" end) + ]] + end) + + it('can be nested crazily with hidden buffers', function() + eq( + true, + exec_lua([[ + local function scratch_buf_call(fn) + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_option_value('cindent', true, {buf = buf}) + return vim._with({buf = buf}, function() + return vim.api.nvim_get_current_buf() == buf + and vim.api.nvim_get_option_value('cindent', {buf = buf}) + and fn() + end) and vim.api.nvim_buf_delete(buf, {}) == nil + end + + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return true + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + ]]) + ) + end) + + it('can return values by reference', function() + eq( + { 4, 7 }, + exec_lua [[ + local val = {4, 10} + local ref = vim._with({ buf = 0}, function() return val end) + ref[2] = 7 + return val + ]] + ) + end) +end) + +describe('vim._with {win = }', function() + it('does not trigger autocmd', function() + exec_lua [[ + local old = vim.api.nvim_get_current_win() + vim.cmd("new") + local new = vim.api.nvim_get_current_win() + vim.api.nvim_create_autocmd( { 'WinEnter', 'WinLeave' }, { + callback = function() _G.n = (_G.n or 0) + 1 end + }) + vim._with({win = old}, function() + end) + assert(_G.n == nil) + ]] + end) + + it('trigger autocmd if changed within context', function() + exec_lua [[ + local old = vim.api.nvim_get_current_win() + vim.cmd("new") + local new = vim.api.nvim_get_current_win() + vim.api.nvim_create_autocmd( { 'WinEnter', 'WinLeave' }, { + callback = function() _G.n = (_G.n or 0) + 1 end + }) + vim._with({}, function() + vim.api.nvim_set_current_win(old) + assert(_G.n ~= nil) + end) + ]] + end) + + it('can access window options', function() + command('vsplit') + local win1 = api.nvim_get_current_win() + command('wincmd w') + local win2 = exec_lua [[ + win2 = vim.api.nvim_get_current_win() + return win2 + ]] + command('wincmd p') + + eq('', api.nvim_get_option_value('winhighlight', { win = win1 })) + eq('', api.nvim_get_option_value('winhighlight', { win = win2 })) + + local val = exec_lua [[ + return vim._with({win = win2}, function() + vim.cmd "setlocal winhighlight=Normal:Normal" + return vim.api.nvim_get_current_win() + end) + ]] + + eq('', api.nvim_get_option_value('winhighlight', { win = win1 })) + eq('Normal:Normal', api.nvim_get_option_value('winhighlight', { win = win2 })) + eq(win1, api.nvim_get_current_win()) + eq(win2, val) + end) + + it('does not cause ml_get errors with invalid visual selection', function() + -- Add lines to the current buffer and make another window looking into an empty buffer. + exec_lua [[ + _G.api = vim.api + _G.t = function(s) return api.nvim_replace_termcodes(s, true, true, true) end + _G.win_lines = api.nvim_get_current_win() + vim.cmd "new" + _G.win_empty = api.nvim_get_current_win() + api.nvim_set_current_win(win_lines) + api.nvim_buf_set_lines(0, 0, -1, true, {"a", "b", "c"}) + ]] + + -- Start Visual in current window, redraw in other window with fewer lines. + exec_lua [[ + api.nvim_feedkeys(t "G<C-V>", "txn", false) + vim._with({win = win_empty}, function() vim.cmd "redraw" end) + ]] + + -- Start Visual in current window, extend it in other window with more lines. + exec_lua [[ + api.nvim_feedkeys(t "<Esc>gg", "txn", false) + api.nvim_set_current_win(win_empty) + api.nvim_feedkeys(t "gg<C-V>", "txn", false) + vim._with({win = win_lines}, function() api.nvim_feedkeys(t "G<C-V>", "txn", false) end) + vim.cmd "redraw" + ]] + end) + + it('updates ruler if cursor moved', function() + local screen = Screen.new(30, 5) + screen:set_default_attr_ids { + [1] = { reverse = true }, + [2] = { bold = true, reverse = true }, + } + screen:attach() + exec_lua [[ + _G.api = vim.api + vim.opt.ruler = true + local lines = {} + for i = 0, 499 do lines[#lines + 1] = tostring(i) end + api.nvim_buf_set_lines(0, 0, -1, true, lines) + api.nvim_win_set_cursor(0, {20, 0}) + vim.cmd "split" + _G.win = api.nvim_get_current_win() + vim.cmd "wincmd w | redraw" + ]] + screen:expect [[ + 19 | + {1:[No Name] [+] 20,1 3%}| + ^19 | + {2:[No Name] [+] 20,1 3%}| + | + ]] + exec_lua [[ + vim._with({win = win}, function() api.nvim_win_set_cursor(0, {100, 0}) end) + vim.cmd "redraw" + ]] + screen:expect [[ + 99 | + {1:[No Name] [+] 100,1 19%}| + ^19 | + {2:[No Name] [+] 20,1 3%}| + | + ]] + end) + + it('can return values by reference', function() + eq( + { 7, 10 }, + exec_lua [[ + local val = {4, 10} + local ref = vim._with({win = 0}, function() return val end) + ref[1] = 7 + return val + ]] + ) + end) + + it('layout in current tabpage does not affect windows in others', function() + command('tab split') + local t2_move_win = api.nvim_get_current_win() + command('vsplit') + local t2_other_win = api.nvim_get_current_win() + command('tabprevious') + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + command('vsplit') + + exec_lua('vim._with({win = ...}, function() vim.cmd.wincmd "J" end)', t2_move_win) + eq({ 'col', { { 'leaf', t2_other_win }, { 'leaf', t2_move_win } } }, fn.winlayout(2)) + end) +end) + +describe('vim._with {lockmarks = true}', function() + it('is reset', function() + local mark = exec_lua [[ + vim.api.nvim_buf_set_lines(0, 0, 0, false, {"marky", "snarky", "malarkey"}) + vim.api.nvim_buf_set_mark(0,"m",1,0, {}) + vim._with({lockmarks = true}, function() + vim.api.nvim_buf_set_lines(0, 0, 2, false, {"mass", "mess", "moss"}) + end) + return vim.api.nvim_buf_get_mark(0,"m") + ]] + t.eq(mark, { 1, 0 }) + end) +end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 6d28b83be8..50e9c0cc0f 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2690,13 +2690,15 @@ describe('LSP', function() { filename = '/fake/uri', lnum = 1, + end_lnum = 2, col = 3, + end_col = 4, text = 'testing', user_data = { uri = 'file:///fake/uri', range = { start = { line = 0, character = 2 }, - ['end'] = { line = 0, character = 3 }, + ['end'] = { line = 1, character = 3 }, }, }, }, @@ -2710,7 +2712,7 @@ describe('LSP', function() uri = 'file:///fake/uri', range = { start = { line = 0, character = 2 }, - ['end'] = { line = 0, character = 3 }, + ['end'] = { line = 1, character = 3 }, } }, } @@ -2723,7 +2725,9 @@ describe('LSP', function() { filename = '/fake/uri', lnum = 1, + end_lnum = 1, col = 3, + end_col = 4, text = 'testing', user_data = { targetUri = 'file:///fake/uri', diff --git a/test/unit/fixtures/rbuffer.c b/test/unit/fixtures/rbuffer.c deleted file mode 100644 index d587d6b054..0000000000 --- a/test/unit/fixtures/rbuffer.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "nvim/rbuffer.h" -#include "rbuffer.h" - - -void ut_rbuffer_each_read_chunk(RBuffer *buf, each_ptr_cb cb) -{ - RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) { - cb(rptr, rcnt); - rbuffer_consumed(buf, rcnt); - } -} - -void ut_rbuffer_each_write_chunk(RBuffer *buf, each_ptr_cb cb) -{ - RBUFFER_UNTIL_FULL(buf, wptr, wcnt) { - cb(wptr, wcnt); - rbuffer_produced(buf, wcnt); - } -} -void ut_rbuffer_each(RBuffer *buf, each_cb cb) -{ - RBUFFER_EACH(buf, c, i) cb(c, i); -} - -void ut_rbuffer_each_reverse(RBuffer *buf, each_cb cb) -{ - RBUFFER_EACH_REVERSE(buf, c, i) cb(c, i); -} diff --git a/test/unit/fixtures/rbuffer.h b/test/unit/fixtures/rbuffer.h deleted file mode 100644 index 640092c627..0000000000 --- a/test/unit/fixtures/rbuffer.h +++ /dev/null @@ -1,9 +0,0 @@ -#include "nvim/rbuffer.h" - -typedef void(*each_ptr_cb)(char *ptr, size_t cnt); -typedef void(*each_cb)(char c, size_t i); - -void ut_rbuffer_each_read_chunk(RBuffer *buf, each_ptr_cb cb); -void ut_rbuffer_each_write_chunk(RBuffer *buf, each_ptr_cb cb); -void ut_rbuffer_each(RBuffer *buf, each_cb cb); -void ut_rbuffer_each_reverse(RBuffer *buf, each_cb cb); diff --git a/test/unit/rbuffer_spec.lua b/test/unit/rbuffer_spec.lua deleted file mode 100644 index ad18ea2ddc..0000000000 --- a/test/unit/rbuffer_spec.lua +++ /dev/null @@ -1,340 +0,0 @@ -local t = require('test.unit.testutil') -local itp = t.gen_itp(it) - -local eq = t.eq -local ffi = t.ffi -local cstr = t.cstr -local to_cstr = t.to_cstr -local child_call_once = t.child_call_once - -local rbuffer = t.cimport('./test/unit/fixtures/rbuffer.h') - -describe('rbuffer functions', function() - local capacity = 16 - local rbuf - - local function inspect() - return ffi.string(rbuf.start_ptr, capacity) - end - - local function write(str) - local buf = to_cstr(str) - return rbuffer.rbuffer_write(rbuf, buf, #str) - end - - local function read(len) - local buf = cstr(len) - len = rbuffer.rbuffer_read(rbuf, buf, len) - return ffi.string(buf, len) - end - - local function get(idx) - return ffi.string(rbuffer.rbuffer_get(rbuf, idx), 1) - end - - before_each(function() - child_call_once(function() - rbuf = ffi.gc(rbuffer.rbuffer_new(capacity), rbuffer.rbuffer_free) - -- fill the internal buffer with the character '0' to simplify inspecting - ffi.C.memset(rbuf.start_ptr, string.byte('0'), capacity) - end) - end) - - describe('RBUFFER_UNTIL_FULL', function() - local chunks - - local function collect_write_chunks() - rbuffer.ut_rbuffer_each_write_chunk(rbuf, function(wptr, wcnt) - table.insert(chunks, ffi.string(wptr, wcnt)) - end) - end - - before_each(function() - chunks = {} - end) - - describe('with empty buffer in one contiguous chunk', function() - itp('is called once with the empty chunk', function() - collect_write_chunks() - eq({ '0000000000000000' }, chunks) - end) - end) - - describe('with partially empty buffer in one contiguous chunk', function() - itp('is called once with the empty chunk', function() - write('string') - collect_write_chunks() - eq({ '0000000000' }, chunks) - end) - end) - - describe('with filled buffer in one contiguous chunk', function() - itp('is not called', function() - write('abcdefghijklmnopq') - collect_write_chunks() - eq({}, chunks) - end) - end) - - describe('with buffer partially empty in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('1234567890') - read(8) - collect_write_chunks() - eq({ '000000', '12345678' }, chunks) - end) - end) - - describe('with buffer empty in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('12345678') - read(8) - collect_write_chunks() - eq({ '00000000', '12345678' }, chunks) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('is not called', function() - write('12345678') - read(8) - write('abcdefghijklmnopq') - collect_write_chunks() - eq({}, chunks) - end) - end) - end) - - describe('RBUFFER_UNTIL_EMPTY', function() - local chunks - - local function collect_read_chunks() - rbuffer.ut_rbuffer_each_read_chunk(rbuf, function(rptr, rcnt) - table.insert(chunks, ffi.string(rptr, rcnt)) - end) - end - - before_each(function() - chunks = {} - end) - - describe('with empty buffer', function() - itp('is not called', function() - collect_read_chunks() - eq({}, chunks) - end) - end) - - describe('with partially filled buffer in one contiguous chunk', function() - itp('is called once with the filled chunk', function() - write('string') - collect_read_chunks() - eq({ 'string' }, chunks) - end) - end) - - describe('with filled buffer in one contiguous chunk', function() - itp('is called once with the filled chunk', function() - write('abcdefghijklmnopq') - collect_read_chunks() - eq({ 'abcdefghijklmnop' }, chunks) - end) - end) - - describe('with buffer partially filled in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('1234567890') - read(10) - write('long string') - collect_read_chunks() - eq({ 'long s', 'tring' }, chunks) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('12345678') - read(8) - write('abcdefghijklmnopq') - collect_read_chunks() - eq({ 'abcdefgh', 'ijklmnop' }, chunks) - end) - end) - end) - - describe('RBUFFER_EACH', function() - local chars - - local function collect_chars() - rbuffer.ut_rbuffer_each(rbuf, function(c, i) - table.insert(chars, { string.char(c), tonumber(i) }) - end) - end - before_each(function() - chars = {} - end) - - describe('with empty buffer', function() - itp('is not called', function() - collect_chars() - eq({}, chars) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('collects each character and index', function() - write('1234567890') - read(10) - write('long string') - collect_chars() - eq({ - { 'l', 0 }, - { 'o', 1 }, - { 'n', 2 }, - { 'g', 3 }, - { ' ', 4 }, - { 's', 5 }, - { 't', 6 }, - { 'r', 7 }, - { 'i', 8 }, - { 'n', 9 }, - { 'g', 10 }, - }, chars) - end) - end) - end) - - describe('RBUFFER_EACH_REVERSE', function() - local chars - - local function collect_chars() - rbuffer.ut_rbuffer_each_reverse(rbuf, function(c, i) - table.insert(chars, { string.char(c), tonumber(i) }) - end) - end - before_each(function() - chars = {} - end) - - describe('with empty buffer', function() - itp('is not called', function() - collect_chars() - eq({}, chars) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('collects each character and index', function() - write('1234567890') - read(10) - write('long string') - collect_chars() - eq({ - { 'g', 10 }, - { 'n', 9 }, - { 'i', 8 }, - { 'r', 7 }, - { 't', 6 }, - { 's', 5 }, - { ' ', 4 }, - { 'g', 3 }, - { 'n', 2 }, - { 'o', 1 }, - { 'l', 0 }, - }, chars) - end) - end) - end) - - describe('rbuffer_cmp', function() - local function cmp(str) - local rv = rbuffer.rbuffer_cmp(rbuf, to_cstr(str), #str) - if rv == 0 then - return 0 - else - return rv / math.abs(rv) - end - end - - describe('with buffer filled in two contiguous chunks', function() - itp('compares the common longest sequence', function() - write('1234567890') - read(10) - write('long string') - eq(0, cmp('long string')) - eq(0, cmp('long strin')) - eq(-1, cmp('long striM')) - eq(1, cmp('long strio')) - eq(0, cmp('long')) - eq(-1, cmp('lonG')) - eq(1, cmp('lonh')) - end) - end) - - describe('with empty buffer', function() - itp('returns 0 since no characters are compared', function() - eq(0, cmp('')) - end) - end) - end) - - describe('rbuffer_write', function() - itp('fills the internal buffer and returns the write count', function() - eq(12, write('short string')) - eq('short string0000', inspect()) - end) - - itp('wont write beyond capacity', function() - eq(16, write('very very long string')) - eq('very very long s', inspect()) - end) - end) - - describe('rbuffer_read', function() - itp('reads what was previously written', function() - write('to read') - eq('to read', read(20)) - end) - - itp('reads nothing if the buffer is empty', function() - eq('', read(20)) - write('empty') - eq('empty', read(20)) - eq('', read(20)) - end) - end) - - describe('rbuffer_get', function() - itp('fetch the pointer at offset, wrapping if required', function() - write('1234567890') - read(10) - write('long string') - eq('l', get(0)) - eq('o', get(1)) - eq('n', get(2)) - eq('g', get(3)) - eq(' ', get(4)) - eq('s', get(5)) - eq('t', get(6)) - eq('r', get(7)) - eq('i', get(8)) - eq('n', get(9)) - eq('g', get(10)) - end) - end) - - describe('wrapping behavior', function() - itp('writing/reading wraps across the end of the internal buffer', function() - write('1234567890') - eq('1234', read(4)) - eq('5678', read(4)) - write('987654321') - eq('3214567890987654', inspect()) - eq('90987654321', read(20)) - eq('', read(4)) - write('abcdefghijklmnopqrs') - eq('nopabcdefghijklm', inspect()) - eq('abcdefghijklmnop', read(20)) - end) - end) -end) |