mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	fix(messages): proper multiline Lua print() messages #31205
Problem:  Separate message emitted for each newline present in Lua
          print() arguments.
Solution: Make msg_multiline() handle NUL bytes. Refactor print() to use
          msg_multiline(). Refactor vim.print() to use print().
			
			
This commit is contained in:
		@@ -796,6 +796,7 @@ must handle.
 | 
				
			|||||||
		"echomsg"	|:echomsg| message
 | 
							"echomsg"	|:echomsg| message
 | 
				
			||||||
		"echoerr"	|:echoerr| message
 | 
							"echoerr"	|:echoerr| message
 | 
				
			||||||
		"lua_error"	Error in |:lua| code
 | 
							"lua_error"	Error in |:lua| code
 | 
				
			||||||
 | 
							"lua_print"	|print()| from |:lua| code
 | 
				
			||||||
		"rpc_error"	Error response from |rpcrequest()|
 | 
							"rpc_error"	Error response from |rpcrequest()|
 | 
				
			||||||
		"return_prompt"	|press-enter| prompt after a multiple messages
 | 
							"return_prompt"	|press-enter| prompt after a multiple messages
 | 
				
			||||||
		"quickfix"	Quickfix navigation message
 | 
							"quickfix"	Quickfix navigation message
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1151,21 +1151,16 @@ end
 | 
				
			|||||||
--- @param ... any
 | 
					--- @param ... any
 | 
				
			||||||
--- @return any # given arguments.
 | 
					--- @return any # given arguments.
 | 
				
			||||||
