patch 8.2.3900: it is not easy to use a script-local function for an option

Problem:    It is not easy to use a script-local function for an option.
Solution:   recognize s: and <SID> at the start of the expression. (Yegappan
            Lakshmanan, closes #9401)
diff --git a/src/optionstr.c b/src/optionstr.c
index dbb93cd..c69354e 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -2026,14 +2026,6 @@
 		newFoldLevel();
 	}
     }
-# ifdef FEAT_EVAL
-    // 'foldexpr'
-    else if (varp == &curwin->w_p_fde)
-    {
-	if (foldmethodIsExpr(curwin))
-	    foldUpdateAll(curwin);
-    }
-# endif
     // 'foldmarker'
     else if (gvarp == &curwin->w_allbuf_opt.wo_fmr)
     {
@@ -2307,6 +2299,89 @@
 # endif
 #endif
 
+#ifdef FEAT_EVAL
+    // '*expr' options
+    else if (
+# ifdef FEAT_BEVAL
+	    varp == &p_bexpr ||
+# endif
+# ifdef FEAT_DIFF
+	    varp == &p_dex ||
+# endif
+# ifdef FEAT_FOLDING
+	    varp == &curwin->w_p_fde ||
+# endif
+	    gvarp == &p_fex ||
+# ifdef FEAT_FIND_ID
+	    gvarp == &p_inex ||
+# endif
+# ifdef FEAT_CINDENT
+	    gvarp == &p_inde ||
+# endif
+# ifdef FEAT_DIFF
+	    varp == &p_pex ||
+# endif
+# ifdef FEAT_POSTSCRIPT
+	    varp == &p_pexpr ||
+# endif
+	    FALSE
+	    )
+    {
+	char_u	**p_opt = NULL;
+	char_u	*name;
+
+	// If the option value starts with <SID> or s:, then replace that with
+	// the script identifier.
+# ifdef FEAT_BEVAL
+	if (varp == &p_bexpr)		// 'balloonexpr'
+	    p_opt = (opt_flags & OPT_LOCAL) ? &curbuf->b_p_bexpr : &p_bexpr;
+# endif
+# ifdef FEAT_DIFF
+	if (varp == &p_dex)	// 'diffexpr'
+	    p_opt = &p_dex;
+# endif
+# ifdef FEAT_FOLDING
+	if(varp == &curwin->w_p_fde)	// 'foldexpr'
+	    p_opt = &curwin->w_p_fde;
+# endif
+	if (gvarp == &p_fex)	// 'formatexpr'
+	    p_opt = &curbuf->b_p_fex;
+# ifdef FEAT_FIND_ID
+	if (gvarp == &p_inex)	// 'includeexpr'
+	    p_opt = &curbuf->b_p_inex;
+# endif
+# ifdef FEAT_CINDENT
+	if (gvarp == &p_inde)	// 'indentexpr'
+	    p_opt = &curbuf->b_p_inde;
+# endif
+# ifdef FEAT_DIFF
+	if (varp == &p_pex)	// 'patchexpr'
+	    p_opt = &p_pex;
+# endif
+# ifdef FEAT_POSTSCRIPT
+	if (varp == &p_pexpr)	// 'printexpr'
+	    p_opt = &p_pexpr;
+# endif
+
+	if (p_opt != NULL)
+	{
+	    name = get_scriptlocal_funcname(*p_opt);
+	    if (name != NULL)
+	    {
+		if (new_value_alloced)
+		    free_string_option(*p_opt);
+		*p_opt = name;
+		new_value_alloced = TRUE;
+	    }
+	}
+
+# ifdef FEAT_FOLDING
+	if (varp == &curwin->w_p_fde && foldmethodIsExpr(curwin))
+	    foldUpdateAll(curwin);
+# endif
+    }
+#endif
+
 #ifdef FEAT_COMPL_FUNC
     // 'completefunc'
     else if (gvarp == &p_cfu)
diff --git a/src/testdir/test_diffmode.vim b/src/testdir/test_diffmode.vim
index 9338fe0..d4c44ce 100644
--- a/src/testdir/test_diffmode.vim
+++ b/src/testdir/test_diffmode.vim
@@ -681,8 +681,19 @@
   set diffexpr=NewDiffFunc()
   call assert_fails('windo diffthis', ['E117:', 'E97:'])
   diffoff!
+
+  " Using a script-local function
+  func s:NewDiffExpr()
+  endfunc
+  set diffexpr=s:NewDiffExpr()
+  call assert_equal(expand('<SID>') .. 'NewDiffExpr()', &diffexpr)
+  set diffexpr=<SID>NewDiffExpr()
+  call assert_equal(expand('<SID>') .. 'NewDiffExpr()', &diffexpr)
+
   %bwipe!
   set diffexpr& diffopt&
+  delfunc DiffExpr
+  delfunc s:NewDiffExpr
 endfunc
 
 func Test_diffpatch()
@@ -1216,10 +1227,19 @@
   call assert_equal(2, winnr('$'))
   call assert_true(&diff)
 
+  " Using a script-local function
+  func s:NewPatchExpr()
+  endfunc
+  set patchexpr=s:NewPatchExpr()
+  call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr)
+  set patchexpr=<SID>NewPatchExpr()
+  call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr)
+
   call delete('Xinput')
   call delete('Xdiff')
   set patchexpr&
   delfunc TPatch
