9e927e8a56d3a1caaae39713a7e50d2964b19cd25c382abb2ef33ce3cbf59bae4a42b98e6bb7158841eef03bc3480f17d112379b91daf9690e5af275e8eec7 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  6. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  7. return new (P || (P = Promise))(function (resolve, reject) {
  8. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  9. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  10. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  11. step((generator = generator.apply(thisArg, _arguments || [])).next());
  12. });
  13. };
  14. import { alert } from '../../../../base/browser/ui/aria/aria.js';
  15. import { asArray, isNonEmptyArray } from '../../../../base/common/arrays.js';
  16. import { CancellationToken } from '../../../../base/common/cancellation.js';
  17. import { onUnexpectedExternalError } from '../../../../base/common/errors.js';
  18. import { Iterable } from '../../../../base/common/iterator.js';
  19. import { LinkedList } from '../../../../base/common/linkedList.js';
  20. import { assertType } from '../../../../base/common/types.js';
  21. import { URI } from '../../../../base/common/uri.js';
  22. import { EditorStateCancellationTokenSource, TextModelCancellationTokenSource } from '../../editorState/browser/editorState.js';
  23. import { isCodeEditor } from '../../../browser/editorBrowser.js';
  24. import { Position } from '../../../common/core/position.js';
  25. import { Range } from '../../../common/core/range.js';
  26. import { Selection } from '../../../common/core/selection.js';
  27. import { IEditorWorkerService } from '../../../common/services/editorWorker.js';
  28. import { ITextModelService } from '../../../common/services/resolverService.js';
  29. import { FormattingEdit } from './formattingEdit.js';
  30. import * as nls from '../../../../nls.js';
  31. import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
  32. import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
  33. import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
  34. import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
  35. export function alertFormattingEdits(edits) {
  36. edits = edits.filter(edit => edit.range);
  37. if (!edits.length) {
  38. return;
  39. }
  40. let { range } = edits[0];
  41. for (let i = 1; i < edits.length; i++) {
  42. range = Range.plusRange(range, edits[i].range);
  43. }
  44. const { startLineNumber, endLineNumber } = range;
  45. if (startLineNumber === endLineNumber) {
  46. if (edits.length === 1) {
  47. alert(nls.localize('hint11', "Made 1 formatting edit on line {0}", startLineNumber));
  48. }
  49. else {
  50. alert(nls.localize('hintn1', "Made {0} formatting edits on line {1}", edits.length, startLineNumber));
  51. }
  52. }
  53. else {
  54. if (edits.length === 1) {
  55. alert(nls.localize('hint1n', "Made 1 formatting edit between lines {0} and {1}", startLineNumber, endLineNumber));
  56. }
  57. else {
  58. alert(nls.localize('hintnn', "Made {0} formatting edits between lines {1} and {2}", edits.length, startLineNumber, endLineNumber));
  59. }
  60. }
  61. }
  62. export function getRealAndSyntheticDocumentFormattersOrdered(documentFormattingEditProvider, documentRangeFormattingEditProvider, model) {
  63. const result = [];
  64. const seen = new Set();
  65. // (1) add all document formatter
  66. const docFormatter = documentFormattingEditProvider.ordered(model);
  67. for (const formatter of docFormatter) {
  68. result.push(formatter);
  69. if (formatter.extensionId) {
  70. seen.add(ExtensionIdentifier.toKey(formatter.extensionId));
  71. }
  72. }
  73. // (2) add all range formatter as document formatter (unless the same extension already did that)
  74. const rangeFormatter = documentRangeFormattingEditProvider.ordered(model);
  75. for (const formatter of rangeFormatter) {
  76. if (formatter.extensionId) {
  77. if (seen.has(ExtensionIdentifier.toKey(formatter.extensionId))) {
  78. continue;
  79. }
  80. seen.add(ExtensionIdentifier.toKey(formatter.extensionId));
  81. }
  82. result.push({
  83. displayName: formatter.displayName,
  84. extensionId: formatter.extensionId,
  85. provideDocumentFormattingEdits(model, options, token) {
  86. return formatter.provideDocumentRangeFormattingEdits(model, model.getFullModelRange(), options, token);
  87. }
  88. });
  89. }
  90. return result;
  91. }
  92. export class FormattingConflicts {
  93. static setFormatterSelector(selector) {
  94. const remove = FormattingConflicts._selectors.unshift(selector);
  95. return { dispose: remove };
  96. }
  97. static select(formatter, document, mode) {
  98. return __awaiter(this, void 0, void 0, function* () {
  99. if (formatter.length === 0) {
  100. return undefined;
  101. }
  102. const selector = Iterable.first(FormattingConflicts._selectors);
  103. if (selector) {
  104. return yield selector(formatter, document, mode);
  105. }
  106. return undefined;
  107. });
  108. }
  109. }
  110. FormattingConflicts._selectors = new LinkedList();
  111. export function formatDocumentRangesWithSelectedProvider(accessor, editorOrModel, rangeOrRanges, mode, progress, token) {
  112. return __awaiter(this, void 0, void 0, function* () {
  113. const instaService = accessor.get(IInstantiationService);
  114. const { documentRangeFormattingEditProvider: documentRangeFormattingEditProviderRegistry } = accessor.get(ILanguageFeaturesService);
  115. const model = isCodeEditor(editorOrModel) ? editorOrModel.getModel() : editorOrModel;
  116. const provider = documentRangeFormattingEditProviderRegistry.ordered(model);
  117. const selected = yield FormattingConflicts.select(provider, model, mode);
  118. if (selected) {
  119. progress.report(selected);
  120. yield instaService.invokeFunction(formatDocumentRangesWithProvider, selected, editorOrModel, rangeOrRanges, token);
  121. }
  122. });
  123. }
  124. export function formatDocumentRangesWithProvider(accessor, provider, editorOrModel, rangeOrRanges, token) {
  125. return __awaiter(this, void 0, void 0, function* () {
  126. const workerService = accessor.get(IEditorWorkerService);
  127. let model;
  128. let cts;
  129. if (isCodeEditor(editorOrModel)) {
  130. model = editorOrModel.getModel();
  131. cts = new EditorStateCancellationTokenSource(editorOrModel, 1 /* CodeEditorStateFlag.Value */ | 4 /* CodeEditorStateFlag.Position */, undefined, token);
  132. }
  133. else {
  134. model = editorOrModel;
  135. cts = new TextModelCancellationTokenSource(editorOrModel, token);
  136. }
  137. // make sure that ranges don't overlap nor touch each other
  138. const ranges = [];
  139. let len = 0;
  140. for (const range of asArray(rangeOrRanges).sort(Range.compareRangesUsingStarts)) {
  141. if (len > 0 && Range.areIntersectingOrTouching(ranges[len - 1], range)) {
  142. ranges[len - 1] = Range.fromPositions(ranges[len - 1].getStartPosition(), range.getEndPosition());
  143. }
  144. else {
  145. len = ranges.push(range);
  146. }
  147. }
  148. const computeEdits = (range) => __awaiter(this, void 0, void 0, function* () {
  149. return (yield provider.provideDocumentRangeFormattingEdits(model, range, model.getFormattingOptions(), cts.token)) || [];
  150. });
  151. const hasIntersectingEdit = (a, b) => {
  152. if (!a.length || !b.length) {
  153. return false;
  154. }
  155. // quick exit if the list of ranges are completely unrelated [O(n)]
  156. const mergedA = a.reduce((acc, val) => { return Range.plusRange(acc, val.range); }, a[0].range);
  157. if (!b.some(x => { return Range.intersectRanges(mergedA, x.range); })) {
  158. return false;
  159. }
  160. // fallback to a complete check [O(n^2)]
  161. for (const edit of a) {
  162. for (const otherEdit of b) {
  163. if (Range.intersectRanges(edit.range, otherEdit.range)) {
  164. return true;
  165. }
  166. }
  167. }
  168. return false;
  169. };
  170. const allEdits = [];
  171. const rawEditsList = [];
  172. try {
  173. for (const range of ranges) {
  174. if (cts.token.isCancellationRequested) {
  175. return true;
  176. }
  177. rawEditsList.push(yield computeEdits(range));
  178. }
  179. for (let i = 0; i < ranges.length; ++i) {
  180. for (let j = i + 1; j < ranges.length; ++j) {
  181. if (cts.token.isCancellationRequested) {
  182. return true;
  183. }
  184. if (hasIntersectingEdit(rawEditsList[i], rawEditsList[j])) {
  185. // Merge ranges i and j into a single range, recompute the associated edits
  186. const mergedRange = Range.plusRange(ranges[i], ranges[j]);
  187. const edits = yield computeEdits(mergedRange);
  188. ranges.splice(j, 1);
  189. ranges.splice(i, 1);
  190. ranges.push(mergedRange);
  191. rawEditsList.splice(j, 1);
  192. rawEditsList.splice(i, 1);
  193. rawEditsList.push(edits);
  194. // Restart scanning
  195. i = 0;
  196. j = 0;
  197. }
  198. }
  199. }
  200. for (const rawEdits of rawEditsList) {
  201. if (cts.token.isCancellationRequested) {
  202. return true;
  203. }
  204. const minimalEdits = yield workerService.computeMoreMinimalEdits(model.uri, rawEdits);
  205. if (minimalEdits) {
  206. allEdits.push(...minimalEdits);
  207. }
  208. }
  209. }
  210. finally {
  211. cts.dispose();
  212. }
  213. if (allEdits.length === 0) {
  214. return false;
  215. }
  216. if (isCodeEditor(editorOrModel)) {
  217. // use editor to apply edits
  218. FormattingEdit.execute(editorOrModel, allEdits, true);
  219. alertFormattingEdits(allEdits);
  220. editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), 1 /* ScrollType.Immediate */);
  221. }
  222. else {
  223. // use model to apply edits
  224. const [{ range }] = allEdits;
  225. const initialSelection = new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
  226. model.pushEditOperations([initialSelection], allEdits.map(edit => {
  227. return {
  228. text: edit.text,
  229. range: Range.lift(edit.range),
  230. forceMoveMarkers: true
  231. };
  232. }), undoEdits => {
  233. for (const { range } of undoEdits) {
  234. if (Range.areIntersectingOrTouching(range, initialSelection)) {
  235. return [new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)];
  236. }
  237. }
  238. return null;
  239. });
  240. }
  241. return true;
  242. });
  243. }
  244. export function formatDocumentWithSelectedProvider(accessor, editorOrModel, mode, progress, token) {
  245. return __awaiter(this, void 0, void 0, function* () {
  246. const instaService = accessor.get(IInstantiationService);
  247. const languageFeaturesService = accessor.get(ILanguageFeaturesService);
  248. const model = isCodeEditor(editorOrModel) ? editorOrModel.getModel() : editorOrModel;
  249. const provider = getRealAndSyntheticDocumentFormattersOrdered(languageFeaturesService.documentFormattingEditProvider, languageFeaturesService.documentRangeFormattingEditProvider, model);
  250. const selected = yield FormattingConflicts.select(provider, model, mode);
  251. if (selected) {
  252. progress.report(selected);
  253. yield instaService.invokeFunction(formatDocumentWithProvider, selected, editorOrModel, mode, token);
  254. }
  255. });
  256. }
  257. export function formatDocumentWithProvider(accessor, provider, editorOrModel, mode, token) {
  258. return __awaiter(this, void 0, void 0, function* () {
  259. const workerService = accessor.get(IEditorWorkerService);
  260. let model;
  261. let cts;
  262. if (isCodeEditor(editorOrModel)) {
  263. model = editorOrModel.getModel();
  264. cts = new EditorStateCancellationTokenSource(editorOrModel, 1 /* CodeEditorStateFlag.Value */ | 4 /* CodeEditorStateFlag.Position */, undefined, token);
  265. }
  266. else {
  267. model = editorOrModel;
  268. cts = new TextModelCancellationTokenSource(editorOrModel, token);
  269. }
  270. let edits;
  271. try {
  272. const rawEdits = yield provider.provideDocumentFormattingEdits(model, model.getFormattingOptions(), cts.token);
  273. edits = yield workerService.computeMoreMinimalEdits(model.uri, rawEdits);
  274. if (cts.token.isCancellationRequested) {
  275. return true;
  276. }
  277. }
  278. finally {
  279. cts.dispose();
  280. }
  281. if (!edits || edits.length === 0) {
  282. return false;
  283. }
  284. if (isCodeEditor(editorOrModel)) {
  285. // use editor to apply edits
  286. FormattingEdit.execute(editorOrModel, edits, mode !== 2 /* FormattingMode.Silent */);
  287. if (mode !== 2 /* FormattingMode.Silent */) {
  288. alertFormattingEdits(edits);
  289. editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), 1 /* ScrollType.Immediate */);
  290. }
  291. }
  292. else {
  293. // use model to apply edits
  294. const [{ range }] = edits;
  295. const initialSelection = new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
  296. model.pushEditOperations([initialSelection], edits.map(edit => {
  297. return {
  298. text: edit.text,
  299. range: Range.lift(edit.range),
  300. forceMoveMarkers: true
  301. };
  302. }), undoEdits => {
  303. for (const { range } of undoEdits) {
  304. if (Range.areIntersectingOrTouching(range, initialSelection)) {
  305. return [new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)];
  306. }
  307. }
  308. return null;
  309. });
  310. }
  311. return true;
  312. });
  313. }
  314. export function getDocumentRangeFormattingEditsUntilResult(workerService, languageFeaturesService, model, range, options, token) {
  315. return __awaiter(this, void 0, void 0, function* () {
  316. const providers = languageFeaturesService.documentRangeFormattingEditProvider.ordered(model);
  317. for (const provider of providers) {
  318. const rawEdits = yield Promise.resolve(provider.provideDocumentRangeFormattingEdits(model, range, options, token)).catch(onUnexpectedExternalError);
  319. if (isNonEmptyArray(rawEdits)) {
  320. return yield workerService.computeMoreMinimalEdits(model.uri, rawEdits);
  321. }
  322. }
  323. return undefined;
  324. });
  325. }
  326. export function getDocumentFormattingEditsUntilResult(workerService, languageFeaturesService, model, options, token) {
  327. return __awaiter(this, void 0, void 0, function* () {
  328. const providers = getRealAndSyntheticDocumentFormattersOrdered(languageFeaturesService.documentFormattingEditProvider, languageFeaturesService.documentRangeFormattingEditProvider, model);
  329. for (const provider of providers) {
  330. const rawEdits = yield Promise.resolve(provider.provideDocumentFormattingEdits(model, options, token)).catch(onUnexpectedExternalError);
  331. if (isNonEmptyArray(rawEdits)) {
  332. return yield workerService.computeMoreMinimalEdits(model.uri, rawEdits);
  333. }
  334. }
  335. return undefined;
  336. });
  337. }
  338. export function getOnTypeFormattingEdits(workerService, languageFeaturesService, model, position, ch, options, token) {
  339. const providers = languageFeaturesService.onTypeFormattingEditProvider.ordered(model);
  340. if (providers.length === 0) {
  341. return Promise.resolve(undefined);
  342. }
  343. if (providers[0].autoFormatTriggerCharacters.indexOf(ch) < 0) {
  344. return Promise.resolve(undefined);
  345. }
  346. return Promise.resolve(providers[0].provideOnTypeFormattingEdits(model, position, ch, options, token)).catch(onUnexpectedExternalError).then(edits => {
  347. return workerService.computeMoreMinimalEdits(model.uri, edits);
  348. });
  349. }
  350. CommandsRegistry.registerCommand('_executeFormatRangeProvider', function (accessor, ...args) {
  351. return __awaiter(this, void 0, void 0, function* () {
  352. const [resource, range, options] = args;
  353. assertType(URI.isUri(resource));
  354. assertType(Range.isIRange(range));
  355. const resolverService = accessor.get(ITextModelService);
  356. const workerService = accessor.get(IEditorWorkerService);
  357. const languageFeaturesService = accessor.get(ILanguageFeaturesService);
  358. const reference = yield resolverService.createModelReference(resource);
  359. try {
  360. return getDocumentRangeFormattingEditsUntilResult(workerService, languageFeaturesService, reference.object.textEditorModel, Range.lift(range), options, CancellationToken.None);
  361. }
  362. finally {
  363. reference.dispose();
  364. }
  365. });
  366. });
  367. CommandsRegistry.registerCommand('_executeFormatDocumentProvider', function (accessor, ...args) {
  368. return __awaiter(this, void 0, void 0, function* () {
  369. const [resource, options] = args;
  370. assertType(URI.isUri(resource));
  371. const resolverService = accessor.get(ITextModelService);
  372. const workerService = accessor.get(IEditorWorkerService);
  373. const languageFeaturesService = accessor.get(ILanguageFeaturesService);
  374. const reference = yield resolverService.createModelReference(resource);
  375. try {
  376. return getDocumentFormattingEditsUntilResult(workerService, languageFeaturesService, reference.object.textEditorModel, options, CancellationToken.None);
  377. }
  378. finally {
  379. reference.dispose();
  380. }
  381. });
  382. });
  383. CommandsRegistry.registerCommand('_executeFormatOnTypeProvider', function (accessor, ...args) {
  384. return __awaiter(this, void 0, void 0, function* () {
  385. const [resource, position, ch, options] = args;
  386. assertType(URI.isUri(resource));
  387. assertType(Position.isIPosition(position));
  388. assertType(typeof ch === 'string');
  389. const resolverService = accessor.get(ITextModelService);
  390. const workerService = accessor.get(IEditorWorkerService);
  391. const languageFeaturesService = accessor.get(ILanguageFeaturesService);
  392. const reference = yield resolverService.createModelReference(resource);
  393. try {
  394. return getOnTypeFormattingEdits(workerService, languageFeaturesService, reference.object.textEditorModel, Position.lift(position), ch, options, CancellationToken.None);
  395. }
  396. finally {
  397. reference.dispose();
  398. }
  399. });
  400. });