Skip to content

Commit e74cad3

Browse files
yegappanchrisbra
authored andcommitted
patch 9.1.0312: heredocs are not supported for :commands
Problem: heredocs are not supported for :commands (@balki) Solution: Add heredoc support (Yegappan Lakshmanan) fixes: #14491 closes: #14528 Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent a1dcd76 commit e74cad3

7 files changed

Lines changed: 189 additions & 14 deletions

File tree

runtime/doc/vim9.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*vim9.txt* For Vim version 9.1. Last change: 2024 Jan 12
1+
*vim9.txt* For Vim version 9.1. Last change: 2024 Apr 12
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -641,6 +641,14 @@ No command can follow the "{", only a comment can be used there.
641641
The block can also be used for defining a user command. Inside the block Vim9
642642
syntax will be used.
643643

644+
This is an example of using here-docs: >
645+
com SomeCommand {
646+
g:someVar =<< trim eval END
647+
ccc
648+
ddd
649+
END
650+
}
651+
644652
If the statements include a dictionary, its closing bracket must not be
645653
written at the start of a line. Otherwise, it would be parsed as the end of
646654
the block. This does not work: >

src/charset.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2088,6 +2088,17 @@ skiptowhite(char_u *p)
20882088
return p;
20892089
}
20902090

2091+
/*
2092+
* skiptowhite: skip over text until ' ' or '\t' or newline or NUL.
2093+
*/
2094+
char_u *
2095+
skiptowhite_or_nl(char_u *p)
2096+
{
2097+
while (*p != ' ' && *p != '\t' && *p != NL && *p != NUL)
2098+
++p;
2099+
return p;
2100+
}
2101+
20912102
/*
20922103
* skiptowhite_esc: Like skiptowhite(), but also skip escaped chars
20932104
*/

src/evalvars.c

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -779,8 +779,10 @@ heredoc_get(exarg_T *eap, char_u *cmd, int script_get, int vim9compile)
779779
int eval_failed = FALSE;
780780
cctx_T *cctx = vim9compile ? eap->cookie : NULL;
781781
int count = 0;
782+
int heredoc_in_string = FALSE;
783+
char_u *line_arg = NULL;
782784

783-
if (eap->ea_getline == NULL)
785+
if (eap->ea_getline == NULL && vim_strchr(cmd, '\n') == NULL)
784786
{
785787
emsg(_(e_cannot_use_heredoc_here));
786788
return NULL;
@@ -824,8 +826,14 @@ heredoc_get(exarg_T *eap, char_u *cmd, int script_get, int vim9compile)
824826
if (*cmd != NUL && *cmd != comment_char)
825827
{
826828
marker = skipwhite(cmd);
827-
p = skiptowhite(marker);
828-
if (*skipwhite(p) != NUL && *skipwhite(p) != comment_char)
829+
p = skiptowhite_or_nl(marker);
830+
if (*p == NL)
831+
{
832+
// heredoc in a string
833+
line_arg = p + 1;
834+
heredoc_in_string = TRUE;
835+
}
836+
else if (*skipwhite(p) != NUL && *skipwhite(p) != comment_char)
829837
{
830838
semsg(_(e_trailing_characters_str), p);
831839
return NULL;
@@ -859,12 +867,38 @@ heredoc_get(exarg_T *eap, char_u *cmd, int script_get, int vim9compile)
859867
int mi = 0;
860868
int ti = 0;
861869

862-
vim_free(theline);
863-
theline = eap->ea_getline(NUL, eap->cookie, 0, FALSE);
864-
if (theline == NULL)
870+
if (heredoc_in_string)
865871
{
866-
semsg(_(e_missing_end_marker_str), marker);
867-
break;
872+
char_u *next_line;
873+
874+
// heredoc in a string separated by newlines. Get the next line
875+
// from the string.
876+
877+
if (*line_arg == NUL)
878+
{
879+
semsg(_(e_missing_end_marker_str), marker);
880+
break;
881+
}
882+
883+
theline = line_arg;
884+
next_line = vim_strchr(theline, '\n');
885+
if (next_line == NULL)
886+
line_arg += STRLEN(line_arg);
887+
else
888+
{
889+
*next_line = NUL;
890+
line_arg = next_line + 1;
891+
}
892+
}
893+
else
894+
{
895+
vim_free(theline);
896+
theline = eap->ea_getline(NUL, eap->cookie, 0, FALSE);
897+
if (theline == NULL)
898+
{
899+
semsg(_(e_missing_end_marker_str), marker);
900+
break;
901+
}
868902
}
869903

870904
// with "trim": skip the indent matching the :let line to find the
@@ -911,6 +945,8 @@ heredoc_get(exarg_T *eap, char_u *cmd, int script_get, int vim9compile)
911945
}
912946
else
913947
{
948+
int free_str = FALSE;
949+
914950
if (evalstr && !eap->skip)
915951
{
916952
str = eval_all_expr_in_str(str);
@@ -920,15 +956,20 @@ heredoc_get(exarg_T *eap, char_u *cmd, int script_get, int vim9compile)
920956
eval_failed = TRUE;
921957
continue;
922958
}
923-
vim_free(theline);
924-
theline = str;
959+
free_str = TRUE;
925960
}
926961

927962
if (list_append_string(l, str, -1) == FAIL)
928963
break;
964+
if (free_str)
965+
vim_free(str);
929966
}
930967
}
931-
vim_free(theline);
968+
if (heredoc_in_string)
969+
// Next command follows the heredoc in the string.
970+
eap->nextcmd = line_arg;
971+
else
972+
vim_free(theline);
932973
vim_free(text_indent);
933974

