From a3dd345793b9c05d09d65596232461bd96a4b169 Mon Sep 17 00:00:00 2001 From: yutent Date: Mon, 5 Jun 2023 10:15:03 +0000 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=8D=A2ts=E4=B8=BAjs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cache.js | 26 +++++ src/cache.ts | 42 ------- src/main.js | 19 ++++ src/main.ts | 91 --------------- src/providers/css.js | 122 ++++++++++++++++++++ src/providers/css.ts | 218 ------------------------------------ src/providers/formatting.js | 40 +++++++ src/providers/formatting.ts | 80 ------------- src/providers/hover.js | 49 ++++++++ src/providers/hover.ts | 98 ---------------- src/providers/html.js | 59 ++++++++++ src/providers/html.ts | 111 ------------------ src/util.js | 104 +++++++++++++++++ src/util.ts | 179 ----------------------------- 14 files changed, 419 insertions(+), 819 deletions(-) create mode 100644 src/cache.js delete mode 100644 src/cache.ts create mode 100644 src/main.js delete mode 100644 src/main.ts create mode 100644 src/providers/css.js delete mode 100644 src/providers/css.ts create mode 100644 src/providers/formatting.js delete mode 100644 src/providers/formatting.ts create mode 100644 src/providers/hover.js delete mode 100644 src/providers/hover.ts create mode 100644 src/providers/html.js delete mode 100644 src/providers/html.ts create mode 100644 src/util.js delete mode 100644 src/util.ts diff --git a/src/cache.js b/src/cache.js new file mode 100644 index 0000000..4be1d71 --- /dev/null +++ b/src/cache.js @@ -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; + } +} diff --git a/src/cache.ts b/src/cache.ts deleted file mode 100644 index 0a1c5f3..0000000 --- a/src/cache.ts +++ /dev/null @@ -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 - } -} diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..086cc7c --- /dev/null +++ b/src/main.js @@ -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'); +} diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 8cd81d3..0000000 --- a/src/main.ts +++ /dev/null @@ -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' - ) -} diff --git a/src/providers/css.js b/src/providers/css.js new file mode 100644 index 0000000..edd74b4 --- /dev/null +++ b/src/providers/css.js @@ -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; + } +} diff --git a/src/providers/css.ts b/src/providers/css.ts deleted file mode 100644 index 050ca98..0000000 --- a/src/providers/css.ts +++ /dev/null @@ -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 { - 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 { - return item - } -} diff --git a/src/providers/formatting.js b/src/providers/formatting.js new file mode 100644 index 0000000..fed3081 --- /dev/null +++ b/src/providers/formatting.js @@ -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; + } +} diff --git a/src/providers/formatting.ts b/src/providers/formatting.ts deleted file mode 100644 index 8860bd2..0000000 --- a/src/providers/formatting.ts +++ /dev/null @@ -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: textEditor.options.tabSize, - insertSpaces: 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 - } -} diff --git a/src/providers/hover.js b/src/providers/hover.js new file mode 100644 index 0000000..1bef9d1 --- /dev/null +++ b/src/providers/hover.js @@ -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; + } +} diff --git a/src/providers/hover.ts b/src/providers/hover.ts deleted file mode 100644 index 06d0b52..0000000 --- a/src/providers/hover.ts +++ /dev/null @@ -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 - } -} diff --git a/src/providers/html.js b/src/providers/html.js new file mode 100644 index 0000000..40b16fd --- /dev/null +++ b/src/providers/html.js @@ -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; + } +} diff --git a/src/providers/html.ts b/src/providers/html.ts deleted file mode 100644 index a145758..0000000 --- a/src/providers/html.ts +++ /dev/null @@ -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 { - return item - } -} diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000..4a92cb9 --- /dev/null +++ b/src/util.js @@ -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; +} diff --git a/src/util.ts b/src/util.ts deleted file mode 100644 index 044ef7c..0000000 --- a/src/util.ts +++ /dev/null @@ -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(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 -}