@@ -185,19 +185,96 @@ export function renderFunctionDetail(context: DetailContext, funcIndex: number):
185185 const block = document . createElement ( 'div' ) ;
186186 block . className = 'detail-code' ;
187187 const lines = decompiledCode . split ( '\n' ) ;
188+
189+ // Match every opening { to its closing }
190+ const bracePairs = new Map < number , number > ( ) ;
191+ const braceStack : number [ ] = [ ] ;
192+ for ( let lineIdx = 0 ; lineIdx < lines . length ; lineIdx ++ ) {
193+ const stripped = lines [ lineIdx ] . trim ( ) ;
194+ if ( stripped . startsWith ( '}' ) ) {
195+ if ( braceStack . length > 0 ) {
196+ bracePairs . set ( braceStack . pop ( ) ! , lineIdx ) ;
197+ }
198+ }
199+ if ( stripped . endsWith ( '{' ) ) {
200+ braceStack . push ( lineIdx ) ;
201+ }
202+ }
203+
188204 const gutterWidth = String ( lines . length ) . length ;
205+
206+ const lineElements : HTMLElement [ ] = [ ] ;
207+ const foldCollapsed = new Set < number > ( ) ;
208+
189209 for ( let lineIdx = 0 ; lineIdx < lines . length ; lineIdx ++ ) {
190210 const lineEl = document . createElement ( 'div' ) ;
191211 lineEl . className = 'code-line' ;
212+
213+ const foldSlot = document . createElement ( 'span' ) ;
214+ foldSlot . className = 'code-fold-slot' ;
215+
216+ const closingLine = bracePairs . get ( lineIdx ) ;
217+ const isFoldable = closingLine !== undefined && closingLine - lineIdx > 1 ;
218+
219+ let ellipsis : HTMLSpanElement | null = null ;
220+ if ( isFoldable ) {
221+ foldSlot . classList . add ( 'foldable' ) ;
222+ foldSlot . textContent = '\u25BE' ;
223+
224+ const openIdx = lineIdx ;
225+ const closeIdx = closingLine ;
226+ const hiddenCount = closeIdx - openIdx - 1 ;
227+
228+ ellipsis = document . createElement ( 'span' ) ;
229+ ellipsis . className = 'code-fold-ellipsis' ;
230+ ellipsis . textContent = `\u2026 ${ hiddenCount } lines` ;
231+ ellipsis . style . display = 'none' ;
232+ ellipsis . addEventListener ( 'click' , ( ) => {
233+ foldSlot . click ( ) ;
234+ } ) ;
235+
236+ foldSlot . addEventListener ( 'click' , ( ) => {
237+ const wasCollapsed = foldCollapsed . has ( openIdx ) ;
238+ if ( wasCollapsed ) {
239+ foldCollapsed . delete ( openIdx ) ;
240+ } else {
241+ foldCollapsed . add ( openIdx ) ;
242+ }
243+ const isNowCollapsed = foldCollapsed . has ( openIdx ) ;
244+ ellipsis ! . style . display = isNowCollapsed ? '' : 'none' ;
245+ foldSlot . textContent = isNowCollapsed ? '\u25B8' : '\u25BE' ;
246+ for ( let targetLine = openIdx + 1 ; targetLine < closeIdx ; targetLine ++ ) {
247+ let shouldHide = false ;
248+ for ( const collapsedOpen of foldCollapsed ) {
249+ const collapsedClose = bracePairs . get ( collapsedOpen ) ;
250+ if ( collapsedClose !== undefined && targetLine > collapsedOpen && targetLine < collapsedClose ) {
251+ shouldHide = true ;
252+ break ;
253+ }
254+ }
255+ lineElements [ targetLine ] . style . display = shouldHide ? 'none' : '' ;
256+ }
257+ } ) ;
258+ }
259+
192260 const gutter = document . createElement ( 'span' ) ;
193261 gutter . className = 'code-line-number' ;
194- gutter . textContent = String ( lineIdx + 1 ) . padStart ( gutterWidth , ' ' ) ;
262+ gutter . textContent = String ( lineIdx + 1 ) . padStart ( gutterWidth ) ;
195263 lineEl . appendChild ( gutter ) ;
196- const content = document . createElement ( 'span' ) ;
197- content . className = 'code-line-content' ;
198- renderHighlightedC ( content , lines [ lineIdx ] , highlightOptions ) ;
199- lineEl . appendChild ( content ) ;
264+
265+ lineEl . appendChild ( foldSlot ) ;
266+
267+ const lineContent = document . createElement ( 'span' ) ;
268+ lineContent . className = 'code-line-content' ;
269+ renderHighlightedC ( lineContent , lines [ lineIdx ] , highlightOptions ) ;
270+ lineEl . appendChild ( lineContent ) ;
271+
272+ if ( ellipsis ) {
273+ lineEl . appendChild ( ellipsis ) ;
274+ }
275+
200276 block . appendChild ( lineEl ) ;
277+ lineElements . push ( lineEl ) ;
201278 }
202279 const wrapper = document . createElement ( 'div' ) ;
203280 wrapper . className = 'detail-block-wrapper' ;
0 commit comments