UNPKG

25.3 kBJavaScriptView Raw
1// code is originally from https://github.com/AnAppAMonth/linewrap
2
3// Presets
4var presetMap = {
5 'html': {
6 skipScheme: 'html',
7 lineBreakScheme: 'html',
8 whitespace: 'collapse'
9 }
10}
11
12// lineBreak Schemes
13var brPat = /<\s*br(?:[\s/]*|\s[^>]*)>/gi
14var lineBreakSchemeMap = {
15 'unix': [/\n/g, '\n'],
16 'dos': [/\r\n/g, '\r\n'],
17 'mac': [/\r/g, '\r'],
18 'html': [brPat, '<br>'],
19 'xhtml': [brPat, '<br/>']
20}
21
22// skip Schemes
23var skipSchemeMap = {
24 'ansi-color': /\x1B\[[^m]*m/g,
25 'html': /<[^>]*>/g,
26 'bbcode': /\[[^]]*\]/g
27}
28
29var modeMap = {
30 'soft': 1,
31 'hard': 1
32}
33
34var wsMap = {
35 'collapse': 1,
36 'default': 1,
37 'line': 1,
38 'all': 1
39}
40
41var rlbMap = {
42 'all': 1,
43 'multi': 1,
44 'none': 1
45}
46var rlbSMPat = /([sm])(\d+)/
47
48var escapePat = /[-/\\^$*+?.()|[\]{}]/g
49function escapeRegExp (s) {
50 return s.replace(escapePat, '\\$&')
51}
52
53var linewrap = module.exports = function (start, stop, params) {
54 if (typeof start === 'object') {
55 params = start
56 start = params.start
57 stop = params.stop
58 }
59
60 if (typeof stop === 'object') {
61 params = stop
62 start = start || params.start
63 stop = undefined
64 }
65
66 if (!stop) {
67 stop = start
68 start = 0
69 }
70
71 if (!params) { params = {}; }
72 // Supported options and default values.
73 var preset,
74 mode = 'soft',
75 whitespace = 'default',
76 tabWidth = 4,
77 skip, skipScheme, lineBreak, lineBreakScheme,
78 respectLineBreaks = 'all',
79 respectNum,
80 preservedLineIndent,
81 wrapLineIndent, wrapLineIndentBase
82
83 var skipPat
84 var lineBreakPat, lineBreakStr
85 var multiLineBreakPat
86 var preservedLinePrefix = ''
87 var wrapLineIndentPat, wrapLineInitPrefix = ''
88 var tabRepl
89 var item, flags
90 var i
91
92 // First process presets, because these settings can be overwritten later.
93 preset = params.preset
94 if (preset) {
95 if (!(preset instanceof Array)) {
96 preset = [preset]
97 }
98 for (i = 0; i < preset.length; i++) {
99 item = presetMap[preset[i]]
100 if (item) {
101 if (item.mode) {
102 mode = item.mode
103 }
104 if (item.whitespace) {
105 whitespace = item.whitespace
106 }
107 if (item.tabWidth !== undefined) {
108 tabWidth = item.tabWidth
109 }
110 if (item.skip) {
111 skip = item.skip
112 }
113 if (item.skipScheme) {
114 skipScheme = item.skipScheme
115 }
116 if (item.lineBreak) {
117 lineBreak = item.lineBreak
118 }
119 if (item.lineBreakScheme) {
120 lineBreakScheme = item.lineBreakScheme
121 }
122 if (item.respectLineBreaks) {
123 respectLineBreaks = item.respectLineBreaks
124 }
125 if (item.preservedLineIndent !== undefined) {
126 preservedLineIndent = item.preservedLineIndent
127 }
128 if (item.wrapLineIndent !== undefined) {
129 wrapLineIndent = item.wrapLineIndent
130 }
131 if (item.wrapLineIndentBase) {
132 wrapLineIndentBase = item.wrapLineIndentBase
133 }
134 } else {
135 throw new TypeError('preset must be one of "' + Object.keys(presetMap).join('", "') + '"')
136 }
137 }
138 }
139
140 if (params.mode) {
141 if (modeMap[params.mode]) {
142 mode = params.mode
143 } else {
144 throw new TypeError('mode must be one of "' + Object.keys(modeMap).join('", "') + '"')
145 }
146 }
147 // Available options: 'collapse', 'default', 'line', and 'all'
148 if (params.whitespace) {
149 if (wsMap[params.whitespace]) {
150 whitespace = params.whitespace
151 } else {
152 throw new TypeError('whitespace must be one of "' + Object.keys(wsMap).join('", "') + '"')
153 }
154 }
155
156 if (params.tabWidth !== undefined) {
157 if (parseInt(params.tabWidth, 10) >= 0) {
158 tabWidth = parseInt(params.tabWidth, 10)
159 } else {
160 throw new TypeError('tabWidth must be a non-negative integer')
161 }
162 }
163 tabRepl = new Array(tabWidth + 1).join(' ')
164
165 // Available options: 'all', 'multi', 'm\d+', 's\d+', 'none'
166 if (params.respectLineBreaks) {
167 if (rlbMap[params.respectLineBreaks] || rlbSMPat.test(params.respectLineBreaks)) {
168 respectLineBreaks = params.respectLineBreaks
169 } else {
170 throw new TypeError('respectLineBreaks must be one of "' + Object.keys(rlbMap).join('", "') +
171 '", "m<num>", "s<num>"')
172 }
173 }
174 // After these conversions, now we have 4 options in `respectLineBreaks`:
175 // 'all', 'none', 'm' and 's'.
176 // `respectNum` is applicable iff `respectLineBreaks` is either 'm' or 's'.
177 if (respectLineBreaks === 'multi') {
178 respectLineBreaks = 'm'
179 respectNum = 2
180 } else if (!rlbMap[respectLineBreaks]) {
181 var match = rlbSMPat.exec(respectLineBreaks)
182 respectLineBreaks = match[1]
183 respectNum = parseInt(match[2], 10)
184 }
185
186 if (params.preservedLineIndent !== undefined) {
187 if (parseInt(params.preservedLineIndent, 10) >= 0) {
188 preservedLineIndent = parseInt(params.preservedLineIndent, 10)
189 } else {
190 throw new TypeError('preservedLineIndent must be a non-negative integer')
191 }
192 }
193
194 if (preservedLineIndent > 0) {
195 preservedLinePrefix = new Array(preservedLineIndent + 1).join(' ')
196 }
197
198 if (params.wrapLineIndent !== undefined) {
199 if (!isNaN(parseInt(params.wrapLineIndent, 10))) {
200 wrapLineIndent = parseInt(params.wrapLineIndent, 10)
201 } else {
202 throw new TypeError('wrapLineIndent must be an integer')
203 }
204 }
205 if (params.wrapLineIndentBase) {
206 wrapLineIndentBase = params.wrapLineIndentBase
207 }
208
209 if (wrapLineIndentBase) {
210 if (wrapLineIndent === undefined) {
211 throw new TypeError('wrapLineIndent must be specified when wrapLineIndentBase is specified')
212 }
213 if (wrapLineIndentBase instanceof RegExp) {
214 wrapLineIndentPat = wrapLineIndentBase
215 } else if (typeof wrapLineIndentBase === 'string') {
216 wrapLineIndentPat = new RegExp(escapeRegExp(wrapLineIndentBase))
217 } else {
218 throw new TypeError('wrapLineIndentBase must be either a RegExp object or a string')
219 }
220 } else if (wrapLineIndent > 0) {
221 wrapLineInitPrefix = new Array(wrapLineIndent + 1).join(' ')
222 } else if (wrapLineIndent < 0) {
223 throw new TypeError('wrapLineIndent must be non-negative when a base is not specified')
224 }
225
226 // NOTE: For the two RegExps `skipPat` and `lineBreakPat` that can be specified
227 // by the user:
228 // 1. We require them to be "global", so we have to convert them to global
229 // if the user specifies a non-global regex.
230 // 2. We cannot call `split()` on them, because they may or may not contain
231 // capturing parentheses which affect the output of `split()`.
232
233 // Precedence: Regex = Str > Scheme
234 if (params.skipScheme) {
235 if (skipSchemeMap[params.skipScheme]) {
236 skipScheme = params.skipScheme
237 } else {
238 throw new TypeError('skipScheme must be one of "' + Object.keys(skipSchemeMap).join('", "') + '"')
239 }
240 }
241 if (params.skip) {
242 skip = params.skip
243 }
244
245 if (skip) {
246 if (skip instanceof RegExp) {
247 skipPat = skip
248 if (!skipPat.global) {
249 flags = 'g'
250 if (skipPat.ignoreCase) { flags += 'i'; }
251 if (skipPat.multiline) { flags += 'm'; }
252 skipPat = new RegExp(skipPat.source, flags)
253 }
254 } else if (typeof skip === 'string') {
255 skipPat = new RegExp(escapeRegExp(skip), 'g')
256 } else {
257 throw new TypeError('skip must be either a RegExp object or a string')
258 }
259 }
260 if (!skipPat && skipScheme) {
261 skipPat = skipSchemeMap[skipScheme]
262 }
263
264 // Precedence:
265 // - for lineBreakPat: Regex > Scheme > Str
266 // - for lineBreakStr: Str > Scheme > Regex
267 if (params.lineBreakScheme) {
268 if (lineBreakSchemeMap[params.lineBreakScheme]) {
269 lineBreakScheme = params.lineBreakScheme
270 } else {
271 throw new TypeError('lineBreakScheme must be one of "' + Object.keys(lineBreakSchemeMap).join('", "') + '"')
272 }
273 }
274 if (params.lineBreak) {
275 lineBreak = params.lineBreak
276 }
277
278 if (lineBreakScheme) {
279 // Supported schemes: 'unix', 'dos', 'mac', 'html', 'xhtml'
280 item = lineBreakSchemeMap[lineBreakScheme]
281 if (item) {
282 lineBreakPat = item[0]
283 lineBreakStr = item[1]
284 }
285 }
286 if (lineBreak) {
287 if (lineBreak instanceof Array) {
288 if (lineBreak.length === 1) {
289 lineBreak = lineBreak[0]
290 } else if (lineBreak.length >= 2) {
291 if (lineBreak[0] instanceof RegExp) {
292 lineBreakPat = lineBreak[0]
293 if (typeof lineBreak[1] === 'string') {
294 lineBreakStr = lineBreak[1]
295 }
296 } else if (lineBreak[1] instanceof RegExp) {
297 lineBreakPat = lineBreak[1]
298 if (typeof lineBreak[0] === 'string') {
299 lineBreakStr = lineBreak[0]
300 }
301 } else if (typeof lineBreak[0] === 'string' && typeof lineBreak[1] === 'string') {
302 lineBreakPat = new RegExp(escapeRegExp(lineBreak[0]), 'g')
303 lineBreakStr = lineBreak[1]
304 } else {
305 lineBreak = lineBreak[0]
306 }
307 }
308 }
309 if (typeof lineBreak === 'string') {
310 lineBreakStr = lineBreak
311 if (!lineBreakPat) {
312 lineBreakPat = new RegExp(escapeRegExp(lineBreak), 'g')
313 }
314 } else if (lineBreak instanceof RegExp) {
315 lineBreakPat = lineBreak
316 } else if (!(lineBreak instanceof Array)) {
317 throw new TypeError('lineBreak must be a RegExp object, a string, or an array consisted of a RegExp object and a string')
318 }
319 }
320 // Only assign defaults when `lineBreakPat` is not assigned.
321 // So if `params.lineBreak` is a RegExp, we don't have a value in `lineBreakStr`
322 // yet. We will try to get the value from the input string, and if failed, we
323 // will throw an exception.
324 if (!lineBreakPat) {
325 lineBreakPat = /\n/g
326 lineBreakStr = '\n'
327 }
328
329 // Create `multiLineBreakPat` based on `lineBreakPat`, that matches strings
330 // consisted of one or more line breaks and zero or more whitespaces.
331 // Also convert `lineBreakPat` to global if not already so.
332 flags = 'g'
333 if (lineBreakPat.ignoreCase) { flags += 'i'; }
334 if (lineBreakPat.multiline) { flags += 'm'; }
335 multiLineBreakPat = new RegExp('\\s*(?:' + lineBreakPat.source + ')(?:' +
336 lineBreakPat.source + '|\\s)*', flags)
337 if (!lineBreakPat.global) {
338 lineBreakPat = new RegExp(lineBreakPat.source, flags)
339 }
340
341 // Initialize other useful variables.
342 var re = mode === 'hard' ? /\b/ : /(\S+\s+)/
343 var prefix = new Array(start + 1).join(' ')
344 var wsStrip = (whitespace === 'default' || whitespace === 'collapse'),
345 wsCollapse = (whitespace === 'collapse'),
346 wsLine = (whitespace === 'line'),
347 wsAll = (whitespace === 'all')
348 var tabPat = /\t/g,
349 collapsePat = / +/g,
350 pPat = /^\s+/,
351 tPat = /\s+$/,
352 nonWsPat = /\S/,
353 wsPat = /\s/
354 var wrapLen = stop - start
355
356 return function (text) {
357 text = text.toString().replace(tabPat, tabRepl)
358
359 var match
360 if (!lineBreakStr) {
361 // Try to get lineBreakStr from `text`
362 lineBreakPat.lastIndex = 0
363 match = lineBreakPat.exec(text)
364 if (match) {
365 lineBreakStr = match[0]
366 } else {
367 throw new TypeError('Line break string for the output not specified')
368 }
369 }
370
371 // text -> blocks; each bloc -> segments; each segment -> chunks
372 var blocks, base = 0
373 var mo, arr, b, res
374 // Split `text` by line breaks.
375 blocks = []
376 multiLineBreakPat.lastIndex = 0
377 match = multiLineBreakPat.exec(text)
378 while(match) {
379 blocks.push(text.substring(base, match.index))
380
381 if (respectLineBreaks !== 'none') {
382 arr = []
383 b = 0
384 lineBreakPat.lastIndex = 0
385 mo = lineBreakPat.exec(match[0])
386 while(mo) {
387 arr.push(match[0].substring(b, mo.index))
388 b = mo.index + mo[0].length
389 mo = lineBreakPat.exec(match[0])
390 }
391 arr.push(match[0].substring(b))
392 blocks.push({type: 'break', breaks: arr})
393 } else {
394 // Strip line breaks and insert spaces when necessary.
395 if (wsCollapse) {
396 res = ' '
397 } else {
398 res = match[0].replace(lineBreakPat, '')
399 }
400 blocks.push({type: 'break', remaining: res})
401 }
402
403 base = match.index + match[0].length
404 match = multiLineBreakPat.exec(text)
405 }
406 blocks.push(text.substring(base))
407
408 var i, j, k
409 var segments
410 if (skipPat) {
411 segments = []
412 for (i = 0; i < blocks.length; i++) {
413 var bloc = blocks[i]
414 if (typeof bloc !== 'string') {
415 // This is an object.
416 segments.push(bloc)
417 } else {
418 base = 0
419 skipPat.lastIndex = 0
420 match = skipPat.exec(bloc)
421 while(match) {
422 segments.push(bloc.substring(base, match.index))
423 segments.push({type: 'skip', value: match[0]})
424 base = match.index + match[0].length
425 match = skipPat.exec(bloc)
426 }
427 segments.push(bloc.substring(base))
428 }
429 }
430 } else {
431 segments = blocks
432 }
433
434 var chunks = []
435 for (i = 0; i < segments.length; i++) {
436 var segment = segments[i]
437 if (typeof segment !== 'string') {
438 // This is an object.
439 chunks.push(segment)
440 } else {
441 if (wsCollapse) {
442 segment = segment.replace(collapsePat, ' ')
443 }
444
445 var parts = segment.split(re),
446 acc = []
447
448 for (j = 0; j < parts.length; j++) {
449 var x = parts[j]
450 if (mode === 'hard') {
451 for (k = 0; k < x.length; k += wrapLen) {
452 acc.push(x.slice(k, k + wrapLen))
453 }
454 } else { acc.push(x); }
455 }
456 chunks = chunks.concat(acc)
457 }
458 }
459
460 var curLine = 0,
461 curLineLength = start + preservedLinePrefix.length,
462 lines = [ prefix + preservedLinePrefix ],
463 // Holds the "real length" (excluding trailing whitespaces) of the
464 // current line if it exceeds `stop`, otherwise 0.
465 // ONLY USED when `wsAll` is true, in `finishOffCurLine()`.
466 bulge = 0,
467 // `cleanLine` is true iff we are at the beginning of an output line. By
468 // "beginning" we mean it doesn't contain any non-whitespace char yet.
469 // But its `curLineLength` can be greater than `start`, or even possibly
470 // be greater than `stop`, if `wsStrip` is false.
471 //
472 // Note that a "clean" line can still contain skip strings, in addition
473 // to whitespaces.
474 //
475 // This variable is used to allow us strip preceding whitespaces when
476 // `wsStrip` is true, or `wsLine` is true and `preservedLine` is false.
477 cleanLine = true,
478 // `preservedLine` is true iff we are in a preserved input line.
479 //
480 // It's used when `wsLine` is true to (combined with `cleanLine`) decide
481 // whether a whitespace is at the beginning of a preserved input line and
482 // should not be stripped.
483 preservedLine = true,
484 // The current indent prefix for wrapped lines.
485 wrapLinePrefix = wrapLineInitPrefix,
486 remnant
487
488 // Always returns '' if `beforeHardBreak` is true.
489 //
490 // Assumption: Each call of this function is always followed by a `lines.push()` call.
491 //
492 // This function can change the status of `cleanLine`, but we don't modify the value of
493 // `cleanLine` in this function. It's fine because `cleanLine` will be set to the correct
494 // value after the `lines.push()` call following this function call. We also don't update
495 // `curLineLength` when pushing a new line and it's safe for the same reason.
496 function finishOffCurLine (beforeHardBreak) {
497 var str = lines[curLine],
498 idx, ln, rBase
499
500 if (!wsAll) {
501 // Strip all trailing whitespaces past `start`.
502 idx = str.length - 1
503 while (idx >= start && str[idx] === ' ') { idx--; }
504 while (idx >= start && wsPat.test(str[idx])) { idx--; }
505 idx++
506
507 if (idx !== str.length) {
508 lines[curLine] = str.substring(0, idx)
509 }
510
511 if (preservedLine && cleanLine && wsLine && curLineLength > stop) {
512 // Add the remnants to the next line, just like when `wsAll` is true.
513 rBase = str.length - (curLineLength - stop)
514 if (rBase < idx) {
515 // We didn't reach `stop` when stripping due to a bulge.
516 rBase = idx
517 }
518 }
519 } else {
520 // Strip trailing whitespaces exceeding stop.
521 if (curLineLength > stop) {
522 bulge = bulge || stop
523 rBase = str.length - (curLineLength - bulge)
524 lines[curLine] = str.substring(0, rBase)
525 }
526 bulge = 0
527 }
528
529 // Bug: the current implementation of `wrapLineIndent` is buggy: we are not
530 // taking the extra space occupied by the additional indentation into account
531 // when wrapping the line. For example, in "hard" mode, we should hard-wrap
532 // long words at `wrapLen - wrapLinePrefix.length` instead of `wrapLen`
533 // and remnants should also be wrapped at `wrapLen - wrapLinePrefix.length`.
534 if (preservedLine) {
535 // This is a preserved line, and the next output line isn't a
536 // preserved line.
537 preservedLine = false
538 if (wrapLineIndentPat) {
539 idx = lines[curLine].substring(start).search(wrapLineIndentPat)
540 if (idx >= 0 && idx + wrapLineIndent > 0) {
541 wrapLinePrefix = new Array(idx + wrapLineIndent + 1).join(' ')
542 } else {
543 wrapLinePrefix = ''
544 }
545 }
546 }
547
548 // Some remnants are left to the next line.
549 if (rBase) {
550 while (rBase + wrapLen < str.length) {
551 if (wsAll) {
552 ln = str.substring(rBase, rBase + wrapLen)
553 lines.push(prefix + wrapLinePrefix + ln)
554 } else {
555 lines.push(prefix + wrapLinePrefix)
556 }
557 rBase += wrapLen
558 curLine++
559 }
560 if (beforeHardBreak) {
561 if (wsAll) {
562 ln = str.substring(rBase)
563 lines.push(prefix + wrapLinePrefix + ln)
564 } else {
565 lines.push(prefix + wrapLinePrefix)
566 }
567 curLine++
568 } else {
569 ln = str.substring(rBase)
570 return wrapLinePrefix + ln
571 }
572 }
573
574 return ''
575 }
576
577 for (i = 0; i < chunks.length; i++) {
578 var chunk = chunks[i]
579
580 if (chunk === '') { continue; }
581
582 if (typeof chunk !== 'string') {
583 if (chunk.type === 'break') {
584 // This is one or more line breaks.
585 // Each entry in `breaks` is just zero or more whitespaces.
586 if (respectLineBreaks !== 'none') {
587 // Note that if `whitespace` is "collapse", we still need
588 // to collapse whitespaces in entries of `breaks`.
589 var breaks = chunk.breaks
590 var num = breaks.length - 1
591
592 if (respectLineBreaks === 's') {
593 // This is the most complex scenario. We have to check
594 // the line breaks one by one.
595 for (j = 0; j < num; j++) {
596 if (breaks[j + 1].length < respectNum) {
597 // This line break should be stripped.
598 if (wsCollapse) {
599 breaks[j + 1] = ' '
600 } else {
601 breaks[j + 1] = breaks[j] + breaks[j + 1]
602 }
603 } else {
604 // This line break should be preserved.
605 // First finish off the current line.
606 if (wsAll) {
607 lines[curLine] += breaks[j]
608 curLineLength += breaks[j].length
609 }
610 finishOffCurLine(true)
611
612 lines.push(prefix + preservedLinePrefix)
613 curLine++
614 curLineLength = start + preservedLinePrefix.length
615
616 preservedLine = cleanLine = true
617 }
618 }
619 // We are adding to either the existing line (if no line break
620 // is qualified for preservance) or a "new" line.
621 if (!cleanLine || wsAll || (wsLine && preservedLine)) {
622 if (wsCollapse || (!cleanLine && breaks[num] === '')) {
623 breaks[num] = ' '
624 }
625 lines[curLine] += breaks[num]
626 curLineLength += breaks[num].length
627 }
628 } else if (respectLineBreaks === 'm' && num < respectNum) {
629 // These line breaks should be stripped.
630 if (!cleanLine || wsAll || (wsLine && preservedLine)) {
631 if (wsCollapse) {
632 chunk = ' '
633 } else {
634 chunk = breaks.join('')
635 if (!cleanLine && chunk === '') {
636 chunk = ' '
637 }
638 }
639 lines[curLine] += chunk
640 curLineLength += chunk.length
641 }
642 } else { // 'all' || ('m' && num >= respectNum)
643 // These line breaks should be preserved.
644 if (wsStrip) {
645 // Finish off the current line.
646 finishOffCurLine(true)
647
648 for (j = 0; j < num; j++) {
649 lines.push(prefix + preservedLinePrefix)
650 curLine++
651 }
652
653 curLineLength = start + preservedLinePrefix.length
654 preservedLine = cleanLine = true
655 } else {
656 if (wsAll || (preservedLine && cleanLine)) {
657 lines[curLine] += breaks[0]
658 curLineLength += breaks[0].length
659 }
660
661 for (j = 0; j < num; j++) {
662 // Finish off the current line.
663 finishOffCurLine(true)
664
665 lines.push(prefix + preservedLinePrefix + breaks[j + 1])
666 curLine++
667 curLineLength = start + preservedLinePrefix.length + breaks[j + 1].length
668
669 preservedLine = cleanLine = true
670 }
671 }
672 }
673 } else {
674 // These line breaks should be stripped.
675 if (!cleanLine || wsAll || (wsLine && preservedLine)) {
676 chunk = chunk.remaining
677
678 // Bug: If `wsAll` is true, `cleanLine` is false, and `chunk`
679 // is '', we insert a space to replace the line break. This
680 // space will be preserved even if we are at the end of an
681 // output line, which is wrong behavior. However, I'm not
682 // sure it's worth it to fix this edge case.
683 if (wsCollapse || (!cleanLine && chunk === '')) {
684 chunk = ' '
685 }
686 lines[curLine] += chunk
687 curLineLength += chunk.length
688 }
689 }
690 } else if (chunk.type === 'skip') {
691 // This is a skip string.
692 // Assumption: skip strings don't end with whitespaces.
693 if (curLineLength > stop) {
694 remnant = finishOffCurLine(false)
695
696 lines.push(prefix + wrapLinePrefix)
697 curLine++
698 curLineLength = start + wrapLinePrefix.length
699
700 if (remnant) {
701 lines[curLine] += remnant
702 curLineLength += remnant.length
703 }
704
705 cleanLine = true
706 }
707 lines[curLine] += chunk.value
708 }
709 continue
710 }
711
712 var chunk2
713 while (1) {
714 chunk2 = undefined
715 if (curLineLength + chunk.length > stop &&
716 curLineLength + (chunk2 = chunk.replace(tPat, '')).length > stop &&
717 chunk2 !== '' &&
718 curLineLength > start) {
719 // This line is full, add `chunk` to the next line
720 remnant = finishOffCurLine(false)
721
722 lines.push(prefix + wrapLinePrefix)
723 curLine++
724 curLineLength = start + wrapLinePrefix.length
725
726 if (remnant) {
727 lines[curLine] += remnant
728 curLineLength += remnant.length
729 cleanLine = true
730 continue
731 }
732
733 if (wsStrip || (wsLine && !(preservedLine && cleanLine))) {
734 chunk = chunk.replace(pPat, '')
735 }
736 cleanLine = false
737 } else {
738 // Add `chunk` to this line
739 if (cleanLine) {
740 if (wsStrip || (wsLine && !(preservedLine && cleanLine))) {
741 chunk = chunk.replace(pPat, '')
742 if (chunk !== '') {
743 cleanLine = false
744 }
745 } else {
746 if (nonWsPat.test(chunk)) {
747 cleanLine = false
748 }
749 }
750 }
751 }
752 break
753 }
754 if (wsAll && chunk2 && curLineLength + chunk2.length > stop) {
755 bulge = curLineLength + chunk2.length
756 }
757 lines[curLine] += chunk
758 curLineLength += chunk.length
759 }
760 // Finally, finish off the last line.
761 finishOffCurLine(true)
762 return lines.join(lineBreakStr)
763 }
764}
765
766linewrap.soft = linewrap
767
768linewrap.hard = function ( /*start, stop, params*/) {
769 var args = [].slice.call(arguments)
770 var last = args.length - 1
771 if (typeof args[last] === 'object') {
772 args[last].mode = 'hard'
773 } else {
774 args.push({ mode: 'hard' })
775 }
776 return linewrap.apply(null, args)
777}
778
779linewrap.wrap = function (text /*, start, stop, params*/) {
780 var args = [].slice.call(arguments)
781 args.shift()
782 return linewrap.apply(null, args)(text)
783}
784