提交 6cc63cdf 编写于 作者: R Ramya Achutha Rao

Use stream reader for TextDocument for more efficient parsing

上级 324cd2ac
......@@ -5,7 +5,7 @@
"version": "0.0.1",
"publisher": "vscode",
"engines": {
"vscode": "^1.10.0"
"vscode": "^1.13.0"
},
"categories": [
"Other"
......
......@@ -4,9 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getNode, getNodeOuterSelection, getNodeInnerSelection, isStyleSheet } from './util';
import { getNode, isStyleSheet } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import { DocumentStreamReader } from './bufferStream';
export function balanceOut() {
balance(true);
......@@ -27,14 +28,12 @@ function balance(out: boolean) {
}
let getRangeFunction = out ? getRangeToBalanceOut : getRangeToBalanceIn;
let rootNode: Node = parse(editor.document.getText());
let rootNode: Node = parse(new DocumentStreamReader(editor.document));
let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let range = getRangeFunction(editor.document, selection, rootNode);
if (range) {
newSelections.push(range);
}
newSelections.push(range ? range : selection);
});
editor.selection = newSelections[0];
......@@ -42,11 +41,16 @@ function balance(out: boolean) {
}
function getRangeToBalanceOut(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.Selection {
let offset = document.offsetAt(selection.start);
let nodeToBalance = getNode(rootNode, offset);
let nodeToBalance = getNode(rootNode, selection.start);
if (!nodeToBalance) {
return;
}
if (!nodeToBalance.close) {
return new vscode.Selection(nodeToBalance.start, nodeToBalance.end);
}
let innerSelection = getNodeInnerSelection(document, nodeToBalance);
let outerSelection = getNodeOuterSelection(document, nodeToBalance);
let innerSelection = new vscode.Selection(nodeToBalance.open.end, nodeToBalance.close.start);
let outerSelection = new vscode.Selection(nodeToBalance.start, nodeToBalance.end);
if (innerSelection.contains(selection) && !innerSelection.isEqual(selection)) {
return innerSelection;
......@@ -58,18 +62,26 @@ function getRangeToBalanceOut(document: vscode.TextDocument, selection: vscode.S
}
function getRangeToBalanceIn(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.Selection {
let offset = document.offsetAt(selection.start);
let nodeToBalance: Node = getNode(rootNode, offset);
let nodeToBalance: Node = getNode(rootNode, selection.start, true);
if (!nodeToBalance) {
return;
}
if (!nodeToBalance.firstChild) {
return selection;
if (nodeToBalance.close) {
return new vscode.Selection(nodeToBalance.open.end, nodeToBalance.close.start);
}
return;
}
if (nodeToBalance.firstChild.start === offset && nodeToBalance.firstChild.end === document.offsetAt(selection.end)) {
return getNodeInnerSelection(document, nodeToBalance.firstChild);
if (selection.start.isEqual(nodeToBalance.firstChild.start)
&& selection.end.isEqual(nodeToBalance.firstChild.end)
&& nodeToBalance.firstChild.close) {
return new vscode.Selection(nodeToBalance.firstChild.open.end, nodeToBalance.firstChild.close.start);
}
return new vscode.Selection(document.positionAt(nodeToBalance.firstChild.start), document.positionAt(nodeToBalance.firstChild.end));
return new vscode.Selection(nodeToBalance.firstChild.start, nodeToBalance.firstChild.end);
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TextDocument, Position, Range, EndOfLine } from 'vscode';
/**
* A stream reader for VSCode's `TextDocument`
* Based on @emmetio/stream-reader and @emmetio/atom-plugin
*/
export class DocumentStreamReader {
private document: TextDocument;
private start: Position;
private _eof: Position;
private pos: Position;
private _eol: string;
/**
* @param {TextDocument} buffer
* @param {Position} pos
* @param {Range} limit
*/
constructor(document: TextDocument, pos?: Position, limit?: Range) {
this.document = document;
this.start = this.pos = pos ? pos : new Position(0, 0);
this._eof = limit ? limit.end : new Position(this.document.lineCount - 1, this._lineLength(this.document.lineCount - 1));
this._eol = this.document.eol === EndOfLine.LF ? '\n' : '\r\n';
}
/**
* Returns true only if the stream is at the end of the file.
* @returns {Boolean}
*/
eof() {
return this.pos.isAfterOrEqual(this._eof);
}
/**
* Creates a new stream instance which is limited to given range for given document
* @param {Position} start
* @param {Position} end
* @return {DocumentStreamReader}
*/
limit(start, end) {
return new DocumentStreamReader(this.document, start, new Range(start, end));
}
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
* @returns {Number}
*/
peek() {
if (this.eof()) {
return NaN;
}
const line = this.document.lineAt(this.pos.line).text;
return this.pos.character < line.length ? line.charCodeAt(this.pos.character) : this._eol.charCodeAt(this.pos.character - line.length);
}
/**
* Returns the next character in the stream and advances it.
* Also returns NaN when no more characters are available.
* @returns {Number}
*/
next() {
if (this.eof()) {
return NaN;
}
const line = this.document.lineAt(this.pos.line).text;
let code: number;
if (this.pos.character < line.length) {
code = line.charCodeAt(this.pos.character);
this.pos = this.pos.translate(0, 1);
} else {
code = this._eol.charCodeAt(this.pos.character - line.length);
this.pos = new Position(this.pos.line + 1, 0);
}
if (this.eof()) {
// restrict pos to eof, if in case it got moved beyond eof
this.pos = new Position(this._eof.line, this._eof.character);
}
return code;
}
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
* @param {Number} n
*/
backUp(n) {
let row = this.pos.line;
let column = this.pos.character;
column -= (n || 1);
while (row >= 0 && column < 0) {
row--;
column += this._lineLength(row);
}
this.pos = row < 0 || column < 0
? new Position(0, 0)
: new Position(row, column);
return this.peek();
}
/**
* Get the string between the start of the current token and the
* current stream position.
* @returns {String}
*/
current() {
return this.substring(this.start, this.pos);
}
/**
* Returns contents for given range
* @param {Position} from
* @param {Position} to
* @return {String}
*/
substring(from, to) {
return this.document.getText(new Range(from, to));
}
/**
* Creates error object with current stream state
* @param {String} message
* @return {Error}
*/
error(message) {
const err = new Error(`${message} at row ${this.pos.line}, column ${this.pos.character}`);
return err;
}
/**
* Returns line length of given row, including line ending
* @param {Number} row
* @return {Number}
*/
_lineLength(row) {
if (row === this.document.lineCount - 1) {
return this.document.lineAt(row).text.length;
}
return this.document.lineAt(row).text.length + this._eol.length;
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
* @param {Number|Function} match
* @returns {Boolean}
*/
eat(match) {
const ch = this.peek();
const ok = typeof match === 'function' ? match(ch) : ch === match;
if (ok) {
this.next();
}
return ok;
}
/**
* Repeatedly calls <code>eat</code> with the given argument, until it
* fails. Returns <code>true</code> if any characters were eaten.
* @param {Object} match
* @returns {Boolean}
*/
eatWhile(match) {
const start = this.pos;
while (!this.eof() && this.eat(match)) { }
return !this.pos.isEqual(start);
}
}
......@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
import { getNode } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import { DocumentStreamReader } from './bufferStream';
export function matchTag() {
let editor = vscode.window.activeTextEditor;
......@@ -15,10 +16,10 @@ export function matchTag() {
return;
}
let rootNode: Node = parse(editor.document.getText());
let rootNode: Node = parse(new DocumentStreamReader(editor.document));
let updatedSelections = [];
editor.selections.forEach(selection => {
let updatedSelection = getUpdatedSelections(editor, editor.document.offsetAt(selection.start), rootNode);
let updatedSelection = getUpdatedSelections(editor, selection.start, rootNode);
if (updatedSelection) {
updatedSelections.push(updatedSelection);
}
......@@ -29,22 +30,16 @@ export function matchTag() {
}
}
function getUpdatedSelections(editor: vscode.TextEditor, offset: number, rootNode: Node): vscode.Selection {
let currentNode = getNode(rootNode, offset, true);
function getUpdatedSelections(editor: vscode.TextEditor, position: vscode.Position, rootNode: Node): vscode.Selection {
let currentNode = getNode(rootNode, position, true);
// If no closing tag or cursor is between open and close tag, then no-op
if (!currentNode.close || (currentNode.open.end < offset && currentNode.close.start > offset)) {
if (!currentNode.close || (position.isAfter(currentNode.open.end) && position.isBefore(currentNode.close.start))) {
return;
}
if (offset <= currentNode.open.end) {
let matchingPosition = editor.document.positionAt(currentNode.close.start);
return new vscode.Selection(matchingPosition, matchingPosition);
} else {
let matchingPosition = editor.document.positionAt(currentNode.open.start);
return new vscode.Selection(matchingPosition, matchingPosition);
}
let finalPosition = position.isBeforeOrEqual(currentNode.open.end) ? currentNode.close.start : currentNode.open.start;
return new vscode.Selection(finalPosition, finalPosition);
}
......@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
import { isStyleSheet, getNode } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import { DocumentStreamReader } from './bufferStream';
export function mergeLines() {
let editor = vscode.window.activeTextEditor;
......@@ -18,7 +19,7 @@ export function mergeLines() {
return;
}
let rootNode: Node = parse(editor.document.getText());
let rootNode: Node = parse(new DocumentStreamReader(editor.document));
editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
......@@ -33,13 +34,13 @@ function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Sel
let endNodeToUpdate: Node;
if (selection.isEmpty) {
startNodeToUpdate = endNodeToUpdate = getNode(rootNode, document.offsetAt(selection.start));
startNodeToUpdate = endNodeToUpdate = getNode(rootNode, selection.start);
} else {
startNodeToUpdate = getNode(rootNode, document.offsetAt(selection.start), true);
endNodeToUpdate = getNode(rootNode, document.offsetAt(selection.end), true);
startNodeToUpdate = getNode(rootNode, selection.start, true);
endNodeToUpdate = getNode(rootNode, selection.end, true);
}
let rangeToReplace = new vscode.Range(document.positionAt(startNodeToUpdate.start), document.positionAt(endNodeToUpdate.end));
let rangeToReplace = new vscode.Range(startNodeToUpdate.start, endNodeToUpdate.end);
let textToReplaceWith = document.getText(rangeToReplace).replace(/\r\n|\n/g, '').replace(/>\s*</g, '><');
return [rangeToReplace, textToReplaceWith];
......
......@@ -31,8 +31,7 @@ export function removeTag() {
}
function getRangeToRemove(editor: vscode.TextEditor, selection: vscode.Selection, indentInSpaces: string): vscode.Range[] {
let offset = editor.document.offsetAt(selection.start);
let [openRange, closeRange] = getOpenCloseRange(editor.document, offset);
let [openRange, closeRange] = getOpenCloseRange(editor.document, selection.start);
if (!openRange.contains(selection.start) && !closeRange.contains(selection.start)) {
return [];
}
......
......@@ -10,6 +10,7 @@ import { nextItemStylesheet, prevItemStylesheet } from './selectItemStylesheet';
import parseStylesheet from '@emmetio/css-parser';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import { DocumentStreamReader } from './bufferStream';
export function fetchSelectItem(direction: string): void {
let editor = vscode.window.activeTextEditor;
......@@ -31,11 +32,11 @@ export function fetchSelectItem(direction: string): void {
parseContent = parse;
}
let rootNode: Node = parseContent(editor.document.getText());
let rootNode: Node = parseContent(new DocumentStreamReader(editor.document));
let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
const selectionStart = editor.document.offsetAt(selection.isReversed ? selection.active : selection.anchor);
const selectionEnd = editor.document.offsetAt(selection.isReversed ? selection.anchor : selection.active);
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
let updatedSelection = direction === 'next' ? nextItem(selectionStart, selectionEnd, editor, rootNode) : prevItem(selectionStart, selectionEnd, editor, rootNode);
newSelections.push(updatedSelection ? updatedSelection : selection);
......
......@@ -7,18 +7,18 @@ import * as vscode from 'vscode';
import { getNode, getDeepestNode, findNextWord, findPrevWord } from './util';
import Node from '@emmetio/node';
export function nextItemHTML(selectionStart: number, selectionEnd: number, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let currentNode = getNode(rootNode, selectionEnd);
let nextNode: Node;
if (currentNode.type !== 'comment') {
// If cursor is in the tag name, select tag
if (selectionEnd < currentNode.open.start + currentNode.name.length) {
if (selectionEnd.translate(0, -currentNode.name.length).isBefore(currentNode.open.start)) {
return getSelectionFromNode(currentNode, editor.document);
}
// If cursor is in the open tag, look for attributes
if (selectionEnd < currentNode.open.end) {
if (selectionEnd.isBefore(currentNode.open.end)) {
let attrSelection = getNextAttribute(selectionStart, selectionEnd, editor.document, currentNode);
if (attrSelection) {
return attrSelection;
......@@ -27,7 +27,7 @@ export function nextItemHTML(selectionStart: number, selectionEnd: number, edito
// Get the first child of current node which is right after the cursor and is not a comment
nextNode = currentNode.firstChild;
while (nextNode && (nextNode.start < selectionEnd || nextNode.type === 'comment')) {
while (nextNode && (selectionEnd.isAfterOrEqual(nextNode.start) || nextNode.type === 'comment')) {
nextNode = nextNode.nextSibling;
}
}
......@@ -49,19 +49,19 @@ export function nextItemHTML(selectionStart: number, selectionEnd: number, edito
return getSelectionFromNode(nextNode, editor.document);
}
export function prevItemHTML(selectionStart: number, selectionEnd: number, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let currentNode = getNode(rootNode, selectionStart);
let prevNode: Node;
if (currentNode.type !== 'comment' && selectionStart > currentNode.open.start + 1) {
if (currentNode.type !== 'comment' && selectionStart.translate(0, -1).isAfter(currentNode.open.start)) {
if (selectionStart < currentNode.open.end || !currentNode.firstChild) {
if (selectionStart.isBefore(currentNode.open.end) || !currentNode.firstChild) {
prevNode = currentNode;
} else {
// Select the child that appears just before the cursor and is not a comment
prevNode = currentNode.firstChild;
let oldOption: Node;
while (prevNode.nextSibling && prevNode.nextSibling.end < selectionStart) {
while (prevNode.nextSibling && selectionStart.isAfterOrEqual(prevNode.nextSibling.end)) {
if (prevNode && prevNode.type !== 'comment') {
oldOption = prevNode;
}
......@@ -92,14 +92,14 @@ export function prevItemHTML(selectionStart: number, selectionEnd: number, edito
function getSelectionFromNode(node: Node, document: vscode.TextDocument): vscode.Selection {
if (node && node.open) {
let selectionStart = document.positionAt(node.open.start + 1);
let selectionStart = (<vscode.Position>node.open.start).translate(0, 1);
let selectionEnd = selectionStart.translate(0, node.name.length);
return new vscode.Selection(selectionStart, selectionEnd);
}
}
function getNextAttribute(selectionStart: number, selectionEnd: number, document: vscode.TextDocument, node: Node): vscode.Selection {
function getNextAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, document: vscode.TextDocument, node: Node): vscode.Selection {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
......@@ -108,19 +108,19 @@ function getNextAttribute(selectionStart: number, selectionEnd: number, document
for (let i = 0; i < node.attributes.length; i++) {
let attr = node.attributes[i];
if (selectionEnd < attr.start) {
if (selectionEnd.isBefore(attr.start)) {
// select full attr
return new vscode.Selection(document.positionAt(attr.start), document.positionAt(attr.end));
return new vscode.Selection(attr.start, attr.end);
}
if (attr.value.start === attr.value.end) {
if ((<vscode.Position>attr.value.start).isEqual(attr.value.end)) {
// No attr value to select
continue;
}
if ((selectionStart === attr.start && selectionEnd === attr.end) || selectionEnd < attr.value.start) {
if ((selectionStart.isEqual(attr.start) && selectionEnd.isEqual(attr.end)) || selectionEnd.isBefore(attr.value.start)) {
// cursor is in attr name, so select full attr value
return new vscode.Selection(document.positionAt(attr.value.start), document.positionAt(attr.value.end));
return new vscode.Selection(attr.value.start, attr.value.end);
}
// Fetch the next word in the attr value
......@@ -131,26 +131,26 @@ function getNextAttribute(selectionStart: number, selectionEnd: number, document
}
let pos = undefined;
if (selectionStart === attr.value.start && selectionEnd === attr.value.end) {
if (selectionStart.isEqual(attr.value.start) && selectionEnd.isEqual(attr.value.end)) {
pos = -1;
}
if (pos === undefined && selectionEnd < attr.end) {
pos = selectionEnd - attr.value.start - 1;
if (pos === undefined && selectionEnd.isBefore(attr.end)) {
pos = selectionEnd.character - attr.value.start.character - 1;
}
if (pos !== undefined) {
let [newSelectionStart, newSelectionEnd] = findNextWord(attr.value.toString(), pos);
if (newSelectionStart >= 0 && newSelectionEnd >= 0) {
newSelectionStart += attr.value.start;
newSelectionEnd += attr.value.start;
return new vscode.Selection(document.positionAt(newSelectionStart), document.positionAt(newSelectionEnd));
let [newSelectionStartOffset, newSelectionEndOffset] = findNextWord(attr.value.toString(), pos);
if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {
const newSelectionStart = (<vscode.Position>attr.value.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>attr.value.start).translate(0, newSelectionEndOffset);
return new vscode.Selection(newSelectionStart, newSelectionEnd);
}
}
}
}
function getPrevAttribute(selectionStart: number, selectionEnd: number, document: vscode.TextDocument, node: Node): vscode.Selection {
function getPrevAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, document: vscode.TextDocument, node: Node): vscode.Selection {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
......@@ -159,32 +159,32 @@ function getPrevAttribute(selectionStart: number, selectionEnd: number, document
for (let i = node.attributes.length - 1; i >= 0; i--) {
let attr = node.attributes[i];
if (selectionStart <= attr.start) {
if (selectionStart.isBeforeOrEqual(attr.start)) {
continue;
}
if (attr.value.start === attr.value.end || selectionStart < attr.value.start) {
if ((<vscode.Position>attr.value.start).isEqual(attr.value.end) || selectionStart.isBefore(attr.value.start)) {
// select full attr
return new vscode.Selection(document.positionAt(attr.start), document.positionAt(attr.end));
return new vscode.Selection(attr.start, attr.end);
}
if (selectionStart === attr.value.start) {
if (selectionEnd >= attr.value.end) {
if (selectionStart.isEqual(attr.value.start)) {
if (selectionEnd.isAfterOrEqual(attr.value.end)) {
// select full attr
return new vscode.Selection(document.positionAt(attr.start), document.positionAt(attr.end));
return new vscode.Selection(attr.start, attr.end);
}
// select attr value
return new vscode.Selection(document.positionAt(attr.value.start), document.positionAt(attr.value.end));
return new vscode.Selection(attr.value.start, attr.value.end);
}
// Fetch the prev word in the attr value
let pos = selectionStart > attr.value.end ? attr.value.toString().length : selectionStart - attr.value.start;
let [newSelectionStart, newSelectionEnd] = findPrevWord(attr.value.toString(), pos);
if (newSelectionStart >= 0 && newSelectionEnd >= 0) {
newSelectionStart += attr.value.start;
newSelectionEnd += attr.value.start;
return new vscode.Selection(document.positionAt(newSelectionStart), document.positionAt(newSelectionEnd));
let pos = selectionStart.isAfter(attr.value.end) ? attr.value.toString().length : selectionStart.character - attr.value.start.character;
let [newSelectionStartOffset, newSelectionEndOffset] = findPrevWord(attr.value.toString(), pos);
if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {
const newSelectionStart = (<vscode.Position>attr.value.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>attr.value.start).translate(0, newSelectionEndOffset);
return new vscode.Selection(newSelectionStart, newSelectionEnd);
}
......
......@@ -7,16 +7,19 @@ import * as vscode from 'vscode';
import { getNode, getDeepestNode, findNextWord, findPrevWord } from './util';
import Node from '@emmetio/node';
export function nextItemStylesheet(startOffset: number, endOffset: number, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
export function nextItemStylesheet(startOffset: vscode.Position, endOffset: vscode.Position, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let currentNode = getNode(rootNode, endOffset, true);
if (!currentNode) {
currentNode = rootNode;
}
// Full property is selected, so select full property value next
if (currentNode.type === 'property' && startOffset === currentNode.start && endOffset === currentNode.end) {
if (currentNode.type === 'property' && startOffset.isEqual(currentNode.start) && endOffset.isEqual(currentNode.end)) {
return getSelectionFromProperty(currentNode, editor.document, startOffset, endOffset, true, 'next');
}
// Part or whole of propertyValue is selected, so select the next word in the propertyValue
if (currentNode.type === 'property' && startOffset >= currentNode.valueToken.start && endOffset <= currentNode.valueToken.end) {
if (currentNode.type === 'property' && startOffset.isAfterOrEqual(currentNode.valueToken.start) && endOffset.isBeforeOrEqual(currentNode.valueToken.end)) {
let singlePropertyValue = getSelectionFromProperty(currentNode, editor.document, startOffset, endOffset, false, 'next');
if (singlePropertyValue) {
return singlePropertyValue;
......@@ -24,14 +27,14 @@ export function nextItemStylesheet(startOffset: number, endOffset: number, edito
}
// Cursor is in the selector or in a property
if ((currentNode.type === 'rule' && endOffset < currentNode.selectorToken.end)
|| (currentNode.type === 'property' && endOffset < currentNode.valueToken.end)) {
if ((currentNode.type === 'rule' && endOffset.isBefore(currentNode.selectorToken.end))
|| (currentNode.type === 'property' && endOffset.isBefore(currentNode.valueToken.end))) {
return getSelectionFromNode(currentNode, editor.document);
}
// Get the first child of current node which is right after the cursor
let nextNode = currentNode.firstChild;
while (nextNode && endOffset >= nextNode.end) {
while (nextNode && endOffset.isAfterOrEqual(nextNode.end)) {
nextNode = nextNode.nextSibling;
}
......@@ -45,32 +48,32 @@ export function nextItemStylesheet(startOffset: number, endOffset: number, edito
}
export function prevItemStylesheet(startOffset: number, endOffset: number, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
export function prevItemStylesheet(startOffset: vscode.Position, endOffset: vscode.Position, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let currentNode = getNode(rootNode, startOffset);
if (!currentNode) {
currentNode = rootNode;
}
// Full property value is selected, so select the whole property next
if (currentNode.type === 'property' && startOffset === currentNode.valueToken.start && endOffset === currentNode.valueToken.end) {
if (currentNode.type === 'property' && startOffset.isEqual(currentNode.valueToken.start) && endOffset.isEqual(currentNode.valueToken.end)) {
return getSelectionFromNode(currentNode, editor.document);
}
// Part of propertyValue is selected, so select the prev word in the propertyValue
if (currentNode.type === 'property' && startOffset >= currentNode.valueToken.start && endOffset <= currentNode.valueToken.end) {
if (currentNode.type === 'property' && startOffset.isAfterOrEqual(currentNode.valueToken.start) && endOffset.isBeforeOrEqual(currentNode.valueToken.end)) {
let singlePropertyValue = getSelectionFromProperty(currentNode, editor.document, startOffset, endOffset, false, 'prev');
if (singlePropertyValue) {
return singlePropertyValue;
}
}
if (currentNode.type === 'property' || !currentNode.firstChild || (currentNode.type === 'rule' && startOffset <= currentNode.firstChild.start)) {
if (currentNode.type === 'property' || !currentNode.firstChild || (currentNode.type === 'rule' && startOffset.isBeforeOrEqual(currentNode.firstChild.start))) {
return getSelectionFromNode(currentNode, editor.document);
}
// Select the child that appears just before the cursor
let prevNode = currentNode.firstChild;
while (prevNode.nextSibling && prevNode.nextSibling.end <= startOffset) {
while (prevNode.nextSibling && startOffset.isAfterOrEqual(prevNode.nextSibling.end)) {
prevNode = prevNode.nextSibling;
}
prevNode = getDeepestNode(prevNode);
......@@ -86,47 +89,47 @@ function getSelectionFromNode(node: Node, document: vscode.TextDocument): vscode
}
let nodeToSelect = node.type === 'rule' ? node.selectorToken : node;
return new vscode.Selection(document.positionAt(nodeToSelect.start), document.positionAt(nodeToSelect.end));
return new vscode.Selection(nodeToSelect.start, nodeToSelect.end);
}
function getSelectionFromProperty(node: Node, document: vscode.TextDocument, selectionStart: number, selectionEnd: number, selectFullValue: boolean, direction: string): vscode.Selection {
function getSelectionFromProperty(node: Node, document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, selectFullValue: boolean, direction: string): vscode.Selection {
if (!node || node.type !== 'property') {
return;
}
let propertyValue = node.valueToken.stream.substring(node.valueToken.start, node.valueToken.end);
selectFullValue = selectFullValue || (direction === 'prev' && selectionStart === node.valueToken.start && selectionEnd < node.valueToken.end);
selectFullValue = selectFullValue || (direction === 'prev' && selectionStart.isEqual(node.valueToken.start) && selectionEnd.isBefore(node.valueToken.end));
if (selectFullValue) {
return new vscode.Selection(document.positionAt(node.valueToken.start), document.positionAt(node.valueToken.end));
return new vscode.Selection(node.valueToken.start, node.valueToken.end);
}
let pos;
if (direction === 'prev') {
if (selectionStart === node.valueToken.start) {
if (selectionStart.isEqual(node.valueToken.start)) {
return;
}
pos = selectionStart > node.valueToken.end ? propertyValue.length : selectionStart - node.valueToken.start;
pos = selectionStart.isAfter(node.valueToken.end) ? propertyValue.length : selectionStart.character - node.valueToken.start.character;
}
if (direction === 'next') {
if (selectionEnd === node.valueToken.end && (selectionStart > node.valueToken.start || propertyValue.indexOf(' ') === -1)) {
if (selectionEnd.isEqual(node.valueToken.end) && (selectionStart.isAfter(node.valueToken.start) || propertyValue.indexOf(' ') === -1)) {
return;
}
pos = selectionEnd === node.valueToken.end ? -1 : selectionEnd - node.valueToken.start - 1;
pos = selectionEnd.isEqual(node.valueToken.end) ? -1 : selectionEnd.character - node.valueToken.start.character - 1;
}
let [newSelectionStart, newSelectionEnd] = direction === 'prev' ? findPrevWord(propertyValue, pos) : findNextWord(propertyValue, pos);
if (!newSelectionStart && !newSelectionEnd) {
let [newSelectionStartOffset, newSelectionEndOffset] = direction === 'prev' ? findPrevWord(propertyValue, pos) : findNextWord(propertyValue, pos);
if (!newSelectionStartOffset && !newSelectionEndOffset) {
return;
}
newSelectionStart += node.valueToken.start;
newSelectionEnd += node.valueToken.start;
const newSelectionStart = (<vscode.Position>node.valueToken.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>node.valueToken.start).translate(0, newSelectionEndOffset);
return new vscode.Selection(document.positionAt(newSelectionStart), document.positionAt(newSelectionEnd));
return new vscode.Selection(newSelectionStart, newSelectionEnd);
}
......
......@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
import { isStyleSheet, getNode } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import { DocumentStreamReader } from './bufferStream';
export function splitJoinTag() {
let editor = vscode.window.activeTextEditor;
......@@ -18,7 +19,7 @@ export function splitJoinTag() {
return;
}
let rootNode: Node = parse(editor.document.getText());
let rootNode: Node = parse(new DocumentStreamReader(editor.document));
editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
......@@ -29,23 +30,24 @@ export function splitJoinTag() {
}
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range, string] {
let offset = document.offsetAt(selection.start);
let nodeToUpdate: Node = getNode(rootNode, offset);
let nodeToUpdate: Node = getNode(rootNode, selection.start);
let rangeToReplace: vscode.Range;
let textToReplaceWith: string;
if (!nodeToUpdate.close) {
// Split Tag
let nodeText = document.getText(new vscode.Range(document.positionAt(nodeToUpdate.start), document.positionAt(nodeToUpdate.end)));
let nodeText = document.getText(new vscode.Range(nodeToUpdate.start, nodeToUpdate.end));
let m = nodeText.match(/(\s*\/)?>$/);
let end = nodeToUpdate.open.end;
let start = m ? end - m[0].length : end;
let end = <vscode.Position>nodeToUpdate.end;
let start = m ? end.translate(0, -m[0].length) : end;
rangeToReplace = new vscode.Range(document.positionAt(start), document.positionAt(end));
rangeToReplace = new vscode.Range(start, end);
textToReplaceWith = `></${nodeToUpdate.name}>`;
} else {
// Join Tag
rangeToReplace = new vscode.Range(document.positionAt(nodeToUpdate.open.end - 1), document.positionAt(nodeToUpdate.close.end));
let start = (<vscode.Position>nodeToUpdate.open.end).translate(0, -1);
let end = <vscode.Position>nodeToUpdate.end;
rangeToReplace = new vscode.Range(start, end);
textToReplaceWith = '/>';
}
......
......@@ -8,7 +8,7 @@ import { getNode, isStyleSheet, getNodesInBetween } from './util';
import parse from '@emmetio/html-matcher';
import parseStylesheet from '@emmetio/css-parser';
import Node from '@emmetio/node';
import { DocumentStreamReader } from './bufferStream';
const startCommentStylesheet = '/*';
const endCommentStylesheet = '*/';
......@@ -39,7 +39,7 @@ export function toggleComment() {
endComment = endCommentHTML;
}
let rootNode = parseContent(editor.document.getText());
let rootNode = parseContent(new DocumentStreamReader(editor.document));
editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
......@@ -58,8 +58,8 @@ export function toggleComment() {
}
function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range[], vscode.Range] {
const selectionStart = document.offsetAt(selection.isReversed ? selection.active : selection.anchor);
const selectionEnd = document.offsetAt(selection.isReversed ? selection.anchor : selection.active);
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
let startNode = getNode(rootNode, selectionStart, true);
let endNode = getNode(rootNode, selectionEnd, true);
......@@ -79,7 +79,7 @@ function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Sele
return [rangesToUnComment, null];
}
let rangeToComment = new vscode.Range(document.positionAt(allNodes[0].start), document.positionAt(allNodes[allNodes.length - 1].end));
let rangeToComment = new vscode.Range(allNodes[0].start, allNodes[allNodes.length - 1].end);
return [rangesToUnComment, rangeToComment];
}
......@@ -88,7 +88,7 @@ function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vs
// If current node is commented, then uncomment and return
if (node.type === 'comment') {
rangesToUnComment.push(new vscode.Range(document.positionAt(node.start), document.positionAt(node.end)));
rangesToUnComment.push(new vscode.Range(node.start, node.end));
return rangesToUnComment;
}
......@@ -103,8 +103,8 @@ function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vs
function toggleCommentStylesheet(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range[], vscode.Range] {
const selectionStart = document.offsetAt(selection.isReversed ? selection.active : selection.anchor);
const selectionEnd = document.offsetAt(selection.isReversed ? selection.anchor : selection.active);
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
let startNode = getNode(rootNode, selectionStart, true);
let endNode = getNode(rootNode, selectionEnd, true);
......@@ -114,19 +114,18 @@ function toggleCommentStylesheet(document: vscode.TextDocument, selection: vscod
// Uncomment the comments that intersect with the selection.
rootNode.comments.forEach(comment => {
let commentStart = document.positionAt(comment.start);
let commentEnd = document.positionAt(comment.end);
if (!isFirstNodeCommented) {
isFirstNodeCommented = (comment.start <= selectionStart && comment.end >= selectionEnd);
isFirstNodeCommented = (selectionStart.isAfterOrEqual(comment.start) && selectionEnd.isBefore(comment.end));
}
if (selection.contains(commentStart) || selection.contains(commentEnd) || (comment.start <= selectionStart && comment.end >= selectionEnd)) {
rangesToUnComment.push(new vscode.Range(document.positionAt(comment.start), document.positionAt(comment.end)));
if (selection.contains(comment.start)
|| selection.contains(comment.end)
|| (selectionStart.isAfterOrEqual(comment.start) && selectionEnd.isBefore(comment.end))) {
rangesToUnComment.push(new vscode.Range(comment.start, comment.end));
}
});
let rangeToComment = isFirstNodeCommented ? null : new vscode.Range(document.positionAt(startNode.start), document.positionAt(endNode.end));
let rangeToComment = isFirstNodeCommented ? null : new vscode.Range(startNode ? startNode.start : selectionStart, endNode ? endNode.end : selectionEnd);
return [rangesToUnComment, rangeToComment];
......
......@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
import { getNode } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import { DocumentStreamReader } from './bufferStream';
export function updateTag(tagName: string) {
let editor = vscode.window.activeTextEditor;
......@@ -15,7 +16,7 @@ export function updateTag(tagName: string) {
return;
}
let rootNode: Node = parse(editor.document.getText());
let rootNode: Node = parse(new DocumentStreamReader(editor.document));
let rangesToUpdate = [];
editor.selections.reverse().forEach(selection => {
rangesToUpdate = rangesToUpdate.concat(getRangesToUpdate(editor, selection, rootNode));
......@@ -29,16 +30,15 @@ export function updateTag(tagName: string) {
}
function getRangesToUpdate(editor: vscode.TextEditor, selection: vscode.Selection, rootNode: Node): vscode.Range[] {
let offset = editor.document.offsetAt(selection.start);
let nodeToUpdate = getNode(rootNode, offset);
let nodeToUpdate = getNode(rootNode, selection.start);
let openStart = editor.document.positionAt(nodeToUpdate.open.start + 1);
let openStart = (<vscode.Position>nodeToUpdate.open.start).translate(0, 1);
let openEnd = openStart.translate(0, nodeToUpdate.name.length);
let ranges = [new vscode.Range(openStart, openEnd)];
if (nodeToUpdate.close) {
let closeStart = editor.document.positionAt(nodeToUpdate.close.start + 2);
let closeEnd = editor.document.positionAt(nodeToUpdate.close.end - 1);
let closeStart = (<vscode.Position>nodeToUpdate.close.start).translate(0, 2);
let closeEnd = (<vscode.Position>nodeToUpdate.close.end).translate(0, -1);
ranges.push(new vscode.Range(closeStart, closeEnd));
}
return ranges;
......
......@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import * as extract from '@emmetio/extract-abbreviation';
import { DocumentStreamReader } from './bufferStream';
export function validate(allowStylesheet: boolean = true): boolean {
let editor = vscode.window.activeTextEditor;
......@@ -80,24 +80,26 @@ export function getProfile(syntax: string): any {
return newOptions;
}
export function getOpenCloseRange(document: vscode.TextDocument, offset: number): [vscode.Range, vscode.Range] {
let rootNode: Node = parse(document.getText());
let nodeToUpdate = getNode(rootNode, offset);
let openRange = new vscode.Range(document.positionAt(nodeToUpdate.open.start), document.positionAt(nodeToUpdate.open.end));
export function getOpenCloseRange(document: vscode.TextDocument, position: vscode.Position): [vscode.Range, vscode.Range] {
let rootNode: Node = parse(new DocumentStreamReader(document));
let nodeToUpdate = getNode(rootNode, position);
let openRange = new vscode.Range(nodeToUpdate.open.start, nodeToUpdate.open.end);
let closeRange = null;
if (nodeToUpdate.close) {
closeRange = new vscode.Range(document.positionAt(nodeToUpdate.close.start), document.positionAt(nodeToUpdate.close.end));
closeRange = new vscode.Range(nodeToUpdate.close.start, nodeToUpdate.close.end);
}
return [openRange, closeRange];
}
export function getNode(root: Node, offset: number, includeNodeBoundary: boolean = false) {
export function getNode(root: Node, position: vscode.Position, includeNodeBoundary: boolean = false) {
let currentNode: Node = root.firstChild;
let foundNode: Node = null;
while (currentNode) {
if ((currentNode.start < offset && currentNode.end > offset)
|| (includeNodeBoundary && (currentNode.start <= offset && currentNode.end >= offset))) {
const nodeStart: vscode.Position = currentNode.start;
const nodeEnd: vscode.Position = currentNode.end;
if ((nodeStart.isBefore(position) && nodeEnd.isAfter(position))
|| (includeNodeBoundary && (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)))) {
foundNode = currentNode;
// Dig deeper
......@@ -110,14 +112,6 @@ export function getNode(root: Node, offset: number, includeNodeBoundary: boolean
return foundNode;
}
export function getNodeOuterSelection(document: vscode.TextDocument, node: Node): vscode.Selection {
return new vscode.Selection(document.positionAt(node.start), document.positionAt(node.end));
}
export function getNodeInnerSelection(document: vscode.TextDocument, node: Node): vscode.Selection {
return new vscode.Selection(document.positionAt(node.open.end), document.positionAt(node.close.start));
}
export function extractAbbreviation(position: vscode.Position): [vscode.Range, string] {
let editor = vscode.window.activeTextEditor;
let currentLine = editor.document.lineAt(position.line).text;
......@@ -229,32 +223,32 @@ export function getNodesInBetween(node1: Node, node2: Node): Node[] {
}
// node2 is ancestor of node1
if (node2.start < node1.start) {
if (node2.start.isBefore(node1.start)) {
return [node2];
}
// node1 is ancestor of node2
if (node2.start < node1.end) {
if (node2.start.isBefore(node1.end)) {
return [node1];
}
// Get the highest ancestor of node1 that should be commented
while (node1.parent && node1.parent.end < node2.start) {
while (node1.parent && node1.parent.end.isBefore(node2.start)) {
node1 = node1.parent;
}
// Get the highest ancestor of node2 that should be commented
while (node2.parent && node2.parent.start > node1.start) {
while (node2.parent && node2.parent.start.isAfter(node1.start)) {
node2 = node2.parent;
}
return getNextSiblingsTillPosition(node1, node2.end);
}
function getNextSiblingsTillPosition(node: Node, position: number): Node[] {
function getNextSiblingsTillPosition(node: Node, position: vscode.Position): Node[] {
let siblings: Node[] = [];
let currentNode = node;
while (currentNode && currentNode.start < position) {
while (currentNode && position.isAfter(currentNode.start)) {
siblings.push(currentNode);
currentNode = currentNode.nextSibling;
}
......@@ -265,5 +259,5 @@ export function sameNodes(node1: Node, node2: Node): boolean {
if (!node1 || !node2) {
return false;
}
return node1.start === node2.start && node1.end === node2.end;
return (<vscode.Position>node1.start).isEqual(node2.start) && (<vscode.Position>node1.end).isEqual(node2.end);
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册