f982f0586fabafd04a8cc490a397398b0e0c72f0c5c63ffd6db8181c9eaaa242baef892650acf6495356bb19e2662b73d35d45c0ee594804012ee2dc4618ae 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import { Emitter } from '../../../base/common/event.js';
  6. import { Position } from '../core/position.js';
  7. import { getWordAtText } from '../core/wordHelper.js';
  8. import { TextModelPart } from './textModelPart.js';
  9. import { TextModelTokenization } from './textModelTokens.js';
  10. import { ContiguousTokensStore } from '../tokens/contiguousTokensStore.js';
  11. import { SparseTokensStore } from '../tokens/sparseTokensStore.js';
  12. export class TokenizationTextModelPart extends TextModelPart {
  13. constructor(_languageService, _languageConfigurationService, _textModel, bracketPairsTextModelPart, _languageId) {
  14. super();
  15. this._languageService = _languageService;
  16. this._languageConfigurationService = _languageConfigurationService;
  17. this._textModel = _textModel;
  18. this.bracketPairsTextModelPart = bracketPairsTextModelPart;
  19. this._languageId = _languageId;
  20. this._onDidChangeLanguage = this._register(new Emitter());
  21. this.onDidChangeLanguage = this._onDidChangeLanguage.event;
  22. this._onDidChangeLanguageConfiguration = this._register(new Emitter());
  23. this.onDidChangeLanguageConfiguration = this._onDidChangeLanguageConfiguration.event;
  24. this._onDidChangeTokens = this._register(new Emitter());
  25. this.onDidChangeTokens = this._onDidChangeTokens.event;
  26. this._backgroundTokenizationState = 0 /* BackgroundTokenizationState.Uninitialized */;
  27. this._onBackgroundTokenizationStateChanged = this._register(new Emitter());
  28. this._tokens = new ContiguousTokensStore(this._languageService.languageIdCodec);
  29. this._semanticTokens = new SparseTokensStore(this._languageService.languageIdCodec);
  30. this._tokenization = new TextModelTokenization(_textModel, this, this._languageService.languageIdCodec);
  31. this._languageRegistryListener = this._languageConfigurationService.onDidChange(e => {
  32. if (e.affects(this._languageId)) {
  33. this._onDidChangeLanguageConfiguration.fire({});
  34. }
  35. });
  36. }
  37. acceptEdit(range, text, eolCount, firstLineLength, lastLineLength) {
  38. this._tokens.acceptEdit(range, eolCount, firstLineLength);
  39. this._semanticTokens.acceptEdit(range, eolCount, firstLineLength, lastLineLength, text.length > 0 ? text.charCodeAt(0) : 0 /* CharCode.Null */);
  40. }
  41. handleDidChangeAttached() {
  42. this._tokenization.handleDidChangeAttached();
  43. }
  44. flush() {
  45. this._tokens.flush();
  46. this._semanticTokens.flush();
  47. }
  48. handleDidChangeContent(change) {
  49. this._tokenization.handleDidChangeContent(change);
  50. }
  51. dispose() {
  52. this._languageRegistryListener.dispose();
  53. this._tokenization.dispose();
  54. super.dispose();
  55. }
  56. get backgroundTokenizationState() {
  57. return this._backgroundTokenizationState;
  58. }
  59. handleTokenizationProgress(completed) {
  60. if (this._backgroundTokenizationState === 2 /* BackgroundTokenizationState.Completed */) {
  61. // We already did a full tokenization and don't go back to progressing.
  62. return;
  63. }
  64. const newState = completed ? 2 /* BackgroundTokenizationState.Completed */ : 1 /* BackgroundTokenizationState.InProgress */;
  65. if (this._backgroundTokenizationState !== newState) {
  66. this._backgroundTokenizationState = newState;
  67. this.bracketPairsTextModelPart.handleDidChangeBackgroundTokenizationState();
  68. this._onBackgroundTokenizationStateChanged.fire();
  69. }
  70. }
  71. setTokens(tokens, backgroundTokenizationCompleted = false) {
  72. if (tokens.length !== 0) {
  73. const ranges = [];
  74. for (let i = 0, len = tokens.length; i < len; i++) {
  75. const element = tokens[i];
  76. let minChangedLineNumber = 0;
  77. let maxChangedLineNumber = 0;
  78. let hasChange = false;
  79. for (let lineNumber = element.startLineNumber; lineNumber <= element.endLineNumber; lineNumber++) {
  80. if (hasChange) {
  81. this._tokens.setTokens(this._languageId, lineNumber - 1, this._textModel.getLineLength(lineNumber), element.getLineTokens(lineNumber), false);
  82. maxChangedLineNumber = lineNumber;
  83. }
  84. else {
  85. const lineHasChange = this._tokens.setTokens(this._languageId, lineNumber - 1, this._textModel.getLineLength(lineNumber), element.getLineTokens(lineNumber), true);
  86. if (lineHasChange) {
  87. hasChange = true;
  88. minChangedLineNumber = lineNumber;
  89. maxChangedLineNumber = lineNumber;
  90. }
  91. }
  92. }
  93. if (hasChange) {
  94. ranges.push({
  95. fromLineNumber: minChangedLineNumber,
  96. toLineNumber: maxChangedLineNumber,
  97. });
  98. }
  99. }
  100. if (ranges.length > 0) {
  101. this._emitModelTokensChangedEvent({
  102. tokenizationSupportChanged: false,
  103. semanticTokensApplied: false,
  104. ranges: ranges,
  105. });
  106. }
  107. }
  108. this.handleTokenizationProgress(backgroundTokenizationCompleted);
  109. }
  110. setSemanticTokens(tokens, isComplete) {
  111. this._semanticTokens.set(tokens, isComplete);
  112. this._emitModelTokensChangedEvent({
  113. tokenizationSupportChanged: false,
  114. semanticTokensApplied: tokens !== null,
  115. ranges: [{ fromLineNumber: 1, toLineNumber: this._textModel.getLineCount() }],
  116. });
  117. }
  118. hasCompleteSemanticTokens() {
  119. return this._semanticTokens.isComplete();
  120. }
  121. hasSomeSemanticTokens() {
  122. return !this._semanticTokens.isEmpty();
  123. }
  124. setPartialSemanticTokens(range, tokens) {
  125. if (this.hasCompleteSemanticTokens()) {
  126. return;
  127. }
  128. const changedRange = this._textModel.validateRange(this._semanticTokens.setPartial(range, tokens));
  129. this._emitModelTokensChangedEvent({
  130. tokenizationSupportChanged: false,
  131. semanticTokensApplied: true,
  132. ranges: [
  133. {
  134. fromLineNumber: changedRange.startLineNumber,
  135. toLineNumber: changedRange.endLineNumber,
  136. },
  137. ],
  138. });
  139. }
  140. tokenizeViewport(startLineNumber, endLineNumber) {
  141. startLineNumber = Math.max(1, startLineNumber);
  142. endLineNumber = Math.min(this._textModel.getLineCount(), endLineNumber);
  143. this._tokenization.tokenizeViewport(startLineNumber, endLineNumber);
  144. }
  145. clearTokens() {
  146. this._tokens.flush();
  147. this._emitModelTokensChangedEvent({
  148. tokenizationSupportChanged: true,
  149. semanticTokensApplied: false,
  150. ranges: [
  151. {
  152. fromLineNumber: 1,
  153. toLineNumber: this._textModel.getLineCount(),
  154. },
  155. ],
  156. });
  157. }
  158. _emitModelTokensChangedEvent(e) {
  159. if (!this._textModel._isDisposing()) {
  160. this.bracketPairsTextModelPart.handleDidChangeTokens(e);
  161. this._onDidChangeTokens.fire(e);
  162. }
  163. }
  164. resetTokenization() {
  165. this._tokenization.reset();
  166. }
  167. forceTokenization(lineNumber) {
  168. if (lineNumber < 1 || lineNumber > this._textModel.getLineCount()) {
  169. throw new Error('Illegal value for lineNumber');
  170. }
  171. this._tokenization.forceTokenization(lineNumber);
  172. }
  173. isCheapToTokenize(lineNumber) {
  174. return this._tokenization.isCheapToTokenize(lineNumber);
  175. }
  176. tokenizeIfCheap(lineNumber) {
  177. if (this.isCheapToTokenize(lineNumber)) {
  178. this.forceTokenization(lineNumber);
  179. }
  180. }
  181. getLineTokens(lineNumber) {
  182. if (lineNumber < 1 || lineNumber > this._textModel.getLineCount()) {
  183. throw new Error('Illegal value for lineNumber');
  184. }
  185. return this._getLineTokens(lineNumber);
  186. }
  187. _getLineTokens(lineNumber) {
  188. const lineText = this._textModel.getLineContent(lineNumber);
  189. const syntacticTokens = this._tokens.getTokens(this._languageId, lineNumber - 1, lineText);
  190. return this._semanticTokens.addSparseTokens(lineNumber, syntacticTokens);
  191. }
  192. getTokenTypeIfInsertingCharacter(lineNumber, column, character) {
  193. const position = this._textModel.validatePosition(new Position(lineNumber, column));
  194. return this._tokenization.getTokenTypeIfInsertingCharacter(position, character);
  195. }
  196. tokenizeLineWithEdit(position, length, newText) {
  197. const validatedPosition = this._textModel.validatePosition(position);
  198. return this._tokenization.tokenizeLineWithEdit(validatedPosition, length, newText);
  199. }
  200. getLanguageConfiguration(languageId) {
  201. return this._languageConfigurationService.getLanguageConfiguration(languageId);
  202. }
  203. // Having tokens allows implementing additional helper methods
  204. getWordAtPosition(_position) {
  205. this.assertNotDisposed();
  206. const position = this._textModel.validatePosition(_position);
  207. const lineContent = this._textModel.getLineContent(position.lineNumber);
  208. const lineTokens = this._getLineTokens(position.lineNumber);
  209. const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
  210. // (1). First try checking right biased word
  211. const [rbStartOffset, rbEndOffset] = TokenizationTextModelPart._findLanguageBoundaries(lineTokens, tokenIndex);
  212. const rightBiasedWord = getWordAtText(position.column, this.getLanguageConfiguration(lineTokens.getLanguageId(tokenIndex)).getWordDefinition(), lineContent.substring(rbStartOffset, rbEndOffset), rbStartOffset);
  213. // Make sure the result touches the original passed in position
  214. if (rightBiasedWord &&
  215. rightBiasedWord.startColumn <= _position.column &&
  216. _position.column <= rightBiasedWord.endColumn) {
  217. return rightBiasedWord;
  218. }
  219. // (2). Else, if we were at a language boundary, check the left biased word
  220. if (tokenIndex > 0 && rbStartOffset === position.column - 1) {
  221. // edge case, where `position` sits between two tokens belonging to two different languages
  222. const [lbStartOffset, lbEndOffset] = TokenizationTextModelPart._findLanguageBoundaries(lineTokens, tokenIndex - 1);
  223. const leftBiasedWord = getWordAtText(position.column, this.getLanguageConfiguration(lineTokens.getLanguageId(tokenIndex - 1)).getWordDefinition(), lineContent.substring(lbStartOffset, lbEndOffset), lbStartOffset);
  224. // Make sure the result touches the original passed in position
  225. if (leftBiasedWord &&
  226. leftBiasedWord.startColumn <= _position.column &&
  227. _position.column <= leftBiasedWord.endColumn) {
  228. return leftBiasedWord;
  229. }
  230. }
  231. return null;
  232. }
  233. static _findLanguageBoundaries(lineTokens, tokenIndex) {
  234. const languageId = lineTokens.getLanguageId(tokenIndex);
  235. // go left until a different language is hit
  236. let startOffset = 0;
  237. for (let i = tokenIndex; i >= 0 && lineTokens.getLanguageId(i) === languageId; i--) {
  238. startOffset = lineTokens.getStartOffset(i);
  239. }
  240. // go right until a different language is hit
  241. let endOffset = lineTokens.getLineContent().length;
  242. for (let i = tokenIndex, tokenCount = lineTokens.getCount(); i < tokenCount && lineTokens.getLanguageId(i) === languageId; i++) {
  243. endOffset = lineTokens.getEndOffset(i);
  244. }
  245. return [startOffset, endOffset];
  246. }
  247. getWordUntilPosition(position) {
  248. const wordAtPosition = this.getWordAtPosition(position);
  249. if (!wordAtPosition) {
  250. return {
  251. word: '',
  252. startColumn: position.column,
  253. endColumn: position.column,
  254. };
  255. }
  256. return {
  257. word: wordAtPosition.word.substr(0, position.column - wordAtPosition.startColumn),
  258. startColumn: wordAtPosition.startColumn,
  259. endColumn: position.column,
  260. };
  261. }
  262. getLanguageId() {
  263. return this._languageId;
  264. }
  265. getLanguageIdAtPosition(lineNumber, column) {
  266. const position = this._textModel.validatePosition(new Position(lineNumber, column));
  267. const lineTokens = this.getLineTokens(position.lineNumber);
  268. return lineTokens.getLanguageId(lineTokens.findTokenIndexAtOffset(position.column - 1));
  269. }
  270. setLanguageId(languageId) {
  271. if (this._languageId === languageId) {
  272. // There's nothing to do
  273. return;
  274. }
  275. const e = {
  276. oldLanguage: this._languageId,
  277. newLanguage: languageId
  278. };
  279. this._languageId = languageId;
  280. this.bracketPairsTextModelPart.handleDidChangeLanguage(e);
  281. this._tokenization.handleDidChangeLanguage(e);
  282. this._onDidChangeLanguage.fire(e);
  283. this._onDidChangeLanguageConfiguration.fire({});
  284. }
  285. }