147 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| const FOLD_FLOW = 'flow';
 | |
| const FOLD_BLOCK = 'block';
 | |
| const FOLD_QUOTED = 'quoted';
 | |
| /**
 | |
|  * Tries to keep input at up to `lineWidth` characters, splitting only on spaces
 | |
|  * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are
 | |
|  * terminated with `\n` and started with `indent`.
 | |
|  */
 | |
| function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
 | |
|     if (!lineWidth || lineWidth < 0)
 | |
|         return text;
 | |
|     if (lineWidth < minContentWidth)
 | |
|         minContentWidth = 0;
 | |
|     const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
 | |
|     if (text.length <= endStep)
 | |
|         return text;
 | |
|     const folds = [];
 | |
|     const escapedFolds = {};
 | |
|     let end = lineWidth - indent.length;
 | |
|     if (typeof indentAtStart === 'number') {
 | |
|         if (indentAtStart > lineWidth - Math.max(2, minContentWidth))
 | |
|             folds.push(0);
 | |
|         else
 | |
|             end = lineWidth - indentAtStart;
 | |
|     }
 | |
|     let split = undefined;
 | |
|     let prev = undefined;
 | |
|     let overflow = false;
 | |
|     let i = -1;
 | |
|     let escStart = -1;
 | |
|     let escEnd = -1;
 | |
|     if (mode === FOLD_BLOCK) {
 | |
|         i = consumeMoreIndentedLines(text, i, indent.length);
 | |
|         if (i !== -1)
 | |
|             end = i + endStep;
 | |
|     }
 | |
|     for (let ch; (ch = text[(i += 1)]);) {
 | |
|         if (mode === FOLD_QUOTED && ch === '\\') {
 | |
|             escStart = i;
 | |
|             switch (text[i + 1]) {
 | |
|                 case 'x':
 | |
|                     i += 3;
 | |
|                     break;
 | |
|                 case 'u':
 | |
|                     i += 5;
 | |
|                     break;
 | |
|                 case 'U':
 | |
|                     i += 9;
 | |
|                     break;
 | |
|                 default:
 | |
|                     i += 1;
 | |
|             }
 | |
|             escEnd = i;
 | |
|         }
 | |
|         if (ch === '\n') {
 | |
|             if (mode === FOLD_BLOCK)
 | |
|                 i = consumeMoreIndentedLines(text, i, indent.length);
 | |
|             end = i + indent.length + endStep;
 | |
|             split = undefined;
 | |
|         }
 | |
|         else {
 | |
|             if (ch === ' ' &&
 | |
|                 prev &&
 | |
|                 prev !== ' ' &&
 | |
|                 prev !== '\n' &&
 | |
|                 prev !== '\t') {
 | |
|                 // space surrounded by non-space can be replaced with newline + indent
 | |
|                 const next = text[i + 1];
 | |
|                 if (next && next !== ' ' && next !== '\n' && next !== '\t')
 | |
|                     split = i;
 | |
|             }
 | |
|             if (i >= end) {
 | |
|                 if (split) {
 | |
|                     folds.push(split);
 | |
|                     end = split + endStep;
 | |
|                     split = undefined;
 | |
|                 }
 | |
|                 else if (mode === FOLD_QUOTED) {
 | |
|                     // white-space collected at end may stretch past lineWidth
 | |
|                     while (prev === ' ' || prev === '\t') {
 | |
|                         prev = ch;
 | |
|                         ch = text[(i += 1)];
 | |
|                         overflow = true;
 | |
|                     }
 | |
|                     // Account for newline escape, but don't break preceding escape
 | |
|                     const j = i > escEnd + 1 ? i - 2 : escStart - 1;
 | |
|                     // Bail out if lineWidth & minContentWidth are shorter than an escape string
 | |
|                     if (escapedFolds[j])
 | |
|                         return text;
 | |
|                     folds.push(j);
 | |
|                     escapedFolds[j] = true;
 | |
|                     end = j + endStep;
 | |
|                     split = undefined;
 | |
|                 }
 | |
|                 else {
 | |
|                     overflow = true;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         prev = ch;
 | |
|     }
 | |
|     if (overflow && onOverflow)
 | |
|         onOverflow();
 | |
|     if (folds.length === 0)
 | |
|         return text;
 | |
|     if (onFold)
 | |
|         onFold();
 | |
|     let res = text.slice(0, folds[0]);
 | |
|     for (let i = 0; i < folds.length; ++i) {
 | |
|         const fold = folds[i];
 | |
|         const end = folds[i + 1] || text.length;
 | |
|         if (fold === 0)
 | |
|             res = `\n${indent}${text.slice(0, end)}`;
 | |
|         else {
 | |
|             if (mode === FOLD_QUOTED && escapedFolds[fold])
 | |
|                 res += `${text[fold]}\\`;
 | |
|             res += `\n${indent}${text.slice(fold + 1, end)}`;
 | |
|         }
 | |
|     }
 | |
|     return res;
 | |
| }
 | |
| /**
 | |
|  * Presumes `i + 1` is at the start of a line
 | |
|  * @returns index of last newline in more-indented block
 | |
|  */
 | |
| function consumeMoreIndentedLines(text, i, indent) {
 | |
|     let end = i;
 | |
|     let start = i + 1;
 | |
|     let ch = text[start];
 | |
|     while (ch === ' ' || ch === '\t') {
 | |
|         if (i < start + indent) {
 | |
|             ch = text[++i];
 | |
|         }
 | |
|         else {
 | |
|             do {
 | |
|                 ch = text[++i];
 | |
|             } while (ch && ch !== '\n');
 | |
|             end = i;
 | |
|             start = i + 1;
 | |
|             ch = text[start];
 | |
|         }
 | |
|     }
 | |
|     return end;
 | |
| }
 | |
| 
 | |
| export { FOLD_BLOCK, FOLD_FLOW, FOLD_QUOTED, foldFlowLines };
 |