diff --git a/CHANGELOG b/CHANGELOG
index 6dcd2e51c..12ab6ef4e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,6 @@
* 1.39.0 (2019-XX-XX)
- * n/a
+ * added support for a new whitespace trimming option ({%~ ~%}, {{~ ~}}, {#~ ~#})
* 1.38.4 (2019-03-23)
diff --git a/doc/templates.rst b/doc/templates.rst
index 91db45aa5..adebd9a14 100644
--- a/doc/templates.rst
+++ b/doc/templates.rst
@@ -844,25 +844,28 @@ Whitespace Control
.. versionadded:: 1.1
Tag level whitespace control was added in Twig 1.1.
-The first newline after a template tag is removed automatically (like in PHP.)
+.. versionadded:: 1.39
+ Tag level Line whitespace control was added in Twig 1.39.
+
+The first newline after a template tag is removed automatically (like in PHP).
Whitespace is not further modified by the template engine, so each whitespace
(spaces, tabs, newlines etc.) is returned unchanged.
-Use the ``spaceless`` tag to remove whitespace *between HTML tags*:
+You can also control whitespace on a per tag level. By using the whitespace
+control modifiers on your tags, you can trim leading and or trailing whitespace.
-.. code-block:: jinja
+Twig supports two modifiers:
- {% spaceless %}
-
- foo bar
-
- {% endspaceless %}
+* *Whitespace trimming* via the ``-`` modifier: Removes all whitespace
+ (including newlines);
- {# output will be foo bar
#}
+* *Line whitespace trimming* via the ``~`` modifier: Removes all whitespace
+ (excluding newlines). Using this modifier on the right disables the default
+ removal of the first newline inherited from PHP.
-In addition to the spaceless tag you can also control whitespace on a per tag
-level. By using the whitespace control modifier on your tags, you can trim
-leading and or trailing whitespace:
+The modifiers can be used on either side of the tags like in ``{%-`` or ``-%}``
+and they consume all whitespace for that side of the tag. It is possible to use
+the modifiers on one side of a tag or on both sides:
.. code-block:: jinja
@@ -871,21 +874,35 @@ leading and or trailing whitespace:
{%- if true -%}
{{- value -}}
{%- endif -%}
-
{# output 'no spaces' #}
-The above sample shows the default whitespace control modifier, and how you can
-use it to remove whitespace around tags. Trimming space will consume all whitespace
-for that side of the tag. It is possible to use whitespace trimming on one side
-of a tag:
-
-.. code-block:: jinja
-
- {% set value = 'no spaces' %}
- {{- value }}
+
+ {{ value }}
+ {# outputs '\n no spaces ' #}
+
+ {{- value }}
{# outputs 'no spaces ' #}
+
+ {{~ value }}
+ {# outputs '\nno spaces ' #}
+
+.. tip::
+
+ In addition to the whitespace modifiers, Twig also has a ``spaceless`` filter
+ that removes whitespace **between HTML tags**:
+
+ .. code-block:: jinja
+
+ {% filter spaceless %}
+
+ foo bar
+
+ {% endfilter %}
+
+ {# output will be foo bar
#}
+
Extensions
----------
diff --git a/src/Lexer.php b/src/Lexer.php
index 756f123b0..c103da0d9 100644
--- a/src/Lexer.php
+++ b/src/Lexer.php
@@ -62,6 +62,8 @@ class Lexer implements \Twig_LexerInterface
'tag_block' => ['{%', '%}'],
'tag_variable' => ['{{', '}}'],
'whitespace_trim' => '-',
+ 'whitespace_line_trim' => '~',
+ 'whitespace_line_chars' => ' \t\0\x0B',
'interpolation' => ['#{', '}'],
], $options);
@@ -72,6 +74,8 @@ class Lexer implements \Twig_LexerInterface
(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1]).'\s*'. // -}}\s*
'|'.
+ preg_quote($this->options['whitespace_line_trim'].$this->options['tag_variable'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~}}[ \t\0\x0B]*
+ '|'.
preg_quote($this->options['tag_variable'][1]). // }}
')
}Ax',
@@ -82,22 +86,27 @@ class Lexer implements \Twig_LexerInterface
(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1]).'\s*\n?'. // -%}\s*\n?
'|'.
+ preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]*
+ '|'.
preg_quote($this->options['tag_block'][1]).'\n?'. // %}\n?
')
}Ax',
// {% endverbatim %}
- 'lex_raw_data' => '{
- ('.
- preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim']). // {%-
+ 'lex_raw_data' => '{'.
+ preg_quote($this->options['tag_block'][0]). // {%
+ '('.
+ $this->options['whitespace_trim']. // -
'|'.
- preg_quote($this->options['tag_block'][0]). // {%
- ')\s*'.
+ $this->options['whitespace_line_trim']. // ~
+ ')?\s*'.
'(?:end%s)'. // endraw or endverbatim
'\s*'.
'(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1]).'\s*'. // -%}
'|'.
+ preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]*
+ '|'.
preg_quote($this->options['tag_block'][1]). // %}
')
}sx',
@@ -109,6 +118,8 @@ class Lexer implements \Twig_LexerInterface
(?:'.
preg_quote($this->options['whitespace_trim']).preg_quote($this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n?
'|'.
+ preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]*
+ '|'.
preg_quote($this->options['tag_comment'][1], '#').'\n?'. // #}\n?
')
}sx',
@@ -121,6 +132,8 @@ class Lexer implements \Twig_LexerInterface
(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1]).'\s*'. // -%}\s*
'|'.
+ preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]*
+ '|'.
preg_quote($this->options['tag_block'][1]). // %}
')
}Asx',
@@ -137,6 +150,8 @@ class Lexer implements \Twig_LexerInterface
preg_quote($this->options['tag_comment'][0], '#'). // {#
')('.
preg_quote($this->options['whitespace_trim']). // -
+ '|'.
+ preg_quote($this->options['whitespace_line_trim']). // ~
')?
}sx',
'interpolation_start' => '{'.preg_quote($this->options['interpolation'][0]).'\s*}A',
@@ -240,8 +255,17 @@ class Lexer implements \Twig_LexerInterface
// push the template text first
$text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor);
+
+ // trim?
if (isset($this->positions[2][$this->position][0])) {
- $text = rtrim($text);
+ if ($this->options['whitespace_trim'] === $this->positions[2][$this->position][0]) {
+ // whitespace_trim detected ({%-, {{- or {#-)
+ $text = rtrim($text);
+ } else {
+ // whitespace_line_trim detected ({%~, {{~ or {#~)
+ // don't trim \r and \n
+ $text = rtrim($text, " \t\0\x0B");
+ }
}
$this->pushToken(Token::TEXT_TYPE, $text);
$this->moveCursor($textContent.$position[0]);
@@ -378,8 +402,16 @@ class Lexer implements \Twig_LexerInterface
$text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
$this->moveCursor($text.$match[0][0]);
- if (false !== strpos($match[1][0], $this->options['whitespace_trim'])) {
- $text = rtrim($text);
+ // trim?
+ if (isset($match[1][0])) {
+ if ($this->options['whitespace_trim'] === $match[1][0]) {
+ // whitespace_trim detected ({%-, {{- or {#-)
+ $text = rtrim($text);
+ } else {
+ // whitespace_line_trim detected ({%~, {{~ or {#~)
+ // don't trim \r and \n
+ $text = rtrim($text, " \t\0\x0B");
+ }
}
$this->pushToken(Token::TEXT_TYPE, $text);
diff --git a/test/Twig/Tests/Fixtures/tags/trim_block.test b/test/Twig/Tests/Fixtures/whitespace/trim_block.test
similarity index 92%
rename from test/Twig/Tests/Fixtures/tags/trim_block.test
rename to test/Twig/Tests/Fixtures/whitespace/trim_block.test
index f1a1310ad..346a11076 100644
--- a/test/Twig/Tests/Fixtures/tags/trim_block.test
+++ b/test/Twig/Tests/Fixtures/whitespace/trim_block.test
@@ -1,9 +1,6 @@
--TEST--
Whitespace trimming on tags.
--TEMPLATE--
-{{ 5 * '{#-'|length }}
-{{ '{{-'|length * 5 + '{%-'|length }}
-
Trim on control tag:
{% for i in range(1, 9) -%}
{{ i }}
@@ -53,9 +50,6 @@ end
--DATA--
return ['leading' => 'leading space', 'trailing' => 'trailing space', 'both' => 'both']
--EXPECT--
-15
-18
-
Trim on control tag:
123456789
diff --git a/test/Twig/Tests/Fixtures/whitespace/trim_delimiter_as_strings.test b/test/Twig/Tests/Fixtures/whitespace/trim_delimiter_as_strings.test
new file mode 100644
index 000000000..a58818dde
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/whitespace/trim_delimiter_as_strings.test
@@ -0,0 +1,10 @@
+--TEST--
+Whitespace trimming as strings.
+--TEMPLATE--
+{{ 5 * '{#-'|length }}
+{{ '{{-'|length * 5 + '{%-'|length }}
+--DATA--
+return []
+--EXPECT--
+15
+18
diff --git a/test/Twig/Tests/Fixtures/whitespace/trim_left.test b/test/Twig/Tests/Fixtures/whitespace/trim_left.test
new file mode 100644
index 000000000..75d0f8031
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/whitespace/trim_left.test
@@ -0,0 +1,32 @@
+--TEST--
+Whitespace trimming on tags (left side).
+--TEMPLATE--
+**{% if true %}
+foo
+
+ {%- endif %}**
+
+**
+
+ {{- 'foo' }}**
+
+**
+
+
+{#- comment #}**
+
+**{% verbatim %}
+foo
+
+ {%- endverbatim %}**
+--DATA--
+return []
+--EXPECT--
+**foo**
+
+**foo**
+
+****
+
+**
+foo**
diff --git a/test/Twig/Tests/Fixtures/whitespace/trim_line_left.test b/test/Twig/Tests/Fixtures/whitespace/trim_line_left.test
new file mode 100644
index 000000000..e5e845f08
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/whitespace/trim_line_left.test
@@ -0,0 +1,33 @@
+--TEST--
+Line whitespace trimming on tags (left side).
+--TEMPLATE--
+**{% if true %}
+foo
+ {%~ endif %}**
+
+**
+ {{~ 'foo' }}**
+
+**
+ {#~ comment #}**
+
+**{% verbatim %}
+foo
+
+ {%~ endverbatim %}**
+--DATA--
+return []
+--EXPECT--
+**foo
+**
+
+**
+foo**
+
+**
+**
+
+**
+foo
+
+**
diff --git a/test/Twig/Tests/Fixtures/whitespace/trim_line_right.test b/test/Twig/Tests/Fixtures/whitespace/trim_line_right.test
new file mode 100644
index 000000000..e7b510c85
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/whitespace/trim_line_right.test
@@ -0,0 +1,32 @@
+--TEST--
+Line whitespace trimming on tags (right side).
+--TEMPLATE--
+**{% if true ~%}
+foo{% endif %}**
+
+**{{ 'foo' ~}}
+foo
+**
+
+**{# comment ~#}
+ foo
+**
+
+**{% verbatim ~%}
+ foo{% endverbatim %}**
+--DATA--
+return []
+--EXPECT--
+**
+foo**
+
+**foo
+foo
+**
+
+**
+ foo
+**
+
+**
+ foo**
diff --git a/test/Twig/Tests/Fixtures/whitespace/trim_right.test b/test/Twig/Tests/Fixtures/whitespace/trim_right.test
new file mode 100644
index 000000000..0ec3b0cea
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/whitespace/trim_right.test
@@ -0,0 +1,28 @@
+--TEST--
+Whitespace trimming on tags (right side).
+--TEMPLATE--
+**{% if true -%}
+
+ foo{% endif %}**
+
+**{{ 'foo' -}}
+
+**
+
+**{# comment -#}
+
+**
+
+**{% verbatim -%}
+
+foo{% endverbatim %}**
+--DATA--
+return []
+--EXPECT--
+**foo**
+
+**foo**
+
+****
+
+**foo**