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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select to bracket #39066

Merged
merged 8 commits into from
Jan 23, 2018
70 changes: 67 additions & 3 deletions src/vs/editor/contrib/bracketMatching/bracketMatching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ 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 { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorBracketMatchBackground, editorBracketMatchBorder } from 'vs/editor/common/view/editorColorRegistry';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model';

class SelectBracketAction extends EditorAction {
class JumpToBracketAction extends EditorAction {
constructor() {
super({
id: 'editor.action.jumpToBracket',
Expand All @@ -45,6 +45,25 @@ class SelectBracketAction extends EditorAction {
}
}

class SelectToBracketAction extends EditorAction {
constructor() {
super({
id: 'editor.action.selectToBracket',
label: nls.localize('smartSelect.selectToBracket', "Select to Bracket"),
alias: 'Select to Bracket',
precondition: null
});
}

public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
let controller = BracketMatchingController.get(editor);
if (!controller) {
return;
}
controller.selectToBracket();
}
}

type Brackets = [Range, Range];

class BracketsData {
Expand Down Expand Up @@ -149,6 +168,50 @@ export class BracketMatchingController extends Disposable implements editorCommo
this._editor.revealRange(newSelections[0]);
}

public selectToBracket(): void {
const model = this._editor.getModel();
if (!model) {
return;
}
const selection = this._editor.getSelection();
if (!selection.isEmpty()) {
return;
}

const position = selection.getStartPosition();

let brackets = model.matchBracket(position);

let openBracket: Position = null;
let closeBracket: Position = null;

if (!brackets) {
const nextBracket = model.findNextBracket(position);
if (nextBracket && nextBracket.range) {
brackets = model.matchBracket(nextBracket.range.getStartPosition());
}
}

if (brackets) {
if (brackets[0].startLineNumber === brackets[1].startLineNumber) {
openBracket = brackets[1].startColumn < brackets[0].startColumn ?
brackets[1].getStartPosition() : brackets[0].getStartPosition();
closeBracket = brackets[1].startColumn < brackets[0].startColumn ?
brackets[0].getEndPosition() : brackets[1].getEndPosition();
} else {
openBracket = brackets[1].startLineNumber < brackets[0].startLineNumber ?
brackets[1].getStartPosition() : brackets[0].getStartPosition();
closeBracket = brackets[1].startLineNumber < brackets[0].startLineNumber ?
brackets[0].getEndPosition() : brackets[1].getEndPosition();
}
}

if (openBracket && closeBracket) {
this._editor.setSelection(new Range(openBracket.lineNumber, openBracket.column, closeBracket.lineNumber, closeBracket.column));
}
}


private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'bracket-match'
Expand Down Expand Up @@ -228,7 +291,8 @@ export class BracketMatchingController extends Disposable implements editorCommo
}

registerEditorContribution(BracketMatchingController);
registerEditorAction(SelectBracketAction);
registerEditorAction(SelectToBracketAction);
registerEditorAction(JumpToBracketAction);
registerThemingParticipant((theme, collector) => {
let bracketMatchBackground = theme.getColor(editorBracketMatchBackground);
if (bracketMatchBackground) {
Expand Down
46 changes: 46 additions & 0 deletions src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import * as assert from 'assert';
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
import { TextModel } from 'vs/editor/common/model/textModel';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
Expand Down Expand Up @@ -98,4 +99,49 @@ suite('bracket matching', () => {
model.dispose();
mode.dispose();
});

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

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


// start position in open brackets
editor.setPosition(new Position(1, 9));
bracketMatchingController.selectToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 20));
assert.deepEqual(editor.getSelection(), new Selection(1, 9, 1, 20));

// start position in close brackets
editor.setPosition(new Position(1, 20));
bracketMatchingController.selectToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 20));
assert.deepEqual(editor.getSelection(), new Selection(1, 9, 1, 20));

// start position between brackets
editor.setPosition(new Position(1, 16));
bracketMatchingController.selectToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 19));
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 19));

// start position outside brackets
editor.setPosition(new Position(1, 21));
bracketMatchingController.selectToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 25));
assert.deepEqual(editor.getSelection(), new Selection(1, 23, 1, 25));

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

bracketMatchingController.dispose();
});

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