Skip to content

Commit f5842c5

Browse files
committed
patch 8.1.1354: getting a list of text lines is clumsy
Problem: Getting a list of text lines is clumsy. Solution: Add the =<< assignment. (Yegappan Lakshmanan, closes #4386)
1 parent 2b39d80 commit f5842c5

File tree

4 files changed

+206
-0
lines changed

4 files changed

+206
-0
lines changed

runtime/doc/eval.txt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11416,6 +11416,44 @@ This does NOT work: >
1141611416
Like above, but append/add/subtract the value for each
1141711417
|List| item.
1141811418

11419+
*:let=<<* *:let-heredoc* *E990* *E991*
11420+
:let {var-name} =<< [trim] {marker}
11421+
text...
11422+
text...
11423+
{marker}
11424+
Set internal variable {var-name} to a List containing
11425+
the lines of text bounded by the string {marker}.
11426+
{marker} must not contain white space.
11427+
The last line should end only with the {marker} string
11428+
without any other character. Watch out for white
11429+
space after {marker}!
11430+
If {marker} is not supplied, then "." is used as the
11431+
default marker.
11432+
11433+
Any white space characters in the lines of text are
11434+
preserved. If "trim" is specified before {marker},
11435+
then all the leading indentation exactly matching the
11436+
leading indentation before `let` is stripped from the
11437+
input lines and the line containing {marker}. Note
11438+
that the difference between space and tab matters
11439+
here.
11440+
11441+
If {var-name} didn't exist yet, it is created.
11442+
Cannot be followed by another command, but can be
11443+
followed by a comment.
11444+
11445+
Examples: >
11446+
let var1 =<< END
11447+
Sample text 1
11448+
Sample text 2
11449+
Sample text 3
11450+
END
11451+
11452+
let data =<< trim DATA
11453+
1 2 3 4
11454+
5 6 7 8
11455+
DATA
11456+
<
1141911457
*E121*
1142011458
:let {var-name} .. List the value of variable {var-name}. Multiple
1142111459
variable names may be given. Special names recognized

src/eval.c

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,102 @@ eval_foldexpr(char_u *arg, int *cp)
12241224
}
12251225
#endif
12261226

