Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .bracket-match {
box-sizing: border-box;
}

View File

@@ -0,0 +1,371 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./bracketMatching';
import * as nls from 'vs/nls';
import { RunOnceScheduler } from 'vs/base/common/async';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { editorBracketMatchBackground, editorBracketMatchBorder } from 'vs/editor/common/view/editorColorRegistry';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
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: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKSLASH,
weight: KeybindingWeight.EditorContrib
}
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
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
}
},
}
}]
}
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
const controller = BracketMatchingController.get(editor);
if (!controller) {
return;
}
let selectBrackets = true;
if (args && args.selectBrackets === false) {
selectBrackets = false;
}
controller.selectToBracket(selectBrackets);
}
}
type Brackets = [Range, Range];
class BracketsData {
public readonly position: Position;
public readonly brackets: Brackets | null;
public readonly options: ModelDecorationOptions;
constructor(position: Position, brackets: Brackets | null, options: ModelDecorationOptions) {
this.position = position;
this.brackets = brackets;
this.options = options;
}
}
export class BracketMatchingController extends Disposable implements IEditorContribution {
public static readonly ID = 'editor.contrib.bracketMatchingController';
public static get(editor: ICodeEditor): BracketMatchingController {
return editor.getContribution<BracketMatchingController>(BracketMatchingController.ID);
}
private readonly _editor: ICodeEditor;
private _lastBracketsData: BracketsData[];
private _lastVersionId: number;
private _decorations: string[];
private readonly _updateBracketsSoon: RunOnceScheduler;
private _matchBrackets: 'never' | 'near' | 'always';
constructor(
editor: ICodeEditor
) {
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(EditorOption.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(EditorOption.matchBrackets)) {
this._matchBrackets = this._editor.getOption(EditorOption.matchBrackets);
this._decorations = this._editor.deltaDecorations(this._decorations, []);
this._lastBracketsData = [];
this._lastVersionId = 0;
this._updateBracketsSoon.schedule();
}
}));
}
public jumpToBracket(): void {
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.matchBracket(position);
let newCursorPosition: Position | null = 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.findEnclosingBrackets(position);
if (enclosingBrackets) {
newCursorPosition = enclosingBrackets[0].getStartPosition();
} else {
// no enclosing brackets, try the very first next bracket
const nextBracket = model.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]);
}
public selectToBracket(selectBrackets: boolean): void {
if (!this._editor.hasModel()) {
return;
}
const model = this._editor.getModel();
const newSelections: Selection[] = [];
this._editor.getSelections().forEach(selection => {
const position = selection.getStartPosition();
let brackets = model.matchBracket(position);
if (!brackets) {
brackets = model.findEnclosingBrackets(position);
if (!brackets) {
const nextBracket = model.findNextBracket(position);
if (nextBracket && nextBracket.range) {
brackets = model.matchBracket(nextBracket.range.getStartPosition());
}
}
}
let selectFrom: Position | null = null;
let selectTo: Position | null = null;
if (brackets) {
brackets.sort(Range.compareRangesUsingStarts);
const [open, close] = brackets;
selectFrom = selectBrackets ? open.getStartPosition() : open.getEndPosition();
selectTo = selectBrackets ? close.getEndPosition() : close.getStartPosition();
}
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]);
}
}
private static readonly _DECORATION_OPTIONS_WITH_OVERVIEW_RULER = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'bracket-match',
overviewRuler: {
color: themeColorFromId(overviewRulerBracketMatchForeground),
position: OverviewRulerLane.Center
}
});
private static readonly _DECORATION_OPTIONS_WITHOUT_OVERVIEW_RULER = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'bracket-match'
});
private _updateBrackets(): void {
if (this._matchBrackets === 'never') {
return;
}
this._recomputeBrackets();
let newDecorations: IModelDeltaDecoration[] = [], 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);
}
private _recomputeBrackets(): void {
if (!this._editor.hasModel()) {
// no model => 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: BracketsData[] = [];
if (this._lastVersionId === versionId) {
// use the previous data only if the model is at the same version id
previousData = this._lastBracketsData;
}
let positions: Position[] = [], 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: BracketsData[] = [], 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.matchBracket(position);
let options = BracketMatchingController._DECORATION_OPTIONS_WITH_OVERVIEW_RULER;
if (!brackets && this._matchBrackets === 'always') {
brackets = model.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;
}
}
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
});

