mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	vim-patch:9.1.0984: exception handling can be improved
Problem:  exception handling can be improved
Solution: add v:stacktrace and getstacktrace()
closes: vim/vim#16360
663d18d610
Co-authored-by: ichizok <gclient.gaap@gmail.com>
Co-authored-by: Naruhiko Nishino <naru123456789@gmail.com>
			
			
This commit is contained in:
		
							
								
								
									
										15
									
								
								runtime/doc/builtin.txt
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								runtime/doc/builtin.txt
									
									
									
										generated
									
									
									
								
							@@ -4182,6 +4182,21 @@ getscriptinfo([{opts}])                                        *getscriptinfo()*
 | 
				
			|||||||
                Return: ~
 | 
					                Return: ~
 | 
				
			||||||
                  (`vim.fn.getscriptinfo.ret[]`)
 | 
					                  (`vim.fn.getscriptinfo.ret[]`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					getstacktrace()                                                *getstacktrace()*
 | 
				
			||||||
 | 
							Returns the current stack trace of Vim scripts.
 | 
				
			||||||
 | 
							Stack trace is a |List|, of which each item is a |Dictionary|
 | 
				
			||||||
 | 
							with the following items:
 | 
				
			||||||
 | 
							    funcref	The funcref if the stack is at the function,
 | 
				
			||||||
 | 
									otherwise this item is not exist.
 | 
				
			||||||
 | 
							    event	The string of the event description if the
 | 
				
			||||||
 | 
									stack is at autocmd event, otherwise this item
 | 
				
			||||||
 | 
									is not exist.
 | 
				
			||||||
 | 
							    lnum	The line number of the script on the stack.
 | 
				
			||||||
 | 
							    filepath	The file path of the script on the stack.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Return: ~
 | 
				
			||||||
 | 
					                  (`table[]`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gettabinfo([{tabnr}])                                             *gettabinfo()*
 | 
					gettabinfo([{tabnr}])                                             *gettabinfo()*
 | 
				
			||||||
		If {tabnr} is not specified, then information about all the
 | 
							If {tabnr} is not specified, then information about all the
 | 
				
			||||||
		tab pages is returned as a |List|. Each List item is a
 | 
							tab pages is returned as a |List|. Each List item is a
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2848,7 +2848,8 @@ in the variable |v:exception|: >
 | 
				
			|||||||
	:    echo "Number thrown.  Value is" v:exception
 | 
						:    echo "Number thrown.  Value is" v:exception
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You may also be interested where an exception was thrown.  This is stored in
 | 
					You may also be interested where an exception was thrown.  This is stored in
 | 
				
			||||||
|v:throwpoint|.  Note that "v:exception" and "v:throwpoint" are valid for the
 | 
					|v:throwpoint|.  And you can obtain the stack trace from |v:stacktrace|.
 | 
				
			||||||
 | 
					Note that "v:exception", "v:stacktrace" and "v:throwpoint" are valid for the
 | 
				
			||||||
exception most recently caught as long it is not finished.
 | 
					exception most recently caught as long it is not finished.
 | 
				
			||||||
   Example: >
 | 
					   Example: >
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1103,7 +1103,8 @@ Various:					*various-functions*
 | 
				
			|||||||
	did_filetype()		check if a FileType autocommand was used
 | 
						did_filetype()		check if a FileType autocommand was used
 | 
				
			||||||
	eventhandler()		check if invoked by an event handler
 | 
						eventhandler()		check if invoked by an event handler
 | 
				
			||||||
	getpid()		get process ID of Vim
 | 
						getpid()		get process ID of Vim
 | 
				
			||||||
	getscriptinfo()		get list of sourced vim scripts
 | 
						getscriptinfo()		get list of sourced Vim scripts
 | 
				
			||||||
 | 
						getstacktrace()		get current stack trace of Vim scripts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	libcall()		call a function in an external library
 | 
						libcall()		call a function in an external library
 | 
				
			||||||
	libcallnr()		idem, returning a number
 | 
						libcallnr()		idem, returning a number
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Predefined variables                                             *vvars*
 | 
					Predefined variables                                             *vvars*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Some variables can be set by the user, but the type cannot be changed.
 | 
					Most variables are read-only, when a variable can be set by the user, it will
 | 
				
			||||||
 | 
					be mentioned at the variable description below. The type cannot be changed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                       Type |gO| to see the table of contents.
 | 
					                                       Type |gO| to see the table of contents.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -195,7 +196,8 @@ v:event
 | 
				
			|||||||
				*v:exception* *exception-variable*
 | 
									*v:exception* *exception-variable*
 | 
				
			||||||
v:exception
 | 
					v:exception
 | 
				
			||||||
		The value of the exception most recently caught and not
 | 
							The value of the exception most recently caught and not
 | 
				
			||||||
		finished.  See also |v:throwpoint| and |throw-variables|.
 | 
							finished.  See also |v:stacktrace|, |v:throwpoint|, and
 | 
				
			||||||
 | 
							|throw-variables|.
 | 
				
			||||||
		Example: >vim
 | 
							Example: >vim
 | 
				
			||||||
		  try
 | 
							  try
 | 
				
			||||||
		    throw "oops"
 | 
							    throw "oops"
 | 
				
			||||||
@@ -586,6 +588,13 @@ v:shell_error
 | 
				
			|||||||
		  endif
 | 
							  endif
 | 
				
			||||||
<
 | 
					<
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									*v:stacktrace* *stacktrace-variable*
 | 
				
			||||||
 | 
					v:stacktrace
 | 
				
			||||||
 | 
							The stack trace of the exception most recently caught and
 | 
				
			||||||
 | 
							not finished.  Refer to |getstacktrace()| for the structure of
 | 
				
			||||||
 | 
							stack trace.  See also |v:exception|, |v:throwpoint|, and
 | 
				
			||||||
 | 
							|throw-variables|.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				*v:statusmsg* *statusmsg-variable*
 | 
									*v:statusmsg* *statusmsg-variable*
 | 
				
			||||||
v:statusmsg
 | 
					v:statusmsg
 | 
				
			||||||
		Last given status message.
 | 
							Last given status message.
 | 
				
			||||||
@@ -679,7 +688,7 @@ v:this_session
 | 
				
			|||||||
v:throwpoint
 | 
					v:throwpoint
 | 
				
			||||||
		The point where the exception most recently caught and not
 | 
							The point where the exception most recently caught and not
 | 
				
			||||||
		finished was thrown.  Not set when commands are typed.  See
 | 
							finished was thrown.  Not set when commands are typed.  See
 | 
				
			||||||
		also |v:exception| and |throw-variables|.
 | 
							also |v:exception|, |v:stacktrace|, and |throw-variables|.
 | 
				
			||||||
		Example: >vim
 | 
							Example: >vim
 | 
				
			||||||
		  try
 | 
							  try
 | 
				
			||||||
		    throw "oops"
 | 
							    throw "oops"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										14
									
								
								runtime/lua/vim/_meta/vimfn.lua
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								runtime/lua/vim/_meta/vimfn.lua
									
									
									
										generated
									
									
									
								
							@@ -3770,6 +3770,20 @@ function vim.fn.getregtype(regname) end
 | 
				
			|||||||
--- @return vim.fn.getscriptinfo.ret[]
 | 
					--- @return vim.fn.getscriptinfo.ret[]
 | 
				
			||||||
function vim.fn.getscriptinfo(opts) end
 | 
					function vim.fn.getscriptinfo(opts) end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- Returns the current stack trace of Vim scripts.
 | 
				
			||||||
 | 
					--- Stack trace is a |List|, of which each item is a |Dictionary|
 | 
				
			||||||
 | 
					--- with the following items:
 | 
				
			||||||
 | 
					---     funcref  The funcref if the stack is at the function,
 | 
				
			||||||
 | 
					---     otherwise this item is not exist.
 | 
				
			||||||
 | 
					---     event  The string of the event description if the
 | 
				
			||||||
 | 
					---     stack is at autocmd event, otherwise this item
 | 
				
			||||||
 | 
					---     is not exist.
 | 
				
			||||||
 | 
					---     lnum  The line number of the script on the stack.
 | 
				
			||||||
 | 
					---     filepath  The file path of the script on the stack.
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					--- @return table[]
 | 
				
			||||||
 | 
					function vim.fn.getstacktrace() end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- If {tabnr} is not specified, then information about all the
 | 
					--- If {tabnr} is not specified, then information about all the
 | 
				
			||||||
--- tab pages is returned as a |List|. Each List item is a
 | 
					--- tab pages is returned as a |List|. Each List item is a
 | 
				
			||||||
--- |Dictionary|.  Otherwise, {tabnr} specifies the tab page
 | 
					--- |Dictionary|.  Otherwise, {tabnr} specifies the tab page
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								runtime/lua/vim/_meta/vvars.lua
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								runtime/lua/vim/_meta/vvars.lua
									
									
									
										generated
									
									
									
								
							@@ -203,7 +203,8 @@ vim.v.errors = ...
 | 
				
			|||||||
vim.v.event = ...
 | 
					vim.v.event = ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- The value of the exception most recently caught and not
 | 
					--- The value of the exception most recently caught and not
 | 
				
			||||||
--- finished.  See also `v:throwpoint` and `throw-variables`.
 | 
					--- finished.  See also `v:stacktrace`, `v:throwpoint`, and
 | 
				
			||||||
 | 
					--- `throw-variables`.
 | 
				
			||||||
--- Example:
 | 
					--- Example:
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
--- ```vim
 | 
					--- ```vim
 | 
				
			||||||
@@ -616,6 +617,13 @@ vim.v.servername = ...
 | 
				
			|||||||
--- @type integer
 | 
					--- @type integer
 | 
				
			||||||
vim.v.shell_error = ...
 | 
					vim.v.shell_error = ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- The stack trace of the exception most recently caught and
 | 
				
			||||||
 | 
					--- not finished.  Refer to `getstacktrace()` for the structure of
 | 
				
			||||||
 | 
					--- stack trace.  See also `v:exception`, `v:throwpoint`, and
 | 
				
			||||||
 | 
					--- `throw-variables`.
 | 
				
			||||||
 | 
					--- @type table[]
 | 
				
			||||||
 | 
					vim.v.stacktrace = ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- Last given status message.
 | 
					--- Last given status message.
 | 
				
			||||||
--- Modifiable (can be set).
 | 
					--- Modifiable (can be set).
 | 
				
			||||||
--- @type string
 | 
					--- @type string
 | 
				
			||||||
@@ -718,7 +726,7 @@ vim.v.this_session = ...
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
--- The point where the exception most recently caught and not
 | 
					--- The point where the exception most recently caught and not
 | 
				
			||||||
--- finished was thrown.  Not set when commands are typed.  See
 | 
					--- finished was thrown.  Not set when commands are typed.  See
 | 
				
			||||||
--- also `v:exception` and `throw-variables`.
 | 
					--- also `v:exception`, `v:stacktrace`, and `throw-variables`.
 | 
				
			||||||
--- Example:
 | 
					--- Example:
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
--- ```vim
 | 
					--- ```vim
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -270,6 +270,7 @@ static struct vimvar {
 | 
				
			|||||||
  VV(VV_COLLATE,          "collate",          VAR_STRING, VV_RO),
 | 
					  VV(VV_COLLATE,          "collate",          VAR_STRING, VV_RO),
 | 
				
			||||||
  VV(VV_EXITING,          "exiting",          VAR_NUMBER, VV_RO),
 | 
					  VV(VV_EXITING,          "exiting",          VAR_NUMBER, VV_RO),
 | 
				
			||||||
  VV(VV_MAXCOL,           "maxcol",           VAR_NUMBER, VV_RO),
 | 
					  VV(VV_MAXCOL,           "maxcol",           VAR_NUMBER, VV_RO),
 | 
				
			||||||
 | 
					  VV(VV_STACKTRACE,       "stacktrace",       VAR_LIST, VV_RO),
 | 
				
			||||||
  // Neovim
 | 
					  // Neovim
 | 
				
			||||||
  VV(VV_STDERR,           "stderr",           VAR_NUMBER, VV_RO),
 | 
					  VV(VV_STDERR,           "stderr",           VAR_NUMBER, VV_RO),
 | 
				
			||||||
  VV(VV_MSGPACK_TYPES,    "msgpack_types",    VAR_DICT, VV_RO),
 | 
					  VV(VV_MSGPACK_TYPES,    "msgpack_types",    VAR_DICT, VV_RO),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -167,6 +167,7 @@ typedef enum {
 | 
				
			|||||||
  VV_COLLATE,
 | 
					  VV_COLLATE,
 | 
				
			||||||
  VV_EXITING,
 | 
					  VV_EXITING,
 | 
				
			||||||
  VV_MAXCOL,
 | 
					  VV_MAXCOL,
 | 
				
			||||||
 | 
					  VV_STACKTRACE,
 | 
				
			||||||
  // Nvim
 | 
					  // Nvim
 | 
				
			||||||
  VV_STDERR,
 | 
					  VV_STDERR,
 | 
				
			||||||
  VV_MSGPACK_TYPES,
 | 
					  VV_MSGPACK_TYPES,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4670,6 +4670,25 @@ M.funcs = {
 | 
				
			|||||||
    returns = 'vim.fn.getscriptinfo.ret[]',
 | 
					    returns = 'vim.fn.getscriptinfo.ret[]',
 | 
				
			||||||
    signature = 'getscriptinfo([{opts}])',
 | 
					    signature = 'getscriptinfo([{opts}])',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  getstacktrace = {
 | 
				
			||||||
 | 
					    args = 0,
 | 
				
			||||||
 | 
					    desc = [=[
 | 
				
			||||||
 | 
					      Returns the current stack trace of Vim scripts.
 | 
				
			||||||
 | 
					      Stack trace is a |List|, of which each item is a |Dictionary|
 | 
				
			||||||
 | 
					      with the following items:
 | 
				
			||||||
 | 
					          funcref	The funcref if the stack is at the function,
 | 
				
			||||||
 | 
					      		otherwise this item is not exist.
 | 
				
			||||||
 | 
					          event	The string of the event description if the
 | 
				
			||||||
 | 
					      		stack is at autocmd event, otherwise this item
 | 
				
			||||||
 | 
					      		is not exist.
 | 
				
			||||||
 | 
					          lnum	The line number of the script on the stack.
 | 
				
			||||||
 | 
					          filepath	The file path of the script on the stack.
 | 
				
			||||||
 | 
					    ]=],
 | 
				
			||||||
 | 
					    name = 'getstacktrace',
 | 
				
			||||||
 | 
					    params = {},
 | 
				
			||||||
 | 
					    returns = 'table[]',
 | 
				
			||||||
 | 
					    signature = 'getstacktrace()',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  gettabinfo = {
 | 
					  gettabinfo = {
 | 
				
			||||||
    args = { 0, 1 },
 | 
					    args = { 0, 1 },
 | 
				
			||||||
    base = 1,
 | 
					    base = 1,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2633,6 +2633,30 @@ int tv_dict_add_allocated_str(dict_T *const d, const char *const key, const size
 | 
				
			|||||||
  return OK;
 | 
					  return OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Add a function entry to dictionary.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// @param[out]  d  Dictionary to add entry to.
 | 
				
			||||||
 | 
					/// @param[in]  key  Key to add.
 | 
				
			||||||
 | 
					/// @param[in]  key_len  Key length.
 | 
				
			||||||
 | 
					/// @param[in]  fp  Function to add.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// @return OK in case of success, FAIL when key already exists.
 | 
				
			||||||
 | 
					int tv_dict_add_func(dict_T *const d, const char *const key, const size_t key_len,
 | 
				
			||||||
 | 
					                     ufunc_T *const fp)
 | 
				
			||||||
 | 
					  FUNC_ATTR_NONNULL_ARG(1, 2, 4)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  item->di_tv.v_type = VAR_FUNC;
 | 
				
			||||||
 | 
					  item->di_tv.vval.v_string = xstrdup(fp->uf_name);
 | 
				
			||||||
 | 
					  if (tv_dict_add(d, item) == FAIL) {
 | 
				
			||||||
 | 
					    tv_dict_item_free(item);
 | 
				
			||||||
 | 
					    return FAIL;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  func_ref(item->di_tv.vval.v_string);
 | 
				
			||||||
 | 
					  return OK;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//{{{2 Operations on the whole dict
 | 
					//{{{2 Operations on the whole dict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary.
 | 
					/// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -479,6 +479,9 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
 | 
				
			|||||||
    excp->throw_lnum = SOURCING_LNUM;
 | 
					    excp->throw_lnum = SOURCING_LNUM;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  excp->stacktrace = stacktrace_create();
 | 
				
			||||||
 | 
					  tv_list_ref(excp->stacktrace);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (p_verbose >= 13 || debug_break_level > 0) {
 | 
					  if (p_verbose >= 13 || debug_break_level > 0) {
 | 
				
			||||||
    int save_msg_silent = msg_silent;
 | 
					    int save_msg_silent = msg_silent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -563,6 +566,7 @@ static void discard_exception(except_T *excp, bool was_finished)
 | 
				
			|||||||
    free_msglist(excp->messages);
 | 
					    free_msglist(excp->messages);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  xfree(excp->throw_name);
 | 
					  xfree(excp->throw_name);
 | 
				
			||||||
 | 
					  tv_list_unref(excp->stacktrace);
 | 
				
			||||||
  xfree(excp);
 | 
					  xfree(excp);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -584,6 +588,7 @@ static void catch_exception(except_T *excp)
 | 
				
			|||||||
  excp->caught = caught_stack;
 | 
					  excp->caught = caught_stack;
 | 
				
			||||||
  caught_stack = excp;
 | 
					  caught_stack = excp;
 | 
				
			||||||
  set_vim_var_string(VV_EXCEPTION, excp->value, -1);
 | 
					  set_vim_var_string(VV_EXCEPTION, excp->value, -1);
 | 
				
			||||||
 | 
					  set_vim_var_list(VV_STACKTRACE, excp->stacktrace);
 | 
				
			||||||
  if (*excp->throw_name != NUL) {
 | 
					  if (*excp->throw_name != NUL) {
 | 
				
			||||||
    if (excp->throw_lnum != 0) {
 | 
					    if (excp->throw_lnum != 0) {
 | 
				
			||||||
      vim_snprintf(IObuff, IOSIZE, _("%s, line %" PRId64),
 | 
					      vim_snprintf(IObuff, IOSIZE, _("%s, line %" PRId64),
 | 
				
			||||||
@@ -633,6 +638,7 @@ static void finish_exception(except_T *excp)
 | 
				
			|||||||
  caught_stack = caught_stack->caught;
 | 
					  caught_stack = caught_stack->caught;
 | 
				
			||||||
  if (caught_stack != NULL) {
 | 
					  if (caught_stack != NULL) {
 | 
				
			||||||
    set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1);
 | 
					    set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1);
 | 
				
			||||||
 | 
					    set_vim_var_list(VV_STACKTRACE, caught_stack->stacktrace);
 | 
				
			||||||
    if (*caught_stack->throw_name != NUL) {
 | 
					    if (*caught_stack->throw_name != NUL) {
 | 
				
			||||||
      if (caught_stack->throw_lnum != 0) {
 | 
					      if (caught_stack->throw_lnum != 0) {
 | 
				
			||||||
        vim_snprintf(IObuff, IOSIZE,
 | 
					        vim_snprintf(IObuff, IOSIZE,
 | 
				
			||||||
@@ -651,6 +657,7 @@ static void finish_exception(except_T *excp)
 | 
				
			|||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    set_vim_var_string(VV_EXCEPTION, NULL, -1);
 | 
					    set_vim_var_string(VV_EXCEPTION, NULL, -1);
 | 
				
			||||||
    set_vim_var_string(VV_THROWPOINT, NULL, -1);
 | 
					    set_vim_var_string(VV_THROWPOINT, NULL, -1);
 | 
				
			||||||
 | 
					    set_vim_var_list(VV_STACKTRACE, NULL);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Discard the exception, but use the finish message for 'verbose'.
 | 
					  // Discard the exception, but use the finish message for 'verbose'.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <stdbool.h>
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nvim/eval/typval_defs.h"
 | 
				
			||||||
#include "nvim/pos_defs.h"
 | 
					#include "nvim/pos_defs.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// A list used for saving values of "emsg_silent".  Used by ex_try() to save the
 | 
					/// A list used for saving values of "emsg_silent".  Used by ex_try() to save the
 | 
				
			||||||
@@ -107,6 +108,7 @@ struct vim_exception {
 | 
				
			|||||||
  msglist_T *messages;  ///< message(s) causing error exception
 | 
					  msglist_T *messages;  ///< message(s) causing error exception
 | 
				
			||||||
  char *throw_name;     ///< name of the throw point
 | 
					  char *throw_name;     ///< name of the throw point
 | 
				
			||||||
  linenr_T throw_lnum;  ///< line number of the throw point
 | 
					  linenr_T throw_lnum;  ///< line number of the throw point
 | 
				
			||||||
 | 
					  list_T *stacktrace;   ///< stacktrace
 | 
				
			||||||
  except_T *caught;     ///< next exception on the caught stack
 | 
					  except_T *caught;     ///< next exception on the caught stack
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -228,6 +228,72 @@ char *estack_sfile(estack_arg_T which)
 | 
				
			|||||||
  return (char *)ga.ga_data;
 | 
					  return (char *)ga.ga_data;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void stacktrace_push_item(list_T *const l, ufunc_T *const fp, const char *const event,
 | 
				
			||||||
 | 
					                                 const linenr_T lnum, char *const filepath,
 | 
				
			||||||
 | 
					                                 const bool filepath_alloced)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  dict_T *const d = tv_dict_alloc_lock(VAR_FIXED);
 | 
				
			||||||
 | 
					  typval_T tv = {
 | 
				
			||||||
 | 
					    .v_type = VAR_DICT,
 | 
				
			||||||
 | 
					    .v_lock = VAR_LOCKED,
 | 
				
			||||||
 | 
					    .vval.v_dict = d,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (fp != NULL) {
 | 
				
			||||||
 | 
					    tv_dict_add_func(d, S_LEN("funcref"), fp);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (event != NULL) {
 | 
				
			||||||
 | 
					    tv_dict_add_str(d, S_LEN("event"), event);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  tv_dict_add_nr(d, S_LEN("lnum"), lnum);
 | 
				
			||||||
 | 
					  if (filepath_alloced) {
 | 
				
			||||||
 | 
					    tv_dict_add_allocated_str(d, S_LEN("filepath"), filepath);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    tv_dict_add_str(d, S_LEN("filepath"), filepath);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  tv_list_append_tv(l, &tv);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create the stacktrace from exestack.
 | 
				
			||||||
 | 
					list_T *stacktrace_create(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  list_T *const l = tv_list_alloc(exestack.ga_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (int i = 0; i < exestack.ga_len; i++) {
 | 
				
			||||||
 | 
					    estack_T *const entry = &((estack_T *)exestack.ga_data)[i];
 | 
				
			||||||
 | 
					    linenr_T lnum = entry->es_lnum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (entry->es_type == ETYPE_SCRIPT) {
 | 
				
			||||||
 | 
					      stacktrace_push_item(l, NULL, NULL, lnum, entry->es_name, false);
 | 
				
			||||||
 | 
					    } else if (entry->es_type == ETYPE_UFUNC) {
 | 
				
			||||||
 | 
					      ufunc_T *const fp = entry->es_info.ufunc;
 | 
				
			||||||
 | 
					      const sctx_T sctx = fp->uf_script_ctx;
 | 
				
			||||||
 | 
					      bool filepath_alloced = false;
 | 
				
			||||||
 | 
					      char *filepath = sctx.sc_sid > 0
 | 
				
			||||||
 | 
					                       ? get_scriptname((LastSet){ .script_ctx = sctx },
 | 
				
			||||||
 | 
					                                        &filepath_alloced) : "";
 | 
				
			||||||
 | 
					      lnum += sctx.sc_lnum;
 | 
				
			||||||
 | 
					      stacktrace_push_item(l, fp, NULL, lnum, filepath, filepath_alloced);
 | 
				
			||||||
 | 
					    } else if (entry->es_type == ETYPE_AUCMD) {
 | 
				
			||||||
 | 
					      const sctx_T sctx = entry->es_info.aucmd->script_ctx;
 | 
				
			||||||
 | 
					      bool filepath_alloced = false;
 | 
				
			||||||
 | 
					      char *filepath = sctx.sc_sid > 0
 | 
				
			||||||
 | 
					                       ? get_scriptname((LastSet){ .script_ctx = sctx },
 | 
				
			||||||
 | 
					                                        &filepath_alloced) : "";
 | 
				
			||||||
 | 
					      lnum += sctx.sc_lnum;
 | 
				
			||||||
 | 
					      stacktrace_push_item(l, NULL, entry->es_name, lnum, filepath, filepath_alloced);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return l;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// getstacktrace() function
 | 
				
			||||||
 | 
					void f_getstacktrace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  tv_list_set_ret(rettv, stacktrace_create());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool runtime_search_path_valid = false;
 | 
					static bool runtime_search_path_valid = false;
 | 
				
			||||||
static int *runtime_search_path_ref = NULL;
 | 
					static int *runtime_search_path_ref = NULL;
 | 
				
			||||||
static RuntimeSearchPath runtime_search_path;
 | 
					static RuntimeSearchPath runtime_search_path;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -220,7 +220,8 @@ M.vars = {
 | 
				
			|||||||
    type = 'string',
 | 
					    type = 'string',
 | 
				
			||||||
    desc = [=[
 | 
					    desc = [=[
 | 
				
			||||||
      The value of the exception most recently caught and not
 | 
					      The value of the exception most recently caught and not
 | 
				
			||||||
      finished.  See also |v:throwpoint| and |throw-variables|.
 | 
					      finished.  See also |v:stacktrace|, |v:throwpoint|, and
 | 
				
			||||||
 | 
					      |throw-variables|.
 | 
				
			||||||
      Example: >vim
 | 
					      Example: >vim
 | 
				
			||||||
        try
 | 
					        try
 | 
				
			||||||
          throw "oops"
 | 
					          throw "oops"
 | 
				
			||||||
@@ -701,6 +702,15 @@ M.vars = {
 | 
				
			|||||||
      <
 | 
					      <
 | 
				
			||||||
    ]=],
 | 
					    ]=],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  stacktrace = {
 | 
				
			||||||
 | 
					    type = 'table[]',
 | 
				
			||||||
 | 
					    desc = [=[
 | 
				
			||||||
 | 
					      The stack trace of the exception most recently caught and
 | 
				
			||||||
 | 
					      not finished.  Refer to |getstacktrace()| for the structure of
 | 
				
			||||||
 | 
					      stack trace.  See also |v:exception|, |v:throwpoint|, and
 | 
				
			||||||
 | 
					      |throw-variables|.
 | 
				
			||||||
 | 
					    ]=],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  statusmsg = {
 | 
					  statusmsg = {
 | 
				
			||||||
    type = 'string',
 | 
					    type = 'string',
 | 
				
			||||||
    desc = [=[
 | 
					    desc = [=[
 | 
				
			||||||
@@ -823,7 +833,7 @@ M.vars = {
 | 
				
			|||||||
    desc = [=[
 | 
					    desc = [=[
 | 
				
			||||||
      The point where the exception most recently caught and not
 | 
					      The point where the exception most recently caught and not
 | 
				
			||||||
      finished was thrown.  Not set when commands are typed.  See
 | 
					      finished was thrown.  Not set when commands are typed.  See
 | 
				
			||||||
      also |v:exception| and |throw-variables|.
 | 
					      also |v:exception|, |v:stacktrace|, and |throw-variables|.
 | 
				
			||||||
      Example: >vim
 | 
					      Example: >vim
 | 
				
			||||||
        try
 | 
					        try
 | 
				
			||||||
          throw "oops"
 | 
					          throw "oops"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										107
									
								
								test/old/testdir/test_stacktrace.vim
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								test/old/testdir/test_stacktrace.vim
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					" Test for getstacktrace() and v:stacktrace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let s:thisfile = expand('%:p')
 | 
				
			||||||
 | 
					let s:testdir = s:thisfile->fnamemodify(':h')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Filepath(name)
 | 
				
			||||||
 | 
					  return s:testdir .. '/' .. a:name
 | 
				
			||||||
 | 
					endfunc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AssertStacktrace(expect, actual)
 | 
				
			||||||
 | 
					  call assert_equal(#{lnum: 581, filepath: Filepath('runtest.vim')}, a:actual[0])
 | 
				
			||||||
 | 
					  call assert_equal(a:expect, a:actual[-len(a:expect):])
 | 
				
			||||||
 | 
					endfunc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_getstacktrace()
 | 
				
			||||||
 | 
					  let g:stacktrace = []
 | 
				
			||||||
 | 
					  let lines1 =<< trim [SCRIPT]
 | 
				
			||||||
 | 
					  " Xscript1
 | 
				
			||||||
 | 
					  source Xscript2
 | 
				
			||||||
 | 
					  func Xfunc1()
 | 
				
			||||||
 | 
					    " Xfunc1
 | 
				
			||||||
 | 
					    call Xfunc2()
 | 
				
			||||||
 | 
					  endfunc
 | 
				
			||||||
 | 
					  [SCRIPT]
 | 
				
			||||||
 | 
					  let lines2 =<< trim [SCRIPT]
 | 
				
			||||||
 | 
					  " Xscript2
 | 
				
			||||||
 | 
					  func Xfunc2()
 | 
				
			||||||
 | 
					    " Xfunc2
 | 
				
			||||||
 | 
					    let g:stacktrace = getstacktrace()
 | 
				
			||||||
 | 
					  endfunc
 | 
				
			||||||
 | 
					  [SCRIPT]
 | 
				
			||||||
 | 
					  call writefile(lines1, 'Xscript1', 'D')
 | 
				
			||||||
 | 
					  call writefile(lines2, 'Xscript2', 'D')
 | 
				
			||||||
 | 
					  source Xscript1
 | 
				
			||||||
 | 
					  call Xfunc1()
 | 
				
			||||||
 | 
					  call AssertStacktrace([
 | 
				
			||||||
 | 
					        \ #{funcref: funcref('Test_getstacktrace'), lnum: 35, filepath: s:thisfile},
 | 
				
			||||||
 | 
					        \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')},
 | 
				
			||||||
 | 
					        \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')},
 | 
				
			||||||
 | 
					        \ ], g:stacktrace)
 | 
				
			||||||
 | 
					  unlet g:stacktrace
 | 
				
			||||||
 | 
					endfunc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_getstacktrace_event()
 | 
				
			||||||
 | 
					  let g:stacktrace = []
 | 
				
			||||||
 | 
					  let lines1 =<< trim [SCRIPT]
 | 
				
			||||||
 | 
					  " Xscript1
 | 
				
			||||||
 | 
					  func Xfunc()
 | 
				
			||||||
 | 
					    " Xfunc
 | 
				
			||||||
 | 
					    let g:stacktrace = getstacktrace()
 | 
				
			||||||
 | 
					  endfunc
 | 
				
			||||||
 | 
					  augroup test_stacktrace
 | 
				
			||||||
 | 
					    autocmd SourcePre * call Xfunc()
 | 
				
			||||||
 | 
					  augroup END
 | 
				
			||||||
 | 
					  [SCRIPT]
 | 
				
			||||||
 | 
					  let lines2 =<< trim [SCRIPT]
 | 
				
			||||||
 | 
					  " Xscript2
 | 
				
			||||||
 | 
					  [SCRIPT]
 | 
				
			||||||
 | 
					  call writefile(lines1, 'Xscript1', 'D')
 | 
				
			||||||
 | 
					  call writefile(lines2, 'Xscript2', 'D')
 | 
				
			||||||
 | 
					  source Xscript1
 | 
				
			||||||
 | 
					  source Xscript2
 | 
				
			||||||
 | 
					  call AssertStacktrace([
 | 
				
			||||||
 | 
					       \ #{funcref: funcref('Test_getstacktrace_event'), lnum: 62, filepath: s:thisfile},
 | 
				
			||||||
 | 
					       \ #{event: 'SourcePre Autocommands for "*"', lnum: 7, filepath: Filepath('Xscript1')},
 | 
				
			||||||
 | 
					       \ #{funcref: funcref('Xfunc'), lnum: 4, filepath: Filepath('Xscript1')},
 | 
				
			||||||
 | 
					       \ ], g:stacktrace)
 | 
				
			||||||
 | 
					  augroup test_stacktrace
 | 
				
			||||||
 | 
					    autocmd!
 | 
				
			||||||
 | 
					  augroup END
 | 
				
			||||||
 | 
					  unlet g:stacktrace
 | 
				
			||||||
 | 
					endfunc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_vstacktrace()
 | 
				
			||||||
 | 
					  let lines1 =<< trim [SCRIPT]
 | 
				
			||||||
 | 
					  " Xscript1
 | 
				
			||||||
 | 
					  source Xscript2
 | 
				
			||||||
 | 
					  func Xfunc1()
 | 
				
			||||||
 | 
					    " Xfunc1
 | 
				
			||||||
 | 
					    call Xfunc2()
 | 
				
			||||||
 | 
					  endfunc
 | 
				
			||||||
 | 
					  [SCRIPT]
 | 
				
			||||||
 | 
					  let lines2 =<< trim [SCRIPT]
 | 
				
			||||||
 | 
					  " Xscript2
 | 
				
			||||||
 | 
					  func Xfunc2()
 | 
				
			||||||
 | 
					    " Xfunc2
 | 
				
			||||||
 | 
					    throw 'Exception from Xfunc2'
 | 
				
			||||||
 | 
					  endfunc
 | 
				
			||||||
 | 
					  [SCRIPT]
 | 
				
			||||||
 | 
					  call writefile(lines1, 'Xscript1', 'D')
 | 
				
			||||||
 | 
					  call writefile(lines2, 'Xscript2', 'D')
 | 
				
			||||||
 | 
					  source Xscript1
 | 
				
			||||||
 | 
					  call assert_equal([], v:stacktrace)
 | 
				
			||||||
 | 
					  try
 | 
				
			||||||
 | 
					    call Xfunc1()
 | 
				
			||||||
 | 
					  catch
 | 
				
			||||||
 | 
					    let stacktrace = v:stacktrace
 | 
				
			||||||
 | 
					  endtry
 | 
				
			||||||
 | 
					  call assert_equal([], v:stacktrace)
 | 
				
			||||||
 | 
					  call AssertStacktrace([
 | 
				
			||||||
 | 
					       \ #{funcref: funcref('Test_vstacktrace'), lnum: 95, filepath: s:thisfile},
 | 
				
			||||||
 | 
					       \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')},
 | 
				
			||||||
 | 
					       \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')},
 | 
				
			||||||
 | 
					       \ ], stacktrace)
 | 
				
			||||||
 | 
					endfunc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					" vim: shiftwidth=2 sts=2 expandtab
 | 
				
			||||||
		Reference in New Issue
	
	Block a user