未验证 提交 c7dbab59 编写于 作者: R Raymond Zhao 提交者: GitHub

Emmet create new html-matcher override instead of using LS (#113508)

上级 4b6280aa
......@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getHtmlNodeLS, offsetRangeToSelection, toLSTextDocument, validate } from './util';
import { parseMarkupDocument } from './parseMarkupDocument';
import { TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { getHtmlFlatNode, offsetRangeToSelection, validate } from './util';
import { getRootNode } from './parseDocument';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
let balanceOutStack: Array<vscode.Selection[]> = [];
let lastBalancedSelections: vscode.Selection[] = [];
......@@ -24,16 +24,16 @@ function balance(out: boolean) {
return;
}
const editor = vscode.window.activeTextEditor;
const document = toLSTextDocument(editor.document);
const htmlDocument = parseMarkupDocument(document);
if (!htmlDocument) {
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
const rangeFn = out ? getRangeToBalanceOut : getRangeToBalanceIn;
let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
const range = rangeFn(document, selection);
const range = rangeFn(document, rootNode, selection);
newSelections.push(range);
});
......@@ -57,17 +57,18 @@ function balance(out: boolean) {
lastBalancedSelections = editor.selections;
}
function getRangeToBalanceOut(document: LSTextDocument, selection: vscode.Selection): vscode.Selection {
const nodeToBalance = getHtmlNodeLS(document, selection.start, false);
function getRangeToBalanceOut(document: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Selection {
const offset = document.offsetAt(selection.start);
const nodeToBalance = getHtmlFlatNode(document.getText(), rootNode, offset, false);
if (!nodeToBalance) {
return selection;
}
if (!nodeToBalance.endTagStart || !nodeToBalance.startTagEnd) {
if (!nodeToBalance.open || !nodeToBalance.close) {
return offsetRangeToSelection(document, nodeToBalance.start, nodeToBalance.end);
}
const innerSelection = offsetRangeToSelection(document, nodeToBalance.startTagEnd, nodeToBalance.endTagStart);
const outerSelection = offsetRangeToSelection(document, nodeToBalance.start, nodeToBalance.end);
const innerSelection = offsetRangeToSelection(document, nodeToBalance.open.end, nodeToBalance.close.start);
const outerSelection = offsetRangeToSelection(document, nodeToBalance.open.start, nodeToBalance.close.end);
if (innerSelection.contains(selection) && !innerSelection.isEqual(selection)) {
return innerSelection;
......@@ -78,34 +79,35 @@ function getRangeToBalanceOut(document: LSTextDocument, selection: vscode.Select
return selection;
}
function getRangeToBalanceIn(document: LSTextDocument, selection: vscode.Selection): vscode.Selection {
const nodeToBalance = getHtmlNodeLS(document, selection.start, true);
function getRangeToBalanceIn(document: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Selection {
const offset = document.offsetAt(selection.start);
const nodeToBalance = getHtmlFlatNode(document.getText(), rootNode, offset, true);
if (!nodeToBalance) {
return selection;
}
const selectionStart = document.offsetAt(selection.start);
const selectionEnd = document.offsetAt(selection.end);
if (nodeToBalance.endTagStart !== undefined && nodeToBalance.startTagEnd !== undefined) {
if (nodeToBalance.open && nodeToBalance.close) {
const entireNodeSelected = selectionStart === nodeToBalance.start && selectionEnd === nodeToBalance.end;
const startInOpenTag = selectionStart > nodeToBalance.start && selectionStart < nodeToBalance.startTagEnd;
const startInCloseTag = selectionStart > nodeToBalance.endTagStart && selectionStart < nodeToBalance.end;
const startInOpenTag = selectionStart > nodeToBalance.open.start && selectionStart < nodeToBalance.open.end;
const startInCloseTag = selectionStart > nodeToBalance.close.start && selectionStart < nodeToBalance.close.end;
if (entireNodeSelected || startInOpenTag || startInCloseTag) {
return offsetRangeToSelection(document, nodeToBalance.startTagEnd, nodeToBalance.endTagStart);
return offsetRangeToSelection(document, nodeToBalance.open.end, nodeToBalance.close.start);
}
}
if (!nodeToBalance.children.length) {
if (!nodeToBalance.firstChild) {
return selection;
}
const firstChild = nodeToBalance.children[0];
const firstChild = nodeToBalance.firstChild;
if (selectionStart === firstChild.start
&& selectionEnd === firstChild.end
&& firstChild.endTagStart !== undefined
&& firstChild.startTagEnd !== undefined) {
return offsetRangeToSelection(document, firstChild.startTagEnd, firstChild.endTagStart);
&& firstChild.open
&& firstChild.close) {
return offsetRangeToSelection(document, firstChild.open.end, firstChild.close.start);
}
return offsetRangeToSelection(document, firstChild.start, firstChild.end);
......
......@@ -17,9 +17,9 @@ import { fetchEditPoint } from './editPoint';
import { fetchSelectItem } from './selectItem';
import { evaluateMathExpression } from './evaluateMathExpression';
import { incrementDecrement } from './incrementDecrement';
import { LANGUAGE_MODES, getMappingForIncludedLanguages, updateEmmetExtensionsPath, getPathBaseName, toLSTextDocument, getSyntaxes, getEmmetMode } from './util';
import { LANGUAGE_MODES, getMappingForIncludedLanguages, updateEmmetExtensionsPath, getPathBaseName, getSyntaxes, getEmmetMode } from './util';
import { reflectCssValue } from './reflectCssValue';
import { addFileToMarkupParseCache, removeFileFromMarkupParseCache } from './parseMarkupDocument';
import { addFileToParseCache, removeFileFromParseCache } from './parseDocument';
export function activateEmmetExtension(context: vscode.ExtensionContext) {
registerCompletionProviders(context);
......@@ -149,15 +149,17 @@ export function activateEmmetExtension(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument((e) => {
const emmetMode = getEmmetMode(e.languageId, []) ?? '';
if (getSyntaxes().markup.includes(emmetMode)) {
addFileToMarkupParseCache(toLSTextDocument(e));
const syntaxes = getSyntaxes();
if (syntaxes.markup.includes(emmetMode) || syntaxes.stylesheet.includes(emmetMode)) {
addFileToParseCache(e);
}
}));
context.subscriptions.push(vscode.workspace.onDidCloseTextDocument((e) => {
const emmetMode = getEmmetMode(e.languageId, []) ?? '';
if (getSyntaxes().markup.includes(emmetMode)) {
removeFileFromMarkupParseCache(toLSTextDocument(e));
const syntaxes = getSyntaxes();
if (syntaxes.markup.includes(emmetMode) || syntaxes.stylesheet.includes(emmetMode)) {
removeFileFromParseCache(e);
}
}));
}
......
......@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { toLSTextDocument, validate, getHtmlNodeLS, offsetRangeToSelection } from './util';
import { TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { validate, getHtmlFlatNode, offsetRangeToSelection } from './util';
import { getRootNode } from './parseDocument';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
export function matchTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
......@@ -13,11 +14,15 @@ export function matchTag() {
}
const editor = vscode.window.activeTextEditor;
const document = toLSTextDocument(editor.document);
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
let updatedSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
const updatedSelection = getUpdatedSelections(document, selection.start);
const updatedSelection = getUpdatedSelections(document, rootNode, selection.start);
if (updatedSelection) {
updatedSelections.push(updatedSelection);
}
......@@ -28,22 +33,21 @@ export function matchTag() {
}
}
function getUpdatedSelections(document: LSTextDocument, position: vscode.Position): vscode.Selection | undefined {
const currentNode = getHtmlNodeLS(document, position, true);
function getUpdatedSelections(document: vscode.TextDocument, rootNode: HtmlFlatNode, position: vscode.Position): vscode.Selection | undefined {
const offset = document.offsetAt(position);
const currentNode = getHtmlFlatNode(document.getText(), rootNode, offset, true);
if (!currentNode) {
return;
}
const offset = document.offsetAt(position);
// If no closing tag or cursor is between open and close tag, then no-op
if (currentNode.endTagStart === undefined
|| currentNode.startTagEnd === undefined
|| (offset > currentNode.startTagEnd && offset < currentNode.endTagStart)) {
// If no opening/closing tag or cursor is between open and close tag, then no-op
if (!currentNode.open
|| !currentNode.close
|| (offset > currentNode.open.end && offset < currentNode.close.start)) {
return;
}
// Place cursor inside the close tag if cursor is inside the open tag, else place it inside the open tag
const finalOffset = (offset <= currentNode.startTagEnd) ? currentNode.endTagStart + 2 : currentNode.start + 1;
const finalOffset = (offset <= currentNode.open.end) ? currentNode.close.start + 2 : currentNode.start + 1;
return offsetRangeToSelection(document, finalOffset, finalOffset);
}
......@@ -3,20 +3,22 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { HTMLDocument, TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { getLanguageService } from './util';
import { TextDocument } from 'vscode';
import { Node as FlatNode } from 'EmmetFlatNode';
import parse from '@emmetio/html-matcher';
import parseStylesheet from '@emmetio/css-parser';
import { isStyleSheet } from './util';
type Pair<K, V> = {
key: K;
value: V;
};
// Map(filename, Pair(fileVersion, parsedContent))
const _parseCache = new Map<string, Pair<number, HTMLDocument> | undefined>();
// Map(filename, Pair(fileVersion, rootNodeOfParsedContent))
const _parseCache = new Map<string, Pair<number, FlatNode> | undefined>();
export function parseMarkupDocument(document: LSTextDocument, useCache: boolean = true): HTMLDocument {
const languageService = getLanguageService();
const key = document.uri;
export function getRootNode(document: TextDocument, useCache: boolean): FlatNode {
const key = document.uri.toString();
const result = _parseCache.get(key);
const documentVersion = document.version;
if (useCache && result) {
......@@ -25,19 +27,20 @@ export function parseMarkupDocument(document: LSTextDocument, useCache: boolean
}
}
const parsedDocument = languageService.parseHTMLDocument(document);
const parseContent = isStyleSheet(document.languageId) ? parseStylesheet : parse;
const rootNode = parseContent(document.getText());
if (useCache) {
_parseCache.set(key, { key: documentVersion, value: parsedDocument });
_parseCache.set(key, { key: documentVersion, value: rootNode });
}
return parsedDocument;
return rootNode;
}
export function addFileToMarkupParseCache(document: LSTextDocument) {
const filename = document.uri;
export function addFileToParseCache(document: TextDocument) {
const filename = document.uri.toString();
_parseCache.set(filename, undefined);
}
export function removeFileFromMarkupParseCache(document: LSTextDocument) {
const filename = document.uri;
export function removeFileFromParseCache(document: TextDocument) {
const filename = document.uri.toString();
_parseCache.delete(filename);
}
......@@ -4,16 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { validate, getHtmlNodeLS, toLSTextDocument, offsetRangeToVsRange } from './util';
import { getRootNode } from './parseDocument';
import { validate, getHtmlFlatNode, offsetRangeToVsRange } from './util';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
export function removeTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
return;
}
const editor = vscode.window.activeTextEditor;
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
let finalRangesToRemove = editor.selections.reverse()
.reduce<vscode.Range[]>((prev, selection) =>
prev.concat(getRangesToRemove(editor.document, selection)), []);
prev.concat(getRangesToRemove(editor.document, rootNode, selection)), []);
return editor.edit(editBuilder => {
finalRangesToRemove.forEach(range => {
......@@ -27,26 +35,32 @@ export function removeTag() {
* It finds the node to remove based on the selection's start position
* and then removes that node, reindenting the content in between.
*/
function getRangesToRemove(document: vscode.TextDocument, selection: vscode.Selection): vscode.Range[] {
const lsDocument = toLSTextDocument(document);
const nodeToUpdate = getHtmlNodeLS(lsDocument, selection.start, true);
function getRangesToRemove(document: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Range[] {
const offset = document.offsetAt(selection.start);
const nodeToUpdate = getHtmlFlatNode(document.getText(), rootNode, offset, true);
if (!nodeToUpdate) {
return [];
}
const openTagRange = offsetRangeToVsRange(lsDocument, nodeToUpdate.start, nodeToUpdate.startTagEnd ?? nodeToUpdate.end);
let openTagRange: vscode.Range | undefined;
if (nodeToUpdate.open) {
openTagRange = offsetRangeToVsRange(document, nodeToUpdate.open.start, nodeToUpdate.open.end);
}
let closeTagRange: vscode.Range | undefined;
if (nodeToUpdate.endTagStart !== undefined) {
closeTagRange = offsetRangeToVsRange(lsDocument, nodeToUpdate.endTagStart, nodeToUpdate.end);
if (nodeToUpdate.close) {
closeTagRange = offsetRangeToVsRange(document, nodeToUpdate.close.start, nodeToUpdate.close.end);
}
let rangesToRemove = [openTagRange];
if (closeTagRange) {
const indentAmountToRemove = calculateIndentAmountToRemove(document, openTagRange, closeTagRange);
for (let i = openTagRange.start.line + 1; i < closeTagRange.start.line; i++) {
rangesToRemove.push(new vscode.Range(i, 0, i, indentAmountToRemove));
let rangesToRemove = [];
if (openTagRange) {
rangesToRemove.push(openTagRange);
if (closeTagRange) {
const indentAmountToRemove = calculateIndentAmountToRemove(document, openTagRange, closeTagRange);
for (let i = openTagRange.start.line + 1; i < closeTagRange.start.line; i++) {
rangesToRemove.push(new vscode.Range(i, 0, i, indentAmountToRemove));
}
rangesToRemove.push(closeTagRange);
}
rangesToRemove.push(closeTagRange);
}
return rangesToRemove;
}
......
......@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { validate, getEmmetMode, getEmmetConfiguration, toLSTextDocument, getHtmlNodeLS, offsetRangeToVsRange } from './util';
import { Node as LSNode, TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { validate, getEmmetMode, getEmmetConfiguration, getHtmlFlatNode, offsetRangeToVsRange } from './util';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
import { getRootNode } from './parseDocument';
export function splitJoinTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
......@@ -13,29 +14,30 @@ export function splitJoinTag() {
}
const editor = vscode.window.activeTextEditor;
const document = toLSTextDocument(editor.document);
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(editor.document, true);
if (!rootNode) {
return;
}
return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
const nodeToUpdate = getHtmlNodeLS(document, selection.start, true);
const documentText = document.getText();
const offset = document.offsetAt(selection.start);
const nodeToUpdate = getHtmlFlatNode(documentText, rootNode, offset, true);
if (nodeToUpdate) {
const textEdit = getRangesToReplace(document, nodeToUpdate);
if (textEdit) {
editBuilder.replace(textEdit.range, textEdit.newText);
}
editBuilder.replace(textEdit.range, textEdit.newText);
}
});
});
}
function getRangesToReplace(document: LSTextDocument, nodeToUpdate: LSNode): vscode.TextEdit | undefined {
function getRangesToReplace(document: vscode.TextDocument, nodeToUpdate: HtmlFlatNode): vscode.TextEdit {
let rangeToReplace: vscode.Range;
let textToReplaceWith: string;
if (!nodeToUpdate?.tag) {
return;
}
if (nodeToUpdate.endTagStart === undefined || nodeToUpdate.startTagEnd === undefined) {
if (!nodeToUpdate.open || !nodeToUpdate.close) {
// Split Tag
const nodeText = document.getText().substring(nodeToUpdate.start, nodeToUpdate.end);
const m = nodeText.match(/(\s*\/)?>$/);
......@@ -43,10 +45,10 @@ function getRangesToReplace(document: LSTextDocument, nodeToUpdate: LSNode): vsc
const start = m ? end - m[0].length : end;
rangeToReplace = offsetRangeToVsRange(document, start, end);
textToReplaceWith = `></${nodeToUpdate.tag}>`;
textToReplaceWith = `></${nodeToUpdate.name}>`;
} else {
// Join Tag
const start = nodeToUpdate.startTagEnd - 1;
const start = nodeToUpdate.open.end - 1;
const end = nodeToUpdate.end;
rangeToReplace = offsetRangeToVsRange(document, start, end);
textToReplaceWith = '/>';
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'EmmetFlatNode' {
export interface Node {
start: number
end: number
type: string
parent: Node | undefined
firstChild: Node | undefined
nextSibling: Node | undefined
previousSibling: Node | undefined
children: Node[]
}
export interface Token {
start: number
end: number
stream: string
toString(): string
}
export interface CssToken extends Token {
size: number
item(number: number): any
type: string
}
export interface HtmlToken extends Token {
value: string
}
export interface Attribute extends Token {
name: Token
value: Token
}
export interface HtmlNode extends Node {
name: string
open: Token | undefined
close: Token | undefined
parent: HtmlNode | undefined
firstChild: HtmlNode | undefined
nextSibling: HtmlNode | undefined
previousSibling: HtmlNode | undefined
children: HtmlNode[]
attributes: Attribute[]
}
export interface CssNode extends Node {
name: string
parent: CssNode | undefined
firstChild: CssNode | undefined
nextSibling: CssNode | undefined
previousSibling: CssNode | undefined
children: CssNode[]
}
export interface Rule extends CssNode {
selectorToken: Token
contentStartToken: Token
contentEndToken: Token
}
export interface Property extends CssNode {
valueToken: Token
separator: string
parent: Rule
terminatorToken: Token
separatorToken: Token
value: string
}
export interface Stylesheet extends Node {
comments: Token[]
}
}
......@@ -5,8 +5,10 @@
declare module '@emmetio/css-parser' {
import { BufferStream, Stylesheet } from 'EmmetNode';
import { Stylesheet as FlatStylesheet } from 'EmmetFlatNode';
function parseStylesheet(stream: BufferStream): Stylesheet;
function parseStylesheet(stream: string): FlatStylesheet;
export default parseStylesheet;
}
......
......@@ -5,8 +5,10 @@
declare module '@emmetio/html-matcher' {
import { BufferStream, HtmlNode } from 'EmmetNode';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
function parse(stream: BufferStream): HtmlNode;
function parse(stream: string): HtmlFlatNode;
export default parse;
}
......
......@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getHtmlNodeLS, toLSTextDocument, validate } from './util';
import { TextDocument as LSTextDocument, Node as LSNode } from 'vscode-html-languageservice';
import { getHtmlFlatNode, validate } from './util';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
import { getRootNode } from './parseDocument';
export function updateTag(tagName: string): Thenable<boolean> | undefined {
if (!validate(false) || !vscode.window.activeTextEditor) {
......@@ -13,9 +14,15 @@ export function updateTag(tagName: string): Thenable<boolean> | undefined {
}
const editor = vscode.window.activeTextEditor;
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
const rangesToUpdate = editor.selections.reverse()
.reduce<vscode.Range[]>((prev, selection) =>
prev.concat(getRangesToUpdate(editor, selection)), []);
prev.concat(getRangesToUpdate(document, selection, rootNode)), []);
return editor.edit(editBuilder => {
rangesToUpdate.forEach(range => {
......@@ -24,34 +31,25 @@ export function updateTag(tagName: string): Thenable<boolean> | undefined {
});
}
function getPositionFromOffset(offset: number | undefined, document: LSTextDocument): vscode.Position | undefined {
if (offset === undefined) {
return undefined;
}
const pos = document.positionAt(offset);
return new vscode.Position(pos.line, pos.character);
}
function getRangesFromNode(node: LSNode, document: LSTextDocument): vscode.Range[] {
const start = getPositionFromOffset(node.start, document)!;
const startTagEnd = getPositionFromOffset(node.startTagEnd, document);
const end = getPositionFromOffset(node.end, document)!;
const endTagStart = getPositionFromOffset(node.endTagStart, document);
function getRangesFromNode(node: HtmlFlatNode, document: vscode.TextDocument): vscode.Range[] {
let ranges: vscode.Range[] = [];
if (startTagEnd) {
if (node.open) {
const start = document.positionAt(node.open.start);
ranges.push(new vscode.Range(start.translate(0, 1),
start.translate(0, 1).translate(0, node.tag!.length ?? 0)));
start.translate(0, 1).translate(0, node.name.length)));
}
if (endTagStart) {
if (node.close) {
const endTagStart = document.positionAt(node.close.start);
const end = document.positionAt(node.close.end);
ranges.push(new vscode.Range(endTagStart.translate(0, 2), end.translate(0, -1)));
}
return ranges;
}
function getRangesToUpdate(editor: vscode.TextEditor, selection: vscode.Selection): vscode.Range[] {
const document = toLSTextDocument(editor.document);
const nodeToUpdate = getHtmlNodeLS(document, selection.start, true);
function getRangesToUpdate(document: vscode.TextDocument, selection: vscode.Selection, rootNode: HtmlFlatNode): vscode.Range[] {
const documentText = document.getText();
const offset = document.offsetAt(selection.start);
const nodeToUpdate = getHtmlFlatNode(documentText, rootNode, offset, true);
if (!nodeToUpdate) {
return [];
}
......
......@@ -7,13 +7,12 @@ import * as vscode from 'vscode';
import parse from '@emmetio/html-matcher';
import parseStylesheet from '@emmetio/css-parser';
import { Node, HtmlNode, CssToken, Property, Rule, Stylesheet } from 'EmmetNode';
import { Node as FlatNode, HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
import { DocumentStreamReader } from './bufferStream';
import * as EmmetHelper from 'vscode-emmet-helper';
import { Position as LSPosition, getLanguageService as getLanguageServiceInternal, LanguageService, LanguageServiceOptions, TextDocument as LSTextDocument, Node as LSNode } from 'vscode-html-languageservice';
import { parseMarkupDocument } from './parseMarkupDocument';
import { TextDocument as LSTextDocument } from 'vscode-languageserver-textdocument';
let _emmetHelper: typeof EmmetHelper;
let _languageService: LanguageService;
let _currentExtensionsPath: string | undefined = undefined;
let _homeDir: vscode.Uri | undefined;
......@@ -33,16 +32,6 @@ export function getEmmetHelper() {
return _emmetHelper;
}
export function getLanguageService(options?: LanguageServiceOptions): LanguageService {
if (!options) {
if (!_languageService) {
_languageService = getLanguageServiceInternal();
}
return _languageService;
}
return getLanguageServiceInternal(options);
}
/**
* Update Emmet Helper to use user snippets from the extensionsPath setting
*/
......@@ -323,7 +312,7 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
/**
* Returns node corresponding to given position in the given root node
*/
export function getNode(root: Node | undefined, position: vscode.Position, includeNodeBoundary: boolean) {
export function getNode(root: Node | undefined, position: vscode.Position, includeNodeBoundary: boolean): Node | null {
if (!root) {
return null;
}
......@@ -348,6 +337,46 @@ export function getNode(root: Node | undefined, position: vscode.Position, inclu
return foundNode;
}
export function getFlatNode(root: FlatNode | undefined, offset: number, includeNodeBoundary: boolean): FlatNode | undefined {
if (!root) {
return;
}
function getFlatNodeChild(child: FlatNode | undefined): FlatNode | undefined {
if (!child) {
return;
}
const nodeStart = child.start;
const nodeEnd = child.end;
if ((nodeStart < offset && nodeEnd > offset)
|| (includeNodeBoundary && nodeStart <= offset && nodeEnd >= offset)) {
return getFlatNodeChildren(child.children) ?? child;
}
else if ('close' in <any>child) {
// We have an HTML node in this case.
// In case this node is an invalid unpaired HTML node,
// we still want to search its children
const htmlChild = <HtmlFlatNode>child;
if (htmlChild.open && !htmlChild.close) {
return getFlatNodeChildren(htmlChild.children);
}
}
return;
}
function getFlatNodeChildren(children: FlatNode[]): FlatNode | undefined {
for (let i = 0; i < children.length; i++) {
const foundChild = getFlatNodeChild(children[i]);
if (foundChild) {
return foundChild;
}
}
return;
}
return getFlatNodeChildren(root.children);
}
export const allowedMimeTypesInScriptTag = ['text/html', 'text/plain', 'text/x-template', 'text/template', 'text/ng-template'];
/**
......@@ -380,37 +409,24 @@ export function getHtmlNode(document: vscode.TextDocument, root: Node | undefine
/**
* Finds the HTML node within an HTML document at a given position
*/
export function getHtmlNodeLS(document: LSTextDocument, position: vscode.Position, includeNodeBoundary: boolean): LSNode | undefined {
const documentText = document.getText();
const offset = document.offsetAt(position);
let selectionStartOffset = offset;
if (includeNodeBoundary && documentText.charAt(offset) === '<') {
selectionStartOffset++;
}
else if (includeNodeBoundary && documentText.charAt(offset) === '>') {
selectionStartOffset--;
}
return getHtmlNodeLSInternal(document, selectionStartOffset);
}
function getHtmlNodeLSInternal(document: LSTextDocument, offset: number, isInTemplateNode: boolean = false): LSNode | undefined {
const useCache = !isInTemplateNode;
const parsedDocument = parseMarkupDocument(document, useCache);
const currentNode: LSNode = parsedDocument.findNodeAt(offset);
if (!currentNode.tag) { return; }
export function getHtmlFlatNode(documentText: string, root: FlatNode | undefined, offset: number, includeNodeBoundary: boolean): HtmlFlatNode | undefined {
const currentNode: HtmlFlatNode | undefined = <HtmlFlatNode | undefined>getFlatNode(root, offset, includeNodeBoundary);
if (!currentNode) { return; }
const isTemplateScript = isNodeTemplateScriptLS(currentNode);
const isTemplateScript = currentNode.name === 'script' &&
(currentNode.attributes &&
currentNode.attributes.some(x => x.name.toString() === 'type'
&& allowedMimeTypesInScriptTag.includes(x.value.toString())));
if (isTemplateScript
&& currentNode.startTagEnd
&& offset > currentNode.startTagEnd
&& (!currentNode.endTagStart || offset < currentNode.endTagStart)) {
&& currentNode.open
&& offset > currentNode.open.end
&& (!currentNode.close || offset < currentNode.close.start)) {
// blank out the rest of the document and search for the node within
const documentText = document.getText();
const beforePadding = ' '.repeat(currentNode.startTagEnd);
const scriptBodyText = beforePadding + documentText.substring(currentNode.startTagEnd, currentNode.endTagStart ?? currentNode.end);
const scriptBodyDocument = LSTextDocument.create(document.uri, document.languageId, document.version, scriptBodyText);
const scriptBodyNode = getHtmlNodeLSInternal(scriptBodyDocument, offset, true);
const beforePadding = ' '.repeat(currentNode.open.end);
const endToUse = currentNode.close ? currentNode.close.start : currentNode.end;
const scriptBodyText = beforePadding + documentText.substring(currentNode.open.end, endToUse);
const innerRoot: HtmlFlatNode = parse(scriptBodyText);
const scriptBodyNode = getHtmlFlatNode(scriptBodyText, innerRoot, offset, includeNodeBoundary);
if (scriptBodyNode) {
scriptBodyNode.parent = currentNode;
currentNode.children.push(scriptBodyNode);
......@@ -420,33 +436,16 @@ function getHtmlNodeLSInternal(document: LSTextDocument, offset: number, isInTem
return currentNode;
}
/**
* Returns whether the node is a <script> node
* that we want to search through and parse for more potential HTML nodes
*/
function isNodeTemplateScriptLS(node: LSNode): boolean {
if (node.tag === 'script' && node.attributes && node.attributes['type']) {
let scriptType = node.attributes['type'];
scriptType = scriptType.substring(1, scriptType.length - 1);
return allowedMimeTypesInScriptTag.includes(scriptType);
}
return false;
}
function toVsPosition(position: LSPosition): vscode.Position {
return new vscode.Position(position.line, position.character);
}
export function offsetRangeToSelection(document: LSTextDocument, start: number, end: number): vscode.Selection {
export function offsetRangeToSelection(document: vscode.TextDocument, start: number, end: number): vscode.Selection {
const startPos = document.positionAt(start);
const endPos = document.positionAt(end);
return new vscode.Selection(toVsPosition(startPos), toVsPosition(endPos));
return new vscode.Selection(startPos, endPos);
}
export function offsetRangeToVsRange(document: LSTextDocument, start: number, end: number): vscode.Range {
export function offsetRangeToVsRange(document: vscode.TextDocument, start: number, end: number): vscode.Range {
const startPos = document.positionAt(start);
const endPos = document.positionAt(end);
return new vscode.Range(toVsPosition(startPos), toVsPosition(endPos));
return new vscode.Range(startPos, endPos);
}
/**
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册