mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from master
This commit is contained in:
49
src/vs/editor/contrib/smartSelect/bracketSelections.ts
Normal file
49
src/vs/editor/contrib/smartSelect/bracketSelections.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SelectionRangeProvider } from 'vs/editor/common/modes';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
|
||||
export class BracketSelectionRangeProvider implements SelectionRangeProvider {
|
||||
|
||||
provideSelectionRanges(model: ITextModel, position: Position): Range[] {
|
||||
|
||||
let result: Range[] = [];
|
||||
let last: Range | undefined;
|
||||
let pos = position;
|
||||
let i = 0;
|
||||
for (; i < 1750; i++) {
|
||||
let bracket = model.findNextBracket(pos);
|
||||
if (!bracket) {
|
||||
// no more brackets
|
||||
break;
|
||||
} else if (bracket.isOpen) {
|
||||
// skip past the closing bracket
|
||||
let matching = model.matchBracket(bracket.range.getEndPosition());
|
||||
if (!matching) {
|
||||
break;
|
||||
}
|
||||
pos = model.getPositionAt(model.getOffsetAt(matching[1].getEndPosition()) + 1);
|
||||
|
||||
} else {
|
||||
// find matching, opening bracket
|
||||
let range = model.findMatchingBracketUp(bracket.close, bracket.range.getStartPosition());
|
||||
if (!range) {
|
||||
break;
|
||||
}
|
||||
if (!last || range.getStartPosition().isBefore(last.getStartPosition())) {
|
||||
const inner = Range.fromPositions(range.getStartPosition(), bracket.range.getEndPosition());
|
||||
const outer = Range.fromPositions(range.getEndPosition(), bracket.range.getStartPosition());
|
||||
result.push(inner, outer);
|
||||
last = outer;
|
||||
}
|
||||
pos = model.getPositionAt(model.getOffsetAt(bracket.range.getEndPosition()) + 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -2,166 +2,152 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, IActionOptions, registerEditorAction, registerEditorContribution, ServicesAccessor, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { registerEditorAction, ServicesAccessor, IActionOptions, EditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { TokenSelectionSupport, ILogicalSelectionEntry } from './tokenSelectionSupport';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as nls from 'vs/nls';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { TokenTreeSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/tokenTree';
|
||||
import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections';
|
||||
import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections';
|
||||
|
||||
// --- selection state machine
|
||||
class SelectionRanges {
|
||||
|
||||
class State {
|
||||
constructor(
|
||||
readonly index: number,
|
||||
readonly ranges: Range[]
|
||||
) { }
|
||||
|
||||
public editor: ICodeEditor;
|
||||
public next: State;
|
||||
public previous: State;
|
||||
public selection: Range;
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
this.editor = editor;
|
||||
this.next = null;
|
||||
this.previous = null;
|
||||
this.selection = editor.getSelection();
|
||||
mov(fwd: boolean): SelectionRanges {
|
||||
let index = this.index + (fwd ? 1 : -1);
|
||||
if (index < 0 || index >= this.ranges.length) {
|
||||
return this;
|
||||
}
|
||||
const res = new SelectionRanges(index, this.ranges);
|
||||
if (res.ranges[index].equalsRange(this.ranges[this.index])) {
|
||||
// next range equals this range, retry with next-next
|
||||
return res.mov(fwd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// -- action implementation
|
||||
|
||||
class SmartSelectController implements IEditorContribution {
|
||||
|
||||
private static readonly ID = 'editor.contrib.smartSelectController';
|
||||
private static readonly _id = 'editor.contrib.smartSelectController';
|
||||
|
||||
public static get(editor: ICodeEditor): SmartSelectController {
|
||||
return editor.getContribution<SmartSelectController>(SmartSelectController.ID);
|
||||
static get(editor: ICodeEditor): SmartSelectController {
|
||||
return editor.getContribution<SmartSelectController>(SmartSelectController._id);
|
||||
}
|
||||
|
||||
private _tokenSelectionSupport: TokenSelectionSupport;
|
||||
private _state: State;
|
||||
private _ignoreSelection: boolean;
|
||||
private readonly _editor: ICodeEditor;
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
this._tokenSelectionSupport = instantiationService.createInstance(TokenSelectionSupport);
|
||||
this._state = null;
|
||||
this._ignoreSelection = false;
|
||||
private _state?: SelectionRanges;
|
||||
private _selectionListener?: IDisposable;
|
||||
private _ignoreSelection: boolean = false;
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
this._editor = editor;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
dispose(this._selectionListener);
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return SmartSelectController.ID;
|
||||
getId(): string {
|
||||
return SmartSelectController._id;
|
||||
}
|
||||
|
||||
public run(forward: boolean): TPromise<void> {
|
||||
|
||||
const selection = this.editor.getSelection();
|
||||
const model = this.editor.getModel();
|
||||
|
||||
// forget about current state
|
||||
if (this._state) {
|
||||
if (this._state.editor !== this.editor) {
|
||||
this._state = null;
|
||||
}
|
||||
run(forward: boolean): Promise<void> | void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let promise: TPromise<void> = TPromise.as(null);
|
||||
if (!this._state) {
|
||||
promise = this._tokenSelectionSupport.getRangesToPosition(model.uri, selection.getStartPosition()).then((elements: ILogicalSelectionEntry[]) => {
|
||||
const selection = this._editor.getSelection();
|
||||
const model = this._editor.getModel();
|
||||
|
||||
if (arrays.isFalsyOrEmpty(elements)) {
|
||||
if (!modes.SelectionRangeRegistry.has(model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let promise: Promise<void> = Promise.resolve(void 0);
|
||||
|
||||
if (!this._state) {
|
||||
promise = provideSelectionRanges(model, selection.getStartPosition(), CancellationToken.None).then(ranges => {
|
||||
if (!arrays.isNonEmptyArray(ranges)) {
|
||||
// invalid result
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel() || !this._editor.getSelection().equalsSelection(selection)) {
|
||||
// invalid editor state
|
||||
return;
|
||||
}
|
||||
|
||||
let lastState: State;
|
||||
elements.filter((element) => {
|
||||
ranges = ranges.filter(range => {
|
||||
// filter ranges inside the selection
|
||||
const selection = this.editor.getSelection();
|
||||
const range = new Range(element.range.startLineNumber, element.range.startColumn, element.range.endLineNumber, element.range.endColumn);
|
||||
return range.containsPosition(selection.getStartPosition()) && range.containsPosition(selection.getEndPosition());
|
||||
|
||||
}).forEach((element) => {
|
||||
// create ranges
|
||||
const range = element.range;
|
||||
const state = new State(this.editor);
|
||||
state.selection = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
|
||||
if (lastState) {
|
||||
state.next = lastState;
|
||||
lastState.previous = state;
|
||||
}
|
||||
lastState = state;
|
||||
});
|
||||
|
||||
// insert current selection
|
||||
const editorState = new State(this.editor);
|
||||
editorState.next = lastState;
|
||||
if (lastState) {
|
||||
lastState.previous = editorState;
|
||||
}
|
||||
this._state = editorState;
|
||||
// prepend current selection
|
||||
ranges.unshift(selection);
|
||||
|
||||
this._state = new SelectionRanges(0, ranges);
|
||||
|
||||
// listen to caret move and forget about state
|
||||
const unhook = this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
|
||||
if (this._ignoreSelection) {
|
||||
return;
|
||||
dispose(this._selectionListener);
|
||||
this._selectionListener = this._editor.onDidChangeCursorPosition(() => {
|
||||
if (!this._ignoreSelection) {
|
||||
dispose(this._selectionListener);
|
||||
this._state = undefined;
|
||||
}
|
||||
this._state = null;
|
||||
unhook.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
|
||||
if (!this._state) {
|
||||
// no state
|
||||
return;
|
||||
}
|
||||
|
||||
this._state = forward ? this._state.next : this._state.previous;
|
||||
if (!this._state) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._state = this._state.mov(forward);
|
||||
const selection = this._state.ranges[this._state.index];
|
||||
this._ignoreSelection = true;
|
||||
try {
|
||||
this.editor.setSelection(this._state.selection);
|
||||
this._editor.setSelection(selection);
|
||||
} finally {
|
||||
this._ignoreSelection = false;
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractSmartSelect extends EditorAction {
|
||||
|
||||
private _forward: boolean;
|
||||
private readonly _forward: boolean;
|
||||
|
||||
constructor(forward: boolean, opts: IActionOptions) {
|
||||
super(opts);
|
||||
this._forward = forward;
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): TPromise<void> {
|
||||
async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
let controller = SmartSelectController.get(editor);
|
||||
if (controller) {
|
||||
return controller.run(this._forward);
|
||||
await controller.run(this._forward);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,3 +200,67 @@ class ShrinkSelectionAction extends AbstractSmartSelect {
|
||||
registerEditorContribution(SmartSelectController);
|
||||
registerEditorAction(GrowSelectionAction);
|
||||
registerEditorAction(ShrinkSelectionAction);
|
||||
|
||||
modes.SelectionRangeRegistry.register('*', new WordSelectionRangeProvider());
|
||||
modes.SelectionRangeRegistry.register('*', new BracketSelectionRangeProvider());
|
||||
modes.SelectionRangeRegistry.register('*', new TokenTreeSelectionRangeProvider());
|
||||
|
||||
export function provideSelectionRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<Range[] | undefined | null> {
|
||||
|
||||
const provider = modes.SelectionRangeRegistry.orderedGroups(model);
|
||||
|
||||
interface RankedRange {
|
||||
rank: number;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
let work: Promise<any>[] = [];
|
||||
let ranges: RankedRange[] = [];
|
||||
let rank = 0;
|
||||
|
||||
for (const group of provider) {
|
||||
rank += 1;
|
||||
for (const prov of group) {
|
||||
work.push(Promise.resolve(prov.provideSelectionRanges(model, position, token)).then(res => {
|
||||
if (arrays.isNonEmptyArray(res)) {
|
||||
for (const range of res) {
|
||||
if (Range.isIRange(range) && Range.containsPosition(range, position)) {
|
||||
ranges.push({ range: Range.lift(range), rank });
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(work).then(() => {
|
||||
ranges.sort((a, b) => {
|
||||
if (Position.isBefore(a.range.getStartPosition(), b.range.getStartPosition())) {
|
||||
return 1;
|
||||
} else if (Position.isBefore(b.range.getStartPosition(), a.range.getStartPosition())) {
|
||||
return -1;
|
||||
} else if (Position.isBefore(a.range.getEndPosition(), b.range.getEndPosition())) {
|
||||
return -1;
|
||||
} else if (Position.isBefore(b.range.getEndPosition(), a.range.getEndPosition())) {
|
||||
return 1;
|
||||
} else {
|
||||
return b.rank - a.rank;
|
||||
}
|
||||
});
|
||||
|
||||
// ranges.sort((a, b) => Range.compareRangesUsingStarts(b.range, a.range));
|
||||
let result: Range[] = [];
|
||||
let last: Range | undefined;
|
||||
for (const { range } of ranges) {
|
||||
if (!last || Range.containsRange(range, last)) {
|
||||
result.push(range);
|
||||
last = range;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
registerDefaultLanguageCommand('_executeSelectionRangeProvider', function (model, position) {
|
||||
return provideSelectionRanges(model, position, CancellationToken.None);
|
||||
});
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { IndentAction } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { TokenSelectionSupport } from 'vs/editor/contrib/smartSelect/tokenSelectionSupport';
|
||||
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
|
||||
import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { isLinux, isMacintosh } from 'vs/base/common/platform';
|
||||
import { TokenTreeSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/tokenTree';
|
||||
import { MarkerService } from 'vs/platform/markers/common/markerService';
|
||||
|
||||
class MockJSMode extends MockMode {
|
||||
|
||||
@@ -30,47 +32,19 @@ class MockJSMode extends MockMode {
|
||||
['[', ']']
|
||||
],
|
||||
|
||||
onEnterRules: [
|
||||
{
|
||||
// e.g. /** | */
|
||||
beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
|
||||
afterText: /^\s*\*\/$/,
|
||||
action: { indentAction: IndentAction.IndentOutdent, appendText: ' * ' }
|
||||
},
|
||||
{
|
||||
// e.g. /** ...|
|
||||
beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
|
||||
action: { indentAction: IndentAction.None, appendText: ' * ' }
|
||||
},
|
||||
{
|
||||
// e.g. * ...|
|
||||
beforeText: /^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/,
|
||||
action: { indentAction: IndentAction.None, appendText: '* ' }
|
||||
},
|
||||
{
|
||||
// e.g. */|
|
||||
beforeText: /^(\t|(\ \ ))*\ \*\/\s*$/,
|
||||
action: { indentAction: IndentAction.None, removeText: 1 }
|
||||
},
|
||||
{
|
||||
// e.g. *-----*/|
|
||||
beforeText: /^(\t|(\ \ ))*\ \*[^/]*\*\/\s*$/,
|
||||
action: { indentAction: IndentAction.None, removeText: 1 }
|
||||
}
|
||||
]
|
||||
onEnterRules: javascriptOnEnterRules
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
suite('TokenSelectionSupport', () => {
|
||||
|
||||
let modelService: ModelServiceImpl = null;
|
||||
let tokenSelectionSupport: TokenSelectionSupport;
|
||||
let mode: MockJSMode = null;
|
||||
let modelService: ModelServiceImpl;
|
||||
let mode: MockJSMode;
|
||||
|
||||
setup(() => {
|
||||
modelService = new ModelServiceImpl(null, new TestConfigurationService());
|
||||
tokenSelectionSupport = new TokenSelectionSupport(modelService);
|
||||
const configurationService = new TestConfigurationService();
|
||||
modelService = new ModelServiceImpl(new MarkerService(), configurationService, new TestTextResourcePropertiesService(configurationService));
|
||||
mode = new MockJSMode();
|
||||
});
|
||||
|
||||
@@ -81,12 +55,13 @@ suite('TokenSelectionSupport', () => {
|
||||
|
||||
function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[]): void {
|
||||
let uri = URI.file('test.js');
|
||||
modelService.createModel(text.join('\n'), mode, uri);
|
||||
let model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(mode.getLanguageIdentifier()), uri);
|
||||
|
||||
let actual = tokenSelectionSupport.getRangesToPositionSync(uri, new Position(lineNumber, column));
|
||||
let actual = new TokenTreeSelectionRangeProvider().provideSelectionRanges(model, new Position(lineNumber, column));
|
||||
|
||||
let actualStr = actual.map(r => new Range(r.range.startLineNumber, r.range.startColumn, r.range.endLineNumber, r.range.endColumn).toString());
|
||||
let desiredStr = ranges.map(r => String(r));
|
||||
|
||||
let actualStr = actual.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString());
|
||||
let desiredStr = ranges.reverse().map(r => String(r));
|
||||
|
||||
assert.deepEqual(actualStr, desiredStr);
|
||||
|
||||
@@ -95,7 +70,7 @@ suite('TokenSelectionSupport', () => {
|
||||
|
||||
test('getRangesToPosition #1', () => {
|
||||
|
||||
assertGetRangesToPosition([
|
||||
return assertGetRangesToPosition([
|
||||
'function a(bar, foo){',
|
||||
'\tif (bar) {',
|
||||
'\t\treturn (bar + (2 * foo))',
|
||||
@@ -115,4 +90,99 @@ suite('TokenSelectionSupport', () => {
|
||||
// new Range(3, 19, 3, 20)
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #56886. Skip empty lines correctly.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
'function a(bar, foo){',
|
||||
'\tif (bar) {',
|
||||
'',
|
||||
'\t}',
|
||||
'}'
|
||||
], 3, 1, [
|
||||
new Range(1, 1, 5, 2),
|
||||
new Range(1, 21, 5, 2),
|
||||
new Range(2, 1, 4, 3),
|
||||
new Range(2, 11, 4, 3)
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #56886. Do not skip lines with only whitespaces.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
'function a(bar, foo){',
|
||||
'\tif (bar) {',
|
||||
' ',
|
||||
'\t}',
|
||||
'}'
|
||||
], 3, 1, [
|
||||
new Range(1, 1, 5, 2),
|
||||
new Range(1, 21, 5, 2),
|
||||
new Range(2, 1, 4, 3),
|
||||
new Range(2, 11, 4, 3),
|
||||
new Range(3, 1, 4, 2),
|
||||
new Range(3, 1, 3, 2)
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #40658. Cursor at first position inside brackets should select line inside.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
' [ ]',
|
||||
' { } ',
|
||||
'( ) '
|
||||
], 2, 3, [
|
||||
new Range(1, 1, 3, 5),
|
||||
new Range(2, 1, 2, 6),
|
||||
new Range(2, 2, 2, 5),
|
||||
new Range(2, 3, 2, 4)
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #40658. Cursor in empty brackets should reveal brackets first.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
' [] ',
|
||||
' { } ',
|
||||
' ( ) '
|
||||
], 1, 3, [
|
||||
new Range(1, 1, 3, 7),
|
||||
new Range(1, 1, 1, 5),
|
||||
new Range(1, 2, 1, 4)
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #40658. Tokens before bracket will be revealed first.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
' [] ',
|
||||
' { } ',
|
||||
'selectthis( ) '
|
||||
], 3, 11, [
|
||||
new Range(1, 1, 3, 15),
|
||||
new Range(3, 1, 3, 15),
|
||||
new Range(3, 1, 3, 11)
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
class TestTextResourcePropertiesService implements ITextResourcePropertiesService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
) {
|
||||
}
|
||||
|
||||
getEOL(resource: URI): string {
|
||||
const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files');
|
||||
if (filesConfiguration && filesConfiguration.eol) {
|
||||
if (filesConfiguration.eol !== 'auto') {
|
||||
return filesConfiguration.eol;
|
||||
}
|
||||
}
|
||||
return (isLinux || isMacintosh) ? '\n' : '\r\n';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { Node, build, find } from './tokenTree';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
||||
/**
|
||||
* Interface used to compute a hierachry of logical ranges.
|
||||
*/
|
||||
export interface ILogicalSelectionEntry {
|
||||
type: string;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
export class TokenSelectionSupport {
|
||||
|
||||
private _modelService: IModelService;
|
||||
|
||||
constructor(@IModelService modelService: IModelService) {
|
||||
this._modelService = modelService;
|
||||
}
|
||||
|
||||
public getRangesToPosition(resource: URI, position: Position): TPromise<ILogicalSelectionEntry[]> {
|
||||
return TPromise.as(this.getRangesToPositionSync(resource, position));
|
||||
}
|
||||
|
||||
public getRangesToPositionSync(resource: URI, position: Position): ILogicalSelectionEntry[] {
|
||||
const model = this._modelService.getModel(resource);
|
||||
let entries: ILogicalSelectionEntry[] = [];
|
||||
|
||||
if (model) {
|
||||
this._doGetRangesToPosition(model, position).forEach(range => {
|
||||
entries.push({
|
||||
type: void 0,
|
||||
range
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private _doGetRangesToPosition(model: ITextModel, position: Position): Range[] {
|
||||
|
||||
let tree = build(model);
|
||||
let node: Node;
|
||||
let lastRange: Range;
|
||||
|
||||
node = find(tree, position);
|
||||
let ranges: Range[] = [];
|
||||
while (node) {
|
||||
if (!lastRange || !Range.equalsRange(lastRange, node.range)) {
|
||||
ranges.push(node.range);
|
||||
}
|
||||
lastRange = node.range;
|
||||
node = node.parent;
|
||||
}
|
||||
ranges = ranges.reverse();
|
||||
return ranges;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@@ -11,7 +10,25 @@ import { LineTokens } from 'vs/editor/common/core/lineTokens';
|
||||
import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports';
|
||||
import { BracketsUtils, RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { LanguageId, StandardTokenType } from 'vs/editor/common/modes';
|
||||
import { LanguageId, StandardTokenType, SelectionRangeProvider } from 'vs/editor/common/modes';
|
||||
|
||||
export class TokenTreeSelectionRangeProvider implements SelectionRangeProvider {
|
||||
|
||||
provideSelectionRanges(model: ITextModel, position: Position): Range[] {
|
||||
let tree = build(model);
|
||||
let node = find(tree, position);
|
||||
let ranges: Range[] = [];
|
||||
let lastRange: Range | undefined;
|
||||
while (node) {
|
||||
if (!lastRange || !Range.equalsRange(lastRange, node.range)) {
|
||||
ranges.push(node.range);
|
||||
}
|
||||
lastRange = node.range;
|
||||
node = node.parent;
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
}
|
||||
|
||||
export const enum TokenTreeBracket {
|
||||
None = 0,
|
||||
@@ -61,7 +78,7 @@ export class NodeList extends Node {
|
||||
return !this.hasChildren && !this.parent;
|
||||
}
|
||||
|
||||
public append(node: Node): boolean {
|
||||
public append(node: Node | null): boolean {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
@@ -106,9 +123,9 @@ class Token {
|
||||
|
||||
readonly range: Range;
|
||||
readonly bracket: TokenTreeBracket;
|
||||
readonly bracketType: string;
|
||||
readonly bracketType: string | null;
|
||||
|
||||
constructor(range: Range, bracket: TokenTreeBracket, bracketType: string) {
|
||||
constructor(range: Range, bracket: TokenTreeBracket, bracketType: string | null) {
|
||||
this.range = range;
|
||||
this.bracket = bracket;
|
||||
this.bracketType = bracketType;
|
||||
@@ -149,7 +166,7 @@ class ModelRawTokenScanner {
|
||||
private _versionId: number;
|
||||
private _lineNumber: number;
|
||||
private _tokenIndex: number;
|
||||
private _lineTokens: LineTokens;
|
||||
private _lineTokens: LineTokens | null;
|
||||
|
||||
constructor(model: ITextModel) {
|
||||
this._model = model;
|
||||
@@ -174,14 +191,14 @@ class ModelRawTokenScanner {
|
||||
this._model.forceTokenization(this._lineNumber);
|
||||
this._lineTokens = this._model.getLineTokens(this._lineNumber);
|
||||
this._tokenIndex = 0;
|
||||
if (this._lineTokens.getCount() === 0) {
|
||||
if (this._lineTokens.getLineContent().length === 0) {
|
||||
// Skip empty lines
|
||||
this._lineTokens = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public next(): RawToken {
|
||||
public next(): RawToken | null {
|
||||
if (!this._lineTokens) {
|
||||
return null;
|
||||
}
|
||||
@@ -200,7 +217,7 @@ class TokenScanner {
|
||||
private _rawTokenScanner: ModelRawTokenScanner;
|
||||
private _nextBuff: Token[];
|
||||
|
||||
private _cachedLanguageBrackets: RichEditBrackets;
|
||||
private _cachedLanguageBrackets: RichEditBrackets | null;
|
||||
private _cachedLanguageId: LanguageId;
|
||||
|
||||
constructor(model: ITextModel) {
|
||||
@@ -210,9 +227,9 @@ class TokenScanner {
|
||||
this._cachedLanguageId = -1;
|
||||
}
|
||||
|
||||
next(): Token {
|
||||
next(): Token | null {
|
||||
if (this._nextBuff.length > 0) {
|
||||
return this._nextBuff.shift();
|
||||
return this._nextBuff.shift()!;
|
||||
}
|
||||
|
||||
const token = this._rawTokenScanner.next();
|
||||
@@ -239,7 +256,7 @@ class TokenScanner {
|
||||
);
|
||||
}
|
||||
|
||||
let foundBracket: Range;
|
||||
let foundBracket: Range | null;
|
||||
do {
|
||||
foundBracket = BracketsUtils.findNextBracketInToken(modeBrackets.forwardRegex, lineNumber, lineText, startOffset, endOffset);
|
||||
if (foundBracket) {
|
||||
@@ -280,7 +297,7 @@ class TokenScanner {
|
||||
));
|
||||
}
|
||||
|
||||
return this._nextBuff.shift();
|
||||
return this._nextBuff.shift() || null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +327,7 @@ class TokenTreeBuilder {
|
||||
let accepted = condt(token);
|
||||
if (!accepted) {
|
||||
this._stack.push(token);
|
||||
this._currentToken = null;
|
||||
// this._currentToken = null;
|
||||
} else {
|
||||
this._currentToken = token;
|
||||
// console.log('accepted: ' + token.__debugContent);
|
||||
@@ -327,7 +344,7 @@ class TokenTreeBuilder {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private _line(): Node {
|
||||
private _line(): Node | null {
|
||||
let node = new NodeList();
|
||||
let lineNumber: number;
|
||||
|
||||
@@ -352,16 +369,16 @@ class TokenTreeBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private _token(): Node {
|
||||
private _token(): Node | null {
|
||||
if (!this._accept(token => token.bracket === TokenTreeBracket.None)) {
|
||||
return null;
|
||||
}
|
||||
return newNode(this._currentToken);
|
||||
}
|
||||
|
||||
private _block(): Node {
|
||||
private _block(): Node | null {
|
||||
|
||||
let bracketType: string;
|
||||
let bracketType: string | null;
|
||||
let accepted: boolean;
|
||||
|
||||
accepted = this._accept(token => {
|
||||
@@ -390,7 +407,7 @@ class TokenTreeBuilder {
|
||||
return bracket;
|
||||
}
|
||||
|
||||
private _any(): Node {
|
||||
private _any(): Node | null {
|
||||
if (!this._accept(_ => true)) {
|
||||
return null;
|
||||
}
|
||||
@@ -409,7 +426,7 @@ export function build(model: ITextModel): Node {
|
||||
return node;
|
||||
}
|
||||
|
||||
export function find(node: Node, position: Position): Node {
|
||||
export function find(node: Node, position: Position): Node | null {
|
||||
if (node instanceof NodeList && node.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
@@ -418,7 +435,7 @@ export function find(node: Node, position: Position): Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result: Node;
|
||||
let result: Node | null = null;
|
||||
|
||||
if (node instanceof NodeList) {
|
||||
if (node.hasChildren) {
|
||||
@@ -428,7 +445,7 @@ export function find(node: Node, position: Position): Node {
|
||||
}
|
||||
|
||||
} else if (node instanceof Block) {
|
||||
result = find(node.open, position) || find(node.elements, position) || find(node.close, position);
|
||||
result = find(node.elements, position) || find(node.open, position) || find(node.close, position);
|
||||
}
|
||||
|
||||
return result || node;
|
||||
|
||||
31
src/vs/editor/contrib/smartSelect/wordSelections.ts
Normal file
31
src/vs/editor/contrib/smartSelect/wordSelections.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SelectionRangeProvider } from 'vs/editor/common/modes';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
|
||||
export class WordSelectionRangeProvider implements SelectionRangeProvider {
|
||||
|
||||
provideSelectionRanges(model: ITextModel, position: Position): Range[] {
|
||||
let result: Range[] = [];
|
||||
this._addWordRanges(result, model, position);
|
||||
this._addLineRanges(result, model, position);
|
||||
return result;
|
||||
}
|
||||
|
||||
private _addWordRanges(bucket: Range[], model: ITextModel, pos: Position): void {
|
||||
const word = model.getWordAtPosition(pos);
|
||||
if (word) {
|
||||
bucket.push(new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn));
|
||||
}
|
||||
}
|
||||
|
||||
private _addLineRanges(bucket: Range[], model: ITextModel, pos: Position): void {
|
||||
bucket.push(new Range(pos.lineNumber, model.getLineFirstNonWhitespaceColumn(pos.lineNumber), pos.lineNumber, model.getLineLastNonWhitespaceColumn(pos.lineNumber)));
|
||||
bucket.push(new Range(pos.lineNumber, model.getLineMinColumn(pos.lineNumber), pos.lineNumber, model.getLineMaxColumn(pos.lineNumber)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user