Twine: <<if>> and whitespace

Update: This behaviour has been changed in Twine 1.4! It no longer applies.

This is just a summary of how <<if>>, by default, handles whitespace characters (that is, line breaks, spaces, tabs, and such.)

The behaviour

The rule is: the <<if>> macro removes all whitespace contained between the <<if>>, <<endif>> or <<else>> tags and any non-whitespace contained text.

Consider this code sample, in which the line breaks are marked:

The magenta line breaks (those contained between the <<if>> and <<endif>> and the actual text) will be removed by the macro. Thus, this will render as follows:

A slice of marmalade toast on a plate,
a bit of quiet,
and a spot of tea.

A slice of marmalade toast 
on a plate,
a bit of quiet,
and a spot of tea.

Resisting this behaviour

The <<if>> macro was primarily designed for inserting whole paragraphs. This behaviour is, however, less useful for, say, inserting sentence fragments into paragraphs, which often have leading spaces. There are a few ways to get around this. Such as:

  • Empty comment syntax

    If you pad the interiors of <<if>> macros with an empty comment tag "/%%/", then the behaviour will be overridden, because the comment tags will be treated as text despite not appearing in the final story.

    First line. <<if $a gt 0>>/%%/
    Second line.
    /%%/<<endif>>
    This might be the most basic method of all listed here.

  • Using <<print>>
    The <<print>> macro can also be used.

    A complete sentence<<if $a gt 0>>
    <<print " with an extra amendment">>
    <<endif>>.

    However, it isn't obvious how to add line breaks using this method. There is a way, obscure though it is:

    <<if $a gt 0>><<print "First line." + String.fromCharCode(13) + "Second line.">>
    <<endif>>

  • Using a HTML <br>

    If you want to preserve a line break inside <<if>>, you can do so by the forceful method of using inline HTML.

    First line.<<if $a gt 0>>
    <br>Second line.
    <<endif>>
    

    [*]Patching <<if>> to not remove whitespace

    Much like most undesirable Twine behaviour, this can be patched out on a story-by-story basis. The script code for doing so is as follows... however, using this will conflict with my <<else if>> script, so you can't have one and the other.

    version.extensions.ifMacros={major:1,minor:1,revision:0};macros["if"]={handler:function(place,macroName,params,parser){
    var conditions=[],clauses=[],srcOffset=parser.source.indexOf(">>",parser.matchStart)+2,src=parser.source.slice(srcOffset),endPos=-1,currentCond=parser.fullArgs(),currentClause="",t=0,nesting=0;
    for(var i=0;i<src.length;i++){if(src.substr(i,9)=="<<endif>>"){nesting--;if(nesting<0){endPos=srcOffset+i+9;
    conditions.push(currentCond);clauses.push(currentClause);break;}}if((src.substr(i,6)=="<<else")&&nesting==0){conditions.push(currentCond);
    clauses.push(currentClause);currentClause="";t=src.indexOf(">>",i+6);if(src.substr(i+6,4)==" if "){currentCond=Wikifier.parse(src.slice(i+10,t));
    }else{currentCond="true";}i=t+2;}if(src.substr(i,5)=="<<if "){nesting++;}currentClause+=src.charAt(i);
    }try{if(endPos!=-1){parser.nextMatch=endPos;for(i=0;i<clauses.length;i++){if(eval(conditions.shift())){new Wikifier(place,clauses[i ]);
    break;}}}else{throwError(place,"can't find matching endif");}}catch(e){throwError(place,"bad condition: "+e.message);
    }}};

    The future
    The next version of Twine 1 may change the behaviour of <<if>> to not remove whitespace. I'm still debating how or whether this should be enabled, and how to enable current story code to continue to function correctly.