initial refactor

master
Brad Cornes 2020-04-11 22:20:45 +01:00
parent f5dfe02f74
commit 072809d9b7
66 changed files with 19551 additions and 1416 deletions

7
.gitignore vendored
View File

@ -1,7 +1,2 @@
dist
node_modules
.vscode-test/
.vsix
*.vsix
.DS_Store
.rts2_cache_cjs
dist

15
.vscode/launch.json vendored
View File

@ -1,15 +0,0 @@
{
"version": "0.1.0",
"configurations": [
{
"name": "Launch Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": ["${workspaceRoot}/dist/**/*.js"]
}
]
}

View File

@ -1,9 +0,0 @@
*
*/**
**/.DS_Store
!package.json
!README.md
!dist/index.js
!dist/server/index.js
!resources/**/*

View File

@ -1,31 +0,0 @@
# Changelog
## 0.2.0
- Support for Tailwind v1 via LSP 🎉
- Support for multi-root workspaces
- Support for reason, slim, edge, njk, svelte files (thanks [@nhducit](https://github.com/nhducit), [@wayness](https://github.com/wayness), [@mattwaler](https://github.com/mattwaler), [@guillaumebriday](https://github.com/guillaumebriday))
- Support for non-default Tailwind separators
- Add `@variants` completions
- Better support for dynamic class(Name) values in JSX
- Disables Emmet support by default. This can be enabled via the `tailwindCSS.emmetCompletions` setting
## 0.1.16
- add support for [EEx templates](https://hexdocs.pm/phoenix/templates.html), via [vscode-elixir](https://marketplace.visualstudio.com/items?itemName=mjmcloug.vscode-elixir) thanks [@dhc02](https://github.com/dhc02)
## 0.1.15
- add support for [leaf](https://github.com/vapor/leaf) files (#16)
## 0.1.10
- add syntax definitions for `@apply` and `config()`:
**Before:**
<img src="https://user-images.githubusercontent.com/2615508/44740655-ed02ee80-aaf2-11e8-8d3e-1075e0801fd7.png" alt="Syntax highlighting before update" width="345" />
**After:**
<img src="https://user-images.githubusercontent.com/2615508/44740606-cba20280-aaf2-11e8-92b8-42adbfe54c61.png" alt="Syntax highlighting after update" width="345" />

View File

@ -1,50 +0,0 @@
# Tailwind CSS IntelliSense
> [Tailwind CSS](https://tailwindcss.com/) class name completion for VS Code
**[Get it from the VS Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)**
<img src="https://raw.githubusercontent.com/bradlc/vscode-tailwindcss/master/img/html.gif" alt="HTML autocompletion" width="750">
## Features
Tailwind CSS IntelliSense uses your projects Tailwind installation and configuration to provide suggestions as you type.
It also includes features that improve the overall Tailwind experience, including improved syntax highlighting, and CSS previews.
### HTML (including Vue, JSX, PHP etc.)
- [Class name suggestions, including support for Emmet syntax](#class-name-suggestions-including-support-for-emmet-syntax)
- Suggestions include color previews where applicable, for example for text and background colors
- They also include a preview of the actual CSS for that class name
- [CSS preview when hovering over class names](#css-preview-when-hovering-over-class-names)
### CSS
- [Suggestions when using `@apply` and `config()`](#suggestions-when-using-apply-and-config)
- Suggestions when using the `@screen` directive
- [Improves syntax highlighting when using `@apply` and `config()`](#improves-syntax-highlighting-when-using-apply-and-config)
## Examples
#### Class name suggestions, including support for Emmet syntax
<img src="https://raw.githubusercontent.com/bradlc/vscode-tailwindcss/master/img/html.gif" alt="HTML autocompletion" width="750">
#### CSS preview when hovering over class names
<img src="https://raw.githubusercontent.com/bradlc/vscode-tailwindcss/master/img/html-hover.gif" alt="HTML hover preview" width="750">
#### Suggestions when using `@apply` and `config()`
<img src="https://raw.githubusercontent.com/bradlc/vscode-tailwindcss/master/img/css.gif" alt="CSS autocompletion" width="750">
#### Improves syntax highlighting when using `@apply` and `config()`
Before:
<img src="https://raw.githubusercontent.com/bradlc/vscode-tailwindcss/master/img/css-highlighting-before.png" alt="CSS syntax highlighting before" width="400">
After:
<img src="https://raw.githubusercontent.com/bradlc/vscode-tailwindcss/master/img/css-highlighting-after.png" alt="CSS syntax highlighting after" width="400">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 592 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

6
lerna.json 100644
View File

@ -0,0 +1,6 @@
{
"packages": [
"packages/*"
],
"version": "independent"
}

6630
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,84 +1,11 @@
{
"name": "vscode-tailwindcss",
"displayName": "Tailwind CSS IntelliSense",
"description": "Tailwind CSS class name completion",
"version": "0.2.0",
"publisher": "bradlc",
"engines": {
"vscode": "^1.30.0"
},
"categories": [
"Other"
],
"galleryBanner": {
"color": "#f1f5f8"
},
"icon": "resources/icon.png",
"keywords": [
"tailwind",
"tailwindcss",
"css",
"intellisense",
"autocomplete",
"vscode"
],
"activationEvents": [
"workspaceContains:**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js"
],
"main": "./dist/index.js",
"contributes": {
"grammars": [
{
"scopeName": "source.css.tailwind",
"path": "./resources/syntaxes/tailwind.tmLanguage.json",
"injectTo": [
"source.css",
"source.css.scss",
"source.css.less",
"source.css.postcss"
]
}
],
"configuration": {
"type": "object",
"title": "Tailwind CSS IntelliSense configuration",
"properties": {
"tailwindCSS.emmetCompletions": {
"type": "boolean",
"default": false,
"description": "Enable class name completions for Emmet-style syntax"
}
}
}
},
"preview": true,
"name": "root",
"private": true,
"scripts": {
"vscode:prepublish": "npm run build",
"build": "ncc build src/index.ts --out dist --minify --external vscode && ncc build src/server.ts --out dist/server --minify",
"watch": "ncc build src/index.ts --out dist --watch --external vscode",
"start": "npm run watch",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "npm run build && node ./node_modules/vscode/bin/test"
},
"author": "Brad Cornes <hello@bradley.dev>",
"license": "MIT",
"homepage": "https://github.com/bradlc/vscode-tailwindcss/blob/master/README.md",
"bugs": {
"url": "https://github.com/bradlc/vscode-tailwindcss/issues",
"email": "hello@bradley.dev"
},
"repository": {
"type": "git",
"url": "https://github.com/bradlc/vscode-tailwindcss.git"
"bootstrap": "lerna bootstrap",
"dev": "lerna run --parallel dev"
},
"devDependencies": {
"@types/node": "^11.13.5",
"@zeit/ncc": "^0.17.4",
"color": "^3.1.0",
"dlv": "^1.1.2",
"tailwindcss-language-server": "0.0.1",
"tmp": "0.0.33",
"vscode": "^1.1.26",
"vscode-languageclient": "^5.2.1"
"lerna": "^3.20.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
{
"name": "tailwindcss-class-names",
"version": "0.0.1",
"description": "",
"main": "dist/index.js",
"scripts": {
"test": "jest",
"dev": "ncc build src/index.js --watch",
"build": "ncc build src/index.js --minify"
},
"keywords": [],
"author": "Brad Cornes <hello@bradley.dev>",
"license": "MIT",
"dependencies": {
"chokidar": "^3.3.1",
"dlv": "^1.1.3",
"dset": "^2.0.1",
"glob": "^7.1.6",
"import-from": "^3.0.0",
"pkg-up": "^3.1.0",
"postcss-selector-parser": "^6.0.2",
"semver": "^7.1.3",
"stack-trace": "0.0.10",
"tiny-invariant": "^1.1.0"
},
"devDependencies": {
"@zeit/ncc": "^0.21.1",
"esm": "^3.2.25",
"jest": "^25.1.0"
}
}

View File

@ -0,0 +1,188 @@
import selectorParser from 'postcss-selector-parser'
import fs from 'fs'
import path from 'path'
import dset from 'dset'
import dlv from 'dlv'
function createSelectorFromNodes(nodes) {
if (nodes.length === 0) return null
const selector = selectorParser.selector()
for (let i = 0; i < nodes.length; i++) {
selector.append(nodes[i])
}
return String(selector).trim()
}
function getClassNamesFromSelector(selector) {
const classNames = []
const { nodes: subSelectors } = selectorParser().astSync(selector)
for (let i = 0; i < subSelectors.length; i++) {
// const final = subSelectors[i].nodes[subSelectors[i].nodes.length - 1]
// if (final.type === 'class') {
// const scope = subSelectors[i].nodes.slice(
// 0,
// subSelectors[i].nodes.length - 1
// )
// classNames.push({
// className: String(final).trim(),
// scope: createSelectorFromNodes(scope)
// })
// }
let scope = []
for (let j = 0; j < subSelectors[i].nodes.length; j++) {
let node = subSelectors[i].nodes[j]
let pseudo = []
if (node.type === 'class') {
let next = subSelectors[i].nodes[j + 1]
while (next && next.type === 'pseudo') {
pseudo.push(next)
j++
next = subSelectors[i].nodes[j + 1]
}
classNames.push({
className: String(node)
.trim()
.substr(1),
scope: createSelectorFromNodes(scope),
__rule: j === subSelectors[i].nodes.length - 1,
// __pseudo: createSelectorFromNodes(pseudo)
__pseudo: pseudo.length === 0 ? null : pseudo.map(String)
})
}
scope.push(node, ...pseudo)
}
}
// console.log(classNames)
return classNames
}
// console.log(getClassNamesFromSelector('h1, h2, h3, .foo .bar, .baz'))
// const css = fs.readFileSync(path.resolve(__dirname, 'tailwind.css'), 'utf8')
async function process(ast) {
const start = new Date()
const tree = {}
const commonContext = {}
ast.root.walkRules(rule => {
const classNames = getClassNamesFromSelector(rule.selector)
const decls = { __decls: true }
rule.walkDecls(decl => {
decls[decl.prop] = decl.value
})
let p = rule
const keys = []
while (p.parent.type !== 'root') {
p = p.parent
if (p.type === 'atrule') {
keys.push(`@${p.name} ${p.params}`)
}
}
for (let i = 0; i < classNames.length; i++) {
const context = keys.concat([])
const baseKeys = classNames[i].className.split('__TAILWIND_SEPARATOR__')
const contextKeys = baseKeys.slice(0, baseKeys.length - 1)
if (classNames[i].scope) {
let index = []
const existing = dlv(tree, baseKeys)
if (typeof existing !== 'undefined') {
if (Array.isArray(existing)) {
const scopeIndex = existing.findIndex(
x => x.__scope === classNames[i].scope
)
if (scopeIndex > -1) {
keys.unshift(scopeIndex)
index.push(scopeIndex)
} else {
keys.unshift(existing.length)
index.push(existing.length)
}
} else {
if (existing.__scope !== classNames[i].scope) {
dset(tree, baseKeys, [existing])
keys.unshift(1)
index.push(1)
}
}
}
if (classNames[i].__rule) {
dset(tree, [...baseKeys, ...index, '__rule'], true)
dsetEach(tree, [...baseKeys, ...keys], decls)
}
if (classNames[i].__pseudo) {
dset(tree, [...baseKeys, ...keys, '__pseudo'], classNames[i].__pseudo)
}
dset(tree, [...baseKeys, ...index, '__scope'], classNames[i].scope)
} else {
if (classNames[i].__rule) {
dset(tree, [...baseKeys, '__rule'], true)
dsetEach(tree, [...baseKeys, ...keys], decls)
} else {
dset(tree, [...baseKeys, ...keys], {})
}
if (classNames[i].__pseudo) {
dset(tree, [...baseKeys, ...keys, '__pseudo'], classNames[i].__pseudo)
}
}
// common context
if (classNames[i].__pseudo) {
context.push(...classNames[i].__pseudo)
}
for (let i = 0; i < contextKeys.length; i++) {
if (typeof commonContext[contextKeys[i]] === 'undefined') {
commonContext[contextKeys[i]] = context
} else {
commonContext[contextKeys[i]] = intersection(
commonContext[contextKeys[i]],
context
)
}
}
}
})
// console.log(`${new Date() - start}ms`)
// console.log(tree)
// console.log(commonContext)
return { classNames: tree, context: commonContext }
}
function intersection(arr1, arr2) {
return arr1.filter(value => arr2.indexOf(value) !== -1)
}
function dsetEach(obj, keys, values) {
const k = Object.keys(values)
for (let i = 0; i < k.length; i++) {
dset(obj, [...keys, k[i]], values[k[i]])
}
}
export default process
// process(`
// .bg-red {
// background-color: red;
// }
// .bg-red {
// color: white;
// }`).then(x => {
// console.log(x)
// })

View File

@ -0,0 +1,71 @@
import * as path from 'path'
import stackTrace from 'stack-trace'
import pkgUp from 'pkg-up'
function isObject(variable) {
return Object.prototype.toString.call(variable) === '[object Object]'
}
export default function getPlugins(config) {
let plugins = config.plugins
if (!Array.isArray(plugins)) {
return []
}
return plugins.map(plugin => {
let pluginConfig = plugin.config
if (!isObject(pluginConfig)) {
pluginConfig = {}
}
let contributes = {
theme: isObject(pluginConfig.theme)
? Object.keys(pluginConfig.theme)
: [],
variants: isObject(pluginConfig.variants)
? Object.keys(pluginConfig.variants)
: []
}
const fn = plugin.handler || plugin
const fnName =
typeof fn.name === 'string' && fn.name !== 'handler' && fn.name !== ''
? fn.name
: null
try {
fn()
} catch (e) {
const trace = stackTrace.parse(e)
if (trace.length === 0)
return {
name: fnName
}
const file = trace[0].fileName
const dir = path.dirname(file)
let pkg = pkgUp.sync({ cwd: dir })
if (!pkg)
return {
name: fnName
}
try {
pkg = __non_webpack_require__(pkg)
} catch (_) {
return {
name: fnName
}
}
if (pkg.name && path.resolve(dir, pkg.main || 'index.js') === file) {
return {
name: pkg.name,
homepage: pkg.homepage,
contributes
}
}
}
return {
name: fnName
}
})
}

View File

@ -0,0 +1,39 @@
import semver from 'semver'
import dlv from 'dlv'
export default function getVariants({ config, version, postcss }) {
let variants = ['responsive', 'hover']
semver.gte(version, '0.3.0') && variants.push('focus', 'group-hover')
semver.gte(version, '0.5.0') && variants.push('active')
semver.gte(version, '0.7.0') && variants.push('focus-within')
semver.gte(version, '1.1.0') &&
variants.push('first', 'last', 'odd', 'even', 'disabled', 'visited')
let plugins = config.plugins
if (!Array.isArray(plugins)) {
plugins = []
}
plugins.forEach(plugin => {
try {
;(plugin.handler || plugin)({
addUtilities: () => {},
addComponents: () => {},
addBase: () => {},
addVariant: name => {
variants.push(name)
},
e: x => x,
prefix: x => x,
theme: (path, defaultValue) =>
dlv(config, `theme.${path}`, defaultValue),
variants: () => [],
config: (path, defaultValue) => dlv(config, path, defaultValue),
postcss
})
} catch (_) {
console.error(_)
}
})
return variants
}

View File

@ -0,0 +1,86 @@
/**
* Adapted from: https://github.com/elastic/require-in-the-middle
*/
import Module from 'module'
export default function Hook(find, onrequire) {
if (!(this instanceof Hook)) return new Hook(find, onrequire)
if (typeof Module._resolveFilename !== 'function') {
throw new Error(
`Error: Expected Module._resolveFilename to be a function (was: ${typeof Module._resolveFilename}) - aborting!`
)
}
this.cache = {}
this.deps = []
this._unhooked = false
this._origRequire = Module.prototype.require
let self = this
let patching = {}
this._require = Module.prototype.require = function(request) {
if (self._unhooked) {
// if the patched require function could not be removed because
// someone else patched it after it was patched here, we just
// abort and pass the request onwards to the original require
return self._origRequire.apply(this, arguments)
}
let filename = Module._resolveFilename(request, this)
// return known patched modules immediately
if (self.cache.hasOwnProperty(filename)) {
return self.cache[filename]
}
// Check if this module has a patcher in-progress already.
// Otherwise, mark this module as patching in-progress.
let patched = patching[filename]
if (!patched) {
patching[filename] = true
}
let exports = self._origRequire.apply(this, arguments)
if (filename !== find) {
if (self._watching) {
self.deps.push(filename)
}
return exports
}
// If it's already patched, just return it as-is.
if (patched) return exports
// The module has already been loaded,
// so the patching mark can be cleaned up.
delete patching[filename]
// only call onrequire the first time a module is loaded
if (!self.cache.hasOwnProperty(filename)) {
// ensure that the cache entry is assigned a value before calling
// onrequire, in case calling onrequire requires the same module.
self.cache[filename] = exports
self.cache[filename] = onrequire(exports)
}
return self.cache[filename]
}
}
Hook.prototype.unhook = function() {
this._unhooked = true
if (this._require === Module.prototype.require) {
Module.prototype.require = this._origRequire
}
}
Hook.prototype.watch = function() {
this._watching = true
}
Hook.prototype.unwatch = function() {
this._watching = false
}

View File

@ -0,0 +1,128 @@
import extractClassNames from './extractClassNames.mjs'
import Hook from './hook.mjs'
import dlv from 'dlv'
import dset from 'dset'
import importFrom from 'import-from'
import nodeGlob from 'glob'
import * as path from 'path'
import chokidar from 'chokidar'
import semver from 'semver'
import invariant from 'tiny-invariant'
import getPlugins from './getPlugins'
import getVariants from './getVariants'
import resolveConfig from './resolveConfig'
function glob(pattern, options = {}) {
return new Promise((resolve, reject) => {
let g = new nodeGlob.Glob(pattern, options)
let matches = []
let max = dlv(options, 'max', Infinity)
g.on('match', match => {
matches.push(path.resolve(options.cwd || process.cwd(), match))
if (matches.length === max) {
g.abort()
resolve(matches)
}
})
g.on('end', () => {
resolve(matches)
})
g.on('error', reject)
})
}
function arraysEqual(arr1, arr2) {
return JSON.stringify(arr1.sort()) === JSON.stringify(arr2.sort())
}
export default async function getClassNames(
cwd = process.cwd(),
{ onChange = () => {} } = {}
) {
let configPath
let postcss
let tailwindcss
let version
try {
configPath = await glob(
'**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js',
{
cwd,
ignore: '**/node_modules/**',
max: 1
}
)
invariant(configPath.length === 1, 'No Tailwind CSS config found.')
configPath = configPath[0]
postcss = importFrom(cwd, 'postcss')
tailwindcss = importFrom(cwd, 'tailwindcss')
version = importFrom(cwd, 'tailwindcss/package.json').version
} catch (_) {
return null
}
async function run() {
const sepLocation = semver.gte(version, '0.99.0')
? ['separator']
: ['options', 'separator']
let userSeperator
let hook = Hook(configPath, exports => {
userSeperator = dlv(exports, sepLocation)
dset(exports, sepLocation, '__TAILWIND_SEPARATOR__')
return exports
})
hook.watch()
const config = __non_webpack_require__(configPath)
hook.unwatch()
const ast = await postcss([tailwindcss(configPath)]).process(
`
@tailwind components;
@tailwind utilities;
`,
{ from: undefined }
)
hook.unhook()
if (typeof userSeperator !== 'undefined') {
dset(config, sepLocation, userSeperator)
} else {
delete config[sepLocation]
}
return {
config: resolveConfig({ cwd, config }),
separator: typeof userSeperator === 'undefined' ? ':' : userSeperator,
classNames: await extractClassNames(ast),
dependencies: [configPath, ...hook.deps],
plugins: getPlugins(config),
variants: getVariants({ config, version, postcss })
}
}
let watcher
function watch(files) {
if (watcher) watcher.close()
watcher = chokidar
.watch(files)
.on('change', handleChange)
.on('unlink', handleChange)
}
let result = await run()
watch(result.dependencies)
async function handleChange() {
const prevDeps = result.dependencies
result = await run()
if (!arraysEqual(prevDeps, result.dependencies)) {
watch(result.dependencies)
}
onChange(result)
}
return result
}

View File

@ -0,0 +1,31 @@
import importFrom from 'import-from'
import * as path from 'path'
export default function resolveConfig({ cwd, config }) {
let resolve = x => x
if (typeof config === 'string') {
if (!cwd) {
cwd = path.dirname(config)
}
config = __non_webpack_require__(config)
}
try {
resolve = importFrom(cwd, 'tailwindcss/resolveConfig.js')
} catch (_) {
try {
const resolveConfig = importFrom(
cwd,
'tailwindcss/lib/util/resolveConfig.js'
)
const defaultConfig = importFrom(
cwd,
'tailwindcss/stubs/defaultConfig.stub.js'
)
resolve = config => resolveConfig([config, defaultConfig])
} catch (_) {}
}
return resolve(config)
}

View File

@ -0,0 +1,256 @@
let postcss = require('postcss')
const esmImport = require('esm')(module)
const process = esmImport('../src/extractClassNames.mjs').default
postcss = postcss([postcss.plugin('no-op', () => () => {})])
const processCss = async css =>
process(await postcss.process(css, { from: undefined }))
test('foo', async () => {
const result = await processCss(`
@media (min-width: 640px) {
.sm__TAILWIND_SEPARATOR__bg-red {
background-color: red;
}
.sm__TAILWIND_SEPARATOR__hover__TAILWIND_SEPARATOR__bg-red:hover {
background-color: red;
}
}
.hover__TAILWIND_SEPARATOR__bg-red:hover {
background-color: red;
}
`)
expect(result).toEqual({
context: {
sm: ['@media (min-width: 640px)'],
hover: [':hover']
},
classNames: {
sm: {
'bg-red': {
__rule: true,
'@media (min-width: 640px)': {
__decls: true,
'background-color': 'red'
}
},
hover: {
'bg-red': {
__rule: true,
'@media (min-width: 640px)': {
__decls: true,
__pseudo: [':hover'],
'background-color': 'red'
}
}
}
},
hover: {
'bg-red': {
__rule: true,
__decls: true,
__pseudo: [':hover'],
'background-color': 'red'
}
}
}
})
})
test('processes basic css', async () => {
const result = await processCss(`
.bg-red {
background-color: red;
}
`)
expect(result).toEqual({
context: {},
classNames: {
'bg-red': {
__rule: true,
__decls: true,
'background-color': 'red'
}
}
})
})
test('processes pseudo selectors', async () => {
const result = await processCss(`
.bg-red:first-child::after {
background-color: red;
}
`)
expect(result).toEqual({
context: {},
classNames: {
'bg-red': {
__rule: true,
__decls: true,
__pseudo: [':first-child', '::after'],
'background-color': 'red'
}
}
})
})
test('processes pseudo selectors in scope', async () => {
const result = await processCss(`
.scope:hover .bg-red {
background-color: red;
}
`)
expect(result).toEqual({
context: {},
classNames: {
scope: {
__pseudo: [':hover']
},
'bg-red': {
__rule: true,
__decls: true,
__scope: '.scope:hover',
'background-color': 'red'
}
}
})
})
test('processes multiple class names in the same rule', async () => {
const result = await processCss(`
.bg-red,
.bg-red-again {
background-color: red;
}
`)
expect(result).toEqual({
context: {},
classNames: {
'bg-red': {
__rule: true,
__decls: true,
'background-color': 'red'
},
'bg-red-again': {
__rule: true,
__decls: true,
'background-color': 'red'
}
}
})
})
test('processes media queries', async () => {
const result = await processCss(`
@media (min-width: 768px) {
.bg-red {
background-color: red;
}
}
`)
expect(result).toEqual({
context: {},
classNames: {
'bg-red': {
__rule: true,
'@media (min-width: 768px)': {
__decls: true,
'background-color': 'red'
}
}
}
})
})
test('merges declarations', async () => {
const result = await processCss(`
.bg-red {
background-color: red;
}
.bg-red {
color: white;
}
`)
expect(result).toEqual({
context: {},
classNames: {
'bg-red': {
__rule: true,
__decls: true,
'background-color': 'red',
color: 'white'
}
}
})
})
test('processes class name scope', async () => {
const result = await processCss(`
.scope .bg-red {
background-color: red;
}
`)
expect(result).toEqual({
context: {},
classNames: {
scope: {},
'bg-red': {
__rule: true,
__decls: true,
__scope: '.scope',
'background-color': 'red'
}
}
})
})
test('processes multiple scopes for the same class name', async () => {
const result = await processCss(`
.scope1 .bg-red {
background-color: red;
}
.scope2 + .bg-red {
background-color: red;
}
.scope3 > .bg-red {
background-color: red;
}
`)
expect(result).toEqual({
context: {},
classNames: {
scope1: {},
scope2: {},
scope3: {},
'bg-red': [
{
__rule: true,
__decls: true,
__scope: '.scope1',
'background-color': 'red'
},
{
__rule: true,
__decls: true,
__scope: '.scope2 +',
'background-color': 'red'
},
{
__rule: true,
__decls: true,
__scope: '.scope3 >',
'background-color': 'red'
}
]
}
})
})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
{
"name": "tailwindcss-language-server",
"version": "0.0.1",
"description": "",
"main": "dist/src/server/index.js",
"scripts": {
"dev1": "ncc build src/server.ts --watch -o dist/foo.js",
"dev": "glob-exec --foreach --parallel \"src/*.ts\" -- \"ncc build {{file}} --watch -o dist/{{file.toString().replace(/\\.ts$/, '')}}\"",
"build": "ncc build src/server.ts --minify"
},
"keywords": [],
"author": "Brad Cornes <hello@bradley.dev",
"license": "MIT",
"engines": {
"node": "*"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/register": "^7.9.0",
"@types/node": "^13.9.3",
"@zeit/ncc": "^0.22.0",
"dlv": "^1.1.3",
"glob-exec": "^0.1.1",
"tailwindcss-class-names": "0.0.1",
"typescript": "^3.8.3",
"vscode-languageserver": "^5.2.1",
"vscode-uri": "^2.1.1"
}
}

View File

@ -0,0 +1,378 @@
import { State } from '../util/state'
import {
CompletionItem,
CompletionItemKind,
CompletionParams,
Range,
MarkupKind,
CompletionList,
} from 'vscode-languageserver'
const dlv = require('dlv')
import removeMeta from '../util/removeMeta'
import { getColor, getColorFromString } from '../util/color'
import { isHtmlDoc } from '../util/html'
import { isCssDoc } from '../util/css'
import { findLast, findJsxStrings, arrFindLast } from '../util/find'
import { stringifyConfigValue } from '../util/stringify'
import isObject from '../util/isObject'
function completionsFromClassList(
state: State,
classList: string,
classListRange: Range
): CompletionList {
let classNames = classList.split(/[\s+]/)
const partialClassName = classNames[classNames.length - 1]
// TODO
let sep = ':'
let parts = partialClassName.split(sep)
let subset: any
let isSubset: boolean = false
let replacementRange = {
...classListRange,
start: {
...classListRange.start,
character: classListRange.end.character - partialClassName.length,
},
}
for (let i = parts.length - 1; i > 0; i--) {
let keys = parts.slice(0, i).filter(Boolean)
subset = dlv(state.classNames.classNames, keys)
if (typeof subset !== 'undefined' && typeof subset.__rule === 'undefined') {
isSubset = true
replacementRange = {
...replacementRange,
start: {
...replacementRange.start,
character:
replacementRange.start.character +
keys.join(sep).length +
sep.length,
},
}
break
}
}
return {
isIncomplete: false,
items: Object.keys(isSubset ? subset : state.classNames.classNames).map(
(className) => {
let kind: CompletionItemKind = CompletionItemKind.Constant
let documentation: string = null
if (isContextItem(state, [className])) {
kind = CompletionItemKind.Module
} else {
const color = getColor(state, [className])
if (color) {
kind = CompletionItemKind.Color
documentation = color
}
}
return {
label: className,
kind,
documentation,
textEdit: {
newText: className,
range: replacementRange,
},
}
}
),
}
}
function provideClassAttributeCompletions(
state: State,
{ context, position, textDocument }: CompletionParams
): CompletionList {
let doc = state.editor.documents.get(textDocument.uri)
let str = doc.getText({
start: { line: Math.max(position.line - 10, 0), character: 0 },
end: position,
})
const match = findLast(/\bclass(?:Name)?=(?<initial>['"`{])/gi, str)
if (match === null) {
return null
}
const rest = str.substr(match.index + match[0].length)
if (match.groups.initial === '{') {
const strings = findJsxStrings('{' + rest)
const lastOpenString = arrFindLast(
strings,
(string) => typeof string.end === 'undefined'
)
if (lastOpenString) {
const classList = str.substr(
str.length - rest.length + lastOpenString.start - 1
)
return completionsFromClassList(state, classList, {
start: {
line: position.line,
character: position.character - classList.length,
},
end: position,
})
}
return null
}
if (rest.indexOf(match.groups.initial) !== -1) {
return null
}
return completionsFromClassList(state, rest, {
start: {
line: position.line,
character: position.character - rest.length,
},
end: position,
})
}
function provideAtApplyCompletions(
state: State,
{ context, position, textDocument }: CompletionParams
): CompletionList {
let doc = state.editor.documents.get(textDocument.uri)
let str = doc.getText({
start: { line: Math.max(position.line - 30, 0), character: 0 },
end: position,
})
const match = findLast(/@apply\s+(?<classList>[^;}]*)$/gi, str)
if (match === null) {
return null
}
const classList = match.groups.classList
return completionsFromClassList(state, classList, {
start: {
line: position.line,
character: position.character - classList.length,
},
end: position,
})
}
function provideClassNameCompletions(
state: State,
params: CompletionParams
): CompletionList {
let doc = state.editor.documents.get(params.textDocument.uri)
if (isHtmlDoc(doc)) {
return provideClassAttributeCompletions(state, params)
}
if (isCssDoc(doc)) {
return provideAtApplyCompletions(state, params)
}
return null
}
function provideCssHelperCompletions(
state: State,
{ position, textDocument }: CompletionParams
): CompletionList {
let doc = state.editor.documents.get(textDocument.uri)
if (!isCssDoc(doc)) {
return null
}
let text = doc.getText({
start: { line: position.line, character: 0 },
// read one extra character so we can see if it's a ] later
end: { line: position.line, character: position.character + 1 },
})
const match = text
.substr(0, text.length - 1) // don't include that extra character from earlier
.match(/\b(?<helper>config|theme)\(['"](?<keys>[^'"]*)$/)
if (match === null) {
return null
}
let base =
match.groups.helper === 'config'
? state.config
: dlv(state.config, 'theme', {})
let parts = match.groups.keys.split(/([\[\].]+)/)
let keys = parts.filter((_, i) => i % 2 === 0)
let separators = parts.filter((_, i) => i % 2 !== 0)
// let obj =
// keys.length === 1 ? base : dlv(base, keys.slice(0, keys.length - 1), {})
// if (!isObject(obj)) return null
function totalLength(arr: string[]): number {
return arr.reduce((acc, cur) => acc + cur.length, 0)
}
let obj: any
let offset: number = 0
let separator: string = separators.length
? separators[separators.length - 1]
: null
if (keys.length === 1) {
obj = base
} else {
for (let i = keys.length - 1; i > 0; i--) {
let o = dlv(base, keys.slice(0, i))
if (isObject(o)) {
obj = o
offset = totalLength(parts.slice(i * 2))
separator = separators[i - 1]
break
}
}
}
if (!obj) return null
return {
isIncomplete: false,
items: Object.keys(obj).map((item) => {
let color = getColorFromString(obj[item])
const replaceDot: boolean =
item.indexOf('.') !== -1 && separator && separator.endsWith('.')
const insertClosingBrace: boolean =
text.charAt(text.length - 1) !== ']' &&
(replaceDot || (separator && separator.endsWith('[')))
return {
label: item,
filterText: `${replaceDot ? '.' : ''}${item}`,
kind: color
? CompletionItemKind.Color
: isObject(obj[item])
? CompletionItemKind.Module
: CompletionItemKind.Property,
detail: stringifyConfigValue(obj[item]),
documentation: color,
textEdit: {
newText: `${replaceDot ? '[' : ''}${item}${
insertClosingBrace ? ']' : ''
}`,
range: {
start: {
line: position.line,
character:
position.character -
keys[keys.length - 1].length -
(replaceDot ? 1 : 0) -
offset,
},
end: position,
},
},
data: 'helper',
}
}),
}
}
export function provideCompletions(
state: State,
params: CompletionParams
): CompletionList {
if (state === null) return { items: [], isIncomplete: false }
return (
provideClassNameCompletions(state, params) ||
provideCssHelperCompletions(state, params)
)
}
export function resolveCompletionItem(
state: State,
item: CompletionItem
): CompletionItem {
if (item.data === 'helper') {
return item
}
const className = state.classNames.classNames[item.label]
if (isContextItem(state, [item.label])) {
item.detail = state.classNames.context[item.label].join(', ')
} else {
item.detail = getCssDetail(state, className)
if (!item.documentation) {
item.documentation = stringifyCss(className)
if (item.detail === item.documentation) {
item.documentation = null
} else {
// item.documentation = {
// kind: MarkupKind.Markdown,
// value: ['```css', item.documentation, '```'].join('\n')
// }
}
}
}
return item
}
function isContextItem(state: State, keys: string[]): boolean {
const item = dlv(state.classNames.classNames, keys)
return Boolean(
!item.__rule &&
!Array.isArray(item) &&
state.classNames.context[keys[keys.length - 1]]
)
}
function stringifyDecls(obj: any): string {
return Object.keys(obj)
.map((prop) => {
return `${prop}: ${obj[prop]};`
})
.join(' ')
}
function stringifyCss(obj: any, indent: number = 0): string {
let indentStr = ' '.repeat(indent)
if (obj.__decls === true) {
return Object.keys(removeMeta(obj))
.reduce((acc, curr, i) => {
return `${acc}${i === 0 ? '' : '\n'}${indentStr}${curr}: ${obj[curr]};`
}, '')
.trim()
}
return Object.keys(removeMeta(obj))
.reduce((acc, curr, i) => {
return `${acc}${i === 0 ? '' : '\n'}${indentStr}${curr} {\n${stringifyCss(
obj[curr],
indent + 2
)}\n${indentStr}}`
}, '')
.trim()
}
function getCssDetail(state: State, className: any): string {
if (Array.isArray(className)) {
return `${className.length} rules`
}
let withoutMeta = removeMeta(className)
if (className.__decls === true) {
return stringifyDecls(withoutMeta)
}
let keys = Object.keys(withoutMeta)
if (keys.length === 1) {
return getCssDetail(state, className[keys[0]])
}
return `${keys.length} rules`
}

View File

@ -0,0 +1,91 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import {
createConnection,
TextDocuments,
ProposedFeatures,
TextDocumentSyncKind,
CompletionItem,
InitializeParams,
InitializeResult,
CompletionParams,
CompletionList,
} from 'vscode-languageserver'
import getTailwindState from 'tailwindcss-class-names'
import { State } from './util/state'
import {
provideCompletions,
resolveCompletionItem,
} from './providers/completionProvider'
import { URI } from 'vscode-uri'
let state: State = null
let connection = createConnection(ProposedFeatures.all)
let documents = new TextDocuments()
let workspaceFolder: string | null
documents.onDidOpen((event) => {
connection.console.log(
`[Server(${process.pid}) ${workspaceFolder}] Document opened: ${event.document.uri}`
)
})
documents.listen(connection)
connection.onInitialize(
async (params: InitializeParams): Promise<InitializeResult> => {
state = await getTailwindState(
params.rootPath || URI.parse(params.rootUri).path,
{
onChange: (newState: State): void => {
state = { ...newState, editor: state.editor }
connection.sendNotification('tailwindcss/configUpdated', [
state.dependencies[0],
state.config,
state.plugins,
])
},
}
)
state.editor = { connection, documents }
return {
capabilities: {
// textDocumentSync: {
// openClose: true,
// change: TextDocumentSyncKind.None
// },
textDocumentSync: documents.syncKind,
completionProvider: {
resolveProvider: true,
triggerCharacters: ['"', "'", '`', ' ', '.', '[', state.separator],
},
},
}
}
)
connection.onInitialized &&
connection.onInitialized(async () => {
connection.sendNotification('tailwindcss/configUpdated', [
state.dependencies[0],
state.config,
state.plugins,
])
})
connection.onCompletion(
(params: CompletionParams): CompletionList => {
return provideCompletions(state, params)
}
)
connection.onCompletionResolve(
(item: CompletionItem): CompletionItem => {
return resolveCompletionItem(state, item)
}
)
connection.listen()

View File

@ -0,0 +1,199 @@
const dlv = require('dlv')
import { State } from './state'
import removeMeta from './removeMeta'
const COLOR_PROPS = [
'caret-color',
'color',
'column-rule-color',
'background-color',
'border-color',
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color',
'fill',
'outline-color',
'stop-color',
'stroke',
'text-decoration-color'
]
const COLOR_NAMES = {
transparent: 'rgba(0, 0, 0, 0.01)',
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#0ff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000',
blanchedalmond: '#ffebcd',
blue: '#00f',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
burntsienna: '#ea7e5d',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#0ff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkgrey: '#a9a9a9',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#f0f',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
grey: '#808080',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgreen: '#90ee90',
lightgrey: '#d3d3d3',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#789',
lightslategrey: '#789',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#0f0',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#f0f',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370db',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#db7093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#f00',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#fff',
whitesmoke: '#f5f5f5',
yellow: '#ff0',
yellowgreen: '#9acd32'
}
export function getColor(state: State, keys: string[]): string {
const item = dlv(state.classNames.classNames, keys)
if (!item.__decls) return null
const props = Object.keys(removeMeta(item))
if (props.length === 0 || props.length > 1) return null
const prop = props[0]
if (COLOR_PROPS.indexOf(prop) === -1) return null
return COLOR_NAMES[item[prop].toLowerCase()] || item[prop]
}
export function isColor(str: any): boolean {
return (
typeof str === 'string' &&
/^(?:#|0x)(?:[a-f0-9]{3,4}|[a-f0-9]{6}|[a-f0-9]{8})\b|(?:rgb|hsl)a?\([^\)]*\)$/.test(
str.trim()
)
)
}
export function getColorFromString(str: string): string {
if (isColor(str)) {
return str
}
return COLOR_NAMES[str] || null
}

View File

@ -0,0 +1,16 @@
import { TextDocument } from 'vscode-languageserver'
export const CSS_LANGUAGES = [
'css',
'less',
'postcss',
'sass',
'scss',
'stylus',
'svelte',
'vue'
]
export function isCssDoc(doc: TextDocument): boolean {
return CSS_LANGUAGES.indexOf(doc.languageId) !== -1
}

View File

@ -0,0 +1,67 @@
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
let match: RegExpMatchArray
let matches: RegExpMatchArray[] = []
while ((match = re.exec(str)) !== null) {
matches.push({ ...match })
}
return matches
}
export function findLast(re: RegExp, str: string): RegExpMatchArray {
const matches = findAll(re, str)
if (matches.length === 0) {
return null
}
return matches[matches.length - 1]
}
export function arrFindLast<T>(arr: T[], predicate: (item: T) => boolean): T {
for (let i = arr.length - 1; i >= 0; --i) {
const x = arr[i]
if (predicate(x)) {
return x
}
}
return null
}
enum Quote {
SINGLE = "'",
DOUBLE = '"',
TICK = '`',
}
type StringInfo = {
start: number
end?: number
char: Quote
}
export function findJsxStrings(str: string): StringInfo[] {
const chars = str.split('')
const strings: StringInfo[] = []
let bracketCount = 0
for (let i = 0; i < chars.length; i++) {
const char = chars[i]
if (char === '{') {
bracketCount += 1
} else if (char === '}') {
bracketCount -= 1
} else if (
char === Quote.SINGLE ||
char === Quote.DOUBLE ||
char === Quote.TICK
) {
let open = arrFindLast(strings, (string) => string.char === char)
if (strings.length === 0 || !open || (open && open.end)) {
strings.push({ start: i + 1, char })
} else {
open.end = i
}
}
if (i !== 0 && bracketCount === 0) {
// end
break
}
}
return strings
}

View File

@ -0,0 +1,30 @@
import { TextDocument } from 'vscode-languageserver'
import { JS_LANGUAGES } from './js'
export const HTML_LANGUAGES = [
'blade',
'django-html',
'edge',
'ejs',
'erb',
'haml',
'handlebars',
'html',
'HTML (Eex)',
'jade',
'leaf',
'markdown',
'njk',
'nunjucks',
'php',
'razor',
'slim',
'svelte',
'twig',
'vue',
...JS_LANGUAGES
]
export function isHtmlDoc(doc: TextDocument): boolean {
return HTML_LANGUAGES.indexOf(doc.languageId) !== -1
}

View File

@ -0,0 +1,3 @@
export default function isObject(variable: any): boolean {
return Object.prototype.toString.call(variable) === '[object Object]'
}

View File

@ -0,0 +1,13 @@
import { TextDocument } from 'vscode-languageserver'
export const JS_LANGUAGES = [
'javascript',
'javascriptreact',
'reason',
'svelte',
'typescriptreact'
]
export function isJsDoc(doc: TextDocument): boolean {
return JS_LANGUAGES.indexOf(doc.languageId) !== -1
}

View File

@ -0,0 +1,13 @@
import isObject from './isObject'
export default function removeMeta(obj: any): any {
let result = {}
for (let key in obj) {
if (isObject(obj[key])) {
result[key] = removeMeta(obj[key])
} else if (key.substr(0, 2) !== '__') {
result[key] = obj[key]
}
}
return result
}

View File

@ -0,0 +1,29 @@
import { TextDocuments, Connection } from 'vscode-languageserver'
export type ClassNamesTree = {
[key: string]: ClassNamesTree
}
export type ClassNamesContext = {
[key: string]: string[]
}
export type ClassNames = {
context: ClassNamesContext
classNames: ClassNamesTree
}
export type EditorState = {
connection: Connection
documents: TextDocuments
}
export type State = null | {
config: any
separator: string
plugins: any[]
variants: string[]
classNames: ClassNames
dependencies: string[]
editor: EditorState
}

View File

@ -0,0 +1,11 @@
export function stringifyConfigValue(x: any): string {
if (typeof x === 'string') return x
if (typeof x === 'number') return x.toString()
if (Array.isArray(x)) {
return x
.filter(y => typeof y === 'string')
.filter(Boolean)
.join(', ')
}
return ''
}

View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"rootDir": "src",
"sourceMap": true,
"moduleResolution": "node",
"esModuleInterop": true
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}

View File

@ -0,0 +1,13 @@
.vscode/**
**/*.ts
**/*.map
.gitignore
**/tsconfig.json
**/tsconfig.base.json
contributing.md
.travis.yml
node_modules/**
!node_modules/vscode-jsonrpc/**
!node_modules/vscode-languageclient/**
!node_modules/vscode-languageserver-protocol/**
!node_modules/vscode-languageserver-types/**

View File

@ -0,0 +1,754 @@
{
"name": "tailwindcss-vscode",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
"dev": true,
"requires": {
"@babel/highlight": "^7.0.0"
}
},
"@babel/highlight": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
"integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
"dev": true,
"requires": {
"chalk": "^2.0.0",
"esutils": "^2.0.2",
"js-tokens": "^4.0.0"
}
},
"@types/mocha": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.1.tgz",
"integrity": "sha512-dOrgprHnkDaj1pmrwdcMAf0QRNQzqTB5rxJph+iIQshSmIvtgRqJ0nim8u1vvXU8iOXZrH96+M46JDFTPLingA==",
"dev": true
},
"@types/node": {
"version": "8.10.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.18.tgz",
"integrity": "sha512-WoepSz+wJlU5Bjq5oK6cO1oXe2FgPcjMtQPgKPS8fVaTAD0lxkScMCCbMimdkVCsykqaA4lvHWz3cmj28yimhA==",
"dev": true
},
"@types/vscode": {
"version": "1.43.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.43.0.tgz",
"integrity": "sha512-kIaR9qzd80rJOxePKpCB/mdy00mz8Apt2QA5Y6rdrKFn13QNFNeP3Hzmsf37Bwh/3cS7QjtAeGSK7wSqAU0sYQ==",
"dev": true
},
"@zeit/ncc": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.22.0.tgz",
"integrity": "sha512-zaS6chwztGSLSEzsTJw9sLTYxQt57bPFBtsYlVtbqGvmDUsfW7xgXPYofzFa1kB9ur2dRop6IxCwPnWLBVCrbQ==",
"dev": true
},
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
"dev": true
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
"requires": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
"wrap-ansi": "^5.1.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"commander": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"concurrently": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-5.1.0.tgz",
"integrity": "sha512-9ViZMu3OOCID3rBgU31mjBftro2chOop0G2u1olq1OuwRBVRw/GxHTg80TVJBUTJfoswMmEUeuOg1g1yu1X2dA==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"date-fns": "^2.0.1",
"lodash": "^4.17.15",
"read-pkg": "^4.0.1",
"rxjs": "^6.5.2",
"spawn-command": "^0.0.2-1",
"supports-color": "^6.1.0",
"tree-kill": "^1.2.2",
"yargs": "^13.3.0"
},
"dependencies": {
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"date-fns": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.11.0.tgz",
"integrity": "sha512-8P1cDi8ebZyDxUyUprBXwidoEtiQAawYPGvpfb+Dg0G6JrQ+VozwOmm91xYC0vAv1+0VmLehEPb+isg4BGUFfA==",
"dev": true
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
"dev": true
},
"dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
"is-arrayish": "^0.2.1"
}
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
"locate-path": "^3.0.0"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"glob": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"glob-exec": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/glob-exec/-/glob-exec-0.1.1.tgz",
"integrity": "sha1-172HjoZ9a5JeoFa67MVicbpeZxM=",
"dev": true,
"requires": {
"glob": "7.1.X",
"subarg": "1.0.X"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"dev": true
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"mkdirp": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz",
"integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==",
"dev": true
},
"normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"dev": true,
"requires": {
"hosted-git-info": "^2.1.4",
"resolve": "^1.10.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"p-limit": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
"integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
"dev": true,
"requires": {
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1"
}
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"read-pkg": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
"integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=",
"dev": true,
"requires": {
"normalize-package-data": "^2.3.2",
"parse-json": "^4.0.0",
"pify": "^3.0.0"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"resolve": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz",
"integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
}
},
"rxjs": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
"integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
"dev": true
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"spawn-command": {
"version": "0.0.2-1",
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
"integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=",
"dev": true
},
"spdx-correct": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
"integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
"dev": true,
"requires": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
}
},
"spdx-exceptions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
"integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
"dev": true
},
"spdx-expression-parse": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
"integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
"dev": true,
"requires": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"spdx-license-ids": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
"integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
"dev": true
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
},
"subarg": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
"integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
"dev": true,
"requires": {
"minimist": "^1.1.0"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
"dev": true
},
"tslint": {
"version": "5.16.0",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz",
"integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"builtin-modules": "^1.1.1",
"chalk": "^2.3.0",
"commander": "^2.12.1",
"diff": "^3.2.0",
"glob": "^7.1.1",
"js-yaml": "^3.13.0",
"minimatch": "^3.0.4",
"mkdirp": "^0.5.1",
"resolve": "^1.3.2",
"semver": "^5.3.0",
"tslib": "^1.8.0",
"tsutils": "^2.29.0"
},
"dependencies": {
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
}
}
},
"tsutils": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
"integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
},
"typescript": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
"dev": true
},
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
"requires": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
}
},
"vscode-jsonrpc": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz",
"integrity": "sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==",
"dev": true
},
"vscode-languageclient": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-5.2.1.tgz",
"integrity": "sha512-7jrS/9WnV0ruqPamN1nE7qCxn0phkH5LjSgSp9h6qoJGoeAKzwKz/PF6M+iGA/aklx4GLZg1prddhEPQtuXI1Q==",
"dev": true,
"requires": {
"semver": "^5.5.0",
"vscode-languageserver-protocol": "3.14.1"
}
},
"vscode-languageserver-protocol": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz",
"integrity": "sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g==",
"dev": true,
"requires": {
"vscode-jsonrpc": "^4.0.0",
"vscode-languageserver-types": "3.14.0"
}
},
"vscode-languageserver-types": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz",
"integrity": "sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==",
"dev": true
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"wrap-ansi": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
"yargs": {
"version": "13.3.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
"dev": true,
"requires": {
"cliui": "^5.0.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^13.1.2"
}
},
"yargs-parser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
}

View File

@ -0,0 +1,61 @@
{
"name": "tailwindcss-vscode",
"description": "",
"author": "Brad Cornes <hello@bradley.dev>",
"license": "MIT",
"version": "0.0.1",
"repository": {
"type": "git",
"url": "https://github.com/bradlc/"
},
"publisher": "bradlc",
"categories": [],
"keywords": [
"multi-root ready"
],
"engines": {
"vscode": "^1.33.0"
},
"activationEvents": [
"onLanguage:plaintext"
],
"main": "dist/src/extension/index.js",
"contributes": {
"grammars": [
{
"scopeName": "source.css.tailwind",
"path": "./syntaxes/tailwind.tmLanguage.json",
"injectTo": [
"source.css",
"source.css.scss",
"source.css.less",
"source.css.postcss"
]
}
]
},
"scripts": {
"vscode:prepublish": "npm run build",
"devOld": "concurrently \"npm:devOld:*\"",
"devOld:client": "ncc build src/extension.ts --watch -o dist/client",
"devOld:server": "ncc build src/server.ts --watch -o dist/server",
"dev": "glob-exec --foreach --parallel \"src/*.ts\" -- \"ncc build {{file}} --watch -o dist/{{file.toString().replace(/\\.ts$/, '')}}\"",
"build": "npm run build:client && npm run build:server",
"build:client": "ncc build src/extension.ts --minify -o dist/client",
"build:server": "ncc build src/server.ts --minify -o dist/server"
},
"devDependencies": {
"@types/mocha": "^5.2.0",
"@types/node": "^8.0.0",
"@types/vscode": "^1.32.0",
"@zeit/ncc": "^0.22.0",
"concurrently": "^5.1.0",
"dlv": "^1.1.3",
"glob-exec": "^0.1.1",
"mkdirp": "^1.0.3",
"tailwindcss-language-server": "0.0.1",
"tslint": "^5.16.0",
"typescript": "^3.8.3",
"vscode-languageclient": "^5.2.1"
}
}

View File

@ -1,200 +1,168 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import {
workspace as Workspace,
window as Window,
ExtensionContext,
TextDocument,
OutputChannel,
WorkspaceFolder,
Uri
} from 'vscode'
import {
LanguageClient,
LanguageClientOptions,
TransportKind
} from 'vscode-languageclient'
import * as path from 'path'
const CONFIG_GLOB =
'**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js'
export const CSS_LANGUAGES: string[] = [
'css',
'less',
'postcss',
'sass',
'scss',
'stylus',
'vue'
]
export const JS_LANGUAGES: string[] = [
'javascript',
'javascriptreact',
'reason',
'typescriptreact'
]
export const HTML_LANGUAGES: string[] = [
'blade',
'django-html',
'edge',
'ejs',
'erb',
'haml',
'handlebars',
'html',
'HTML (Eex)',
'HTML (EEx)',
'jade',
'leaf',
'markdown',
'njk',
'nunjucks',
'php',
'razor',
'slim',
'svelte',
'twig',
'vue',
...JS_LANGUAGES
]
export const LANGUAGES: string[] = [...CSS_LANGUAGES, ...HTML_LANGUAGES].filter(
(val, index, arr) => arr.indexOf(val) === index
)
let defaultClient: LanguageClient
let clients: Map<string, LanguageClient> = new Map()
let _sortedWorkspaceFolders: string[] | undefined
function sortedWorkspaceFolders(): string[] {
if (_sortedWorkspaceFolders === void 0) {
_sortedWorkspaceFolders = Workspace.workspaceFolders
? Workspace.workspaceFolders
.map(folder => {
let result = folder.uri.toString()
if (result.charAt(result.length - 1) !== '/') {
result = result + '/'
}
return result
})
.sort((a, b) => {
return a.length - b.length
})
: []
}
return _sortedWorkspaceFolders
}
Workspace.onDidChangeWorkspaceFolders(
() => (_sortedWorkspaceFolders = undefined)
)
function getOuterMostWorkspaceFolder(folder: WorkspaceFolder): WorkspaceFolder {
let sorted = sortedWorkspaceFolders()
for (let element of sorted) {
let uri = folder.uri.toString()
if (uri.charAt(uri.length - 1) !== '/') {
uri = uri + '/'
}
if (uri.startsWith(element)) {
return Workspace.getWorkspaceFolder(Uri.parse(element))!
}
}
return folder
}
export async function activate(context: ExtensionContext) {
let module = context.asAbsolutePath(path.join('dist', 'server', 'index.js'))
let outputChannel: OutputChannel = Window.createOutputChannel(
'tailwindcss-language-server'
)
async function didOpenTextDocument(document: TextDocument): Promise<void> {
if (
document.uri.scheme !== 'file' ||
LANGUAGES.indexOf(document.languageId) === -1
) {
return
}
let uri = document.uri
let folder = Workspace.getWorkspaceFolder(uri)
// Files outside a folder can't be handled. This might depend on the language.
// Single file languages like JSON might handle files outside the workspace folders.
if (!folder) {
return
}
// If we have nested workspace folders we only start a server on the outer most workspace folder.
folder = getOuterMostWorkspaceFolder(folder)
if (!clients.has(folder.uri.toString())) {
// placeholder
clients.set(folder.uri.toString(), null)
let files = await Workspace.findFiles(
CONFIG_GLOB,
'**/node_modules/**',
1
)
if (!files.length) return
let debugOptions = {
execArgv: ['--nolazy', `--inspect=${6011 + clients.size}`]
}
let serverOptions = {
run: { module, transport: TransportKind.ipc },
debug: { module, transport: TransportKind.ipc, options: debugOptions }
}
let clientOptions: LanguageClientOptions = {
documentSelector: LANGUAGES.map(language => ({
scheme: 'file',
language,
pattern: `${folder.uri.fsPath}/**/*`
})),
diagnosticCollectionName: 'tailwindcss-language-server',
workspaceFolder: folder,
outputChannel: outputChannel,
synchronize: {
fileEvents: Workspace.createFileSystemWatcher(CONFIG_GLOB)
}
}
let client = new LanguageClient(
'tailwindcss-language-server',
'Tailwind CSS Language Server',
serverOptions,
clientOptions
)
client.start()
clients.set(folder.uri.toString(), client)
}
}
Workspace.onDidOpenTextDocument(didOpenTextDocument)
Workspace.textDocuments.forEach(didOpenTextDocument)
Workspace.onDidChangeWorkspaceFolders(event => {
for (let folder of event.removed) {
let client = clients.get(folder.uri.toString())
if (client) {
clients.delete(folder.uri.toString())
client.stop()
}
}
})
}
export function deactivate(): Thenable<void> {
let promises: Thenable<void>[] = []
if (defaultClient) {
promises.push(defaultClient.stop())
}
for (let client of clients.values()) {
if (client) {
promises.push(client.stop())
}
}
return Promise.all(promises).then(() => undefined)
}
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as path from 'path'
import {
workspace as Workspace,
window as Window,
ExtensionContext,
TextDocument,
OutputChannel,
WorkspaceFolder,
Uri,
} from 'vscode'
import {
LanguageClient,
LanguageClientOptions,
TransportKind,
} from 'vscode-languageclient'
let defaultClient: LanguageClient
let clients: Map<string, LanguageClient> = new Map()
const LANGS = ['css', 'javascript', 'html']
let _sortedWorkspaceFolders: string[] | undefined
function sortedWorkspaceFolders(): string[] {
if (_sortedWorkspaceFolders === void 0) {
_sortedWorkspaceFolders = Workspace.workspaceFolders
? Workspace.workspaceFolders
.map((folder) => {
let result = folder.uri.toString()
if (result.charAt(result.length - 1) !== '/') {
result = result + '/'
}
return result
})
.sort((a, b) => {
return a.length - b.length
})
: []
}
return _sortedWorkspaceFolders
}
Workspace.onDidChangeWorkspaceFolders(
() => (_sortedWorkspaceFolders = undefined)
)
function getOuterMostWorkspaceFolder(folder: WorkspaceFolder): WorkspaceFolder {
let sorted = sortedWorkspaceFolders()
for (let element of sorted) {
let uri = folder.uri.toString()
if (uri.charAt(uri.length - 1) !== '/') {
uri = uri + '/'
}
if (uri.startsWith(element)) {
return Workspace.getWorkspaceFolder(Uri.parse(element))!
}
}
return folder
}
export function activate(context: ExtensionContext) {
let module = context.asAbsolutePath(
path.join('dist', 'src', 'server', 'index.js')
)
let outputChannel: OutputChannel = Window.createOutputChannel(
'lsp-multi-server-example'
)
function didOpenTextDocument(document: TextDocument): void {
// We are only interested in language mode text
if (
LANGS.indexOf(document.languageId) === -1 ||
(document.uri.scheme !== 'file' && document.uri.scheme !== 'untitled')
) {
return
}
let uri = document.uri
// Untitled files go to a default client.
if (uri.scheme === 'untitled' && !defaultClient) {
let debugOptions = { execArgv: ['--nolazy', '--inspect=6010'] }
let serverOptions = {
run: { module, transport: TransportKind.ipc },
debug: { module, transport: TransportKind.ipc, options: debugOptions },
}
let clientOptions: LanguageClientOptions = {
documentSelector: LANGS.map((language) => ({
scheme: 'untitled',
language,
})),
diagnosticCollectionName: 'lsp-multi-server-example',
outputChannel: outputChannel,
}
defaultClient = new LanguageClient(
'lsp-multi-server-example',
'LSP Multi Server Example',
serverOptions,
clientOptions
)
defaultClient.start()
return
}
let folder = Workspace.getWorkspaceFolder(uri)
// Files outside a folder can't be handled. This might depend on the language.
// Single file languages like JSON might handle files outside the workspace folders.
if (!folder) {
return
}
// If we have nested workspace folders we only start a server on the outer most workspace folder.
folder = getOuterMostWorkspaceFolder(folder)
if (!clients.has(folder.uri.toString())) {
let debugOptions = {
execArgv: ['--nolazy', `--inspect=${6011 + clients.size}`],
}
let serverOptions = {
run: { module, transport: TransportKind.ipc },
debug: { module, transport: TransportKind.ipc, options: debugOptions },
}
let clientOptions: LanguageClientOptions = {
documentSelector: LANGS.map((language) => ({
scheme: 'file',
language,
pattern: `${folder.uri.fsPath}/**/*`,
})),
diagnosticCollectionName: 'lsp-multi-server-example',
workspaceFolder: folder,
outputChannel: outputChannel,
middleware: {},
}
let client = new LanguageClient(
'lsp-multi-server-example',
'LSP Multi Server Example',
serverOptions,
clientOptions
)
client.start()
clients.set(folder.uri.toString(), client)
}
}
Workspace.onDidOpenTextDocument(didOpenTextDocument)
Workspace.textDocuments.forEach(didOpenTextDocument)
Workspace.onDidChangeWorkspaceFolders((event) => {
for (let folder of event.removed) {
let client = clients.get(folder.uri.toString())
if (client) {
clients.delete(folder.uri.toString())
client.stop()
}
}
})
}
export function deactivate(): Thenable<void> {
let promises: Thenable<void>[] = []
if (defaultClient) {
promises.push(defaultClient.stop())
}
for (let client of clients.values()) {
promises.push(client.stop())
}
return Promise.all(promises).then(() => undefined)
}

View File

@ -5,7 +5,7 @@
"name": "TailwindCSS",
"patterns": [
{
"begin": "^\\s*(@)apply\\b",
"begin": "(@)apply\\b",
"beginCaptures": {
"0": {
"name": "keyword.control.at-rule.apply.tailwind"

View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"rootDir": "src",
"sourceMap": true,
"moduleResolution": "node"
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M3 3v18h18V3H3zm8 16H5v-6h6v6zm0-8H5V5h6v6zm8 8h-6v-6h6v6zm0-8h-6V5h6v6z"/><path d="M0 0h24v24H0z" fill="none"/></svg>

Before

Width:  |  Height:  |  Size: 226 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M4 6h18V4H4c-1.1 0-2 .9-2 2v11H0v3h14v-3H4V6zm19 2h-6c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h6c.55 0 1-.45 1-1V9c0-.55-.45-1-1-1zm-1 9h-4v-7h4v7z"/></svg>

Before

Width:  |  Height:  |  Size: 258 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z"/></svg>

Before

Width:  |  Height:  |  Size: 409 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><g transform="translate(24,0) scale(-1,1)"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3c0 1.1.89 2 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9c-1.11 0-2 .9-2 2v10c0 1.1.89 2 2 2h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 12H9V5h10v10zm-8 6h2v-2h-2v2zm-4 0h2v-2H7v2z"/></g></svg>

Before

Width:  |  Height:  |  Size: 410 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>

Before

Width:  |  Height:  |  Size: 383 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M6 7h2.5L5 3.5 1.5 7H4v10H1.5L5 20.5 8.5 17H6V7zm4-2v2h12V5H10zm0 14h12v-2H10v2zm0-6h12v-2H10v2z"/></svg>

Before

Width:  |  Height:  |  Size: 213 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 4v3h5v12h3V7h5V4H9zm-6 8h3v7h3v-7h3V9H3v3z"/></svg>

Before

Width:  |  Height:  |  Size: 199 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27-7.38 5.74zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16z"/></svg>

Before

Width:  |  Height:  |  Size: 224 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M17.66 8L12 2.35 6.34 8C4.78 9.56 4 11.64 4 13.64s.78 4.11 2.34 5.67 3.61 2.35 5.66 2.35 4.1-.79 5.66-2.35S20 15.64 20 13.64 19.22 9.56 17.66 8zM6 14c.01-2 .62-3.27 1.76-4.4L12 5.27l4.24 4.38C17.38 10.77 17.99 12 18 14H6z"/></svg>

Before

Width:  |  Height:  |  Size: 338 B

View File

@ -1,11 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3" xmlns="http://www.w3.org/2000/svg">
<path d="M9 9H7C7 7.9 7.9 7 9 7V9Z"/>
<path d="M15 9L15 7C16.1 7 17 7.9 17 9L15 9Z"/>
<path d="M9 15L9 17C7.9 17 7 16.1 7 15L9 15Z"/>
<path d="M15 15L17 15C17 16.1 16.1 17 15 17L15 15Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M21 3H3V21H21V3ZM19 5H5V19H19V5Z"/>
<rect x="11" y="7" width="2" height="2"/>
<rect x="7" y="11" width="2" height="2"/>
<rect x="11" y="15" width="2" height="2"/>
<rect x="15" y="11" width="2" height="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 567 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/></svg>

Before

Width:  |  Height:  |  Size: 569 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M21 15h2v2h-2v-2zm0-4h2v2h-2v-2zm2 8h-2v2c1 0 2-1 2-2zM13 3h2v2h-2V3zm8 4h2v2h-2V7zm0-4v2h2c0-1-1-2-2-2zM1 7h2v2H1V7zm16-4h2v2h-2V3zm0 16h2v2h-2v-2zM3 3C2 3 1 4 1 5h2V3zm6 0h2v2H9V3zM5 3h2v2H5V3zm-4 8v8c0 1.1.9 2 2 2h12V11H1zm2 8l2.5-3.21 1.79 2.15 2.5-3.22L13 19H3z"/></svg>

Before

Width:  |  Height:  |  Size: 383 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><defs><path id="a" d="M0 0h24v24H0V0z"/></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"/></clipPath><path clip-path="url(#b)" d="M19 19h2v2h-2v-2zm0-2h2v-2h-2v2zM3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm0-4h2V3H3v2zm4 0h2V3H7v2zm8 16h2v-2h-2v2zm-4 0h2v-2h-2v2zm4 0h2v-2h-2v2zm-8 0h2v-2H7v2zm-4 0h2v-2H3v2zM21 8c0-2.76-2.24-5-5-5h-5v2h5c1.65 0 3 1.35 3 3v5h2V8z"/></svg>

Before

Width:  |  Height:  |  Size: 528 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM7 17h10V7H7v10zm2-8h6v6H9V9z"/></svg>

Before

Width:  |  Height:  |  Size: 421 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="#44a8b3"><path d="M15.95 10.78c.03-.25.05-.51.05-.78s-.02-.53-.06-.78l1.69-1.32c.15-.12.19-.34.1-.51l-1.6-2.77c-.1-.18-.31-.24-.49-.18l-1.99.8c-.42-.32-.86-.58-1.35-.78L12 2.34c-.03-.2-.2-.34-.4-.34H8.4c-.2 0-.36.14-.39.34l-.3 2.12c-.49.2-.94.47-1.35.78l-1.99-.8c-.18-.07-.39 0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69 1.32c-.04.25-.07.52-.07.78s.02.53.06.78L2.37 12.1c-.15.12-.19.34-.1.51l1.6 2.77c.1.18.31.24.49.18l1.99-.8c.42.32.86.58 1.35.78l.3 2.12c.04.2.2.34.4.34h3.2c.2 0 .37-.14.39-.34l.3-2.12c.49-.2.94-.47 1.35-.78l1.99.8c.18.07.39 0 .49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z"/></svg>

Before

Width:  |  Height:  |  Size: 751 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z"/></svg>

Before

Width:  |  Height:  |  Size: 238 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3"><path d="M5 4v3h5.5v12h3V7H19V4z"/></svg>

Before

Width:  |  Height:  |  Size: 140 B

View File

@ -1,4 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="#44a8b3" xmlns="http://www.w3.org/2000/svg">
<path d="M11 2L5.5 16H7.75L8.87 13H15.12L16.24 16H18.49L13 2H11ZM9.62 11L12 4.67L14.38 11H9.62V11Z"/>
<path d="M17.5 24L17.5 21.5L6.5 21.5L6.5 24L3 20.5L6.5 17L6.5 19.5L17.5 19.5L17.5 17L21 20.5L17.5 24Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 313 B

View File

@ -0,0 +1,16 @@
{
"folders": [
{
"path": "packages/tailwindcss-vscode"
},
{
"path": "packages/tailwindcss-language-server"
},
{
"path": "packages/tailwindcss-class-names"
}
],
"settings": {
"typescript.tsc.autoDetect": "off"
}
}

View File

@ -1,9 +0,0 @@
{
"compilerOptions": {
"target": "es2015",
"moduleResolution": "node",
"types": ["node"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}