{{!
Computes content height based on line count in interactions.
Expected hash inputs: `interactions`, `const`.
}}
{{~#*inline "compute_content_height"}}
{{#scope lines=0 margins=0 displayed_interactions=0}}
{{#each interactions}}
{{#if (not input.hidden)}}
{{lines set=(add (lines) (count_lines input.text))}}
{{margins set=(add (margins) 1)}}
{{displayed_interactions set=(add (displayed_interactions) 1)}}
{{/if}}
{{lines set=(add (lines) (len output_svg))}}
{{#if (ne 0 (len output_svg))}}
{{margins set=(add (margins) 1)}}
{{/if}}
{{/each}}
{{#if (gt (margins) 0)}}
{{! The last margin is not displayed. }}
{{margins set=(sub (margins) 1)}}
{{/if}}
{{add (mul (lines) const.LINE_HEIGHT)
(mul (margins) const.BLOCK_MARGIN)
(mul 2 (displayed_interactions) const.USER_INPUT_PADDING) }}
{{/scope}}
{{/inline~}}
{{!
Computes scroll animation parameters.
Expected hash inputs: `content_height`, `const`, `scroll`, `width`
}}
{{~#*inline "compute_scroll_animation"}}
{{#if (gte scroll.max_height content_height)}}
{{! No need for scroll animation }}
null
{{else}}
{{#scope
steps=(div (sub content_height scroll.max_height) scroll.pixels_per_scroll round="up")
y_step=0
view_box=""
scrollbar_y=""
sep=""
}}
{{y_step set=(div (sub scroll.max_height const.SCROLLBAR_HEIGHT) (steps))}}
{{#each (range 0 (add (steps) 1))}}
{{#sep}}{{#if @first}}""{{else}}";"{{/if}}{{/sep}}
{{#view_box}}"{{view_box}}{{sep}}0 {{mul ../scroll.pixels_per_scroll @index}} {{../width}} {{../scroll.max_height}}"{{/view_box}}
{{#scrollbar_y}}"{{scrollbar_y}}{{sep}}0 {{mul (y_step) @index round="nearest"}}"{{/scrollbar_y}}
{{/each}}
{
"duration": {{mul scroll.interval (steps)}},
"view_box": "{{view_box}}",
"scrollbar_x": {{sub width const.SCROLLBAR_RIGHT_OFFSET}},
"scrollbar_y": "{{scrollbar_y}}"
}
{{/scope}}
{{/if}}
{{/inline~}}
{{! Root template }}
{{~#*inline "root"}}
<!-- Created with {{{creator.name}}} v{{{creator.version}}} ({{{creator.repo}}}) -->
<svg viewBox="0 {{#if window_frame}}-{{const.WINDOW_FRAME_HEIGHT}}{{else}}0{{/if}} {{width}} {{height}}" width="{{width}}" height="{{height}}" xmlns="http://www.w3.org/2000/svg">
{{>styles}}
{{>background}}
{{~>content}}
{{~#if (scroll_animation)}}
{{>scrollbar}}
{{/if}}
</svg>
{{/inline~}}
{{! CSS definitions }}
{{~#*inline "styles"}}
<style>
{{~#if additional_styles}}
{{{additional_styles}}}
{{~/if}}
.container {
font: 14px {{font_family}};
line-height: {{const.LINE_HEIGHT}}px;
}
.input,.output,.output-bg {
white-space: pre;
}
.input-bg { fill: #fff; fill-opacity: 0.1; }
.output-bg { user-select: none; text-rendering: geometricPrecision; stroke-width: 0.1; }
{{~#if has_failures}}
.input-bg .input-failure { fill: #ff0041; fill-opacity: 0.15; }
.input-failure-hl { fill: #ff0041; fill-opacity: 1; }
{{/if}}
{{~#if (scroll_animation)}}
.scrollbar { fill: #fff; fill-opacity: 0.35; }
{{~/if}}
{{~#if line_numbers}}
.line-numbers { text-anchor: end; fill-opacity: 0.35; user-select: none; }
{{/if}}
.bold,.prompt { font-weight: 600; }
.italic { font-style: italic; }
.underline { text-decoration: underline; }
.dimmed { fill-opacity: 0.7; }
{{~#if wrap}}
.hard-br { font-size: 16px; fill-opacity: 0.8; user-select: none; }
{{~/if}}
.fg0 { fill: {{ palette.colors.black }}; } .output-bg .fg0 { stroke: {{ palette.colors.black }}; }
.fg1 { fill: {{ palette.colors.red }}; } .output-bg .fg1 { stroke: {{ palette.colors.red }}; }
.fg2 { fill: {{ palette.colors.green }}; } .output-bg .fg2 { stroke: {{ palette.colors.green }}; }
.fg3 { fill: {{ palette.colors.yellow }}; } .output-bg .fg3 { stroke: {{ palette.colors.yellow }}; }
.fg4 { fill: {{ palette.colors.blue }}; } .output-bg .fg4 { stroke: {{ palette.colors.blue }}; }
.fg5 { fill: {{ palette.colors.magenta }}; } .output-bg .fg5 { stroke: {{ palette.colors.magenta }}; }
.fg6 { fill: {{ palette.colors.cyan }}; } .output-bg .fg6 { stroke: {{ palette.colors.cyan }}; }
.fg7 { fill: {{ palette.colors.white }}; } .output-bg .fg7 { stroke: {{ palette.colors.white }}; }
.fg8 { fill: {{ palette.intense_colors.black }}; } .output-bg .fg8 { stroke: {{ palette.intense_colors.black }}; }
.fg9 { fill: {{ palette.intense_colors.red }}; } .output-bg .fg9 { stroke: {{ palette.intense_colors.red }}; }
.fg10 { fill: {{ palette.intense_colors.green }}; } .output-bg .fg10 { stroke: {{ palette.intense_colors.green }}; }
.fg11 { fill: {{ palette.intense_colors.yellow }}; } .output-bg .fg11 { stroke: {{ palette.intense_colors.yellow }}; }
.fg12 { fill: {{ palette.intense_colors.blue }}; } .output-bg .fg12 { stroke: {{ palette.intense_colors.blue }}; }
.fg13 { fill: {{ palette.intense_colors.magenta }}; } .output-bg .fg13 { stroke: {{ palette.intense_colors.magenta }}; }
.fg14 { fill: {{ palette.intense_colors.cyan }}; } .output-bg .fg14 { stroke: {{ palette.intense_colors.cyan }}; }
.fg15 { fill: {{ palette.intense_colors.white }}; } .output-bg .fg15 { stroke: {{ palette.intense_colors.white }}; }
</style>
{{/inline~}}
{{! Terminal background }}
{{~#*inline "background"}}
<rect width="100%" height="100%" y="{{#if window_frame}}-{{const.WINDOW_FRAME_HEIGHT}}{{else}}0{{/if}}" rx="4.5" style="fill: {{ palette.colors.black }};" />
{{~#if window_frame}}
<rect width="100%" height="26" y="-22" clip-path="inset(0 0 -10 0 round 4.5)" style="fill: #fff; fill-opacity: 0.1;"/>
<circle cx="17" cy="-9" r="7" style="fill: {{ palette.colors.red }};"/>
<circle cx="37" cy="-9" r="7" style="fill: {{ palette.colors.yellow }};"/>
<circle cx="57" cy="-9" r="7" style="fill: {{ palette.colors.green }};"/>
{{~/if}}
{{/inline~}}
{{~#*inline "content"}}
<svg x="0" y="{{const.WINDOW_PADDING}}" width="{{width}}" height="{{screen_height}}" viewBox="0 0 {{width}} {{screen_height}}">
{{~#if (scroll_animation)}}
{{~#with (scroll_animation)}}
<animate attributeName="viewBox" values="{{view_box}}" dur="{{duration}}s" repeatCount="indefinite" calcMode="discrete" />
{{~/with}}
{{~/if}}
{{! Render backgrounds for each input beforehand since they cannot be placed in <text>. }}
{{#scope y_pos=0 input_height=0}}
<g class="input-bg">
{{~#each interactions}}
{{~#if (not input.hidden)}}
{{~input_height set=(add (mul (count_lines input.text) ../const.LINE_HEIGHT) (mul 2 ../const.USER_INPUT_PADDING))~}}
<rect x="0" y="{{y_pos}}" width="100%" height="{{input_height}}"{{#if failure}} class="input-failure"{{/if}}>
{{~#if failure~}}
<title>This command exited with non-zero code</title>
{{~/if~}}
</rect>
{{~#if failure~}}
<rect x="0" y="{{y_pos}}" width="2" height="{{input_height}}" class="input-failure-hl" />
<rect x="100%" y="{{y_pos}}" width="2" height="{{input_height}}" class="input-failure-hl" transform="translate(-2, 0)" />
{{~/if~}}
{{~y_pos set=(add (y_pos) (input_height) ../const.BLOCK_MARGIN)~}}
{{~/if~}} {{! if (not input.hidden) }}
{{~y_pos set=(add (y_pos) (mul ../const.LINE_HEIGHT (len output_svg)))~}}
{{~#if (ne (len output_svg) 0)}}
{{~y_pos set=(add (y_pos) ../const.BLOCK_MARGIN)}}
{{~/if}}
{{~/each~}}
</g>
{{~/scope}}
{{#if line_numbers}}
{{>number_lines}}
{{/if}}
{{! Render main text }}
{{#scope
x_pos=const.WINDOW_PADDING
input_x_pos=const.WINDOW_PADDING
y_pos=14
}}
{{~#if line_numbers~}}
{{x_pos set=(add (x_pos) const.LN_WIDTH const.LN_PADDING)}}
{{~/if~}}
{{~#if (eq line_numbers "continuous")~}}
{{input_x_pos set=(x_pos)}}
{{~/if}}
{{! The awkward newlines at the end of line <tspan>s are required for the text to be properly copyable }}
<text class="container fg7">
{{~#each interactions~}}
{{~#if (not input.hidden)~}}
{{~y_pos set=(add (y_pos) ../const.USER_INPUT_PADDING)~}}
<tspan xml:space="preserve" x="{{input_x_pos}}" y="{{y_pos}}" class="input{{#if failure}} input-failure{{/if}}">
{{~#each (split_lines input.text)~}}
<tspan x="{{input_x_pos}}" y="{{y_pos}}">{{#if @first}}<tspan class="prompt">{{../input.prompt}}</tspan> {{/if}}{{this}}
</tspan>
{{~y_pos set=(add (y_pos) ../../const.LINE_HEIGHT)}}
{{~/each~}}
</tspan>
{{~y_pos set=(add (y_pos) ../const.USER_INPUT_PADDING ../const.BLOCK_MARGIN)}}
{{~/if~}} {{! if (not input.hidden) }}
{{~#each output_svg}}
{{~#if (ne background null)~}}
<tspan xml:space="preserve" x="{{x_pos}}" y="{{y_pos}}" class="output-bg">{{{background}}}</tspan>
{{~/if~}}
<tspan xml:space="preserve" x="{{x_pos}}" y="{{y_pos}}" class="output">{{{foreground}}}
</tspan>
{{~y_pos set=(add (y_pos) ../../const.LINE_HEIGHT)~}}
{{~/each~}}
{{~#if (gt (len output_svg) 0)~}}
{{~y_pos set=(add (y_pos) ../const.BLOCK_MARGIN)~}}
{{~/if~}}
{{~/each~}}
</text>
{{/scope}}
</svg>
{{/inline~}}
{{~#*inline "scrollbar"}}
{{#with (scroll_animation)}}
<rect class="scrollbar" x="{{scrollbar_x}}" y="10" width="5" height="40">
<animateTransform attributeName="transform" attributeType="XML" type="translate" values="{{scrollbar_y}}" dur="{{duration}}s" repeatCount="indefinite" calcMode="discrete" />
</rect>
{{/with}}
{{/inline~}}
{{~#*inline "number_lines"~}}
{{~#scope
x_pos=(add const.WINDOW_PADDING const.LN_WIDTH)
y_pos=14
line_number=1
~}}
<text class="container fg7 line-numbers">
{{~#each interactions}}
{{~#if (not input.hidden)}}
{{~y_pos set=(add (y_pos) ../const.USER_INPUT_PADDING)~}}
{{~#if (eq ../line_numbers "continuous")}}
{{~#each (range 0 (count_lines input.text))~}}
<tspan x="{{x_pos}}" y="{{y_pos}}">{{add this (line_number)}}</tspan>
{{~y_pos set=(add (y_pos) ../../const.LINE_HEIGHT)~}}
{{~/each~}}
{{~line_number set=(add (line_number) (count_lines input.text))~}}
{{~else~}}
{{~y_pos set=(add (y_pos) (mul (count_lines input.text) ../const.LINE_HEIGHT))~}}
{{~/if~}}
{{~y_pos set=(add (y_pos) ../const.USER_INPUT_PADDING ../const.BLOCK_MARGIN)~}}
{{~/if~}}
{{! Number lines in the output }}
{{~#each (range 0 (len output_svg))~}}
<tspan x="{{x_pos}}" y="{{y_pos}}">{{add this (line_number)}}</tspan>
{{~y_pos set=(add (y_pos) ../../const.LINE_HEIGHT)~}}
{{~/each~}}
{{~#if (gt (len output_svg) 0)~}}
{{~y_pos set=(add (y_pos) ../const.BLOCK_MARGIN)~}}
{{~/if~}}
{{~#if (ne ../line_numbers "each_output")~}}
{{line_number set=(add (line_number) (len output_svg))}}
{{~/if~}}
{{/each~}}
</text>
{{~/scope~}}
{{~/inline~}}
{{! Main logic }}
{{#scope
content_height=(eval "compute_content_height" const=const interactions=interactions)
scroll_animation=null
screen_height=0
height=0
line_number=1
}}
{{~#if scroll~}}
{{scroll_animation set=(eval "compute_scroll_animation"
const=const
scroll=scroll
width=width
content_height=(content_height)
)}}
{{~/if~}}
{{~#if (scroll_animation)~}}
{{screen_height set=scroll.max_height}}
{{~else~}}
{{screen_height set=(content_height)}}
{{~/if~}}
{{~height set=(add (screen_height) (mul const.WINDOW_PADDING 2))~}}
{{~#if window_frame~}}
{{height set=(add (height) const.WINDOW_FRAME_HEIGHT)}}
{{~/if~}}
{{>root~}} {{! <-- All rendering happens here }}
{{/scope}}