mirror of
https://github.com/zulip/zulip.git
synced 2026-07-18 21:04:19 +08:00
In this commit we add the ability of recognizing comments in handlebar and Jinja2 templates to the template parser. This fixes issue of template parser picking up code tags which are commented out in files. Also we fix the problem of too much deep nesting in the Jinja2 logic statement. Now only nested Jinja2 logic statements will get a deeper indentation level.With this another fix was introduced relating with the tags before a nested handlebar or django tag getting wrong indent. We also fix the issue with wrong offsets with closing tags in cases if they were not matching indent level of the starting tag intially. Also we also start to ignore any tags occuring in between 'pre' tags for deeper indent levels. As well we start to filter out django non block tags from getting deeper indent level.
170 lines
7.8 KiB
Python
170 lines
7.8 KiB
Python
from __future__ import absolute_import
|
|
from __future__ import print_function
|
|
|
|
from typing import Any, Dict, List
|
|
|
|
from .template_parser import (
|
|
tokenize,
|
|
Token,
|
|
is_django_block_tag,
|
|
)
|
|
from six.moves import range
|
|
|
|
def pretty_print_html(html, num_spaces=4):
|
|
# type: (str, int) -> str
|
|
# We use 1-based indexing for both rows and columns.
|
|
tokens = tokenize(html)
|
|
lines = html.split('\n')
|
|
|
|
# We will keep a stack of "start" tags so that we know
|
|
# when HTML ranges end. Note that some start tags won't
|
|
# be blocks from an indentation standpoint.
|
|
stack = [] # type: List[Dict[str, Any]]
|
|
|
|
# Seed our stack with a pseudo entry to make depth calculations
|
|
# easier.
|
|
info = dict(
|
|
block=False,
|
|
depth=-1,
|
|
line=-1,
|
|
token_kind='html_start',
|
|
tag='html',
|
|
extra_indent=0)
|
|
stack.append(info)
|
|
|
|
# Our main job is to figure out offsets that we use to nudge lines
|
|
# over by.
|
|
offsets = {} # type: Dict[int, int]
|
|
|
|
# Loop through our start/end tokens, and calculate offsets. As
|
|
# we proceed, we will push/pop info dictionaries on/off a stack.
|
|
for token in tokens:
|
|
|
|
if token.kind in ('html_start', 'handlebars_start',
|
|
'html_singleton', 'django_start') and stack[-1]['tag'] != 'pre':
|
|
# An HTML start tag should only cause a new indent if we
|
|
# are on a new line.
|
|
if (token.tag not in ('extends', 'include', 'else', 'elif') and
|
|
(is_django_block_tag(token.tag) or
|
|
token.kind != 'django_start')):
|
|
is_block = token.line > stack[-1]['line']
|
|
|
|
if is_block:
|
|
if (((token.kind == 'handlebars_start' and
|
|
stack[-1]['token_kind'] == 'handlebars_start') or
|
|
(token.kind == 'django_start' and
|
|
stack[-1]['token_kind'] == 'django_start')) and
|
|
not stack[-1]['indenting']):
|
|
info = stack.pop()
|
|
info['depth'] = info['depth'] + 1
|
|
info['indenting'] = True
|
|
info['adjust_offset_until'] = token.line
|
|
stack.append(info)
|
|
new_depth = stack[-1]['depth'] + 1
|
|
extra_indent = stack[-1]['extra_indent']
|
|
line = lines[token.line - 1]
|
|
adjustment = len(line)-len(line.lstrip()) + 1
|
|
offset = (1 + extra_indent + new_depth * num_spaces) - adjustment
|
|
info = dict(
|
|
block=True,
|
|
depth=new_depth,
|
|
actual_depth=new_depth,
|
|
line=token.line,
|
|
tag=token.tag,
|
|
token_kind=token.kind,
|
|
line_span=token.line_span,
|
|
offset=offset,
|
|
extra_indent=token.col - adjustment + extra_indent,
|
|
extra_indent_prev=extra_indent,
|
|
adjustment=adjustment,
|
|
indenting=True,
|
|
adjust_offset_until=token.line
|
|
)
|
|
if token.kind in ('handlebars_start', 'django_start'):
|
|
info.update(dict(depth=new_depth - 1, indenting=False))
|
|
else:
|
|
info = dict(
|
|
block=False,
|
|
depth=stack[-1]['depth'],
|
|
actual_depth=stack[-1]['depth'],
|
|
line=token.line,
|
|
tag=token.tag,
|
|
token_kind=token.kind,
|
|
extra_indent=stack[-1]['extra_indent']
|
|
)
|
|
stack.append(info)
|
|
elif token.kind in ('html_end', 'handlebars_end',
|
|
'html_singleton_end', 'django_end') and (stack[-1]['tag'] != 'pre' or token.tag == 'pre'):
|
|
info = stack.pop()
|
|
if info['block']:
|
|
# We are at the end of an indentation block. We
|
|
# assume the whole block was formatted ok before, just
|
|
# possibly at an indentation that we don't like, so we
|
|
# nudge over all lines in the block by the same offset.
|
|
start_line = info['line']
|
|
end_line = token.line
|
|
if token.tag == 'pre':
|
|
offsets[start_line] = 0
|
|
offsets[end_line] = 0
|
|
else:
|
|
offsets[start_line] = info['offset']
|
|
line = lines[token.line - 1]
|
|
adjustment = len(line)-len(line.lstrip()) + 1
|
|
if adjustment == token.col:
|
|
offsets[end_line] = (info['offset'] +
|
|
info['adjustment'] -
|
|
adjustment +
|
|
info['extra_indent'] -
|
|
info['extra_indent_prev'])
|
|
elif (start_line + info['line_span'] - 1 == end_line and
|
|
info['line_span'] > 2 and token.kind != 'html_singleton_end'):
|
|
offsets[end_line] = (1 + info['extra_indent'] + (info['depth'] + 1) * num_spaces) - adjustment
|
|
elif token.line != info['line']:
|
|
offsets[end_line] = info['offset']
|
|
if token.tag != 'pre' and token.kind != 'html_singleton_end' and token.tag != 'script':
|
|
for line_num in range(start_line + 1, end_line):
|
|
# Be careful not to override offsets that happened
|
|
# deeper in the HTML within our block.
|
|
if line_num not in offsets:
|
|
line = lines[line_num - 1]
|
|
new_depth = info['depth'] + 1
|
|
if (line.lstrip().startswith('{{else}}') or
|
|
line.lstrip().startswith('{% else %}') or
|
|
line.lstrip().startswith('{% elif')):
|
|
new_depth = info['actual_depth']
|
|
extra_indent = info['extra_indent']
|
|
adjustment = len(line)-len(line.lstrip()) + 1
|
|
offset = (1 + extra_indent + new_depth * num_spaces) - adjustment
|
|
offsets[line_num] = offset
|
|
elif (token.kind in ('handlebars_end', 'django_end') and
|
|
info['indenting'] and
|
|
line_num < info['adjust_offset_until']):
|
|
offsets[line_num] += num_spaces
|
|
elif token.tag != 'pre':
|
|
for line_num in range(start_line + 1, end_line):
|
|
if line_num not in offsets:
|
|
offsets[line_num] = info['offset']
|
|
else:
|
|
for line_num in range(start_line + 1, end_line):
|
|
if line_num not in offsets:
|
|
offsets[line_num] = 0
|
|
|
|
# Now that we have all of our offsets calculated, we can just
|
|
# join all our lines together, fixing up offsets as needed.
|
|
formatted_lines = []
|
|
for i, line in enumerate(html.split('\n')):
|
|
row = i + 1
|
|
offset = offsets.get(row, 0)
|
|
pretty_line = line
|
|
if line.strip() == '':
|
|
pretty_line = ''
|
|
else:
|
|
if offset > 0:
|
|
pretty_line = (' ' * offset) + pretty_line
|
|
elif offset < 0:
|
|
pretty_line = pretty_line[-1 * offset:]
|
|
assert line.strip() == pretty_line.strip()
|
|
formatted_lines.append(pretty_line)
|
|
|
|
return '\n'.join(formatted_lines)
|