diff --git a/src/extension.ts b/src/extension.ts index ca54c38dc180e8240640ee2a0af5e80d911af0a0..5f796c94c1b678aeeb1c99ff1350732c2200f194 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,8 +1,7 @@ 'use strict' import * as vscode from 'vscode' -import { dirname } from 'path' -const htmlElements = require('./htmlElements.js') +import { join, dirname } from 'path' const tailwindClassNames = require('tailwind-class-names') const dlv = require('dlv') const Color = require('color') @@ -16,13 +15,16 @@ 'jade', 'razor', 'php', 'blade', + 'vue', 'twig', 'markdown', 'erb', 'handlebars', 'ejs', 'nunjucks', - 'haml' + 'haml', + // for jsx + ...JS_TYPES ] const CSS_TYPES = ['css', 'sass', 'scss', 'less', 'postcss', 'stylus'] @@ -101,25 +103,14 @@ } export function deactivate() {} -function createCompletionItemProvider({ +function createCompletionItemProvider( items, - languages, - regex, - triggerCharacters, + languages: string[], + regex: RegExp, + triggerCharacters: string[], config, - prefix = '', - enable = () => true, - emmet = false -}: { - items? - languages?: string[] - regex?: RegExp - triggerCharacters?: string[] - config? - prefix?: string - enable?: (text: string) => boolean - emmet?: boolean -} = {}): vscode.Disposable { + prefix = '' +): vscode.Disposable { return vscode.languages.registerCompletionItemProvider( languages, { @@ -131,38 +122,21 @@ const separator = config.options.separator || ':' let str const range: vscode.Range = new vscode.Range( - new vscode.Position(0, 0), + new vscode.Position(Math.max(position.line - 5, 0), 0), position ) const text: string = document.getText(range) - if (!enable(text)) return [] - - let lines = text.split(/[\n\r]/) - - let matches = lines - .slice(-5) - .join('\n') - .match(regex) + let matches = text.match(regex) if (matches) { let parts = matches[matches.length - 1].split(' ') str = parts[parts.length - 1] - } else if (emmet) { + } else if (languages.indexOf('html') !== -1) { // match emmet style syntax // e.g. .flex.items-center - let currentLine = lines[lines.length - 1] - 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 lineText = text.split('\n').pop() + matches = lineText.match(/\.([^()#>*^ \[\]=$@{}]*)$/i) if (matches) { let parts = matches[matches.length - 1].split('.') str = parts[parts.length - 1] @@ -197,59 +171,6 @@ ...triggerCharacters ) } -function createConfigItemProvider({ - languages, - items, - enable = () => true -}: { - languages?: string[] - items?: vscode.CompletionItem[] - enable?: (text: string) => boolean -} = {}) { - return vscode.languages.registerCompletionItemProvider( - languages, - { - provideCompletionItems: ( - document: vscode.TextDocument, - position: vscode.Position - ): vscode.CompletionItem[] => { - const range: vscode.Range = new vscode.Range( - new vscode.Position(0, 0), - position - ) - const text: string = document.getText(range) - - if (!enable(text)) return [] - - let lines = text.split(/[\n\r]/) - - let matches = lines - .slice(-5) - .join('\n') - .match(/config\(["']([^"']*)$/) - - if (!matches) return [] - - let objPath = - matches[1] - .replace(/\.[^.]*$/, '') - .replace('.', '.children.') - .trim() + '.children' - let foo = dlv(items, objPath) - - if (foo) { - return Object.keys(foo).map(x => foo[x].item) - } - - return Object.keys(items).map(x => items[x].item) - } - }, - "'", - '"', - '.' - ) -} - function prefixItems(items, str, prefix) { const addPrefix = typeof prefix !== 'undefined' && prefix !== '' && str === prefix @@ -344,7 +265,7 @@ return items } -function createConfigItems(config, prefix = '') { +function createConfigItems(config) { let items = {} let i = 0 @@ -357,7 +278,7 @@ if (depthOf(config[key]) === 0) { if (key === 'plugins') return - item.filterText = item.insertText = `${prefix}${key}` + item.filterText = item.insertText = `.${key}` item.sortText = naturalExpand(i.toString()) if (typeof config[key] === 'string' || typeof config[key] === 'number') { item.detail = config[key] @@ -377,7 +298,7 @@ item.filterText = item.insertText = `${key}.` item.sortText = naturalExpand(i.toString()) item.command = { title: '', command: 'editor.action.triggerSuggest' } - items[key] = { item, children: createConfigItems(config[key], prefix) } + items[key] = { item, children: createConfigItems(config[key]) } } i++ @@ -392,7 +313,6 @@ private _disposable: vscode.Disposable private _tailwind private _items private _configItems - private _prefixedConfigItems constructor(tailwind) { if (tailwind) { @@ -410,235 +330,162 @@ if (separator !== ':') return this._items = createItems(tailwind.classNames, separator, tailwind.config) this._configItems = createConfigItems(tailwind.config) - this._prefixedConfigItems = createConfigItems(tailwind.config, '.') this._providers = [] this._providers.push( - createCompletionItemProvider({ - items: this._items, - languages: JS_TYPES, - regex: /\btw`([^`]*)$/, - triggerCharacters: ['`', ' ', separator], - config: tailwind.config - }) + createCompletionItemProvider( + this._items, + JS_TYPES, + /\btw`([^`]*)$/, + ['`', ' ', separator], + tailwind.config + ) ) this._providers.push( - createCompletionItemProvider({ - items: this._items, - languages: CSS_TYPES, - regex: /@apply ([^;}]*)$/, - triggerCharacters: ['.', separator], - config: tailwind.config, - prefix: '.' - }) + createCompletionItemProvider( + this._items, + CSS_TYPES, + /@apply ([^;}]*)$/, + ['.', separator], + tailwind.config, + '.' + ) ) this._providers.push( - createCompletionItemProvider({ - items: this._items, - languages: HTML_TYPES, - regex: /\bclass=["']([^"']*)$/, // /\bclass(Name)?=(["'])(?!.*?\2)/ - triggerCharacters: ["'", '"', ' ', '.', separator], - config: tailwind.config, - emmet: true - }) + createCompletionItemProvider( + this._items, + HTML_TYPES, + /\bclass(Name)?=["']([^"']*)$/, // /\bclass(Name)?=(["'])(?!.*?\2)/ + ["'", '"', ' ', '.', separator], + tailwind.config + ) ) 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 - }) - ) + vscode.languages.registerCompletionItemProvider( + CSS_TYPES, + { + provideCompletionItems: ( + document: vscode.TextDocument, + position: vscode.Position + ): vscode.CompletionItem[] => { + const range: vscode.Range = new vscode.Range( + new vscode.Position(Math.max(position.line - 5, 0), 0), + position + ) + const text: string = document.getText(range) + + let matches = text.match(/config\(["']([^"']*)$/) + + if (!matches) return [] + + let objPath = + matches[1] + .replace(/\.[^.]*$/, '') + .replace('.', '.children.') + .trim() + '.children' + let foo = dlv(this._configItems, objPath) - // Vue.js - this._providers.push( - createCompletionItemProvider({ - items: this._items, - languages: ['vue'], - regex: /\bclass=["']([^"']*)$/, - enable: text => { - if ( - text.indexOf('') === -1 - ) { - return true + if (foo) { + console.log(Object.keys(foo).map(x => foo[x].item)) + return Object.keys(foo).map(x => foo[x].item) + } + + return Object.keys(this._configItems).map( + x => this._configItems[x].item + ) } - return false }, - triggerCharacters: ["'", '"', ' ', separator] - .concat([ - Object.keys( - vscode.workspace.getConfiguration('emmet.includeLanguages') - ).indexOf('vue-html') !== -1 && '.' - ]) - .filter(Boolean), - config: tailwind.config, - 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('') === -1 - ) { - return true - } - return false - }, - triggerCharacters: ["'", '"', ' ', separator], - config: tailwind.config - }) - ) - this._providers.push( - createCompletionItemProvider({ - items: this._items, - languages: ['vue'], - regex: /@apply ([^;}]*)$/, - triggerCharacters: ['.', separator], - config: tailwind.config, - enable: text => { - if ( - text.indexOf('') === -1 - ) { - return true - } - return false - } - }) + "'", + '"', + '.' + ) ) this._providers.push( - createConfigItemProvider({ - languages: CSS_TYPES, - items: this._prefixedConfigItems - }) - ) + vscode.languages.registerHoverProvider(HTML_TYPES, { + provideHover: (document, position, token) => { + const range1: vscode.Range = new vscode.Range( + new vscode.Position(Math.max(position.line - 5, 0), 0), + position + ) + const text1: string = document.getText(range1) - this._providers.push( - createConfigItemProvider({ - languages: ['vue'], - items: this._configItems, - enable: text => { - if ( - text.indexOf('') === -1 - ) { - return true - } - return false - } - }) - ) + if (!/\bclass(Name)?=['"][^'"]*$/.test(text1)) return - this._providers.push( - vscode.languages.registerHoverProvider( - [...HTML_TYPES, ...JS_TYPES, 'vue'], - { - provideHover: (document, position, token) => { - const range1: vscode.Range = new vscode.Range( - new vscode.Position(Math.max(position.line - 5, 0), 0), - position - ) - const text1: string = document.getText(range1) + const range2: vscode.Range = new vscode.Range( + new vscode.Position(Math.max(position.line - 5, 0), 0), + position.with({ line: position.line + 1 }) + ) + const text2: string = document.getText(range2) - if (!/\bclass(Name)?=['"][^'"]*$/.test(text1)) return + let str = text1 + text2.substr(text1.length).match(/^([^"' ]*)/)[0] + let matches = str.match(/\bclass(Name)?=["']([^"']*)$/) - const range2: vscode.Range = new vscode.Range( - new vscode.Position(Math.max(position.line - 5, 0), 0), - position.with({ line: position.line + 1 }) - ) - const text2: string = document.getText(range2) + if (matches && matches[2]) { + let className = matches[2].split(' ').pop() + let parts = className.split(':') - let str = text1 + text2.substr(text1.length).match(/^([^"' ]*)/)[0] - let matches = str.match(/\bclass(Name)?=["']([^"']*)$/) + if (typeof dlv(this._tailwind.classNames, parts) === 'string') { + let base = parts.pop() + let selector = `.${escapeClassName(className)}` - if (matches && matches[2]) { - let className = matches[2].split(' ').pop() - let parts = className.split(':') - - if (typeof dlv(this._tailwind.classNames, parts) === 'string') { - let base = parts.pop() - let selector = `.${escapeClassName(className)}` + if (parts.indexOf('hover') !== -1) { + selector += ':hover' + } else if (parts.indexOf('focus') !== -1) { + selector += ':focus' + } else if (parts.indexOf('active') !== -1) { + selector += ':active' + } else if (parts.indexOf('group-hover') !== -1) { + selector = `.group:hover ${selector}` + } - if (parts.indexOf('hover') !== -1) { - selector += ':hover' - } else if (parts.indexOf('focus') !== -1) { - selector += ':focus' - } else if (parts.indexOf('active') !== -1) { - selector += ':active' - } else if (parts.indexOf('group-hover') !== -1) { - 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', {}) - 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() + Object.keys(screens).some(screen => { + if (parts.indexOf(screen) !== -1) { + code = `@media (min-width: ${ + screens[screen] + }) {\n${code.replace(/^/gm, ' ')}\n}` + return true } - css = css.replace(/([;{]) /g, '$1\n').replace(/^/gm, ' ') - let code = `${selector} {\n${css}\n}` - let screens = dlv(this._tailwind.config, 'screens', {}) + return false + }) + hoverStr.appendCodeblock(code, 'css') - 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 - ) + 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) diff --git a/src/htmlElements.ts b/src/htmlElements.ts deleted file mode 100644 index 4bb7b1c1220b17799c08d5b923dd5e8ac0e4f280..0000000000000000000000000000000000000000 --- a/src/htmlElements.ts +++ /dev/null @@ -1,142 +0,0 @@ -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' -]