Skip to content

Commit d51c971

Browse files
G-Rathmoofoo
andauthoredAug 23, 2021
Improve performance of the .clear() method (#182)
Co-authored-by: moofoo <nathancookdev@gmail.com>
1 parent 476935f commit d51c971

File tree

3 files changed

+579
-2
lines changed

3 files changed

+579
-2
lines changed
 

‎index.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class Ora {
142142
}
143143

144144
this._indent = indent;
145+
this.updateLineCount();
145146
}
146147

147148
_updateInterval(interval) {
@@ -215,7 +216,7 @@ class Ora {
215216
const columns = this.stream.columns || 80;
216217
const fullPrefixText = this.getFullPrefixText(this.prefixText, '-');
217218
this.lineCount = 0;
218-
for (const line of stripAnsi(fullPrefixText + '--' + this[TEXT]).split('\n')) {
219+
for (const line of stripAnsi(' '.repeat(this.indent) + fullPrefixText + '--' + this[TEXT]).split('\n')) {
219220
this.lineCount += Math.max(1, Math.ceil(wcwidth(line) / columns));
220221
}
221222
}
@@ -264,15 +265,22 @@ class Ora {
264265
return this;
265266
}
266267

268+
this.stream.cursorTo(0);
269+
267270
for (let i = 0; i < this.linesToClear; i++) {
268271
if (i > 0) {
269272
this.stream.moveCursor(0, -1);
270273
}
271274

272-
this.stream.clearLine();
275+
this.stream.clearLine(1);
276+
}
277+
278+
if (this.indent || this.lastIndent !== this.indent) {
273279
this.stream.cursorTo(this.indent);
274280
}
275281

282+
this.lastIndent = this.indent;
283+
276284
this.linesToClear = 0;
277285

278286
return this;

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@types/node": "^14.14.35",
5252
"ava": "^2.4.0",
5353
"get-stream": "^6.0.0",
54+
"transform-tty": "^1.0.11",
5455
"tsd": "^0.14.0",
5556
"xo": "^0.38.2"
5657
}

‎test.js

+568
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import getStream from 'get-stream';
33
import test from 'ava';
44
import stripAnsi from 'strip-ansi';
55
import Ora from './index.js';
6+
import TransformTTY from 'transform-tty';
7+
import cliSpinners from 'cli-spinners';
68

79
const spinnerCharacter = process.platform === 'win32' ? '-' : '⠋';
810
const noop = () => {};
@@ -365,6 +367,27 @@ test('indent option throws', t => {
365367
}, 'The `indent` option must be an integer from 0 and up');
366368
});
367369

