master
yutent 2023-03-03 12:07:45 +08:00
commit cec2620c5f
22 changed files with 1254 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
**/*.vsix
**/*.js.map
node_modules
client
.vscode
.DS_STORE

10
.prettierrc.yaml Normal file
View File

@ -0,0 +1,10 @@
jsxBracketSameLine: true
jsxSingleQuote: true
semi: false
singleQuote: true
printWidth: 80
useTabs: false
tabWidth: 2
trailingComma: none
bracketSpacing: true
arrowParens: avoid

7
.vscodeignore Normal file
View File

@ -0,0 +1,7 @@
**/*.ts
**/tsconfig.json
server/**
tests/**
.vscode/**
.gitignore
.git

3
CHANGELOG.md Normal file
View File

@ -0,0 +1,3 @@
# Changelog
## v1.0.0

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Ahmed Tarek
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2
README.md Normal file
View File

@ -0,0 +1,2 @@
## string-html-css
一个高亮js代码中的 html/css/scss/sass/less的字符串, 并支持emmet.

BIN
assets/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

127
package.json Normal file
View File

@ -0,0 +1,127 @@
{
"name": "string-html-css",
"displayName": "string-html-css",
"description": "一个高亮js代码中的 html/css/scss/sass/less的字符串, 并支持emmet.",
"version": "1.0.0",
"publisher": "yutent",
"license": "MIT",
"icon": "docs/logo.png",
"bugs": {
"url": "https://github.com/yutent/vscode-string-html/issues"
},
"keywords": [
"html",
"css",
"template",
"polymer",
"lit-html"
],
"repository": {
"type": "git",
"url": "https://github.com/yutent/vscode-string-html"
},
"engines": {
"vscode": "^1.22.0"
},
"scripts": {
"compile": "npx tsc -p .",
"watch:compile": "npx tsc --watch -p .",
"package": "npx vsce package"
},
"categories": [
"Programming Languages"
],
"activationEvents": [
"onLanguage:javascript",
"onLanguage:typescript",
"onLanguage:javascriptreact",
"onLanguage:typescriptreact"
],
"main": "./dist/main.js",
"contributes": {
"commands": [
{
"command": "editor.action.formatInlineHtml",
"title": "Format Inline HTML/CSS"
}
],
"grammars": [
{
"injectTo": [
"source.js",
"source.js.jsx",
"source.jsx",
"source.ts",
"source.ts.tsx",
"source.tsx"
],
"scopeName": "es6.inline.html",
"path": "./syntaxes/es6.inline.html.json",
"embeddedLanguages": {
"meta.embedded.block.html": "html",
"meta.template.expression.ts": "typescript"
}
},
{
"injectTo": [
"source.js",
"source.js.jsx",
"source.jsx",
"source.ts",
"source.ts.tsx",
"source.tsx"
],
"scopeName": "es6.inline.css",
"path": "./syntaxes/es6.inline.css.json",
"embeddedLanguages": {
"meta.embedded.block.css": "css",
"meta.template.expression.ts": "typescript"
}
},
{
"injectTo": [
"source.js",
"source.js.jsx",
"source.jsx",
"source.ts",
"source.ts.tsx",
"source.tsx"
],
"scopeName": "es6.inline.scss",
"path": "./syntaxes/es6.inline.scss.json",
"embeddedLanguages": {
"meta.embedded.block.css": "scss",
"meta.template.expression.ts": "typescript"
}
},
{
"injectTo": [
"source.js",
"source.js.jsx",
"source.jsx",
"source.ts",
"source.ts.tsx",
"source.tsx"
],
"scopeName": "es6.inline.less",
"path": "./syntaxes/es6.inline.less.json",
"embeddedLanguages": {
"meta.embedded.block.css": "less",
"meta.template.expression.ts": "typescript"
}
}
]
},
"devDependencies": {
"@types/node": "7.0.43",
"@types/vscode": "^1.22.0",
"typescript": "^3.2.2",
"vsce": "^1.102.0",
"vscode-languageserver-types": "^3.6.0"
},
"dependencies": {
"vscode-css-languageservice": "^3.0.7",
"vscode-emmet-helper": "^1.2.0",
"vscode-html-languageservice": "^2.1.1"
}
}

42
src/cache.ts Normal file
View File

@ -0,0 +1,42 @@
// 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: CompletionList
) {
this._cachedCompletionsFile = context.fileName
this._cachedCompletionsPosition = position
this._cachedCompletionsContent = context.getText()
this._completions = completions
}
}

91
src/main.ts Normal file
View File

@ -0,0 +1,91 @@
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'
)
}

217
src/providers/css.ts Normal file
View File

@ -0,0 +1,217 @@
import {
CompletionList,
CompletionItem,
TextDocument,
Position,
CancellationToken,
CompletionItemProvider
} from 'vscode'
import {
getLanguageService as GetHTMLanguageService,
LanguageService as HTMLanguageService,
CompletionList as HTMLCompletionList
} from 'vscode-html-languageservice'
import {
getCSSLanguageService as GetCSSLanguageService,
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 = GetCSSLanguageService()
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 as CompletionList)
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 = GetCSSLanguageService()
private _expression = /(\/\*\s*(css|less|scss)\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 as CompletionList)
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,81 @@
import {
Range,
TextDocument,
WorkspaceEdit,
workspace as Workspace,
TextEditor,
commands as Commands,
Uri,
TextEdit,
Position
} from 'vscode'
import {
getLanguageService as GetHTMLanguageService,
TextDocument as HTMLTextDocument,
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
}
}

98
src/providers/hover.ts Normal file
View File

@ -0,0 +1,98 @@
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
}
}

111
src/providers/html.ts Normal file
View File

@ -0,0 +1,111 @@
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 as CompletionList)
return {
isIncomplete: completions.isIncomplete,
items: TranslateCompletionItems(completions.items, currentLine, true)
} as CompletionList
}
public resolveCompletionItem?(
item: CompletionItem,
token: CancellationToken
): CompletionItem | Thenable<CompletionItem> {
return item
}
}

176
src/util.ts Normal file
View File

@ -0,0 +1,176 @@
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
}

View File

@ -0,0 +1,41 @@
{
"fileTypes": [
"js",
"jsx",
"ts",
"tsx"
],
"injectionSelector": "L:source.js -comment -string, L:source.jsx -comment -string, L:source.ts -comment -string, L:source.tsx -comment -string",
"patterns": [
{
"contentName": "meta.embedded.block.css",
"begin": "(?x)(\\s*?(\\w+\\.)?(?:css|\/\\*\\s*css\\s*\\*\/)\\s*)(`)",
"beginCaptures": {
"0": {
"name": "string.template.ts, punctuation.definition.string.template.begin.ts"
},
"1": {
"name": "entity.name.function.tagged-template.ts"
}
},
"end": "(`)",
"endCaptures": {
"0": {
"name": "string.template.ts, punctuation.definition.string.template.end.ts"
}
},
"patterns": [
{
"include": "source.ts#template-substitution-element"
},
{
"include": "source.css"
}
]
},
{
"include": "source.ts#template-substitution-element"
}
],
"scopeName": "es6.inline.css"
}

View File

@ -0,0 +1,51 @@
{
"fileTypes": [
"js",
"jsx",
"ts",
"tsx"
],
"injectionSelector": "L:source.js -comment -string, L:source.jsx -comment -string, L:source.ts -comment -string, L:source.tsx -comment -string",
"injections": {
"L:source": {
"patterns": [
{
"match": "<",
"name": "invalid.illegal.bad-angle-bracket.html"
}
]
}
},
"patterns": [
{
"contentName": "meta.embedded.block.html",
"begin": "(?x)(\\s*?(\\w+\\.)?(?:html|\/\\*\\s*html\\s*\\*\/)\\s*)(`)",
"beginCaptures": {
"0": {
"name": "string.template.ts, punctuation.definition.string.template.begin.ts"
},
"1": {
"name": "entity.name.function.tagged-template.ts"
}
},
"end": "(`)",
"endCaptures": {
"0": {
"name": "string.template.ts, punctuation.definition.string.template.end.ts"
}
},
"patterns": [
{
"include": "source.ts#template-substitution-element"
},
{
"include": "text.html.basic"
}
]
},
{
"include": "source.ts#template-substitution-element"
}
],
"scopeName": "es6.inline.html"
}

View File

@ -0,0 +1,41 @@
{
"fileTypes": [
"js",
"jsx",
"ts",
"tsx"
],
"injectionSelector": "L:source.js -comment -string, L:source.jsx -comment -string, L:source.ts -comment -string, L:source.tsx -comment -string",
"patterns": [
{
"contentName": "meta.embedded.block.css",
"begin": "(?x)(\\s*?(\\w+\\.)?(?:\/\\*\\s*less\\s*\\*\/)\\s*)(`)",
"beginCaptures": {
"0": {
"name": "string.template.ts, punctuation.definition.string.template.begin.ts"
},
"1": {
"name": "entity.name.function.tagged-template.ts"
}
},
"end": "(`)",
"endCaptures": {
"0": {
"name": "string.template.ts, punctuation.definition.string.template.end.ts"
}
},
"patterns": [
{
"include": "source.ts#template-substitution-element"
},
{
"include": "source.css.less"
}
]
},
{
"include": "source.ts#template-substitution-element"
}
],
"scopeName": "es6.inline.less"
}

View File

@ -0,0 +1,41 @@
{
"fileTypes": [
"js",
"jsx",
"ts",
"tsx"
],
"injectionSelector": "L:source.js -comment -string, L:source.jsx -comment -string, L:source.ts -comment -string, L:source.tsx -comment -string",
"patterns": [
{
"contentName": "meta.embedded.block.css",
"begin": "(?x)(\\s*?(\\w+\\.)?(?:\/\\*\\s*scss\\s*\\*\/)\\s*)(`)",
"beginCaptures": {
"0": {
"name": "string.template.ts, punctuation.definition.string.template.begin.ts"
},
"1": {
"name": "entity.name.function.tagged-template.ts"
}
},
"end": "(`)",
"endCaptures": {
"0": {
"name": "string.template.ts, punctuation.definition.string.template.end.ts"
}
},
"patterns": [
{
"include": "source.ts#template-substitution-element"
},
{
"include": "source.css.scss"
}
]
},
{
"include": "source.ts#template-substitution-element"
}
],
"scopeName": "es6.inline.scss"
}

77
tests/index.js Normal file
View File

@ -0,0 +1,77 @@
function html()
{
html`
<style>
:host {
display: block;
}
</style>
<body>
<input type="button" @click=${(e) => this.click(e)} value="deadmau5 🐭" />
<div></div>
<div></div>
<div></div>
<h3>${['❤️', '💛', '💚', '💙', '💜', '🖤']}</h3>
</body>
`;
/* html */`
<div></div>
<div></div>
<div></div>
<div></div>
<div>
<p></p>
</div>
`;
bug(/* html*/`
<div></div>
`);
bug(html`
<div></div>
`);
}
function bug()
{
html`div...`;
}
function css()
{
css`
:host {
display: block;
}
`;
/* css */`
:host {
display: block;
height: 50px;
}
`;
/* css */`
:host {
display: block;
}
`;
bug(/*css */`
:host {
display: block;
}
`)
bug(css`
:host {
display: block;
}
`)
}

11
tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"sourceMap": false,
"outDir": "dist",
"rootDir": "src",
"lib": ["es2016"]
},
"exclude": ["node_modules", "tests"]
}