db69964f4449abf5c6368dfac3ff07b9aad7adef3468ed3500277268c2e16120a6c40f947c2b5af4cac7e4b6be4c624898710e9b2689403dd05a5178cccd66 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  15. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  16. return new (P || (P = Promise))(function (resolve, reject) {
  17. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  18. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  19. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  20. step((generator = generator.apply(thisArg, _arguments || [])).next());
  21. });
  22. };
  23. import { alert } from '../../../../base/browser/ui/aria/aria.js';
  24. import { IdleValue, raceCancellation } from '../../../../base/common/async.js';
  25. import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
  26. import { onUnexpectedError } from '../../../../base/common/errors.js';
  27. import { DisposableStore } from '../../../../base/common/lifecycle.js';
  28. import { assertType } from '../../../../base/common/types.js';
  29. import { URI } from '../../../../base/common/uri.js';
  30. import { EditorStateCancellationTokenSource } from '../../editorState/browser/editorState.js';
  31. import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand } from '../../../browser/editorExtensions.js';
  32. import { IBulkEditService, ResourceEdit } from '../../../browser/services/bulkEditService.js';
  33. import { ICodeEditorService } from '../../../browser/services/codeEditorService.js';
  34. import { Position } from '../../../common/core/position.js';
  35. import { Range } from '../../../common/core/range.js';
  36. import { EditorContextKeys } from '../../../common/editorContextKeys.js';
  37. import { ITextResourceConfigurationService } from '../../../common/services/textResourceConfiguration.js';
  38. import { MessageController } from '../../message/browser/messageController.js';
  39. import * as nls from '../../../../nls.js';
  40. import { Extensions } from '../../../../platform/configuration/common/configurationRegistry.js';
  41. import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
  42. import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
  43. import { ILogService } from '../../../../platform/log/common/log.js';
  44. import { INotificationService } from '../../../../platform/notification/common/notification.js';
  45. import { IEditorProgressService } from '../../../../platform/progress/common/progress.js';
  46. import { Registry } from '../../../../platform/registry/common/platform.js';
  47. import { CONTEXT_RENAME_INPUT_VISIBLE, RenameInputField } from './renameInputField.js';
  48. import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
  49. class RenameSkeleton {
  50. constructor(model, position, registry) {
  51. this.model = model;
  52. this.position = position;
  53. this._providerRenameIdx = 0;
  54. this._providers = registry.ordered(model);
  55. }
  56. hasProvider() {
  57. return this._providers.length > 0;
  58. }
  59. resolveRenameLocation(token) {
  60. return __awaiter(this, void 0, void 0, function* () {
  61. const rejects = [];
  62. for (this._providerRenameIdx = 0; this._providerRenameIdx < this._providers.length; this._providerRenameIdx++) {
  63. const provider = this._providers[this._providerRenameIdx];
  64. if (!provider.resolveRenameLocation) {
  65. break;
  66. }
  67. const res = yield provider.resolveRenameLocation(this.model, this.position, token);
  68. if (!res) {
  69. continue;
  70. }
  71. if (res.rejectReason) {
  72. rejects.push(res.rejectReason);
  73. continue;
  74. }
  75. return res;
  76. }
  77. const word = this.model.getWordAtPosition(this.position);
  78. if (!word) {
  79. return {
  80. range: Range.fromPositions(this.position),
  81. text: '',
  82. rejectReason: rejects.length > 0 ? rejects.join('\n') : undefined
  83. };
  84. }
  85. return {
  86. range: new Range(this.position.lineNumber, word.startColumn, this.position.lineNumber, word.endColumn),
  87. text: word.word,
  88. rejectReason: rejects.length > 0 ? rejects.join('\n') : undefined
  89. };
  90. });
  91. }
  92. provideRenameEdits(newName, token) {
  93. return __awaiter(this, void 0, void 0, function* () {
  94. return this._provideRenameEdits(newName, this._providerRenameIdx, [], token);
  95. });
  96. }
  97. _provideRenameEdits(newName, i, rejects, token) {
  98. return __awaiter(this, void 0, void 0, function* () {
  99. const provider = this._providers[i];
  100. if (!provider) {
  101. return {
  102. edits: [],
  103. rejectReason: rejects.join('\n')
  104. };
  105. }
  106. const result = yield provider.provideRenameEdits(this.model, this.position, newName, token);
  107. if (!result) {
  108. return this._provideRenameEdits(newName, i + 1, rejects.concat(nls.localize('no result', "No result.")), token);
  109. }
  110. else if (result.rejectReason) {
  111. return this._provideRenameEdits(newName, i + 1, rejects.concat(result.rejectReason), token);
  112. }
  113. return result;
  114. });
  115. }
  116. }
  117. export function rename(registry, model, position, newName) {
  118. return __awaiter(this, void 0, void 0, function* () {
  119. const skeleton = new RenameSkeleton(model, position, registry);
  120. const loc = yield skeleton.resolveRenameLocation(CancellationToken.None);
  121. if (loc === null || loc === void 0 ? void 0 : loc.rejectReason) {
  122. return { edits: [], rejectReason: loc.rejectReason };
  123. }
  124. return skeleton.provideRenameEdits(newName, CancellationToken.None);
  125. });
  126. }
  127. // --- register actions and commands
  128. let RenameController = class RenameController {
  129. constructor(editor, _instaService, _notificationService, _bulkEditService, _progressService, _logService, _configService, _languageFeaturesService) {
  130. this.editor = editor;
  131. this._instaService = _instaService;
  132. this._notificationService = _notificationService;
  133. this._bulkEditService = _bulkEditService;
  134. this._progressService = _progressService;
  135. this._logService = _logService;
  136. this._configService = _configService;
  137. this._languageFeaturesService = _languageFeaturesService;
  138. this._disposableStore = new DisposableStore();
  139. this._cts = new CancellationTokenSource();
  140. this._renameInputField = this._disposableStore.add(new IdleValue(() => this._disposableStore.add(this._instaService.createInstance(RenameInputField, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview']))));
  141. }
  142. static get(editor) {
  143. return editor.getContribution(RenameController.ID);
  144. }
  145. dispose() {
  146. this._disposableStore.dispose();
  147. this._cts.dispose(true);
  148. }
  149. run() {
  150. var _a, _b;
  151. return __awaiter(this, void 0, void 0, function* () {
  152. this._cts.dispose(true);
  153. if (!this.editor.hasModel()) {
  154. return undefined;
  155. }
  156. const position = this.editor.getPosition();
  157. const skeleton = new RenameSkeleton(this.editor.getModel(), position, this._languageFeaturesService.renameProvider);
  158. if (!skeleton.hasProvider()) {
  159. return undefined;
  160. }
  161. this._cts = new EditorStateCancellationTokenSource(this.editor, 4 /* CodeEditorStateFlag.Position */ | 1 /* CodeEditorStateFlag.Value */);
  162. // resolve rename location
  163. let loc;
  164. try {
  165. const resolveLocationOperation = skeleton.resolveRenameLocation(this._cts.token);
  166. this._progressService.showWhile(resolveLocationOperation, 250);
  167. loc = yield resolveLocationOperation;
  168. }
  169. catch (e) {
  170. (_a = MessageController.get(this.editor)) === null || _a === void 0 ? void 0 : _a.showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position);
  171. return undefined;
  172. }
  173. if (!loc) {
  174. return undefined;
  175. }
  176. if (loc.rejectReason) {
  177. (_b = MessageController.get(this.editor)) === null || _b === void 0 ? void 0 : _b.showMessage(loc.rejectReason, position);
  178. return undefined;
  179. }
  180. if (this._cts.token.isCancellationRequested) {
  181. return undefined;
  182. }
  183. this._cts.dispose();
  184. this._cts = new EditorStateCancellationTokenSource(this.editor, 4 /* CodeEditorStateFlag.Position */ | 1 /* CodeEditorStateFlag.Value */, loc.range);
  185. // do rename at location
  186. const selection = this.editor.getSelection();
  187. let selectionStart = 0;
  188. let selectionEnd = loc.text.length;
  189. if (!Range.isEmpty(selection) && !Range.spansMultipleLines(selection) && Range.containsRange(loc.range, selection)) {
  190. selectionStart = Math.max(0, selection.startColumn - loc.range.startColumn);
  191. selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn;
  192. }
  193. const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview');
  194. const inputFieldResult = yield this._renameInputField.value.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, this._cts.token);
  195. // no result, only hint to focus the editor or not
  196. if (typeof inputFieldResult === 'boolean') {
  197. if (inputFieldResult) {
  198. this.editor.focus();
  199. }
  200. return undefined;
  201. }
  202. this.editor.focus();
  203. const renameOperation = raceCancellation(skeleton.provideRenameEdits(inputFieldResult.newName, this._cts.token), this._cts.token).then((renameResult) => __awaiter(this, void 0, void 0, function* () {
  204. if (!renameResult || !this.editor.hasModel()) {
  205. return;
  206. }
  207. if (renameResult.rejectReason) {
  208. this._notificationService.info(renameResult.rejectReason);
  209. return;
  210. }
  211. // collapse selection to active end
  212. this.editor.setSelection(Range.fromPositions(this.editor.getSelection().getPosition()));
  213. this._bulkEditService.apply(ResourceEdit.convert(renameResult), {
  214. editor: this.editor,
  215. showPreview: inputFieldResult.wantsPreview,
  216. label: nls.localize('label', "Renaming '{0}' to '{1}'", loc === null || loc === void 0 ? void 0 : loc.text, inputFieldResult.newName),
  217. code: 'undoredo.rename',
  218. quotableLabel: nls.localize('quotableLabel', "Renaming {0} to {1}", loc === null || loc === void 0 ? void 0 : loc.text, inputFieldResult.newName),
  219. respectAutoSaveConfig: true
  220. }).then(result => {
  221. if (result.ariaSummary) {
  222. alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc.text, inputFieldResult.newName, result.ariaSummary));
  223. }
  224. }).catch(err => {
  225. this._notificationService.error(nls.localize('rename.failedApply', "Rename failed to apply edits"));
  226. this._logService.error(err);
  227. });
  228. }), err => {
  229. this._notificationService.error(nls.localize('rename.failed', "Rename failed to compute edits"));
  230. this._logService.error(err);
  231. });
  232. this._progressService.showWhile(renameOperation, 250);
  233. return renameOperation;
  234. });
  235. }
  236. acceptRenameInput(wantsPreview) {
  237. this._renameInputField.value.acceptInput(wantsPreview);
  238. }
  239. cancelRenameInput() {
  240. this._renameInputField.value.cancelInput(true);
  241. }
  242. };
  243. RenameController.ID = 'editor.contrib.renameController';
  244. RenameController = __decorate([
  245. __param(1, IInstantiationService),
  246. __param(2, INotificationService),
  247. __param(3, IBulkEditService),
  248. __param(4, IEditorProgressService),
  249. __param(5, ILogService),
  250. __param(6, ITextResourceConfigurationService),
  251. __param(7, ILanguageFeaturesService)
  252. ], RenameController);
  253. // ---- action implementation
  254. export class RenameAction extends EditorAction {
  255. constructor() {
  256. super({
  257. id: 'editor.action.rename',
  258. label: nls.localize('rename.label', "Rename Symbol"),
  259. alias: 'Rename Symbol',
  260. precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasRenameProvider),
  261. kbOpts: {
  262. kbExpr: EditorContextKeys.editorTextFocus,
  263. primary: 60 /* KeyCode.F2 */,
  264. weight: 100 /* KeybindingWeight.EditorContrib */
  265. },
  266. contextMenuOpts: {
  267. group: '1_modification',
  268. order: 1.1
  269. }
  270. });
  271. }
  272. runCommand(accessor, args) {
  273. const editorService = accessor.get(ICodeEditorService);
  274. const [uri, pos] = Array.isArray(args) && args || [undefined, undefined];
  275. if (URI.isUri(uri) && Position.isIPosition(pos)) {
  276. return editorService.openCodeEditor({ resource: uri }, editorService.getActiveCodeEditor()).then(editor => {
  277. if (!editor) {
  278. return;
  279. }
  280. editor.setPosition(pos);
  281. editor.invokeWithinContext(accessor => {
  282. this.reportTelemetry(accessor, editor);
  283. return this.run(accessor, editor);
  284. });
  285. }, onUnexpectedError);
  286. }
  287. return super.runCommand(accessor, args);
  288. }
  289. run(accessor, editor) {
  290. const controller = RenameController.get(editor);
  291. if (controller) {
  292. return controller.run();
  293. }
  294. return Promise.resolve();
  295. }
  296. }
  297. registerEditorContribution(RenameController.ID, RenameController);
  298. registerEditorAction(RenameAction);
  299. const RenameCommand = EditorCommand.bindToContribution(RenameController.get);
  300. registerEditorCommand(new RenameCommand({
  301. id: 'acceptRenameInput',
  302. precondition: CONTEXT_RENAME_INPUT_VISIBLE,
  303. handler: x => x.acceptRenameInput(false),
  304. kbOpts: {
  305. weight: 100 /* KeybindingWeight.EditorContrib */ + 99,
  306. kbExpr: EditorContextKeys.focus,
  307. primary: 3 /* KeyCode.Enter */
  308. }
  309. }));
  310. registerEditorCommand(new RenameCommand({
  311. id: 'acceptRenameInputWithPreview',
  312. precondition: ContextKeyExpr.and(CONTEXT_RENAME_INPUT_VISIBLE, ContextKeyExpr.has('config.editor.rename.enablePreview')),
  313. handler: x => x.acceptRenameInput(true),
  314. kbOpts: {
  315. weight: 100 /* KeybindingWeight.EditorContrib */ + 99,
  316. kbExpr: EditorContextKeys.focus,
  317. primary: 1024 /* KeyMod.Shift */ + 3 /* KeyCode.Enter */
  318. }
  319. }));
  320. registerEditorCommand(new RenameCommand({
  321. id: 'cancelRenameInput',
  322. precondition: CONTEXT_RENAME_INPUT_VISIBLE,
  323. handler: x => x.cancelRenameInput(),
  324. kbOpts: {
  325. weight: 100 /* KeybindingWeight.EditorContrib */ + 99,
  326. kbExpr: EditorContextKeys.focus,
  327. primary: 9 /* KeyCode.Escape */,
  328. secondary: [1024 /* KeyMod.Shift */ | 9 /* KeyCode.Escape */]
  329. }
  330. }));
  331. // ---- api bridge command
  332. registerModelAndPositionCommand('_executeDocumentRenameProvider', function (accessor, model, position, ...args) {
  333. const [newName] = args;
  334. assertType(typeof newName === 'string');
  335. const { renameProvider } = accessor.get(ILanguageFeaturesService);
  336. return rename(renameProvider, model, position, newName);
  337. });
  338. registerModelAndPositionCommand('_executePrepareRename', function (accessor, model, position) {
  339. return __awaiter(this, void 0, void 0, function* () {
  340. const { renameProvider } = accessor.get(ILanguageFeaturesService);
  341. const skeleton = new RenameSkeleton(model, position, renameProvider);
  342. const loc = yield skeleton.resolveRenameLocation(CancellationToken.None);
  343. if (loc === null || loc === void 0 ? void 0 : loc.rejectReason) {
  344. throw new Error(loc.rejectReason);
  345. }
  346. return loc;
  347. });
  348. });
  349. //todo@jrieken use editor options world
  350. Registry.as(Extensions.Configuration).registerConfiguration({
  351. id: 'editor',
  352. properties: {
  353. 'editor.rename.enablePreview': {
  354. scope: 5 /* ConfigurationScope.LANGUAGE_OVERRIDABLE */,
  355. description: nls.localize('enablePreview', "Enable/disable the ability to preview changes before renaming"),
  356. default: true,
  357. type: 'boolean'
  358. }
  359. }
  360. });