Skip to content

Commit

Permalink
improve vscode plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
mantou132 committed Nov 4, 2024
1 parent 463b5f2 commit 914216f
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 120 deletions.
3 changes: 3 additions & 0 deletions packages/vscode-gem-plugin/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CSS_REG = /(?<start>\/\*\s*css\s*\*\/\s*`|(?<!`)(?:css|less|scss)\s*`)(?<content>.*?)(`(?=;|\s))/gis;
export const HTML_REG = /(?<start>\/\*\s*html\s*\*\/\s*`|(?<!`)(?:html|raw)\s*`)(?<content>[^`]*)(`)/gi;
export const STYLE_REG = /(?<start>\/\*\s*style\s*\*\/\s*`|(?<!`)styled?\s*`)(?<content>.*?)(`(?=,|\s*}\s*\)))/gis;
74 changes: 56 additions & 18 deletions packages/vscode-gem-plugin/src/providers/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,62 @@ import type {
} from 'vscode';
import type { LanguageService as HTMLanguageService } from 'vscode-html-languageservice';
import type { LanguageService as CSSLanguageService } from 'vscode-css-languageservice';
import { getLanguageService as getHTMLanguageService } from 'vscode-html-languageservice';
import { getLanguageService as getHTMLanguageService, TokenType as HTMLTokenType } from 'vscode-html-languageservice';
import { getCSSLanguageService as getCSSLanguageService } from 'vscode-css-languageservice';

import {
matchOffset,
createVirtualDocument,
getLanguageRegions,
getRegionAtOffset,
translateCompletionList,
translateToCSS,
} from '../util';
import { matchOffset, createVirtualDocument, translateCompletionList, removeSlot } from '../util';
import { CompletionsCache } from '../cache';
import { CSS_REG, HTML_REG } from '../constants';

export function getRegionAtOffset(regions: IEmbeddedRegion[], offset: number) {
for (const region of regions) {
if (region.start <= offset) {
if (offset <= region.end) {
return region;
}
} else {
break;
}
}
return null;
}

export interface IEmbeddedRegion {
languageId: string;
start: number;
end: number;
length: number;
content: string;
}

export function getLanguageRegions(service: HTMLanguageService, data: string) {
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 class HTMLStyleCompletionItemProvider implements CompletionItemProvider {
#cssLanguageService: CSSLanguageService = getCSSLanguageService();
#htmlLanguageService: HTMLanguageService = getHTMLanguageService();
#expression = /(\/\*\s*html\s*\*\/\s*`|(?<!`)html\s*`)([^`]*)(`)/gi;
#expression = HTML_REG;
#cache = new CompletionsCache();

provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken) {
Expand All @@ -42,8 +81,8 @@ export class HTMLStyleCompletionItemProvider implements CompletionItemProvider {

if (!match) return empty;

const matchContent = match[2];
const matchStartOffset = match.index + match[1].length;
const matchContent = match.groups!.content;
const matchStartOffset = match.index + match.groups!.start.length;
const regions = getLanguageRegions(this.#htmlLanguageService, matchContent);

if (regions.length <= 0) return empty;
Expand All @@ -53,7 +92,7 @@ export class HTMLStyleCompletionItemProvider implements CompletionItemProvider {
if (!region) return empty;

const virtualOffset = currentOffset - (matchStartOffset + region.start);
const virtualDocument = createVirtualDocument('css', translateToCSS(region.content));
const virtualDocument = createVirtualDocument('css', removeSlot(region.content));
const stylesheet = this.#cssLanguageService.parseStylesheet(virtualDocument);

const completions = this.#cssLanguageService.doComplete(
Expand All @@ -72,7 +111,7 @@ export class HTMLStyleCompletionItemProvider implements CompletionItemProvider {

export class CSSCompletionItemProvider implements CompletionItemProvider {
#cssLanguageService: CSSLanguageService = getCSSLanguageService();
#expression = /(\/\*\s*(css|less|scss)\s*\*\/\s*`|(?<!`)(?:css|less|scss|stylesheet)\s*`)([^`]*)(`)/gi;
#expression = CSS_REG;
#cache = new CompletionsCache();

provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken) {
Expand All @@ -90,11 +129,10 @@ export class CSSCompletionItemProvider implements CompletionItemProvider {

if (!match) return empty;

const dialect = match[2];
const matchContent = match[3];
const matchStartOffset = match.index + match[1].length;
const matchContent = match.groups!.content;
const matchStartOffset = match.index + match.groups!.start.length;
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = createVirtualDocument(dialect, translateToCSS(matchContent));
const virtualDocument = createVirtualDocument('css', removeSlot(matchContent));
const vCss = this.#cssLanguageService.parseStylesheet(virtualDocument);

const completions = this.#cssLanguageService.doComplete(
Expand Down
27 changes: 13 additions & 14 deletions packages/vscode-gem-plugin/src/providers/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import type { HoverProvider, TextDocument, Position, CancellationToken } from 'v
import type { LanguageService as HtmlLanguageService, Hover as HtmlHover } from 'vscode-html-languageservice';
import type { LanguageService as CssLanguageService } from 'vscode-css-languageservice';

import { createVirtualDocument, matchOffset } from '../util';
import { createVirtualDocument, matchOffset, removeSlot } from '../util';
import { CSS_REG, HTML_REG, STYLE_REG } from '../constants';

function translateHover(hover: HtmlHover | null): Hover | null {
if (!hover) return null;
Expand All @@ -25,7 +26,7 @@ function translateHover(hover: HtmlHover | null): Hover | null {

export class HTMLHoverProvider implements HoverProvider {
#htmlLanguageService: HtmlLanguageService = getHtmlLanguageService();
#expression = /(\/\*\s*html\s*\*\/\s*`|(?<!`)(?:html|raw)\s*`)([^`]*)(`)/gi;
#expression = HTML_REG;

provideHover(document: TextDocument, position: Position, _token: CancellationToken) {
const currentOffset = document.offsetAt(position);
Expand All @@ -34,8 +35,8 @@ export class HTMLHoverProvider implements HoverProvider {

if (!match) return null;

const matchContent = match[2];
const matchStartOffset = match.index + match[1].length;
const matchContent = match.groups!.content;
const matchStartOffset = match.index + match.groups!.start.length;
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = createVirtualDocument('html', matchContent);
const html = this.#htmlLanguageService.parseHTMLDocument(virtualDocument);
Expand All @@ -50,7 +51,7 @@ export class HTMLHoverProvider implements HoverProvider {

export class CSSHoverProvider implements HoverProvider {
#cssLanguageService: CssLanguageService = getCssLanguageService();
#expression = /(\/\*\s*(css|less|scss)\s*\*\/\s*`|(?:css|stylesheet)\s*`)([^`]*)(`)/gi;
#expression = CSS_REG;

provideHover(document: TextDocument, position: Position, _token: CancellationToken) {
const currentOffset = document.offsetAt(position);
Expand All @@ -59,12 +60,10 @@ export class CSSHoverProvider implements HoverProvider {

if (!match) return null;

const dialect = match[2];

const matchContent = match[3];
const matchStartOffset = match.index + match[1].length;
const matchContent = match.groups!.content;
const matchStartOffset = match.index + match.groups!.start.length;
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = createVirtualDocument(dialect, matchContent);
const virtualDocument = createVirtualDocument('css', removeSlot(matchContent));
const stylesheet = this.#cssLanguageService.parseStylesheet(virtualDocument);
const hover = this.#cssLanguageService.doHover(
virtualDocument,
Expand All @@ -82,7 +81,7 @@ export class CSSHoverProvider implements HoverProvider {

export class StyleHoverProvider implements HoverProvider {
#cssLanguageService: CssLanguageService = getCssLanguageService();
#expression = /(\/\*\s*(style)\s*\*\/\s*`|styled?\s*`)([^`]*)(`)/gi;
#expression = STYLE_REG;

provideHover(document: TextDocument, position: Position, _token: CancellationToken) {
const currentOffset = document.offsetAt(position);
Expand All @@ -91,10 +90,10 @@ export class StyleHoverProvider implements HoverProvider {

if (!match) return null;

const matchContent = match[3];
const matchStartOffset = match.index + match[1].length;
const matchContent = match.groups!.content;
const matchStartOffset = match.index + match.groups!.start.length;
const virtualOffset = currentOffset - matchStartOffset + 8;
const virtualDocument = createVirtualDocument('css', `:host { ${matchContent} }`);
const virtualDocument = createVirtualDocument('css', `:host { ${removeSlot(matchContent)} }`);
const stylesheet = this.#cssLanguageService.parseStylesheet(virtualDocument);
const hover = this.#cssLanguageService.doHover(
virtualDocument,
Expand Down
23 changes: 19 additions & 4 deletions packages/vscode-gem-plugin/src/providers/html.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// eslint-disable-next-line import/no-unresolved
import { workspace } from 'vscode';
import type {
CompletionList,
CompletionItem,
Expand All @@ -12,13 +14,26 @@ import type {
} from 'vscode-html-languageservice';
import { getLanguageService as getHTMLanguageService } from 'vscode-html-languageservice';
import { doComplete as doEmmetComplete } from '@vscode/emmet-helper';
import type { VSCodeEmmetConfig } from '@vscode/emmet-helper';

import { getEmmetConfiguration, matchOffset, createVirtualDocument, translateCompletionList } from '../util';
import { matchOffset, createVirtualDocument, translateCompletionList } from '../util';
import { CompletionsCache } from '../cache';
import { HTML_REG } from '../constants';

export function getEmmetConfiguration() {
const emmetConfig = workspace.getConfiguration('emmet');
return {
useNewEmmet: true,
showExpandedAbbreviation: emmetConfig.showExpandedAbbreviation,
showAbbreviationSuggestions: emmetConfig.showAbbreviationSuggestions,
syntaxProfiles: emmetConfig.syntaxProfiles,
variables: emmetConfig.variables,
} as VSCodeEmmetConfig;
}

export class HTMLCompletionItemProvider implements CompletionItemProvider {
#htmlLanguageService: HTMLanguageService = getHTMLanguageService();
#expression = /(\/\*\s*html\s*\*\/\s*`|(?<!`)html\s*`)([^`]*)(`)/gi;
#expression = HTML_REG;
#cache = new CompletionsCache();

provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken) {
Expand All @@ -36,8 +51,8 @@ export class HTMLCompletionItemProvider implements CompletionItemProvider {

if (!match) return empty;

const matchContent = match[2];
const matchStartOffset = match.index + match[1].length;
const matchContent = match.groups!.content;
const matchStartOffset = match.index + match.groups!.start.length;
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = createVirtualDocument('html', matchContent);
const vHtml = this.#htmlLanguageService.parseHTMLDocument(virtualDocument);
Expand Down
11 changes: 6 additions & 5 deletions packages/vscode-gem-plugin/src/providers/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import type {
import type { LanguageService as CSSLanguageService } from 'vscode-css-languageservice';
import { getCSSLanguageService as getCSSLanguageService } from 'vscode-css-languageservice';

import { matchOffset, createVirtualDocument, translateCompletionList, translateToCSS } from '../util';
import { matchOffset, createVirtualDocument, translateCompletionList, removeSlot } from '../util';
import { CompletionsCache } from '../cache';
import { STYLE_REG } from '../constants';

export class StyleCompletionItemProvider implements CompletionItemProvider {
#cssLanguageService: CSSLanguageService = getCSSLanguageService();
#expression = /(\/\*\s*(style)\s*\*\/\s*`|(?<!`)styled?\s*`)([^`]*)(`)/gi;
#expression = STYLE_REG;
#cache = new CompletionsCache();

provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken): CompletionList {
Expand All @@ -32,10 +33,10 @@ export class StyleCompletionItemProvider implements CompletionItemProvider {

if (!match) return empty;

const matchContent = match[3];
const matchStartOffset = match.index + match[1].length;
const matchContent = match.groups!.content;
const matchStartOffset = match.index + match.groups!.start.length;
const virtualOffset = currentOffset - matchStartOffset + 8; // accounting for :host { }
const virtualDocument = createVirtualDocument('css', `:host { ${translateToCSS(matchContent)} }`);
const virtualDocument = createVirtualDocument('css', `:host { ${removeSlot(matchContent)} }`);
const vCss = this.#cssLanguageService.parseStylesheet(virtualDocument);

const completions = this.#cssLanguageService.doComplete(
Expand Down
92 changes: 14 additions & 78 deletions packages/vscode-gem-plugin/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// eslint-disable-next-line import/no-unresolved
import { workspace, Range, Position } from 'vscode';
import { TextDocument as HTMLTextDocument, TokenType as HTMLTokenType } from 'vscode-html-languageservice';
import { Range, Position } from 'vscode';
import { TextDocument as HTMLTextDocument } from 'vscode-html-languageservice';
import type { TextLine, CompletionItem } from 'vscode';
import type { LanguageService, CompletionList as HtmlCompletionList } from 'vscode-html-languageservice';
import type { VSCodeEmmetConfig } from '@vscode/emmet-helper';
import type { CompletionList as HtmlCompletionList } from 'vscode-html-languageservice';

export function translateToCSS(text: string) {
return text.replace(/\$\{.*\}/g, (str) => 'x'.repeat(str.length));
export function removeSlot(text: string) {
const v = text.replace(/\$\{[^${]*?\}/g, (str) => str.replaceAll(/[^\n]/g, ' '));
if (v === text) return v;
return removeSlot(v);
}

export function translateCompletionList(list: HtmlCompletionList, line: TextLine, expand?: boolean) {
Expand Down Expand Up @@ -38,86 +39,21 @@ export function translateCompletionList(list: HtmlCompletionList, line: TextLine
};
}

export function getEmmetConfiguration() {
const emmetConfig = workspace.getConfiguration('emmet');
return {
useNewEmmet: true,
showExpandedAbbreviation: emmetConfig.showExpandedAbbreviation,
showAbbreviationSuggestions: emmetConfig.showAbbreviationSuggestions,
syntaxProfiles: emmetConfig.syntaxProfiles,
variables: emmetConfig.variables,
} as VSCodeEmmetConfig;
}

export function notNull<T>(input: any) {
if (!input) {
return {} as T;
}
return input as T;
}

export function matchOffset(regex: RegExp, data: string, offset: number) {
export function matchOffset(regex: RegExp, docText: string, offset: number) {
regex.exec('null');

let match: RegExpExecArray | null;
while ((match = regex.exec(data)) !== null) {
if (offset > match.index + match[1].length && offset < match.index + match[0].length) {
while ((match = regex.exec(docText)) !== null) {
const [fullStr, startStr] = match;
const start = match.index + startStr.length;
const end = match.index + fullStr.length;
if (offset > start && offset < end) {
return match;
}
}
return null;
}

export function getLanguageRegions(service: LanguageService, data: string) {
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) {
for (const region of regions) {
if (region.start <= offset) {
if (offset <= region.end) {
return region;
}
} else {
break;
}
}
return null;
}

export function createVirtualDocument(
// context: TextDocument | HTMLTextDocument,
languageId: string,
// position: Position | HtmlPosition,
content: string,
) {
export function createVirtualDocument(languageId: string, content: string) {
return HTMLTextDocument.create(`embedded://document.${languageId}`, languageId, 1, content);
}

export interface IEmbeddedRegion {
languageId: string;
start: number;
end: number;
length: number;
content: string;
}
2 changes: 1 addition & 1 deletion packages/vscode-gem-plugin/syntaxes/es6.inline.css.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"patterns": [
{
"contentName": "meta.embedded.block.css",
"begin": "(?ix)(\\s*?(\\w+\\.)?(?:css|stylesheet|/\\*\\s*css\\s*\\*/)\\s*)(`)",
"begin": "(?ix)(\\s*?(\\w+\\.)?(?:css|/\\*\\s*css\\s*\\*/)\\s*)(`)",
"beginCaptures": {
"0": {
"name": "string.template.ts, punctuation.definition.string.template.begin.ts"
Expand Down

0 comments on commit 914216f

Please sign in to comment.