tree-sitter-language-mode.js
1 const Parser = require('tree-sitter'); 2 const { Point, Range, spliceArray } = require('text-buffer'); 3 const { Patch } = require('superstring'); 4 const { Emitter } = require('event-kit'); 5 const ScopeDescriptor = require('./scope-descriptor'); 6 const Token = require('./token'); 7 const TokenizedLine = require('./tokenized-line'); 8 const TextMateLanguageMode = require('./text-mate-language-mode'); 9 const { matcherForSelector } = require('./selectors'); 10 11 let nextId = 0; 12 const MAX_RANGE = new Range(Point.ZERO, Point.INFINITY).freeze(); 13 const PARSER_POOL = []; 14 const WORD_REGEX = /\w/; 15 16 class TreeSitterLanguageMode { 17 static _patchSyntaxNode() { 18 if (!Parser.SyntaxNode.prototype.hasOwnProperty('range')) { 19 Object.defineProperty(Parser.SyntaxNode.prototype, 'range', { 20 get() { 21 return rangeForNode(this); 22 } 23 }); 24 } 25 } 26 27 constructor({ buffer, grammar, config, grammars, syncTimeoutMicros }) { 28 TreeSitterLanguageMode._patchSyntaxNode(); 29 this.id = nextId++; 30 this.buffer = buffer; 31 this.grammar = grammar; 32 this.config = config; 33 this.grammarRegistry = grammars; 34 this.parser = new Parser(); 35 this.rootLanguageLayer = new LanguageLayer(this, grammar, 0); 36 this.injectionsMarkerLayer = buffer.addMarkerLayer(); 37 38 if (syncTimeoutMicros != null) { 39 this.syncTimeoutMicros = syncTimeoutMicros; 40 } 41 42 this.rootScopeDescriptor = new ScopeDescriptor({ 43 scopes: [this.grammar.scopeName] 44 }); 45 this.emitter = new Emitter(); 46 this.isFoldableCache = []; 47 this.hasQueuedParse = false; 48 49 this.grammarForLanguageString = this.grammarForLanguageString.bind(this); 50 51 this.rootLanguageLayer 52 .update(null) 53 .then(() => this.emitter.emit('did-tokenize')); 54 55 // TODO: Remove this once TreeSitterLanguageMode implements its own auto-indentation system. This 56 // is temporarily needed in order to delegate to the TextMateLanguageMode's auto-indent system. 57 this.regexesByPattern = {}; 58 } 59 60 async parseCompletePromise() { 61 let done = false; 62 while (!done) { 63 if (this.rootLanguageLayer.currentParsePromise) { 64 await this.rootLanguageLayer.currentParsePromises; 65 } else { 66 done = true; 67 for (const marker of this.injectionsMarkerLayer.getMarkers()) { 68 if (marker.languageLayer.currentParsePromise) { 69 done = false; 70 await marker.languageLayer.currentParsePromise; 71 break; 72 } 73 } 74 } 75 await new Promise(resolve => setTimeout(resolve, 0)); 76 } 77 } 78 79 destroy() { 80 this.injectionsMarkerLayer.destroy(); 81 this.rootLanguageLayer = null; 82 this.parser = null; 83 } 84 85 getLanguageId() { 86 return this.grammar.scopeName; 87 } 88 89 bufferDidChange({ oldRange, newRange, oldText, newText }) { 90 const edit = this.rootLanguageLayer._treeEditForBufferChange( 91 oldRange.start, 92 oldRange.end, 93 newRange.end, 94 oldText, 95 newText 96 ); 97 this.rootLanguageLayer.handleTextChange(edit, oldText, newText); 98 for (const marker of this.injectionsMarkerLayer.getMarkers()) { 99 marker.languageLayer.handleTextChange(edit, oldText, newText); 100 } 101 } 102 103 bufferDidFinishTransaction({ changes }) { 104 for (let i = 0, { length } = changes; i < length; i++) { 105 const { oldRange, newRange } = changes[i]; 106 spliceArray( 107 this.isFoldableCache, 108 newRange.start.row, 109 oldRange.end.row - oldRange.start.row, 110 { length: newRange.end.row - newRange.start.row } 111 ); 112 } 113 this.rootLanguageLayer.update(null); 114 } 115 116 parse(language, oldTree, ranges) { 117 const parser = PARSER_POOL.pop() || new Parser(); 118 parser.setLanguage(language); 119 const result = parser.parseTextBuffer(this.buffer.buffer, oldTree, { 120 syncTimeoutMicros: this.syncTimeoutMicros, 121 includedRanges: ranges 122 }); 123 124 if (result.then) { 125 return result.then(tree => { 126 PARSER_POOL.push(parser); 127 return tree; 128 }); 129 } else { 130 PARSER_POOL.push(parser); 131 return result; 132 } 133 } 134 135 get tree() { 136 return this.rootLanguageLayer.tree; 137 } 138 139 updateForInjection(grammar) { 140 this.rootLanguageLayer.updateInjections(grammar); 141 } 142 143 /* 144 Section - Highlighting 145 */ 146 147 buildHighlightIterator() { 148 if (!this.rootLanguageLayer) return new NullHighlightIterator(); 149 return new HighlightIterator(this); 150 } 151 152 onDidTokenize(callback) { 153 return this.emitter.on('did-tokenize', callback); 154 } 155 156 onDidChangeHighlighting(callback) { 157 return this.emitter.on('did-change-highlighting', callback); 158 } 159 160 classNameForScopeId(scopeId) { 161 return this.grammar.classNameForScopeId(scopeId); 162 } 163 164 /* 165 Section - Commenting 166 */ 167 168 commentStringsForPosition(position) { 169 const range = 170 this.firstNonWhitespaceRange(position.row) || 171 new Range(position, position); 172 const { grammar } = this.getSyntaxNodeAndGrammarContainingRange(range); 173 return grammar.commentStrings; 174 } 175 176 isRowCommented(row) { 177 const range = this.firstNonWhitespaceRange(row); 178 if (range) { 179 const firstNode = this.getSyntaxNodeContainingRange(range); 180 if (firstNode) return firstNode.type.includes('comment'); 181 } 182 return false; 183 } 184 185 /* 186 Section - Indentation 187 */ 188 189 suggestedIndentForLineAtBufferRow(row, line, tabLength) { 190 return this._suggestedIndentForLineWithScopeAtBufferRow( 191 row, 192 line, 193 this.rootScopeDescriptor, 194 tabLength 195 ); 196 } 197 198 suggestedIndentForBufferRow(row, tabLength, options) { 199 return this._suggestedIndentForLineWithScopeAtBufferRow( 200 row, 201 this.buffer.lineForRow(row), 202 this.rootScopeDescriptor, 203 tabLength, 204 options 205 ); 206 } 207 208 indentLevelForLine(line, tabLength) { 209 let indentLength = 0; 210 for (let i = 0, { length } = line; i < length; i++) { 211 const char = line[i]; 212 if (char === '\t') { 213 indentLength += tabLength - (indentLength % tabLength); 214 } else if (char === ' ') { 215 indentLength++; 216 } else { 217 break; 218 } 219 } 220 return indentLength / tabLength; 221 } 222 223 /* 224 Section - Folding 225 */ 226 227 isFoldableAtRow(row) { 228 if (this.isFoldableCache[row] != null) return this.isFoldableCache[row]; 229 const result = 230 this.getFoldableRangeContainingPoint(Point(row, Infinity), 0, true) != 231 null; 232 this.isFoldableCache[row] = result; 233 return result; 234 } 235 236 getFoldableRanges() { 237 return this.getFoldableRangesAtIndentLevel(null); 238 } 239 240 /** 241 * TODO: Make this method generate folds for nested languages (currently, 242 * folds are only generated for the root language layer). 243 */ 244 getFoldableRangesAtIndentLevel(goalLevel) { 245 let result = []; 246 let stack = [{ node: this.tree.rootNode, level: 0 }]; 247 while (stack.length > 0) { 248 const { node, level } = stack.pop(); 249 250 const range = this.getFoldableRangeForNode(node, this.grammar); 251 if (range) { 252 if (goalLevel == null || level === goalLevel) { 253 let updatedExistingRange = false; 254 for (let i = 0, { length } = result; i < length; i++) { 255 if ( 256 result[i].start.row === range.start.row && 257 result[i].end.row === range.end.row 258 ) { 259 result[i] = range; 260 updatedExistingRange = true; 261 break; 262 } 263 } 264 if (!updatedExistingRange) result.push(range); 265 } 266 } 267 268 const parentStartRow = node.startPosition.row; 269 const parentEndRow = node.endPosition.row; 270 for ( 271 let children = node.namedChildren, i = 0, { length } = children; 272 i < length; 273 i++ 274 ) { 275 const child = children[i]; 276 const { startPosition: childStart, endPosition: childEnd } = child; 277 if (childEnd.row > childStart.row) { 278 if ( 279 childStart.row === parentStartRow && 280 childEnd.row === parentEndRow 281 ) { 282 stack.push({ node: child, level: level }); 283 } else { 284 const childLevel = 285 range && 286 range.containsPoint(childStart) && 287 range.containsPoint(childEnd) 288 ? level + 1 289 : level; 290 if (childLevel <= goalLevel || goalLevel == null) { 291 stack.push({ node: child, level: childLevel }); 292 } 293 } 294 } 295 } 296 } 297 298 return result.sort((a, b) => a.start.row - b.start.row); 299 } 300 301 getFoldableRangeContainingPoint(point, tabLength, existenceOnly = false) { 302 if (!this.tree) return null; 303 304 let smallestRange; 305 this._forEachTreeWithRange(new Range(point, point), (tree, grammar) => { 306 let node = tree.rootNode.descendantForPosition( 307 this.buffer.clipPosition(point) 308 ); 309 while (node) { 310 if (existenceOnly && node.startPosition.row < point.row) return; 311 if (node.endPosition.row > point.row) { 312 const range = this.getFoldableRangeForNode(node, grammar); 313 if (range && rangeIsSmaller(range, smallestRange)) { 314 smallestRange = range; 315 return; 316 } 317 } 318 node = node.parent; 319 } 320 }); 321 322 return existenceOnly 323 ? smallestRange && smallestRange.start.row === point.row 324 : smallestRange; 325 } 326 327 _forEachTreeWithRange(range, callback) { 328 if (this.rootLanguageLayer.tree) { 329 callback(this.rootLanguageLayer.tree, this.rootLanguageLayer.grammar); 330 } 331 332 const injectionMarkers = this.injectionsMarkerLayer.findMarkers({ 333 intersectsRange: range 334 }); 335 336 for (const injectionMarker of injectionMarkers) { 337 const { tree, grammar } = injectionMarker.languageLayer; 338 if (tree) callback(tree, grammar); 339 } 340 } 341 342 getFoldableRangeForNode(node, grammar, existenceOnly) { 343 const { children } = node; 344 const childCount = children.length; 345 346 for (var i = 0, { length } = grammar.folds; i < length; i++) { 347 const foldSpec = grammar.folds[i]; 348 349 if (foldSpec.matchers && !hasMatchingFoldSpec(foldSpec.matchers, node)) 350 continue; 351 352 let foldStart; 353 const startEntry = foldSpec.start; 354 if (startEntry) { 355 let foldStartNode; 356 if (startEntry.index != null) { 357 foldStartNode = children[startEntry.index]; 358 if ( 359 !foldStartNode || 360 (startEntry.matchers && 361 !hasMatchingFoldSpec(startEntry.matchers, foldStartNode)) 362 ) 363 continue; 364 } else { 365 foldStartNode = children.find(child => 366 hasMatchingFoldSpec(startEntry.matchers, child) 367 ); 368 if (!foldStartNode) continue; 369 } 370 foldStart = new Point(foldStartNode.endPosition.row, Infinity); 371 } else { 372 foldStart = new Point(node.startPosition.row, Infinity); 373 } 374 375 let foldEnd; 376 const endEntry = foldSpec.end; 377 if (endEntry) { 378 let foldEndNode; 379 if (endEntry.index != null) { 380 const index = 381 endEntry.index < 0 ? childCount + endEntry.index : endEntry.index; 382 foldEndNode = children[index]; 383 if ( 384 !foldEndNode || 385 (endEntry.type && endEntry.type !== foldEndNode.type) 386 ) 387 continue; 388 } else { 389 foldEndNode = children.find(child => 390 hasMatchingFoldSpec(endEntry.matchers, child) 391 ); 392 if (!foldEndNode) continue; 393 } 394 395 if (foldEndNode.startPosition.row <= foldStart.row) continue; 396 397 foldEnd = foldEndNode.startPosition; 398 if ( 399 this.buffer.findInRangeSync( 400 WORD_REGEX, 401 new Range(foldEnd, new Point(foldEnd.row, Infinity)) 402 ) 403 ) { 404 foldEnd = new Point(foldEnd.row - 1, Infinity); 405 } 406 } else { 407 const { endPosition } = node; 408 if (endPosition.column === 0) { 409 foldEnd = Point(endPosition.row - 1, Infinity); 410 } else if (childCount > 0) { 411 foldEnd = endPosition; 412 } else { 413 foldEnd = Point(endPosition.row, 0); 414 } 415 } 416 417 return existenceOnly ? true : new Range(foldStart, foldEnd); 418 } 419 } 420 421 /* 422 Section - Syntax Tree APIs 423 */ 424 425 getSyntaxNodeContainingRange(range, where = _ => true) { 426 return this.getSyntaxNodeAndGrammarContainingRange(range, where).node; 427 } 428 429 getSyntaxNodeAndGrammarContainingRange(range, where = _ => true) { 430 const startIndex = this.buffer.characterIndexForPosition(range.start); 431 const endIndex = this.buffer.characterIndexForPosition(range.end); 432 const searchEndIndex = Math.max(0, endIndex - 1); 433 434 let smallestNode = null; 435 let smallestNodeGrammar = this.grammar; 436 this._forEachTreeWithRange(range, (tree, grammar) => { 437 let node = tree.rootNode.descendantForIndex(startIndex, searchEndIndex); 438 while (node) { 439 if ( 440 nodeContainsIndices(node, startIndex, endIndex) && 441 where(node, grammar) 442 ) { 443 if (nodeIsSmaller(node, smallestNode)) { 444 smallestNode = node; 445 smallestNodeGrammar = grammar; 446 } 447 break; 448 } 449 node = node.parent; 450 } 451 }); 452 453 return { node: smallestNode, grammar: smallestNodeGrammar }; 454 } 455 456 getRangeForSyntaxNodeContainingRange(range, where) { 457 const node = this.getSyntaxNodeContainingRange(range, where); 458 return node && node.range; 459 } 460 461 getSyntaxNodeAtPosition(position, where) { 462 return this.getSyntaxNodeContainingRange( 463 new Range(position, position), 464 where 465 ); 466 } 467 468 bufferRangeForScopeAtPosition(selector, position) { 469 const nodeCursorAdapter = new NodeCursorAdaptor(); 470 if (typeof selector === 'string') { 471 const match = matcherForSelector(selector); 472 selector = (node, grammar) => { 473 const rules = grammar.scopeMap.get([node.type], [0], node.named); 474 nodeCursorAdapter.node = node; 475 const scopeName = applyLeafRules(rules, nodeCursorAdapter); 476 if (scopeName != null) { 477 return match(scopeName); 478 } 479 }; 480 } 481 if (selector === null) selector = undefined; 482 const node = this.getSyntaxNodeAtPosition(position, selector); 483 return node && node.range; 484 } 485 486 /* 487 Section - Backward compatibility shims 488 */ 489 490 tokenizedLineForRow(row) { 491 const lineText = this.buffer.lineForRow(row); 492 const tokens = []; 493 494 const iterator = this.buildHighlightIterator(); 495 let start = { row, column: 0 }; 496 const scopes = iterator.seek(start, row); 497 while (true) { 498 const end = iterator.getPosition(); 499 if (end.row > row) { 500 end.row = row; 501 end.column = lineText.length; 502 } 503 504 if (end.column > start.column) { 505 tokens.push( 506 new Token({ 507 value: lineText.substring(start.column, end.column), 508 scopes: scopes.map(s => this.grammar.scopeNameForScopeId(s)) 509 }) 510 ); 511 } 512 513 if (end.column < lineText.length) { 514 const closeScopeCount = iterator.getCloseScopeIds().length; 515 for (let i = 0; i < closeScopeCount; i++) { 516 scopes.pop(); 517 } 518 scopes.push(...iterator.getOpenScopeIds()); 519 start = end; 520 iterator.moveToSuccessor(); 521 } else { 522 break; 523 } 524 } 525 526 return new TokenizedLine({ 527 openScopes: [], 528 text: lineText, 529 tokens, 530 tags: [], 531 ruleStack: [], 532 lineEnding: this.buffer.lineEndingForRow(row), 533 tokenIterator: null, 534 grammar: this.grammar 535 }); 536 } 537 538 syntaxTreeScopeDescriptorForPosition(point) { 539 const nodes = []; 540 point = this.buffer.clipPosition(Point.fromObject(point)); 541 542 // If the position is the end of a line, get node of left character instead of newline 543 // This is to match TextMate behaviour, see https://github.com/atom/atom/issues/18463 544 if ( 545 point.column > 0 && 546 point.column === this.buffer.lineLengthForRow(point.row) 547 ) { 548 point = point.copy(); 549 point.column--; 550 } 551 552 this._forEachTreeWithRange(new Range(point, point), tree => { 553 let node = tree.rootNode.descendantForPosition(point); 554 while (node) { 555 nodes.push(node); 556 node = node.parent; 557 } 558 }); 559 560 // The nodes are mostly already sorted from smallest to largest, 561 // but for files with multiple syntax trees (e.g. ERB), each tree's 562 // nodes are separate. Sort the nodes from largest to smallest. 563 nodes.reverse(); 564 nodes.sort( 565 (a, b) => a.startIndex - b.startIndex || b.endIndex - a.endIndex 566 ); 567 568 const nodeTypes = nodes.map(node => node.type); 569 nodeTypes.unshift(this.grammar.scopeName); 570 return new ScopeDescriptor({ scopes: nodeTypes }); 571 } 572 573 scopeDescriptorForPosition(point) { 574 point = this.buffer.clipPosition(Point.fromObject(point)); 575 576 // If the position is the end of a line, get scope of left character instead of newline 577 // This is to match TextMate behaviour, see https://github.com/atom/atom/issues/18463 578 if ( 579 point.column > 0 && 580 point.column === this.buffer.lineLengthForRow(point.row) 581 ) { 582 point = point.copy(); 583 point.column--; 584 } 585 586 const iterator = this.buildHighlightIterator(); 587 const scopes = []; 588 for (const scope of iterator.seek(point, point.row + 1)) { 589 scopes.push(this.grammar.scopeNameForScopeId(scope)); 590 } 591 if (point.isEqual(iterator.getPosition())) { 592 for (const scope of iterator.getOpenScopeIds()) { 593 scopes.push(this.grammar.scopeNameForScopeId(scope)); 594 } 595 } 596 if (scopes.length === 0 || scopes[0] !== this.grammar.scopeName) { 597 scopes.unshift(this.grammar.scopeName); 598 } 599 return new ScopeDescriptor({ scopes }); 600 } 601 602 tokenForPosition(point) { 603 const node = this.getSyntaxNodeAtPosition(point); 604 const scopes = this.scopeDescriptorForPosition(point).getScopesArray(); 605 return new Token({ value: node.text, scopes }); 606 } 607 608 getGrammar() { 609 return this.grammar; 610 } 611 612 /* 613 Section - Private 614 */ 615 616 firstNonWhitespaceRange(row) { 617 return this.buffer.findInRangeSync( 618 /\S/, 619 new Range(new Point(row, 0), new Point(row, Infinity)) 620 ); 621 } 622 623 grammarForLanguageString(languageString) { 624 return this.grammarRegistry.treeSitterGrammarForLanguageString( 625 languageString 626 ); 627 } 628 629 emitRangeUpdate(range) { 630 const startRow = range.start.row; 631 const endRow = range.end.row; 632 for (let row = startRow; row < endRow; row++) { 633 this.isFoldableCache[row] = undefined; 634 } 635 this.emitter.emit('did-change-highlighting', range); 636 } 637 } 638 639 class LanguageLayer { 640 constructor(languageMode, grammar, depth) { 641 this.languageMode = languageMode; 642 this.grammar = grammar; 643 this.tree = null; 644 this.currentParsePromise = null; 645 this.patchSinceCurrentParseStarted = null; 646 this.depth = depth; 647 } 648 649 buildHighlightIterator() { 650 if (this.tree) { 651 return new LayerHighlightIterator(this, this.tree.walk()); 652 } else { 653 return new NullHighlightIterator(); 654 } 655 } 656 657 handleTextChange(edit, oldText, newText) { 658 const { startPosition, oldEndPosition, newEndPosition } = edit; 659 660 if (this.tree) { 661 this.tree.edit(edit); 662 if (this.editedRange) { 663 if (startPosition.isLessThan(this.editedRange.start)) { 664 this.editedRange.start = startPosition; 665 } 666 if (oldEndPosition.isLessThan(this.editedRange.end)) { 667 this.editedRange.end = newEndPosition.traverse( 668 this.editedRange.end.traversalFrom(oldEndPosition) 669 ); 670 } else { 671 this.editedRange.end = newEndPosition; 672 } 673 } else { 674 this.editedRange = new Range(startPosition, newEndPosition); 675 } 676 } 677 678 if (this.patchSinceCurrentParseStarted) { 679 this.patchSinceCurrentParseStarted.splice( 680 startPosition, 681 oldEndPosition.traversalFrom(startPosition), 682 newEndPosition.traversalFrom(startPosition), 683 oldText, 684 newText 685 ); 686 } 687 } 688 689 destroy() { 690 for (const marker of this.languageMode.injectionsMarkerLayer.getMarkers()) { 691 if (marker.parentLanguageLayer === this) { 692 marker.languageLayer.destroy(); 693 marker.destroy(); 694 } 695 } 696 } 697 698 async update(nodeRangeSet) { 699 if (!this.currentParsePromise) { 700 while ( 701 !this.destroyed && 702 (!this.tree || this.tree.rootNode.hasChanges()) 703 ) { 704 const params = { async: false }; 705 this.currentParsePromise = this._performUpdate(nodeRangeSet, params); 706 if (!params.async) break; 707 await this.currentParsePromise; 708 } 709 this.currentParsePromise = null; 710 } 711 } 712 713 updateInjections(grammar) { 714 if (grammar.injectionRegex) { 715 if (!this.currentParsePromise) 716 this.currentParsePromise = Promise.resolve(); 717 this.currentParsePromise = this.currentParsePromise.then(async () => { 718 await this._populateInjections(MAX_RANGE, null); 719 this.currentParsePromise = null; 720 }); 721 } 722 } 723 724 async _performUpdate(nodeRangeSet, params) { 725 let includedRanges = null; 726 if (nodeRangeSet) { 727 includedRanges = nodeRangeSet.getRanges(this.languageMode.buffer); 728 if (includedRanges.length === 0) { 729 this.tree = null; 730 this.destroyed = true; 731 return; 732 } 733 } 734 735 let affectedRange = this.editedRange; 736 this.editedRange = null; 737 738 this.patchSinceCurrentParseStarted = new Patch(); 739 let tree = this.languageMode.parse( 740 this.grammar.languageModule, 741 this.tree, 742 includedRanges 743 ); 744 if (tree.then) { 745 params.async = true; 746 tree = await tree; 747 } 748 749 const changes = this.patchSinceCurrentParseStarted.getChanges(); 750 this.patchSinceCurrentParseStarted = null; 751 for (const { 752 oldStart, 753 newStart, 754 oldEnd, 755 newEnd, 756 oldText, 757 newText 758 } of changes) { 759 const newExtent = Point.fromObject(newEnd).traversalFrom(newStart); 760 tree.edit( 761 this._treeEditForBufferChange( 762 newStart, 763 oldEnd, 764 Point.fromObject(oldStart).traverse(newExtent), 765 oldText, 766 newText 767 ) 768 ); 769 } 770 771 if (this.tree) { 772 const rangesWithSyntaxChanges = this.tree.getChangedRanges(tree); 773 this.tree = tree; 774 775 if (rangesWithSyntaxChanges.length > 0) { 776 for (const range of rangesWithSyntaxChanges) { 777 this.languageMode.emitRangeUpdate(rangeForNode(range)); 778 } 779 780 const combinedRangeWithSyntaxChange = new Range( 781 rangesWithSyntaxChanges[0].startPosition, 782 last(rangesWithSyntaxChanges).endPosition 783 ); 784 785 if (affectedRange) { 786 this.languageMode.emitRangeUpdate(affectedRange); 787 affectedRange = affectedRange.union(combinedRangeWithSyntaxChange); 788 } else { 789 affectedRange = combinedRangeWithSyntaxChange; 790 } 791 } 792 } else { 793 this.tree = tree; 794 this.languageMode.emitRangeUpdate(rangeForNode(tree.rootNode)); 795 if (includedRanges) { 796 affectedRange = new Range( 797 includedRanges[0].startPosition, 798 last(includedRanges).endPosition 799 ); 800 } else { 801 affectedRange = MAX_RANGE; 802 } 803 } 804 805 if (affectedRange) { 806 const injectionPromise = this._populateInjections( 807 affectedRange, 808 nodeRangeSet 809 ); 810 if (injectionPromise) { 811 params.async = true; 812 return injectionPromise; 813 } 814 } 815 } 816 817 _populateInjections(range, nodeRangeSet) { 818 const existingInjectionMarkers = this.languageMode.injectionsMarkerLayer 819 .findMarkers({ intersectsRange: range }) 820 .filter(marker => marker.parentLanguageLayer === this); 821 822 if (existingInjectionMarkers.length > 0) { 823 range = range.union( 824 new Range( 825 existingInjectionMarkers[0].getRange().start, 826 last(existingInjectionMarkers).getRange().end 827 ) 828 ); 829 } 830 831 const markersToUpdate = new Map(); 832 const nodes = this.tree.rootNode.descendantsOfType( 833 Object.keys(this.grammar.injectionPointsByType), 834 range.start, 835 range.end 836 ); 837 838 let existingInjectionMarkerIndex = 0; 839 for (const node of nodes) { 840 for (const injectionPoint of this.grammar.injectionPointsByType[ 841 node.type 842 ]) { 843 const languageName = injectionPoint.language(node); 844 if (!languageName) continue; 845 846 const grammar = this.languageMode.grammarForLanguageString( 847 languageName 848 ); 849 if (!grammar) continue; 850 851 const contentNodes = injectionPoint.content(node); 852 if (!contentNodes) continue; 853 854 const injectionNodes = [].concat(contentNodes); 855 if (!injectionNodes.length) continue; 856 857 const injectionRange = rangeForNode(node); 858 859 let marker; 860 for ( 861 let i = existingInjectionMarkerIndex, 862 n = existingInjectionMarkers.length; 863 i < n; 864 i++ 865 ) { 866 const existingMarker = existingInjectionMarkers[i]; 867 const comparison = existingMarker.getRange().compare(injectionRange); 868 if (comparison > 0) { 869 break; 870 } else if (comparison === 0) { 871 existingInjectionMarkerIndex = i; 872 if (existingMarker.languageLayer.grammar === grammar) { 873 marker = existingMarker; 874 break; 875 } 876 } else { 877 existingInjectionMarkerIndex = i; 878 } 879 } 880 881 if (!marker) { 882 marker = this.languageMode.injectionsMarkerLayer.markRange( 883 injectionRange 884 ); 885 marker.languageLayer = new LanguageLayer( 886 this.languageMode, 887 grammar, 888 this.depth + 1 889 ); 890 marker.parentLanguageLayer = this; 891 } 892 893 markersToUpdate.set( 894 marker, 895 new NodeRangeSet( 896 nodeRangeSet, 897 injectionNodes, 898 injectionPoint.newlinesBetween, 899 injectionPoint.includeChildren 900 ) 901 ); 902 } 903 } 904 905 for (const marker of existingInjectionMarkers) { 906 if (!markersToUpdate.has(marker)) { 907 marker.languageLayer.destroy(); 908 this.languageMode.emitRangeUpdate(marker.getRange()); 909 marker.destroy(); 910 } 911 } 912 913 if (markersToUpdate.size > 0) { 914 const promises = []; 915 for (const [marker, nodeRangeSet] of markersToUpdate) { 916 promises.push(marker.languageLayer.update(nodeRangeSet)); 917 } 918 return Promise.all(promises); 919 } 920 } 921 922 _treeEditForBufferChange(start, oldEnd, newEnd, oldText, newText) { 923 const startIndex = this.languageMode.buffer.characterIndexForPosition( 924 start 925 ); 926 return { 927 startIndex, 928 oldEndIndex: startIndex + oldText.length, 929 newEndIndex: startIndex + newText.length, 930 startPosition: start, 931 oldEndPosition: oldEnd, 932 newEndPosition: newEnd 933 }; 934 } 935 } 936 937 class HighlightIterator { 938 constructor(languageMode) { 939 this.languageMode = languageMode; 940 this.iterators = null; 941 } 942 943 seek(targetPosition, endRow) { 944 const injectionMarkers = this.languageMode.injectionsMarkerLayer.findMarkers( 945 { 946 intersectsRange: new Range(targetPosition, new Point(endRow + 1, 0)) 947 } 948 ); 949 950 const containingTags = []; 951 const containingTagStartIndices = []; 952 const targetIndex = this.languageMode.buffer.characterIndexForPosition( 953 targetPosition 954 ); 955 956 this.iterators = []; 957 const iterator = this.languageMode.rootLanguageLayer.buildHighlightIterator(); 958 if (iterator.seek(targetIndex, containingTags, containingTagStartIndices)) { 959 this.iterators.push(iterator); 960 } 961 962 // Populate the iterators array with all of the iterators whose syntax 963 // trees span the given position. 964 for (const marker of injectionMarkers) { 965 const iterator = marker.languageLayer.buildHighlightIterator(); 966 if ( 967 iterator.seek(targetIndex, containingTags, containingTagStartIndices) 968 ) { 969 this.iterators.push(iterator); 970 } 971 } 972 973 // Sort the iterators so that the last one in the array is the earliest 974 // in the document, and represents the current position. 975 this.iterators.sort((a, b) => b.compare(a)); 976 this.detectCoveredScope(); 977 978 return containingTags; 979 } 980 981 moveToSuccessor() { 982 // Advance the earliest layer iterator to its next scope boundary. 983 let leader = last(this.iterators); 984 985 // Maintain the sorting of the iterators by their position in the document. 986 if (leader.moveToSuccessor()) { 987 const leaderIndex = this.iterators.length - 1; 988 let i = leaderIndex; 989 while (i > 0 && this.iterators[i - 1].compare(leader) < 0) i--; 990 if (i < leaderIndex) { 991 this.iterators.splice(i, 0, this.iterators.pop()); 992 } 993 } else { 994 // If the layer iterator was at the end of its syntax tree, then remove 995 // it from the array. 996 this.iterators.pop(); 997 } 998 999 this.detectCoveredScope(); 1000 } 1001 1002 // Detect whether or not another more deeply-nested language layer has a 1003 // scope boundary at this same position. If so, the current language layer's 1004 // scope boundary should not be reported. 1005 detectCoveredScope() { 1006 const layerCount = this.iterators.length; 1007 if (layerCount > 1) { 1008 const first = this.iterators[layerCount - 1]; 1009 const next = this.iterators[layerCount - 2]; 1010 if ( 1011 next.offset === first.offset && 1012 next.atEnd === first.atEnd && 1013 next.depth > first.depth && 1014 !next.isAtInjectionBoundary() 1015 ) { 1016 this.currentScopeIsCovered = true; 1017 return; 1018 } 1019 } 1020 this.currentScopeIsCovered = false; 1021 } 1022 1023 getPosition() { 1024 const iterator = last(this.iterators); 1025 if (iterator) { 1026 return iterator.getPosition(); 1027 } else { 1028 return Point.INFINITY; 1029 } 1030 } 1031 1032 getCloseScopeIds() { 1033 const iterator = last(this.iterators); 1034 if (iterator && !this.currentScopeIsCovered) { 1035 return iterator.getCloseScopeIds(); 1036 } 1037 return []; 1038 } 1039 1040 getOpenScopeIds() { 1041 const iterator = last(this.iterators); 1042 if (iterator && !this.currentScopeIsCovered) { 1043 return iterator.getOpenScopeIds(); 1044 } 1045 return []; 1046 } 1047 1048 logState() { 1049 const iterator = last(this.iterators); 1050 if (iterator && iterator.treeCursor) { 1051 console.log( 1052 iterator.getPosition(), 1053 iterator.treeCursor.nodeType, 1054 `depth=${iterator.languageLayer.depth}`, 1055 new Range( 1056 iterator.languageLayer.tree.rootNode.startPosition, 1057 iterator.languageLayer.tree.rootNode.endPosition 1058 ).toString() 1059 ); 1060 if (this.currentScopeIsCovered) { 1061 console.log('covered'); 1062 } else { 1063 console.log( 1064 'close', 1065 iterator.closeTags.map(id => 1066 this.languageMode.grammar.scopeNameForScopeId(id) 1067 ) 1068 ); 1069 console.log( 1070 'open', 1071 iterator.openTags.map(id => 1072 this.languageMode.grammar.scopeNameForScopeId(id) 1073 ) 1074 ); 1075 } 1076 } 1077 } 1078 } 1079 1080 class LayerHighlightIterator { 1081 constructor(languageLayer, treeCursor) { 1082 this.languageLayer = languageLayer; 1083 this.depth = this.languageLayer.depth; 1084 1085 // The iterator is always positioned at either the start or the end of some node 1086 // in the syntax tree. 1087 this.atEnd = false; 1088 this.treeCursor = treeCursor; 1089 this.offset = 0; 1090 1091 // In order to determine which selectors match its current node, the iterator maintains 1092 // a list of the current node's ancestors. Because the selectors can use the `:nth-child` 1093 // pseudo-class, each node's child index is also stored. 1094 this.containingNodeTypes = []; 1095 this.containingNodeChildIndices = []; 1096 this.containingNodeEndIndices = []; 1097 1098 // At any given position, the iterator exposes the list of class names that should be 1099 // *ended* at its current position and the list of class names that should be *started* 1100 // at its current position. 1101 this.closeTags = []; 1102 this.openTags = []; 1103 } 1104 1105 seek(targetIndex, containingTags, containingTagStartIndices) { 1106 while (this.treeCursor.gotoParent()) {} 1107 1108 this.atEnd = true; 1109 this.closeTags.length = 0; 1110 this.openTags.length = 0; 1111 this.containingNodeTypes.length = 0; 1112 this.containingNodeChildIndices.length = 0; 1113 this.containingNodeEndIndices.length = 0; 1114 1115 const containingTagEndIndices = []; 1116 1117 if (targetIndex >= this.treeCursor.endIndex) { 1118 return false; 1119 } 1120 1121 let childIndex = -1; 1122 for (;;) { 1123 this.containingNodeTypes.push(this.treeCursor.nodeType); 1124 this.containingNodeChildIndices.push(childIndex); 1125 this.containingNodeEndIndices.push(this.treeCursor.endIndex); 1126 1127 const scopeId = this._currentScopeId(); 1128 if (scopeId) { 1129 if (this.treeCursor.startIndex < targetIndex) { 1130 insertContainingTag( 1131 scopeId, 1132 this.treeCursor.startIndex, 1133 containingTags, 1134 containingTagStartIndices 1135 ); 1136 containingTagEndIndices.push(this.treeCursor.endIndex); 1137 } else { 1138 this.atEnd = false; 1139 this.openTags.push(scopeId); 1140 this._moveDown(); 1141 break; 1142 } 1143 } 1144 1145 childIndex = this.treeCursor.gotoFirstChildForIndex(targetIndex); 1146 if (childIndex === null) break; 1147 if (this.treeCursor.startIndex >= targetIndex) this.atEnd = false; 1148 } 1149 1150 if (this.atEnd) { 1151 this.offset = this.treeCursor.endIndex; 1152 for (let i = 0, { length } = containingTags; i < length; i++) { 1153 if (containingTagEndIndices[i] === this.offset) { 1154 this.closeTags.push(containingTags[i]); 1155 } 1156 } 1157 } else { 1158 this.offset = this.treeCursor.startIndex; 1159 } 1160 1161 return true; 1162 } 1163 1164 moveToSuccessor() { 1165 this.closeTags.length = 0; 1166 this.openTags.length = 0; 1167 1168 while (!this.closeTags.length && !this.openTags.length) { 1169 if (this.atEnd) { 1170 if (this._moveRight()) { 1171 const scopeId = this._currentScopeId(); 1172 if (scopeId) this.openTags.push(scopeId); 1173 this.atEnd = false; 1174 this._moveDown(); 1175 } else if (this._moveUp(true)) { 1176 this.atEnd = true; 1177 } else { 1178 return false; 1179 } 1180 } else if (!this._moveDown()) { 1181 const scopeId = this._currentScopeId(); 1182 if (scopeId) this.closeTags.push(scopeId); 1183 this.atEnd = true; 1184 this._moveUp(false); 1185 } 1186 } 1187 1188 if (this.atEnd) { 1189 this.offset = this.treeCursor.endIndex; 1190 } else { 1191 this.offset = this.treeCursor.startIndex; 1192 } 1193 1194 return true; 1195 } 1196 1197 getPosition() { 1198 if (this.atEnd) { 1199 return this.treeCursor.endPosition; 1200 } else { 1201 return this.treeCursor.startPosition; 1202 } 1203 } 1204 1205 compare(other) { 1206 const result = this.offset - other.offset; 1207 if (result !== 0) return result; 1208 if (this.atEnd && !other.atEnd) return -1; 1209 if (other.atEnd && !this.atEnd) return 1; 1210 return this.languageLayer.depth - other.languageLayer.depth; 1211 } 1212 1213 getCloseScopeIds() { 1214 return this.closeTags.slice(); 1215 } 1216 1217 getOpenScopeIds() { 1218 return this.openTags.slice(); 1219 } 1220 1221 isAtInjectionBoundary() { 1222 return this.containingNodeTypes.length === 1; 1223 } 1224 1225 // Private methods 1226 1227 _moveUp(atLastChild) { 1228 let result = false; 1229 const { endIndex } = this.treeCursor; 1230 let depth = this.containingNodeEndIndices.length; 1231 1232 // The iterator should not move up until it has visited all of the children of this node. 1233 while ( 1234 depth > 1 && 1235 (atLastChild || this.containingNodeEndIndices[depth - 2] === endIndex) 1236 ) { 1237 atLastChild = false; 1238 result = true; 1239 this.treeCursor.gotoParent(); 1240 this.containingNodeTypes.pop(); 1241 this.containingNodeChildIndices.pop(); 1242 this.containingNodeEndIndices.pop(); 1243 --depth; 1244 const scopeId = this._currentScopeId(); 1245 if (scopeId) this.closeTags.push(scopeId); 1246 } 1247 return result; 1248 } 1249 1250 _moveDown() { 1251 let result = false; 1252 const { startIndex } = this.treeCursor; 1253 1254 // Once the iterator has found a scope boundary, it needs to stay at the same 1255 // position, so it should not move down if the first child node starts later than the 1256 // current node. 1257 while (this.treeCursor.gotoFirstChild()) { 1258 if ( 1259 (this.closeTags.length || this.openTags.length) && 1260 this.treeCursor.startIndex > startIndex 1261 ) { 1262 this.treeCursor.gotoParent(); 1263 break; 1264 } 1265 1266 result = true; 1267 this.containingNodeTypes.push(this.treeCursor.nodeType); 1268 this.containingNodeChildIndices.push(0); 1269 this.containingNodeEndIndices.push(this.treeCursor.endIndex); 1270 1271 const scopeId = this._currentScopeId(); 1272 if (scopeId) this.openTags.push(scopeId); 1273 } 1274 1275 return result; 1276 } 1277 1278 _moveRight() { 1279 if (this.treeCursor.gotoNextSibling()) { 1280 const depth = this.containingNodeTypes.length; 1281 this.containingNodeTypes[depth - 1] = this.treeCursor.nodeType; 1282 this.containingNodeChildIndices[depth - 1]++; 1283 this.containingNodeEndIndices[depth - 1] = this.treeCursor.endIndex; 1284 return true; 1285 } 1286 } 1287 1288 _currentScopeId() { 1289 const value = this.languageLayer.grammar.scopeMap.get( 1290 this.containingNodeTypes, 1291 this.containingNodeChildIndices, 1292 this.treeCursor.nodeIsNamed 1293 ); 1294 const scopeName = applyLeafRules(value, this.treeCursor); 1295 if (scopeName) { 1296 return this.languageLayer.languageMode.grammar.idForScope(scopeName); 1297 } 1298 } 1299 } 1300 1301 const applyLeafRules = (rules, cursor) => { 1302 if (!rules || typeof rules === 'string') return rules; 1303 if (Array.isArray(rules)) { 1304 for (let i = 0, { length } = rules; i !== length; ++i) { 1305 const result = applyLeafRules(rules[i], cursor); 1306 if (result) return result; 1307 } 1308 return undefined; 1309 } 1310 if (typeof rules === 'object') { 1311 if (rules.exact) { 1312 return cursor.nodeText === rules.exact 1313 ? applyLeafRules(rules.scopes, cursor) 1314 : undefined; 1315 } 1316 if (rules.match) { 1317 return rules.match.test(cursor.nodeText) 1318 ? applyLeafRules(rules.scopes, cursor) 1319 : undefined; 1320 } 1321 } 1322 }; 1323 1324 class NodeCursorAdaptor { 1325 get nodeText() { 1326 return this.node.text; 1327 } 1328 } 1329 1330 class NullHighlightIterator { 1331 seek() { 1332 return []; 1333 } 1334 compare() { 1335 return 1; 1336 } 1337 moveToSuccessor() {} 1338 getPosition() { 1339 return Point.INFINITY; 1340 } 1341 getOpenScopeIds() { 1342 return []; 1343 } 1344 getCloseScopeIds() { 1345 return []; 1346 } 1347 } 1348 1349 class NodeRangeSet { 1350 constructor(previous, nodes, newlinesBetween, includeChildren) { 1351 this.previous = previous; 1352 this.nodes = nodes; 1353 this.newlinesBetween = newlinesBetween; 1354 this.includeChildren = includeChildren; 1355 } 1356 1357 getRanges(buffer) { 1358 const previousRanges = this.previous && this.previous.getRanges(buffer); 1359 const result = []; 1360 1361 for (const node of this.nodes) { 1362 let position = node.startPosition; 1363 let index = node.startIndex; 1364 1365 if (!this.includeChildren) { 1366 for (const child of node.children) { 1367 const nextIndex = child.startIndex; 1368 if (nextIndex > index) { 1369 this._pushRange(buffer, previousRanges, result, { 1370 startIndex: index, 1371 endIndex: nextIndex, 1372 startPosition: position, 1373 endPosition: child.startPosition 1374 }); 1375 } 1376 position = child.endPosition; 1377 index = child.endIndex; 1378 } 1379 } 1380 1381 if (node.endIndex > index) { 1382 this._pushRange(buffer, previousRanges, result, { 1383 startIndex: index, 1384 endIndex: node.endIndex, 1385 startPosition: position, 1386 endPosition: node.endPosition 1387 }); 1388 } 1389 } 1390 1391 return result; 1392 } 1393 1394 _pushRange(buffer, previousRanges, newRanges, newRange) { 1395 if (!previousRanges) { 1396 if (this.newlinesBetween) { 1397 const { startIndex, startPosition } = newRange; 1398 this._ensureNewline(buffer, newRanges, startIndex, startPosition); 1399 } 1400 newRanges.push(newRange); 1401 return; 1402 } 1403 1404 for (const previousRange of previousRanges) { 1405 if (previousRange.endIndex <= newRange.startIndex) continue; 1406 if (previousRange.startIndex >= newRange.endIndex) break; 1407 const startIndex = Math.max( 1408 previousRange.startIndex, 1409 newRange.startIndex 1410 ); 1411 const endIndex = Math.min(previousRange.endIndex, newRange.endIndex); 1412 const startPosition = Point.max( 1413 previousRange.startPosition, 1414 newRange.startPosition 1415 ); 1416 const endPosition = Point.min( 1417 previousRange.endPosition, 1418 newRange.endPosition 1419 ); 1420 if (this.newlinesBetween) { 1421 this._ensureNewline(buffer, newRanges, startIndex, startPosition); 1422 } 1423 newRanges.push({ startIndex, endIndex, startPosition, endPosition }); 1424 } 1425 } 1426 1427 // For injection points with `newlinesBetween` enabled, ensure that a 1428 // newline is included between each disjoint range. 1429 _ensureNewline(buffer, newRanges, startIndex, startPosition) { 1430 const lastRange = newRanges[newRanges.length - 1]; 1431 if (lastRange && lastRange.endPosition.row < startPosition.row) { 1432 newRanges.push({ 1433 startPosition: new Point( 1434 startPosition.row - 1, 1435 buffer.lineLengthForRow(startPosition.row - 1) 1436 ), 1437 endPosition: new Point(startPosition.row, 0), 1438 startIndex: startIndex - startPosition.column - 1, 1439 endIndex: startIndex - startPosition.column 1440 }); 1441 } 1442 } 1443 } 1444 1445 function insertContainingTag(tag, index, tags, indices) { 1446 const i = indices.findIndex(existingIndex => existingIndex > index); 1447 if (i === -1) { 1448 tags.push(tag); 1449 indices.push(index); 1450 } else { 1451 tags.splice(i, 0, tag); 1452 indices.splice(i, 0, index); 1453 } 1454 } 1455 1456 // Return true iff `mouse` is smaller than `house`. Only correct if 1457 // mouse and house overlap. 1458 // 1459 // * `mouse` {Range} 1460 // * `house` {Range} 1461 function rangeIsSmaller(mouse, house) { 1462 if (!house) return true; 1463 const mvec = vecFromRange(mouse); 1464 const hvec = vecFromRange(house); 1465 return Point.min(mvec, hvec) === mvec; 1466 } 1467 1468 function vecFromRange({ start, end }) { 1469 return end.translate(start.negate()); 1470 } 1471 1472 function rangeForNode(node) { 1473 return new Range(node.startPosition, node.endPosition); 1474 } 1475 1476 function nodeContainsIndices(node, start, end) { 1477 if (node.startIndex < start) return node.endIndex >= end; 1478 if (node.startIndex === start) return node.endIndex > end; 1479 return false; 1480 } 1481 1482 function nodeIsSmaller(left, right) { 1483 if (!left) return false; 1484 if (!right) return true; 1485 return left.endIndex - left.startIndex < right.endIndex - right.startIndex; 1486 } 1487 1488 function last(array) { 1489 return array[array.length - 1]; 1490 } 1491 1492 function hasMatchingFoldSpec(specs, node) { 1493 return specs.some( 1494 ({ type, named }) => type === node.type && named === node.isNamed 1495 ); 1496 } 1497 1498 // TODO: Remove this once TreeSitterLanguageMode implements its own auto-indent system. 1499 [ 1500 '_suggestedIndentForLineWithScopeAtBufferRow', 1501 'suggestedIndentForEditedBufferRow', 1502 'increaseIndentRegexForScopeDescriptor', 1503 'decreaseIndentRegexForScopeDescriptor', 1504 'decreaseNextIndentRegexForScopeDescriptor', 1505 'regexForPattern', 1506 'getNonWordCharacters' 1507 ].forEach(methodName => { 1508 TreeSitterLanguageMode.prototype[methodName] = 1509 TextMateLanguageMode.prototype[methodName]; 1510 }); 1511 1512 TreeSitterLanguageMode.LanguageLayer = LanguageLayer; 1513 TreeSitterLanguageMode.prototype.syncTimeoutMicros = 1000; 1514 1515 module.exports = TreeSitterLanguageMode;