Skip to content

Commit

Permalink
Add support for Emmet
Browse files Browse the repository at this point in the history
  • Loading branch information
zignd committed Feb 18, 2018
1 parent 3fcb3a2 commit 725464d
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 28 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 1.17.1 (Fev 18, 2018)
* Added support for "class" in TypeScript React, JavaScript and JavaScript React language modes. Previously only "className" was supported.
* Added support for Emmet.

### 1.16.2 (Fev 10, 2018)
* Workaround for a bug in the VS Code API.

Expand Down
33 changes: 22 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# IntelliSense for CSS class names
# IntelliSense for CSS class names in HTML

A Visual Studio Code extension that provides CSS class name completion for the HTML `class` attribute based on the definitions found in your workspace or external files referenced through the `link` element.

Expand All @@ -25,29 +25,40 @@ A Visual Studio Code extension that provides CSS class name completion for the H
* Handlebars
* EJS (.ejs)

## Library Specific Support
* @apply in CSS, SASS and SCSS Files for [Tailwind CSS](https://tailwindcss.com)
## Specific Support
* "@apply" directive in CSS, SASS and SCSS Files for [Tailwind CSS](https://tailwindcss.com)
* "className" and "class" in TypeScript React, JavaScript and JavaScript React language modes
* Emmet abbreviations support triggered by typing a "." (comes disabled by default, check the User Settings topic for more information)

## Contributions
You can request new features and contribute to the extension development on its [repository on GitHub](https://github.com/Zignd/HTML-CSS-Class-Completion/issues). Look for an issue you're interested in working on, comment on it to let me know you're working on it and submit your pull request! :D

## What's new in version 1.16.2 (Fev 1015, 2018)
* Automatic re-caching when the extension's User Settings change.
* Fix mistake in production build.
* Workaround for a bug in the VS Code API.
## What's new in version 1.17.1 (Fev 18, 2018)
* Added support for "class" in TypeScript React, JavaScript and JavaScript React language modes. Previously only "className" was supported.
* Added support for Emmet.

Check out the [changelog](https://github.com/zignd/HTML-CSS-Class-Completion/blob/master/CHANGELOG.md) for the current and previous updates.

## Usage
If there are HTML or JS files on your workspace, the extension automatically starts and looks for CSS class definitions. In case new CSS classes are defined, or new CSS files are added to the workspace, and you also want auto-completion for them, just hit the lightning icon on the status bar. Also, you can execute the command by pressing `Ctrl+Shift+P`(`Cmd+Shift+P` for Mac) and then typing "Cache CSS class definitions."

### User Settings
You can change the folders and files the extension will consider or exclude during the caching process by setting the following User Settings:
The extension supports a few user settings, changes to these settings will be automatically recognized and the caching process will be re-executed.

* `html-css-class-completion.includeGlobPattern` (default: "**/*.{css,html}")
* `html-css-class-completion.excludeGlobPattern` (default: "")
#### Folders and Files

Changes to these settings will be recognized by the extension and the caching process will be automatically executed.
You can change the folders and files the extension will consider or exclude during the caching process by setting the following user settings:

* `"html-css-class-completion.includeGlobPattern"` (default: `"**/*.{css,html}"`)
* `"html-css-class-completion.excludeGlobPattern"` (default: `""`)

#### Emmet

Emmet support comes disabled by default, the reason behind this choice is because it the current implementation simply triggers completion when you type a "." (period) and this behavior might be considered a little annoying, but it might change in the future.

Currently it supports the following languages (those are [language identifier](https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers)): "html", "razor", "php", "blade", "vue", "twig", "markdown", "erb", "handlebars", "ejs", "typescriptreact", "javascript", "javascriptreact".

* `"html-css-class-completion.enableEmmetSupport"` (default: `false`)

![](https://i.imgur.com/O7NjEUW.gif)
![](https://i.imgur.com/uyiXqMb.gif)
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
{
"name": "html-css-class-completion",
"displayName": "IntelliSense for CSS class names",
"displayName": "IntelliSense for CSS class names in HTML",
"description": "CSS class name completion for the HTML class attribute based on the definitions found in your workspace.",
"version": "1.16.2",
"version": "1.17.0",
"publisher": "Zignd",
"engines": {
"vscode": "^1.19.0"
},
"enableProposedApi": true,
"keywords": [
"html",
"css",
Expand Down Expand Up @@ -42,6 +41,11 @@
"type": "string",
"default": "",
"description": "A glob pattern that defines files and folders to exclude. The glob pattern will be matched against the file paths of resulting matches relative to their workspace."
},
"html-css-class-completion.enableEmmetSupport": {
"type": "boolean",
"default": false,
"description": "Enables completion when you're writing Emmet abbreviations."
}
}
}
Expand Down
54 changes: 40 additions & 14 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Bluebird from "bluebird";
import * as _ from "lodash";
import "source-map-support/register";
import * as verror from "verror";
import * as VError from "verror";
import {
commands, CompletionItem, CompletionItemKind, Disposable,
ExtensionContext, languages, Position, Range, TextDocument, Uri, window,
Expand All @@ -20,6 +20,8 @@ const completionTriggerChars = ['"', "'", " ", "."];

let caching: boolean = false;

const emmetDisposables: Array<{ dispose(): any }> = [];

async function cache(): Promise<void> {
try {
notifier.notify("eye", "Looking for CSS classes in the workspace...");
Expand Down Expand Up @@ -56,7 +58,7 @@ async function cache(): Promise<void> {
}, { concurrency: 30 });
} catch (err) {
notifier.notify("alert", "Failed to cache the CSS classes in the workspace (click for another attempt)");
throw new verror.VError(err, "Failed to parse the documents");
throw new VError(err, "Failed to parse the documents");
}

uniqueDefinitions = _.uniqBy(definitions, (def) => def.className);
Expand All @@ -71,12 +73,13 @@ async function cache(): Promise<void> {
notifier.notify("zap", "CSS classes cached (click to cache again)");
} catch (err) {
notifier.notify("alert", "Failed to cache the CSS classes in the workspace (click for another attempt)");
throw new verror.VError(err,
throw new VError(err,
"Failed to cache the class definitions during the iterations over the documents that were found");
}
}

function provideCompletionItemsGenerator(languageSelector: string, classMatchRegex: RegExp, classPrefix: string = "") {
function provideCompletionItemsGenerator(languageSelector: string, classMatchRegex: RegExp,
classPrefix: string = "", splitChar: string = " ") {
return languages.registerCompletionItemProvider(languageSelector, {
provideCompletionItems(document: TextDocument, position: Position): CompletionItem[] {
const start: Position = new Position(position.line, 0);
Expand All @@ -90,7 +93,7 @@ function provideCompletionItemsGenerator(languageSelector: string, classMatchReg
}

// Will store the classes found on the class attribute
const classesOnAttribute = rawClasses[1].split(" ");
const classesOnAttribute = rawClasses[1].split(splitChar);

// Creates a collection of CompletionItem based on the classes already cached
const completionItems = uniqueDefinitions.map((definition) => {
Expand All @@ -117,18 +120,37 @@ function provideCompletionItemsGenerator(languageSelector: string, classMatchReg
}, ...completionTriggerChars);
}

function enableEmmetSupport(disposables: Disposable[]) {
const emmetRegex = /(?=\.)([\w-\. ]*$)/;
const languageModes = ["html", "razor", "php", "blade", "vue", "twig", "markdown", "erb",
"handlebars", "ejs", "typescriptreact", "javascript", "javascriptreact"];
languageModes.forEach((language) => {
emmetDisposables.push(provideCompletionItemsGenerator(language, emmetRegex, "", "."));
});
}

function disableEmmetSupport(disposables: Disposable[]) {
for (const emmetDisposable of disposables) {
emmetDisposable.dispose();
}
}

export async function activate(context: ExtensionContext): Promise<void> {
const disposables: Disposable[] = [];
workspace.onDidChangeConfiguration(async (e) => {
if (!e.affectsConfiguration("html-css-class-completion.includeGlobPattern") &&
!e.affectsConfiguration("html-css-class-completion.excludeGlobPattern")) {
return;
}

try {
await cache();
if (e.affectsConfiguration("html-css-class-completion.includeGlobPattern") ||
e.affectsConfiguration("html-css-class-completion.excludeGlobPattern")) {
await cache();
}

if (e.affectsConfiguration("html-css-class-completion.enableEmmetSupport")) {
const isEnabled = workspace.getConfiguration()
.get<boolean>("html-css-class-completion.enableEmmetSupport");
isEnabled ? enableEmmetSupport(emmetDisposables) : disableEmmetSupport(emmetDisposables);
}
} catch (err) {
err = new verror.VError(err, "Failed to automatically re-cache the CSS classes in the workspace");
err = new VError(err, "Failed to automatically reload the extension after the configuration change");
console.error(err);
window.showErrorMessage(err.message);
}
Expand All @@ -144,7 +166,7 @@ export async function activate(context: ExtensionContext): Promise<void> {
try {
await cache();
} catch (err) {
err = new verror.VError(err, "Failed to cache the CSS classes in the workspace");
err = new VError(err, "Failed to cache the CSS classes in the workspace");
console.error(err);
window.showErrorMessage(err.message);
} finally {
Expand Down Expand Up @@ -173,10 +195,14 @@ export async function activate(context: ExtensionContext): Promise<void> {
try {
await cache();
} catch (err) {
err = new verror.VError(err, "Failed to cache the CSS classes in the workspace for the first time");
err = new VError(err, "Failed to cache the CSS classes in the workspace for the first time");
console.error(err);
window.showErrorMessage(err.message);
} finally {
caching = false;
}
}

export function deactivate(): void {
emmetDisposables.forEach((disposable) => disposable.dispose());
}

0 comments on commit 725464d

Please sign in to comment.