-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
830433b
commit 27eed36
Showing
3 changed files
with
120 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
const parseAnsi = require('./parseAnsi'), | ||
{ parseEscape, closeEscapes } = require('./utils'); | ||
|
||
/** | ||
* Split a string with ANSI escape codes into an array of lines. Supports both `CRLF` and `LF` newlines. | ||
* @param {string} string - string to split | ||
* @returns {string[]} - array of lines in the input string | ||
*/ | ||
module.exports = function splitLines(string) { | ||
// ansi escapes stack, items in the form [seq, isLink, close, [i, j]] | ||
const ansiStack = [], | ||
// result array to hold processed lines | ||
result = [], | ||
// split input string into lines | ||
lines = String(string).split(/\r?\n/g); | ||
// iterate through each line in the input string | ||
for (let i = 0, n = lines.length; i < n; i += 1) { | ||
// the processed line | ||
let line = '', | ||
// intraline index | ||
j = -1; | ||
// match all ansi escape codes in the input line | ||
for (const [chunk, isEscape] of parseAnsi(lines[i])) { | ||
// check if chunk is an escape sequence | ||
if (isEscape) { | ||
// process this escape sequence | ||
const closed = parseEscape(ansiStack, chunk, [i, j]); | ||
if (closed && j >= 0) { | ||
// append escape if it closes an active item in the stack | ||
const [xi, xj] = closed; | ||
if (xi < i || (xi === i && xj < j)) line += chunk; | ||
} else if (closed === null) { | ||
// escape is not a SGR/hyperlink escape | ||
line += chunk; | ||
} | ||
continue; | ||
} | ||
// append any new escape sequences from the ansi stack | ||
line += (j < 0 ? ansiStack : ansiStack.filter(([,,, [xi, xj]]) => (xi === i && xj === j))) | ||
.map(([s]) => s) | ||
.join(''); | ||
// append this chunk | ||
line += chunk; | ||
// increment the intraline index | ||
j += 1; | ||
} | ||
// close open escape sequences if line is not empty | ||
if (j >= 0) { | ||
line += closeEscapes(ansiStack.filter(([,,, [xi, xj]]) => (xi < i || (xi === i && xj < j)))); | ||
} | ||
// add proccessed line to the result array | ||
result.push(line); | ||
} | ||
return result; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
const splitLines = require('../lib/splitLines'); | ||
|
||
describe('splitLines', () => { | ||
test('supports both LF and CRLF newline types', () => { | ||
expect(splitLines('AA\nBB\r\nCC')).toEqual(['AA', 'BB', 'CC']); | ||
}); | ||
|
||
test('handles non-string inputs', () => { | ||
expect(splitLines(10)).toEqual(['10']); | ||
}); | ||
|
||
test('splits style escapes that span multiple lines', () => { | ||
expect(splitLines('\u001b[41mAAA\u001b[33mBBB\nCCC\u001b[39mDDD\u001b[49m')).toEqual([ | ||
'\u001b[41mAAA\u001b[33mBBB\u001b[39m\u001b[49m', | ||
'\u001b[41m\u001b[33mCCC\u001b[39mDDD\u001b[49m', | ||
]); | ||
}); | ||
|
||
test('splits style escapes that span a single line', () => { | ||
expect(splitLines('\u001b[41mAAAA\u001b[49m\u001b[33m\nBBBB\u001b[39m')).toEqual([ | ||
'\u001b[41mAAAA\u001b[49m', | ||
'\u001b[33mBBBB\u001b[39m', | ||
]); | ||
}); | ||
|
||
test('splits style escape sequences that overlap across a line break', () => { | ||
expect(splitLines('\u001b[41mAAAA\u001b[33m\n\u001b[49mBBBB\u001b[39m')).toEqual([ | ||
'\u001b[41mAAAA\u001b[49m', | ||
'\u001b[33mBBBB\u001b[39m', | ||
]); | ||
}); | ||
|
||
test('splits style escape sequences that span empty lines', () => { | ||
expect(splitLines('\u001b[41m\nAAA\n\nBBB\u001b[49m')).toEqual([ | ||
'', | ||
'\u001b[41mAAA\u001b[49m', | ||
'', | ||
'\u001b[41mBBB\u001b[49m', | ||
]); | ||
}); | ||
|
||
test('scrubs empty escape sequences', () => { | ||
expect(splitLines('AA\u001b[41m\u001b[49mA\nBB')).toEqual(['AAA', 'BB']); | ||
}); | ||
|
||
test('scrubs escape sequences that span only line breaks', () => { | ||
expect(splitLines('AAAA\u001b[41m\n\u001b[49mBBBB')).toEqual(['AAAA', 'BBBB']); | ||
}); | ||
|
||
test('supports ansi hyperlink escapes', () => { | ||
expect(splitLines('\u001b]8;;link\u0007AA\nB\u001b]8;;\u0007b')).toEqual([ | ||
'\u001b]8;;link\u0007AA\u001b]8;;\u0007', | ||
'\u001b]8;;link\u0007B\u001b]8;;\u0007b', | ||
]); | ||
}); | ||
|
||
test('handles non-SGR/non-hyperlink ansi escape sequences', () => { | ||
expect(splitLines('AA\u001B]0;window_title\u0007\nBB')).toEqual([ | ||
'AA\u001B]0;window_title\u0007', | ||
'BB', | ||
]); | ||
}); | ||
}); |