improve emmet syntax handling (#3)

master
Brad Cornes 2018-08-25 11:57:21 +01:00
parent 3ed7a35646
commit cc42400081
2 changed files with 279 additions and 79 deletions

View File

@ -1,7 +1,8 @@
'use strict' 'use strict'
import * as vscode from 'vscode' import * as vscode from 'vscode'
import { join, dirname } from 'path' import { dirname } from 'path'
const htmlElements = require('./htmlElements.js')
const tailwindClassNames = require('tailwind-class-names') const tailwindClassNames = require('tailwind-class-names')
const dlv = require('dlv') const dlv = require('dlv')
const Color = require('color') const Color = require('color')
@ -21,9 +22,7 @@ const HTML_TYPES = [
'handlebars', 'handlebars',
'ejs', 'ejs',
'nunjucks', 'nunjucks',
'haml', 'haml'
// for jsx
...JS_TYPES
] ]
const CSS_TYPES = ['css', 'sass', 'scss', 'less', 'postcss', 'stylus'] const CSS_TYPES = ['css', 'sass', 'scss', 'less', 'postcss', 'stylus']
@ -147,7 +146,17 @@ function createCompletionItemProvider({
// match emmet style syntax // match emmet style syntax
// e.g. .flex.items-center // e.g. .flex.items-center
let currentLine = lines[lines.length - 1] let currentLine = lines[lines.length - 1]
matches = currentLine.match(/\.([^()#>*^ \[\]=$@{}]*)$/i) let currentWord = currentLine.split(' ').pop()
matches = currentWord.match(/^\.([^.()#>*^ \[\]=$@{}]*)$/)
if (!matches) {
matches = currentWord.match(
new RegExp(
`^([A-Z][a-zA-Z0-9]*|[a-z][a-z0-9]*-[a-z0-9-]+|${htmlElements.join(
'|'
)}).*?\\.([^.()#>*^ \\[\\]=$@{}]*)$`
)
)
}
let parts = matches[matches.length - 1].split('.') let parts = matches[matches.length - 1].split('.')
str = parts[parts.length - 1] str = parts[parts.length - 1]
} }
@ -422,32 +431,78 @@ class TailwindIntellisense {
createCompletionItemProvider({ createCompletionItemProvider({
items: this._items, items: this._items,
languages: HTML_TYPES, languages: HTML_TYPES,
regex: /\bclass(Name)?=["']([^"']*)$/, // /\bclass(Name)?=(["'])(?!.*?\2)/ regex: /\bclass=["']([^"']*)$/, // /\bclass(Name)?=(["'])(?!.*?\2)/
triggerCharacters: ["'", '"', ' ', '.', separator], triggerCharacters: ["'", '"', ' ', '.', separator],
config: tailwind.config, config: tailwind.config,
emmet: true emmet: true
}) })
) )
this._providers.push(
createCompletionItemProvider({
items: this._items,
languages: JS_TYPES,
regex: /\bclass(Name)?=["']([^"']*)$/, // /\bclass(Name)?=(["'])(?!.*?\2)/
triggerCharacters: ["'", '"', ' ', separator]
.concat([
Object.keys(
vscode.workspace.getConfiguration('emmet.includeLanguages')
).indexOf('javascript') !== -1 && '.'
])
.filter(Boolean),
config: tailwind.config,
emmet:
Object.keys(
vscode.workspace.getConfiguration('emmet.includeLanguages')
).indexOf('javascript') !== -1
})
)
// Vue.js // Vue.js
this._providers.push( this._providers.push(
createCompletionItemProvider({ createCompletionItemProvider({
items: this._items, items: this._items,
languages: ['vue'], languages: ['vue'],
regex: /\bclass(Name)?=["']([^"']*)$/, regex: /\bclass=["']([^"']*)$/,
enable: text => { enable: text => {
if ( if (
(text.indexOf('<template') !== -1 && text.indexOf('<template') !== -1 &&
text.indexOf('</template>') === -1) || text.indexOf('</template>') === -1
(text.indexOf('<script') !== -1 && text.indexOf('</script>') === -1)
) { ) {
return true return true
} }
return false return false
}, },
triggerCharacters: ["'", '"', ' ', '.', separator], triggerCharacters: ["'", '"', ' ', separator]
.concat([
Object.keys(
vscode.workspace.getConfiguration('emmet.includeLanguages')
).indexOf('vue-html') !== -1 && '.'
])
.filter(Boolean),
config: tailwind.config, config: tailwind.config,
emmet: true emmet:
Object.keys(
vscode.workspace.getConfiguration('emmet.includeLanguages')
).indexOf('vue-html') !== -1
})
)
this._providers.push(
createCompletionItemProvider({
items: this._items,
languages: ['vue'],
regex: /\bclass=["']([^"']*)$/,
enable: text => {
if (
text.indexOf('<script') !== -1 &&
text.indexOf('</script>') === -1
) {
return true
}
return false
},
triggerCharacters: ["'", '"', ' ', separator],
config: tailwind.config
}) })
) )
this._providers.push( this._providers.push(
@ -493,86 +548,89 @@ class TailwindIntellisense {
) )
this._providers.push( this._providers.push(
vscode.languages.registerHoverProvider(HTML_TYPES, { vscode.languages.registerHoverProvider(
provideHover: (document, position, token) => { [...HTML_TYPES, ...JS_TYPES, 'vue'],
const range1: vscode.Range = new vscode.Range( {
new vscode.Position(Math.max(position.line - 5, 0), 0), provideHover: (document, position, token) => {
position const range1: vscode.Range = new vscode.Range(
) new vscode.Position(Math.max(position.line - 5, 0), 0),
const text1: string = document.getText(range1) position
)
const text1: string = document.getText(range1)
if (!/\bclass(Name)?=['"][^'"]*$/.test(text1)) return if (!/\bclass(Name)?=['"][^'"]*$/.test(text1)) return
const range2: vscode.Range = new vscode.Range( const range2: vscode.Range = new vscode.Range(
new vscode.Position(Math.max(position.line - 5, 0), 0), new vscode.Position(Math.max(position.line - 5, 0), 0),
position.with({ line: position.line + 1 }) position.with({ line: position.line + 1 })
) )
const text2: string = document.getText(range2) const text2: string = document.getText(range2)
let str = text1 + text2.substr(text1.length).match(/^([^"' ]*)/)[0] let str = text1 + text2.substr(text1.length).match(/^([^"' ]*)/)[0]
let matches = str.match(/\bclass(Name)?=["']([^"']*)$/) let matches = str.match(/\bclass(Name)?=["']([^"']*)$/)
if (matches && matches[2]) { if (matches && matches[2]) {
let className = matches[2].split(' ').pop() let className = matches[2].split(' ').pop()
let parts = className.split(':') let parts = className.split(':')
if (typeof dlv(this._tailwind.classNames, parts) === 'string') { if (typeof dlv(this._tailwind.classNames, parts) === 'string') {
let base = parts.pop() let base = parts.pop()
let selector = `.${escapeClassName(className)}` let selector = `.${escapeClassName(className)}`
if (parts.indexOf('hover') !== -1) { if (parts.indexOf('hover') !== -1) {
selector += ':hover' selector += ':hover'
} else if (parts.indexOf('focus') !== -1) { } else if (parts.indexOf('focus') !== -1) {
selector += ':focus' selector += ':focus'
} else if (parts.indexOf('active') !== -1) { } else if (parts.indexOf('active') !== -1) {
selector += ':active' selector += ':active'
} else if (parts.indexOf('group-hover') !== -1) { } else if (parts.indexOf('group-hover') !== -1) {
selector = `.group:hover ${selector}` selector = `.group:hover ${selector}`
}
let hoverStr = new vscode.MarkdownString()
let css = this._tailwind.classNames[base]
let m = css.match(/^(::?[a-z-]+) {(.*?)}/)
if (m) {
selector += m[1]
css = m[2].trim()
}
css = css.replace(/([;{]) /g, '$1\n').replace(/^/gm, ' ')
let code = `${selector} {\n${css}\n}`
let screens = dlv(this._tailwind.config, 'screens', {})
Object.keys(screens).some(screen => {
if (parts.indexOf(screen) !== -1) {
code = `@media (min-width: ${
screens[screen]
}) {\n${code.replace(/^/gm, ' ')}\n}`
return true
} }
return false
})
hoverStr.appendCodeblock(code, 'css')
let hoverRange = new vscode.Range( let hoverStr = new vscode.MarkdownString()
new vscode.Position( let css = this._tailwind.classNames[base]
position.line, let m = css.match(/^(::?[a-z-]+) {(.*?)}/)
position.character + if (m) {
str.length - selector += m[1]
text1.length - css = m[2].trim()
className.length }
), css = css.replace(/([;{]) /g, '$1\n').replace(/^/gm, ' ')
new vscode.Position( let code = `${selector} {\n${css}\n}`
position.line, let screens = dlv(this._tailwind.config, 'screens', {})
position.character + str.length - text1.length
Object.keys(screens).some(screen => {
if (parts.indexOf(screen) !== -1) {
code = `@media (min-width: ${
screens[screen]
}) {\n${code.replace(/^/gm, ' ')}\n}`
return true
}
return false
})
hoverStr.appendCodeblock(code, 'css')
let hoverRange = new vscode.Range(
new vscode.Position(
position.line,
position.character +
str.length -
text1.length -
className.length
),
new vscode.Position(
position.line,
position.character + str.length - text1.length
)
) )
)
return new vscode.Hover(hoverStr, hoverRange) return new vscode.Hover(hoverStr, hoverRange)
}
} }
}
return null return null
}
} }
}) )
) )
this._disposable = vscode.Disposable.from(...this._providers) this._disposable = vscode.Disposable.from(...this._providers)

142
src/htmlElements.ts 100644
View File

@ -0,0 +1,142 @@
module.exports = [
'a',
'abbr',
'acronym',
'address',
'applet',
'area',
'article',
'aside',
'audio',
'b',
'base',
'basefont',
'bdi',
'bdo',
'bgsound',
'big',
'blink',
'blockquote',
'body',
'br',
'button',
'canvas',
'caption',
'center',
'cite',
'code',
'col',
'colgroup',
'command',
'content',
'data',
'datalist',
'dd',
'del',
'details',
'dfn',
'dialog',
'dir',
'div',
'dl',
'dt',
'element',
'em',
'embed',
'fieldset',
'figcaption',
'figure',
'font',
'footer',
'form',
'frame',
'frameset',
'h1',
'head',
'header',
'hgroup',
'hr',
'html',
'i',
'iframe',
'image',
'img',
'input',
'ins',
'isindex',
'kbd',
'keygen',
'label',
'legend',
'li',
'link',
'listing',
'main',
'map',
'mark',
'marquee',
'menu',
'menuitem',
'meta',
'meter',
'multicol',
'nav',
'nextid',
'nobr',
'noembed',
'noframes',
'noscript',
'object',
'ol',
'optgroup',
'option',
'output',
'p',
'param',
'picture',
'plaintext',
'pre',
'progress',
'q',
'rb',
'rp',
'rt',
'rtc',
'ruby',
's',
'samp',
'script',
'section',
'select',
'shadow',
'slot',
'small',
'source',
'spacer',
'span',
'strike',
'strong',
'style',
'sub',
'summary',
'sup',
'table',
'tbody',
'td',
'template',
'textarea',
'tfoot',
'th',
'thead',
'time',
'title',
'tr',
'track',
'tt',
'u',
'ul',
'var',
'video',
'wbr',
'xmp'
]