11import {
22 codeFolding ,
33 ensureSyntaxTree ,
4- foldable ,
54 foldEffect ,
65 foldedRanges ,
6+ foldNodeProp ,
7+ foldService ,
78 foldState ,
9+ syntaxTree ,
810 unfoldEffect ,
911} from "@codemirror/language" ;
1012import { StateEffect } from "@codemirror/state" ;
1113
1214const FULL_PARSE_BUDGET_MS = 100 ;
15+ const INCOMPLETE_PARSE_MARGIN = 50 ;
16+
17+ function findServiceFold ( state , line ) {
18+ for ( const service of state . facet ( foldService ) ) {
19+ const range = service ( state , line . from , line . to ) ;
20+ if ( range ) return range ;
21+ }
22+ return null ;
23+ }
24+
25+ function addSyntaxFolds ( state , tree , line , addRange ) {
26+ if ( ! tree || tree . length < line . to ) return ;
27+
28+ for ( let iter = tree . resolveStack ( line . to , 1 ) ; iter ; iter = iter . next ) {
29+ const node = iter . node ;
30+ if ( node . to <= line . to || node . from > line . to ) continue ;
31+ const lastChild = node . lastChild ;
32+ if (
33+ tree . length !== state . doc . length &&
34+ node . to >= tree . length - INCOMPLETE_PARSE_MARGIN &&
35+ lastChild ?. to === node . to &&
36+ lastChild . type . isError
37+ ) {
38+ continue ;
39+ }
40+
41+ const fold = node . type . prop ( foldNodeProp ) ;
42+ if ( ! fold ) continue ;
43+
44+ const range = fold ( node , state ) ;
45+ if (
46+ range &&
47+ range . from >= line . from &&
48+ range . from <= line . to &&
49+ range . to > line . to
50+ ) {
51+ addRange ( range ) ;
52+ }
53+ }
54+ }
1355
1456/**
1557 * Fold every foldable block, including nested blocks. CodeMirror's built-in
1658 * foldAll intentionally skips nested ranges after finding a top-level fold.
1759 */
1860export function foldAllCodeBlocks ( view ) {
1961 const { state } = view ;
20- ensureSyntaxTree ( state , state . doc . length , FULL_PARSE_BUDGET_MS ) ;
62+ const tree =
63+ ensureSyntaxTree ( state , state . doc . length , FULL_PARSE_BUDGET_MS ) ??
64+ syntaxTree ( state ) ;
2165
2266 const existing = new Set ( ) ;
2367 foldedRanges ( state ) . between ( 0 , state . doc . length , ( from , to ) => {
@@ -26,15 +70,18 @@ export function foldAllCodeBlocks(view) {
2670
2771 const effects = [ ] ;
2872 const discovered = new Set ( ) ;
29- for ( let lineNumber = 1 ; lineNumber <= state . doc . lines ; lineNumber += 1 ) {
30- const line = state . doc . line ( lineNumber ) ;
31- const range = foldable ( state , line . from , line . to ) ;
32- if ( ! range ) continue ;
33-
73+ const addRange = ( range ) => {
3474 const id = `${ range . from } :${ range . to } ` ;
35- if ( existing . has ( id ) || discovered . has ( id ) ) continue ;
75+ if ( existing . has ( id ) || discovered . has ( id ) ) return ;
3676 discovered . add ( id ) ;
3777 effects . push ( foldEffect . of ( range ) ) ;
78+ } ;
79+
80+ for ( let lineNumber = 1 ; lineNumber <= state . doc . lines ; lineNumber += 1 ) {
81+ const line = state . doc . line ( lineNumber ) ;
82+ const serviceRange = findServiceFold ( state , line ) ;
83+ if ( serviceRange ) addRange ( serviceRange ) ;
84+ else addSyntaxFolds ( state , tree , line , addRange ) ;
3885 }
3986
4087 if ( ! effects . length ) return false ;
0 commit comments