+  delfunc s:NewPatchExpr
   %bwipe!
 endfunc
 
diff --git a/src/testdir/test_edit.vim b/src/testdir/test_edit.vim
index 46a4752..c39c8a0 100644
--- a/src/testdir/test_edit.vim
+++ b/src/testdir/test_edit.vim
@@ -324,6 +324,16 @@
   set cinkeys&vim indentkeys&vim
   set nocindent indentexpr=
   delfu Do_Indent
+
+  " Using a script-local function
+  func s:NewIndentExpr()
+  endfunc
+  set indentexpr=s:NewIndentExpr()
+  call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
+  set indentexpr=<SID>NewIndentExpr()
+  call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
+  set indentexpr&
+
   bw!
 endfunc
 
diff --git a/src/testdir/test_fold.vim b/src/testdir/test_fold.vim
index 85fa43d..d35b7c8 100644
--- a/src/testdir/test_fold.vim
+++ b/src/testdir/test_fold.vim
@@ -1382,4 +1382,30 @@
   bw!
 endfunc
 
+" Test for using a script-local function for 'foldexpr'
+func Test_foldexpr_scriptlocal_func()
+  func! s:FoldFunc()
+    let g:FoldLnum = v:lnum
+  endfunc
+  new | only
+  call setline(1, 'abc')
+  let g:FoldLnum = 0
+  set foldmethod=expr foldexpr=s:FoldFunc()
+  redraw!
+  call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr)
+  call assert_equal(1, g:FoldLnum)
+  set foldmethod& foldexpr=
+  bw!
+  new | only
+  call setline(1, 'abc')
+  let g:FoldLnum = 0
+  set foldmethod=expr foldexpr=<SID>FoldFunc()
+  redraw!
+  call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr)
+  call assert_equal(1, g:FoldLnum)
+  set foldmethod& foldexpr=
+  delfunc s:FoldFunc
+  bw!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_gf.vim b/src/testdir/test_gf.vim
index 5e5de41..1b13c42 100644
--- a/src/testdir/test_gf.vim
+++ b/src/testdir/test_gf.vim
@@ -217,4 +217,30 @@
   delfunc IncFunc
 endfunc
 
