patch 9.1.1224: cannot :put while keeping indent

Problem:  cannot :put while keeping indent (Peter Aronoff)
Solution: add the :iput ex command (64-bitman)

fixes: #16225
closes: #16886

Co-authored-by: zeertzjq <zeertzjq@outlook.com>
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: 64-bitman <60551350+64-bitman@users.noreply.github.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/ex_cmdidxs.h b/src/ex_cmdidxs.h
index 0b13a6c..609a81d 100644
--- a/src/ex_cmdidxs.h
+++ b/src/ex_cmdidxs.h
@@ -14,23 +14,23 @@
   /* g */ 184,
   /* h */ 190,
   /* i */ 200,
-  /* j */ 220,
-  /* k */ 222,
-  /* l */ 227,
-  /* m */ 290,
-  /* n */ 308,
-  /* o */ 328,
-  /* p */ 340,
-  /* q */ 381,
-  /* r */ 384,
-  /* s */ 404,
-  /* t */ 474,
-  /* u */ 521,
-  /* v */ 532,
-  /* w */ 553,
-  /* x */ 567,
-  /* y */ 577,
-  /* z */ 578
+  /* j */ 221,
+  /* k */ 223,
+  /* l */ 228,
+  /* m */ 291,
+  /* n */ 309,
+  /* o */ 329,
+  /* p */ 341,
+  /* q */ 382,
+  /* r */ 385,
+  /* s */ 405,
+  /* t */ 475,
+  /* u */ 522,
+  /* v */ 533,
+  /* w */ 554,
+  /* x */ 568,
+  /* y */ 578,
+  /* z */ 579
 };
 
 /*
@@ -49,7 +49,7 @@
   /* f */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10,  0,  0,  0,  0,  0, 16,  0,  0,  0,  0,  0 },
   /* g */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  2,  0,  0,  4,  5,  0,  0,  0,  0 },
   /* h */ {  5,  0,  0,  0,  0,  0,  0,  0,  6,  0,  0,  0,  0,  0,  9,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
-  /* i */ {  1,  0,  0,  0,  0,  3,  0,  0,  0,  4,  0,  5,  6,  0,  0,  0,  0,  0, 15,  0, 17,  0,  0,  0,  0,  0 },
+  /* i */ {  1,  0,  0,  0,  0,  3,  0,  0,  0,  4,  0,  5,  6,  0,  0, 15,  0,  0, 16,  0, 18,  0,  0,  0,  0,  0 },
   /* j */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0 },
   /* k */ {  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
   /* l */ {  3, 11, 15, 19, 20, 25, 28, 33,  0,  0,  0, 35, 38, 41, 45, 51,  0, 53, 62, 54, 55, 59, 61,  0,  0,  0 },
@@ -69,4 +69,4 @@
   /* z */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }
 };
 
-static const int command_count = 595;
+static const int command_count = 596;
diff --git a/src/ex_cmds.h b/src/ex_cmds.h
index d217cd9..0659f87 100644
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -761,6 +761,9 @@
 EXCMD(CMD_interface,	"interface",	ex_class,
 	EX_EXTRA|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE|EX_EXPORT,
 	ADDR_NONE),
+EXCMD(CMD_iput,		"iput",		ex_iput,
+	EX_RANGE|EX_WHOLEFOLD|EX_BANG|EX_REGSTR|EX_TRLBAR|EX_ZEROR|EX_CMDWIN|EX_LOCK_OK|EX_MODIFY,
+	ADDR_LINES),
 EXCMD(CMD_isearch,	"isearch",	ex_findpat,
 	EX_BANG|EX_RANGE|EX_DFLALL|EX_WHOLEFOLD|EX_EXTRA|EX_CMDWIN|EX_LOCK_OK,
 	ADDR_LINES),
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 80ec8a1..bda20f5 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -231,6 +231,7 @@
 #endif
 static void	ex_operators(exarg_T *eap);
 static void	ex_put(exarg_T *eap);
+static void	ex_iput(exarg_T *eap);
 static void	ex_copymove(exarg_T *eap);
 static void	ex_submagic(exarg_T *eap);
 static void	ex_join(exarg_T *eap);
@@ -2372,8 +2373,8 @@
 	    goto doend;
 	}
 #endif
