folding.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  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 { createCancelablePromise, Delayer, RunOnceScheduler } from '../../../base/common/async.js';
  15. import { onUnexpectedError } from '../../../base/common/errors.js';
  16. import { KeyChord } from '../../../base/common/keyCodes.js';
  17. import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
  18. import { escapeRegExpCharacters } from '../../../base/common/strings.js';
  19. import * as types from '../../../base/common/types.js';
  20. import './folding.css';
  21. import { StableEditorScrollState } from '../../browser/core/editorState.js';
  22. import { EditorAction, registerEditorAction, registerEditorContribution, registerInstantiatedEditorAction } from '../../browser/editorExtensions.js';
  23. import { EditorContextKeys } from '../../common/editorContextKeys.js';
  24. import { FoldingRangeKind, FoldingRangeProviderRegistry } from '../../common/modes.js';
  25. import { LanguageConfigurationRegistry } from '../../common/modes/languageConfigurationRegistry.js';
  26. import { FoldingModel, getNextFoldLine, getParentFoldLine as getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateForType, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp, toggleCollapseState } from './foldingModel.js';
  27. import { HiddenRangeModel } from './hiddenRangeModel.js';
  28. import { IndentRangeProvider } from './indentRangeProvider.js';
  29. import { ID_INIT_PROVIDER, InitializingRangeProvider } from './intializingRangeProvider.js';
  30. import * as nls from '../../../nls.js';
  31. import { IContextKeyService, RawContextKey } from '../../../platform/contextkey/common/contextkey.js';
  32. import { editorSelectionBackground, iconForeground, registerColor, transparent } from '../../../platform/theme/common/colorRegistry.js';
  33. import { registerThemingParticipant, ThemeIcon } from '../../../platform/theme/common/themeService.js';
  34. import { foldingCollapsedIcon, FoldingDecorationProvider, foldingExpandedIcon } from './foldingDecorations.js';
  35. import { ID_SYNTAX_PROVIDER, SyntaxRangeProvider } from './syntaxRangeProvider.js';
  36. const CONTEXT_FOLDING_ENABLED = new RawContextKey('foldingEnabled', false);
  37. let FoldingController = class FoldingController extends Disposable {
  38. constructor(editor, contextKeyService) {
  39. super();
  40. this.contextKeyService = contextKeyService;
  41. this.localToDispose = this._register(new DisposableStore());
  42. this.editor = editor;
  43. const options = this.editor.getOptions();
  44. this._isEnabled = options.get(37 /* folding */);
  45. this._useFoldingProviders = options.get(38 /* foldingStrategy */) !== 'indentation';
  46. this._unfoldOnClickAfterEndOfLine = options.get(41 /* unfoldOnClickAfterEndOfLine */);
  47. this._restoringViewState = false;
  48. this._currentModelHasFoldedImports = false;
  49. this._foldingImportsByDefault = options.get(40 /* foldingImportsByDefault */);
  50. this.foldingModel = null;
  51. this.hiddenRangeModel = null;
  52. this.rangeProvider = null;
  53. this.foldingRegionPromise = null;
  54. this.foldingStateMemento = null;
  55. this.foldingModelPromise = null;
  56. this.updateScheduler = null;
  57. this.cursorChangedScheduler = null;
  58. this.mouseDownInfo = null;
  59. this.foldingDecorationProvider = new FoldingDecorationProvider(editor);
  60. this.foldingDecorationProvider.autoHideFoldingControls = options.get(98 /* showFoldingControls */) === 'mouseover';
  61. this.foldingDecorationProvider.showFoldingHighlights = options.get(39 /* foldingHighlight */);
  62. this.foldingEnabled = CONTEXT_FOLDING_ENABLED.bindTo(this.contextKeyService);
  63. this.foldingEnabled.set(this._isEnabled);
  64. this._register(this.editor.onDidChangeModel(() => this.onModelChanged()));
  65. this._register(this.editor.onDidChangeConfiguration((e) => {
  66. if (e.hasChanged(37 /* folding */)) {
  67. this._isEnabled = this.editor.getOptions().get(37 /* folding */);
  68. this.foldingEnabled.set(this._isEnabled);
  69. this.onModelChanged();
  70. }
  71. if (e.hasChanged(98 /* showFoldingControls */) || e.hasChanged(39 /* foldingHighlight */)) {
  72. const options = this.editor.getOptions();
  73. this.foldingDecorationProvider.autoHideFoldingControls = options.get(98 /* showFoldingControls */) === 'mouseover';
  74. this.foldingDecorationProvider.showFoldingHighlights = options.get(39 /* foldingHighlight */);
  75. this.triggerFoldingModelChanged();
  76. }
  77. if (e.hasChanged(38 /* foldingStrategy */)) {
  78. this._useFoldingProviders = this.editor.getOptions().get(38 /* foldingStrategy */) !== 'indentation';
  79. this.onFoldingStrategyChanged();
  80. }
  81. if (e.hasChanged(41 /* unfoldOnClickAfterEndOfLine */)) {
  82. this._unfoldOnClickAfterEndOfLine = this.editor.getOptions().get(41 /* unfoldOnClickAfterEndOfLine */);
  83. }
  84. if (e.hasChanged(40 /* foldingImportsByDefault */)) {
  85. this._foldingImportsByDefault = this.editor.getOptions().get(40 /* foldingImportsByDefault */);
  86. }
  87. }));
  88. this.onModelChanged();
  89. }
  90. static get(editor) {
  91. return editor.getContribution(FoldingController.ID);
  92. }
  93. /**
  94. * Store view state.
  95. */
  96. saveViewState() {
  97. let model = this.editor.getModel();
  98. if (!model || !this._isEnabled || model.isTooLargeForTokenization()) {
  99. return {};
  100. }
  101. if (this.foldingModel) { // disposed ?
  102. let collapsedRegions = this.foldingModel.isInitialized ? this.foldingModel.getMemento() : this.hiddenRangeModel.getMemento();
  103. let provider = this.rangeProvider ? this.rangeProvider.id : undefined;
  104. return { collapsedRegions, lineCount: model.getLineCount(), provider, foldedImports: this._currentModelHasFoldedImports };
  105. }
  106. return undefined;
  107. }
  108. /**
  109. * Restore view state.
  110. */
  111. restoreViewState(state) {
  112. let model = this.editor.getModel();
  113. if (!model || !this._isEnabled || model.isTooLargeForTokenization() || !this.hiddenRangeModel) {
  114. return;
  115. }
  116. if (!state || state.lineCount !== model.getLineCount()) {
  117. return;
  118. }
  119. this._currentModelHasFoldedImports = !!state.foldedImports;
  120. if (!state.collapsedRegions) {
  121. return;
  122. }
  123. if (state.provider === ID_SYNTAX_PROVIDER || state.provider === ID_INIT_PROVIDER) {
  124. this.foldingStateMemento = state;
  125. }
  126. const collapsedRegions = state.collapsedRegions;
  127. // set the hidden ranges right away, before waiting for the folding model.
  128. if (this.hiddenRangeModel.applyMemento(collapsedRegions)) {
  129. const foldingModel = this.getFoldingModel();
  130. if (foldingModel) {
  131. foldingModel.then(foldingModel => {
  132. if (foldingModel) {
  133. this._restoringViewState = true;
  134. try {
  135. foldingModel.applyMemento(collapsedRegions);
  136. }
  137. finally {
  138. this._restoringViewState = false;
  139. }
  140. }
  141. }).then(undefined, onUnexpectedError);
  142. }
  143. }
  144. }
  145. onModelChanged() {
  146. this.localToDispose.clear();
  147. let model = this.editor.getModel();
  148. if (!this._isEnabled || !model || model.isTooLargeForTokenization()) {
  149. // huge files get no view model, so they cannot support hidden areas
  150. return;
  151. }
  152. this._currentModelHasFoldedImports = false;
  153. this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider);
  154. this.localToDispose.add(this.foldingModel);
  155. this.hiddenRangeModel = new HiddenRangeModel(this.foldingModel);
  156. this.localToDispose.add(this.hiddenRangeModel);
  157. this.localToDispose.add(this.hiddenRangeModel.onDidChange(hr => this.onHiddenRangesChanges(hr)));
  158. this.updateScheduler = new Delayer(200);
  159. this.cursorChangedScheduler = new RunOnceScheduler(() => this.revealCursor(), 200);
  160. this.localToDispose.add(this.cursorChangedScheduler);
  161. this.localToDispose.add(FoldingRangeProviderRegistry.onDidChange(() => this.onFoldingStrategyChanged()));
  162. this.localToDispose.add(this.editor.onDidChangeModelLanguageConfiguration(() => this.onFoldingStrategyChanged())); // covers model language changes as well
  163. this.localToDispose.add(this.editor.onDidChangeModelContent(e => this.onDidChangeModelContent(e)));
  164. this.localToDispose.add(this.editor.onDidChangeCursorPosition(() => this.onCursorPositionChanged()));
  165. this.localToDispose.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
  166. this.localToDispose.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));
  167. this.localToDispose.add({
  168. dispose: () => {
  169. if (this.foldingRegionPromise) {
  170. this.foldingRegionPromise.cancel();
  171. this.foldingRegionPromise = null;
  172. }
  173. if (this.updateScheduler) {
  174. this.updateScheduler.cancel();
  175. }
  176. this.updateScheduler = null;
  177. this.foldingModel = null;
  178. this.foldingModelPromise = null;
  179. this.hiddenRangeModel = null;
  180. this.cursorChangedScheduler = null;
  181. this.foldingStateMemento = null;
  182. if (this.rangeProvider) {
  183. this.rangeProvider.dispose();
  184. }
  185. this.rangeProvider = null;
  186. }
  187. });
  188. this.triggerFoldingModelChanged();
  189. }
  190. onFoldingStrategyChanged() {
  191. if (this.rangeProvider) {
  192. this.rangeProvider.dispose();
  193. }
  194. this.rangeProvider = null;
  195. this.triggerFoldingModelChanged();
  196. }
  197. getRangeProvider(editorModel) {
  198. if (this.rangeProvider) {
  199. return this.rangeProvider;
  200. }
  201. this.rangeProvider = new IndentRangeProvider(editorModel); // fallback
  202. if (this._useFoldingProviders && this.foldingModel) {
  203. let foldingProviders = FoldingRangeProviderRegistry.ordered(this.foldingModel.textModel);
  204. if (foldingProviders.length === 0 && this.foldingStateMemento && this.foldingStateMemento.collapsedRegions) {
  205. const rangeProvider = this.rangeProvider = new InitializingRangeProvider(editorModel, this.foldingStateMemento.collapsedRegions, () => {
  206. // if after 30 the InitializingRangeProvider is still not replaced, force a refresh
  207. this.foldingStateMemento = null;
  208. this.onFoldingStrategyChanged();
  209. }, 30000);
  210. return rangeProvider; // keep memento in case there are still no foldingProviders on the next request.
  211. }
  212. else if (foldingProviders.length > 0) {
  213. this.rangeProvider = new SyntaxRangeProvider(editorModel, foldingProviders, () => this.triggerFoldingModelChanged());
  214. }
  215. }
  216. this.foldingStateMemento = null;
  217. return this.rangeProvider;
  218. }
  219. getFoldingModel() {
  220. return this.foldingModelPromise;
  221. }
  222. onDidChangeModelContent(e) {
  223. var _a;
  224. (_a = this.hiddenRangeModel) === null || _a === void 0 ? void 0 : _a.notifyChangeModelContent(e);
  225. this.triggerFoldingModelChanged();
  226. }
  227. triggerFoldingModelChanged() {
  228. if (this.updateScheduler) {
  229. if (this.foldingRegionPromise) {
  230. this.foldingRegionPromise.cancel();
  231. this.foldingRegionPromise = null;
  232. }
  233. this.foldingModelPromise = this.updateScheduler.trigger(() => {
  234. const foldingModel = this.foldingModel;
  235. if (!foldingModel) { // null if editor has been disposed, or folding turned off
  236. return null;
  237. }
  238. const provider = this.getRangeProvider(foldingModel.textModel);
  239. let foldingRegionPromise = this.foldingRegionPromise = createCancelablePromise(token => provider.compute(token));
  240. return foldingRegionPromise.then(foldingRanges => {
  241. if (foldingRanges && foldingRegionPromise === this.foldingRegionPromise) { // new request or cancelled in the meantime?
  242. let scrollState;
  243. if (this._foldingImportsByDefault && !this._currentModelHasFoldedImports) {
  244. const hasChanges = foldingRanges.setCollapsedAllOfType(FoldingRangeKind.Imports.value, true);
  245. if (hasChanges) {
  246. scrollState = StableEditorScrollState.capture(this.editor);
  247. this._currentModelHasFoldedImports = hasChanges;
  248. }
  249. }
  250. // some cursors might have moved into hidden regions, make sure they are in expanded regions
  251. let selections = this.editor.getSelections();
  252. let selectionLineNumbers = selections ? selections.map(s => s.startLineNumber) : [];
  253. foldingModel.update(foldingRanges, selectionLineNumbers);
  254. if (scrollState) {
  255. scrollState.restore(this.editor);
  256. }
  257. }
  258. return foldingModel;
  259. });
  260. }).then(undefined, (err) => {
  261. onUnexpectedError(err);
  262. return null;
  263. });
  264. }
  265. }
  266. onHiddenRangesChanges(hiddenRanges) {
  267. if (this.hiddenRangeModel && hiddenRanges.length && !this._restoringViewState) {
  268. let selections = this.editor.getSelections();
  269. if (selections) {
  270. if (this.hiddenRangeModel.adjustSelections(selections)) {
  271. this.editor.setSelections(selections);
  272. }
  273. }
  274. }
  275. this.editor.setHiddenAreas(hiddenRanges);
  276. }
  277. onCursorPositionChanged() {
  278. if (this.hiddenRangeModel && this.hiddenRangeModel.hasRanges()) {
  279. this.cursorChangedScheduler.schedule();
  280. }
  281. }
  282. revealCursor() {
  283. const foldingModel = this.getFoldingModel();
  284. if (!foldingModel) {
  285. return;
  286. }
  287. foldingModel.then(foldingModel => {
  288. if (foldingModel) {
  289. let selections = this.editor.getSelections();
  290. if (selections && selections.length > 0) {
  291. let toToggle = [];
  292. for (let selection of selections) {
  293. let lineNumber = selection.selectionStartLineNumber;
  294. if (this.hiddenRangeModel && this.hiddenRangeModel.isHidden(lineNumber)) {
  295. toToggle.push(...foldingModel.getAllRegionsAtLine(lineNumber, r => r.isCollapsed && lineNumber > r.startLineNumber));
  296. }
  297. }
  298. if (toToggle.length) {
  299. foldingModel.toggleCollapseState(toToggle);
  300. this.reveal(selections[0].getPosition());
  301. }
  302. }
  303. }
  304. }).then(undefined, onUnexpectedError);
  305. }
  306. onEditorMouseDown(e) {
  307. this.mouseDownInfo = null;
  308. if (!this.hiddenRangeModel || !e.target || !e.target.range) {
  309. return;
  310. }
  311. if (!e.event.leftButton && !e.event.middleButton) {
  312. return;
  313. }
  314. const range = e.target.range;
  315. let iconClicked = false;
  316. switch (e.target.type) {
  317. case 4 /* GUTTER_LINE_DECORATIONS */:
  318. const data = e.target.detail;
  319. const offsetLeftInGutter = e.target.element.offsetLeft;
  320. const gutterOffsetX = data.offsetX - offsetLeftInGutter;
  321. // const gutterOffsetX = data.offsetX - data.glyphMarginWidth - data.lineNumbersWidth - data.glyphMarginLeft;
  322. // TODO@joao TODO@alex TODO@martin this is such that we don't collide with dirty diff
  323. if (gutterOffsetX < 5) { // the whitespace between the border and the real folding icon border is 5px
  324. return;
  325. }
  326. iconClicked = true;
  327. break;
  328. case 7 /* CONTENT_EMPTY */: {
  329. if (this._unfoldOnClickAfterEndOfLine && this.hiddenRangeModel.hasRanges()) {
  330. const data = e.target.detail;
  331. if (!data.isAfterLines) {
  332. break;
  333. }
  334. }
  335. return;
  336. }
  337. case 6 /* CONTENT_TEXT */: {
  338. if (this.hiddenRangeModel.hasRanges()) {
  339. let model = this.editor.getModel();
  340. if (model && range.startColumn === model.getLineMaxColumn(range.startLineNumber)) {
  341. break;
  342. }
  343. }
  344. return;
  345. }
  346. default:
  347. return;
  348. }
  349. this.mouseDownInfo = { lineNumber: range.startLineNumber, iconClicked };
  350. }
  351. onEditorMouseUp(e) {
  352. const foldingModel = this.getFoldingModel();
  353. if (!foldingModel || !this.mouseDownInfo || !e.target) {
  354. return;
  355. }
  356. let lineNumber = this.mouseDownInfo.lineNumber;
  357. let iconClicked = this.mouseDownInfo.iconClicked;
  358. let range = e.target.range;
  359. if (!range || range.startLineNumber !== lineNumber) {
  360. return;
  361. }
  362. if (iconClicked) {
  363. if (e.target.type !== 4 /* GUTTER_LINE_DECORATIONS */) {
  364. return;
  365. }
  366. }
  367. else {
  368. let model = this.editor.getModel();
  369. if (!model || range.startColumn !== model.getLineMaxColumn(lineNumber)) {
  370. return;
  371. }
  372. }
  373. foldingModel.then(foldingModel => {
  374. if (foldingModel) {
  375. let region = foldingModel.getRegionAtLine(lineNumber);
  376. if (region && region.startLineNumber === lineNumber) {
  377. let isCollapsed = region.isCollapsed;
  378. if (iconClicked || isCollapsed) {
  379. let surrounding = e.event.altKey;
  380. let toToggle = [];
  381. if (surrounding) {
  382. let filter = (otherRegion) => !otherRegion.containedBy(region) && !region.containedBy(otherRegion);
  383. let toMaybeToggle = foldingModel.getRegionsInside(null, filter);
  384. for (const r of toMaybeToggle) {
  385. if (r.isCollapsed) {
  386. toToggle.push(r);
  387. }
  388. }
  389. // if any surrounding regions are folded, unfold those. Otherwise, fold all surrounding
  390. if (toToggle.length === 0) {
  391. toToggle = toMaybeToggle;
  392. }
  393. }
  394. else {
  395. let recursive = e.event.middleButton || e.event.shiftKey;
  396. if (recursive) {
  397. for (const r of foldingModel.getRegionsInside(region)) {
  398. if (r.isCollapsed === isCollapsed) {
  399. toToggle.push(r);
  400. }
  401. }
  402. }
  403. // when recursive, first only collapse all children. If all are already folded or there are no children, also fold parent.
  404. if (isCollapsed || !recursive || toToggle.length === 0) {
  405. toToggle.push(region);
  406. }
  407. }
  408. foldingModel.toggleCollapseState(toToggle);
  409. this.reveal({ lineNumber, column: 1 });
  410. }
  411. }
  412. }
  413. }).then(undefined, onUnexpectedError);
  414. }
  415. reveal(position) {
  416. this.editor.revealPositionInCenterIfOutsideViewport(position, 0 /* Smooth */);
  417. }
  418. };
  419. FoldingController.ID = 'editor.contrib.folding';
  420. FoldingController = __decorate([
  421. __param(1, IContextKeyService)
  422. ], FoldingController);
  423. export { FoldingController };
  424. class FoldingAction extends EditorAction {
  425. runEditorCommand(accessor, editor, args) {
  426. let foldingController = FoldingController.get(editor);
  427. if (!foldingController) {
  428. return;
  429. }
  430. let foldingModelPromise = foldingController.getFoldingModel();
  431. if (foldingModelPromise) {
  432. this.reportTelemetry(accessor, editor);
  433. return foldingModelPromise.then(foldingModel => {
  434. if (foldingModel) {
  435. this.invoke(foldingController, foldingModel, editor, args);
  436. const selection = editor.getSelection();
  437. if (selection) {
  438. foldingController.reveal(selection.getStartPosition());
  439. }
  440. }
  441. });
  442. }
  443. }
  444. getSelectedLines(editor) {
  445. let selections = editor.getSelections();
  446. return selections ? selections.map(s => s.startLineNumber) : [];
  447. }
  448. getLineNumbers(args, editor) {
  449. if (args && args.selectionLines) {
  450. return args.selectionLines.map(l => l + 1); // to 0-bases line numbers
  451. }
  452. return this.getSelectedLines(editor);
  453. }
  454. run(_accessor, _editor) {
  455. }
  456. }
  457. function foldingArgumentsConstraint(args) {
  458. if (!types.isUndefined(args)) {
  459. if (!types.isObject(args)) {
  460. return false;
  461. }
  462. const foldingArgs = args;
  463. if (!types.isUndefined(foldingArgs.levels) && !types.isNumber(foldingArgs.levels)) {
  464. return false;
  465. }
  466. if (!types.isUndefined(foldingArgs.direction) && !types.isString(foldingArgs.direction)) {
  467. return false;
  468. }
  469. if (!types.isUndefined(foldingArgs.selectionLines) && (!types.isArray(foldingArgs.selectionLines) || !foldingArgs.selectionLines.every(types.isNumber))) {
  470. return false;
  471. }
  472. }
  473. return true;
  474. }
  475. class UnfoldAction extends FoldingAction {
  476. constructor() {
  477. super({
  478. id: 'editor.unfold',
  479. label: nls.localize('unfoldAction.label', "Unfold"),
  480. alias: 'Unfold',
  481. precondition: CONTEXT_FOLDING_ENABLED,
  482. kbOpts: {
  483. kbExpr: EditorContextKeys.editorTextFocus,
  484. primary: 2048 /* CtrlCmd */ | 1024 /* Shift */ | 89 /* BracketRight */,
  485. mac: {
  486. primary: 2048 /* CtrlCmd */ | 512 /* Alt */ | 89 /* BracketRight */
  487. },
  488. weight: 100 /* EditorContrib */
  489. },
  490. description: {
  491. description: 'Unfold the content in the editor',
  492. args: [
  493. {
  494. name: 'Unfold editor argument',
  495. description: `Property-value pairs that can be passed through this argument:
  496. * 'levels': Number of levels to unfold. If not set, defaults to 1.
  497. * 'direction': If 'up', unfold given number of levels up otherwise unfolds down.
  498. * 'selectionLines': The start lines (0-based) of the editor selections to apply the unfold action to. If not set, the active selection(s) will be used.
  499. `,
  500. constraint: foldingArgumentsConstraint,
  501. schema: {
  502. 'type': 'object',
  503. 'properties': {
  504. 'levels': {
  505. 'type': 'number',
  506. 'default': 1
  507. },
  508. 'direction': {
  509. 'type': 'string',
  510. 'enum': ['up', 'down'],
  511. 'default': 'down'
  512. },
  513. 'selectionLines': {
  514. 'type': 'array',
  515. 'items': {
  516. 'type': 'number'
  517. }
  518. }
  519. }
  520. }
  521. }
  522. ]
  523. }
  524. });
  525. }
  526. invoke(_foldingController, foldingModel, editor, args) {
  527. let levels = args && args.levels || 1;
  528. let lineNumbers = this.getLineNumbers(args, editor);
  529. if (args && args.direction === 'up') {
  530. setCollapseStateLevelsUp(foldingModel, false, levels, lineNumbers);
  531. }
  532. else {
  533. setCollapseStateLevelsDown(foldingModel, false, levels, lineNumbers);
  534. }
  535. }
  536. }
  537. class UnFoldRecursivelyAction extends FoldingAction {
  538. constructor() {
  539. super({
  540. id: 'editor.unfoldRecursively',
  541. label: nls.localize('unFoldRecursivelyAction.label', "Unfold Recursively"),
  542. alias: 'Unfold Recursively',
  543. precondition: CONTEXT_FOLDING_ENABLED,
  544. kbOpts: {
  545. kbExpr: EditorContextKeys.editorTextFocus,
  546. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | 89 /* BracketRight */),
  547. weight: 100 /* EditorContrib */
  548. }
  549. });
  550. }
  551. invoke(_foldingController, foldingModel, editor, _args) {
  552. setCollapseStateLevelsDown(foldingModel, false, Number.MAX_VALUE, this.getSelectedLines(editor));
  553. }
  554. }
  555. class FoldAction extends FoldingAction {
  556. constructor() {
  557. super({
  558. id: 'editor.fold',
  559. label: nls.localize('foldAction.label', "Fold"),
  560. alias: 'Fold',
  561. precondition: CONTEXT_FOLDING_ENABLED,
  562. kbOpts: {
  563. kbExpr: EditorContextKeys.editorTextFocus,
  564. primary: 2048 /* CtrlCmd */ | 1024 /* Shift */ | 87 /* BracketLeft */,
  565. mac: {
  566. primary: 2048 /* CtrlCmd */ | 512 /* Alt */ | 87 /* BracketLeft */
  567. },
  568. weight: 100 /* EditorContrib */
  569. },
  570. description: {
  571. description: 'Fold the content in the editor',
  572. args: [
  573. {
  574. name: 'Fold editor argument',
  575. description: `Property-value pairs that can be passed through this argument:
  576. * 'levels': Number of levels to fold.
  577. * 'direction': If 'up', folds given number of levels up otherwise folds down.
  578. * 'selectionLines': The start lines (0-based) of the editor selections to apply the fold action to. If not set, the active selection(s) will be used.
  579. If no levels or direction is set, folds the region at the locations or if already collapsed, the first uncollapsed parent instead.
  580. `,
  581. constraint: foldingArgumentsConstraint,
  582. schema: {
  583. 'type': 'object',
  584. 'properties': {
  585. 'levels': {
  586. 'type': 'number',
  587. },
  588. 'direction': {
  589. 'type': 'string',
  590. 'enum': ['up', 'down'],
  591. },
  592. 'selectionLines': {
  593. 'type': 'array',
  594. 'items': {
  595. 'type': 'number'
  596. }
  597. }
  598. }
  599. }
  600. }
  601. ]
  602. }
  603. });
  604. }
  605. invoke(_foldingController, foldingModel, editor, args) {
  606. let lineNumbers = this.getLineNumbers(args, editor);
  607. const levels = args && args.levels;
  608. const direction = args && args.direction;
  609. if (typeof levels !== 'number' && typeof direction !== 'string') {
  610. // fold the region at the location or if already collapsed, the first uncollapsed parent instead.
  611. setCollapseStateUp(foldingModel, true, lineNumbers);
  612. }
  613. else {
  614. if (direction === 'up') {
  615. setCollapseStateLevelsUp(foldingModel, true, levels || 1, lineNumbers);
  616. }
  617. else {
  618. setCollapseStateLevelsDown(foldingModel, true, levels || 1, lineNumbers);
  619. }
  620. }
  621. }
  622. }
  623. class ToggleFoldAction extends FoldingAction {
  624. constructor() {
  625. super({
  626. id: 'editor.toggleFold',
  627. label: nls.localize('toggleFoldAction.label', "Toggle Fold"),
  628. alias: 'Toggle Fold',
  629. precondition: CONTEXT_FOLDING_ENABLED,
  630. kbOpts: {
  631. kbExpr: EditorContextKeys.editorTextFocus,
  632. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | 42 /* KeyL */),
  633. weight: 100 /* EditorContrib */
  634. }
  635. });
  636. }
  637. invoke(_foldingController, foldingModel, editor) {
  638. let selectedLines = this.getSelectedLines(editor);
  639. toggleCollapseState(foldingModel, 1, selectedLines);
  640. }
  641. }
  642. class FoldRecursivelyAction extends FoldingAction {
  643. constructor() {
  644. super({
  645. id: 'editor.foldRecursively',
  646. label: nls.localize('foldRecursivelyAction.label', "Fold Recursively"),
  647. alias: 'Fold Recursively',
  648. precondition: CONTEXT_FOLDING_ENABLED,
  649. kbOpts: {
  650. kbExpr: EditorContextKeys.editorTextFocus,
  651. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | 87 /* BracketLeft */),
  652. weight: 100 /* EditorContrib */
  653. }
  654. });
  655. }
  656. invoke(_foldingController, foldingModel, editor) {
  657. let selectedLines = this.getSelectedLines(editor);
  658. setCollapseStateLevelsDown(foldingModel, true, Number.MAX_VALUE, selectedLines);
  659. }
  660. }
  661. class FoldAllBlockCommentsAction extends FoldingAction {
  662. constructor() {
  663. super({
  664. id: 'editor.foldAllBlockComments',
  665. label: nls.localize('foldAllBlockComments.label', "Fold All Block Comments"),
  666. alias: 'Fold All Block Comments',
  667. precondition: CONTEXT_FOLDING_ENABLED,
  668. kbOpts: {
  669. kbExpr: EditorContextKeys.editorTextFocus,
  670. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | 85 /* Slash */),
  671. weight: 100 /* EditorContrib */
  672. }
  673. });
  674. }
  675. invoke(_foldingController, foldingModel, editor) {
  676. if (foldingModel.regions.hasTypes()) {
  677. setCollapseStateForType(foldingModel, FoldingRangeKind.Comment.value, true);
  678. }
  679. else {
  680. const editorModel = editor.getModel();
  681. if (!editorModel) {
  682. return;
  683. }
  684. const comments = LanguageConfigurationRegistry.getComments(editorModel.getLanguageId());
  685. if (comments && comments.blockCommentStartToken) {
  686. let regExp = new RegExp('^\\s*' + escapeRegExpCharacters(comments.blockCommentStartToken));
  687. setCollapseStateForMatchingLines(foldingModel, regExp, true);
  688. }
  689. }
  690. }
  691. }
  692. class FoldAllRegionsAction extends FoldingAction {
  693. constructor() {
  694. super({
  695. id: 'editor.foldAllMarkerRegions',
  696. label: nls.localize('foldAllMarkerRegions.label', "Fold All Regions"),
  697. alias: 'Fold All Regions',
  698. precondition: CONTEXT_FOLDING_ENABLED,
  699. kbOpts: {
  700. kbExpr: EditorContextKeys.editorTextFocus,
  701. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | 29 /* Digit8 */),
  702. weight: 100 /* EditorContrib */
  703. }
  704. });
  705. }
  706. invoke(_foldingController, foldingModel, editor) {
  707. if (foldingModel.regions.hasTypes()) {
  708. setCollapseStateForType(foldingModel, FoldingRangeKind.Region.value, true);
  709. }
  710. else {
  711. const editorModel = editor.getModel();
  712. if (!editorModel) {
  713. return;
  714. }
  715. const foldingRules = LanguageConfigurationRegistry.getFoldingRules(editorModel.getLanguageId());
  716. if (foldingRules && foldingRules.markers && foldingRules.markers.start) {
  717. let regExp = new RegExp(foldingRules.markers.start);
  718. setCollapseStateForMatchingLines(foldingModel, regExp, true);
  719. }
  720. }
  721. }
  722. }
  723. class UnfoldAllRegionsAction extends FoldingAction {
  724. constructor() {
  725. super({
  726. id: 'editor.unfoldAllMarkerRegions',
  727. label: nls.localize('unfoldAllMarkerRegions.label', "Unfold All Regions"),
  728. alias: 'Unfold All Regions',
  729. precondition: CONTEXT_FOLDING_ENABLED,
  730. kbOpts: {
  731. kbExpr: EditorContextKeys.editorTextFocus,
  732. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | 30 /* Digit9 */),
  733. weight: 100 /* EditorContrib */
  734. }
  735. });
  736. }
  737. invoke(_foldingController, foldingModel, editor) {
  738. if (foldingModel.regions.hasTypes()) {
  739. setCollapseStateForType(foldingModel, FoldingRangeKind.Region.value, false);
  740. }
  741. else {
  742. const editorModel = editor.getModel();
  743. if (!editorModel) {
  744. return;
  745. }
  746. const foldingRules = LanguageConfigurationRegistry.getFoldingRules(editorModel.getLanguageId());
  747. if (foldingRules && foldingRules.markers && foldingRules.markers.start) {
  748. let regExp = new RegExp(foldingRules.markers.start);
  749. setCollapseStateForMatchingLines(foldingModel, regExp, false);
  750. }
  751. }
  752. }
  753. }
  754. class FoldAllRegionsExceptAction extends FoldingAction {
  755. constructor() {
  756. super({
  757. id: 'editor.foldAllExcept',
  758. label: nls.localize('foldAllExcept.label', "Fold All Regions Except Selected"),
  759. alias: 'Fold All Regions Except Selected',
  760. precondition: CONTEXT_FOLDING_ENABLED,
  761. kbOpts: {
  762. kbExpr: EditorContextKeys.editorTextFocus,
  763. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | 83 /* Minus */),
  764. weight: 100 /* EditorContrib */
  765. }
  766. });
  767. }
  768. invoke(_foldingController, foldingModel, editor) {
  769. let selectedLines = this.getSelectedLines(editor);
  770. setCollapseStateForRest(foldingModel, true, selectedLines);
  771. }
  772. }
  773. class UnfoldAllRegionsExceptAction extends FoldingAction {
  774. constructor() {
  775. super({
  776. id: 'editor.unfoldAllExcept',
  777. label: nls.localize('unfoldAllExcept.label', "Unfold All Regions Except Selected"),
  778. alias: 'Unfold All Regions Except Selected',
  779. precondition: CONTEXT_FOLDING_ENABLED,
  780. kbOpts: {
  781. kbExpr: EditorContextKeys.editorTextFocus,
  782. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | 81 /* Equal */),
  783. weight: 100 /* EditorContrib */
  784. }
  785. });
  786. }
  787. invoke(_foldingController, foldingModel, editor) {
  788. let selectedLines = this.getSelectedLines(editor);
  789. setCollapseStateForRest(foldingModel, false, selectedLines);
  790. }
  791. }
  792. class FoldAllAction extends FoldingAction {
  793. constructor() {
  794. super({
  795. id: 'editor.foldAll',
  796. label: nls.localize('foldAllAction.label', "Fold All"),
  797. alias: 'Fold All',
  798. precondition: CONTEXT_FOLDING_ENABLED,
  799. kbOpts: {
  800. kbExpr: EditorContextKeys.editorTextFocus,
  801. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | 21 /* Digit0 */),
  802. weight: 100 /* EditorContrib */
  803. }
  804. });
  805. }
  806. invoke(_foldingController, foldingModel, _editor) {
  807. setCollapseStateLevelsDown(foldingModel, true);
  808. }
  809. }
  810. class UnfoldAllAction extends FoldingAction {
  811. constructor() {
  812. super({
  813. id: 'editor.unfoldAll',
  814. label: nls.localize('unfoldAllAction.label', "Unfold All"),
  815. alias: 'Unfold All',
  816. precondition: CONTEXT_FOLDING_ENABLED,
  817. kbOpts: {
  818. kbExpr: EditorContextKeys.editorTextFocus,
  819. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | 40 /* KeyJ */),
  820. weight: 100 /* EditorContrib */
  821. }
  822. });
  823. }
  824. invoke(_foldingController, foldingModel, _editor) {
  825. setCollapseStateLevelsDown(foldingModel, false);
  826. }
  827. }
  828. class FoldLevelAction extends FoldingAction {
  829. getFoldingLevel() {
  830. return parseInt(this.id.substr(FoldLevelAction.ID_PREFIX.length));
  831. }
  832. invoke(_foldingController, foldingModel, editor) {
  833. setCollapseStateAtLevel(foldingModel, this.getFoldingLevel(), true, this.getSelectedLines(editor));
  834. }
  835. }
  836. FoldLevelAction.ID_PREFIX = 'editor.foldLevel';
  837. FoldLevelAction.ID = (level) => FoldLevelAction.ID_PREFIX + level;
  838. /** Action to go to the parent fold of current line */
  839. class GotoParentFoldAction extends FoldingAction {
  840. constructor() {
  841. super({
  842. id: 'editor.gotoParentFold',
  843. label: nls.localize('gotoParentFold.label', "Go to Parent Fold"),
  844. alias: 'Go to Parent Fold',
  845. precondition: CONTEXT_FOLDING_ENABLED,
  846. kbOpts: {
  847. kbExpr: EditorContextKeys.editorTextFocus,
  848. weight: 100 /* EditorContrib */
  849. }
  850. });
  851. }
  852. invoke(_foldingController, foldingModel, editor) {
  853. let selectedLines = this.getSelectedLines(editor);
  854. if (selectedLines.length > 0) {
  855. let startLineNumber = getParentFoldLine(selectedLines[0], foldingModel);
  856. if (startLineNumber !== null) {
  857. editor.setSelection({
  858. startLineNumber: startLineNumber,
  859. startColumn: 1,
  860. endLineNumber: startLineNumber,
  861. endColumn: 1
  862. });
  863. }
  864. }
  865. }
  866. }
  867. /** Action to go to the previous fold of current line */
  868. class GotoPreviousFoldAction extends FoldingAction {
  869. constructor() {
  870. super({
  871. id: 'editor.gotoPreviousFold',
  872. label: nls.localize('gotoPreviousFold.label', "Go to Previous Folding Range"),
  873. alias: 'Go to Previous Folding Range',
  874. precondition: CONTEXT_FOLDING_ENABLED,
  875. kbOpts: {
  876. kbExpr: EditorContextKeys.editorTextFocus,
  877. weight: 100 /* EditorContrib */
  878. }
  879. });
  880. }
  881. invoke(_foldingController, foldingModel, editor) {
  882. let selectedLines = this.getSelectedLines(editor);
  883. if (selectedLines.length > 0) {
  884. let startLineNumber = getPreviousFoldLine(selectedLines[0], foldingModel);
  885. if (startLineNumber !== null) {
  886. editor.setSelection({
  887. startLineNumber: startLineNumber,
  888. startColumn: 1,
  889. endLineNumber: startLineNumber,
  890. endColumn: 1
  891. });
  892. }
  893. }
  894. }
  895. }
  896. /** Action to go to the next fold of current line */
  897. class GotoNextFoldAction extends FoldingAction {
  898. constructor() {
  899. super({
  900. id: 'editor.gotoNextFold',
  901. label: nls.localize('gotoNextFold.label', "Go to Next Folding Range"),
  902. alias: 'Go to Next Folding Range',
  903. precondition: CONTEXT_FOLDING_ENABLED,
  904. kbOpts: {
  905. kbExpr: EditorContextKeys.editorTextFocus,
  906. weight: 100 /* EditorContrib */
  907. }
  908. });
  909. }
  910. invoke(_foldingController, foldingModel, editor) {
  911. let selectedLines = this.getSelectedLines(editor);
  912. if (selectedLines.length > 0) {
  913. let startLineNumber = getNextFoldLine(selectedLines[0], foldingModel);
  914. if (startLineNumber !== null) {
  915. editor.setSelection({
  916. startLineNumber: startLineNumber,
  917. startColumn: 1,
  918. endLineNumber: startLineNumber,
  919. endColumn: 1
  920. });
  921. }
  922. }
  923. }
  924. }
  925. registerEditorContribution(FoldingController.ID, FoldingController);
  926. registerEditorAction(UnfoldAction);
  927. registerEditorAction(UnFoldRecursivelyAction);
  928. registerEditorAction(FoldAction);
  929. registerEditorAction(FoldRecursivelyAction);
  930. registerEditorAction(FoldAllAction);
  931. registerEditorAction(UnfoldAllAction);
  932. registerEditorAction(FoldAllBlockCommentsAction);
  933. registerEditorAction(FoldAllRegionsAction);
  934. registerEditorAction(UnfoldAllRegionsAction);
  935. registerEditorAction(FoldAllRegionsExceptAction);
  936. registerEditorAction(UnfoldAllRegionsExceptAction);
  937. registerEditorAction(ToggleFoldAction);
  938. registerEditorAction(GotoParentFoldAction);
  939. registerEditorAction(GotoPreviousFoldAction);
  940. registerEditorAction(GotoNextFoldAction);
  941. for (let i = 1; i <= 7; i++) {
  942. registerInstantiatedEditorAction(new FoldLevelAction({
  943. id: FoldLevelAction.ID(i),
  944. label: nls.localize('foldLevelAction.label', "Fold Level {0}", i),
  945. alias: `Fold Level ${i}`,
  946. precondition: CONTEXT_FOLDING_ENABLED,
  947. kbOpts: {
  948. kbExpr: EditorContextKeys.editorTextFocus,
  949. primary: KeyChord(2048 /* CtrlCmd */ | 41 /* KeyK */, 2048 /* CtrlCmd */ | (21 /* Digit0 */ + i)),
  950. weight: 100 /* EditorContrib */
  951. }
  952. }));
  953. }
  954. export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hc: null }, nls.localize('foldBackgroundBackground', "Background color behind folded ranges. The color must not be opaque so as not to hide underlying decorations."), true);
  955. export const editorFoldForeground = registerColor('editorGutter.foldingControlForeground', { dark: iconForeground, light: iconForeground, hc: iconForeground }, nls.localize('editorGutter.foldingControlForeground', 'Color of the folding control in the editor gutter.'));
  956. registerThemingParticipant((theme, collector) => {
  957. const foldBackground = theme.getColor(foldBackgroundBackground);
  958. if (foldBackground) {
  959. collector.addRule(`.monaco-editor .folded-background { background-color: ${foldBackground}; }`);
  960. }
  961. const editorFoldColor = theme.getColor(editorFoldForeground);
  962. if (editorFoldColor) {
  963. collector.addRule(`
  964. .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingExpandedIcon)},
  965. .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingCollapsedIcon)} {
  966. color: ${editorFoldColor} !important;
  967. }
  968. `);
  969. }
  970. });