mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
198
src/vs/editor/contrib/smartSelect/common/smartSelect.ts
Normal file
198
src/vs/editor/contrib/smartSelect/common/smartSelect.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ICommonCodeEditor, IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { editorAction, ServicesAccessor, IActionOptions, EditorAction, commonEditorContribution } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { TokenSelectionSupport, ILogicalSelectionEntry } from './tokenSelectionSupport';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
|
||||
// --- selection state machine
|
||||
|
||||
class State {
|
||||
|
||||
public editor: ICommonCodeEditor;
|
||||
public next: State;
|
||||
public previous: State;
|
||||
public selection: Range;
|
||||
|
||||
constructor(editor: ICommonCodeEditor) {
|
||||
this.editor = editor;
|
||||
this.next = null;
|
||||
this.previous = null;
|
||||
this.selection = editor.getSelection();
|
||||
}
|
||||
}
|
||||
|
||||
// --- shared state between grow and shrink actions
|
||||
var state: State = null;
|
||||
var ignoreSelection = false;
|
||||
|
||||
// -- action implementation
|
||||
|
||||
@commonEditorContribution
|
||||
class SmartSelectController implements IEditorContribution {
|
||||
|
||||
private static ID = 'editor.contrib.smartSelectController';
|
||||
|
||||
public static get(editor: ICommonCodeEditor): SmartSelectController {
|
||||
return editor.getContribution<SmartSelectController>(SmartSelectController.ID);
|
||||
}
|
||||
|
||||
private _tokenSelectionSupport: TokenSelectionSupport;
|
||||
|
||||
constructor(
|
||||
private editor: ICommonCodeEditor,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
this._tokenSelectionSupport = instantiationService.createInstance(TokenSelectionSupport);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return SmartSelectController.ID;
|
||||
}
|
||||
|
||||
public run(forward: boolean): TPromise<void> {
|
||||
|
||||
var selection = this.editor.getSelection();
|
||||
var model = this.editor.getModel();
|
||||
|
||||
// forget about current state
|
||||
if (state) {
|
||||
if (state.editor !== this.editor) {
|
||||
state = null;
|
||||
}
|
||||
}
|
||||
|
||||
var promise: TPromise<void> = TPromise.as(null);
|
||||
if (!state) {
|
||||
promise = this._tokenSelectionSupport.getRangesToPosition(model.uri, selection.getStartPosition()).then((elements: ILogicalSelectionEntry[]) => {
|
||||
|
||||
if (arrays.isFalsyOrEmpty(elements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lastState: State;
|
||||
elements.filter((element) => {
|
||||
// filter ranges inside the selection
|
||||
var selection = this.editor.getSelection();
|
||||
var 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
|
||||
var range = element.range;
|
||||
var 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
|
||||
var editorState = new State(this.editor);
|
||||
editorState.next = lastState;
|
||||
if (lastState) {
|
||||
lastState.previous = editorState;
|
||||
}
|
||||
state = editorState;
|
||||
|
||||
// listen to caret move and forget about state
|
||||
var unhook = this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
|
||||
if (ignoreSelection) {
|
||||
return;
|
||||
}
|
||||
state = null;
|
||||
unhook.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = forward ? state.next : state.previous;
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
ignoreSelection = true;
|
||||
try {
|
||||
this.editor.setSelection(state.selection);
|
||||
} finally {
|
||||
ignoreSelection = false;
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractSmartSelect extends EditorAction {
|
||||
|
||||
private _forward: boolean;
|
||||
|
||||
constructor(forward: boolean, opts: IActionOptions) {
|
||||
super(opts);
|
||||
this._forward = forward;
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): TPromise<void> {
|
||||
let controller = SmartSelectController.get(editor);
|
||||
if (controller) {
|
||||
return controller.run(this._forward);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
class GrowSelectionAction extends AbstractSmartSelect {
|
||||
constructor() {
|
||||
super(true, {
|
||||
id: 'editor.action.smartSelect.grow',
|
||||
label: nls.localize('smartSelect.grow', "Expand Select"),
|
||||
alias: 'Expand Select',
|
||||
precondition: null,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.textFocus,
|
||||
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.RightArrow,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyMod.Shift | KeyCode.RightArrow }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
class ShrinkSelectionAction extends AbstractSmartSelect {
|
||||
constructor() {
|
||||
super(false, {
|
||||
id: 'editor.action.smartSelect.shrink',
|
||||
label: nls.localize('smartSelect.shrink', "Shrink Select"),
|
||||
alias: 'Shrink Select',
|
||||
precondition: null,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.textFocus,
|
||||
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.LeftArrow,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyMod.Shift | KeyCode.LeftArrow }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IModel } from 'vs/editor/common/editorCommon';
|
||||
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[] {
|
||||
var model = this._modelService.getModel(resource),
|
||||
entries: ILogicalSelectionEntry[] = [];
|
||||
|
||||
if (model) {
|
||||
this._doGetRangesToPosition(model, position).forEach(range => {
|
||||
entries.push({
|
||||
type: void 0,
|
||||
range
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private _doGetRangesToPosition(model: IModel, position: Position): Range[] {
|
||||
|
||||
var tree = build(model),
|
||||
node: Node,
|
||||
lastRange: Range;
|
||||
|
||||
node = find(tree, position);
|
||||
var 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;
|
||||
}
|
||||
|
||||
}
|
||||
425
src/vs/editor/contrib/smartSelect/common/tokenTree.ts
Normal file
425
src/vs/editor/contrib/smartSelect/common/tokenTree.ts
Normal file
@@ -0,0 +1,425 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
import { IModel } from 'vs/editor/common/editorCommon';
|
||||
import { LineToken } 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';
|
||||
|
||||
export const enum TokenTreeBracket {
|
||||
None = 0,
|
||||
Open = 1,
|
||||
Close = -1
|
||||
}
|
||||
|
||||
export class Node {
|
||||
|
||||
start: Position;
|
||||
|
||||
end: Position;
|
||||
|
||||
get range(): Range {
|
||||
return new Range(
|
||||
this.start.lineNumber,
|
||||
this.start.column,
|
||||
this.end.lineNumber,
|
||||
this.end.column
|
||||
);
|
||||
}
|
||||
|
||||
parent: Node;
|
||||
}
|
||||
|
||||
export class NodeList extends Node {
|
||||
|
||||
children: Node[];
|
||||
|
||||
get start(): Position {
|
||||
return this.hasChildren
|
||||
? this.children[0].start
|
||||
: this.parent.start;
|
||||
}
|
||||
|
||||
get end(): Position {
|
||||
return this.hasChildren
|
||||
? this.children[this.children.length - 1].end
|
||||
: this.parent.end;
|
||||
}
|
||||
|
||||
get hasChildren() {
|
||||
return this.children && this.children.length > 0;
|
||||
}
|
||||
|
||||
get isEmpty() {
|
||||
return !this.hasChildren && !this.parent;
|
||||
}
|
||||
|
||||
public append(node: Node): boolean {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
node.parent = this;
|
||||
if (!this.children) {
|
||||
this.children = [];
|
||||
}
|
||||
if (node instanceof NodeList) {
|
||||
if (node.children) {
|
||||
this.children.push.apply(this.children, node.children);
|
||||
}
|
||||
} else {
|
||||
this.children.push(node);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class Block extends Node {
|
||||
|
||||
open: Node;
|
||||
close: Node;
|
||||
elements: NodeList;
|
||||
|
||||
get start(): Position {
|
||||
return this.open.start;
|
||||
}
|
||||
|
||||
get end(): Position {
|
||||
return this.close.end;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.elements = new NodeList();
|
||||
this.elements.parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
class Token {
|
||||
_tokenBrand: void;
|
||||
|
||||
readonly range: Range;
|
||||
readonly bracket: TokenTreeBracket;
|
||||
readonly bracketType: string;
|
||||
|
||||
constructor(range: Range, bracket: TokenTreeBracket, bracketType: string) {
|
||||
this.range = range;
|
||||
this.bracket = bracket;
|
||||
this.bracketType = bracketType;
|
||||
}
|
||||
}
|
||||
|
||||
function newNode(token: Token): Node {
|
||||
var node = new Node();
|
||||
node.start = token.range.getStartPosition();
|
||||
node.end = token.range.getEndPosition();
|
||||
return node;
|
||||
}
|
||||
|
||||
class RawToken {
|
||||
_basicTokenBrand: void;
|
||||
|
||||
public lineNumber: number;
|
||||
public lineText: string;
|
||||
public startOffset: number;
|
||||
public endOffset: number;
|
||||
public type: StandardTokenType;
|
||||
public languageId: LanguageId;
|
||||
|
||||
constructor(source: LineToken, lineNumber: number, lineText: string) {
|
||||
this.lineNumber = lineNumber;
|
||||
this.lineText = lineText;
|
||||
this.startOffset = source.startOffset;
|
||||
this.endOffset = source.endOffset;
|
||||
this.type = source.tokenType;
|
||||
this.languageId = source.languageId;
|
||||
}
|
||||
}
|
||||
|
||||
class ModelRawTokenScanner {
|
||||
|
||||
private _model: IModel;
|
||||
private _lineCount: number;
|
||||
private _versionId: number;
|
||||
private _lineNumber: number;
|
||||
private _lineText: string;
|
||||
private _next: LineToken;
|
||||
|
||||
constructor(model: IModel) {
|
||||
this._model = model;
|
||||
this._lineCount = this._model.getLineCount();
|
||||
this._versionId = this._model.getVersionId();
|
||||
this._lineNumber = 0;
|
||||
this._lineText = null;
|
||||
this._advance();
|
||||
}
|
||||
|
||||
private _advance(): void {
|
||||
this._next = (this._next ? this._next.next() : null);
|
||||
while (!this._next && this._lineNumber < this._lineCount) {
|
||||
this._lineNumber++;
|
||||
this._lineText = this._model.getLineContent(this._lineNumber);
|
||||
this._model.forceTokenization(this._lineNumber);
|
||||
let currentLineTokens = this._model.getLineTokens(this._lineNumber);
|
||||
this._next = currentLineTokens.firstToken();
|
||||
}
|
||||
}
|
||||
|
||||
public next(): RawToken {
|
||||
if (!this._next) {
|
||||
return null;
|
||||
}
|
||||
if (this._model.getVersionId() !== this._versionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result = new RawToken(this._next, this._lineNumber, this._lineText);
|
||||
this._advance();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class TokenScanner {
|
||||
|
||||
private _rawTokenScanner: ModelRawTokenScanner;
|
||||
private _nextBuff: Token[];
|
||||
|
||||
private _cachedLanguageBrackets: RichEditBrackets;
|
||||
private _cachedLanguageId: LanguageId;
|
||||
|
||||
constructor(model: IModel) {
|
||||
this._rawTokenScanner = new ModelRawTokenScanner(model);
|
||||
this._nextBuff = [];
|
||||
this._cachedLanguageBrackets = null;
|
||||
this._cachedLanguageId = -1;
|
||||
}
|
||||
|
||||
next(): Token {
|
||||
if (this._nextBuff.length > 0) {
|
||||
return this._nextBuff.shift();
|
||||
}
|
||||
|
||||
const token = this._rawTokenScanner.next();
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
const lineNumber = token.lineNumber;
|
||||
const lineText = token.lineText;
|
||||
const tokenType = token.type;
|
||||
let startOffset = token.startOffset;
|
||||
const endOffset = token.endOffset;
|
||||
|
||||
if (this._cachedLanguageId !== token.languageId) {
|
||||
this._cachedLanguageId = token.languageId;
|
||||
this._cachedLanguageBrackets = LanguageConfigurationRegistry.getBracketsSupport(this._cachedLanguageId);
|
||||
}
|
||||
const modeBrackets = this._cachedLanguageBrackets;
|
||||
|
||||
if (!modeBrackets || ignoreBracketsInToken(tokenType)) {
|
||||
return new Token(
|
||||
new Range(lineNumber, startOffset + 1, lineNumber, endOffset + 1),
|
||||
TokenTreeBracket.None,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
let foundBracket: Range;
|
||||
do {
|
||||
foundBracket = BracketsUtils.findNextBracketInToken(modeBrackets.forwardRegex, lineNumber, lineText, startOffset, endOffset);
|
||||
if (foundBracket) {
|
||||
const foundBracketStartOffset = foundBracket.startColumn - 1;
|
||||
const foundBracketEndOffset = foundBracket.endColumn - 1;
|
||||
|
||||
if (startOffset < foundBracketStartOffset) {
|
||||
// there is some text before this bracket in this token
|
||||
this._nextBuff.push(new Token(
|
||||
new Range(lineNumber, startOffset + 1, lineNumber, foundBracketStartOffset + 1),
|
||||
TokenTreeBracket.None,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
let bracketText = lineText.substring(foundBracketStartOffset, foundBracketEndOffset);
|
||||
bracketText = bracketText.toLowerCase();
|
||||
|
||||
const bracketData = modeBrackets.textIsBracket[bracketText];
|
||||
const bracketIsOpen = modeBrackets.textIsOpenBracket[bracketText];
|
||||
|
||||
this._nextBuff.push(new Token(
|
||||
new Range(lineNumber, foundBracketStartOffset + 1, lineNumber, foundBracketEndOffset + 1),
|
||||
bracketIsOpen ? TokenTreeBracket.Open : TokenTreeBracket.Close,
|
||||
`${bracketData.languageIdentifier.language};${bracketData.open};${bracketData.close}`
|
||||
));
|
||||
|
||||
startOffset = foundBracketEndOffset;
|
||||
}
|
||||
} while (foundBracket);
|
||||
|
||||
if (startOffset < endOffset) {
|
||||
// there is some remaining none-bracket text in this token
|
||||
this._nextBuff.push(new Token(
|
||||
new Range(lineNumber, startOffset + 1, lineNumber, endOffset + 1),
|
||||
TokenTreeBracket.None,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
return this._nextBuff.shift();
|
||||
}
|
||||
}
|
||||
|
||||
class TokenTreeBuilder {
|
||||
|
||||
private _scanner: TokenScanner;
|
||||
private _stack: Token[] = [];
|
||||
private _currentToken: Token;
|
||||
|
||||
constructor(model: IModel) {
|
||||
this._scanner = new TokenScanner(model);
|
||||
}
|
||||
|
||||
public build(): Node {
|
||||
var node = new NodeList();
|
||||
while (node.append(this._line() || this._any())) {
|
||||
// accept all
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private _accept(condt: (info: Token) => boolean): boolean {
|
||||
var token = this._stack.pop() || this._scanner.next();
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
var accepted = condt(token);
|
||||
if (!accepted) {
|
||||
this._stack.push(token);
|
||||
this._currentToken = null;
|
||||
} else {
|
||||
this._currentToken = token;
|
||||
// console.log('accepted: ' + token.__debugContent);
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
private _peek(condt: (info: Token) => boolean): boolean {
|
||||
var ret = false;
|
||||
this._accept(info => {
|
||||
ret = condt(info);
|
||||
return false;
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
private _line(): Node {
|
||||
var node = new NodeList(),
|
||||
lineNumber: number;
|
||||
|
||||
// capture current linenumber
|
||||
this._peek(info => {
|
||||
lineNumber = info.range.startLineNumber;
|
||||
return false;
|
||||
});
|
||||
|
||||
while (this._peek(info => info.range.startLineNumber === lineNumber)
|
||||
&& node.append(this._token() || this._block())) {
|
||||
|
||||
// all children that started on this line
|
||||
}
|
||||
|
||||
if (!node.children || node.children.length === 0) {
|
||||
return null;
|
||||
} else if (node.children.length === 1) {
|
||||
return node.children[0];
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
private _token(): Node {
|
||||
if (!this._accept(token => token.bracket === TokenTreeBracket.None)) {
|
||||
return null;
|
||||
}
|
||||
return newNode(this._currentToken);
|
||||
}
|
||||
|
||||
private _block(): Node {
|
||||
|
||||
var bracketType: string,
|
||||
accepted: boolean;
|
||||
|
||||
accepted = this._accept(token => {
|
||||
bracketType = token.bracketType;
|
||||
return token.bracket === TokenTreeBracket.Open;
|
||||
});
|
||||
if (!accepted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var bracket = new Block();
|
||||
bracket.open = newNode(this._currentToken);
|
||||
while (bracket.elements.append(this._line())) {
|
||||
// inside brackets
|
||||
}
|
||||
|
||||
if (!this._accept(token => token.bracket === TokenTreeBracket.Close && token.bracketType === bracketType)) {
|
||||
// missing closing bracket -> return just a node list
|
||||
var nodelist = new NodeList();
|
||||
nodelist.append(bracket.open);
|
||||
nodelist.append(bracket.elements);
|
||||
return nodelist;
|
||||
}
|
||||
|
||||
bracket.close = newNode(this._currentToken);
|
||||
return bracket;
|
||||
}
|
||||
|
||||
private _any(): Node {
|
||||
if (!this._accept(_ => true)) {
|
||||
return null;
|
||||
}
|
||||
return newNode(this._currentToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses this grammar:
|
||||
* grammer = { line }
|
||||
* line = { block | "token" }
|
||||
* block = "open_bracket" { line } "close_bracket"
|
||||
*/
|
||||
export function build(model: IModel): Node {
|
||||
var node = new TokenTreeBuilder(model).build();
|
||||
return node;
|
||||
}
|
||||
|
||||
export function find(node: Node, position: Position): Node {
|
||||
if (node instanceof NodeList && node.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Range.containsPosition(node.range, position)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var result: Node;
|
||||
|
||||
if (node instanceof NodeList) {
|
||||
if (node.hasChildren) {
|
||||
for (var i = 0, len = node.children.length; i < len && !result; i++) {
|
||||
result = find(node.children[i], position);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (node instanceof Block) {
|
||||
result = find(node.open, position) || find(node.elements, position) || find(node.close, position);
|
||||
}
|
||||
|
||||
return result || node;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { 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/common/tokenSelectionSupport';
|
||||
import { MockMode } 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';
|
||||
|
||||
class MockJSMode extends MockMode {
|
||||
|
||||
private static _id = new LanguageIdentifier('mockJSMode', 3);
|
||||
|
||||
constructor() {
|
||||
super(MockJSMode._id);
|
||||
|
||||
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
|
||||
brackets: [
|
||||
['(', ')'],
|
||||
['{', '}'],
|
||||
['[', ']']
|
||||
],
|
||||
|
||||
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 }
|
||||
}
|
||||
]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
suite('TokenSelectionSupport', () => {
|
||||
|
||||
let modelService: ModelServiceImpl = null;
|
||||
let tokenSelectionSupport: TokenSelectionSupport;
|
||||
let mode: MockJSMode = null;
|
||||
|
||||
setup(() => {
|
||||
modelService = new ModelServiceImpl(null, new TestConfigurationService());
|
||||
tokenSelectionSupport = new TokenSelectionSupport(modelService);
|
||||
mode = new MockJSMode();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
modelService.dispose();
|
||||
mode.dispose();
|
||||
});
|
||||
|
||||
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 actual = tokenSelectionSupport.getRangesToPositionSync(uri, 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));
|
||||
|
||||
assert.deepEqual(actualStr, desiredStr);
|
||||
|
||||
modelService.destroyModel(uri);
|
||||
}
|
||||
|
||||
test('getRangesToPosition #1', () => {
|
||||
|
||||
assertGetRangesToPosition([
|
||||
'function a(bar, foo){',
|
||||
'\tif (bar) {',
|
||||
'\t\treturn (bar + (2 * foo))',
|
||||
'\t}',
|
||||
'}'
|
||||
], 3, 20, [
|
||||
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, 27),
|
||||
new Range(3, 10, 3, 27),
|
||||
new Range(3, 11, 3, 26),
|
||||
new Range(3, 17, 3, 26),
|
||||
new Range(3, 18, 3, 25),
|
||||
// new Range(3, 19, 3, 20)
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user