e22bb489dc2152d2880827b2e152ccac7561b87bce8ba3e241e03a9919fef8d223fc63d6bd09cc91da0935b8ad18adb540b5306e8ac3b999ba239e5ed2d00f 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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 { DisposableStore } from '../../../../base/common/lifecycle.js';
  15. import { assertType } from '../../../../base/common/types.js';
  16. import { EditorCommand, registerEditorCommand, registerEditorContribution } from '../../../browser/editorExtensions.js';
  17. import { Position } from '../../../common/core/position.js';
  18. import { Selection } from '../../../common/core/selection.js';
  19. import { EditorContextKeys } from '../../../common/editorContextKeys.js';
  20. import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
  21. import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
  22. import { showSimpleSuggestions } from '../../suggest/browser/suggest.js';
  23. import { localize } from '../../../../nls.js';
  24. import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
  25. import { ILogService } from '../../../../platform/log/common/log.js';
  26. import { SnippetSession } from './snippetSession.js';
  27. const _defaultOptions = {
  28. overwriteBefore: 0,
  29. overwriteAfter: 0,
  30. undoStopBefore: true,
  31. undoStopAfter: true,
  32. adjustWhitespace: true,
  33. clipboardText: undefined,
  34. overtypingCapturer: undefined
  35. };
  36. let SnippetController2 = class SnippetController2 {
  37. constructor(_editor, _logService, _languageFeaturesService, contextKeyService, _languageConfigurationService) {
  38. this._editor = _editor;
  39. this._logService = _logService;
  40. this._languageFeaturesService = _languageFeaturesService;
  41. this._languageConfigurationService = _languageConfigurationService;
  42. this._snippetListener = new DisposableStore();
  43. this._modelVersionId = -1;
  44. this._inSnippet = SnippetController2.InSnippetMode.bindTo(contextKeyService);
  45. this._hasNextTabstop = SnippetController2.HasNextTabstop.bindTo(contextKeyService);
  46. this._hasPrevTabstop = SnippetController2.HasPrevTabstop.bindTo(contextKeyService);
  47. }
  48. static get(editor) {
  49. return editor.getContribution(SnippetController2.ID);
  50. }
  51. dispose() {
  52. var _a;
  53. this._inSnippet.reset();
  54. this._hasPrevTabstop.reset();
  55. this._hasNextTabstop.reset();
  56. (_a = this._session) === null || _a === void 0 ? void 0 : _a.dispose();
  57. this._snippetListener.dispose();
  58. }
  59. apply(edits, opts) {
  60. try {
  61. this._doInsert(edits, typeof opts === 'undefined' ? _defaultOptions : Object.assign(Object.assign({}, _defaultOptions), opts));
  62. }
  63. catch (e) {
  64. this.cancel();
  65. this._logService.error(e);
  66. this._logService.error('snippet_error');
  67. this._logService.error('insert_edits=', edits);
  68. this._logService.error('existing_template=', this._session ? this._session._logInfo() : '<no_session>');
  69. }
  70. }
  71. insert(template, opts) {
  72. // this is here to find out more about the yet-not-understood
  73. // error that sometimes happens when we fail to inserted a nested
  74. // snippet
  75. try {
  76. this._doInsert(template, typeof opts === 'undefined' ? _defaultOptions : Object.assign(Object.assign({}, _defaultOptions), opts));
  77. }
  78. catch (e) {
  79. this.cancel();
  80. this._logService.error(e);
  81. this._logService.error('snippet_error');
  82. this._logService.error('insert_template=', template);
  83. this._logService.error('existing_template=', this._session ? this._session._logInfo() : '<no_session>');
  84. }
  85. }
  86. _doInsert(template, opts) {
  87. var _a;
  88. if (!this._editor.hasModel()) {
  89. return;
  90. }
  91. // don't listen while inserting the snippet
  92. // as that is the inflight state causing cancelation
  93. this._snippetListener.clear();
  94. if (opts.undoStopBefore) {
  95. this._editor.getModel().pushStackElement();
  96. }
  97. // don't merge
  98. if (this._session && typeof template !== 'string') {
  99. this.cancel();
  100. }
  101. if (!this._session) {
  102. this._modelVersionId = this._editor.getModel().getAlternativeVersionId();
  103. this._session = new SnippetSession(this._editor, template, opts, this._languageConfigurationService);
  104. this._session.insert();
  105. }
  106. else {
  107. assertType(typeof template === 'string');
  108. this._session.merge(template, opts);
  109. }
  110. if (opts.undoStopAfter) {
  111. this._editor.getModel().pushStackElement();
  112. }
  113. // regster completion item provider when there is any choice element
  114. if ((_a = this._session) === null || _a === void 0 ? void 0 : _a.hasChoice) {
  115. this._choiceCompletionItemProvider = {
  116. provideCompletionItems: (model, position) => {
  117. if (!this._session || model !== this._editor.getModel() || !Position.equals(this._editor.getPosition(), position)) {
  118. return undefined;
  119. }
  120. const { activeChoice } = this._session;
  121. if (!activeChoice || activeChoice.choice.options.length === 0) {
  122. return undefined;
  123. }
  124. const word = model.getValueInRange(activeChoice.range);
  125. const isAnyOfOptions = Boolean(activeChoice.choice.options.find(o => o.value === word));
  126. const suggestions = [];
  127. for (let i = 0; i < activeChoice.choice.options.length; i++) {
  128. const option = activeChoice.choice.options[i];
  129. suggestions.push({
  130. kind: 13 /* CompletionItemKind.Value */,
  131. label: option.value,
  132. insertText: option.value,
  133. sortText: 'a'.repeat(i + 1),
  134. range: activeChoice.range,
  135. filterText: isAnyOfOptions ? `${word}_${option.value}` : undefined,
  136. command: { id: 'jumpToNextSnippetPlaceholder', title: localize('next', 'Go to next placeholder...') }
  137. });
  138. }
  139. return { suggestions };
  140. }
  141. };
  142. const registration = this._languageFeaturesService.completionProvider.register({
  143. language: this._editor.getModel().getLanguageId(),
  144. pattern: this._editor.getModel().uri.fsPath,
  145. scheme: this._editor.getModel().uri.scheme
  146. }, this._choiceCompletionItemProvider);
  147. this._snippetListener.add(registration);
  148. }
  149. this._updateState();
  150. this._snippetListener.add(this._editor.onDidChangeModelContent(e => e.isFlush && this.cancel()));
  151. this._snippetListener.add(this._editor.onDidChangeModel(() => this.cancel()));
  152. this._snippetListener.add(this._editor.onDidChangeCursorSelection(() => this._updateState()));
  153. }
  154. _updateState() {
  155. if (!this._session || !this._editor.hasModel()) {
  156. // canceled in the meanwhile
  157. return;
  158. }
  159. if (this._modelVersionId === this._editor.getModel().getAlternativeVersionId()) {
  160. // undo until the 'before' state happened
  161. // and makes use cancel snippet mode
  162. return this.cancel();
  163. }
  164. if (!this._session.hasPlaceholder) {
  165. // don't listen for selection changes and don't
  166. // update context keys when the snippet is plain text
  167. return this.cancel();
  168. }
  169. if (this._session.isAtLastPlaceholder || !this._session.isSelectionWithinPlaceholders()) {
  170. this._editor.getModel().pushStackElement();
  171. return this.cancel();
  172. }
  173. this._inSnippet.set(true);
  174. this._hasPrevTabstop.set(!this._session.isAtFirstPlaceholder);
  175. this._hasNextTabstop.set(!this._session.isAtLastPlaceholder);
  176. this._handleChoice();
  177. }
  178. _handleChoice() {
  179. if (!this._session || !this._editor.hasModel()) {
  180. this._currentChoice = undefined;
  181. return;
  182. }
  183. const { activeChoice } = this._session;
  184. if (!activeChoice || !this._choiceCompletionItemProvider) {
  185. this._currentChoice = undefined;
  186. return;
  187. }
  188. if (this._currentChoice !== activeChoice.choice) {
  189. this._currentChoice = activeChoice.choice;
  190. // trigger suggest with the special choice completion provider
  191. queueMicrotask(() => {
  192. showSimpleSuggestions(this._editor, this._choiceCompletionItemProvider);
  193. });
  194. }
  195. }
  196. finish() {
  197. while (this._inSnippet.get()) {
  198. this.next();
  199. }
  200. }
  201. cancel(resetSelection = false) {
  202. var _a;
  203. this._inSnippet.reset();
  204. this._hasPrevTabstop.reset();
  205. this._hasNextTabstop.reset();
  206. this._snippetListener.clear();
  207. this._currentChoice = undefined;
  208. (_a = this._session) === null || _a === void 0 ? void 0 : _a.dispose();
  209. this._session = undefined;
  210. this._modelVersionId = -1;
  211. if (resetSelection) {
  212. // reset selection to the primary cursor when being asked
  213. // for. this happens when explicitly cancelling snippet mode,
  214. // e.g. when pressing ESC
  215. this._editor.setSelections([this._editor.getSelection()]);
  216. }
  217. }
  218. prev() {
  219. if (this._session) {
  220. this._session.prev();
  221. }
  222. this._updateState();
  223. }
  224. next() {
  225. if (this._session) {
  226. this._session.next();
  227. }
  228. this._updateState();
  229. }
  230. isInSnippet() {
  231. return Boolean(this._inSnippet.get());
  232. }
  233. };
  234. SnippetController2.ID = 'snippetController2';
  235. SnippetController2.InSnippetMode = new RawContextKey('inSnippetMode', false, localize('inSnippetMode', "Whether the editor in current in snippet mode"));
  236. SnippetController2.HasNextTabstop = new RawContextKey('hasNextTabstop', false, localize('hasNextTabstop', "Whether there is a next tab stop when in snippet mode"));
  237. SnippetController2.HasPrevTabstop = new RawContextKey('hasPrevTabstop', false, localize('hasPrevTabstop', "Whether there is a previous tab stop when in snippet mode"));
  238. SnippetController2 = __decorate([
  239. __param(1, ILogService),
  240. __param(2, ILanguageFeaturesService),
  241. __param(3, IContextKeyService),
  242. __param(4, ILanguageConfigurationService)
  243. ], SnippetController2);
  244. export { SnippetController2 };
  245. registerEditorContribution(SnippetController2.ID, SnippetController2);
  246. const CommandCtor = EditorCommand.bindToContribution(SnippetController2.get);
  247. registerEditorCommand(new CommandCtor({
  248. id: 'jumpToNextSnippetPlaceholder',
  249. precondition: ContextKeyExpr.and(SnippetController2.InSnippetMode, SnippetController2.HasNextTabstop),
  250. handler: ctrl => ctrl.next(),
  251. kbOpts: {
  252. weight: 100 /* KeybindingWeight.EditorContrib */ + 30,
  253. kbExpr: EditorContextKeys.editorTextFocus,
  254. primary: 2 /* KeyCode.Tab */
  255. }
  256. }));
  257. registerEditorCommand(new CommandCtor({
  258. id: 'jumpToPrevSnippetPlaceholder',
  259. precondition: ContextKeyExpr.and(SnippetController2.InSnippetMode, SnippetController2.HasPrevTabstop),
  260. handler: ctrl => ctrl.prev(),
  261. kbOpts: {
  262. weight: 100 /* KeybindingWeight.EditorContrib */ + 30,
  263. kbExpr: EditorContextKeys.editorTextFocus,
  264. primary: 1024 /* KeyMod.Shift */ | 2 /* KeyCode.Tab */
  265. }
  266. }));
  267. registerEditorCommand(new CommandCtor({
  268. id: 'leaveSnippet',
  269. precondition: SnippetController2.InSnippetMode,
  270. handler: ctrl => ctrl.cancel(true),
  271. kbOpts: {
  272. weight: 100 /* KeybindingWeight.EditorContrib */ + 30,
  273. kbExpr: EditorContextKeys.editorTextFocus,
  274. primary: 9 /* KeyCode.Escape */,
  275. secondary: [1024 /* KeyMod.Shift */ | 9 /* KeyCode.Escape */]
  276. }
  277. }));
  278. registerEditorCommand(new CommandCtor({
  279. id: 'acceptSnippet',
  280. precondition: SnippetController2.InSnippetMode,
  281. handler: ctrl => ctrl.finish(),
  282. // kbOpts: {
  283. // weight: KeybindingWeight.EditorContrib + 30,
  284. // kbExpr: EditorContextKeys.textFocus,
  285. // primary: KeyCode.Enter,
  286. // }
  287. }));
  288. // ---
  289. export function performSnippetEdit(editor, snippet, selections) {
  290. const controller = SnippetController2.get(editor);
  291. if (!controller) {
  292. return false;
  293. }
  294. editor.focus();
  295. controller.apply(selections.map(selection => {
  296. return {
  297. range: Selection.liftSelection(selection),
  298. template: snippet
  299. };
  300. }));
  301. return controller.isInSnippet();
  302. }