patch 9.1.1341: cannot define completion triggers

Problem:  Cannot define completion triggers and act upon it
Solution: add the new option 'isexpand' and add the complete_match()
          function to return the completion matches according to the
          'isexpand' setting (glepnir)

Currently, completion trigger position is determined solely by the
'iskeyword' pattern (\k\+$), which causes issues when users need
different completion behaviors - such as triggering after '/' for
comments or '.' for methods. Modifying 'iskeyword' to include these
characters has undesirable side effects on other Vim functionality that
relies on keyword definitions.

Introduce a new buffer-local option 'isexpand' that allows specifying
different completion triggers and add the complete_match() function that
finds the appropriate start column for completion based on these
triggers, scanning backwards from cursor position.

This separation of concerns allows customized completion behavior
without affecting iskeyword-dependent features. The option's
buffer-local nature enables per-filetype completion triggers.

closes: #16716

Signed-off-by: glepnir <glephunter@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index e6747ad..9f91f47 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt*	For Vim version 9.1.  Last change: 2025 Apr 23
+*builtin.txt*	For Vim version 9.1.  Last change: 2025 Apr 24
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -136,6 +136,7 @@
 complete_add({expr})		Number	add completion match
 complete_check()		Number	check for key typed during completion
 complete_info([{what}])		Dict	get current completion information
+complete_match([{lnum}, {col}])	List	get completion column and trigger text
 confirm({msg} [, {choices} [, {default} [, {type}]]])
 				Number	number of choice picked by user
 copy({expr})			any	make a shallow copy of {expr}
@@ -2032,6 +2033,50 @@
 <
 		Return type: dict<any>
 
+complete_match([{lnum}, {col}])			*complete_match()*
+		Returns a List of matches found according to the 'isexpand'
+		option. Each match is represented as a List containing
+		[startcol, trigger_text] where:
+		- startcol: column position where completion should start,
+		  or -1 if no trigger position is found. For multi-character
+		  triggers, returns the column of the first character.
+		- trigger_text: the matching trigger string from 'isexpand',
+		  or empty string if no match was found or when using the
+		  default 'iskeyword' pattern.
+
+		When 'isexpand' is empty, uses the 'iskeyword' pattern
+		"\k\+$" to find the start of the current keyword.
+
+		When no arguments are provided, uses the current cursor
+		position.
+
+		Examples: >
+	set isexpand=.,->,/,/*,abc
+	func CustomComplete()
+	  let res = complete_match()
+	  if res->len() == 0 | return | endif
+	  let [col, trigger] = res[0]
+	  let items = []
+	  if trigger == '/*'
+	    let items = ['/** */']
+	  elseif trigger == '/'
+	    let items = ['/*! */', '// TODO:', '// fixme:']
+	  elseif trigger == '.'
+	    let items = ['length()']
+	  elseif trigger =~ '^\->'
+	    let items = ['map()', 'reduce()']
+	  elseif trigger =~ '^\abc'
+	    let items = ['def', 'ghk']
+	  endif
+	  if items->len() > 0
+	    let startcol = trigger =~ '^/' ? col : col + len(trigger)
+	    call complete(startcol, items)
+	  endif
+	endfunc
+	inoremap <Tab> <Cmd>call CustomComplete()<CR>
+<
+		Return type: list<list<any>>
+
 						*confirm()*
 confirm({msg} [, {choices} [, {default} [, {type}]]])
 		confirm() offers the user a dialog, from which a choice can be
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 8073457..03a6a6c 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*	For Vim version 9.1.  Last change: 2025 Apr 19
+*options.txt*	For Vim version 9.1.  Last change: 2025 Apr 24
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -4983,6 +4983,19 @@
 	and there is a letter before it, the completed part is made uppercase.
 	With 'noinfercase' the match is used as-is.
 