-	if (valid_yank_reg(*ea.arg, (ea.cmdidx != CMD_put
-					      && !IS_USER_CMDIDX(ea.cmdidx))))
+	if (valid_yank_reg(*ea.arg, (!IS_USER_CMDIDX(ea.cmdidx)
+			    && ea.cmdidx != CMD_put && ea.cmdidx != CMD_iput)))
 	{
 	    ea.regname = *ea.arg++;
 #ifdef FEAT_EVAL
@@ -8521,6 +8522,25 @@
 }
 
 /*
+ * ":iput".
+ */
+    static void
+ex_iput(exarg_T *eap)
+{
+    // ":0iput" works like ":1iput!".
+    if (eap->line2 == 0)
+    {
+	eap->line2 = 1;
+	eap->forceit = TRUE;
+    }
+    curwin->w_cursor.lnum = eap->line2;
+    check_cursor_col();
+    do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1L,
+						      PUT_LINE|PUT_CURSLINE
+						      |PUT_FIXINDENT);
+}
+
+/*
  * Handle ":copy" and ":move".
  */
     static void
diff --git a/src/proto/vim9cmds.pro b/src/proto/vim9cmds.pro
index bd2b5c2..12f0560 100644
--- a/src/proto/vim9cmds.pro
+++ b/src/proto/vim9cmds.pro
@@ -26,7 +26,7 @@
 int get_defer_var_idx(cctx_T *cctx);
 char_u *compile_defer(char_u *arg_start, cctx_T *cctx);
 char_u *compile_mult_expr(char_u *arg, int cmdidx, long cmd_count, cctx_T *cctx);
-char_u *compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx);
+char_u *compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx, int fixindent);
 char_u *compile_exec(char_u *line_arg, exarg_T *eap, cctx_T *cctx);
 char_u *compile_script(char_u *line, cctx_T *cctx);
 char_u *compile_substitute(char_u *arg, exarg_T *eap, cctx_T *cctx);
diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro
index 3fcf08c..641648f 100644
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -68,7 +68,7 @@
 int generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count);
 int generate_ECHOWINDOW(cctx_T *cctx, int count, long time);
 int generate_SOURCE(cctx_T *cctx, int sid);
-int generate_PUT(cctx_T *cctx, int regname, linenr_T lnum);
+int generate_PUT(cctx_T *cctx, int regname, linenr_T lnum, int fixindent);
 int generate_LOCKUNLOCK(cctx_T *cctx, char_u *line, int is_arg);
 int generate_EXEC_copy(cctx_T *cctx, isntype_T isntype, char_u *line);
 int generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *str);
diff --git a/src/testdir/test_put.vim b/src/testdir/test_put.vim
index 2af31db..26eb7f0 100644
--- a/src/testdir/test_put.vim
+++ b/src/testdir/test_put.vim
Binary files differ
diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim
index 900bf8a..26a3f09 100644
--- a/src/testdir/test_vim9_cmd.vim
+++ b/src/testdir/test_vim9_cmd.vim
@@ -1350,6 +1350,59 @@
   bwipe!
 enddef
 
+def Test_iput()
+  new
+  set noexpandtab
+
+  call feedkeys("i\<Tab>foo", 'x')
+
+  @p = "ppp"
+  iput p
+  call assert_equal("\<Tab>ppp", getline(2))
+
+  iput ="below"
+  assert_equal("\<Tab>below", getline(3))
+  iput! ="above"
+  assert_equal("\<Tab>above", getline(3))
+  assert_equal("\<Tab>below", getline(4))
+
+  :2iput =['a', 'b', 'c']
+  assert_equal(["\<Tab>ppp", "\<Tab>a", "\<Tab>b", "\<Tab>c", "\<Tab>above"], getline(2, 6))
+
+  :0iput =  "\<Tab>\<Tab>first"
+  assert_equal("\<Tab>first", getline(1))
+  :1iput! ="first again"
+  assert_equal("\<Tab>first again", getline(1))
+
+  bw!
+  v9.CheckDefFailure(['iput =xxx'], 'E1001:')
+enddef
+
+def Test_iput_with_linebreak()
+  new
+  var lines =<< trim END
+    vim9script
+    ip =split('abc', '\zs')
+            ->join()
+  END
+  v9.CheckScriptSuccess(lines)
+  getline(2)->assert_equal('a b c')
+  bwipe!
+enddef
+
+def Test_iput_not_put()
+  new
+  call feedkeys("ggS\<Tab>foo", 'x')
+  @a = "putting"
+  :0iput a
+  assert_equal("\<Tab>putting", getline(1))
+  put a
+  assert_equal("putting", getline(2))
+  iput a
+  assert_equal("putting", getline(3))
+  bwipe!
+enddef
+
 def Test_command_star_range()
   new
   setline(1, ['xxx foo xxx', 'xxx bar xxx', 'xxx foo xx bar'])
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 7a41b5d..27b71fe 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -286,6 +286,20 @@
         res)
 enddef
 
