languageConfigurationRegistry.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  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. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __param = (this && this.__param) || function (paramIndex, decorator) {
  12. return function (target, key) { decorator(target, key, paramIndex); }
  13. };
  14. import { Emitter } from '../../../base/common/event.js';
  15. import { Disposable, toDisposable } from '../../../base/common/lifecycle.js';
  16. import * as strings from '../../../base/common/strings.js';
  17. import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from '../model/wordHelper.js';
  18. import { IndentAction, AutoClosingPairs } from './languageConfiguration.js';
  19. import { createScopedLineTokens } from './supports.js';
  20. import { CharacterPairSupport } from './supports/characterPair.js';
  21. import { BracketElectricCharacterSupport } from './supports/electricCharacter.js';
  22. import { IndentRulesSupport } from './supports/indentRules.js';
  23. import { OnEnterSupport } from './supports/onEnter.js';
  24. import { RichEditBrackets } from './supports/richEditBrackets.js';
  25. import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
  26. import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
  27. import { IModeService } from '../services/modeService.js';
  28. import { registerSingleton } from '../../../platform/instantiation/common/extensions.js';
  29. export class LanguageConfigurationServiceChangeEvent {
  30. constructor(languageId) {
  31. this.languageId = languageId;
  32. }
  33. affects(languageId) {
  34. return !this.languageId ? true : this.languageId === languageId;
  35. }
  36. }
  37. export const ILanguageConfigurationService = createDecorator('languageConfigurationService');
  38. let LanguageConfigurationService = class LanguageConfigurationService extends Disposable {
  39. constructor(configurationService, modeService) {
  40. super();
  41. this.configurationService = configurationService;
  42. this.modeService = modeService;
  43. this.onDidChangeEmitter = this._register(new Emitter());
  44. this.onDidChange = this.onDidChangeEmitter.event;
  45. this.configurations = new Map();
  46. const languageConfigKeys = new Set(Object.values(customizedLanguageConfigKeys));
  47. this._register(this.configurationService.onDidChangeConfiguration((e) => {
  48. const globalConfigChanged = e.change.keys.some((k) => languageConfigKeys.has(k));
  49. const localConfigChanged = e.change.overrides
  50. .filter(([overrideLangName, keys]) => keys.some((k) => languageConfigKeys.has(k)))
  51. .map(([overrideLangName]) => this.modeService.validateLanguageId(overrideLangName));
  52. if (globalConfigChanged) {
  53. this.configurations.clear();
  54. this.onDidChangeEmitter.fire(new LanguageConfigurationServiceChangeEvent(undefined));
  55. }
  56. else {
  57. for (const languageId of localConfigChanged) {
  58. if (languageId) {
  59. this.configurations.delete(languageId);
  60. this.onDidChangeEmitter.fire(new LanguageConfigurationServiceChangeEvent(languageId));
  61. }
  62. }
  63. }
  64. }));
  65. this._register(LanguageConfigurationRegistry.onDidChange((e) => {
  66. this.configurations.delete(e.languageId);
  67. this.onDidChangeEmitter.fire(new LanguageConfigurationServiceChangeEvent(e.languageId));
  68. }));
  69. }
  70. getLanguageConfiguration(languageId) {
  71. let result = this.configurations.get(languageId);
  72. if (!result) {
  73. result = computeConfig(languageId, this.configurationService, this.modeService);
  74. this.configurations.set(languageId, result);
  75. }
  76. return result;
  77. }
  78. };
  79. LanguageConfigurationService = __decorate([
  80. __param(0, IConfigurationService),
  81. __param(1, IModeService)
  82. ], LanguageConfigurationService);
  83. export { LanguageConfigurationService };
  84. function computeConfig(languageId, configurationService, modeService) {
  85. let languageConfig = LanguageConfigurationRegistry.getLanguageConfiguration(languageId);
  86. if (!languageConfig) {
  87. const validLanguageId = modeService.validateLanguageId(languageId);
  88. if (!validLanguageId) {
  89. throw new Error('Unexpected languageId');
  90. }
  91. languageConfig = new ResolvedLanguageConfiguration(validLanguageId, {});
  92. }
  93. const customizedConfig = getCustomizedLanguageConfig(languageConfig.languageId, configurationService);
  94. const data = combineLanguageConfigurations([languageConfig.underlyingConfig, customizedConfig]);
  95. const config = new ResolvedLanguageConfiguration(languageConfig.languageId, data);
  96. return config;
  97. }
  98. const customizedLanguageConfigKeys = {
  99. brackets: 'editor.language.brackets',
  100. colorizedBracketPairs: 'editor.language.colorizedBracketPairs'
  101. };
  102. function getCustomizedLanguageConfig(languageId, configurationService) {
  103. const brackets = configurationService.getValue(customizedLanguageConfigKeys.brackets, {
  104. overrideIdentifier: languageId,
  105. });
  106. const colorizedBracketPairs = configurationService.getValue(customizedLanguageConfigKeys.colorizedBracketPairs, {
  107. overrideIdentifier: languageId,
  108. });
  109. return {
  110. brackets: validateBracketPairs(brackets),
  111. colorizedBracketPairs: validateBracketPairs(colorizedBracketPairs),
  112. };
  113. }
  114. function validateBracketPairs(data) {
  115. if (!Array.isArray(data)) {
  116. return undefined;
  117. }
  118. return data.map(pair => {
  119. if (!Array.isArray(pair) || pair.length !== 2) {
  120. return undefined;
  121. }
  122. return [pair[0], pair[1]];
  123. }).filter((p) => !!p);
  124. }
  125. export class LanguageConfigurationChangeEvent {
  126. constructor(languageId) {
  127. this.languageId = languageId;
  128. }
  129. }
  130. export class LanguageConfigurationRegistryImpl {
  131. constructor() {
  132. this._entries = new Map();
  133. this._onDidChange = new Emitter();
  134. this.onDidChange = this._onDidChange.event;
  135. }
  136. /**
  137. * @param priority Use a higher number for higher priority
  138. */
  139. register(languageId, configuration, priority = 0) {
  140. let entries = this._entries.get(languageId);
  141. if (!entries) {
  142. entries = new ComposedLanguageConfiguration(languageId);
  143. this._entries.set(languageId, entries);
  144. }
  145. const disposable = entries.register(configuration, priority);
  146. this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageId));
  147. return toDisposable(() => {
  148. disposable.dispose();
  149. this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageId));
  150. });
  151. }
  152. getLanguageConfiguration(languageId) {
  153. let entries = this._entries.get(languageId);
  154. return (entries === null || entries === void 0 ? void 0 : entries.getResolvedConfiguration()) || null;
  155. }
  156. getIndentationRules(languageId) {
  157. const value = this.getLanguageConfiguration(languageId);
  158. return value ? value.indentationRules || null : null;
  159. }
  160. // begin electricCharacter
  161. _getElectricCharacterSupport(languageId) {
  162. let value = this.getLanguageConfiguration(languageId);
  163. if (!value) {
  164. return null;
  165. }
  166. return value.electricCharacter || null;
  167. }
  168. getElectricCharacters(languageId) {
  169. let electricCharacterSupport = this._getElectricCharacterSupport(languageId);
  170. if (!electricCharacterSupport) {
  171. return [];
  172. }
  173. return electricCharacterSupport.getElectricCharacters();
  174. }
  175. /**
  176. * Should return opening bracket type to match indentation with
  177. */
  178. onElectricCharacter(character, context, column) {
  179. let scopedLineTokens = createScopedLineTokens(context, column - 1);
  180. let electricCharacterSupport = this._getElectricCharacterSupport(scopedLineTokens.languageId);
  181. if (!electricCharacterSupport) {
  182. return null;
  183. }
  184. return electricCharacterSupport.onElectricCharacter(character, scopedLineTokens, column - scopedLineTokens.firstCharOffset);
  185. }
  186. // end electricCharacter
  187. getComments(languageId) {
  188. let value = this.getLanguageConfiguration(languageId);
  189. if (!value) {
  190. return null;
  191. }
  192. return value.comments || null;
  193. }
  194. // begin characterPair
  195. _getCharacterPairSupport(languageId) {
  196. let value = this.getLanguageConfiguration(languageId);
  197. if (!value) {
  198. return null;
  199. }
  200. return value.characterPair || null;
  201. }
  202. getAutoClosingPairs(languageId) {
  203. const characterPairSupport = this._getCharacterPairSupport(languageId);
  204. return new AutoClosingPairs(characterPairSupport ? characterPairSupport.getAutoClosingPairs() : []);
  205. }
  206. getAutoCloseBeforeSet(languageId) {
  207. let characterPairSupport = this._getCharacterPairSupport(languageId);
  208. if (!characterPairSupport) {
  209. return CharacterPairSupport.DEFAULT_AUTOCLOSE_BEFORE_LANGUAGE_DEFINED;
  210. }
  211. return characterPairSupport.getAutoCloseBeforeSet();
  212. }
  213. getSurroundingPairs(languageId) {
  214. let characterPairSupport = this._getCharacterPairSupport(languageId);
  215. if (!characterPairSupport) {
  216. return [];
  217. }
  218. return characterPairSupport.getSurroundingPairs();
  219. }
  220. // end characterPair
  221. getWordDefinition(languageId) {
  222. let value = this.getLanguageConfiguration(languageId);
  223. if (!value) {
  224. return ensureValidWordDefinition(null);
  225. }
  226. return ensureValidWordDefinition(value.wordDefinition || null);
  227. }
  228. getFoldingRules(languageId) {
  229. let value = this.getLanguageConfiguration(languageId);
  230. if (!value) {
  231. return {};
  232. }
  233. return value.foldingRules;
  234. }
  235. // begin Indent Rules
  236. getIndentRulesSupport(languageId) {
  237. let value = this.getLanguageConfiguration(languageId);
  238. if (!value) {
  239. return null;
  240. }
  241. return value.indentRulesSupport || null;
  242. }
  243. /**
  244. * Get nearest preceding line which doesn't match unIndentPattern or contains all whitespace.
  245. * Result:
  246. * -1: run into the boundary of embedded languages
  247. * 0: every line above are invalid
  248. * else: nearest preceding line of the same language
  249. */
  250. getPrecedingValidLine(model, lineNumber, indentRulesSupport) {
  251. let languageID = model.getLanguageIdAtPosition(lineNumber, 0);
  252. if (lineNumber > 1) {
  253. let lastLineNumber;
  254. let resultLineNumber = -1;
  255. for (lastLineNumber = lineNumber - 1; lastLineNumber >= 1; lastLineNumber--) {
  256. if (model.getLanguageIdAtPosition(lastLineNumber, 0) !== languageID) {
  257. return resultLineNumber;
  258. }
  259. let text = model.getLineContent(lastLineNumber);
  260. if (indentRulesSupport.shouldIgnore(text) || /^\s+$/.test(text) || text === '') {
  261. resultLineNumber = lastLineNumber;
  262. continue;
  263. }
  264. return lastLineNumber;
  265. }
  266. }
  267. return -1;
  268. }
  269. /**
  270. * Get inherited indentation from above lines.
  271. * 1. Find the nearest preceding line which doesn't match unIndentedLinePattern.
  272. * 2. If this line matches indentNextLinePattern or increaseIndentPattern, it means that the indent level of `lineNumber` should be 1 greater than this line.
  273. * 3. If this line doesn't match any indent rules
  274. * a. check whether the line above it matches indentNextLinePattern
  275. * b. If not, the indent level of this line is the result
  276. * c. If so, it means the indent of this line is *temporary*, go upward utill we find a line whose indent is not temporary (the same workflow a -> b -> c).
  277. * 4. Otherwise, we fail to get an inherited indent from aboves. Return null and we should not touch the indent of `lineNumber`
  278. *
  279. * This function only return the inherited indent based on above lines, it doesn't check whether current line should decrease or not.
  280. */
  281. getInheritIndentForLine(autoIndent, model, lineNumber, honorIntentialIndent = true) {
  282. if (autoIndent < 4 /* Full */) {
  283. return null;
  284. }
  285. const indentRulesSupport = this.getIndentRulesSupport(model.getLanguageId());
  286. if (!indentRulesSupport) {
  287. return null;
  288. }
  289. if (lineNumber <= 1) {
  290. return {
  291. indentation: '',
  292. action: null
  293. };
  294. }
  295. const precedingUnIgnoredLine = this.getPrecedingValidLine(model, lineNumber, indentRulesSupport);
  296. if (precedingUnIgnoredLine < 0) {
  297. return null;
  298. }
  299. else if (precedingUnIgnoredLine < 1) {
  300. return {
  301. indentation: '',
  302. action: null
  303. };
  304. }
  305. const precedingUnIgnoredLineContent = model.getLineContent(precedingUnIgnoredLine);
  306. if (indentRulesSupport.shouldIncrease(precedingUnIgnoredLineContent) || indentRulesSupport.shouldIndentNextLine(precedingUnIgnoredLineContent)) {
  307. return {
  308. indentation: strings.getLeadingWhitespace(precedingUnIgnoredLineContent),
  309. action: IndentAction.Indent,
  310. line: precedingUnIgnoredLine
  311. };
  312. }
  313. else if (indentRulesSupport.shouldDecrease(precedingUnIgnoredLineContent)) {
  314. return {
  315. indentation: strings.getLeadingWhitespace(precedingUnIgnoredLineContent),
  316. action: null,
  317. line: precedingUnIgnoredLine
  318. };
  319. }
  320. else {
  321. // precedingUnIgnoredLine can not be ignored.
  322. // it doesn't increase indent of following lines
  323. // it doesn't increase just next line
  324. // so current line is not affect by precedingUnIgnoredLine
  325. // and then we should get a correct inheritted indentation from above lines
  326. if (precedingUnIgnoredLine === 1) {
  327. return {
  328. indentation: strings.getLeadingWhitespace(model.getLineContent(precedingUnIgnoredLine)),
  329. action: null,
  330. line: precedingUnIgnoredLine
  331. };
  332. }
  333. const previousLine = precedingUnIgnoredLine - 1;
  334. const previousLineIndentMetadata = indentRulesSupport.getIndentMetadata(model.getLineContent(previousLine));
  335. if (!(previousLineIndentMetadata & (1 /* INCREASE_MASK */ | 2 /* DECREASE_MASK */)) &&
  336. (previousLineIndentMetadata & 4 /* INDENT_NEXTLINE_MASK */)) {
  337. let stopLine = 0;
  338. for (let i = previousLine - 1; i > 0; i--) {
  339. if (indentRulesSupport.shouldIndentNextLine(model.getLineContent(i))) {
  340. continue;
  341. }
  342. stopLine = i;
  343. break;
  344. }
  345. return {
  346. indentation: strings.getLeadingWhitespace(model.getLineContent(stopLine + 1)),
  347. action: null,
  348. line: stopLine + 1
  349. };
  350. }
  351. if (honorIntentialIndent) {
  352. return {
  353. indentation: strings.getLeadingWhitespace(model.getLineContent(precedingUnIgnoredLine)),
  354. action: null,
  355. line: precedingUnIgnoredLine
  356. };
  357. }
  358. else {
  359. // search from precedingUnIgnoredLine until we find one whose indent is not temporary
  360. for (let i = precedingUnIgnoredLine; i > 0; i--) {
  361. const lineContent = model.getLineContent(i);
  362. if (indentRulesSupport.shouldIncrease(lineContent)) {
  363. return {
  364. indentation: strings.getLeadingWhitespace(lineContent),
  365. action: IndentAction.Indent,
  366. line: i
  367. };
  368. }
  369. else if (indentRulesSupport.shouldIndentNextLine(lineContent)) {
  370. let stopLine = 0;
  371. for (let j = i - 1; j > 0; j--) {
  372. if (indentRulesSupport.shouldIndentNextLine(model.getLineContent(i))) {
  373. continue;
  374. }
  375. stopLine = j;
  376. break;
  377. }
  378. return {
  379. indentation: strings.getLeadingWhitespace(model.getLineContent(stopLine + 1)),
  380. action: null,
  381. line: stopLine + 1
  382. };
  383. }
  384. else if (indentRulesSupport.shouldDecrease(lineContent)) {
  385. return {
  386. indentation: strings.getLeadingWhitespace(lineContent),
  387. action: null,
  388. line: i
  389. };
  390. }
  391. }
  392. return {
  393. indentation: strings.getLeadingWhitespace(model.getLineContent(1)),
  394. action: null,
  395. line: 1
  396. };
  397. }
  398. }
  399. }
  400. getGoodIndentForLine(autoIndent, virtualModel, languageId, lineNumber, indentConverter) {
  401. if (autoIndent < 4 /* Full */) {
  402. return null;
  403. }
  404. const richEditSupport = this.getLanguageConfiguration(languageId);
  405. if (!richEditSupport) {
  406. return null;
  407. }
  408. const indentRulesSupport = this.getIndentRulesSupport(languageId);
  409. if (!indentRulesSupport) {
  410. return null;
  411. }
  412. const indent = this.getInheritIndentForLine(autoIndent, virtualModel, lineNumber);
  413. const lineContent = virtualModel.getLineContent(lineNumber);
  414. if (indent) {
  415. const inheritLine = indent.line;
  416. if (inheritLine !== undefined) {
  417. const enterResult = richEditSupport.onEnter(autoIndent, '', virtualModel.getLineContent(inheritLine), '');
  418. if (enterResult) {
  419. let indentation = strings.getLeadingWhitespace(virtualModel.getLineContent(inheritLine));
  420. if (enterResult.removeText) {
  421. indentation = indentation.substring(0, indentation.length - enterResult.removeText);
  422. }
  423. if ((enterResult.indentAction === IndentAction.Indent) ||
  424. (enterResult.indentAction === IndentAction.IndentOutdent)) {
  425. indentation = indentConverter.shiftIndent(indentation);
  426. }
  427. else if (enterResult.indentAction === IndentAction.Outdent) {
  428. indentation = indentConverter.unshiftIndent(indentation);
  429. }
  430. if (indentRulesSupport.shouldDecrease(lineContent)) {
  431. indentation = indentConverter.unshiftIndent(indentation);
  432. }
  433. if (enterResult.appendText) {
  434. indentation += enterResult.appendText;
  435. }
  436. return strings.getLeadingWhitespace(indentation);
  437. }
  438. }
  439. if (indentRulesSupport.shouldDecrease(lineContent)) {
  440. if (indent.action === IndentAction.Indent) {
  441. return indent.indentation;
  442. }
  443. else {
  444. return indentConverter.unshiftIndent(indent.indentation);
  445. }
  446. }
  447. else {
  448. if (indent.action === IndentAction.Indent) {
  449. return indentConverter.shiftIndent(indent.indentation);
  450. }
  451. else {
  452. return indent.indentation;
  453. }
  454. }
  455. }
  456. return null;
  457. }
  458. getIndentForEnter(autoIndent, model, range, indentConverter) {
  459. if (autoIndent < 4 /* Full */) {
  460. return null;
  461. }
  462. model.forceTokenization(range.startLineNumber);
  463. const lineTokens = model.getLineTokens(range.startLineNumber);
  464. const scopedLineTokens = createScopedLineTokens(lineTokens, range.startColumn - 1);
  465. const scopedLineText = scopedLineTokens.getLineContent();
  466. let embeddedLanguage = false;
  467. let beforeEnterText;
  468. if (scopedLineTokens.firstCharOffset > 0 && lineTokens.getLanguageId(0) !== scopedLineTokens.languageId) {
  469. // we are in the embeded language content
  470. embeddedLanguage = true; // if embeddedLanguage is true, then we don't touch the indentation of current line
  471. beforeEnterText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset);
  472. }
  473. else {
  474. beforeEnterText = lineTokens.getLineContent().substring(0, range.startColumn - 1);
  475. }
  476. let afterEnterText;
  477. if (range.isEmpty()) {
  478. afterEnterText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset);
  479. }
  480. else {
  481. const endScopedLineTokens = this.getScopedLineTokens(model, range.endLineNumber, range.endColumn);
  482. afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset);
  483. }
  484. const indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId);
  485. if (!indentRulesSupport) {
  486. return null;
  487. }
  488. const beforeEnterResult = beforeEnterText;
  489. const beforeEnterIndent = strings.getLeadingWhitespace(beforeEnterText);
  490. const virtualModel = {
  491. getLineTokens: (lineNumber) => {
  492. return model.getLineTokens(lineNumber);
  493. },
  494. getLanguageId: () => {
  495. return model.getLanguageId();
  496. },
  497. getLanguageIdAtPosition: (lineNumber, column) => {
  498. return model.getLanguageIdAtPosition(lineNumber, column);
  499. },
  500. getLineContent: (lineNumber) => {
  501. if (lineNumber === range.startLineNumber) {
  502. return beforeEnterResult;
  503. }
  504. else {
  505. return model.getLineContent(lineNumber);
  506. }
  507. }
  508. };
  509. const currentLineIndent = strings.getLeadingWhitespace(lineTokens.getLineContent());
  510. const afterEnterAction = this.getInheritIndentForLine(autoIndent, virtualModel, range.startLineNumber + 1);
  511. if (!afterEnterAction) {
  512. const beforeEnter = embeddedLanguage ? currentLineIndent : beforeEnterIndent;
  513. return {
  514. beforeEnter: beforeEnter,
  515. afterEnter: beforeEnter
  516. };
  517. }
  518. let afterEnterIndent = embeddedLanguage ? currentLineIndent : afterEnterAction.indentation;
  519. if (afterEnterAction.action === IndentAction.Indent) {
  520. afterEnterIndent = indentConverter.shiftIndent(afterEnterIndent);
  521. }
  522. if (indentRulesSupport.shouldDecrease(afterEnterText)) {
  523. afterEnterIndent = indentConverter.unshiftIndent(afterEnterIndent);
  524. }
  525. return {
  526. beforeEnter: embeddedLanguage ? currentLineIndent : beforeEnterIndent,
  527. afterEnter: afterEnterIndent
  528. };
  529. }
  530. /**
  531. * We should always allow intentional indentation. It means, if users change the indentation of `lineNumber` and the content of
  532. * this line doesn't match decreaseIndentPattern, we should not adjust the indentation.
  533. */
  534. getIndentActionForType(autoIndent, model, range, ch, indentConverter) {
  535. if (autoIndent < 4 /* Full */) {
  536. return null;
  537. }
  538. const scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn);
  539. if (scopedLineTokens.firstCharOffset) {
  540. // this line has mixed languages and indentation rules will not work
  541. return null;
  542. }
  543. const indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId);
  544. if (!indentRulesSupport) {
  545. return null;
  546. }
  547. const scopedLineText = scopedLineTokens.getLineContent();
  548. const beforeTypeText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset);
  549. // selection support
  550. let afterTypeText;
  551. if (range.isEmpty()) {
  552. afterTypeText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset);
  553. }
  554. else {
  555. const endScopedLineTokens = this.getScopedLineTokens(model, range.endLineNumber, range.endColumn);
  556. afterTypeText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset);
  557. }
  558. // If previous content already matches decreaseIndentPattern, it means indentation of this line should already be adjusted
  559. // Users might change the indentation by purpose and we should honor that instead of readjusting.
  560. if (!indentRulesSupport.shouldDecrease(beforeTypeText + afterTypeText) && indentRulesSupport.shouldDecrease(beforeTypeText + ch + afterTypeText)) {
  561. // after typing `ch`, the content matches decreaseIndentPattern, we should adjust the indent to a good manner.
  562. // 1. Get inherited indent action
  563. const r = this.getInheritIndentForLine(autoIndent, model, range.startLineNumber, false);
  564. if (!r) {
  565. return null;
  566. }
  567. let indentation = r.indentation;
  568. if (r.action !== IndentAction.Indent) {
  569. indentation = indentConverter.unshiftIndent(indentation);
  570. }
  571. return indentation;
  572. }
  573. return null;
  574. }
  575. getIndentMetadata(model, lineNumber) {
  576. const indentRulesSupport = this.getIndentRulesSupport(model.getLanguageId());
  577. if (!indentRulesSupport) {
  578. return null;
  579. }
  580. if (lineNumber < 1 || lineNumber > model.getLineCount()) {
  581. return null;
  582. }
  583. return indentRulesSupport.getIndentMetadata(model.getLineContent(lineNumber));
  584. }
  585. // end Indent Rules
  586. // begin onEnter
  587. getEnterAction(autoIndent, model, range) {
  588. const scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn);
  589. const richEditSupport = this.getLanguageConfiguration(scopedLineTokens.languageId);
  590. if (!richEditSupport) {
  591. return null;
  592. }
  593. const scopedLineText = scopedLineTokens.getLineContent();
  594. const beforeEnterText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset);
  595. // selection support
  596. let afterEnterText;
  597. if (range.isEmpty()) {
  598. afterEnterText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset);
  599. }
  600. else {
  601. const endScopedLineTokens = this.getScopedLineTokens(model, range.endLineNumber, range.endColumn);
  602. afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset);
  603. }
  604. let previousLineText = '';
  605. if (range.startLineNumber > 1 && scopedLineTokens.firstCharOffset === 0) {
  606. // This is not the first line and the entire line belongs to this mode
  607. const oneLineAboveScopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber - 1);
  608. if (oneLineAboveScopedLineTokens.languageId === scopedLineTokens.languageId) {
  609. // The line above ends with text belonging to the same mode
  610. previousLineText = oneLineAboveScopedLineTokens.getLineContent();
  611. }
  612. }
  613. const enterResult = richEditSupport.onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText);
  614. if (!enterResult) {
  615. return null;
  616. }
  617. const indentAction = enterResult.indentAction;
  618. let appendText = enterResult.appendText;
  619. const removeText = enterResult.removeText || 0;
  620. // Here we add `\t` to appendText first because enterAction is leveraging appendText and removeText to change indentation.
  621. if (!appendText) {
  622. if ((indentAction === IndentAction.Indent) ||
  623. (indentAction === IndentAction.IndentOutdent)) {
  624. appendText = '\t';
  625. }
  626. else {
  627. appendText = '';
  628. }
  629. }
  630. else if (indentAction === IndentAction.Indent) {
  631. appendText = '\t' + appendText;
  632. }
  633. let indentation = this.getIndentationAtPosition(model, range.startLineNumber, range.startColumn);
  634. if (removeText) {
  635. indentation = indentation.substring(0, indentation.length - removeText);
  636. }
  637. return {
  638. indentAction: indentAction,
  639. appendText: appendText,
  640. removeText: removeText,
  641. indentation: indentation
  642. };
  643. }
  644. getIndentationAtPosition(model, lineNumber, column) {
  645. const lineText = model.getLineContent(lineNumber);
  646. let indentation = strings.getLeadingWhitespace(lineText);
  647. if (indentation.length > column - 1) {
  648. indentation = indentation.substring(0, column - 1);
  649. }
  650. return indentation;
  651. }
  652. getScopedLineTokens(model, lineNumber, columnNumber) {
  653. model.forceTokenization(lineNumber);
  654. const lineTokens = model.getLineTokens(lineNumber);
  655. const column = (typeof columnNumber === 'undefined' ? model.getLineMaxColumn(lineNumber) - 1 : columnNumber - 1);
  656. return createScopedLineTokens(lineTokens, column);
  657. }
  658. }
  659. export const LanguageConfigurationRegistry = new LanguageConfigurationRegistryImpl();
  660. class ComposedLanguageConfiguration {
  661. constructor(languageId) {
  662. this.languageId = languageId;
  663. this._resolved = null;
  664. this._entries = [];
  665. this._order = 0;
  666. this._resolved = null;
  667. }
  668. register(configuration, priority) {
  669. const entry = new LanguageConfigurationContribution(configuration, priority, ++this._order);
  670. this._entries.push(entry);
  671. this._resolved = null;
  672. return toDisposable(() => {
  673. for (let i = 0; i < this._entries.length; i++) {
  674. if (this._entries[i] === entry) {
  675. this._entries.splice(i, 1);
  676. this._resolved = null;
  677. break;
  678. }
  679. }
  680. });
  681. }
  682. getResolvedConfiguration() {
  683. if (!this._resolved) {
  684. const config = this._resolve();
  685. if (config) {
  686. this._resolved = new ResolvedLanguageConfiguration(this.languageId, config);
  687. }
  688. }
  689. return this._resolved;
  690. }
  691. _resolve() {
  692. if (this._entries.length === 0) {
  693. return null;
  694. }
  695. this._entries.sort(LanguageConfigurationContribution.cmp);
  696. return combineLanguageConfigurations(this._entries.map(e => e.configuration));
  697. }
  698. }
  699. function combineLanguageConfigurations(configs) {
  700. let result = {
  701. comments: undefined,
  702. brackets: undefined,
  703. wordPattern: undefined,
  704. indentationRules: undefined,
  705. onEnterRules: undefined,
  706. autoClosingPairs: undefined,
  707. surroundingPairs: undefined,
  708. autoCloseBefore: undefined,
  709. folding: undefined,
  710. colorizedBracketPairs: undefined,
  711. __electricCharacterSupport: undefined,
  712. };
  713. for (const entry of configs) {
  714. result = {
  715. comments: entry.comments || result.comments,
  716. brackets: entry.brackets || result.brackets,
  717. wordPattern: entry.wordPattern || result.wordPattern,
  718. indentationRules: entry.indentationRules || result.indentationRules,
  719. onEnterRules: entry.onEnterRules || result.onEnterRules,
  720. autoClosingPairs: entry.autoClosingPairs || result.autoClosingPairs,
  721. surroundingPairs: entry.surroundingPairs || result.surroundingPairs,
  722. autoCloseBefore: entry.autoCloseBefore || result.autoCloseBefore,
  723. folding: entry.folding || result.folding,
  724. colorizedBracketPairs: entry.colorizedBracketPairs || result.colorizedBracketPairs,
  725. __electricCharacterSupport: entry.__electricCharacterSupport || result.__electricCharacterSupport,
  726. };
  727. }
  728. return result;
  729. }
  730. class LanguageConfigurationContribution {
  731. constructor(configuration, priority, order) {
  732. this.configuration = configuration;
  733. this.priority = priority;
  734. this.order = order;
  735. }
  736. static cmp(a, b) {
  737. if (a.priority === b.priority) {
  738. // higher order last
  739. return a.order - b.order;
  740. }
  741. // higher priority last
  742. return a.priority - b.priority;
  743. }
  744. }
  745. /**
  746. * Immutable.
  747. */
  748. export class ResolvedLanguageConfiguration {
  749. constructor(languageId, underlyingConfig) {
  750. this.languageId = languageId;
  751. this.underlyingConfig = underlyingConfig;
  752. this._brackets = null;
  753. this._electricCharacter = null;
  754. this._onEnterSupport =
  755. this.underlyingConfig.brackets ||
  756. this.underlyingConfig.indentationRules ||
  757. this.underlyingConfig.onEnterRules
  758. ? new OnEnterSupport(this.underlyingConfig)
  759. : null;
  760. this.comments = ResolvedLanguageConfiguration._handleComments(this.underlyingConfig);
  761. this.characterPair = new CharacterPairSupport(this.underlyingConfig);
  762. this.wordDefinition = this.underlyingConfig.wordPattern || DEFAULT_WORD_REGEXP;
  763. this.indentationRules = this.underlyingConfig.indentationRules;
  764. if (this.underlyingConfig.indentationRules) {
  765. this.indentRulesSupport = new IndentRulesSupport(this.underlyingConfig.indentationRules);
  766. }
  767. else {
  768. this.indentRulesSupport = null;
  769. }
  770. this.foldingRules = this.underlyingConfig.folding || {};
  771. }
  772. getWordDefinition() {
  773. return ensureValidWordDefinition(this.wordDefinition);
  774. }
  775. get brackets() {
  776. if (!this._brackets && this.underlyingConfig.brackets) {
  777. this._brackets = new RichEditBrackets(this.languageId, this.underlyingConfig.brackets);
  778. }
  779. return this._brackets;
  780. }
  781. get electricCharacter() {
  782. if (!this._electricCharacter) {
  783. this._electricCharacter = new BracketElectricCharacterSupport(this.brackets);
  784. }
  785. return this._electricCharacter;
  786. }
  787. onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText) {
  788. if (!this._onEnterSupport) {
  789. return null;
  790. }
  791. return this._onEnterSupport.onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText);
  792. }
  793. static _handleComments(conf) {
  794. let commentRule = conf.comments;
  795. if (!commentRule) {
  796. return null;
  797. }
  798. // comment configuration
  799. let comments = {};
  800. if (commentRule.lineComment) {
  801. comments.lineCommentToken = commentRule.lineComment;
  802. }
  803. if (commentRule.blockComment) {
  804. let [blockStart, blockEnd] = commentRule.blockComment;
  805. comments.blockCommentStartToken = blockStart;
  806. comments.blockCommentEndToken = blockEnd;
  807. }
  808. return comments;
  809. }
  810. }
  811. registerSingleton(ILanguageConfigurationService, LanguageConfigurationService);