function vim.print(...)
 | 
					function vim.print(...)
 | 
				
			||||||
  if vim.in_fast_event() then
 | 
					  local msg = {}
 | 
				
			||||||
    print(...)
 | 
					 | 
				
			||||||
    return ...
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for i = 1, select('#', ...) do
 | 
					  for i = 1, select('#', ...) do
 | 
				
			||||||
    local o = select(i, ...)
 | 
					    local o = select(i, ...)
 | 
				
			||||||
    if type(o) == 'string' then
 | 
					    if type(o) == 'string' then
 | 
				
			||||||
      vim.api.nvim_out_write(o)
 | 
					      table.insert(msg, o)
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      vim.api.nvim_out_write(vim.inspect(o, { newline = '\n', indent = '  ' }))
 | 
					      table.insert(msg, vim.inspect(o, { newline = '\n', indent = '  ' }))
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    vim.api.nvim_out_write('\n')
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					  print(table.concat(msg, '\n'))
 | 
				
			||||||
  return ...
 | 
					  return ...
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7881,7 +7881,7 @@ void ex_echo(exarg_T *eap)
 | 
				
			|||||||
      char *tofree = encode_tv2echo(&rettv, NULL);
 | 
					      char *tofree = encode_tv2echo(&rettv, NULL);
 | 
				
			||||||
      if (*tofree != NUL) {
 | 
					      if (*tofree != NUL) {
 | 
				
			||||||
        msg_ext_set_kind("echo");
 | 
					        msg_ext_set_kind("echo");
 | 
				
			||||||
        msg_multiline(tofree, echo_hl_id, true, false, &need_clear);
 | 
					        msg_multiline(cstr_as_string(tofree), echo_hl_id, true, false, &need_clear);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      xfree(tofree);
 | 
					      xfree(tofree);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -189,7 +189,7 @@ void do_ascii(exarg_T *eap)
 | 
				
			|||||||
                   transchar(c), buf1, buf2, cval, cval, cval);
 | 
					                   transchar(c), buf1, buf2, cval, cval, cval);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    msg_multiline(IObuff, 0, true, false, &need_clear);
 | 
					    msg_multiline(cstr_as_string(IObuff), 0, true, false, &need_clear);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    off += (size_t)utf_ptr2len(data);  // needed for overlong ascii?
 | 
					    off += (size_t)utf_ptr2len(data);  // needed for overlong ascii?
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -224,7 +224,7 @@ void do_ascii(exarg_T *eap)
 | 
				
			|||||||
                   c, c, c);
 | 
					                   c, c, c);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    msg_multiline(IObuff, 0, true, false, &need_clear);
 | 
					    msg_multiline(cstr_as_string(IObuff), 0, true, false, &need_clear);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    off += (size_t)utf_ptr2len(data + off);  // needed for overlong ascii?
 | 
					    off += (size_t)utf_ptr2len(data + off);  // needed for overlong ascii?
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -954,41 +954,10 @@ static void nlua_common_free_all_mem(lua_State *lstate)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static void nlua_print_event(void **argv)
 | 
					static void nlua_print_event(void **argv)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  char *str = argv[0];
 | 
					  HlMessage msg = KV_INITIAL_VALUE;
 | 
				
			||||||
  const size_t len = (size_t)(intptr_t)argv[1] - 1;  // exclude final NUL
 | 
					  HlMessageChunk chunk = { { .data = argv[0], .size = (size_t)(intptr_t)argv[1] - 1 }, 0 };
 | 
				
			||||||
 | 
					  kv_push(msg, chunk);
 | 
				
			||||||
  for (size_t i = 0; i < len;) {
 | 
					  msg_multihl(msg, "lua_print", true);
 | 
				
			||||||
    if (got_int) {
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const size_t start = i;
 | 
					 | 
				
			||||||
    while (i < len) {
 | 
					 | 
				
			||||||
      switch (str[i]) {
 | 
					 | 
				
			||||||
      case NUL:
 | 
					 | 
				
			||||||
        str[i] = NL;
 | 
					 | 
				
			||||||
        i++;
 | 
					 | 
				
			||||||
        continue;
 | 
					 | 
				
			||||||
      case NL:
 | 
					 | 
				
			||||||
        // TODO(bfredl): use proper multiline msg? Probably should implement
 | 
					 | 
				
			||||||
        // print() in lua in terms of nvim_message(), when it is available.
 | 
					 | 
				
			||||||
        str[i] = NUL;
 | 
					 | 
				
			||||||
        i++;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      default:
 | 
					 | 
				
			||||||
        i++;
 | 
					 | 
				
			||||||
        continue;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    msg(str + start, 0);
 | 
					 | 
				
			||||||
    if (msg_silent == 0) {
 | 
					 | 
				
			||||||
      msg_didout = true;  // Make blank lines work properly
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (len && str[len - 1] == NUL) {  // Last was newline
 | 
					 | 
				
			||||||
    msg("", 0);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  xfree(str);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Print as a Vim message
 | 
					/// Print as a Vim message
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -249,35 +249,33 @@ bool msg(const char *s, const int hl_id)
 | 
				
			|||||||
  return msg_hl_keep(s, hl_id, false, false);
 | 
					  return msg_hl_keep(s, hl_id, false, false);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Similar to msg_outtrans, but support newlines and tabs.
 | 
					/// Similar to msg_outtrans_len, but support newlines and tabs.
 | 
				
			||||||
void msg_multiline(const char *s, int hl_id, bool check_int, bool hist, bool *need_clear)
 | 
					void msg_multiline(String str, int hl_id, bool check_int, bool hist, bool *need_clear)
 | 
				
			||||||
  FUNC_ATTR_NONNULL_ALL
 | 
					  FUNC_ATTR_NONNULL_ALL
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  const char *next_spec = s;
 | 
					  const char *s = str.data;
 | 
				
			||||||
 | 
					  const char *chunk = s;
 | 
				
			||||||
  while (next_spec != NULL) {
 | 
					  while ((size_t)(s - str.data) < str.size) {
 | 
				
			||||||
    if (check_int && got_int) {
 | 
					    if (check_int && got_int) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    next_spec = strpbrk(s, "\t\n\r");
 | 
					    if (*s == '\n' || *s == TAB || *s == '\r') {
 | 
				
			||||||
 | 
					      // Print all chars before the delimiter
 | 
				
			||||||
 | 
					      msg_outtrans_len(chunk, (int)(s - chunk), hl_id, hist);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (next_spec != NULL) {
 | 
					      if (*s != TAB && *need_clear) {
 | 
				
			||||||
      // Printing all char that are before the char found by strpbrk
 | 
					 | 
				
			||||||
      msg_outtrans_len(s, (int)(next_spec - s), hl_id, hist);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (*next_spec != TAB && *need_clear) {
 | 
					 | 
				
			||||||
        msg_clr_eos();
 | 
					        msg_clr_eos();
 | 
				
			||||||
        *need_clear = false;
 | 
					        *need_clear = false;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      msg_putchar_hl((uint8_t)(*next_spec), hl_id);
 | 
					      msg_putchar_hl((uint8_t)(*s), hl_id);
 | 
				
			||||||
      s = next_spec + 1;
 | 
					      chunk = s + 1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    s++;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Print the rest of the message. We know there is no special
 | 
					  // Print the rest of the message
 | 
				
			||||||
  // character because strpbrk returned NULL
 | 
					  if (*chunk != NUL) {
 | 
				
			||||||
  if (*s != NUL) {
 | 
					    msg_outtrans_len(chunk, (int)(str.size - (size_t)(chunk - str.data)), hl_id, hist);
 | 
				
			||||||
    msg_outtrans(s, hl_id, hist);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -290,7 +288,7 @@ void msg_multihl(HlMessage hl_msg, const char *kind, bool history)
 | 
				
			|||||||
  msg_ext_set_kind(kind);
 | 
					  msg_ext_set_kind(kind);
 | 
				
			||||||
  for (uint32_t i = 0; i < kv_size(hl_msg); i++) {
 | 
					  for (uint32_t i = 0; i < kv_size(hl_msg); i++) {
 | 
				
			||||||
    HlMessageChunk chunk = kv_A(hl_msg, i);
 | 
					    HlMessageChunk chunk = kv_A(hl_msg, i);
 | 
				
			||||||
    msg_multiline(chunk.text.data, chunk.hl_id, true, false, &need_clear);
 | 
					    msg_multiline(chunk.text, chunk.hl_id, true, false, &need_clear);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (history && kv_size(hl_msg)) {
 | 
					  if (history && kv_size(hl_msg)) {
 | 
				
			||||||
    add_msg_hist_multihl(NULL, 0, 0, true, hl_msg);
 | 
					    add_msg_hist_multihl(NULL, 0, 0, true, hl_msg);
 | 
				
			||||||
@@ -349,7 +347,7 @@ bool msg_hl_keep(const char *s, int hl_id, bool keep, bool multiline)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  bool need_clear = true;
 | 
					  bool need_clear = true;
 | 
				
			||||||
  if (multiline) {
 | 
					  if (multiline) {
 | 
				
			||||||
    msg_multiline(s, hl_id, false, false, &need_clear);
 | 
					    msg_multiline(cstr_as_string(s), hl_id, false, false, &need_clear);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    msg_outtrans(s, hl_id, false);
 | 
					    msg_outtrans(s, hl_id, false);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -2689,12 +2687,13 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen)
 | 
				
			|||||||
    // primitive way to compute the current column
 | 
					    // primitive way to compute the current column
 | 
				
			||||||
    if (*s == '\r' || *s == '\n') {
 | 
					    if (*s == '\r' || *s == '\n') {
 | 
				
			||||||
      msg_col = 0;
 | 
					      msg_col = 0;
 | 
				
			||||||
 | 
					      msg_didout = false;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      msg_col += cw;
 | 
					      msg_col += cw;
 | 
				
			||||||
 | 
					      msg_didout = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    s += len;
 | 
					    s += len;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  msg_didout = true;  // assume that line is not empty
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Show the more-prompt and handle the user response.
 | 
					/// Show the more-prompt and handle the user response.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -277,10 +277,8 @@ describe('startup', function()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      -- nvim <vim args> -l foo.lua <vim args>
 | 
					      -- nvim <vim args> -l foo.lua <vim args>
 | 
				
			||||||
      assert_l_out(
 | 
					      assert_l_out(
 | 
				
			||||||
        -- luacheck: ignore 611 (Line contains only whitespaces)
 | 
					 | 
				
			||||||
        [[
 | 
					        [[
 | 
				
			||||||
            wrap
 | 
					            wrap
 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          bufs:
 | 
					          bufs:
 | 
				
			||||||
          nvim args: 7
 | 
					          nvim args: 7
 | 
				
			||||||
          lua args: { "-c", "set wrap?",
 | 
					          lua args: { "-c", "set wrap?",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -142,7 +142,7 @@ describe('vim.ui_attach', function()
 | 
				
			|||||||
        'msg_history_show',
 | 
					        'msg_history_show',
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          { 'echomsg', { { 0, 'message1', 0 } } },
 | 
					          { 'echomsg', { { 0, 'message1', 0 } } },
 | 
				
			||||||
          { '', { { 0, 'message2', 0 } } },
 | 
					          { 'lua_print', { { 0, 'message2', 0 } } },
 | 
				
			||||||
          { 'echomsg', { { 0, 'message3', 0 } } },
 | 
					          { 'echomsg', { { 0, 'message3', 0 } } },
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1113,6 +1113,33 @@ stack traceback:
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
    eq(showmode, 1)
 | 
					    eq(showmode, 1)
 | 
				
			||||||
  end)
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('emits single message for multiline print())', function()
 | 
				
			||||||
 | 
					    exec_lua([[print("foo\nbar\nbaz")]])
 | 
				
			||||||
 | 
					    screen:expect({
 | 
				
			||||||
 | 
					      messages = {
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          content = { { 'foo\nbar\nbaz' } },
 | 
				
			||||||
 | 
					          kind = 'lua_print',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    exec_lua([[print(vim.inspect({ foo = "bar" }))]])
 | 
				
			||||||
 | 
					    screen:expect({
 | 
				
			||||||
 | 
					      grid = [[
 | 
				
			||||||
 | 
					        ^                         |
 | 
				
			||||||
 | 
					        {1:~                        }|*4
 | 
				
			||||||
 | 
					      ]],
 | 
				
			||||||
 | 
					      messages = {
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          content = { { '{\n  foo = "bar"\n}' } },
 | 
				
			||||||
 | 
					          kind = 'lua_print',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    exec_lua([[vim.print({ foo = "bar" })]])
 | 
				
			||||||
 | 
					    screen:expect_unchanged()
 | 
				
			||||||
 | 
					  end)
 | 
				
			||||||
end)
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('ui/builtin messages', function()
 | 
					describe('ui/builtin messages', function()
 | 
				
			||||||
@@ -2062,8 +2089,6 @@ aliquip ex ea commodo consequat.]]
 | 
				
			|||||||
  end)
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('can be quit with Lua #11224 #16537', function()
 | 
					  it('can be quit with Lua #11224 #16537', function()
 | 
				
			||||||
    -- NOTE: adds "4" to message history, although not displayed initially
 | 
					 | 
				
			||||||
    --       (triggered the more prompt).
 | 
					 | 
				
			||||||
    screen:try_resize(40, 5)
 | 
					    screen:try_resize(40, 5)
 | 
				
			||||||
    feed(':lua for i=0,10 do print(i) end<cr>')
 | 
					    feed(':lua for i=0,10 do print(i) end<cr>')
 | 
				
			||||||
    screen:expect {
 | 
					    screen:expect {
 | 
				
			||||||
@@ -2093,13 +2118,13 @@ aliquip ex ea commodo consequat.]]
 | 
				
			|||||||
      {4:-- More --}^                              |
 | 
					      {4:-- More --}^                              |
 | 
				
			||||||
    ]],
 | 
					    ]],
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    feed('j')
 | 
					    feed('G')
 | 
				
			||||||
    screen:expect {
 | 
					    screen:expect {
 | 
				
			||||||
      grid = [[
 | 
					      grid = [[
 | 
				
			||||||
      1                                       |
 | 
					      7                                       |
 | 
				
			||||||
      2                                       |
 | 
					      8                                       |
 | 
				
			||||||
      3                                       |
 | 
					      9                                       |
 | 
				
			||||||
      4                                       |
 | 
					      10                                      |
 | 
				
			||||||
      {4:Press ENTER or type command to continue}^ |
 | 
					      {4:Press ENTER or type command to continue}^ |
 | 
				
			||||||
    ]],
 | 
					    ]],
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user