+def s:IputExpr()
+  :3iput ="text"
+enddef
+
+def Test_disassemble_iput_expr()
+  var res = execute('disass s:IputExpr')
+  assert_match('<SNR>\d*_IputExpr.*' ..
+        ' :3iput ="text"\_s*' ..
+        '\d PUSHS "text"\_s*' ..
+        '\d IPUT = 3\_s*' ..
+        '\d RETURN void',
+        res)
+enddef
+
 def s:PutRange()
   :$-2put a
   :$-3put! b
@@ -305,6 +319,25 @@
         res)
 enddef
 
+def s:IputRange()
+  :$-2iput a
+  :$-3iput! b
+enddef
+
+def Test_disassemble_iput_range()
+  var res = execute('disass s:IputRange')
+  assert_match('<SNR>\d*_IputRange.*' ..
+        ' :$-2iput a\_s*' ..
+        '\d RANGE $-2\_s*' ..
+        '\d IPUT a range\_s*' ..
+
+        ' :$-3iput! b\_s*' ..
+        '\d RANGE $-3\_s*' ..
+        '\d IPUT b above range\_s*' ..
+        '\d RETURN void',
+        res)
+enddef
+
 def s:ScriptFuncPush()
   var localbool = true
   var localspec = v:none
diff --git a/src/version.c b/src/version.c
index b4f6312..02f30ab 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1224,
+/**/
     1223,
 /**/
     1222,
diff --git a/src/vim9.h b/src/vim9.h
index 63d7116..7c731fa 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -200,6 +200,7 @@
     ISN_USEDICT,    // use or clear dict saved by ISN_MEMBER/ISN_STRINGMEMBER
 
     ISN_PUT,	    // ":put", uses isn_arg.put
+    ISN_IPUT,	    // ":iput", uses isn_arg.put
 
     ISN_CMDMOD,	    // set cmdmod
     ISN_CMDMOD_REV, // undo ISN_CMDMOD
diff --git a/src/vim9cmds.c b/src/vim9cmds.c
index f8ebfb1..aeb742e 100644
--- a/src/vim9cmds.c
+++ b/src/vim9cmds.c
@@ -2161,9 +2161,12 @@
 /*
  * :put r
  * :put ={expr}
+ * or if fixindent == TRUE
+ * :iput r
+ * :iput ={expr}
  */
     char_u *
-compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx)
+compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx, int fixindent)
 {
     char_u	*line = arg;
     linenr_T	lnum;
@@ -2202,7 +2205,8 @@
 	    --lnum;
     }
 
-    generate_PUT(cctx, eap->regname, lnum);
+    generate_PUT(cctx, eap->regname, lnum, fixindent);
+
     return line;
 }
 
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 9d0bf71..cb7b948 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -4595,7 +4595,12 @@
 
 	    case CMD_put:
 		    ea.cmd = cmd;
-		    line = compile_put(p, &ea, cctx);
+		    line = compile_put(p, &ea, cctx, FALSE);
+		    break;
+
+	    case CMD_iput:
+		    ea.cmd = cmd;
+		    line = compile_put(p, &ea, cctx, TRUE);
 		    break;
 
 	    case CMD_substitute:
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 4b69dfa..c0c3103 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -3253,6 +3253,58 @@
 }
 
 /*
+ * do ISN_PUT or ISN_IPUT instruction depending on fixindent parameter
+ */
+    static void
+isn_put_do(ectx_T *ectx, isn_T *iptr, typval_T *tv, int fixindent)
+{
+    int		regname = iptr->isn_arg.put.put_regname;
+    linenr_T	lnum = iptr->isn_arg.put.put_lnum;
+    char_u	*expr = NULL;
+    int		dir = FORWARD;
+
+    if (lnum < -2)
+    {
+	// line number was put on the stack by ISN_RANGE
+	tv = STACK_TV_BOT(-1);
+	curwin->w_cursor.lnum = tv->vval.v_number;
+	if (lnum == LNUM_VARIABLE_RANGE_ABOVE)
+	    dir = BACKWARD;
+	--ectx->ec_stack.ga_len;
+    }
+    else if (lnum == -2)
+	// :put! above cursor
+	dir = BACKWARD;
+    else if (lnum >= 0)
+    {
+	curwin->w_cursor.lnum = lnum;
+	if (lnum == 0)
+	    // check_cursor() below will move to line 1
+	    dir = BACKWARD;
+    }
+
+    if (regname == '=')
+    {
+	tv = STACK_TV_BOT(-1);
+	if (tv->v_type == VAR_STRING)
+	    expr = tv->vval.v_string;
+	else
+	{
+	    expr = typval2string(tv, TRUE); // allocates value
+	    clear_tv(tv);
+	}
+	--ectx->ec_stack.ga_len;
+    }
+    check_cursor();
+
+    if (fixindent)
+	do_put(regname, expr, dir, 1L, PUT_LINE|PUT_CURSLINE|PUT_FIXINDENT);
+    else
+	do_put(regname, expr, dir, 1L, PUT_LINE|PUT_CURSLINE);
+    vim_free(expr);
+}
+
+/*
  * Execute instructions in execution context "ectx".
  * Return OK or FAIL;
  */