370+
test('handles wrapped lines when length of indent + text is greater than columns', t => {
371+
const stream = getPassThroughStream();
372+
stream.isTTY = true;
373+
stream.columns = 20;
374+
375+
const spinner = new Ora({
376+
stream,
377+
text: 'foo',
378+
color: false,
379+
isEnabled: true
380+
});
381+
382+
spinner.render();
383+
384+
spinner.text = '0'.repeat(spinner.stream.columns - 5);
385+
spinner.indent = 15;
386+
spinner.render();
387+
388+
t.is(spinner.lineCount, 2);
389+
});
390+
368391
test('.stopAndPersist() with prefixText', macro, spinner => {
369392
spinner.stopAndPersist({symbol: '@', text: 'foo'});
370393
}, /bar @ foo\n$/, {prefixText: 'bar'});
@@ -384,3 +407,548 @@ test('.stopAndPersist() with manual empty prefixText', macro, spinner => {
384407
test('.stopAndPersist() with dynamic prefixText', macro, spinner => {
385408
spinner.stopAndPersist({symbol: '&', prefixText: () => 'babeee', text: 'yorkie'});
386409
}, /babeee & yorkie\n$/, {prefixText: () => 'babeee'});
410+
411+
// New clear method tests
412+
413+
const currentClearMethod = transFormTTY => {
414+
const spinner = new Ora({
415+
text: 'foo',
416+
color: false,
417+
isEnabled: true,
418+
stream: transFormTTY,
419+
spinner: {
420+
frames: ['-']
421+
}
422+
});
423+
424+
let firstIndent = true;
425+
426+
spinner.clear = function () {
427+
if (!this.isEnabled || !this.stream.isTTY) {
428+
return this;
429+
}
430+
431+
for (let i = 0; i < this.linesToClear; i++) {
432+
if (i > 0) {
433+
this.stream.moveCursor(0, -1);
434+
}
435+
436+
this.stream.clearLine();
437+
this.stream.cursorTo(this.indent);
438+
}
439+
440+
// It's too quick to be noticeable, but indent doesn't get applied
441+
// for the first render if linesToClear = 0. New clear method
442+
// doesn't have this issue, since it's called outside of the loop
443+
if (this.linesToClear === 0 && firstIndent && this.indent) {
444+
this.stream.cursorTo(this.indent);
445+
firstIndent = false;
446+
}
447+
448+
this.linesToClear = 0;
449+
450+
return this;
451+
}.bind(spinner);
452+
453+
return spinner;
454+
};
455+
456+
test.serial('new clear method test, basic', t => {
457+
const transformTTY = new TransformTTY({crlf: true});
458+
transformTTY.addSequencer();
459+
transformTTY.addSequencer(null, true);
460+
/*
461+
If the frames from this sequence differ from the previous sequence,
462+
it means the spinner.clear method has failed to fully clear output between calls to render
463+
*/
464+
465+
const currentClearTTY = new TransformTTY({crlf: true});
466+
currentClearTTY.addSequencer();
467+
468+
const currentOra = currentClearMethod(currentClearTTY);
469+
470+
const spinner = new Ora({
471+
text: 'foo',
472+
color: false,
473+
isEnabled: true,
474+
stream: transformTTY,
475+
spinner: {
476+
frames: ['-']
477+
}
478+
});
479+
480+
currentOra.render();
481+
spinner.render();
482+
483+
currentOra.text = 'bar';
484+
currentOra.indent = 5;
485+
currentOra.render();
486+
487+
spinner.text = 'bar';
488+
spinner.indent = 5;
489+
spinner.render();
490+
491+
currentOra.text = 'baz';
492+
currentOra.indent = 10;
493+
currentOra.render();
494+
495+
spinner.text = 'baz';
496+
spinner.indent = 10;
497+
spinner.render();
498+
499+
currentOra.succeed('boz?');
500+
501+
spinner.succeed('boz?');
502+
503+
const [sequenceString, clearedSequenceString] = transformTTY.getSequenceStrings();
504+
const [frames, clearedFrames] = transformTTY.getFrames();
505+
506+
t.is(sequenceString, ' ✔ boz?\n');
507+
t.is(sequenceString, clearedSequenceString);
508+
509+
t.deepEqual(clearedFrames, ['- foo', ' - bar', ' - baz', ' ✔ boz?\n']);
510+
t.deepEqual(frames, clearedFrames);
511+
512+
const currentString = currentClearTTY.getSequenceStrings();
513+
514+
t.is(currentString, ' ✔ boz?\n');
515+
516+
const currentFrames = currentClearTTY.getFrames();
517+
518+
t.deepEqual(frames, currentFrames);
519+
// Frames created using new clear method are deep equal to frames created using current clear method
520+
});
521+
522+
test('new clear method test, erases wrapped lines', t => {
523+
const transformTTY = new TransformTTY({crlf: true, columns: 40});
524+
transformTTY.addSequencer();
525+
transformTTY.addSequencer(null, true);
526+
527+
const currentClearTTY = new TransformTTY({crlf: true, columns: 40});
528+
currentClearTTY.addSequencer();
529+
530+
const currentOra = currentClearMethod(currentClearTTY);
531+
532+
const cursorAtRow = () => {
533+
const cursor = transformTTY.getCursorPos();
534+
return cursor.y === 0 ? 0 : cursor.y * -1;
535+
};
536+
537+
const clearedLines = () => {
538+
return transformTTY.toString().split('\n').length;
539+
};
540+
541+
const spinner = new Ora({
542+
text: 'foo',
543+
color: false,
544+
isEnabled: true,
545+
stream: transformTTY,
546+
spinner: {
547+
frames: ['-']
548+
}
549+
});
550+
551+
currentOra.render();
552+
553+
spinner.render();
554+
t.is(clearedLines(), 1); // Cleared 'foo'
555+
t.is(cursorAtRow(), 0);
556+
557+
currentOra.text = 'foo\n\nbar';
558+
currentOra.render();
559+
560+
spinner.text = 'foo\n\nbar';
561+
spinner.render();
562+
t.is(clearedLines(), 3); // Cleared 'foo\n\nbar'
563+
t.is(cursorAtRow(), -2);
564+
565+
currentOra.clear();
566+
currentOra.text = '0'.repeat(currentOra.stream.columns + 10);
567+
currentOra.render();
568+
currentOra.render();
569+
570+
spinner.clear();
571+
spinner.text = '0'.repeat(spinner.stream.columns + 10);
572+
spinner.render();
573+
spinner.render();
574+
t.is(clearedLines(), 2);
575+
t.is(cursorAtRow(), -1);
576+
577+
currentOra.clear();
578+
currentOra.text = '🦄'.repeat(currentOra.stream.columns + 10);
579+
currentOra.render();
580+
currentOra.render();
581+
582+
spinner.clear();
583+
spinner.text = '🦄'.repeat(spinner.stream.columns + 10);
584+
spinner.render();
585+
spinner.render();
586+
t.is(clearedLines(), 3);
587+
t.is(cursorAtRow(), -2);
588+
589+
currentOra.clear();
590+
currentOra.text = '🦄'.repeat(currentOra.stream.columns - 2) + '\nfoo';
591+
currentOra.render();
592+
currentOra.render();
593+
594+
spinner.clear();
595+
spinner.text = '🦄'.repeat(spinner.stream.columns - 2) + '\nfoo';
596+
spinner.render();
597+
spinner.render();
598+
t.is(clearedLines(), 3);
599+
t.is(cursorAtRow(), -2);
600+
601+
currentOra.clear();
602+
currentOra.prefixText = 'foo\n';
603+
currentOra.text = '\nbar';
604+
currentOra.render();
605+
currentOra.render();
606+
607+
spinner.clear();
608+
spinner.prefixText = 'foo\n';
609+
spinner.text = '\nbar';
610+
spinner.render();
611+
spinner.render();
612+
t.is(clearedLines(), 3); // Cleared 'foo\n\nbar'
613+
t.is(cursorAtRow(), -2);
614+
615+
const [sequenceString, clearedSequenceString] = transformTTY.getSequenceStrings();
616+
const [frames, clearedFrames] = transformTTY.getFrames();
617+
618+
t.is(sequenceString, 'foo\n - \nbar');
619+
t.is(sequenceString, clearedSequenceString);
620+
621+
t.deepEqual(clearedFrames, [
622+
'- foo',
623+
'- foo\n\nbar',
624+
'- 00000000000000000000000000000000000000\n000000000000',
625+
'- 00000000000000000000000000000000000000\n000000000000',
626+
'- 🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄\n' +
627+
'🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄\n' +
628+
'🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄',
629+
'- 🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄\n' +
630+
'🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄\n' +
631+
'🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄',
632+
'- 🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄\n' +
633+
'🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄\n' +
634+
'foo',
635+
'- 🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄\n' +
636+
'🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄\n' +
637+
'foo',
638+
'foo\n - \nbar',
639+
'foo\n - \nbar'
640+
]);
641+
642+
t.deepEqual(frames, clearedFrames);
643+
644+
const currentClearString = currentClearTTY.toString();
645+
t.is(currentClearString, 'foo\n - \nbar');
646+
647+
const currentFrames = currentClearTTY.getFrames();
648+
t.deepEqual(frames, currentFrames);
649+
});
650+
651+
test('new clear method, stress test', t => {
652+
const rando = (min, max) => {
653+
min = Math.ceil(min);
654+
max = Math.floor(max);
655+
return Math.floor(Math.random() * ((max - min) + min));
656+
};
657+
658+
const rAnDoMaNiMaLs = (min, max) => {
659+
const length = rando(min, max);
660+
let result = '';
661+
const THEAMINALS = ['🐯', '🦁', '🐮', '🐷', '🐽', '🐸', '🐙', '🐵', '🐦', '🐧', '🐔', '🐒', '🙉', '🙈', '🐣', '🐥', '🐺', '🐗', '🐴', '🦄', '🐝', '🐛', ...Array.from({length: 5}).fill('\n')];
662+
663+
for (let i = 0; i < length; i++) {
664+
result += THEAMINALS[Math.floor(Math.random() * THEAMINALS.length)];
665+
}
666+
667+
return result;
668+
};
669+
670+
const randos = () => {
671+
return rAnDoMaNiMaLs(rando(5, 15), rando(25, 50));
672+
};
673+
674+
const randomize = (s1, s2) => {
675+
const spnr = cliSpinners.random;
676+
const txt = randos();
677+
const indent = rando(0, 15);
678+
679+
s1.spinner = spnr;
680+
s2.spinner = spnr;
681+
s1.text = txt;
682+
s2.text = txt;
683+
s1.indent = indent;
684+
s2.indent = indent;
685+
};
686+
687+
const transformTTY = new TransformTTY({crlf: true});
688+
transformTTY.addSequencer();
689+
transformTTY.addSequencer(null, true);
690+
691+
const currentClearTTY = new TransformTTY({crlf: true});
692+
currentClearTTY.addSequencer();
693+
694+
const currentOra = currentClearMethod(currentClearTTY);
695+
696+
const spinner = new Ora({
697+
color: false,
698+
isEnabled: true,
699+
stream: transformTTY
700+
});
701+
702+
randomize(spinner, currentOra);
703+
704+
for (let x = 0; x < 100; x++) {
705+
if (x % 10 === 0) {
706+
randomize(spinner, currentOra);
707+
}
708+
709+
if (x % 5 === 0) {
710+
const indent = rando(0, 25);
711+
spinner.indent = indent;
712+
currentOra.indent = indent;
713+
}
714+
715+
if (x % 15 === 0) {
716+
let {text} = spinner;
717+
const loops = rando(1, 10);
718+
719+
for (let x = 0; x < loops; x++) {
720+
const pos = Math.floor(Math.random() * text.length);
721+
text = text.slice(0, pos) + '\n' + text.slice(pos + 1);
722+
}
723+
724+
spinner.text = text;
725+
currentOra.text = text;
726+
}
727+
728+
spinner.render();
729+
currentOra.render();
730+
}
731+
732+
spinner.succeed('🙉');
733+
currentOra.succeed('🙉');
734+
735+
const currentFrames = currentClearTTY.getFrames();
736+
const [frames, clearedFrames] = transformTTY.getFrames();
737+
738+
t.deepEqual(frames, clearedFrames);
739+
740+
t.deepEqual(frames.slice(0, currentFrames.length), currentFrames);
741+
742+
// Console.log(frames);
743+
// console.log(clearFrames);
744+
});
745+
/*
746+
Example output:
747+
748+
[
749+
' ▏ \n',
750+
' ▎ \n',
751+
' ▍ \n',
752+
' ▌ \n',
753+
' ▋ \n',
754+
' ▊ \n',
755+
' ▉ \n',
756+
' ▊ \n',
757+
' ▋ \n',
758+
' ▌ \n',
759+
' d ',
760+
' q ',
761+
' p ',
762+
' b ',
763+
' d ',
764+
' q \n',
765+
' p \n',
766+
' b \n',
767+
' d \n',
768+
' q \n',
769+
' ◢ 🐗🐧🐥🐺🐵\n\n',
770+
' ◣ 🐗🐧🐥🐺🐵\n\n',
771+
' ◤ 🐗🐧🐥🐺🐵\n\n',
772+
' ◥ 🐗🐧🐥🐺🐵\n\n',
773+
' ◢ 🐗🐧🐥🐺🐵\n\n',
774+
' ◣ 🐗🐧🐥🐺🐵\n\n',
775+
' ◤ 🐗🐧🐥🐺🐵\n\n',
776+
' ◥ 🐗🐧🐥🐺🐵\n\n',
777+
' ◢ 🐗🐧🐥🐺🐵\n\n',
778+
' ◣ 🐗🐧🐥🐺🐵\n\n',
779+
' ⠋ \n�🐮�\n\n�\n',
780+
' ⠙ \n�🐮�\n\n�\n',
781+
' ⠹ \n�🐮�\n\n�\n',
782+
' ⠸ \n�🐮�\n\n�\n',
783+
' ⠼ \n�🐮�\n\n�\n',
784+
' ⠴ \n�🐮�\n\n�\n',
785+
' ⠦ \n�🐮�\n\n�\n',
786+
' ⠧ \n�🐮�\n\n�\n',
787+
' ⠇ \n�🐮�\n\n�\n',
788+
' ⠏ \n�🐮�\n\n�\n',
789+
' □ ',
790+
' ■ ',
791+
' □ ',
792+
' ■ ',
793+
' □ ',
794+
' ■ \n',
795+
' □ \n',
796+
' ■ \n',
797+
' □ \n',
798+
' ■ \n',
799+
' . 🐗',
800+
' .. 🐗',
801+
' ... 🐗',
802+
' 🐗',
803+
' . 🐗',
804+
' .. 🐗',
805+
' ... 🐗',
806+
' 🐗',
807+
' . 🐗',
808+
' .. 🐗',
809+
' ▖ 🐔\n🐸\n',
810+
' ▘ 🐔\n🐸\n',
811+
' ▝ 🐔\n🐸\n',
812+
' ▗ 🐔\n🐸\n',
813+
' ▖ 🐔\n🐸\n',
814+
' ▘ 🐔\n🐸\n',
815+
' ▝ 🐔\n🐸\n',
816+
' ▗ 🐔\n🐸\n',
817+
' ▖ 🐔\n🐸\n',
818+
' ▘ 🐔\n🐸\n',
819+
' ( ● ) 🐔🐗',
820+
' ( ● ) 🐔🐗',
821+
' ( ● ) 🐔🐗',
822+
' ( ● ) 🐔🐗',
823+
' ( ●) 🐔🐗',
824+
'( ● ) �\n\n�',
825+
'( ● ) �\n\n�',
826+
'( ● ) �\n\n�',
827+
'( ● ) �\n\n�',
828+
'(● ) �\n\n�',
829+
' ⧇ 🐷🐛🐔🦁🐷🙉',
830+
' ⧆ 🐷🐛🐔🦁🐷🙉',
831+
' ⧇ 🐷🐛🐔🦁🐷🙉',
832+
' ⧆ 🐷🐛🐔🦁🐷🙉',
833+
' ⧇ 🐷🐛🐔🦁🐷🙉',
834+
' ⧆ 🐷🐛🐔🦁🐷🙉',
835+
' ⧇ 🐷🐛🐔🦁🐷🙉',
836+
' ⧆ 🐷🐛🐔🦁🐷🙉',
837+
' ⧇ 🐷🐛🐔🦁🐷🙉',
838+
' ⧆ 🐷🐛🐔🦁🐷🙉',
839+
' _ 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
840+
' _ 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
841+
' _ 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
842+
' - 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
843+
' ` 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
844+
' ` 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
845+
" ' 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n",
846+
' ´ 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
847+
' - 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
848+
' _ 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
849+
... 1 more item
850+
]
851+
[
852+
' ▏ \n',
853+
' ▎ \n',
854+
' ▍ \n',
855+
' ▌ \n',
856+
' ▋ \n',
857+
' ▊ \n',
858+
' ▉ \n',
859+
' ▊ \n',
860+
' ▋ \n',
861+
' ▌ \n',
862+
' d ',
863+
' q ',
864+
' p ',
865+
' b ',
866+
' d ',
867+
' q \n',
868+
' p \n',
869+
' b \n',
870+
' d \n',
871+
' q \n',
872+
' ◢ 🐗🐧🐥🐺🐵\n\n',
873+
' ◣ 🐗🐧🐥🐺🐵\n\n',
874+
' ◤ 🐗🐧🐥🐺🐵\n\n',
875+
' ◥ 🐗🐧🐥🐺🐵\n\n',
876+
' ◢ 🐗🐧🐥🐺🐵\n\n',
877+
' ◣ 🐗🐧🐥🐺🐵\n\n',
878+
' ◤ 🐗🐧🐥🐺🐵\n\n',
879+
' ◥ 🐗🐧🐥🐺🐵\n\n',
880+
' ◢ 🐗🐧🐥🐺🐵\n\n',
881+
' ◣ 🐗🐧🐥🐺🐵\n\n',
882+
' ⠋ \n�🐮�\n\n�\n',
883+
' ⠙ \n�🐮�\n\n�\n',
884+
' ⠹ \n�🐮�\n\n�\n',
885+
' ⠸ \n�🐮�\n\n�\n',
886+
' ⠼ \n�🐮�\n\n�\n',
887+
' ⠴ \n�🐮�\n\n�\n',
888+
' ⠦ \n�🐮�\n\n�\n',
889+
' ⠧ \n�🐮�\n\n�\n',
890+
' ⠇ \n�🐮�\n\n�\n',
891+
' ⠏ \n�🐮�\n\n�\n',
892+
' □ ',
893+
' ■ ',
894+
' □ ',
895+
' ■ ',
896+
' □ ',
897+
' ■ \n',
898+
' □ \n',
899+
' ■ \n',
900+
' □ \n',
901+
' ■ \n',
902+
' . 🐗',
903+
' .. 🐗',
904+
' ... 🐗',
905+
' 🐗',
906+
' . 🐗',
907+
' .. 🐗',
908+
' ... 🐗',
909+
' 🐗',
910+
' . 🐗',
911+
' .. 🐗',
912+
' ▖ 🐔\n🐸\n',
913+
' ▘ 🐔\n🐸\n',
914+
' ▝ 🐔\n🐸\n',
915+
' ▗ 🐔\n🐸\n',
916+
' ▖ 🐔\n🐸\n',
917+
' ▘ 🐔\n🐸\n',
918+
' ▝ 🐔\n🐸\n',
919+
' ▗ 🐔\n🐸\n',
920+
' ▖ 🐔\n🐸\n',
921+
' ▘ 🐔\n🐸\n',
922+
' ( ● ) 🐔🐗',
923+
' ( ● ) 🐔🐗',
924+
' ( ● ) 🐔🐗',
925+
' ( ● ) 🐔🐗',
926+
' ( ●) 🐔🐗',
927+
'( ● ) �\n\n�',
928+
'( ● ) �\n\n�',
929+
'( ● ) �\n\n�',
930+
'( ● ) �\n\n�',
931+
'(● ) �\n\n�',
932+
' ⧇ 🐷🐛🐔🦁🐷🙉',
933+
' ⧆ 🐷🐛🐔🦁🐷🙉',
934+
' ⧇ 🐷🐛🐔🦁🐷🙉',
935+
' ⧆ 🐷🐛🐔🦁🐷🙉',
936+
' ⧇ 🐷🐛🐔🦁🐷🙉',
937+
' ⧆ 🐷🐛🐔🦁🐷🙉',
938+
' ⧇ 🐷🐛🐔🦁🐷🙉',
939+
' ⧆ 🐷🐛🐔🦁🐷🙉',
940+
' ⧇ 🐷🐛🐔🦁🐷🙉',
941+
' ⧆ 🐷🐛🐔🦁🐷🙉',
942+
' _ 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
943+
' _ 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
944+
' _ 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
945+
' - 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
946+
' ` 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
947+
' ` 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
948+
" ' 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n",
949+
' ´ 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
950+
' - 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
951+
' _ 🐽🦄🐣\n🐣🐧🐔🦁🐦�\n',
952+
... 1 more item
953+
]
954+
*/

0 commit comments

Comments
 (0)
Please sign in to comment.