Preserving indents in Code Blocks

Code which I paste into FoldingText is often tab-indented rather than space-indented.

This means that using FT > Format > CodeBlock loses my indentations, translating from:

to:

What I need, for correct display on Github, and in Marked, MultiMarkdown, etc is four spaces after or before (rather than instead of) the leading tabs.

(Tabs and four-space multiples are interchangeable in Markdown code indents

but as FoldingText uses tabs for outline structure, you need four spaces at one end of the indent to mark the line as ‘code’).

Here is one way of getting that – an applescript which adds 4 spaces (to the selected text) after any tab indents:

property pTitle : "Mark selected block or lines as code"
property pVer : "0.2"
property pAuthor : "Rob Trew"

property pblnDebug : false

property pblnFenced : false

property pstrFormatCodeBlock : "

function(editor, options) {

	var	rgxIndent=/^(\\s*)/gm,
			rgxCode=/^    (\\s*)/gm,
			oMatch,
			rngSeln, rngFullLines,
			nodeFirst, nodeLast,
			strSeln, strCode, 
			lngCodeLines=0, lngLines; 

	// ensure block or full line selection
		strSeln = editor.selectedText();
		rngSeln = editor.selectedRange();
		if (strSeln == '') {
			selectBlock(editor);
		} else {
			rngFullLines = editor.tree().createRangeFromNodes(rngSeln.startNode, 0, rngSeln.endNode, -1);
			editor.setSelectedRange(rngFullLines);
		}
		strSeln = editor.selectedText();
		rngSeln = editor.selectedRange();

	if (options.fenced) {
			nodeFirst = rngSeln.startNode;
			nodeLast = rngSeln.endNode;

		if (nodeFirst.type() !== 'fencedcodetopboundary' && nodeLast.type() !== 'fencedcodebottomboundary') {
				strCode = '```\\n' + strSeln + '\\n```'
				if (nodeFirst.previousLineNode().type() !== 'empty') {
					strCode = '\\n' + strCode;
				}
				if (nodeLast.nextLineNode().type() !== 'empty') {
					strCode = strCode + '\\n';
				}
			} else {
				editor.tree().removeNodes([nodeFirst, nodeLast]);
			}
		} else {
			// does every line contain four spaces ?
			lngLines = strSeln.split('\\n').length;
			oMatch = strSeln.match(rgxCode);
			if (oMatch) lngCodeLines = oMatch.length;
	
		if (lngLines != lngCodeLines) {
				// add four spaces before indent
				strCode = strSeln.replace(rgxIndent, '    $1');
			} else {
				// remove four spaces at start of indent
				strCode = strSeln.replace(rgxCode,'$1');
			}
		}
		editor.replaceSelection(strCode);

	function selectBlock(editor) {
			// select back to previous gap (or start of doc)
			// and forward to next gap (or end of doc)
	
		var	rngSeln = editor.selectedRange(),
	
		idSelnStart = rngSeln.startNode.id,
			idSelnEnd = rngSeln.endNode.id,
	
		// PATHS: PREVIOUS GAP, AND NEXT GAP
			strPathPrevGap = '//@id=' + idSelnStart + '/preceding::(@type=empty)[-1]',
			strPathNextGap = '//@id=' + idSelnEnd + '/following::(@type=empty)[0]',
	
		tree = editor.tree(),
			lstBefore = tree.evaluateNodePath(strPathPrevGap),
			lstAfter = tree.evaluateNodePath(strPathNextGap),
	
		lngFrom, lngTo, rngNew;
	
		// most recent empty line, or start of doc
			if (lstBefore.length) {
				lngFrom = lstBefore[0].lineTextStart()+1;
			} else {
				lngFrom = 0;
			}
	
		// next empty line, or end of doc
			if (lstAfter.length) {
				lngTo = lstAfter[0].lineTextStart()-1;
			} else {
				lngTo = tree.textLength();
			}
	
		// define the range by start position and length
			rngNew = tree.createRangeFromLocation(lngFrom, lngTo-lngFrom);
	
		// and select it
			editor.setSelectedRange(rngNew);
	
		return true;
		}
	}

"

on run
	set varResult to missing value
	tell application "FoldingText"
		if not pblnDebug then
			set lstDocs to documents
			if lstDocs ≠ {} then
				tell item 1 of lstDocs
					set varResult to (evaluate script pstrFormatCodeBlock with options {fenced:pblnFenced})
				end tell
			end if
		else
			-- debug script automatically refers to the SDK version of the editor
			-- which must be open: FoldingText > Help > SDK > Run Editor
			set varResult to (debug script pstrFormatCodeBlock with options {fenced:pblnFenced})
		end if
	end tell
	return varResult
end run

As an alternative, what about using github style code blocks? Empty line with “" before code and empty line with "” after code. I now use that syntax for pretty much codeblock over a few lines now.

Yes, I think fenced code is good in many contexts – and I’ve now amended the code above to fence/unfence the selected block/lines if you change the value of property pblnFenced : (near the top of the script) to true

On the other hand, prepending four spaces can also have advantages – it takes fewer lines, and perhaps looks a little better in the source text.

More particularly as it happens, I tried just now to paste the updated code in fenced format, and found that the forum software left patches of it in non-code status. I then reformatted it with prepended spaces (property pblnFenced : false) and all of the lines were correctly formatted as code :- )

( PS in draft 2 above, I’ve also adjusted the 4 space option, to prepend the space before the indents, rather than adding them at the end of the indents )