3afb84b655f0b145628b616d22beb4e8fa4e134dcaa2db89a1f8f568662e8c04284202ee3b204a76f9da9423cf50e7e99baec08193850053e8c9211d2afa52 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  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 * as nls from '../../nls.js';
  6. import { URI } from '../../base/common/uri.js';
  7. import { ICodeEditorService } from './services/codeEditorService.js';
  8. import { Position } from '../common/core/position.js';
  9. import { IModelService } from '../common/services/model.js';
  10. import { ITextModelService } from '../common/services/resolverService.js';
  11. import { MenuId, MenuRegistry } from '../../platform/actions/common/actions.js';
  12. import { CommandsRegistry } from '../../platform/commands/common/commands.js';
  13. import { ContextKeyExpr, IContextKeyService } from '../../platform/contextkey/common/contextkey.js';
  14. import { IInstantiationService } from '../../platform/instantiation/common/instantiation.js';
  15. import { KeybindingsRegistry } from '../../platform/keybinding/common/keybindingsRegistry.js';
  16. import { Registry } from '../../platform/registry/common/platform.js';
  17. import { ITelemetryService } from '../../platform/telemetry/common/telemetry.js';
  18. import { withNullAsUndefined, assertType } from '../../base/common/types.js';
  19. import { ILogService } from '../../platform/log/common/log.js';
  20. export class Command {
  21. constructor(opts) {
  22. this.id = opts.id;
  23. this.precondition = opts.precondition;
  24. this._kbOpts = opts.kbOpts;
  25. this._menuOpts = opts.menuOpts;
  26. this._description = opts.description;
  27. }
  28. register() {
  29. if (Array.isArray(this._menuOpts)) {
  30. this._menuOpts.forEach(this._registerMenuItem, this);
  31. }
  32. else if (this._menuOpts) {
  33. this._registerMenuItem(this._menuOpts);
  34. }
  35. if (this._kbOpts) {
  36. const kbOptsArr = Array.isArray(this._kbOpts) ? this._kbOpts : [this._kbOpts];
  37. for (const kbOpts of kbOptsArr) {
  38. let kbWhen = kbOpts.kbExpr;
  39. if (this.precondition) {
  40. if (kbWhen) {
  41. kbWhen = ContextKeyExpr.and(kbWhen, this.precondition);
  42. }
  43. else {
  44. kbWhen = this.precondition;
  45. }
  46. }
  47. const desc = {
  48. id: this.id,
  49. weight: kbOpts.weight,
  50. args: kbOpts.args,
  51. when: kbWhen,
  52. primary: kbOpts.primary,
  53. secondary: kbOpts.secondary,
  54. win: kbOpts.win,
  55. linux: kbOpts.linux,
  56. mac: kbOpts.mac,
  57. };
  58. KeybindingsRegistry.registerKeybindingRule(desc);
  59. }
  60. }
  61. CommandsRegistry.registerCommand({
  62. id: this.id,
  63. handler: (accessor, args) => this.runCommand(accessor, args),
  64. description: this._description
  65. });
  66. }
  67. _registerMenuItem(item) {
  68. MenuRegistry.appendMenuItem(item.menuId, {
  69. group: item.group,
  70. command: {
  71. id: this.id,
  72. title: item.title,
  73. icon: item.icon,
  74. precondition: this.precondition
  75. },
  76. when: item.when,
  77. order: item.order
  78. });
  79. }
  80. }
  81. export class MultiCommand extends Command {
  82. constructor() {
  83. super(...arguments);
  84. this._implementations = [];
  85. }
  86. /**
  87. * A higher priority gets to be looked at first
  88. */
  89. addImplementation(priority, name, implementation) {
  90. this._implementations.push({ priority, name, implementation });
  91. this._implementations.sort((a, b) => b.priority - a.priority);
  92. return {
  93. dispose: () => {
  94. for (let i = 0; i < this._implementations.length; i++) {
  95. if (this._implementations[i].implementation === implementation) {
  96. this._implementations.splice(i, 1);
  97. return;
  98. }
  99. }
  100. }
  101. };
  102. }
  103. runCommand(accessor, args) {
  104. const logService = accessor.get(ILogService);
  105. logService.trace(`Executing Command '${this.id}' which has ${this._implementations.length} bound.`);
  106. for (const impl of this._implementations) {
  107. const result = impl.implementation(accessor, args);
  108. if (result) {
  109. logService.trace(`Command '${this.id}' was handled by '${impl.name}'.`);
  110. if (typeof result === 'boolean') {
  111. return;
  112. }
  113. return result;
  114. }
  115. }
  116. logService.trace(`The Command '${this.id}' was not handled by any implementation.`);
  117. }
  118. }
  119. //#endregion
  120. /**
  121. * A command that delegates to another command's implementation.
  122. *
  123. * This lets different commands be registered but share the same implementation
  124. */
  125. export class ProxyCommand extends Command {
  126. constructor(command, opts) {
  127. super(opts);
  128. this.command = command;
  129. }
  130. runCommand(accessor, args) {
  131. return this.command.runCommand(accessor, args);
  132. }
  133. }
  134. export class EditorCommand extends Command {
  135. /**
  136. * Create a command class that is bound to a certain editor contribution.
  137. */
  138. static bindToContribution(controllerGetter) {
  139. return class EditorControllerCommandImpl extends EditorCommand {
  140. constructor(opts) {
  141. super(opts);
  142. this._callback = opts.handler;
  143. }
  144. runEditorCommand(accessor, editor, args) {
  145. const controller = controllerGetter(editor);
  146. if (controller) {
  147. this._callback(controller, args);
  148. }
  149. }
  150. };
  151. }
  152. static runEditorCommand(accessor, args, precondition, runner) {
  153. const codeEditorService = accessor.get(ICodeEditorService);
  154. // Find the editor with text focus or active
  155. const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor();
  156. if (!editor) {
  157. // well, at least we tried...
  158. return;
  159. }
  160. return editor.invokeWithinContext((editorAccessor) => {
  161. const kbService = editorAccessor.get(IContextKeyService);
  162. if (!kbService.contextMatchesRules(withNullAsUndefined(precondition))) {
  163. // precondition does not hold
  164. return;
  165. }
  166. return runner(editorAccessor, editor, args);
  167. });
  168. }
  169. runCommand(accessor, args) {
  170. return EditorCommand.runEditorCommand(accessor, args, this.precondition, (accessor, editor, args) => this.runEditorCommand(accessor, editor, args));
  171. }
  172. }
  173. export class EditorAction extends EditorCommand {
  174. constructor(opts) {
  175. super(EditorAction.convertOptions(opts));
  176. this.label = opts.label;
  177. this.alias = opts.alias;
  178. }
  179. static convertOptions(opts) {
  180. let menuOpts;
  181. if (Array.isArray(opts.menuOpts)) {
  182. menuOpts = opts.menuOpts;
  183. }
  184. else if (opts.menuOpts) {
  185. menuOpts = [opts.menuOpts];
  186. }
  187. else {
  188. menuOpts = [];
  189. }
  190. function withDefaults(item) {
  191. if (!item.menuId) {
  192. item.menuId = MenuId.EditorContext;
  193. }
  194. if (!item.title) {
  195. item.title = opts.label;
  196. }
  197. item.when = ContextKeyExpr.and(opts.precondition, item.when);
  198. return item;
  199. }
  200. if (Array.isArray(opts.contextMenuOpts)) {
  201. menuOpts.push(...opts.contextMenuOpts.map(withDefaults));
  202. }
  203. else if (opts.contextMenuOpts) {
  204. menuOpts.push(withDefaults(opts.contextMenuOpts));
  205. }
  206. opts.menuOpts = menuOpts;
  207. return opts;
  208. }
  209. runEditorCommand(accessor, editor, args) {
  210. this.reportTelemetry(accessor, editor);
  211. return this.run(accessor, editor, args || {});
  212. }
  213. reportTelemetry(accessor, editor) {
  214. accessor.get(ITelemetryService).publicLog2('editorActionInvoked', { name: this.label, id: this.id });
  215. }
  216. }
  217. export class MultiEditorAction extends EditorAction {
  218. constructor() {
  219. super(...arguments);
  220. this._implementations = [];
  221. }
  222. /**
  223. * A higher priority gets to be looked at first
  224. */
  225. addImplementation(priority, implementation) {
  226. this._implementations.push([priority, implementation]);
  227. this._implementations.sort((a, b) => b[0] - a[0]);
  228. return {
  229. dispose: () => {
  230. for (let i = 0; i < this._implementations.length; i++) {
  231. if (this._implementations[i][1] === implementation) {
  232. this._implementations.splice(i, 1);
  233. return;
  234. }
  235. }
  236. }
  237. };
  238. }
  239. run(accessor, editor, args) {
  240. for (const impl of this._implementations) {
  241. const result = impl[1](accessor, editor, args);
  242. if (result) {
  243. if (typeof result === 'boolean') {
  244. return;
  245. }
  246. return result;
  247. }
  248. }
  249. }
  250. }
  251. //#endregion
  252. // --- Registration of commands and actions
  253. export function registerModelAndPositionCommand(id, handler) {
  254. CommandsRegistry.registerCommand(id, function (accessor, ...args) {
  255. const instaService = accessor.get(IInstantiationService);
  256. const [resource, position] = args;
  257. assertType(URI.isUri(resource));
  258. assertType(Position.isIPosition(position));
  259. const model = accessor.get(IModelService).getModel(resource);
  260. if (model) {
  261. const editorPosition = Position.lift(position);
  262. return instaService.invokeFunction(handler, model, editorPosition, ...args.slice(2));
  263. }
  264. return accessor.get(ITextModelService).createModelReference(resource).then(reference => {
  265. return new Promise((resolve, reject) => {
  266. try {
  267. const result = instaService.invokeFunction(handler, reference.object.textEditorModel, Position.lift(position), args.slice(2));
  268. resolve(result);
  269. }
  270. catch (err) {
  271. reject(err);
  272. }
  273. }).finally(() => {
  274. reference.dispose();
  275. });
  276. });
  277. });
  278. }
  279. export function registerEditorCommand(editorCommand) {
  280. EditorContributionRegistry.INSTANCE.registerEditorCommand(editorCommand);
  281. return editorCommand;
  282. }
  283. export function registerEditorAction(ctor) {
  284. const action = new ctor();
  285. EditorContributionRegistry.INSTANCE.registerEditorAction(action);
  286. return action;
  287. }
  288. export function registerMultiEditorAction(action) {
  289. EditorContributionRegistry.INSTANCE.registerEditorAction(action);
  290. return action;
  291. }
  292. export function registerInstantiatedEditorAction(editorAction) {
  293. EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction);
  294. }
  295. export function registerEditorContribution(id, ctor) {
  296. EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor);
  297. }
  298. export var EditorExtensionsRegistry;
  299. (function (EditorExtensionsRegistry) {
  300. function getEditorCommand(commandId) {
  301. return EditorContributionRegistry.INSTANCE.getEditorCommand(commandId);
  302. }
  303. EditorExtensionsRegistry.getEditorCommand = getEditorCommand;
  304. function getEditorActions() {
  305. return EditorContributionRegistry.INSTANCE.getEditorActions();
  306. }
  307. EditorExtensionsRegistry.getEditorActions = getEditorActions;
  308. function getEditorContributions() {
  309. return EditorContributionRegistry.INSTANCE.getEditorContributions();
  310. }
  311. EditorExtensionsRegistry.getEditorContributions = getEditorContributions;
  312. function getSomeEditorContributions(ids) {
  313. return EditorContributionRegistry.INSTANCE.getEditorContributions().filter(c => ids.indexOf(c.id) >= 0);
  314. }
  315. EditorExtensionsRegistry.getSomeEditorContributions = getSomeEditorContributions;
  316. function getDiffEditorContributions() {
  317. return EditorContributionRegistry.INSTANCE.getDiffEditorContributions();
  318. }
  319. EditorExtensionsRegistry.getDiffEditorContributions = getDiffEditorContributions;
  320. })(EditorExtensionsRegistry || (EditorExtensionsRegistry = {}));
  321. // Editor extension points
  322. const Extensions = {
  323. EditorCommonContributions: 'editor.contributions'
  324. };
  325. class EditorContributionRegistry {
  326. constructor() {
  327. this.editorContributions = [];
  328. this.diffEditorContributions = [];
  329. this.editorActions = [];
  330. this.editorCommands = Object.create(null);
  331. }
  332. registerEditorContribution(id, ctor) {
  333. this.editorContributions.push({ id, ctor: ctor });
  334. }
  335. getEditorContributions() {
  336. return this.editorContributions.slice(0);
  337. }
  338. getDiffEditorContributions() {
  339. return this.diffEditorContributions.slice(0);
  340. }
  341. registerEditorAction(action) {
  342. action.register();
  343. this.editorActions.push(action);
  344. }
  345. getEditorActions() {
  346. return this.editorActions.slice(0);
  347. }
  348. registerEditorCommand(editorCommand) {
  349. editorCommand.register();
  350. this.editorCommands[editorCommand.id] = editorCommand;
  351. }
  352. getEditorCommand(commandId) {
  353. return (this.editorCommands[commandId] || null);
  354. }
  355. }
  356. EditorContributionRegistry.INSTANCE = new EditorContributionRegistry();
  357. Registry.add(Extensions.EditorCommonContributions, EditorContributionRegistry.INSTANCE);
  358. function registerCommand(command) {
  359. command.register();
  360. return command;
  361. }
  362. export const UndoCommand = registerCommand(new MultiCommand({
  363. id: 'undo',
  364. precondition: undefined,
  365. kbOpts: {
  366. weight: 0 /* KeybindingWeight.EditorCore */,
  367. primary: 2048 /* KeyMod.CtrlCmd */ | 56 /* KeyCode.KeyZ */
  368. },
  369. menuOpts: [{
  370. menuId: MenuId.MenubarEditMenu,
  371. group: '1_do',
  372. title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"),
  373. order: 1
  374. }, {
  375. menuId: MenuId.CommandPalette,
  376. group: '',
  377. title: nls.localize('undo', "Undo"),
  378. order: 1
  379. }]
  380. }));
  381. registerCommand(new ProxyCommand(UndoCommand, { id: 'default:undo', precondition: undefined }));
  382. export const RedoCommand = registerCommand(new MultiCommand({
  383. id: 'redo',
  384. precondition: undefined,
  385. kbOpts: {
  386. weight: 0 /* KeybindingWeight.EditorCore */,
  387. primary: 2048 /* KeyMod.CtrlCmd */ | 55 /* KeyCode.KeyY */,
  388. secondary: [2048 /* KeyMod.CtrlCmd */ | 1024 /* KeyMod.Shift */ | 56 /* KeyCode.KeyZ */],
  389. mac: { primary: 2048 /* KeyMod.CtrlCmd */ | 1024 /* KeyMod.Shift */ | 56 /* KeyCode.KeyZ */ }
  390. },
  391. menuOpts: [{
  392. menuId: MenuId.MenubarEditMenu,
  393. group: '1_do',
  394. title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"),
  395. order: 2
  396. }, {
  397. menuId: MenuId.CommandPalette,
  398. group: '',
  399. title: nls.localize('redo', "Redo"),
  400. order: 1
  401. }]
  402. }));
  403. registerCommand(new ProxyCommand(RedoCommand, { id: 'default:redo', precondition: undefined }));
  404. export const SelectAllCommand = registerCommand(new MultiCommand({
  405. id: 'editor.action.selectAll',
  406. precondition: undefined,
  407. kbOpts: {
  408. weight: 0 /* KeybindingWeight.EditorCore */,
  409. kbExpr: null,
  410. primary: 2048 /* KeyMod.CtrlCmd */ | 31 /* KeyCode.KeyA */
  411. },
  412. menuOpts: [{
  413. menuId: MenuId.MenubarSelectionMenu,
  414. group: '1_basic',
  415. title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"),
  416. order: 1
  417. }, {
  418. menuId: MenuId.CommandPalette,
  419. group: '',
  420. title: nls.localize('selectAll', "Select All"),
  421. order: 1
  422. }]
  423. }));