1227+
/*
1228+
* Get a list of lines from a HERE document. The here document is a list of
1229+
* lines surrounded by a marker.
1230+
* cmd << {marker}
1231+
* {line1}
1232+
* {line2}
1233+
* ....
1234+
* {marker}
1235+
*
1236+
* The {marker} is a string. If the optional 'trim' word is supplied before the
1237+
* marker, then the leading indentation before the lines (matching the
1238+
* indentation in the 'cmd' line) is stripped.
1239+
* Returns a List with {lines} or NULL.
1240+
*/
1241+
static list_T *
1242+
heredoc_get(exarg_T *eap, char_u *cmd)
1243+
{
1244+
char_u *theline;
1245+
char_u *marker;
1246+
list_T *l;
1247+
char_u *p;
1248+
int indent_len = 0;
1249+
1250+
if (eap->getline == NULL)
1251+
{
1252+
emsg(_("E991: cannot use =<< here"));
1253+
return NULL;
1254+
}
1255+
1256+
// Check for the optional 'trim' word before the marker
1257+
cmd = skipwhite(cmd);
1258+
if (STRNCMP(cmd, "trim", 4) == 0 && (cmd[4] == NUL || VIM_ISWHITE(cmd[4])))
1259+
{
1260+
cmd = skipwhite(cmd + 4);
1261+
1262+
// Trim the indentation from all the lines in the here document
1263+
// The amount of indentation trimmed is the same as the indentation of
1264+
// the :let command line.
1265+
p = *eap->cmdlinep;
1266+
while (VIM_ISWHITE(*p))
1267+
{
1268+
p++;
1269+
indent_len++;
1270+
}
1271+
}
1272+
1273+
// The marker is the next word. Default marker is "."
1274+
if (*cmd != NUL && *cmd != '"')
1275+
{
1276+
marker = skipwhite(cmd);
1277+
p = skiptowhite(marker);
1278+
if (*skipwhite(p) != NUL && *skipwhite(p) != '"')
1279+
{
1280+
emsg(_(e_trailing));
1281+
return NULL;
1282+
}
1283+
*p = NUL;
1284+
}
1285+
else
1286+
marker = (char_u *)".";
1287+
1288+
l = list_alloc();
1289+
if (l == NULL)
1290+
return NULL;
1291+
1292+
for (;;)
1293+
{
1294+
int i = 0;
1295+
1296+
theline = eap->getline(NUL, eap->cookie, 0);
1297+
if (theline != NULL && indent_len > 0)
1298+
{
1299+
// trim the indent matching the first line
1300+
if (STRNCMP(theline, *eap->cmdlinep, indent_len) == 0)
1301+
i = indent_len;
1302+
}
1303+
1304+
if (theline == NULL)
1305+
{
1306+
semsg(_("E990: Missing end marker '%s'"), marker);
1307+
break;
1308+
}
1309+
if (STRCMP(marker, theline + i) == 0)
1310+
{
1311+
vim_free(theline);
1312+
break;
1313+
}
1314+
1315+
if (list_append_string(l, theline + i, -1) == FAIL)
1316+
break;
1317+
vim_free(theline);
1318+
}
1319+
1320+
return l;
1321+
}
1322+
12271323
/*
12281324
* ":let" list all variable values
12291325
* ":let var1 var2" list variable values
@@ -1286,6 +1382,22 @@ ex_let(exarg_T *eap)
12861382
}
12871383
eap->nextcmd = check_nextcmd(arg);
12881384
}
1385+
else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<')
1386+
{
1387+
list_T *l;
1388+
1389+
// HERE document
1390+
l = heredoc_get(eap, expr + 3);
1391+
if (l != NULL)
1392+
{
1393+
rettv_list_set(&rettv, l);
1394+
op[0] = '=';
1395+
op[1] = NUL;
1396+
(void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count,
1397+
op);
1398+
clear_tv(&rettv);
1399+
}
1400+
}
12891401
else
12901402
{
12911403
op[0] = '=';

src/testdir/test_let.vim

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,57 @@ func Test_let_utf8_environment()
151151
let $a = 'ĀĒĪŌŪあいうえお'
152152
call assert_equal('ĀĒĪŌŪあいうえお', $a)
153153
endfunc
154+
155+
" Test for the setting a variable using the heredoc syntax
156+
func Test_let_heredoc()
157+
let var1 =<< END
158+
Some sample text
159+
Text with indent
160+
!@#$%^&*()-+_={}|[]\~`:";'<>?,./
161+
END
162+
163+
call assert_equal(["Some sample text", "\tText with indent", " !@#$%^&*()-+_={}|[]\\~`:\";'<>?,./"], var1)
164+
165+
let var2 =<<
166+
Editor
167+
.
168+
call assert_equal(['Editor'], var2)
169+
170+
let var3 =<<END
171+
END
172+
call assert_equal([], var3)
173+
174+
let var3 =<<END
175+
vim
176+
177+
end
178+
END
179+
END
180+
END
181+
call assert_equal(['vim', '', 'end', ' END', 'END '], var3)
182+
183+
let var1 =<< trim END
184+
Line1
185+
Line2
186+
Line3
187+
END
188+
END
189+
call assert_equal(['Line1', ' Line2', "\tLine3", ' END'], var1)
190+
191+
let var1 =<< trim
192+
Line1
193+
.
194+
call assert_equal([' Line1'], var1)
195+
196+
call assert_fails('let v =<< marker', 'E991:')
197+
call assert_fails('call WrongSyntax()', 'E488:')
198+
call assert_fails('call MissingEnd()', 'E990:')
199+
endfunc
200+
201+
func WrongSyntax()
202+
let fail =<< that there
203+
endfunc
204+
205+
func MissingEnd()
206+
let fail =<< END
207+
endfunc

src/version.c

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

768768
static int included_patches[] =
769769
{ /* Add new patch number below this line */
770+
/**/
771+
1354,
770772
/**/
771773
1353,
772774
/**/

0 commit comments

Comments
 (0)