Skip to content

Commit

Permalink
Stylesheet partial parse (#46376)
Browse files Browse the repository at this point in the history
* First draft at partial parsing of css for emmet

* Partial parsing of stylesheets for Emmet

* Addressing feedback

* Refactoring and fixing little bug

* Liitle fix

* Equals are now equals

* We don't want emmet in selectors

* Skip comments when preparsing

* Addressing feedback, refactoring and cleaning up code.

* Update endPosition only if not eof

* Use function to find closing comment

* Don't check more than 5000 characters back
  • Loading branch information
gushuro authored and ramya-rao-a committed Mar 27, 2018
1 parent c05e57d commit 91416ff
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 2 deletions.
4 changes: 2 additions & 2 deletions extensions/emmet/src/defaultCompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import * as vscode from 'vscode';
import { Node } from 'EmmetNode';
import { isValidLocationForEmmetAbbreviation } from './abbreviationActions';
import { getEmmetHelper, getNode, getMappingForIncludedLanguages, parseDocument, getEmmetConfiguration, getEmmetMode, isStyleSheet } from './util';
import { getEmmetHelper, getNode, getMappingForIncludedLanguages, parsePartialStylesheet, getEmmetConfiguration, getEmmetMode, isStyleSheet, parseDocument } from './util';

export class DefaultCompletionItemProvider implements vscode.CompletionItemProvider {

Expand Down Expand Up @@ -40,7 +40,7 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
validateLocation = syntax === 'html' || isStyleSheet(document.languageId);
// If document can be css parsed, get currentNode
if (isStyleSheet(document.languageId)) {
const rootNode = parseDocument(document, false);
const rootNode = document.lineCount > 1000 ? parsePartialStylesheet(document, position) : parseDocument(document, false);
if (!rootNode) {
return;
}
Expand Down
99 changes: 99 additions & 0 deletions extensions/emmet/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,105 @@ export function parseDocument(document: vscode.TextDocument, showError: boolean
return undefined;
}

export function parsePartialStylesheet(document: vscode.TextDocument, position: vscode.Position): Node | undefined {

let startPosition = new vscode.Position(0, 0);
let endPosition = new vscode.Position(document.lineCount - 1, document.lineAt(document.lineCount - 1).text.length);
const closeBrace = 125;
const openBrace = 123;
let slash = 47;
let star = 42;
let isCSS = document.languageId === 'css';

let singleLineCommentIndex = document.lineAt(position.line).text.indexOf('//');
if (!isCSS && singleLineCommentIndex > -1 && singleLineCommentIndex < position.character) {
return;
}

// Go forward until we found a closing brace.
let stream = new DocumentStreamReader(document, position);
while (!stream.eof() && !stream.eat(closeBrace)) {
if (stream.eat(slash)) {
if (stream.eat(slash) && !isCSS) {
// Single line Comment, we continue searching from next line.
stream.pos = new vscode.Position(stream.pos.line + 1, 0);
} else if (stream.eat(star)) {
stream.pos = findClosingCommentAfterPosition(document, stream.pos) || endPosition;
}
} else {
stream.next();
}
}

if (!stream.eof()) {
endPosition = stream.pos;
}

// Go back until we found an opening brace. If we find a closing one, we first find its opening brace and then we continue.
stream.pos = position;
let openBracesRemaining = 1;
let currentLine = position.line;
let limitCharacter = document.offsetAt(position) - 5000;
let limitPosition = limitCharacter > 0 ? document.positionAt(limitCharacter) : startPosition;

while (openBracesRemaining > 0 && !stream.sof()) {
if (position.line - stream.pos.line > 100 || stream.pos.isBeforeOrEqual(limitPosition)) {
return parseStylesheet(new DocumentStreamReader(document, startPosition, new vscode.Range(startPosition, endPosition)));
} else if (!isCSS && stream.pos.line !== currentLine) {
// In not CSS stylesheets, we need to skip singleLine comments.
currentLine = stream.pos.line;
let startLineComment = document.lineAt(currentLine).text.indexOf('//');
if (startLineComment > -1) {
stream.pos = new vscode.Position(currentLine, startLineComment);
}
}
let ch = stream.backUp(1);
if (ch === openBrace) {
openBracesRemaining--;
} else if (ch === closeBrace) {
if (isCSS) {
stream.next();
return parseStylesheet(new DocumentStreamReader(document, stream.pos, new vscode.Range(stream.pos, endPosition)));
}
openBracesRemaining++;
} else if (ch === slash) {
stream.backUp(1);
if (stream.peek() === star) {
stream.pos = findOpeningCommentBeforePosition(document, stream.pos) || startPosition;
} else {
stream.next();
}
}
}
// We are at an opening brace. We need to include its selector, but with one nonspace character is enough.
while (!stream.sof() && String.fromCharCode(stream.backUp(1)).match(/\s/)) { }

startPosition = stream.pos;
try {
return parseStylesheet(new DocumentStreamReader(document, startPosition, new vscode.Range(startPosition, endPosition)));
} catch (e) {
}
}

function findOpeningCommentBeforePosition(document: vscode.TextDocument, position: vscode.Position): vscode.Position | undefined {
let text = document.getText(new vscode.Range(0, 0, position.line, position.character));
let offset = text.lastIndexOf('/*');
if (offset === -1) {
return;
}
return document.positionAt(offset);
}

function findClosingCommentAfterPosition(document: vscode.TextDocument, position: vscode.Position): vscode.Position | undefined {
let text = document.getText(new vscode.Range(position.line, position.character, document.lineCount - 1, document.lineAt(document.lineCount - 1).text.length));
let offset = text.indexOf('*/');
if (offset === -1) {
return;
}
offset += 2;
return document.positionAt(offset);
}

/**
* Returns node corresponding to given position in the given root node
*/
Expand Down

0 comments on commit 91416ff

Please sign in to comment.