patch 9.1.1396: 'errorformat' is a global option

Problem:  The 'grepformat' option is global option, but it would be
          useful to have it buffer-local, similar to 'errorformat' and
          other quickfix related options (Dani Dickstein)
Solution: Add the necessary code to support global-local 'grepformat',
          allowing different buffers to parse different grep output
          formats (glepnir)

fixes: #17316
closes: #17315

Signed-off-by: glepnir <glephunter@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 274d56e..e74c5e8 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 May 14
+*options.txt*	For Vim version 9.1.  Last change: 2025 May 16
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -4062,7 +4062,7 @@
 
 						*'grepformat'* *'gfm'*
 'grepformat' 'gfm'	string	(default "%f:%l:%m,%f:%l%m,%f  %l%m")
-			global
+			global or local to buffer |global-local|
 	Format to recognize for the ":grep" command output.
 	This is a scanf-like string that uses the same format as the
 	'errorformat' option: see |errorformat|.
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 5242842..e03deed 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 May 14
+*version9.txt*  For Vim version 9.1.  Last change: 2025 May 16
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41634,17 +41634,19 @@
 - the default for 'commentstring' contains whitespace padding to have
   automatic comments look nicer |comment-install|
 - 'completeopt' is now a |global-local| option.
-- 'nrformats' accepts the new "blank" suboption, to determine a signed or
-  unsigned number based on whitespace in front of a minus sign.
 - add 'cpoptions' flag "z" |cpo-z|, to disable some (traditional) vi
   behaviour/inconsistency (see |d-special| and |cw|).
+- new option values for 'fillchars':
+	"trunc"		- configure truncation indicator, 'pummaxwidth'
+	"truncrl"	- like "trunc" but in 'rl' mode, 'pummaxwidth'
+	"tpl_vert"	- separators for the 'tabpanel'
+- 'grepformat' is now a |global-local| option.
+- adjust for GTK3 dropping some mouse cursors 'mouseshape'
+- 'nrformats' accepts the new "blank" suboption, to determine a signed or
+  unsigned number based on whitespace in front of a minus sign.
 - 'rulerformat' now supports the |stl-%!| item
 - use 'smoothscroll' logic for CTRL-F / CTRL-B for pagewise scrolling
   and CTRL-D / CTRL-U for half-pagewise scrolling
-- New option value for 'fillchars':
-	"trunc"		- configure truncation indicator, 'pummaxwidth'
-	"truncrl"	- like "trunc" but in 'rl' mode, 'pummaxwidth'
-- adjust for GTK3 dropping some mouse cursors 'mouseshape'
 
 Ex commands: ~
 - allow to specify a priority when defining a new sign |:sign-define|
diff --git a/src/buffer.c b/src/buffer.c
index 48e8cb6..fe19269 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2508,6 +2508,7 @@
     free_callback(&buf->b_tsrfu_cb);
 #endif
 #ifdef FEAT_QUICKFIX
+    clear_string_option(&buf->b_p_gefm);
     clear_string_option(&buf->b_p_gp);
     clear_string_option(&buf->b_p_mp);
     clear_string_option(&buf->b_p_efm);
diff --git a/src/option.c b/src/option.c
index d6a0098..85c3705 100644
--- a/src/option.c
+++ b/src/option.c
@@ -6429,6 +6429,9 @@
 	case PV_EFM:
 	    clear_string_option(&buf->b_p_efm);
 	    break;
+	case PV_GEFM:
+	    clear_string_option(&buf->b_p_gefm);
+	    break;
 	case PV_GP:
 	    clear_string_option(&buf->b_p_gp);
 	    break;
@@ -6508,6 +6511,7 @@
 #endif
 #ifdef FEAT_QUICKFIX
 	    case PV_EFM:  return (char_u *)&(curbuf->b_p_efm);
+	    case PV_GEFM: return (char_u *)&(curbuf->b_p_gefm);
 	    case PV_GP:   return (char_u *)&(curbuf->b_p_gp);
 	    case PV_MP:   return (char_u *)&(curbuf->b_p_mp);
 #endif
@@ -6626,6 +6630,8 @@
 #ifdef FEAT_QUICKFIX
 	case PV_EFM:	return *curbuf->b_p_efm != NUL
 				    ? (char_u *)&(curbuf->b_p_efm) : p->var;
+	case PV_GEFM:	return *curbuf->b_p_gefm != NUL
+				    ? (char_u *)&(curbuf->b_p_gefm) : p->var;
 	case PV_GP:	return *curbuf->b_p_gp != NUL
 				    ? (char_u *)&(curbuf->b_p_gp) : p->var;
 	case PV_MP:	return *curbuf->b_p_mp != NUL
