171 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			171 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|   | /*--------------------------------------------------------------------------------------------- | ||
|  |  *  Copyright (c) Microsoft Corporation. All rights reserved. | ||
|  |  *  Licensed under the MIT License. See License.txt in the project root for license information. | ||
|  |  *--------------------------------------------------------------------------------------------*/ | ||
|  | import { TokenType, FoldingRangeKind } from '../htmlLanguageTypes'; | ||
|  | import { createScanner } from '../parser/htmlScanner'; | ||
|  | export class HTMLFolding { | ||
|  |     constructor(dataManager) { | ||
|  |         this.dataManager = dataManager; | ||
|  |     } | ||
|  |     limitRanges(ranges, rangeLimit) { | ||
|  |         ranges = ranges.sort((r1, r2) => { | ||
|  |             let diff = r1.startLine - r2.startLine; | ||
|  |             if (diff === 0) { | ||
|  |                 diff = r1.endLine - r2.endLine; | ||
|  |             } | ||
|  |             return diff; | ||
|  |         }); | ||
|  |         // compute each range's nesting level in 'nestingLevels'.
 | ||
|  |         // count the number of ranges for each level in 'nestingLevelCounts'
 | ||
|  |         let top = void 0; | ||
|  |         const previous = []; | ||
|  |         const nestingLevels = []; | ||
|  |         const nestingLevelCounts = []; | ||
|  |         const setNestingLevel = (index, level) => { | ||
|  |             nestingLevels[index] = level; | ||
|  |             if (level < 30) { | ||
|  |                 nestingLevelCounts[level] = (nestingLevelCounts[level] || 0) + 1; | ||
|  |             } | ||
|  |         }; | ||
|  |         // compute nesting levels and sanitize
 | ||
|  |         for (let i = 0; i < ranges.length; i++) { | ||
|  |             const entry = ranges[i]; | ||
|  |             if (!top) { | ||
|  |                 top = entry; | ||
|  |                 setNestingLevel(i, 0); | ||
|  |             } | ||
|  |             else { | ||
|  |                 if (entry.startLine > top.startLine) { | ||
|  |                     if (entry.endLine <= top.endLine) { | ||
|  |                         previous.push(top); | ||
|  |                         top = entry; | ||
|  |                         setNestingLevel(i, previous.length); | ||
|  |                     } | ||
|  |                     else if (entry.startLine > top.endLine) { | ||
|  |                         do { | ||
|  |                             top = previous.pop(); | ||
|  |                         } while (top && entry.startLine > top.endLine); | ||
|  |                         if (top) { | ||
|  |                             previous.push(top); | ||
|  |                         } | ||
|  |                         top = entry; | ||
|  |                         setNestingLevel(i, previous.length); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         let entries = 0; | ||
|  |         let maxLevel = 0; | ||
|  |         for (let i = 0; i < nestingLevelCounts.length; i++) { | ||
|  |             const n = nestingLevelCounts[i]; | ||
|  |             if (n) { | ||
|  |                 if (n + entries > rangeLimit) { | ||
|  |                     maxLevel = i; | ||
|  |                     break; | ||
|  |                 } | ||
|  |                 entries += n; | ||
|  |             } | ||
|  |         } | ||
|  |         const result = []; | ||
|  |         for (let i = 0; i < ranges.length; i++) { | ||
|  |             const level = nestingLevels[i]; | ||
|  |             if (typeof level === 'number') { | ||
|  |                 if (level < maxLevel || (level === maxLevel && entries++ < rangeLimit)) { | ||
|  |                     result.push(ranges[i]); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         return result; | ||
|  |     } | ||
|  |     getFoldingRanges(document, context) { | ||
|  |         const voidElements = this.dataManager.getVoidElements(document.languageId); | ||
|  |         const scanner = createScanner(document.getText()); | ||
|  |         let token = scanner.scan(); | ||
|  |         const ranges = []; | ||
|  |         const stack = []; | ||
|  |         let lastTagName = null; | ||
|  |         let prevStart = -1; | ||
|  |         function addRange(range) { | ||
|  |             ranges.push(range); | ||
|  |             prevStart = range.startLine; | ||
|  |         } | ||
|  |         while (token !== TokenType.EOS) { | ||
|  |             switch (token) { | ||
|  |                 case TokenType.StartTag: { | ||
|  |                     const tagName = scanner.getTokenText(); | ||
|  |                     const startLine = document.positionAt(scanner.getTokenOffset()).line; | ||
|  |                     stack.push({ startLine, tagName }); | ||
|  |                     lastTagName = tagName; | ||
|  |                     break; | ||
|  |                 } | ||
|  |                 case TokenType.EndTag: { | ||
|  |                     lastTagName = scanner.getTokenText(); | ||
|  |                     break; | ||
|  |                 } | ||
|  |                 case TokenType.StartTagClose: | ||
|  |                     if (!lastTagName || !this.dataManager.isVoidElement(lastTagName, voidElements)) { | ||
|  |                         break; | ||
|  |                     } | ||
|  |                 // fallthrough
 | ||
|  |                 case TokenType.EndTagClose: | ||
|  |                 case TokenType.StartTagSelfClose: { | ||
|  |                     let i = stack.length - 1; | ||
|  |                     while (i >= 0 && stack[i].tagName !== lastTagName) { | ||
|  |                         i--; | ||
|  |                     } | ||
|  |                     if (i >= 0) { | ||
|  |                         const stackElement = stack[i]; | ||
|  |                         stack.length = i; | ||
|  |                         const line = document.positionAt(scanner.getTokenOffset()).line; | ||
|  |                         const startLine = stackElement.startLine; | ||
|  |                         const endLine = line - 1; | ||
|  |                         if (endLine > startLine && prevStart !== startLine) { | ||
|  |                             addRange({ startLine, endLine }); | ||
|  |                         } | ||
|  |                     } | ||
|  |                     break; | ||
|  |                 } | ||
|  |                 case TokenType.Comment: { | ||
|  |                     let startLine = document.positionAt(scanner.getTokenOffset()).line; | ||
|  |                     const text = scanner.getTokenText(); | ||
|  |                     const m = text.match(/^\s*#(region\b)|(endregion\b)/); | ||
|  |                     if (m) { | ||
|  |                         if (m[1]) { // start pattern match
 | ||
|  |                             stack.push({ startLine, tagName: '' }); // empty tagName marks region
 | ||
|  |                         } | ||
|  |                         else { | ||
|  |                             let i = stack.length - 1; | ||
|  |                             while (i >= 0 && stack[i].tagName.length) { | ||
|  |                                 i--; | ||
|  |                             } | ||
|  |                             if (i >= 0) { | ||
|  |                                 const stackElement = stack[i]; | ||
|  |                                 stack.length = i; | ||
|  |                                 const endLine = startLine; | ||
|  |                                 startLine = stackElement.startLine; | ||
|  |                                 if (endLine > startLine && prevStart !== startLine) { | ||
|  |                                     addRange({ startLine, endLine, kind: FoldingRangeKind.Region }); | ||
|  |                                 } | ||
|  |                             } | ||
|  |                         } | ||
|  |                     } | ||
|  |                     else { | ||
|  |                         const endLine = document.positionAt(scanner.getTokenOffset() + scanner.getTokenLength()).line; | ||
|  |                         if (startLine < endLine) { | ||
|  |                             addRange({ startLine, endLine, kind: FoldingRangeKind.Comment }); | ||
|  |                         } | ||
|  |                     } | ||
|  |                     break; | ||
|  |                 } | ||
|  |             } | ||
|  |             token = scanner.scan(); | ||
|  |         } | ||
|  |         const rangeLimit = context && context.rangeLimit || Number.MAX_VALUE; | ||
|  |         if (ranges.length > rangeLimit) { | ||
|  |             return this.limitRanges(ranges, rangeLimit); | ||
|  |         } | ||
|  |         return ranges; | ||
|  |     } | ||
|  | } |