add lsp boilerplate

master
Brad Cornes 2018-12-18 19:27:33 +00:00
parent def423a7fe
commit 99be4063c4
20 changed files with 7286 additions and 4135 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
out dist
node_modules node_modules
.vscode-test/ .vscode-test/
.vsix .vsix
.DS_Store .DS_Store
.rts2_cache_cjs

19
.vscode/launch.json vendored
View File

@ -1,28 +1,15 @@
// A launch configuration that compiles the extension and then opens it inside a new window
{ {
"version": "0.1.0", "version": "0.1.0",
"configurations": [ "configurations": [
{ {
"name": "Extension", "name": "Launch Extension",
"type": "extensionHost", "type": "extensionHost",
"request": "launch", "request": "launch",
"runtimeExecutable": "${execPath}", "runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ], "args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"stopOnEntry": false, "stopOnEntry": false,
"sourceMaps": true, "sourceMaps": true,
"outFiles": [ "${workspaceRoot}/out/**/*.js" ], "outFiles": ["${workspaceRoot}/dist/**/*.js"]
"preLaunchTask": "npm: watch"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/out/test/**/*.js" ],
"preLaunchTask": "npm: watch"
} }
] ]
} }

View File

@ -1,9 +0,0 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
}
}

20
.vscode/tasks.json vendored
View File

@ -1,20 +0,0 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -1,21 +0,0 @@
# Changelog
## 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">

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

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

