Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved "Go To Bracket" to jump to next bracket. #32110

Merged
merged 11 commits into from
Aug 9, 2017
28 changes: 15 additions & 13 deletions src/vs/editor/common/editorCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -875,19 +875,21 @@ export interface ITokenizedModel extends ITextModel {
*/
findMatchingBracketUp(bracket: string, position: IPosition): Range;

// /**
// * Find the first bracket in the model before `position`.
// * @param position The position at which to start the search.
// * @return The info for the first bracket before `position`, or null if there are no more brackets before `positions`.
// */
// findPrevBracket(position:IPosition): IFoundBracket;

// /**
// * Find the first bracket in the model after `position`.
// * @param position The position at which to start the search.
// * @return The info for the first bracket after `position`, or null if there are no more brackets after `positions`.
// */
// findNextBracket(position:IPosition): IFoundBracket;
/**
* Find the first bracket in the model before `position`.
* @param position The position at which to start the search.
* @return The info for the first bracket before `position`, or null if there are no more brackets before `positions`.
* @internal
*/
findPrevBracket(position:IPosition): IFoundBracket;

/**
* Find the first bracket in the model after `position`.
* @param position The position at which to start the search.
* @return The info for the first bracket after `position`, or null if there are no more brackets after `positions`.
* @internal
*/
findNextBracket(position:IPosition): IFoundBracket;

/**
* Given a `position`, if the position is on top or near a bracket,
Expand Down
46 changes: 26 additions & 20 deletions src/vs/editor/contrib/bracketMatching/common/bracketMatching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable } from 'vs/base/common/lifecycle';
import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
import { RunOnceScheduler } from 'vs/base/common/async';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { editorAction, commonEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions';
Expand Down Expand Up @@ -113,28 +114,33 @@ export class BracketMatchingController extends Disposable implements editorCommo
return;
}

const selection = this._editor.getSelection();
if (!selection.isEmpty()) {
return;
}
let newSelections = this._editor.getSelections().map(selection => {
const position = selection.getStartPosition();

const position = selection.getStartPosition();
const brackets = model.matchBracket(position);
if (!brackets) {
return;
}
// find matching brackets if position is on a bracket
const brackets = model.matchBracket(position);
let newCursorPosition: Position = 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 next bracket if the position isn't on a matching bracket
const nextBracket = model.findNextBracket(position);
if (nextBracket && nextBracket.range) {
newCursorPosition = nextBracket.range.getStartPosition();
}
}

let resultingPosition: Position = null;
if (brackets[0].containsPosition(position)) {
resultingPosition = brackets[1].getStartPosition();
} else if (brackets[1].containsPosition(position)) {
resultingPosition = brackets[0].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);
});

if (resultingPosition) {
this._editor.setPosition(resultingPosition);
this._editor.revealPosition(resultingPosition);
}
this._editor.setSelections(newSelections);
}

private static _DECORATION_OPTIONS = ModelDecorationOptions.register({
Expand Down Expand Up @@ -224,4 +230,4 @@ registerThemingParticipant((theme, collector) => {
if (bracketMatchBorder) {
collector.addRule(`.monaco-editor .bracket-match { border: 1px solid ${bracketMatchBorder}; }`);
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,30 @@ import { LanguageIdentifier } from 'vs/editor/common/modes';
import { BracketMatchingController } from 'vs/editor/contrib/bracketMatching/common/bracketMatching';

suite('bracket matching', () => {
test('issue #183: jump to matching bracket position', () => {
class BracketMode extends MockMode {

private static _id = new LanguageIdentifier('bracketMode', 3);

constructor() {
super(BracketMode._id);
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
brackets: [
['{', '}'],
['[', ']'],
['(', ')'],
]
}));
}
class BracketMode extends MockMode {

private static _id = new LanguageIdentifier('bracketMode', 3);

constructor() {
super(BracketMode._id);
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
brackets: [
['{', '}'],
['[', ']'],
['(', ')'],
]
}));
}
}

test('issue #183: jump to matching bracket position', () => {
let mode = new BracketMode();
let model = Model.createFromString('var x = (3 + (5-7)) + ((5+3)+5);', undefined, mode.getLanguageIdentifier());

withMockCodeEditor(null, { model: model }, (editor, cursor) => {
let bracketMatchingController = editor.registerAndInstantiateContribution<BracketMatchingController>(BracketMatchingController);

// start on closing bracket
editor.setPosition(new Position(1, 20));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 9));
Expand All @@ -45,6 +46,7 @@ suite('bracket matching', () => {
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 9));

// start on opening bracket
editor.setPosition(new Position(1, 23));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 31));
Expand All @@ -59,4 +61,41 @@ suite('bracket matching', () => {
model.dispose();
mode.dispose();
});

test('Jump to next bracket', () => {
let mode = new BracketMode();
let model = Model.createFromString('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier());

withMockCodeEditor(null, { model: model }, (editor, cursor) => {
let bracketMatchingController = editor.registerAndInstantiateContribution<BracketMatchingController>(BracketMatchingController);

// start position between brackets
editor.setPosition(new Position(1, 16));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 18));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 14));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 18));

// skip brackets in comments
editor.setPosition(new Position(1, 21));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 23));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 24));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 23));

// do not break if no brackets are available
editor.setPosition(new Position(1, 26));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 26));

bracketMatchingController.dispose();
});

model.dispose();
mode.dispose();
});
});