934975
if (vim9compile && cctx->ctx_skip != SKIP_YES && !eval_failed)

src/proto/charset.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ int vim_isalpha(int c);
6161
int vim_toupper(int c);
6262
int vim_tolower(int c);
6363
char_u *skiptowhite(char_u *p);
64+
char_u *skiptowhite_or_nl(char_u *p);
6465
char_u *skiptowhite_esc(char_u *p);
6566
long getdigits(char_u **pp);
6667
long getdigits_quoted(char_u **pp);

src/testdir/test_let.vim

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,20 @@ END
715715
LINES
716716
call v9.CheckScriptFailure(lines, 'E15:')
717717

718+
" Test for using heredoc in a single string using execute()
719+
call assert_equal(["['one', 'two']"],
720+
\ execute("let x =<< trim END\n one\n two\nEND\necho x")->split("\n"))
721+
call assert_equal(["[' one', ' two']"],
722+
\ execute("let x =<< END\n one\n two\nEND\necho x")->split("\n"))
723+
let cmd = 'execute("let x =<< END\n one\n two\necho x")'
724+
call assert_fails(cmd, "E990: Missing end marker 'END'")
725+
let cmd = 'execute("let x =<<\n one\n two\necho x")'
726+
call assert_fails(cmd, "E990: Missing end marker ''")
727+
let cmd = 'execute("let x =<< trim\n one\n two\necho x")'
728+
call assert_fails(cmd, "E221: Marker cannot start with lower case letter")
729+
let cmd = 'execute("let x =<< eval END\n one\n two{y}\nEND\necho x")'
730+
call assert_fails(cmd, 'E121: Undefined variable: y')
731+
718732
" skipped heredoc
719733
if 0
720734
let msg =<< trim eval END

src/testdir/test_vim9_script.vim

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ func s:InvokeSomeCommand()
458458
SomeCommand
459459
endfunc
460460

461-
def Test_autocommand_block()
461+
def Test_command_block()
462462
com SomeCommand {
463463
g:someVar = 'some'
464464
}
@@ -469,7 +469,105 @@ def Test_autocommand_block()
469469
unlet g:someVar
470470
enddef
471471

472-
def Test_command_block()
472+
" Test for using heredoc in a :command command block
473+
def Test_command_block_heredoc()
474+
var lines =<< trim CODE
475+
vim9script
476+
com SomeCommand {
477+
g:someVar =<< trim END
478+
aaa
479+
bbb
480+
END
481+
}
482+
SomeCommand
483+
assert_equal(['aaa', 'bbb'], g:someVar)
484+
def Foo()
485+
g:someVar = []
486+
SomeCommand
487+
assert_equal(['aaa', 'bbb'], g:someVar)
488+
enddef
489+
Foo()
490+
delcommand SomeCommand
491+
unlet g:someVar
492+
CODE
493+
v9.CheckSourceSuccess( lines)
494+
495+
# Execute a command with heredoc in a block
496+
lines =<< trim CODE
497+
vim9script
498+
com SomeCommand {
499+
g:someVar =<< trim END
500+
aaa
501+
bbb
502+
END
503+
}
504+
execute('SomeCommand')
505+
assert_equal(['aaa', 'bbb'], g:someVar)
506+
delcommand SomeCommand
507+
unlet g:someVar
508+
CODE
509+
v9.CheckSourceSuccess(lines)
510+
511+
# heredoc evaluation
512+
lines =<< trim CODE
513+
vim9script
514+
com SomeCommand {
515+
var suffix = '---'
516+
g:someVar =<< trim eval END
517+
ccc{suffix}
518+
ddd
519+
END
520+
}
521+
SomeCommand
522+
assert_equal(['ccc---', 'ddd'], g:someVar)
523+
def Foo()
524+
g:someVar = []
525+
SomeCommand
526+
assert_equal(['ccc---', 'ddd'], g:someVar)
527+
enddef
528+
Foo()
529+
delcommand SomeCommand
530+
unlet g:someVar
531+
CODE
532+
v9.CheckSourceSuccess(lines)
533+
534+
# command following heredoc
535+
lines =<< trim CODE
536+
vim9script
537+
com SomeCommand {
538+
var l =<< trim END
539+
eee
540+
fff
541+
END
542+
g:someVar = l
543+
}
544+
SomeCommand
545+
assert_equal(['eee', 'fff'], g:someVar)
546+
delcommand SomeCommand
547+
unlet g:someVar
548+
CODE
549+
v9.CheckSourceSuccess(lines)
550+
551+
# Error in heredoc
552+
lines =<< trim CODE
553+
vim9script
554+
com SomeCommand {
555+
g:someVar =<< trim END
556+
eee
557+
fff
558+
}
559+
try
560+
SomeCommand
561+
catch
562+
assert_match("E990: Missing end marker 'END'", v:exception)
563+
endtry
564+
delcommand SomeCommand
565+
unlet g:someVar
566+
CODE
567+
v9.CheckSourceSuccess(lines)
568+
enddef
569+
570+
def Test_autocommand_block()
473571
au BufNew *.xml {
474572
g:otherVar = 'other'
475573
}

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,8 @@ static char *(features[]) =
704704

705705
static int included_patches[] =
706706
{ /* Add new patch number below this line */
707+
/**/
708+
312,
707709
/**/
708710
311,
709711
/**/

0 commit comments

Comments
 (0)