123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { RunOnceScheduler } from '../../../base/common/async.js';
- import { Disposable } from '../../../base/common/lifecycle.js';
- import './bracketMatching.css';
- import { EditorAction, registerEditorAction, registerEditorContribution } from '../../browser/editorExtensions.js';
- import { Position } from '../../common/core/position.js';
- import { Range } from '../../common/core/range.js';
- import { Selection } from '../../common/core/selection.js';
- import { EditorContextKeys } from '../../common/editorContextKeys.js';
- import { OverviewRulerLane } from '../../common/model.js';
- import { ModelDecorationOptions } from '../../common/model/textModel.js';
- import { editorBracketMatchBackground, editorBracketMatchBorder } from '../../common/view/editorColorRegistry.js';
- import * as nls from '../../../nls.js';
- import { MenuId, MenuRegistry } from '../../../platform/actions/common/actions.js';
- import { registerColor } from '../../../platform/theme/common/colorRegistry.js';
- import { registerThemingParticipant, themeColorFromId } from '../../../platform/theme/common/themeService.js';
- const overviewRulerBracketMatchForeground = registerColor('editorOverviewRuler.bracketMatchForeground', { dark: '#A0A0A0', light: '#A0A0A0', hc: '#A0A0A0' }, nls.localize('overviewRulerBracketMatchForeground', 'Overview ruler marker color for matching brackets.'));
- class JumpToBracketAction extends EditorAction {
- constructor() {
- super({
- id: 'editor.action.jumpToBracket',
- label: nls.localize('smartSelect.jumpBracket', "Go to Bracket"),
- alias: 'Go to Bracket',
- precondition: undefined,
- kbOpts: {
- kbExpr: EditorContextKeys.editorTextFocus,
- primary: 2048 /* CtrlCmd */ | 1024 /* Shift */ | 88 /* Backslash */,
- weight: 100 /* EditorContrib */
- }
- });
- }
- run(accessor, editor) {
- let controller = BracketMatchingController.get(editor);
- if (!controller) {
- return;
- }
- controller.jumpToBracket();
- }
- }
- class SelectToBracketAction extends EditorAction {
- constructor() {
- super({
- id: 'editor.action.selectToBracket',
- label: nls.localize('smartSelect.selectToBracket', "Select to Bracket"),
- alias: 'Select to Bracket',
- precondition: undefined,
- description: {
- description: `Select to Bracket`,
- args: [{
- name: 'args',
- schema: {
- type: 'object',
- properties: {
- 'selectBrackets': {
- type: 'boolean',
- default: true
- }
- },
- }
- }]
- }
- });
- }
- run(accessor, editor, args) {
- const controller = BracketMatchingController.get(editor);
- if (!controller) {
- return;
- }
- let selectBrackets = true;
- if (args && args.selectBrackets === false) {
- selectBrackets = false;
- }
- controller.selectToBracket(selectBrackets);
- }
- }
- class BracketsData {
- constructor(position, brackets, options) {
- this.position = position;
- this.brackets = brackets;
- this.options = options;
- }
- }
- export class BracketMatchingController extends Disposable {
- constructor(editor) {
- super();
- this._editor = editor;
- this._lastBracketsData = [];
- this._lastVersionId = 0;
- this._decorations = [];
- this._updateBracketsSoon = this._register(new RunOnceScheduler(() => this._updateBrackets(), 50));
- this._matchBrackets = this._editor.getOption(63 /* matchBrackets */);
- this._updateBracketsSoon.schedule();
- this._register(editor.onDidChangeCursorPosition((e) => {
- if (this._matchBrackets === 'never') {
- // Early exit if nothing needs to be done!
- // Leave some form of early exit check here if you wish to continue being a cursor position change listener ;)
- return;
- }
- this._updateBracketsSoon.schedule();
- }));
- this._register(editor.onDidChangeModelContent((e) => {
- this._updateBracketsSoon.schedule();
- }));
- this._register(editor.onDidChangeModel((e) => {
- this._lastBracketsData = [];
- this._decorations = [];
- this._updateBracketsSoon.schedule();
- }));
- this._register(editor.onDidChangeModelLanguageConfiguration((e) => {
- this._lastBracketsData = [];
- this._updateBracketsSoon.schedule();
- }));
- this._register(editor.onDidChangeConfiguration((e) => {
- if (e.hasChanged(63 /* matchBrackets */)) {
- this._matchBrackets = this._editor.getOption(63 /* matchBrackets */);
- this._decorations = this._editor.deltaDecorations(this._decorations, []);
- this._lastBracketsData = [];
- this._lastVersionId = 0;
- this._updateBracketsSoon.schedule();
- }
- }));
- this._register(editor.onDidBlurEditorWidget(() => {
- this._updateBracketsSoon.schedule();
- }));
- this._register(editor.onDidFocusEditorWidget(() => {
- this._updateBracketsSoon.schedule();
- }));
- }
- static get(editor) {
- return editor.getContribution(BracketMatchingController.ID);
- }
- jumpToBracket() {
- if (!this._editor.hasModel()) {
- return;
- }
- const model = this._editor.getModel();
- const newSelections = this._editor.getSelections().map(selection => {
- const position = selection.getStartPosition();
- // find matching brackets if position is on a bracket
- const brackets = model.bracketPairs.matchBracket(position);
- let newCursorPosition = null;
- if (brackets) {
- if (brackets[0].containsPosition(position)) {
- newCursorPosition = brackets[1].getStartPosition();
- }
- else if (brackets[1].containsPosition(position)) {
- newCursorPosition = brackets[0].getStartPosition();
- }
- }
- else {
- // find the enclosing brackets if the position isn't on a matching bracket
- const enclosingBrackets = model.bracketPairs.findEnclosingBrackets(position);
- if (enclosingBrackets) {
- newCursorPosition = enclosingBrackets[0].getStartPosition();
- }
- else {
- // no enclosing brackets, try the very first next bracket
- const nextBracket = model.bracketPairs.findNextBracket(position);
- if (nextBracket && nextBracket.range) {
- newCursorPosition = nextBracket.range.getStartPosition();
- }
- }
- }
- if (newCursorPosition) {
- return new Selection(newCursorPosition.lineNumber, newCursorPosition.column, newCursorPosition.lineNumber, newCursorPosition.column);
- }
- return new Selection(position.lineNumber, position.column, position.lineNumber, position.column);
- });
- this._editor.setSelections(newSelections);
- this._editor.revealRange(newSelections[0]);
- }
- selectToBracket(selectBrackets) {
- if (!this._editor.hasModel()) {
- return;
- }
- const model = this._editor.getModel();
- const newSelections = [];
- this._editor.getSelections().forEach(selection => {
- const position = selection.getStartPosition();
- let brackets = model.bracketPairs.matchBracket(position);
- if (!brackets) {
- brackets = model.bracketPairs.findEnclosingBrackets(position);
- if (!brackets) {
- const nextBracket = model.bracketPairs.findNextBracket(position);
- if (nextBracket && nextBracket.range) {
- brackets = model.bracketPairs.matchBracket(nextBracket.range.getStartPosition());
- }
- }
- }
- let selectFrom = null;
- let selectTo = null;
- if (brackets) {
- brackets.sort(Range.compareRangesUsingStarts);
- const [open, close] = brackets;
- selectFrom = selectBrackets ? open.getStartPosition() : open.getEndPosition();
- selectTo = selectBrackets ? close.getEndPosition() : close.getStartPosition();
- if (close.containsPosition(position)) {
- // select backwards if the cursor was on the closing bracket
- const tmp = selectFrom;
- selectFrom = selectTo;
- selectTo = tmp;
- }
- }
- if (selectFrom && selectTo) {
- newSelections.push(new Selection(selectFrom.lineNumber, selectFrom.column, selectTo.lineNumber, selectTo.column));
- }
- });
- if (newSelections.length > 0) {
- this._editor.setSelections(newSelections);
- this._editor.revealRange(newSelections[0]);
- }
- }
- _updateBrackets() {
- if (this._matchBrackets === 'never') {
- return;
- }
- this._recomputeBrackets();
- let newDecorations = [], newDecorationsLen = 0;
- for (const bracketData of this._lastBracketsData) {
- let brackets = bracketData.brackets;
- if (brackets) {
- newDecorations[newDecorationsLen++] = { range: brackets[0], options: bracketData.options };
- newDecorations[newDecorationsLen++] = { range: brackets[1], options: bracketData.options };
- }
- }
- this._decorations = this._editor.deltaDecorations(this._decorations, newDecorations);
- }
- _recomputeBrackets() {
- if (!this._editor.hasModel() || !this._editor.hasWidgetFocus()) {
- // no model or no focus => no brackets!
- this._lastBracketsData = [];
- this._lastVersionId = 0;
- return;
- }
- const selections = this._editor.getSelections();
- if (selections.length > 100) {
- // no bracket matching for high numbers of selections
- this._lastBracketsData = [];
- this._lastVersionId = 0;
- return;
- }
- const model = this._editor.getModel();
- const versionId = model.getVersionId();
- let previousData = [];
- if (this._lastVersionId === versionId) {
- // use the previous data only if the model is at the same version id
- previousData = this._lastBracketsData;
- }
- let positions = [], positionsLen = 0;
- for (let i = 0, len = selections.length; i < len; i++) {
- let selection = selections[i];
- if (selection.isEmpty()) {
- // will bracket match a cursor only if the selection is collapsed
- positions[positionsLen++] = selection.getStartPosition();
- }
- }
- // sort positions for `previousData` cache hits
- if (positions.length > 1) {
- positions.sort(Position.compare);
- }
- let newData = [], newDataLen = 0;
- let previousIndex = 0, previousLen = previousData.length;
- for (let i = 0, len = positions.length; i < len; i++) {
- let position = positions[i];
- while (previousIndex < previousLen && previousData[previousIndex].position.isBefore(position)) {
- previousIndex++;
- }
- if (previousIndex < previousLen && previousData[previousIndex].position.equals(position)) {
- newData[newDataLen++] = previousData[previousIndex];
- }
- else {
- let brackets = model.bracketPairs.matchBracket(position);
- let options = BracketMatchingController._DECORATION_OPTIONS_WITH_OVERVIEW_RULER;
- if (!brackets && this._matchBrackets === 'always') {
- brackets = model.bracketPairs.findEnclosingBrackets(position, 20 /* give at most 20ms to compute */);
- options = BracketMatchingController._DECORATION_OPTIONS_WITHOUT_OVERVIEW_RULER;
- }
- newData[newDataLen++] = new BracketsData(position, brackets, options);
- }
- }
- this._lastBracketsData = newData;
- this._lastVersionId = versionId;
- }
- }
- BracketMatchingController.ID = 'editor.contrib.bracketMatchingController';
- BracketMatchingController._DECORATION_OPTIONS_WITH_OVERVIEW_RULER = ModelDecorationOptions.register({
- description: 'bracket-match-overview',
- stickiness: 1 /* NeverGrowsWhenTypingAtEdges */,
- className: 'bracket-match',
- overviewRuler: {
- color: themeColorFromId(overviewRulerBracketMatchForeground),
- position: OverviewRulerLane.Center
- }
- });
- BracketMatchingController._DECORATION_OPTIONS_WITHOUT_OVERVIEW_RULER = ModelDecorationOptions.register({
- description: 'bracket-match-no-overview',
- stickiness: 1 /* NeverGrowsWhenTypingAtEdges */,
- className: 'bracket-match'
- });
- registerEditorContribution(BracketMatchingController.ID, BracketMatchingController);
- registerEditorAction(SelectToBracketAction);
- registerEditorAction(JumpToBracketAction);
- registerThemingParticipant((theme, collector) => {
- const bracketMatchBackground = theme.getColor(editorBracketMatchBackground);
- if (bracketMatchBackground) {
- collector.addRule(`.monaco-editor .bracket-match { background-color: ${bracketMatchBackground}; }`);
- }
- const bracketMatchBorder = theme.getColor(editorBracketMatchBorder);
- if (bracketMatchBorder) {
- collector.addRule(`.monaco-editor .bracket-match { border: 1px solid ${bracketMatchBorder}; }`);
- }
- });
- // Go to menu
- MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
- group: '5_infile_nav',
- command: {
- id: 'editor.action.jumpToBracket',
- title: nls.localize({ key: 'miGoToBracket', comment: ['&& denotes a mnemonic'] }, "Go to &&Bracket")
- },
- order: 2
- });
|