+" Test for using a script-local function for 'includeexpr'
+func Test_includeexpr_scriptlocal_func()
+  func! s:IncludeFunc()
+    let g:IncludeFname = v:fname
+    return ''
+  endfunc
+  set includeexpr=s:IncludeFunc()
+  call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr)
+  new | only
+  call setline(1, 'TestFile1')
+  let g:IncludeFname = ''
+  call assert_fails('normal! gf', 'E447:')
+  call assert_equal('TestFile1', g:IncludeFname)
+  bw!
+  set includeexpr=<SID>IncludeFunc()
+  call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr)
+  new | only
+  call setline(1, 'TestFile2')
+  let g:IncludeFname = ''
+  call assert_fails('normal! gf', 'E447:')
+  call assert_equal('TestFile2', g:IncludeFname)
+  set includeexpr&
+  delfunc s:IncludeFunc
+  bw!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_gui.vim b/src/testdir/test_gui.vim
index 133298d..9055284 100644
--- a/src/testdir/test_gui.vim
+++ b/src/testdir/test_gui.vim
@@ -258,6 +258,15 @@
   setl balloonexpr&
   call assert_equal('', &balloonexpr)
   delfunc MyBalloonExpr
+
+  " Using a script-local function
+  func s:NewBalloonExpr()
+  endfunc
+  set balloonexpr=s:NewBalloonExpr()
+  call assert_equal(expand('<SID>') .. 'NewBalloonExpr()', &balloonexpr)
+  set balloonexpr=<SID>NewBalloonExpr()
+  call assert_equal(expand('<SID>') .. 'NewBalloonExpr()', &balloonexpr)
+  delfunc s:NewBalloonExpr
   bwipe!
 
   " Multiline support
diff --git a/src/testdir/test_hardcopy.vim b/src/testdir/test_hardcopy.vim
index e390bd5..be83728 100644
--- a/src/testdir/test_hardcopy.vim
+++ b/src/testdir/test_hardcopy.vim
@@ -125,6 +125,14 @@
   set printexpr=PrintFails(v:fname_in)
   call assert_fails('hardcopy', 'E365:')
 
+  " Using a script-local function
+  func s:NewPrintExpr()
+  endfunc
+  set printexpr=s:NewPrintExpr()
+  call assert_equal(expand('<SID>') .. 'NewPrintExpr()', &printexpr)
+  set printexpr=<SID>NewPrintExpr()
+  call assert_equal(expand('<SID>') .. 'NewPrintExpr()', &printexpr)
+
   set printexpr&
   bwipe
 endfunc
diff --git a/src/testdir/test_normal.vim b/src/testdir/test_normal.vim
index 57ea6d3..6458a1b 100644
--- a/src/testdir/test_normal.vim
+++ b/src/testdir/test_normal.vim
@@ -253,6 +253,45 @@
   close!
 endfunc
 
+" Test for using a script-local function for 'formatexpr'
+func Test_formatexpr_scriptlocal_func()
+  func! s:Format()
+    let g:FormatArgs = [v:lnum, v:count]
+  endfunc
+  set formatexpr=s:Format()
+  call assert_equal(expand('<SID>') .. 'Format()', &formatexpr)
+  new | only
+  call setline(1, range(1, 40))
+  let g:FormatArgs = []
+  normal! 2GVjgq
+  call assert_equal([2, 2], g:FormatArgs)
+  bw!
+  set formatexpr=<SID>Format()
+  call assert_equal(expand('<SID>') .. 'Format()', &formatexpr)
+  new | only
+  call setline(1, range(1, 40))
+  let g:FormatArgs = []
+  normal! 4GVjgq
+  call assert_equal([4, 2], g:FormatArgs)
+  bw!
+  let &formatexpr = 's:Format()'
+  new | only
+  call setline(1, range(1, 40))
+  let g:FormatArgs = []
+  normal! 6GVjgq
+  call assert_equal([6, 2], g:FormatArgs)
+  bw!
+  let &formatexpr = '<SID>Format()'
+  new | only
+  call setline(1, range(1, 40))
+  let g:FormatArgs = []
+  normal! 8GVjgq
+  call assert_equal([8, 2], g:FormatArgs)
+  setlocal formatexpr=
+  delfunc s:Format
+  bw!
+endfunc
+
 " basic test for formatprg
 func Test_normal06_formatprg()
   " only test on non windows platform
diff --git a/src/version.c b/src/version.c
index 260182b..1ada856 100644
--- a/src/version.c
+++ b/src/version.c
@@ -750,6 +750,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3900,
+/**/
     3899,
 /**/
     3898,