移除ts
parent
a3dd345793
commit
494b9f68e5
11
package.json
11
package.json
|
@ -14,7 +14,7 @@
|
|||
"css",
|
||||
"template",
|
||||
"polymer",
|
||||
"lit-html"
|
||||
"wkit"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -24,8 +24,7 @@
|
|||
"vscode": "^1.22.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "npx tsc --watch -p .",
|
||||
"build": "npx tsc -p ."
|
||||
"build": "node build.js"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages"
|
||||
|
@ -111,12 +110,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.14.5",
|
||||
"@types/vscode": "^1.22.0",
|
||||
"typescript": "^4.9.5",
|
||||
"vscode-languageserver-types": "^3.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-css-languageservice": "^3.0.7",
|
||||
"vscode-emmet-helper": "^1.2.0",
|
||||
|
|
46
src/cache.js
46
src/cache.js
|
@ -1,26 +1,28 @@
|
|||
// 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;
|
||||
_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
|
||||
}
|
||||
}
|
||||
|
|
97
src/main.js
97
src/main.js
|
@ -1,19 +1,84 @@
|
|||
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';
|
||||
import { languages as Languages } from 'vscode'
|
||||
import { HTMLCompletionItemProvider } from './providers/html.js'
|
||||
import {
|
||||
CSSCompletionItemProvider,
|
||||
HTMLStyleCompletionItemProvider
|
||||
} from './providers/css.js'
|
||||
import { HTMLHoverProvider, CSSHoverProvider } from './providers/hover.js'
|
||||
import { CodeFormatterProvider } from './providers/formatting.js'
|
||||
const selector = [
|
||||
'typescriptreact',
|
||||
'javascriptreact',
|
||||
'typescript',
|
||||
'javascript'
|
||||
];
|
||||
'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');
|
||||
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'
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,122 +1,150 @@
|
|||
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';
|
||||
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.js'
|
||||
import { CompletionsCache } from '../cache.js'
|
||||
|
||||
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)
|
||||
};
|
||||
_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
|
||||
}
|
||||
resolveCompletionItem(item, _token) {
|
||||
return item;
|
||||
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)
|
||||
};
|
||||
_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
|
||||
}
|
||||
resolveCompletionItem(item, _token) {
|
||||
return item;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +1,59 @@
|
|||
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';
|
||||
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.js'
|
||||
|
||||
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;
|
||||
_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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +1,63 @@
|
|||
import { getLanguageService as GetHtmlLanguageService } from 'vscode-html-languageservice';
|
||||
import { getCSSLanguageService as GetCssLanguageService } from 'vscode-css-languageservice';
|
||||
import { CreateVirtualDocument, MatchOffset } from '../util';
|
||||
import { getLanguageService as GetHtmlLanguageService } from 'vscode-html-languageservice'
|
||||
import { getCSSLanguageService as GetCssLanguageService } from 'vscode-css-languageservice'
|
||||
import { CreateVirtualDocument, MatchOffset } from '../util.js'
|
||||
|
||||
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;
|
||||
_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;
|
||||
_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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +1,75 @@
|
|||
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';
|
||||
import { getLanguageService as GetHTMLanguageService } from 'vscode-html-languageservice'
|
||||
import * as emmet from 'vscode-emmet-helper'
|
||||
import {
|
||||
GetEmmetConfiguration,
|
||||
MatchOffset,
|
||||
CreateVirtualDocument,
|
||||
TranslateCompletionItems
|
||||
} from '../util.js'
|
||||
import { CompletionsCache } from '../cache.js'
|
||||
|
||||
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)
|
||||
};
|
||||
_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
|
||||
}
|
||||
resolveCompletionItem(item, token) {
|
||||
return item;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
188
src/util.js
188
src/util.js
|
@ -1,104 +1,126 @@
|
|||
import { workspace, TextEdit, Position, Range } from 'vscode';
|
||||
import { TextDocument as HTMLTextDocument, TokenType as HTMLTokenType } from 'vscode-html-languageservice';
|
||||
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
|
||||
};
|
||||
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;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return null
|
||||
}
|
||||
export function Match(regex, data) {
|
||||
regex.exec(null);
|
||||
let match;
|
||||
while ((match = regex.exec(data)) !== null) {
|
||||
return match;
|
||||
}
|
||||
return null;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return regions
|
||||
}
|
||||
export function GetRegionAtOffset(regions, offset) {
|
||||
for (let region of regions) {
|
||||
if (region.start <= offset) {
|
||||
if (offset <= region.end) {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
for (let region of regions) {
|
||||
if (region.start <= offset) {
|
||||
if (offset <= region.end) {
|
||||
return region
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
return null;
|
||||
}
|
||||
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);
|
||||
});
|
||||
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;
|
||||
});
|
||||
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;
|
||||
// context: TextDocument | HTMLTextDocument,
|
||||
languageId,
|
||||
// position: Position | HtmlPosition,
|
||||
content
|
||||
) {
|
||||
const doc = HTMLTextDocument.create(
|
||||
`embedded://document.${languageId}`,
|
||||
languageId,
|
||||
1,
|
||||
content
|
||||
)
|
||||
return doc
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"sourceMap": false,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"lib": ["esnext"]
|
||||
},
|
||||
"exclude": ["node_modules", "tests"]
|
||||
}
|
Loading…
Reference in New Issue