更换ts为js

develop
yutent 2023-06-05 10:15:03 +00:00
parent 7d4010cdf6
commit a3dd345793
14 changed files with 419 additions and 819 deletions

26
src/cache.js Normal file
View File

@ -0,0 +1,26 @@
// Code from https://github.com/Microsoft/typescript-styled-plugin/blob/master/src/styled-template-language-service.ts
export class CompletionsCache {
_cachedCompletionsFile;
_cachedCompletionsPosition;
_cachedCompletionsContent;
_completions;
equalPositions(left, right) {
return left.line === right.line && left.character === right.character;
}
getCached(context, position) {
if (this._completions &&
context.fileName === this._cachedCompletionsFile &&
this._cachedCompletionsPosition &&
this.equalPositions(position, this._cachedCompletionsPosition) &&
context.getText() === this._cachedCompletionsContent) {
return this._completions;
}
return undefined;
}
updateCached(context, position, completions) {
this._cachedCompletionsFile = context.fileName;
this._cachedCompletionsPosition = position;
this._cachedCompletionsContent = context.getText();
this._completions = completions;
}
}

View File

@ -1,42 +0,0 @@
// Code from https://github.com/Microsoft/typescript-styled-plugin/blob/master/src/styled-template-language-service.ts
import { CompletionList, TextDocument, Position } from 'vscode'
export class CompletionsCache {
private _cachedCompletionsFile?: string
private _cachedCompletionsPosition?: Position
private _cachedCompletionsContent?: string
private _completions?: CompletionList
private equalPositions(left: Position, right: Position): boolean {
return left.line === right.line && left.character === right.character
}
public getCached(
context: TextDocument,
position: Position
): CompletionList | undefined {
if (
this._completions &&
context.fileName === this._cachedCompletionsFile &&
this._cachedCompletionsPosition &&
this.equalPositions(position, this._cachedCompletionsPosition) &&
context.getText() === this._cachedCompletionsContent
) {
return this._completions
}
return undefined
}
public updateCached(
context: TextDocument,
position: Position,
completions: any
) {
this._cachedCompletionsFile = context.fileName
this._cachedCompletionsPosition = position
this._cachedCompletionsContent = context.getText()
this._completions = completions
}
}

19
src/main.js Normal file
View File

