add lsp boilerplate
|
@ -1,5 +1,6 @@
|
||||||
out
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
.vscode-test/
|
.vscode-test/
|
||||||
.vsix
|
.vsix
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.rts2_cache_cjs
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
21
CHANGELOG.md
|
@ -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" />
|
|
50
README.md
|
@ -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">
|
|
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 32 KiB |
BIN
img/css.gif
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 592 KiB |
BIN
img/html.gif
Before Width: | Height: | Size: 2.8 MiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.3 MiB |
30
package.json
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
914
src/extension.ts
|
@ -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 },
|
||||||
|
debug: { module, transport: TransportKind.ipc, options: debugOptions }
|
||||||
}
|
}
|
||||||
|
let clientOptions: LanguageClientOptions = {
|
||||||
async function getTailwind() {
|
documentSelector: LANGUAGES.map(language => ({
|
||||||
if (!vscode.workspace.name) return
|
scheme: 'file',
|
||||||
|
language,
|
||||||
let files = await vscode.workspace.findFiles(
|
pattern: `${folder.uri.fsPath}/**/*`
|
||||||
CONFIG_GLOB,
|
})),
|
||||||
'**/node_modules/**',
|
diagnosticCollectionName: 'lsp-multi-server-example',
|
||||||
1
|
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
|
||||||
]
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es6",
|
|
||||||
"outDir": "out",
|
|
||||||
"lib": [
|
|
||||||
"es6"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
|
||||||
"rootDir": "src"
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
".vscode-test"
|
|
||||||
]
|
|
||||||
}
|
|