+						*'isexpand'* *'ise'*
+'isexpand' 'ise'	string	(default: "")
+			local to buffer
+	Defines characters and patterns for completion in insert mode. Used by
+	the |complete_match()| function to determine the starting position for
+	completion. This is a comma-separated list of triggers. Each trigger
+	can be:
+	- A single character like "." or "/"
+	- A sequence of characters like "->", "/*", or "/**"
+
+	Note: Use "\\," to add a literal comma as trigger character, see
+	|option-backslash|.
+
 			*'insertmode'* *'im'* *'noinsertmode'* *'noim'*
 'insertmode' 'im'	boolean	(default off)
 			global
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 6369770..a094e14 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -436,6 +436,8 @@
 'infercase'	options.txt	/*'infercase'*
 'insertmode'	options.txt	/*'insertmode'*
 'is'	options.txt	/*'is'*
+'ise'	options.txt	/*'ise'*
+'isexpand'	options.txt	/*'isexpand'*
 'isf'	options.txt	/*'isf'*
 'isfname'	options.txt	/*'isfname'*
 'isi'	options.txt	/*'isi'*
@@ -6663,6 +6665,7 @@
 complete_check()	builtin.txt	/*complete_check()*
 complete_info()	builtin.txt	/*complete_info()*
 complete_info_mode	builtin.txt	/*complete_info_mode*
+complete_match()	builtin.txt	/*complete_match()*
 completed_item-variable	eval.txt	/*completed_item-variable*
 completion-functions	usr_41.txt	/*completion-functions*
 complex-change	change.txt	/*complex-change*
diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt
index a3f2d7c..5223687 100644
--- a/runtime/doc/todo.txt
+++ b/runtime/doc/todo.txt
@@ -1,4 +1,4 @@
-*todo.txt*      For Vim version 9.1.  Last change: 2025 Mar 27
+*todo.txt*      For Vim version 9.1.  Last change: 2025 Apr 24
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -4749,20 +4749,10 @@
 7   When expanding file names with an environment variable, add the match with
     the unexpanded var.  So $HOME/tm expands to "/home/guy/tmp" and
     "$HOME/tmp"
-8   When there is no word before the cursor but something like "sys." complete
-    with "sys.".  Works well for C and similar languages.
 9   ^X^L completion doesn't repeat correctly.  It uses the first match with
     the last added line, instead of continuing where the last match ended.
     (Webb)
-8   Add option to set different behavior for Insert mode completion:
-    - ignore/match case
-    - different characters than 'iskeyword'
-8   Add option 'isexpand', containing characters when doing expansion (so that
-    "." and "\" can be included, without changing 'iskeyword'). (Goldfarb)
-    Also: 'istagword': characters used for CTRL-].
-    When 'isexpand' or 'istagword' are empty, use 'iskeyword'.
-    Alternative: Use a pattern so that start and end of a keyword can be
-    defined, only allow dash in the middle, etc.
+8   Add option 'istagword': characters used for CTRL-]. like 'isexpand'
 8   Add a command to undo the completion, go back to the original text.
 7   Completion of an abbreviation: Can leave letters out, like what Instant
     text does: www.textware.com
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 0c24b84..a22b6f7 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1,4 +1,4 @@
-*usr_41.txt*	For Vim version 9.1.  Last change: 2025 Apr 21
+*usr_41.txt*	For Vim version 9.1.  Last change: 2025 Apr 24
 
 		     VIM USER MANUAL - by Bram Moolenaar
 
@@ -1124,6 +1124,8 @@
 	complete_add()		add to found matches
 	complete_check()	check if completion should be aborted
 	complete_info()		get current completion information
+	complete_match()	get insert completion start match col and
+				trigger text
 	pumvisible()		check if the popup menu is displayed
 	pum_getpos()		position and size of popup menu if visible
 
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index dfbe457..9560b1d 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2025 Apr 23
+*version9.txt*  For Vim version 9.1.  Last change: 2025 Apr 24
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41688,6 +41688,7 @@
 |blob2str()|		convert a blob into a List of strings
 |bindtextdomain()|	set message lookup translation base path
 |cmdcomplete_info()|	get current cmdline completion info
+|complete_match()|	get completion and trigger info
 |diff()|		diff two Lists of strings
 |filecopy()|		copy a file {from} to {to}
 |foreach()|		apply function to List items
@@ -41750,6 +41751,7 @@
 'eventignorewin'	autocommand events that are ignored in a window
 'findfunc'		Vim function to obtain the results for a |:find|
 			command
+'isexpand'		defines triggers for completion
 'lhistory'		Size of the location list stack |quickfix-stack|.
 'messagesopt'		configure |:messages| and |hit-enter| prompt
 'pummaxwidth'		maximum width for the completion popup menu
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index c5e77af..941a63c 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -1,7 +1,7 @@
 " These commands create the option window.
 "
 " Maintainer:	The Vim Project <https://github.com/vim/vim>
-" Last Change:	2025 Apr 07
+" Last Change:	2025 Apr 24
 " Former Maintainer:	Bram Moolenaar <Bram@vim.org>
 
 " If there already is an option window, jump to that one.
@@ -1254,6 +1254,8 @@
 call <SID>OptionG("isf", &isf)
 call <SID>AddOption("isident", gettext("specifies the characters in an identifier"))
 call <SID>OptionG("isi", &isi)
+call <SID>AddOption("isexpand", gettext("defines trigger strings for complete_match()"))
+call append("$", "\t" .. s:local_to_buffer)
 call <SID>AddOption("iskeyword", gettext("specifies the characters in a keyword"))
 call append("$", "\t" .. s:local_to_buffer)
 call <SID>OptionL("isk")