Skip to content

Commit

Permalink
Removed validation
Browse files Browse the repository at this point in the history
  • Loading branch information
ecmel committed Jan 9, 2021
1 parent 10831e2 commit 16caa86
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 157 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

All notable changes to the extension will be documented in this file.

## [1.6.4] -
## [1.7.0] - 2021-01-09

- Use nextTick()
- Removed validation.

## [1.6.3] - 2021-01-07

Expand Down
17 changes: 2 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Visual Studio Code CSS Intellisense for HTML

HTML `id` and `class` attribute completion and validation for Visual Studio Code.
HTML `id` and `class` attribute completion for Visual Studio Code.

## Features

- HTML `id` and `class` attribute completion and validation.
- HTML `id` and `class` attribute completion.
- Supports linked and embedded style sheets.
- Supports template inheritance.
- Supports additional style sheets.
Expand Down Expand Up @@ -145,19 +145,6 @@ If it is not possible to specify local or remote styles in HTML or via template

This configuration is same as the [first](#linked-and-embedded-style-sheets) example. All relative paths will be evaluated relative to the HTML file being edited.

## Selector Validation

Validated selectors can be configured with the `css.validation` setting. By default `class` selectors are validated:

```json
{
"css.validation": {
"id": false,
"class": true
}
}
```

## Supported Languages

Supported languages can be configured with the `css.enabledLanguages` setting. By default `html` is enabled:
Expand Down
22 changes: 2 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-html-css",
"displayName": "HTML CSS Support",
"description": "CSS Intellisense for HTML",
"version": "1.6.4",
"version": "1.7.0",
"publisher": "ecmel",
"license": "MIT",
"homepage": "https://github.com/ecmel/vscode-html-css",
Expand Down Expand Up @@ -42,29 +42,11 @@
"css.enabledLanguages": {
"type": "array",
"scope": "application",
"description": "List of languages where suggestions are desired.",
"description": "List of languages which suggestions are desired.",
"default": [
"html"
]
},
"css.validation": {
"type": "object",
"scope": "resource",
"description": "Map of selectors to validate.",
"default": {},
"properties": {
"id": {
"type": "boolean",
"description": "Whether to validate 'id' selectors.",
"default": false
},
"class": {
"type": "boolean",
"description": "Whether to validate 'class' selectors.",
"default": true
}
}
},
"css.styleSheets": {
"type": "array",
"scope": "resource",
Expand Down
112 changes: 22 additions & 90 deletions src/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ import {
CompletionItemKind,
CompletionItemProvider,
CompletionList,
Diagnostic,
DiagnosticSeverity,
Disposable,
languages,
Position,
ProviderResult,
Range,
Expand All @@ -21,11 +18,6 @@ import {
workspace
} from "vscode";

export type Validation = {
id: boolean,
class: boolean
};

export type Selector = {
ids: Map<string, CompletionItem>,
classes: Map<string, CompletionItem>,
Expand All @@ -38,19 +30,16 @@ export class SelectorCompletionItemProvider implements CompletionItemProvider, D
readonly start = new Position(0, 0);
readonly cache = new Map<string, Map<string, CompletionItem>>();
readonly watchers = new Map<string, Disposable>();
readonly selectors = new Map<string, Selector>();
readonly warnings = languages.createDiagnosticCollection();
readonly isRemote = /^https?:\/\//i;
readonly canComplete = /(id|class|className)\s*=\s*("|')(?:(?!\2).)*$/si;
readonly findLinkRel = /rel\s*=\s*("|')((?:(?!\1).)+)\1/si;
readonly findLinkHref = /href\s*=\s*("|')((?:(?!\1).)+)\1/si;
readonly findExtended = /(?:{{<|{{>|{%\s*extends|@extends\s*\()\s*("|')?([./A-Za-z_0-9\\\-]+)\1\s*(?:\)|%}|}})/i;

dispose() {
this.watchers.forEach(v => v.dispose());
this.cache.clear();
this.watchers.clear();
this.selectors.clear();
this.warnings.dispose();
this.cache.clear();
}

watchFile(uri: Uri, listener: (e: Uri) => any) {
Expand All @@ -71,15 +60,6 @@ export class SelectorCompletionItemProvider implements CompletionItemProvider, D
return workspace.getConfiguration("css", uri).get<string[]>("styleSheets", []);
}

getValidation(uri: Uri): Validation {
const config = workspace.getConfiguration("css", uri);

return {
id: config.get<boolean>("validation.id", false),
class: config.get<boolean>("validation.class", true)
};
}

getRelativePath(uri: Uri, spec: string, ext?: string): string {
const folder = workspace.getWorkspaceFolder(uri);
const name = ext ? join(dirname(spec), basename(spec, ext) + ext) : spec;
Expand Down Expand Up @@ -214,100 +194,52 @@ export class SelectorCompletionItemProvider implements CompletionItemProvider, D
}
}

async validate(document: TextDocument): Promise<void> {
async findAll(document: TextDocument, kind: CompletionItemKind): Promise<Map<string, CompletionItem>> {
const keys = new Set<string>();
const uri = document.uri;
const text = document.getText();

this.findDocumentStyles(uri, keys, text);

await this.findStyleSheets(uri, keys);
await this.findDocumentLinks(uri, keys, text);
await this.findExtendedStyles(uri, keys, text);

const ids = new Map<string, CompletionItem>();
const classes = new Map<string, CompletionItem>();
const rangesId: Range[] = [];
const rangesClass: Range[] = [];

keys.forEach(key => this.cache.get(key)?.forEach((v, k) =>
(v.kind === CompletionItemKind.Value ? ids : classes).set(k, v)));

const validation = this.getValidation(uri);
const diagnostics: Diagnostic[] = [];
const findAttribute = /(id|class|className)\s*=\s*("|')(.*?)\2/gsi;

let attribute;

while ((attribute = findAttribute.exec(text)) !== null) {
const offset = findAttribute.lastIndex
- attribute[3].length
+ attribute[3].indexOf(attribute[2]);

(attribute[1] === "id" ? rangesId : rangesClass).push(new Range(
document.positionAt(offset),
document.positionAt(findAttribute.lastIndex - 1)));

const findSelector = /([^(\[{}\])\s]+)(?![^(\[{]*[}\])])/gi;

let value;

while ((value = findSelector.exec(attribute[3])) !== null) {
const anchor = findSelector.lastIndex + offset;
const end = document.positionAt(anchor);
const start = document.positionAt(anchor - value[1].length);
const items = new Map<string, CompletionItem>();

if (attribute[1] === "id") {
if (validation.id && !ids.has(value[1])) {
diagnostics.push(new Diagnostic(new Range(start, end),
`CSS id selector '${value[1]}' not found.`,
DiagnosticSeverity.Information));
}
} else {
if (validation.class && !classes.has(value[1])) {
diagnostics.push(new Diagnostic(new Range(start, end),
`CSS class selector '${value[1]}' not found.`,
DiagnosticSeverity.Warning));
}
}
keys.forEach(key => this.cache.get(key)?.forEach((v, k) => {
if (v.kind === kind) {
items.set(k, v);
}
}
}));

this.warnings.set(uri, diagnostics);
this.selectors.set(uri.toString(), { ids, classes, rangesId, rangesClass });
return items;
}

provideCompletionItems(
document: TextDocument,
position: Position,
token: CancellationToken,
context: CompletionContext)
: ProviderResult<CompletionItem[] | CompletionList<CompletionItem>> {
context: CompletionContext): ProviderResult<CompletionItem[] | CompletionList<CompletionItem>> {

return new Promise((resolve, reject) => nextTick(() => {
if (token.isCancellationRequested) {
reject();
return;
}

const selector = this.selectors.get(document.uri.toString());
} else {
const range = new Range(this.start, position);
const text = document.getText(range);
const canComplete = this.canComplete.exec(text);

if (selector) {
for (const range of selector.rangesClass) {
if (range.contains(position)) {
resolve([...selector.classes.values()]);
return;
}
}
if (canComplete) {
const kind = canComplete[1] === "id"
? CompletionItemKind.Value
: CompletionItemKind.Enum;

for (const range of selector.rangesId) {
if (range.contains(position)) {
resolve([...selector.ids.values()]);
return;
}
this.findAll(document, kind).then((items => resolve([...items.values()])));
} else {
reject();
}
}

reject();
}));
}
}
31 changes: 1 addition & 30 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,12 @@
import { SelectorCompletionItemProvider } from "./completion";
import {
ExtensionContext,
languages,
TextDocument,
TextDocumentChangeEvent,
workspace
} from "vscode";
import { ExtensionContext, languages, workspace } from "vscode";

export function activate(context: ExtensionContext) {
const config = workspace.getConfiguration("css");
const enabledLanguages = config.get<string[]>("enabledLanguages", ["html"]);
const timeouts = new Map<string, NodeJS.Timeout>();
const provider = new SelectorCompletionItemProvider();

const validate = (e: TextDocumentChangeEvent | TextDocument) => {
const document = (e as TextDocumentChangeEvent).document || e;

if (enabledLanguages.includes(document.languageId)) {
const uri = document.uri.toString();
const timeout = timeouts.get(uri);

if (timeout) {
clearTimeout(timeout);
}

timeouts.set(uri, setTimeout(() => {
timeouts.delete(uri);
provider.validate(document);
}, 300));
}
};

workspace.textDocuments.forEach(validate);

context.subscriptions.push(
workspace.onDidOpenTextDocument(validate),
workspace.onDidChangeTextDocument(validate),
languages.registerCompletionItemProvider(enabledLanguages, provider),
provider
);
Expand Down
Loading

0 comments on commit 16caa86

Please sign in to comment.