@@ -59,106 +59,122 @@ function shouldIncludeSgrAfterEnd(token, activeStyles) {
5959 return hasClosingEffect && ! hasStartFragment ;
6060}
6161
62- function applySgrToken ( { token, isPastEnd, activeStyles, returnValue, include, activeHyperlink, position} ) {
63- if ( isPastEnd && ! shouldIncludeSgrAfterEnd ( token , activeStyles ) ) {
64- return {
65- activeStyles,
66- activeHyperlink,
67- position,
68- returnValue,
69- include,
70- } ;
62+ function hasSgrStartFragment ( token ) {
63+ return token . fragments . some ( fragment => fragment . type === 'start' ) ;
64+ }
65+
66+ function clearPendingHyperlink ( parameters ) {
67+ if (
68+ parameters . activeHyperlink
69+ && ! parameters . activeHyperlinkHasVisibleText
70+ && parameters . activeHyperlinkOutputIndex !== undefined
71+ ) {
72+ parameters . returnValue = parameters . returnValue . slice ( 0 , parameters . activeHyperlinkOutputIndex ) ;
7173 }
7274
73- activeStyles = applySgrFragments ( activeStyles , token . fragments ) ;
74- if ( include ) {
75- returnValue += token . code ;
75+ parameters . activeHyperlink = undefined ;
76+ parameters . activeHyperlinkHasVisibleText = false ;
77+ parameters . activeHyperlinkOutputIndex = undefined ;
78+ }
79+
80+ function applySgrToken ( parameters ) {
81+ if (
82+ parameters . isPastEnd
83+ && ! shouldIncludeSgrAfterEnd ( parameters . token , parameters . activeStyles )
84+ ) {
85+ return parameters ;
7686 }
7787
78- return {
79- activeStyles,
80- activeHyperlink,
81- position,
82- returnValue,
83- include,
84- } ;
88+ if (
89+ parameters . include
90+ && hasSgrStartFragment ( parameters . token )
91+ && parameters . pendingSgrOutputIndex === undefined
92+ ) {
93+ parameters . pendingSgrOutputIndex = parameters . returnValue . length ;
94+ parameters . pendingSgrActiveStyles = new Map ( parameters . activeStyles ) ;
95+ }
96+
97+ parameters . activeStyles = applySgrFragments ( parameters . activeStyles , parameters . token . fragments ) ;
98+ if ( parameters . include ) {
99+ parameters . returnValue += parameters . token . code ;
100+ }
101+
102+ return parameters ;
85103}
86104
87- function applyHyperlinkToken ( { token , isPastEnd , activeStyles , activeHyperlink , position , returnValue , include } ) {
105+ function applyHyperlinkToken ( parameters ) {
88106 if (
89- isPastEnd
107+ parameters . isPastEnd
90108 && (
91- token . action !== 'close'
92- || ! activeHyperlink
109+ parameters . token . action !== 'close'
110+ || ! parameters . activeHyperlink
93111 )
94112 ) {
95- return {
96- activeStyles,
97- activeHyperlink,
98- position,
99- returnValue,
100- include,
101- } ;
113+ return parameters ;
102114 }
103115
104- if ( token . action === 'open' ) {
105- activeHyperlink = token ;
106- } else if ( token . action === 'close' ) {
107- activeHyperlink = undefined ;
116+ if ( parameters . token . action === 'open' ) {
117+ parameters . activeHyperlink = parameters . token ;
118+ parameters . activeHyperlinkHasVisibleText = false ;
119+ parameters . activeHyperlinkOutputIndex = undefined ;
120+ if ( parameters . include ) {
121+ parameters . activeHyperlinkOutputIndex = parameters . returnValue . length ;
122+ }
123+ } else if ( parameters . token . action === 'close' ) {
124+ if (
125+ parameters . include
126+ && parameters . activeHyperlink
127+ && ! parameters . activeHyperlinkHasVisibleText
128+ ) {
129+ clearPendingHyperlink ( parameters ) ;
130+ return parameters ;
131+ }
132+
133+ parameters . activeHyperlink = undefined ;
134+ parameters . activeHyperlinkHasVisibleText = false ;
135+ parameters . activeHyperlinkOutputIndex = undefined ;
108136 }
109137
110- if ( include ) {
111- returnValue += token . code ;
138+ if ( parameters . include ) {
139+ parameters . returnValue += parameters . token . code ;
112140 }
113141
114- return {
115- activeStyles,
116- activeHyperlink,
117- position,
118- returnValue,
119- include,
120- } ;
142+ return parameters ;
121143}
122144
123- function applyControlToken ( { token , isPastEnd , activeStyles , activeHyperlink , position , returnValue , include } ) {
124- if ( ! isPastEnd && include ) {
125- returnValue += token . code ;
145+ function applyControlToken ( parameters ) {
146+ if ( ! parameters . isPastEnd && parameters . include ) {
147+ parameters . returnValue += parameters . token . code ;
126148 }
127149
128- return {
129- activeStyles,
130- activeHyperlink,
131- position,
132- returnValue,
133- include,
134- } ;
150+ return parameters ;
135151}
136152
137- function applyCharacterToken ( { token , start , activeStyles , activeHyperlink , position , returnValue , include } ) {
153+ function applyCharacterToken ( parameters ) {
138154 if (
139- ! include
140- && position >= start
141- && ! token . isGraphemeContinuation
155+ ! parameters . include
156+ && parameters . position >= parameters . start
157+ && ! parameters . token . isGraphemeContinuation
142158 ) {
143- include = true ;
144- returnValue = [ ...activeStyles . values ( ) ] . join ( '' ) ;
145- if ( activeHyperlink ) {
146- returnValue += activeHyperlink . code ;
159+ parameters . include = true ;
160+ parameters . returnValue = [ ...parameters . activeStyles . values ( ) ] . join ( '' ) ;
161+ if ( parameters . activeHyperlink ) {
162+ parameters . activeHyperlinkOutputIndex = parameters . returnValue . length ;
163+ parameters . returnValue += parameters . activeHyperlink . code ;
147164 }
148165 }
149166
150- if ( include ) {
151- returnValue += token . value ;
167+ if ( parameters . include ) {
168+ parameters . returnValue += parameters . token . value ;
169+ parameters . pendingSgrOutputIndex = undefined ;
170+ parameters . pendingSgrActiveStyles = undefined ;
171+ if ( parameters . activeHyperlink ) {
172+ parameters . activeHyperlinkHasVisibleText = true ;
173+ }
152174 }
153175
154- position += token . visibleWidth ;
155- return {
156- activeStyles,
157- activeHyperlink,
158- position,
159- returnValue,
160- include,
161- } ;
176+ parameters . position += parameters . token . visibleWidth ;
177+ return parameters ;
162178}
163179
164180const tokenHandlers = {
@@ -171,21 +187,7 @@ const tokenHandlers = {
171187function applyToken ( parameters ) {
172188 const tokenHandler = tokenHandlers [ parameters . token . type ] ;
173189 if ( ! tokenHandler ) {
174- const {
175- activeStyles,
176- activeHyperlink,
177- position,
178- returnValue,
179- include,
180- } = parameters ;
181-
182- return {
183- activeStyles,
184- activeHyperlink,
185- position,
186- returnValue,
187- include,
188- } ;
190+ return parameters ;
189191 }
190192
191193 return tokenHandler ( parameters ) ;
@@ -206,17 +208,35 @@ function createHasContinuationAheadMap(tokens) {
206208 return hasContinuationAhead ;
207209}
208210
211+ function isPastEndBoundary ( token , position , end ) {
212+ if ( end === undefined ) {
213+ return false ;
214+ }
215+
216+ if ( position >= end ) {
217+ return true ;
218+ }
219+
220+ return token . type === 'character'
221+ && ! token . isGraphemeContinuation
222+ && position + token . visibleWidth > end ;
223+ }
224+
209225export default function sliceAnsi ( string , start , end ) {
210226 const tokens = tokenizeAnsi ( string , { endCharacter : end } ) ;
211227 const hasContinuationAhead = createHasContinuationAheadMap ( tokens ) ;
212228 let activeStyles = new Map ( ) ;
213229 let activeHyperlink ;
230+ let activeHyperlinkHasVisibleText = false ;
231+ let activeHyperlinkOutputIndex ;
232+ let pendingSgrOutputIndex ;
233+ let pendingSgrActiveStyles ;
214234 let position = 0 ;
215235 let returnValue = '' ;
216236 let include = false ;
217237
218238 for ( const [ tokenIndex , token ] of tokens . entries ( ) ) {
219- let isPastEnd = end !== undefined && position >= end ;
239+ let isPastEnd = isPastEndBoundary ( token , position , end ) ;
220240 if (
221241 isPastEnd
222242 && token . type !== 'character'
@@ -230,15 +250,42 @@ export default function sliceAnsi(string, start, end) {
230250 && token . type === 'character'
231251 && ! token . isGraphemeContinuation
232252 ) {
253+ if ( activeHyperlink && ! activeHyperlinkHasVisibleText ) {
254+ const hyperlinkState = {
255+ activeHyperlink,
256+ activeHyperlinkHasVisibleText,
257+ activeHyperlinkOutputIndex,
258+ returnValue,
259+ } ;
260+ clearPendingHyperlink ( hyperlinkState ) ;
261+ ( {
262+ activeHyperlink,
263+ activeHyperlinkHasVisibleText,
264+ activeHyperlinkOutputIndex,
265+ returnValue,
266+ } = hyperlinkState ) ;
267+ }
268+
269+ if ( pendingSgrOutputIndex !== undefined ) {
270+ returnValue = returnValue . slice ( 0 , pendingSgrOutputIndex ) ;
271+ activeStyles = pendingSgrActiveStyles ;
272+ pendingSgrOutputIndex = undefined ;
273+ pendingSgrActiveStyles = undefined ;
274+ }
275+
233276 break ;
234277 }
235278
236- ( { activeStyles, activeHyperlink, position, returnValue, include} = applyToken ( {
279+ ( { activeStyles, activeHyperlink, activeHyperlinkHasVisibleText , activeHyperlinkOutputIndex , pendingSgrOutputIndex , pendingSgrActiveStyles , position, returnValue, include} = applyToken ( {
237280 token,
238281 isPastEnd,
239282 start,
240283 activeStyles,
241284 activeHyperlink,
285+ activeHyperlinkHasVisibleText,
286+ activeHyperlinkOutputIndex,
287+ pendingSgrOutputIndex,
288+ pendingSgrActiveStyles,
242289 position,
243290 returnValue,
244291 include,
0 commit comments