@ -0,0 +1,19 @@
import { languages as Languages } from 'vscode';
import { HTMLCompletionItemProvider } from './providers/html';
import { CSSCompletionItemProvider, HTMLStyleCompletionItemProvider } from './providers/css';
import { HTMLHoverProvider, CSSHoverProvider } from './providers/hover';
import { CodeFormatterProvider } from './providers/formatting';
const selector = [
'typescriptreact',
'javascriptreact',
'typescript',
'javascript'
];
export function activate(Context) {
new CodeFormatterProvider();
Languages.registerCompletionItemProvider(selector, new HTMLCompletionItemProvider(), '<', '!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
Languages.registerHoverProvider(selector, new HTMLHoverProvider());
Languages.registerCompletionItemProvider(selector, new HTMLStyleCompletionItemProvider(), '!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
Languages.registerHoverProvider(selector, new CSSHoverProvider());
Languages.registerCompletionItemProvider(selector, new CSSCompletionItemProvider(), '!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
}

View File

@ -1,91 +0,0 @@
import {
languages as Languages,
ExtensionContext,
commands as Commands,
DocumentSelector
} from 'vscode'
import { HTMLCompletionItemProvider } from './providers/html'
import {
CSSCompletionItemProvider,
HTMLStyleCompletionItemProvider
} from './providers/css'
import { HTMLHoverProvider, CSSHoverProvider } from './providers/hover'
import { CodeFormatterProvider } from './providers/formatting'
const selector: DocumentSelector = [
'typescriptreact',
'javascriptreact',
'typescript',
'javascript'
]
export function activate(Context: ExtensionContext) {
new CodeFormatterProvider()
Languages.registerCompletionItemProvider(
selector,
new HTMLCompletionItemProvider(),
'<',
'!',
'.',
'}',
':',
'*',
'$',
']',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9'
)
Languages.registerHoverProvider(selector, new HTMLHoverProvider())
Languages.registerCompletionItemProvider(
selector,
new HTMLStyleCompletionItemProvider(),
'!',
'.',
'}',
':',
'*',
'$',
']',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9'
)
Languages.registerHoverProvider(selector, new CSSHoverProvider())
Languages.registerCompletionItemProvider(
selector,
new CSSCompletionItemProvider(),
'!',
'.',
'}',
':',
'*',
'$',
']',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9'
)
}

122
src/providers/css.js Normal file
View File

@ -0,0 +1,122 @@
import { getLanguageService as GetHTMLanguageService } from 'vscode-html-languageservice';
import { getSCSSLanguageService as GetSCSSLanguageService } from 'vscode-css-languageservice';
import * as emmet from 'vscode-emmet-helper';
import { GetEmmetConfiguration, MatchOffset, CreateVirtualDocument, GetLanguageRegions, GetRegionAtOffset, TranslateCompletionItems } from '../util';
import { CompletionsCache } from '../cache';
export class HTMLStyleCompletionItemProvider {
_cssLanguageService = GetSCSSLanguageService();
_HTMLanguageService = GetHTMLanguageService();
_expression = /(\/\*\s*html\s*\*\/\s*`|html\s*`)([^`]*)(`)/g;
_cache = new CompletionsCache();
provideCompletionItems(document, position, _token) {
const cached = this._cache.getCached(document, position);
if (cached) {
return cached;
}
const currentLine = document.lineAt(position.line);
const empty = {
isIncomplete: false,
items: []
};
if (currentLine.isEmptyOrWhitespace) {
return empty;
}
const currentLineText = currentLine.text.trim();
const currentOffset = document.offsetAt(position);
const documentText = document.getText();
const match = MatchOffset(this._expression, documentText, currentOffset);
if (!match) {
return empty;
}
// tslint:disable-next-line:no-magic-numbers
const matchContent = match[2];
const matchStartOffset = match.index + match[1].length;
const matchEndOffset = match.index + match[0].length;
const regions = GetLanguageRegions(this._HTMLanguageService, matchContent);
if (regions.length <= 0) {
return empty;
}
const region = GetRegionAtOffset(regions, currentOffset - matchStartOffset);
if (!region) {
return empty;
}
const virtualOffset = currentOffset - (matchStartOffset + region.start);
const virtualDocument = CreateVirtualDocument('css', region.content);
const stylesheet = this._cssLanguageService.parseStylesheet(virtualDocument);
const emmetResults = {
isIncomplete: true,
items: []
};
this._cssLanguageService.setCompletionParticipants([
emmet.getEmmetCompletionParticipants(virtualDocument, virtualDocument.positionAt(virtualOffset), 'css', GetEmmetConfiguration(), emmetResults)
]);
const completions = this._cssLanguageService.doComplete(virtualDocument, virtualDocument.positionAt(virtualOffset), stylesheet);
if (emmetResults.items.length) {
completions.items.push(...emmetResults.items);
completions.isIncomplete = true;
}
this._cache.updateCached(document, position, completions);
return {
isIncomplete: completions.isIncomplete,
items: TranslateCompletionItems(completions.items, currentLine)
};
}
resolveCompletionItem(item, _token) {
return item;
}
}
export class CSSCompletionItemProvider {
_CSSLanguageService = GetSCSSLanguageService();
_expression = /(\/\*\s*(css|less|scss|sass)\s*\*\/\s*`|css\s*`)([^`]*)(`)/g;
_cache = new CompletionsCache();
provideCompletionItems(document, position, _token) {
const cached = this._cache.getCached(document, position);
if (cached) {
return cached;
}
const currentLine = document.lineAt(position.line);
const empty = {
isIncomplete: false,
items: []
};
if (currentLine.isEmptyOrWhitespace) {
return empty;
}
const currentLineText = currentLine.text.trim();
const currentOffset = document.offsetAt(position);
const documentText = document.getText();
const match = MatchOffset(this._expression, documentText, currentOffset);
if (!match) {
return empty;
}
const dialect = match[2];
// tslint:disable-next-line:no-magic-numbers
const matchContent = match[3];
const matchStartOffset = match.index + match[1].length;
const matchEndOffset = match.index + match[0].length;
const matchPosition = document.positionAt(matchStartOffset);
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = CreateVirtualDocument(dialect, matchContent);
const vCss = this._CSSLanguageService.parseStylesheet(virtualDocument);
const emmetResults = {
isIncomplete: true,
items: []
};
this._CSSLanguageService.setCompletionParticipants([
emmet.getEmmetCompletionParticipants(virtualDocument, virtualDocument.positionAt(virtualOffset), dialect, GetEmmetConfiguration(), emmetResults)
]);
const completions = this._CSSLanguageService.doComplete(virtualDocument, virtualDocument.positionAt(virtualOffset), vCss);
if (emmetResults.items.length) {
completions.items.push(...emmetResults.items);
completions.isIncomplete = true;
}
this._cache.updateCached(document, position, completions);
return {
isIncomplete: completions.isIncomplete,
items: TranslateCompletionItems(completions.items, currentLine)
};
}
resolveCompletionItem(item, _token) {
return item;
}
}

View File

@ -1,218 +0,0 @@
import {
CompletionList,
CompletionItem,
TextDocument,
Position,
CancellationToken,
CompletionItemProvider
} from 'vscode'
import {
getLanguageService as GetHTMLanguageService,
LanguageService as HTMLanguageService,
CompletionList as HTMLCompletionList
} from 'vscode-html-languageservice'
import {
getSCSSLanguageService as GetSCSSLanguageService,
LanguageService as CSSLanguageService,
CompletionList as CSSCompletionList
} from 'vscode-css-languageservice'
import * as emmet from 'vscode-emmet-helper'
import {
GetEmmetConfiguration,
MatchOffset,
CreateVirtualDocument,
GetLanguageRegions,
GetRegionAtOffset,
TranslateCompletionItems
} from '../util'
import { CompletionsCache } from '../cache'
export class HTMLStyleCompletionItemProvider implements CompletionItemProvider {
private _cssLanguageService: CSSLanguageService = GetSCSSLanguageService()
private _HTMLanguageService: HTMLanguageService = GetHTMLanguageService()
private _expression = /(\/\*\s*html\s*\*\/\s*`|html\s*`)([^`]*)(`)/g
private _cache = new CompletionsCache()
public provideCompletionItems(
document: TextDocument,
position: Position,
_token: CancellationToken
): CompletionList {
const cached = this._cache.getCached(document, position)
if (cached) {
return cached
}
const currentLine = document.lineAt(position.line)
const empty = {
isIncomplete: false,
items: []
} as CompletionList
if (currentLine.isEmptyOrWhitespace) {
return empty
}
const currentLineText = currentLine.text.trim()
const currentOffset = document.offsetAt(position)
const documentText = document.getText()
const match = MatchOffset(this._expression, documentText, currentOffset)
if (!match) {
return empty
}
// tslint:disable-next-line:no-magic-numbers
const matchContent: string = match[2]
const matchStartOffset = match.index + match[1].length
const matchEndOffset = match.index + match[0].length
const regions = GetLanguageRegions(this._HTMLanguageService, matchContent)
if (regions.length <= 0) {
return empty
}
const region = GetRegionAtOffset(regions, currentOffset - matchStartOffset)
if (!region) {
return empty
}
const virtualOffset = currentOffset - (matchStartOffset + region.start)
const virtualDocument = CreateVirtualDocument('css', region.content)
const stylesheet = this._cssLanguageService.parseStylesheet(virtualDocument)
const emmetResults: HTMLCompletionList = {
isIncomplete: true,
items: []
}
this._cssLanguageService.setCompletionParticipants([
emmet.getEmmetCompletionParticipants(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
'css',
GetEmmetConfiguration(),
emmetResults
)
])
const completions = this._cssLanguageService.doComplete(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
stylesheet
)
if (emmetResults.items.length) {
completions.items.push(...emmetResults.items)
completions.isIncomplete = true
}
this._cache.updateCached(document, position, completions)
return {
isIncomplete: completions.isIncomplete,
items: TranslateCompletionItems(completions.items, currentLine)
} as CompletionList
}
public resolveCompletionItem?(
item: CompletionItem,
_token: CancellationToken
): CompletionItem | Thenable<CompletionItem> {
return item
}
}
export class CSSCompletionItemProvider implements CompletionItemProvider {
private _CSSLanguageService: CSSLanguageService = GetSCSSLanguageService()
private _expression =
/(\/\*\s*(css|less|scss|sass)\s*\*\/\s*`|css\s*`)([^`]*)(`)/g
private _cache = new CompletionsCache()
public provideCompletionItems(
document: TextDocument,
position: Position,
_token: CancellationToken
): CompletionList {
const cached = this._cache.getCached(document, position)
if (cached) {
return cached
}
const currentLine = document.lineAt(position.line)
const empty = {
isIncomplete: false,
items: []
} as CompletionList
if (currentLine.isEmptyOrWhitespace) {
return empty
}
const currentLineText = currentLine.text.trim()
const currentOffset = document.offsetAt(position)
const documentText = document.getText()
const match = MatchOffset(this._expression, documentText, currentOffset)
if (!match) {
return empty
}
const dialect = match[2]
// tslint:disable-next-line:no-magic-numbers
const matchContent: string = match[3]
const matchStartOffset = match.index + match[1].length
const matchEndOffset = match.index + match[0].length
const matchPosition = document.positionAt(matchStartOffset)
const virtualOffset = currentOffset - matchStartOffset
const virtualDocument = CreateVirtualDocument(dialect, matchContent)
const vCss = this._CSSLanguageService.parseStylesheet(virtualDocument)
const emmetResults: CSSCompletionList = {
isIncomplete: true,
items: []
}
this._CSSLanguageService.setCompletionParticipants([
emmet.getEmmetCompletionParticipants(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
dialect,
GetEmmetConfiguration(),
emmetResults
)
])
const completions = this._CSSLanguageService.doComplete(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
vCss
)
if (emmetResults.items.length) {
completions.items.push(...emmetResults.items)
completions.isIncomplete = true
}
this._cache.updateCached(document, position, completions)
return {
isIncomplete: completions.isIncomplete,
items: TranslateCompletionItems(completions.items, currentLine)
} as CompletionList
}
public resolveCompletionItem?(
item: CompletionItem,
_token: CancellationToken
): CompletionItem | Thenable<CompletionItem> {
return item
}
}

View File

@ -0,0 +1,40 @@
import { Range, WorkspaceEdit, workspace as Workspace, commands as Commands } from 'vscode';
import { getLanguageService as GetHTMLanguageService } from 'vscode-html-languageservice';
import { CreateVirtualDocument, TranslateHTMLTextEdits, Match } from '../util';
export class CodeFormatterProvider {
_expression = /(\/\*\s*html\s*\*\/\s*`|html\s*`)([^`]*)(`)/g;
document;
constructor() {
Commands.registerTextEditorCommand('editor.action.formatInlineHtml', this.format, this);
}
format(textEditor) {
this.document = textEditor.document;
var documentText = this.document.getText();
var match = Match(this._expression, documentText);
if (!match) {
return [];
}
// TODO - Refactor, This have been used multiple times thourgh out the
// TODO - extension.
var matchStartOffset = match.index + match[1].length;
var matchEndOffset = match.index + (match[2].length + match[3].length + 1);
var matchStartPosition = this.document.positionAt(matchStartOffset);
var matchEndPosition = this.document.positionAt(matchEndOffset);
var text = this.document.getText(new Range(matchStartPosition, matchEndPosition));
var vHTML = CreateVirtualDocument('html', text);
// TODO - Expose Formatting Options
const edits = TranslateHTMLTextEdits(GetHTMLanguageService().format(vHTML, null, {
indentInnerHtml: false,
preserveNewLines: true,
tabSize: textEditor.options.tabSize,
insertSpaces: textEditor.options.insertSpaces,
endWithNewline: true
}), matchStartPosition.line + 1);
Workspace.applyEdit(this.composeEdits(this.document.uri, edits));
}
composeEdits(uri, edits) {
var ws = new WorkspaceEdit();
ws.set(uri, edits);
return ws;
}
}

View File

@ -1,80 +0,0 @@
import {
Range,
TextDocument,
WorkspaceEdit,
workspace as Workspace,
TextEditor,
commands as Commands,
Uri,
TextEdit,
Position
} from 'vscode'
import {
getLanguageService as GetHTMLanguageService,
Position as HTMLPosition
} from 'vscode-html-languageservice'
import {
CreateVirtualDocument,
TranslateHTMLTextEdits,
Match,
GetLanguageRegions,
IEmbeddedRegion
} from '../util'
export class CodeFormatterProvider {
private _expression = /(\/\*\s*html\s*\*\/\s*`|html\s*`)([^`]*)(`)/g
private document: TextDocument
constructor() {
Commands.registerTextEditorCommand(
'editor.action.formatInlineHtml',
this.format,
this
)
}
public format(textEditor: TextEditor) {
this.document = textEditor.document
var documentText = this.document.getText()
var match = Match(this._expression, documentText)
if (!match) {
return []
}
// TODO - Refactor, This have been used multiple times thourgh out the
// TODO - extension.
var matchStartOffset = match.index + match[1].length
var matchEndOffset = match.index + (match[2].length + match[3].length + 1)
var matchStartPosition = this.document.positionAt(matchStartOffset)
var matchEndPosition = this.document.positionAt(matchEndOffset)
var text = this.document.getText(
new Range(matchStartPosition, matchEndPosition)
)
var vHTML = CreateVirtualDocument('html', text)
// TODO - Expose Formatting Options
const edits = TranslateHTMLTextEdits(
GetHTMLanguageService().format(vHTML, null, {
indentInnerHtml: false,
preserveNewLines: true,
tabSize: <number>textEditor.options.tabSize,
insertSpaces: <boolean>textEditor.options.insertSpaces,
endWithNewline: true
}),
matchStartPosition.line + 1
)
Workspace.applyEdit(this.composeEdits(this.document.uri, edits))
}
private composeEdits(uri: Uri, edits: TextEdit[]): WorkspaceEdit {
var ws = new WorkspaceEdit()
ws.set(uri, edits)
return ws
}
}

49
src/providers/hover.js Normal file
View File

@ -0,0 +1,49 @@
import { getLanguageService as GetHtmlLanguageService } from 'vscode-html-languageservice';
import { getCSSLanguageService as GetCssLanguageService } from 'vscode-css-languageservice';
import { CreateVirtualDocument, MatchOffset } from '../util';
export class HTMLHoverProvider {
_htmlLanguageService = GetHtmlLanguageService();
_cssLanguageService = GetCssLanguageService();
// private _expression = /(html\s*`)([^`]*)(`)/g
_expression = /(\/\*\s*html\s*\*\/\s*`|html\s*`)([^`]*)(`)/g;
provideHover(document, position, token) {
const currentOffset = document.offsetAt(position);
const documentText = document.getText();
const match = MatchOffset(this._expression, documentText, currentOffset);
if (!match) {
return null;
}
// tslint:disable-next-line:no-magic-numbers
const matchContent = match[2];
const matchStartOffset = match.index + match[1].length;
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = CreateVirtualDocument('html', matchContent);
const html = this._htmlLanguageService.parseHTMLDocument(virtualDocument);
const stylesheet = this._cssLanguageService.parseStylesheet(virtualDocument);
const hover = this._htmlLanguageService.doHover(virtualDocument, virtualDocument.positionAt(virtualOffset), html) ||
this._cssLanguageService.doHover(virtualDocument, virtualDocument.positionAt(virtualOffset), stylesheet);
return hover;
}
}
export class CSSHoverProvider {
_htmlLanguageService = GetHtmlLanguageService();
_cssLanguageService = GetCssLanguageService();
_expression = /(\/\*\s*(css|less|scss)\s*\*\/\s*`|css\s*`)([^`]*)(`)/g;
provideHover(document, position, token) {
const currentOffset = document.offsetAt(position);
const documentText = document.getText();
const match = MatchOffset(this._expression, documentText, currentOffset);
if (!match) {
return null;
}
const dialect = match[2];
// tslint:disable-next-line:no-magic-numbers
const matchContent = match[3];
const matchStartOffset = match.index + match[1].length;
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = CreateVirtualDocument(dialect, matchContent);
const stylesheet = this._cssLanguageService.parseStylesheet(virtualDocument);
const hover = this._cssLanguageService.doHover(virtualDocument, virtualDocument.positionAt(virtualOffset), stylesheet);
return hover;
}
}

View File

@ -1,98 +0,0 @@
import {
HoverProvider,
TextDocument,
Position,
CancellationToken,
Hover
} from 'vscode'
import {
getLanguageService as GetHtmlLanguageService,
LanguageService as HtmlLanguageService,
CompletionList as HtmlCompletionList
} from 'vscode-html-languageservice'
import {
getCSSLanguageService as GetCssLanguageService,
LanguageService as CssLanguageService
} from 'vscode-css-languageservice'
import { CreateVirtualDocument, MatchOffset } from '../util'
export class HTMLHoverProvider implements HoverProvider {
private _htmlLanguageService: HtmlLanguageService = GetHtmlLanguageService()
private _cssLanguageService: CssLanguageService = GetCssLanguageService()
// private _expression = /(html\s*`)([^`]*)(`)/g
private _expression = /(\/\*\s*html\s*\*\/\s*`|html\s*`)([^`]*)(`)/g
provideHover(
document: TextDocument,
position: Position,
token: CancellationToken
): Hover {
const currentOffset = document.offsetAt(position)
const documentText = document.getText()
const match = MatchOffset(this._expression, documentText, currentOffset)
if (!match) {
return null
}
// tslint:disable-next-line:no-magic-numbers
const matchContent: string = match[2]
const matchStartOffset = match.index + match[1].length
const virtualOffset = currentOffset - matchStartOffset
const virtualDocument = CreateVirtualDocument('html', matchContent)
const html = this._htmlLanguageService.parseHTMLDocument(virtualDocument)
const stylesheet = this._cssLanguageService.parseStylesheet(virtualDocument)
const hover =
this._htmlLanguageService.doHover(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
html
) ||
this._cssLanguageService.doHover(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
stylesheet
)
return hover as Hover
}
}
export class CSSHoverProvider implements HoverProvider {
private _htmlLanguageService: HtmlLanguageService = GetHtmlLanguageService()
private _cssLanguageService: CssLanguageService = GetCssLanguageService()
private _expression = /(\/\*\s*(css|less|scss)\s*\*\/\s*`|css\s*`)([^`]*)(`)/g
provideHover(
document: TextDocument,
position: Position,
token: CancellationToken
): Hover {
const currentOffset = document.offsetAt(position)
const documentText = document.getText()
const match = MatchOffset(this._expression, documentText, currentOffset)
if (!match) {
return null
}
const dialect = match[2]
// tslint:disable-next-line:no-magic-numbers
const matchContent: string = match[3]
const matchStartOffset = match.index + match[1].length
const virtualOffset = currentOffset - matchStartOffset
const virtualDocument = CreateVirtualDocument(dialect, matchContent)
const stylesheet = this._cssLanguageService.parseStylesheet(virtualDocument)
const hover = this._cssLanguageService.doHover(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
stylesheet
)
return hover as Hover
}
}

59
src/providers/html.js Normal file
View File

@ -0,0 +1,59 @@
import { getLanguageService as GetHTMLanguageService } from 'vscode-html-languageservice';
import * as emmet from 'vscode-emmet-helper';
import { GetEmmetConfiguration, MatchOffset, CreateVirtualDocument, TranslateCompletionItems } from '../util';
import { CompletionsCache } from '../cache';
export class HTMLCompletionItemProvider {
_htmlLanguageService = GetHTMLanguageService();
_expression = /(\/\*\s*html\s*\*\/\s*`|html\s*`)([^`]*)(`)/g;
// private _expression = /(html\s*`)([^`]*)(`)/g
_cache = new CompletionsCache();
provideCompletionItems(document, position, token) {
const cached = this._cache.getCached(document, position);
if (cached) {
return cached;
}
const currentLine = document.lineAt(position.line);
const empty = {
isIncomplete: false,
items: []
};
if (currentLine.isEmptyOrWhitespace) {
return empty;
}
const currentLineText = currentLine.text.trim();
const currentOffset = document.offsetAt(position);
const documentText = document.getText();
const match = MatchOffset(this._expression, documentText, currentOffset);
if (!match) {
return empty;
}
// tslint:disable-next-line:no-magic-numbers
const matchContent = match[2];
const matchStartOffset = match.index + match[1].length;
const matchEndOffset = match.index + match[0].length;
const matchPosition = document.positionAt(matchStartOffset);
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = CreateVirtualDocument('html', matchContent);
const vHtml = this._htmlLanguageService.parseHTMLDocument(virtualDocument);
const emmetResults = {
isIncomplete: true,
items: []
};
this._htmlLanguageService.setCompletionParticipants([
emmet.getEmmetCompletionParticipants(virtualDocument, virtualDocument.positionAt(virtualOffset), 'html', GetEmmetConfiguration(), emmetResults)
]);
const completions = this._htmlLanguageService.doComplete(virtualDocument, virtualDocument.positionAt(virtualOffset), vHtml);
if (emmetResults.items.length) {
completions.items.push(...emmetResults.items);
completions.isIncomplete = true;
}
this._cache.updateCached(document, position, completions);
return {
isIncomplete: completions.isIncomplete,
items: TranslateCompletionItems(completions.items, currentLine, true)
};
}
resolveCompletionItem(item, token) {
return item;
}
}

View File

@ -1,111 +0,0 @@
import {
CompletionList,
CompletionItem,
TextDocument,
Position,
CancellationToken,
CompletionItemProvider
} from 'vscode'
import {
getLanguageService as GetHTMLanguageService,
LanguageService as HTMLanguageService,
CompletionList as HTMLCompletionList
} from 'vscode-html-languageservice'
import * as emmet from 'vscode-emmet-helper'
import {
GetEmmetConfiguration,
MatchOffset,
CreateVirtualDocument,
TranslateCompletionItems
} from '../util'
import { CompletionsCache } from '../cache'
export class HTMLCompletionItemProvider implements CompletionItemProvider {
private _htmlLanguageService: HTMLanguageService = GetHTMLanguageService()
private _expression = /(\/\*\s*html\s*\*\/\s*`|html\s*`)([^`]*)(`)/g
// private _expression = /(html\s*`)([^`]*)(`)/g
private _cache = new CompletionsCache()
public provideCompletionItems(
document: TextDocument,
position: Position,
token: CancellationToken
): CompletionList {
const cached = this._cache.getCached(document, position)
if (cached) {
return cached
}
const currentLine = document.lineAt(position.line)
const empty = {
isIncomplete: false,
items: []
} as CompletionList
if (currentLine.isEmptyOrWhitespace) {
return empty
}
const currentLineText = currentLine.text.trim()
const currentOffset = document.offsetAt(position)
const documentText = document.getText()
const match = MatchOffset(this._expression, documentText, currentOffset)
if (!match) {
return empty
}
// tslint:disable-next-line:no-magic-numbers
const matchContent: string = match[2]
const matchStartOffset = match.index + match[1].length
const matchEndOffset = match.index + match[0].length
const matchPosition = document.positionAt(matchStartOffset)
const virtualOffset = currentOffset - matchStartOffset
const virtualDocument = CreateVirtualDocument('html', matchContent)
const vHtml = this._htmlLanguageService.parseHTMLDocument(virtualDocument)
const emmetResults: HTMLCompletionList = {
isIncomplete: true,
items: []
}
this._htmlLanguageService.setCompletionParticipants([
emmet.getEmmetCompletionParticipants(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
'html',
GetEmmetConfiguration(),
emmetResults
)
])
const completions = this._htmlLanguageService.doComplete(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
vHtml
)
if (emmetResults.items.length) {
completions.items.push(...emmetResults.items)
completions.isIncomplete = true
}
this._cache.updateCached(document, position, completions)
return {
isIncomplete: completions.isIncomplete,
items: TranslateCompletionItems(completions.items, currentLine, true)
} as CompletionList
}
public resolveCompletionItem?(
item: CompletionItem,
token: CancellationToken
): CompletionItem | Thenable<CompletionItem> {
return item
}
}

104
src/util.js Normal file
View File

@ -0,0 +1,104 @@
import { workspace, TextEdit, Position, Range } from 'vscode';
import { TextDocument as HTMLTextDocument, TokenType as HTMLTokenType } from 'vscode-html-languageservice';
export function GetEmmetConfiguration() {
const emmetConfig = workspace.getConfiguration('emmet');
return {
useNewEmmet: true,
showExpandedAbbreviation: emmetConfig.showExpandedAbbreviation,
showAbbreviationSuggestions: emmetConfig.showAbbreviationSuggestions,
syntaxProfiles: emmetConfig.syntaxProfiles,
variables: emmetConfig.variables
};
}
export function NotNull(input) {
if (!input) {
return {};
}
return input;
}
export function MatchOffset(regex, data, offset) {
regex.exec(null);
let match;
while ((match = regex.exec(data)) !== null) {
if (offset > match.index + match[1].length &&
offset < match.index + match[0].length) {
return match;
}
}
return null;
}
export function Match(regex, data) {
regex.exec(null);
let match;
while ((match = regex.exec(data)) !== null) {
return match;
}
return null;
}
export function GetLanguageRegions(service, data) {
const scanner = service.createScanner(data);
const regions = [];
let tokenType;
while ((tokenType = scanner.scan()) !== HTMLTokenType.EOS) {
switch (tokenType) {
case HTMLTokenType.Styles:
regions.push({
languageId: 'css',
start: scanner.getTokenOffset(),
end: scanner.getTokenEnd(),
length: scanner.getTokenLength(),
content: scanner.getTokenText()
});
break;
default:
break;
}
}
return regions;
}
export function GetRegionAtOffset(regions, offset) {
for (let region of regions) {
if (region.start <= offset) {
if (offset <= region.end) {
return region;
}
}
else {
break;
}
}
return null;
}
export function TranslateHTMLTextEdits(input, offset) {
return input.map((item) => {
const startPosition = new Position(item.range.start.line + offset, item.range.start.character);
const endPosition = new Position(item.range.end.line + offset - 1, item.range.end.character);
const itemRange = new Range(startPosition, endPosition);
return new TextEdit(itemRange, item.newText);
});
}
export function TranslateCompletionItems(items, line, expand = false) {
return items.map((item) => {
const result = item;
const range = new Range(new Position(line.lineNumber, result.textEdit.range.start.character), new Position(line.lineNumber, result.textEdit.range.end.character));
result.textEdit = null;
// @ts-ignore - setting range for intellisense to show results properly
result.range = range;
if (expand) {
// i use this to both expand html abbreviations and auto complete tags
result.command = {
title: 'Emmet Expand Abbreviation',
command: 'editor.emmet.action.expandAbbreviation'
};
}
return result;
});
}
export function CreateVirtualDocument(
// context: TextDocument | HTMLTextDocument,
languageId,
// position: Position | HtmlPosition,
content) {
const doc = HTMLTextDocument.create(`embedded://document.${languageId}`, languageId, 1, content);
return doc;
}

View File

@ -1,179 +0,0 @@
import {
workspace,
TextLine,
TextEdit,
Position,
Range,
CompletionItem,
Command
} from 'vscode'
import {
TextDocument as HTMLTextDocument,
LanguageService,
TokenType as HTMLTokenType,
TextEdit as HTMLTextEdit
} from 'vscode-html-languageservice'
import { EmmetConfiguration } from 'vscode-emmet-helper'
export function GetEmmetConfiguration(): EmmetConfiguration {
const emmetConfig = workspace.getConfiguration('emmet')
return {
useNewEmmet: true,
showExpandedAbbreviation: emmetConfig.showExpandedAbbreviation,
showAbbreviationSuggestions: emmetConfig.showAbbreviationSuggestions,
syntaxProfiles: emmetConfig.syntaxProfiles,
variables: emmetConfig.variables
} as EmmetConfiguration
}
export function NotNull<T>(input: any): T {
if (!input) {
return {} as T
}
return input as T
}
export function MatchOffset(
regex: RegExp,
data: string,
offset: number
): RegExpMatchArray {
regex.exec(null)
let match: RegExpExecArray
while ((match = regex.exec(data)) !== null) {
if (
offset > match.index + match[1].length &&
offset < match.index + match[0].length
) {
return match
}
}
return null
}
export function Match(regex: RegExp, data: string): RegExpMatchArray {
regex.exec(null)
let match: RegExpExecArray
while ((match = regex.exec(data)) !== null) {
return match
}
return null
}
export function GetLanguageRegions(
service: LanguageService,
data: string
): IEmbeddedRegion[] {
const scanner = service.createScanner(data)
const regions: IEmbeddedRegion[] = []
let tokenType: HTMLTokenType
while ((tokenType = scanner.scan()) !== HTMLTokenType.EOS) {
switch (tokenType) {
case HTMLTokenType.Styles:
regions.push({
languageId: 'css',
start: scanner.getTokenOffset(),
end: scanner.getTokenEnd(),
length: scanner.getTokenLength(),
content: scanner.getTokenText()
})
break
default:
break
}
}
return regions
}
export function GetRegionAtOffset(
regions: IEmbeddedRegion[],
offset: number
): IEmbeddedRegion {
for (let region of regions) {
if (region.start <= offset) {
if (offset <= region.end) {
return region
}
} else {
break
}
}
return null
}
export function TranslateHTMLTextEdits(
input: HTMLTextEdit[],
offset: number
): TextEdit[] {
return input.map((item: HTMLTextEdit) => {
const startPosition = new Position(
item.range.start.line + offset,
item.range.start.character
)
const endPosition = new Position(
item.range.end.line + offset - 1,
item.range.end.character
)
const itemRange = new Range(startPosition, endPosition)
return new TextEdit(itemRange, item.newText)
})
}
export function TranslateCompletionItems(
items,
line: TextLine,
expand: boolean = false
): CompletionItem[] {
return items.map((item: CompletionItem) => {
const result = item as CompletionItem
const range = new Range(
new Position(line.lineNumber, result.textEdit.range.start.character),
new Position(line.lineNumber, result.textEdit.range.end.character)
)
result.textEdit = null
// @ts-ignore - setting range for intellisense to show results properly
result.range = range
if (expand) {
// i use this to both expand html abbreviations and auto complete tags
result.command = {
title: 'Emmet Expand Abbreviation',
command: 'editor.emmet.action.expandAbbreviation'
} as Command
}
return result
})
}
export function CreateVirtualDocument(
// context: TextDocument | HTMLTextDocument,
languageId: string,
// position: Position | HtmlPosition,
content: string
): HTMLTextDocument {
const doc = HTMLTextDocument.create(
`embedded://document.${languageId}`,
languageId,
1,
content
)
return doc
}
export interface IEmbeddedRegion {
languageId: string
start: number
end: number
length: number
content: string
}