View File

@@ -0,0 +1,248 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { BracketMatchingController } from 'vs/editor/contrib/bracketMatching/bracketMatching';
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
suite('bracket matching', () => {
class BracketMode extends MockMode {
private static readonly _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 = createTextModel('var x = (3 + (5-7)) + ((5+3)+5);', undefined, mode.getLanguageIdentifier());
withTestCodeEditor(null, { model: model }, (editor) => {
let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController);
// start on closing bracket
editor.setPosition(new Position(1, 20));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 9));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 19));
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));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 23));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getPosition(), new Position(1, 31));
bracketMatchingController.dispose();
});
model.dispose();
mode.dispose();
});
test('Jump to next bracket', () => {
let mode = new BracketMode();
let model = createTextModel('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier());
withTestCodeEditor(null, { model: model }, (editor) => {
let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, 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();
});
test('Select to next bracket', () => {
let mode = new BracketMode();
let model = createTextModel('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier());
withTestCodeEditor(null, { model: model }, (editor) => {
let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController);
// start position in open brackets
editor.setPosition(new Position(1, 9));
bracketMatchingController.selectToBracket(true);
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(true);
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(true);
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(true);
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(true);
assert.deepEqual(editor.getPosition(), new Position(1, 26));
assert.deepEqual(editor.getSelection(), new Selection(1, 26, 1, 26));
bracketMatchingController.dispose();
});
model.dispose();
mode.dispose();
});
test('issue #1772: jump to enclosing brackets', () => {
const text = [
'const x = {',
' something: [0, 1, 2],',
' another: true,',
' somethingmore: [0, 2, 4]',
'};',
].join('\n');
const mode = new BracketMode();
const model = createTextModel(text, undefined, mode.getLanguageIdentifier());
withTestCodeEditor(null, { model: model }, (editor) => {
const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController);
editor.setPosition(new Position(3, 5));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getSelection(), new Selection(5, 1, 5, 1));
bracketMatchingController.dispose();
});
model.dispose();
mode.dispose();
});
test('issue #43371: argument to not select brackets', () => {
const text = [
'const x = {',
' something: [0, 1, 2],',
' another: true,',
' somethingmore: [0, 2, 4]',
'};',
].join('\n');
const mode = new BracketMode();
const model = createTextModel(text, undefined, mode.getLanguageIdentifier());
withTestCodeEditor(null, { model: model }, (editor) => {
const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController);
editor.setPosition(new Position(3, 5));
bracketMatchingController.selectToBracket(false);
assert.deepEqual(editor.getSelection(), new Selection(1, 12, 5, 1));
bracketMatchingController.dispose();
});
model.dispose();
mode.dispose();
});
test('issue #45369: Select to Bracket with multicursor', () => {
let mode = new BracketMode();
let model = createTextModel('{ } { } { }', undefined, mode.getLanguageIdentifier());
withTestCodeEditor(null, { model: model }, (editor) => {
let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController);
// cursors inside brackets become selections of the entire bracket contents
editor.setSelections([
new Selection(1, 3, 1, 3),
new Selection(1, 10, 1, 10),
new Selection(1, 17, 1, 17)
]);
bracketMatchingController.selectToBracket(true);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(1, 8, 1, 13),
new Selection(1, 16, 1, 19)
]);
// cursors to the left of bracket pairs become selections of the entire pair
editor.setSelections([
new Selection(1, 1, 1, 1),
new Selection(1, 6, 1, 6),
new Selection(1, 14, 1, 14)
]);
bracketMatchingController.selectToBracket(true);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(1, 8, 1, 13),
new Selection(1, 16, 1, 19)
]);
// cursors just right of a bracket pair become selections of the entire pair
editor.setSelections([
new Selection(1, 5, 1, 5),
new Selection(1, 13, 1, 13),
new Selection(1, 19, 1, 19)
]);
bracketMatchingController.selectToBracket(true);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(1, 8, 1, 13),
new Selection(1, 16, 1, 19)
]);
bracketMatchingController.dispose();
});
model.dispose();
mode.dispose();
});
});