From 91416ffab8a59b6f5a52abef6ff7920a5d067c1f Mon Sep 17 00:00:00 2001 From: Gus Hurovich Date: Mon, 26 Mar 2018 18:14:28 -0700 Subject: [PATCH] Stylesheet partial parse (#46376) * 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 --- .../emmet/src/defaultCompletionProvider.ts | 4 +- extensions/emmet/src/util.ts | 99 +++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/extensions/emmet/src/defaultCompletionProvider.ts b/extensions/emmet/src/defaultCompletionProvider.ts index 458d1fc6d4320..54aa40999d12b 100644 --- a/extensions/emmet/src/defaultCompletionProvider.ts +++ b/extensions/emmet/src/defaultCompletionProvider.ts @@ -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 { @@ -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; } diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts index e9f21c463aeac..4fed8eb00d26e 100644 --- a/extensions/emmet/src/util.ts +++ b/extensions/emmet/src/util.ts @@ -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 */