6662
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"version": "0.1.16", "version": "0.1.16",
"publisher": "bradlc", "publisher": "bradlc",
"engines": { "engines": {
"vscode": "^1.20.0" "vscode": "^1.23.0"
}, },
"categories": [ "categories": [
"Other" "Other"
@ -25,7 +25,8 @@
"activationEvents": [ "activationEvents": [
"workspaceContains:**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js" "workspaceContains:**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js"
], ],
"main": "./out/extension", "source": "./src/extension.ts",
"main": "./dist/extension.js",
"contributes": { "contributes": {
"grammars": [ "grammars": [
{ {
@ -42,21 +43,11 @@
}, },
"preview": true, "preview": true,
"scripts": { "scripts": {
"vscode:prepublish": "npm run compile", "vscode:prepublish": "npm run build",
"compile": "tsc -p ./", "build": "microbundle -f cjs --external vscode,child_process,os,crypto,net",
"watch": "tsc -watch -p ./", "watch": "npm run build -- --watch",
"postinstall": "node ./node_modules/vscode/bin/install", "postinstall": "node ./node_modules/vscode/bin/install",
"test": "npm run compile && node ./node_modules/vscode/bin/test" "test": "npm run build && node ./node_modules/vscode/bin/test"
},
"devDependencies": {
"@types/node": "^7.0.43",
"typescript": "^2.6.1",
"vscode": "^1.1.6"
},
"dependencies": {
"color": "^3.0.0",
"dlv": "^1.1.1",
"tailwind-class-names": "0.6.0"
}, },
"author": "Brad Cornes <bradlc41@gmail.com>", "author": "Brad Cornes <bradlc41@gmail.com>",
"license": "MIT", "license": "MIT",
@ -68,5 +59,12 @@
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/bradlc/vscode-tailwindcss.git" "url": "https://github.com/bradlc/vscode-tailwindcss.git"
},
"dependencies": {
"vscode-languageclient": "^5.2.1"
},
"devDependencies": {
"microbundle": "^0.8.3",
"vscode": "^1.1.26"
} }
} }

View File

@ -1,829 +1,139 @@
'use strict' /* --------------------------------------------------------------------------------------------
* 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 * as vscode from 'vscode' import {
import { dirname } from 'path' LanguageClient,
const htmlElements = require('./htmlElements.js') LanguageClientOptions,
// const tailwindClassNames = require('tailwind-class-names') TransportKind
const tailwindClassNames = require('/Users/brad/Code/tailwind-class-names/dist') } from 'vscode-languageclient'
const dlv = require('dlv')
const Color = require('color')
const CONFIG_GLOB = let LANGUAGES: string[] = ['html']
'**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js'
const JS_TYPES = ['typescriptreact', 'javascript', 'javascriptreact']
const HTML_TYPES = [
'html',
'jade',
'razor',
'php',
'blade',
'twig',
'markdown',
'erb',
'handlebars',
'ejs',
'nunjucks',
'haml',
'leaf',
'HTML (Eex)'
]
const CSS_TYPES = ['css', 'sass', 'scss', 'less', 'stylus']
export async function activate(context: vscode.ExtensionContext) { let defaultClient: LanguageClient
let tw let clients: Map<string, LanguageClient> = new Map()
try { let _sortedWorkspaceFolders: string[] | undefined
tw = await getTailwind() function sortedWorkspaceFolders(): string[] {
} catch (err) {} 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)
)
let intellisense = new TailwindIntellisense(tw) function getOuterMostWorkspaceFolder(folder: WorkspaceFolder): WorkspaceFolder {
context.subscriptions.push(intellisense) 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
}
let watcher = vscode.workspace.createFileSystemWatcher(CONFIG_GLOB) export function activate(context: ExtensionContext) {
// let module = context.asAbsolutePath(path.join('server', 'out', 'server.js'))
let module = '/Users/brad/Code/tailwindcss-language-server/dist/index.js'
let outputChannel: OutputChannel = Window.createOutputChannel(
'lsp-multi-server-example'
)
watcher.onDidChange(onFileChange) function didOpenTextDocument(document: TextDocument): void {
watcher.onDidCreate(onFileChange) if (
watcher.onDidDelete(onFileChange) document.uri.scheme !== 'file' ||
LANGUAGES.indexOf(document.languageId) === -1
async function onFileChange(event) { ) {
try {
tw = await getTailwind()
} catch (err) {
intellisense.dispose()
return return
} }
if (!tw) { let uri = document.uri
intellisense.dispose() 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 return
} }
// If we have nested workspace folders we only start a server on the outer most workspace folder.
folder = getOuterMostWorkspaceFolder(folder)
intellisense.reload(tw) if (!clients.has(folder.uri.toString())) {
let debugOptions = {
execArgv: ['--nolazy', `--inspect=${6011 + clients.size}`]
} }
} let serverOptions = {
run: { module, transport: TransportKind.ipc },
async function getTailwind() { debug: { module, transport: TransportKind.ipc, options: debugOptions }
if (!vscode.workspace.name) return }
let clientOptions: LanguageClientOptions = {
let files = await vscode.workspace.findFiles( documentSelector: LANGUAGES.map(language => ({
CONFIG_GLOB, scheme: 'file',
'**/node_modules/**', language,
1 pattern: `${folder.uri.fsPath}/**/*`
})),
diagnosticCollectionName: 'lsp-multi-server-example',
workspaceFolder: folder,
outputChannel: outputChannel
}
let client = new LanguageClient(
'lsp-multi-server-example',
'LSP Multi Server Example',
serverOptions,
clientOptions
) )
client.start()
if (!files.length) return null clients.set(folder.uri.toString(), client)
let configPath = files[0].fsPath
delete require.cache[configPath]
let tailwindPackage = await vscode.workspace.findFiles(
'**/node_modules/tailwindcss/package.json',
null,
1
)
if (!tailwindPackage.length) return null
let pluginPath = dirname(tailwindPackage[0].fsPath)
let tw
try {
tw = await tailwindClassNames({
configPath,
pluginPath,
tree: true,
strings: true
})
} catch (err) {
return null
}
return tw
}
export function deactivate() {}
function createCompletionItemProvider({
items,
prefixedItems,
languages,
regex,
triggerCharacters,
config,
enable = () => true,
emmet = false
}: {
items?
prefixedItems?
languages?: string[]
regex?: RegExp
triggerCharacters?: string[]
config?
prefix?: string
enable?: (text: string) => boolean
emmet?: boolean
} = {}): vscode.Disposable {
return vscode.languages.registerCompletionItemProvider(
languages,
{
provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position
): vscode.CompletionItem[] {
const separator = config.options.separator || ':'
let str
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(regex)
if (matches) {
let parts = matches[matches.length - 1].split(' ')
str = parts[parts.length - 1]
} else if (emmet) {
// 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
.map(x => `${x}\\b`)
.join('|')}).*?\\.([^.()#>*^ \\[\\]=$@{}]*)$`
)
)
}
if (matches) {
let parts = matches[matches.length - 1].split('.')
str = parts[parts.length - 1]
} }
} }
let screens = Object.keys(dlv(config, 'screens', {})) Workspace.onDidOpenTextDocument(didOpenTextDocument)
let states = ['hover', 'focus', 'active', 'group-hover'] Workspace.textDocuments.forEach(didOpenTextDocument)
Workspace.onDidChangeWorkspaceFolders(event => {
if (typeof str !== 'undefined') { for (let folder of event.removed) {
console.log(str) let client = clients.get(folder.uri.toString())
const pth = str if (client) {
.replace( clients.delete(folder.uri.toString())
new RegExp( client.stop()
`^(${[...screens, ...states].join('|')})${separator}`,
'g'
),
'$1.'
)
.replace(new RegExp(`\\.(${states.join('|')})${separator}`), '.$1.')
.replace(/\.$/, '')
.replace(/^\./, '')
.replace(/\./g, '.children.')
let hasSep = new RegExp(
`^(${[...screens, ...states].join('|')})${separator}`
).test(str)
if (!hasSep && str.endsWith(separator)) {
// token.cancel()
return getItemsWithRange(
items,
new vscode.Range(position.translate(0, -str.length), position)
)
let mobNav = new vscode.CompletionItem(
'mob-nav-',
vscode.CompletionItemKind.Constant
)
mobNav.range = new vscode.Range(position.translate(0, -4), position)
return [mobNav]
}
if (pth !== '') {
const itms =
prefixedItems && str.indexOf('.') === 0 && !hasSep
? dlv(prefixedItems, pth)
: dlv(items, pth)
if (itms) {
return getItemsWithRange(itms.children)
} }
} }
if (!hasSep) {
return prefixedItems && str.indexOf('.') === 0
? getItemsWithRange(prefixedItems)
: getItemsWithRange(items)
}
return []
}
return []
}
},
...triggerCharacters
)
}
function getItemsWithRange(items, range: vscode.Range = undefined) {
return Object.keys(items).map(x => {
let i = items[x].item
i.range = range
return i
}) })
} }
function createConfigItemProvider({ export function deactivate(): Thenable<void> {
languages, let promises: Thenable<void>[] = []
items, if (defaultClient) {
enable = () => true promises.push(defaultClient.stop())
}: {
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)
} }
for (let client of clients.values()) {
return Object.keys(items).map(x => items[x].item) promises.push(client.stop())
} }
}, return Promise.all(promises).then(() => undefined)
"'",
'"',
'.'
)
}
function prefixItems(items, str, prefix) {
const addPrefix =
typeof prefix !== 'undefined' && prefix !== '' && str === prefix
return Object.keys(items).map(x => {
const item = items[x].item
if (addPrefix) {
item.filterText = item.insertText = `${prefix}${item.label}`
} else {
item.filterText = item.insertText = item.label
}
return item
})
}
function depthOf(obj) {
if (typeof obj !== 'object' || Array.isArray(obj)) return 0
let level = 1
for (let key in obj) {
if (!obj.hasOwnProperty(key)) continue
if (typeof obj[key] === 'object') {
const depth = depthOf(obj[key]) + 1
level = Math.max(depth, level)
}
}
return level
}
function createItems(classNames, separator, config, prefix = '', parent = '') {
let items = {}
let i = 0
Object.keys(classNames).forEach(key => {
if (depthOf(classNames[key]) === 0) {
const item = new vscode.CompletionItem(
key,
vscode.CompletionItemKind.Constant
)
item.filterText = item.insertText = `${prefix}${key}`
item.sortText = naturalExpand(i.toString())
if (key !== 'container' && key !== 'group') {
if (parent) {
item.detail = classNames[key].replace(
new RegExp(`:${parent} \{(.*?)\}`),
'$1'
)
} else {
item.detail = classNames[key]
}
let color = getColorFromDecl(item.detail)
if (color) {
item.kind = vscode.CompletionItemKind.Color
item.documentation = color
}
}
items[key] = {
item
}
i++
} else {
const item = new vscode.CompletionItem(
`${key}${separator}`,
vscode.CompletionItemKind.Constant
)
item.filterText = item.insertText = `${prefix}${key}${separator}`
item.sortText = naturalExpand(i.toString())
item.command = { title: '', command: 'editor.action.triggerSuggest' }
if (key === 'hover' || key === 'focus' || key === 'active') {
item.detail = `:${key}`
item.sortText = `a${item.sortText}`
} else if (key === 'group-hover') {
item.detail = '.group:hover &'
item.sortText = `a${item.sortText}`
} else if (
config.screens &&
Object.keys(config.screens).indexOf(key) !== -1
) {
item.detail = `@media (min-width: ${config.screens[key]})`
item.sortText = `m${item.sortText}`
}
items[key] = {
item,
children: createItems(classNames[key], separator, config, prefix, key)
}
i++
}
})
return items
}
function createConfigItems(config, prefix = '') {
let items = {}
let i = 0
Object.keys(config).forEach(key => {
let item = new vscode.CompletionItem(
key,
vscode.CompletionItemKind.Constant
)
if (depthOf(config[key]) === 0) {
if (key === 'plugins') return
item.filterText = item.insertText = `${prefix}${key}`
item.sortText = naturalExpand(i.toString())
if (typeof config[key] === 'string' || typeof config[key] === 'number') {
item.detail = config[key].toString()
let color = getColorFromValue(item.detail)
if (color) {
item.kind = vscode.CompletionItemKind.Color
item.documentation = color
}
} else if (Array.isArray(config[key])) {
item.detail = stringifyArray(config[key])
}
items[key] = { item }
} else {
if (key === 'modules' || key === 'options') return
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) }
}
i++
})
return items
}
class TailwindIntellisense {
private _providers: vscode.Disposable[]
private _disposable: vscode.Disposable
private _tailwind
private _items
private _prefixedItems
private _configItems
private _prefixedConfigItems
constructor(tailwind) {
if (tailwind) {
this._tailwind = tailwind
this.reload(tailwind)
}
}
public reload(tailwind) {
this.dispose()
const separator = dlv(tailwind.config, 'options.separator', ':')
// if (separator !== ':') return
this._items = createItems(tailwind.classNames, separator, tailwind.config)
this._prefixedItems = 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
})
)
this._providers.push(
createCompletionItemProvider({
items: this._items,
prefixedItems: this._prefixedItems,
languages: CSS_TYPES,
regex: /@apply ([^;}]*)$/,
triggerCharacters: ['.', separator],
config: tailwind.config
})
)
this._providers.push(
createCompletionItemProvider({
items: this._items,
prefixedItems: this._items,
languages: ['postcss'],
regex: /@apply ([^;}]*)$/,
triggerCharacters: ['.', separator],
config: tailwind.config
})
)
this._providers.push(
createCompletionItemProvider({
items: this._items,
languages: HTML_TYPES,
regex: /\bclass=["']([^"']*)$/, // /\bclass(Name)?=(["'])(?!.*?\2)/
triggerCharacters: ["'", '"', ' ', '.', separator],
config: tailwind.config,
emmet: true
})
)
this._providers.push(
createCompletionItemProvider({
items: this._items,
languages: JS_TYPES,
regex: /\bclass(Name)?=["']([^"']*)$/, // /\bclass(Name)?=(["'])(?!.*?\2)/
triggerCharacters: ["'", '"', ' ', separator]
.concat([
Object.keys(
vscode.workspace.getConfiguration('emmet.includeLanguages')
).indexOf('javascript') !== -1 && '.'
])
.filter(Boolean),
config: tailwind.config,
emmet:
Object.keys(
vscode.workspace.getConfiguration('emmet.includeLanguages')
).indexOf('javascript') !== -1
})
)
// Vue.js
this._providers.push(
createCompletionItemProvider({
items: this._items,
languages: ['vue'],
regex: /\bclass=["']([^"']*)$/,
enable: isWithinTemplate,
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('<script') !== -1 &&
text.indexOf('</script>') === -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('<style') !== -1 &&
text.indexOf('</style>') === -1
) {
return true
}
return false
}
})
)
this._providers.push(
createConfigItemProvider({
languages: CSS_TYPES,
items: this._prefixedConfigItems
})
)
this._providers.push(
createConfigItemProvider({
languages: ['postcss'],
items: this._configItems
})
)
this._providers.push(
createConfigItemProvider({
languages: ['vue'],
items: this._configItems,
enable: text => {
if (
text.indexOf('<style') !== -1 &&
text.indexOf('</style>') === -1
) {
return true
}
return false
}
})
)
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)
if (!/\bclass(Name)?=['"][^'"]*$/.test(text1)) return
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)
let str = text1 + text2.substr(text1.length).match(/^([^"' ]*)/)[0]
let matches = str.match(/\bclass(Name)?=["']([^"']*)$/)
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}`
}
let hoverStr = new vscode.MarkdownString()
let css = this._tailwind.classNames[base]
let m = css.match(/^(::?[a-z-]+) {(.*?)}/)
if (m) {
selector += m[1]
css = m[2].trim()
}
css = css.replace(/([;{]) /g, '$1\n').replace(/^/gm, ' ')
let code = `${selector} {\n${css}\n}`
let screens = dlv(this._tailwind.config, 'screens', {})
Object.keys(screens).some(screen => {
if (parts.indexOf(screen) !== -1) {
code = `@media (min-width: ${
screens[screen]
}) {\n${code.replace(/^/gm, ' ')}\n}`
return true
}
return false
})
hoverStr.appendCodeblock(code, 'css')
let hoverRange = new vscode.Range(
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 null
}
}
)
)
// @screen
this._providers.push(
createScreenCompletionItemProvider({
config: tailwind.config,
languages: [...CSS_TYPES, 'postcss', 'vue']
})
)
this._disposable = vscode.Disposable.from(...this._providers)
}
dispose() {
if (this._disposable) {
this._disposable.dispose()
}
}
}
function pad(n) {
return ('00000000' + n).substr(-8)
}
function naturalExpand(a: string) {
return a.replace(/\d+/g, pad)
}
function stringifyArray(arr: Array<any>): string {
return arr
.reduce((acc, curr) => {
let str = curr.toString()
if (str.includes(' ')) {
acc.push(`"${str.replace(/\s\s+/g, ' ')}"`)
} else {
acc.push(str)
}
return acc
}, [])
.join(', ')
}
function escapeClassName(className) {
return className.replace(/([^A-Za-z0-9\-])/g, '\\$1')
}
function getColorFromDecl(cssStr: string) {
let matches = cssStr.match(/: ([^;]+);/g)
if (matches === null || matches.length > 1) return
let color = matches[0].slice(2, -1).trim()
return getColorFromValue(color)
}
function getColorFromValue(value: string) {
if (value === 'transparent') {
return 'rgba(0, 0, 0, 0.01)'
}
try {
let parsed = Color(value)
if (parsed.valpha === 0) return 'rgba(0, 0, 0, 0.01)'
return parsed.rgb().string()
} catch (err) {
return
}
}
function createScreenCompletionItemProvider({
languages,
config
}): vscode.Disposable {
return vscode.languages.registerCompletionItemProvider(
languages,
{
provideCompletionItems: (
document: vscode.TextDocument,
position: vscode.Position
): vscode.CompletionItem[] => {
let range: vscode.Range = new vscode.Range(
new vscode.Position(0, 0),
position
)
let text: string = document.getText(range)
if (
document.languageId === 'vue' &&
!(text.indexOf('<style') !== -1 && text.indexOf('</style>') === -1)
)
return []
let line = text.split(/[\n\r]/).pop()
if (/@screen $/.test(line)) {
return Object.keys(dlv(config, 'screens', {})).map((screen, i) => {
let item = new vscode.CompletionItem(
screen,
vscode.CompletionItemKind.Constant
)
item.insertText = new vscode.SnippetString(`${screen} {\n\t$0\n}`)
item.detail =
typeof config.screens[screen] === 'string'
? config.screens[screen]
: ''
item.sortText = naturalExpand(i.toString())
return item
})
}
return []
}
},
' '
)
}
function isWithinTemplate(text: string) {
let regex = /(<\/?template\b)/g
let match
let d = 0
while ((match = regex.exec(text)) !== null) {
d += match[0] === '</template' ? -1 : 1
}
return d !== 0
} }

View File

@ -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'
]

View File

@ -1,75 +0,0 @@
{
"scopeName": "source.css.tailwind",
"fileTypes": [],
"injectionSelector": "meta.property-list.css, meta.property-list.scss",
"name": "TailwindCSS",
"patterns": [
{
"begin": "^\\s*(@)apply\\b",
"beginCaptures": {
"0": {
"name": "keyword.control.at-rule.apply.tailwind"
},
"1": {
"name": "punctuation.definition.keyword.tailwind"
}
},
"end": ";",
"endCaptures": {
"0": {
"name": "punctuation.terminator.rule.tailwind"
}
},
"patterns": [
{
"begin": "(?x)\n(?=\n (?:\\|)? # Possible anonymous namespace prefix\n (?:\n [-\\[:.*\\#a-zA-Z_] # Valid selector character\n |\n [^\\x00-\\x7F] # Which can include non-ASCII symbols\n |\n \\\\ # Or an escape sequence\n (?:[0-9a-fA-F]{1,6}|.)\n )\n)",
"end": "(?=\\s*[;])",
"patterns": [
{
"match": "!\\s*important(?![\\w-])",
"name": "keyword.other.important.tailwind"
},
{
"captures": {
"1": {
"name": "punctuation.definition.entity.tailwind"
},
"2": {
"patterns": [
{
"include": "source.css#escapes"
}
]
}
},
"match": "(?x)\n(\\.)? # Valid class-name\n(\n (?: [-a-zA-Z_0-9]|[^\\x00-\\x7F] # Valid identifier characters\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n )+\n) # Followed by either:\n(?= $ # - End of the line\n | [\\s,.\\#)\\[:{>;+~|] # - Another selector\n | /\\* # - A block comment\n)",
"name": "entity.other.attribute-name.class.tailwind"
}
]
}
]
},
{
"begin": "(?i)(?<![\\w-])(config)(\\()",
"beginCaptures": {
"1": {
"name": "support.function.config.tailwind"
},
"2": {
"name": "punctuation.section.function.begin.bracket.round.tailwind"
}
},
"end": "\\)",
"endCaptures": {
"0": {
"name": "punctuation.section.function.end.bracket.round.tailwind"
}
},
"patterns": [
{
"include": "source.css#property-values"
}
]
}
]
}

View File

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