@@ -5948,48 +6000,12 @@
 		break;
 
 	    case ISN_PUT:
-		{
-		    int		regname = iptr->isn_arg.put.put_regname;
-		    linenr_T	lnum = iptr->isn_arg.put.put_lnum;
-		    char_u	*expr = NULL;
-		    int		dir = FORWARD;
-
-		    if (lnum < -2)
-		    {
-			// line number was put on the stack by ISN_RANGE
-			tv = STACK_TV_BOT(-1);
-			curwin->w_cursor.lnum = tv->vval.v_number;
-			if (lnum == LNUM_VARIABLE_RANGE_ABOVE)
-			    dir = BACKWARD;
-			--ectx->ec_stack.ga_len;
-		    }
-		    else if (lnum == -2)
-			// :put! above cursor
-			dir = BACKWARD;
-		    else if (lnum >= 0)
-		    {
-			curwin->w_cursor.lnum = lnum;
-			if (lnum == 0)
-			    // check_cursor() below will move to line 1
-			    dir = BACKWARD;
-		    }
-
-		    if (regname == '=')
-		    {
-			tv = STACK_TV_BOT(-1);
-			if (tv->v_type == VAR_STRING)
-			    expr = tv->vval.v_string;
-			else
-			{
-			    expr = typval2string(tv, TRUE); // allocates value
-			    clear_tv(tv);
-			}
-			--ectx->ec_stack.ga_len;
-		    }
-		    check_cursor();
-		    do_put(regname, expr, dir, 1L, PUT_LINE|PUT_CURSLINE);
-		    vim_free(expr);
-		}
+		tv = NULL; // initialize it so we don't get a warning
+		isn_put_do(ectx, iptr, tv, FALSE);
+		break;
+	    case ISN_IPUT:
+		tv = NULL;
+		isn_put_do(ectx, iptr, tv, TRUE);
 		break;
 
 	    case ISN_CMDMOD:
@@ -7619,7 +7635,18 @@
 						 iptr->isn_arg.put.put_regname,
 					     (long)iptr->isn_arg.put.put_lnum);
 		break;
-
+	    case ISN_IPUT:
+		if (iptr->isn_arg.put.put_lnum == LNUM_VARIABLE_RANGE_ABOVE)
+		    smsg("%s%4d IPUT %c above range",
+				  pfx, current, iptr->isn_arg.put.put_regname);
+		else if (iptr->isn_arg.put.put_lnum == LNUM_VARIABLE_RANGE)
+		    smsg("%s%4d IPUT %c range",
+				  pfx, current, iptr->isn_arg.put.put_regname);
+		else
+		    smsg("%s%4d IPUT %c %ld", pfx, current,
+						 iptr->isn_arg.put.put_regname,
+					     (long)iptr->isn_arg.put.put_lnum);
+		break;
 	    case ISN_CMDMOD:
 		{
 		    char_u  *buf;
@@ -7846,5 +7873,4 @@
     return OK;
 }
 
-
 #endif // FEAT_EVAL
diff --git a/src/vim9instr.c b/src/vim9instr.c
index 86fddd9..3da56bf 100644
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -2188,15 +2188,17 @@
 }
 
 /*
- * Generate an ISN_PUT instruction.
+ * Generate an ISN_PUT or ISN_IPUT instruction depending on fixindent.
  */
     int
-generate_PUT(cctx_T *cctx, int regname, linenr_T lnum)
+generate_PUT(cctx_T *cctx, int regname, linenr_T lnum, int fixindent)
 {
     isn_T	*isn;
 
     RETURN_OK_IF_SKIP(cctx);
-    if ((isn = generate_instr(cctx, ISN_PUT)) == NULL)
+    isn = (fixindent) ? generate_instr(cctx, ISN_IPUT) :
+			generate_instr(cctx, ISN_PUT);
+    if (isn == NULL)
 	return FAIL;
     isn->isn_arg.put.put_regname = regname;
     isn->isn_arg.put.put_lnum = lnum;
@@ -2814,6 +2816,7 @@
 	case ISN_PUSHOBJ:
 	case ISN_PUSHSPEC:
 	case ISN_PUT:
+	case ISN_IPUT:
 	case ISN_REDIREND:
 	case ISN_REDIRSTART:
 	case ISN_RETURN: