11import GraphemeSplitter from 'grapheme-splitter'
2- import { isEqual } from 'lodash'
32import stripAnsi from 'strip-ansi'
43
54import {
@@ -24,6 +23,8 @@ export type Grapheme = {
2423 textStyles ?: Modifier [ ]
2524}
2625
26+ export type GraphemeImage = Grapheme [ ] [ ]
27+
2728const splitter = new GraphemeSplitter ( )
2829
2930export function toGraphemeString ( grapheme : string ) : $GraphemeString {
@@ -38,106 +39,148 @@ export function toGraphemeString(grapheme: string): $GraphemeString {
3839 return first as $GraphemeString
3940}
4041
41- function equalStyles ( a : Grapheme , b : Grapheme ) : boolean {
42- type GraphemeStyle = Omit < Grapheme , 'grapheme' > &
43- Partial < Pick < Grapheme , 'grapheme' > >
44- const aStyles : GraphemeStyle = { textStyles : [ ] , ...a }
45- delete aStyles . grapheme
46- const bStyles : GraphemeStyle = { textStyles : [ ] , ...b }
47- delete bStyles . grapheme
48- return isEqual ( aStyles , bStyles )
42+ type GraphemeColor =
43+ | { type : 'color' ; color : Color | BackgroundColor }
44+ | { type : 'rgb' ; rgb : RGB }
45+
46+ function colorsEqual (
47+ a : GraphemeColor | undefined ,
48+ b : GraphemeColor | undefined ,
49+ ) : boolean {
50+ if ( ! a && ! b ) {
51+ return true
52+ }
53+
54+ if ( ! a || ! b ) {
55+ return false
56+ }
57+
58+ if ( a . type !== b . type ) {
59+ return false
60+ }
61+
62+ if ( a . type === 'color' && b . type === 'color' ) {
63+ return a . color === b . color
64+ }
65+
66+ if ( a . type === 'rgb' && b . type === 'rgb' ) {
67+ return (
68+ a . rgb [ 0 ] === b . rgb [ 0 ] &&
69+ a . rgb [ 1 ] === b . rgb [ 1 ] &&
70+ a . rgb [ 2 ] === b . rgb [ 2 ]
71+ )
72+ }
73+
74+ return false
75+ }
76+
77+ function stylesEqual ( a : Grapheme , b : Grapheme ) : boolean {
78+ if ( ! colorsEqual ( a . textColor , b . textColor ) ) {
79+ return false
80+ }
81+
82+ if ( ! colorsEqual ( a . backgroundColor , b . backgroundColor ) ) {
83+ return false
84+ }
85+
86+ const aStyles = a . textStyles ?? [ ]
87+ const bStyles = b . textStyles ?? [ ]
88+ if ( aStyles . length !== bStyles . length ) {
89+ return false
90+ }
91+ for ( let i = 0 ; i < aStyles . length ; i ++ ) {
92+ if ( aStyles [ i ] !== bStyles [ i ] ) {
93+ return false
94+ }
95+ }
96+
97+ return true
4998}
5099
51- function graphemeCommands ( grapheme : Grapheme ) : string [ ] {
52- const commands : string [ ] = [ ]
100+ function graphemeCommands ( grapheme : Grapheme ) : string {
101+ let command = ''
53102 if ( grapheme . textColor ) {
54- commands . push (
55- ansiCode (
56- grapheme . textColor . type === 'color'
57- ? {
58- type : 'style' ,
59- style : grapheme . textColor . color ,
60- }
61- : {
62- type : 'text' ,
63- rgb : grapheme . textColor . rgb ,
64- } ,
65- ) ,
103+ command += ansiCode (
104+ grapheme . textColor . type === 'color'
105+ ? {
106+ type : 'style' ,
107+ style : grapheme . textColor . color ,
108+ }
109+ : {
110+ type : 'text' ,
111+ rgb : grapheme . textColor . rgb ,
112+ } ,
66113 )
67114 }
68115 if ( grapheme . backgroundColor ) {
69- commands . push (
70- ansiCode (
71- grapheme . backgroundColor . type === 'color'
72- ? {
73- type : 'style' ,
74- style : grapheme . backgroundColor . color ,
75- }
76- : {
77- type : 'text' ,
78- rgb : grapheme . backgroundColor . rgb ,
79- } ,
80- ) ,
116+ command += ansiCode (
117+ grapheme . backgroundColor . type === 'color'
118+ ? {
119+ type : 'style' ,
120+ style : grapheme . backgroundColor . color ,
121+ }
122+ : {
123+ type : 'text' ,
124+ rgb : grapheme . backgroundColor . rgb ,
125+ } ,
81126 )
82127 }
83128
84129 if ( grapheme . textStyles ) {
85130 for ( const style of grapheme . textStyles ) {
86- commands . push ( ansiCode ( { type : 'style' , style } ) )
131+ command += ansiCode ( { type : 'style' , style } )
87132 }
88133 }
89134
90- commands . push ( grapheme . grapheme )
135+ command += grapheme . grapheme
91136
92- return commands
137+ return command
93138}
94139
95140function graphemeDiffCommands (
96141 prevGrapheme : Grapheme | null ,
97142 newGrapheme : Grapheme ,
98- ) : string [ ] {
143+ ) : string {
99144 if ( ! prevGrapheme ) {
100145 return graphemeCommands ( newGrapheme )
101146 }
102147
103- if ( equalStyles ( prevGrapheme , newGrapheme ) ) {
104- return [ newGrapheme . grapheme ]
148+ if ( stylesEqual ( prevGrapheme , newGrapheme ) ) {
149+ return newGrapheme . grapheme
105150 }
106151
107- return [
108- ... ansiCode ( { type : 'style' , style : STYLE . RESET } ) ,
109- ... graphemeCommands ( newGrapheme ) ,
110- ]
152+ return (
153+ ansiCode ( { type : 'style' , style : STYLE . RESET } ) +
154+ graphemeCommands ( newGrapheme )
155+ )
111156}
112157
113- export type GraphemeImage = Grapheme [ ] [ ]
114-
115- export function fullImageCommands ( image : GraphemeImage ) : string [ ] {
116- const commands : string [ ] = [ moveCursor ( 0 , 0 ) ]
158+ export function fullImageCommands ( image : GraphemeImage ) : string {
159+ let command = moveCursor ( 0 , 0 )
117160
118161 let lastGrapheme : Grapheme | null = null
119162 for ( const row of image ) {
120163 for ( const grapheme of row ) {
121- commands . push ( ... graphemeDiffCommands ( lastGrapheme , grapheme ) )
164+ command += graphemeDiffCommands ( lastGrapheme , grapheme )
122165 lastGrapheme = grapheme
123166 }
124167 }
125168
126- return commands
169+ return command
127170}
128171
129172export function diffImageCommands (
130173 oldImage : GraphemeImage ,
131174 newImage : GraphemeImage ,
132- ) : string [ ] {
175+ ) : string {
133176 if ( oldImage . length !== newImage . length ) {
134177 return fullImageCommands ( newImage )
135178 }
136179 if ( oldImage [ 0 ] . length !== newImage [ 0 ] . length ) {
137180 return fullImageCommands ( newImage )
138181 }
139182
140- const commands : string [ ] = [ ]
183+ let command = ''
141184 let prevWrittenGrapheme : Grapheme | null = null
142185 let skipped = true
143186 for ( const [ r , newRow ] of newImage . entries ( ) ) {
@@ -146,20 +189,20 @@ export function diffImageCommands(
146189 const prevFrameGrapheme = oldRow [ c ]
147190 if (
148191 newGrapheme . grapheme === prevFrameGrapheme . grapheme &&
149- equalStyles ( newGrapheme , prevFrameGrapheme )
192+ stylesEqual ( newGrapheme , prevFrameGrapheme )
150193 ) {
151194 skipped = true
152195 continue
153196 }
154197
155198 if ( skipped ) {
156- commands . push ( moveCursor ( r , c ) )
199+ command += moveCursor ( r , c )
157200 skipped = false
158201 }
159202
160- commands . push ( ... graphemeDiffCommands ( prevWrittenGrapheme , newGrapheme ) )
203+ command += graphemeDiffCommands ( prevWrittenGrapheme , newGrapheme )
161204 prevWrittenGrapheme = newGrapheme
162205 }
163206 }
164- return commands
207+ return command
165208}
0 commit comments