HTML Configurable Autocomplete
Autocomplete your HTML content, navigate to definitions and find references by providing simple matching rules. Be proud of your frameworkless / non-conformant project and be productive again with this extension!
Features
This extension provides:
- completion items when you type specific character(s). They are enabled for HTML files;
- navigate to definitions of components. They are enabled for HTML files;
- find references of a component. They are enabled for JavaScript files.
It needs your configuration so it can find completion items and definitions in your project. Here's a sample configuration you can see in action in this GitHub project.
https://github.com/Halleymedia/vuejs-es6-demo
Take a look at the .vscode/settings.json file in particular.
{
// Enable the extension (it can be omitted)
"htmlConfigurableAutocomplete.enable": true,
// Enable debug but only if you need more verbose logging to diagnose transformers or regexp issues
"htmlConfigurableAutocomplete.debug": true,
// Tell it how to autocomplete HTML content
"htmlConfigurableAutocomplete.completionItemProviders": [
{
// Can be omitted
"enable": true,
// It should be activated when < is pressed (for tag names)
"triggerCharacters": [
"<"
],
// Then look into js files in the components directory
"includeGlobPattern": "src/components/**/*.js",
// Find the component name in there and show it as a completion item
"contentRegexp": "@Component\\(\\s*['\"]\\s*([a-z-]+)",
// It should have this icon
"itemKind": "Constant"
},
{
// Can be omitted
"enable": true,
// It should also be activated when space is pressed (for attributes)
"triggerCharacters": [
" "
],
// But just when the cursor is in an element tag, at a position where an attribute name can be inserted
"triggerRegexp": "<[a-z-]+(\\s+[a-z-]+(=\".*?\")?)*[^\"<>]*",
// Show a couple statically-defined completion items
"staticItems": [
"data-for",
"data-if"
],
// We're typing an attribute name here, so let's help the user by adding ="" right after. The cursor will stop in between double quotes
"completionItemSuffix": "=\"\t\"",
// They should have this icon
"itemKind": "Enum"
},
{
// Can be omitted
"enable": true,
// It should also be activated when space is pressed (for attributes)
"triggerCharacters": [
" "
],
// But just when the cursor is in an element tag, at a position where an attribute name can be inserted
"triggerRegexp": "<[a-z-]+(\\s+[a-z-]+(=\".*?\")?)*[^\"<>]*",
// Let's go look for this component definition and get its public fields (the definition is found thanks to the definition provider configured below)
"includeGlobPattern": "${definitionFilePath}",
// Transform original JavaScript content into a list of class and member definitions
"contentTransformer": "es6-module-nodes",
// And inside of it, look for setter methods
"contentRegexp": "instance public set ([a-z0-9_]+)",
// We're typing an attribute name here, so let's help the user by adding ="" right after. The cursor will stop in between double quotes
"completionItemSuffix": "=\"\t\"",
// JavaScript properties are in camel case, so we transform them in kebab case since they will be used as element attributes
"completionItemTransformer": "camelcase-to-kebabcase",
// They should have this icon
"itemKind": "Field"
},
{
// Can be omitted
"enable": true,
// It should also be activated when { is pressed (for moustached syntax)
"triggerCharacters": [
"{"
],
// But just when two { have been typed already
"triggerRegexp": "{{",
// Go look inside a js file that has the same name and lives in the same directory
"includeGlobPattern": "${dirPath}${fileNameWithoutExtension}[.]js",
// Transform JS content as a list of class and members definitions
"contentTransformer": "es6-module-nodes",
// And inside of it, look for properties
"contentRegexp": "instance public property ([a-z0-9_]+)",
// It should have this icon
"itemKind": "Field"
},
{
// Can be omitted
"enable": true,
// It should also be activated when { is pressed (for moustached syntax)
"triggerCharacters": [
"{"
],
// But just when the cursor is inside an element with the data-for attribute
"triggerRegexp": "data-for=.*?>.*{{",
// Before executing triggerRexep though, flatten the HTML of the current document
"triggerTransformer": "flatten-html",
// Show a couple statically-defined completion items
"staticItems": [
"$item",
"$index"
],
// They should have this icon
"itemKind": "Enum"
},
{
// Fixed config
"enable": true,
// It should also be activated when { is pressed (for moustached syntax)
"triggerCharacters": [
"{", " "
],
// But just when two { have been typed already
"triggerRegexp": "{{\\s*",
// This is a global property we can use in the html views
"staticItems": [
"localization"
],
"completionItemSuffix": ".",
// It should have this icon
"itemKind": "Constant"
},
{
// Can be omitted
"enable": true,
// It should also be activated when { or . is pressed
"triggerCharacters": [
"{", "."
],
// But just when two { have been typed already and also when 'localization' has been typed
"triggerRegexp": "{{\\s*localization\\.([^} ]*)",
// Go look inside the localization JSON file
"includeGlobPattern": "src/localization[.]json",
// Transform JSON content to a flattened hierarchy
"contentTransformer": "flatten-json",
// And inside of it, look for properties. The $1 placeholder will be substituted by the capture in triggerRegexp (i.e. what the user typed)
"contentRegexp": " $1([a-z0-9_]+)",
// It should have this icon
"itemKind": "Field"
}
],
// Now tell it how to navigate to definitions
"htmlConfigurableAutocomplete.definitionProviders": [
{
// Can be omitted
"enable": true,
// Definitions are provided when the cursor is on tag names having a - in them
"definitionRegexp": "</?([a-z]+-[a-z]+)[^>]*",
// Then go look inside js files that live in the components directory
"includeGlobPattern": "src/components/**/*.js",
// And find that very same tag name in them; if one is found, VSCode navigates to definition!
"contentRegexp": "@Component\\(\\s*['\"]\\s*([a-z-]+)"
},
{
// Can be omitted
"enable": true,
// Definitions are provided when the cursor is inside moustaches
"definitionRegexp": "{{\\s*(.+?)\\s*}}",
// Then go look inside js file that lives in the same directory and has the same name with the js extension
"includeGlobPattern": "${dirPath}${fileNameWithoutExtension}[.]js",
// Then find the field it's referring to
"contentRegexp": "^\\s*([a-z0-9_]+)\\s*;?\\s*$"
}
],
// Tell it how to find references (i.e. usages) in the project
"htmlConfigurableAutocomplete.referenceProviders": [
{
// Can be omitted
"enable": true,
// Get the name of the component so we can find references in the project
"referenceRegexp": "@Component\\(\\s*['\"]\\s*([a-z-]+)",
// Look for references inside HTML files
"includeGlobPattern": "src/components/**/*.html",
// References look like tag elements with a - in their name
"contentRegexp": "<([a-z]+-[a-z]+)"
}
]
}
Requirements
No requirements.
Extension Settings
This extension contributes the following settings:
htmlConfigurableAutocomplete.enable
: default is true. Set to false only if you want to disable this extension;
htmlConfigurableAutocomplete.debug
: set to true for a more verbose logging. It will give you better visibility into regular expression matches and content transformation results;
htmlConfigurableAutocomplete.completionItemProviders
: set rules for item completion (see the example above for details);
htmlConfigurableAutocomplete.definitionProviders
: set rules for definitions (see the example above for details).
htmlConfigurableAutocomplete.referenceProviders
: set rules for references (see the example above for details).
Important This extension supports hot configuration reload so any change will be immediately applied. No need to restart VSCode. If something's not working, check the output channel "HTML Configurable Autocomplete" because you might find helpful messages there (e.g. an invalid regexp which is breaking the provider). Enable more verbose logging by setting htmlConfigurableAutocomplete.debug
to true
.
The following configuration keys indicate glob patterns:
htmlConfigurableAutocomplete.completionItemProviders[].includeGlobPattern
;
htmlConfigurableAutocomplete.completionItemProviders[].excludeGlobPattern
;
htmlConfigurableAutocomplete.definitionProviders[].includeGlobPattern
;
htmlConfigurableAutocomplete.definitionProviders[].excludeGlobPattern
;
htmlConfigurableAutocomplete.referenceProviders[].includeGlobPattern
;
htmlConfigurableAutocomplete.referenceProviders[].excludeGlobPattern
.
They support the following placeholders which will be replaced at runtime. Suppose the user has the editor opened on src/components/layout/MainLayout.html
:
${dirName}
is replaced with layout
;
${dirPath}
is replaced with src/components/layout/
;
${filePath}
is replaced with src/components/layout/MainLayout.html
;
${fileName}
is replaced with MainLayout.html
;
${fileNameWithoutExtension}
is replaced with MainLayout
.
Additionally, the following placeholders are also supported for glob patterns of completion item providers. They need at least one configured definition provider which is matching code at the current cursor position. If a definition is found, its file path will be used to replace these placeholders. Supposed a definition is found in src/components/shared/LoadingIndicator.js
:
${definitionDirName}
is replaced with shared
;
${definitionDirPath}
is replaced with src/components/shared/
;
${definitionFilePath}
is replaced with src/components/shared/LoadingIndicator.js
;
${definitionFileName}
is replaced with LoadingIndicator.js
;
${definitionFileNameWithoutExtension}
is replaced with LoadingIndicator
.
Using regular expressions to match parts of a JavaScript file might yield imprecise results. For instance, suppose you wanted to find all properties in a ES6 class like this.
export default class Foo {
loading
animating
load () {
if (loading) {
return
}
this.loading = true
console.log('loading started')
}
}
If you wrote a regular expression like ^\s*([a-z0-9_]+)\s*$
, it would correctly match loading
and animating
, which is the two properties we are looking for, but it would also match return
which is obviously not a property.
By using transformers, this extension can pre-process content before a regular expression is executed. For instance, by using the es6-module-nodes
transformer, the previous ES6 class would be transformed like so:
0,15 default class Foo
1,4 default class Foo instance public property loading
2,4 default class Foo instance public property animating
4,4 default class Foo instance public method load
As you can see, each line represents a class or member definition and its node position in the original file. This way, it becomes way easier to write a regular expression that matches all properties. Try with instance public property ([a-z0-9_]+)
es6-module-nodes
Internally uses babel-eslint
to create a textual representation of a ES6 class that's easier for regexp to work on.
It can be used for the following settings:
htmlConfigurableAutocomplete.completionItemProviders[].contentTransformer
htmlConfigurableAutocomplete.definitionProviders[].contentTransformer
Here's a more complete example. The following ES6 class:
@Fizz @Buzz
class Foo {
#loading
@Bindable
animating
@Handler
load () {
if (loading) {
return
}
this.loading = true
console.log('loading started')
}
static configure() {
void(0)
}
get percent() {
return Math.random()
}
set visibile(value) {
void(value)
}
}
export default Foo
It's going to be transformed to
0,0 default class Foo @Fizz,@Buzz
2,4 default class Foo @Fizz,@Buzz instance private property loading
4,4 default class Foo @Fizz,@Buzz instance public property animating @Bindable
7,4 default class Foo @Fizz,@Buzz instance public method load @Handler
15,4 default class Foo @Fizz,@Buzz static public method configure
18,4 default class Foo @Fizz,@Buzz instance public get percent
21,4 default class Foo @Fizz,@Buzz instance public set visibile
Class info is repeated for each of its members, in case you wanted to match members of just a particular class. Each output line starts with the zero-based position (line,character) where the original node started.
The previous example showed the ES6-style exports but it also works with node-style exports (e.g. module.exports = class Foo {}
).
flatten-html
Sometimes you want to display some completion items only when you're in a specific portion of the HTML document, such as an ancestor element having a specific attribute. This transformer uses htmlparser2 to parse the HTML content and then it will flatten the HTML hierarchy of elements so it will be easier for you to write a regular expression.
It can be used for the following settings:
htmlConfigurableAutocomplete.completionItemProviders[].triggerTransformer
Suppose the current editor has this HTML content:
<div data-for="foo">
<span>bar</span>
</div>
flatten-json
In case you want to autocomplete the hierarchy of properties found in a json file, then this is the transformer for you. It uses clarinet to parse the JSON content and flatten its hierarchy of properties so it will be easier for you to write a regular expression.
It can be used for the following settings:
htmlConfigurableAutocomplete.completionItemProviders[].contentTransformer
htmlConfigurableAutocomplete.definitionProviders[].contentTransformer
Suppose the current editor has this JSON content:
{
"aa": [
{"bb": 1},
{"cc": 2},
{"dd": [ {"ee": 3}, {"ff": 5} ] }
]
}
It will be transformed as follows, by outputting the full path of each property.
1,2 aa
2,5 aa[0].bb
3,5 aa[1].cc
4,5 aa[2].dd
4,14 aa[2].dd[0].ee
4,25 aa[2].dd[1].ff
It's now easier to write the contentRegexp
, since it will execute on this transformed content. Here's an example of a contentRegexp
which will match and suggest the bb
property after the user typed $1
where $1
is a placeholder for what the user typed in the VSCode editor.
$1([a-z0-9_]+)
We must now provide a triggerRegexp
containing a capture so it will be used in lieu of the $1
placeholder.
(aa\[\n\]\.)
camelcase-to-kebabcase
Simply transforms a camelCase
string into kebab-case
.
It can be used for the following setting:
htmlConfigurableAutocomplete.completionItemProviders[].completionItemTransformer
For instance, myProperty
is transformed to my-property
.
kebabcase-to-camelcase
Simply transforms a kebab-case
string into camelCase
.
It can be used for the following setting:
htmlConfigurableAutocomplete.definitionProviders[].definitionTransformer
For instance, my-property
is transformed to myProperty
.
Logging
This extension will log messages to a Visual Studio Code output channel named "HTML Configurable Autocomplete". These messages might help you understanding how your rules and regular expressions are behaving, expecially if you set the htmlConfigurableAutocomplete.debug
option to true
.
Known Issues
Oh well, some things could be improved...
- The use of transformers might lead to suboptimal performance with large projects since no caching mechanism is used in the current version. Caching for completion items, definitions and references might be implemented in a future version, along with file watchers for automatic cache invalidation.
Release Notes
1.4.2 (2020-11-03)
- more opportunities of completion: whenever a trigger of a completion item provider captures transformed content, that same content is fed into a definition provider. Useful when you need to suggest slot names of custom components.
1.4.1 (2020-10-28)
- fixed a bug on completion item providers throwing an exception on a trigger regexp containing optional captures.
1.4.0 (2020-10-10)
- transformer
flatten-json
;
- in completion item providers, the
contentRegexp
can use placeholders such as $1
, $2
, ..., $n
. They will be replaced by the capture group values from the triggerRegexp
(if any).
1.3.1 (2020-09-22)
- fixed a bug on navigating to definition when the
es6-module-nodes
transformer is used.
1.3.0 (2020-08-26)
- transformer
flatten-html
;
- completion item prefix and suffix.
1.2.0 (2020-08-19)
- 3 transformers:
es6-module-nodes
, camelcase-to-kebabcase
, kebabcase-to-camelcase
;
- Verbose logging via the
debug
setting.
1.1.0 (2020-07-29)
- Added reference providers;
- Added support for
${definition...}
placeholders in glob patterns of completion item providers;
- Added hot configuration reload.
1.0.0 (2020-07-26)
Initial release of these two features for HTML files:
- Added completion item providers;
- Added definition providers.