@@ -7415,6 +7421,7 @@
 	    buf->b_p_bkc = empty_option;
 	    buf->b_bkc_flags = 0;
 #ifdef FEAT_QUICKFIX
+	    buf->b_p_gefm = empty_option;
 	    buf->b_p_gp = empty_option;
 	    buf->b_p_mp = empty_option;
 	    buf->b_p_efm = empty_option;
diff --git a/src/option.h b/src/option.h
index e78a7cb..740f6ee 100644
--- a/src/option.h
+++ b/src/option.h
@@ -1157,6 +1157,7 @@
     , BV_BT
 #ifdef FEAT_QUICKFIX
     , BV_EFM
+    , BV_GEFM
     , BV_GP
     , BV_MP
 #endif
diff --git a/src/optiondefs.h b/src/optiondefs.h
index d509463..cb4376c 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -33,6 +33,7 @@
 #define PV_BT		OPT_BUF(BV_BT)
 #ifdef FEAT_QUICKFIX
 # define PV_EFM		OPT_BOTH(OPT_BUF(BV_EFM))
+# define PV_GEFM	OPT_BOTH(OPT_BUF(BV_GEFM))
 # define PV_GP		OPT_BOTH(OPT_BUF(BV_GP))
 # define PV_MP		OPT_BOTH(OPT_BUF(BV_MP))
 #endif
@@ -1154,7 +1155,7 @@
 			    {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
     {"grepformat",  "gfm",  P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
 #ifdef FEAT_QUICKFIX
-			    (char_u *)&p_gefm, PV_NONE, NULL, NULL,
+			    (char_u *)&p_gefm, PV_GEFM, NULL, NULL,
 			    {(char_u *)DFLT_GREPFORMAT, (char_u *)0L}
 #else
 			    (char_u *)NULL, PV_NONE, NULL, NULL,
diff --git a/src/optionstr.c b/src/optionstr.c
index 8e233e8..7a1cd69 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -327,6 +327,7 @@
     check_string_option(&buf->b_p_keymap);
 #endif
 #ifdef FEAT_QUICKFIX
+    check_string_option(&buf->b_p_gefm);
     check_string_option(&buf->b_p_gp);
     check_string_option(&buf->b_p_mp);
     check_string_option(&buf->b_p_efm);
diff --git a/src/quickfix.c b/src/quickfix.c
index d012ea0..ab595d7 100644
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -5503,7 +5503,7 @@
     incr_quickfix_busy();
 
     if (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake)
-	errorformat = p_gefm;
+	errorformat =  *curbuf->b_p_gefm != NUL ? curbuf->b_p_gefm : p_gefm;
     if (eap->cmdidx == CMD_grepadd || eap->cmdidx == CMD_lgrepadd)
 	newlist = FALSE;
 
diff --git a/src/structs.h b/src/structs.h
index cd7370d..00b0746 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -3371,6 +3371,7 @@
      * local values for options which are normally global
      */
 #ifdef FEAT_QUICKFIX
+    char_u	*b_p_gefm;	// 'grepformat' local value
     char_u	*b_p_gp;	// 'grepprg' local value
     char_u	*b_p_mp;	// 'makeprg' local value
     char_u	*b_p_efm;	// 'errorformat' local value
diff --git a/src/testdir/test_quickfix.vim b/src/testdir/test_quickfix.vim
index fe85887..dc2a2a4 100644
--- a/src/testdir/test_quickfix.vim
+++ b/src/testdir/test_quickfix.vim
@@ -2379,6 +2379,25 @@
   call s:test_xgrep('l')
 endfunc
 
+func Test_local_grepformat()
+  let save_grepformat = &grepformat
+  set grepformat=%f:%l:%m
+  " The following line are used for the local grep test. Don't remove.
+  " UNIQUEPREFIX:2:3: Local grepformat test
+  new
+  setlocal grepformat=UNIQUEPREFIX:%c:%n:%m
+  call assert_equal('UNIQUEPREFIX:%c:%n:%m', &l:grepformat)
+  call assert_equal('%f:%l:%m', &g:grepformat)
+
+  set grepprg=internal
+  silent grep "^[[:space:]]*\" UNIQUEPREFIX:" test_quickfix.vim
+  call assert_equal(1, len(getqflist()))
+  set grepprg&vim
+
+  bwipe!
+  let &grepformat = save_grepformat
+endfunc
+
 func Test_two_windows()
   " Use one 'errorformat' for two windows.  Add an expression to each of them,
   " make sure they each keep their own state.
diff --git a/src/version.c b/src/version.c
index e367036..f282c1f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -710,6 +710,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1396,
+/**/
     1395,
 /**/
     1394,