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
.vscode-test/
.vsix
.DS_Store
.rts2_cache_cjs

39
.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",
"configurations": [
{
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/out/**/*.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"
}
]
"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 @@
// 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

10064
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"version": "0.1.16",
"publisher": "bradlc",
"engines": {
"vscode": "^1.20.0"
"vscode": "^1.23.0"
},
"categories": [
"Other"
@ -25,7 +25,8 @@
"activationEvents": [
"workspaceContains:**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js"
],
"main": "./out/extension",
"source": "./src/extension.ts",
"main": "./dist/extension.js",
"contributes": {
"grammars": [
{
@ -42,21 +43,11 @@
},
"preview": true,
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"vscode:prepublish": "npm run build",
"build": "microbundle -f cjs --external vscode,child_process,os,crypto,net",
"watch": "npm run build -- --watch",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "npm run compile && 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"
"test": "npm run build && node ./node_modules/vscode/bin/test"
},
"author": "Brad Cornes <bradlc41@gmail.com>",
"license": "MIT",
@ -68,5 +59,12 @@
"repository": {
"type": "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 { dirname } from 'path'
const htmlElements = require('./htmlElements.js')
// const tailwindClassNames = require('tailwind-class-names')
const tailwindClassNames = require('/Users/brad/Code/tailwind-class-names/dist')
const dlv = require('dlv')
const Color = require('color')
import {
LanguageClient,
LanguageClientOptions,
TransportKind
} from 'vscode-languageclient'
const CONFIG_GLOB =
'**/{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']
let LANGUAGES: string[] = ['html']
export async function activate(context: vscode.ExtensionContext) {
let tw
let defaultClient: LanguageClient
let clients: Map<string, LanguageClient> = new Map()
try {
tw = await getTailwind()
} catch (err) {}
let intellisense = new TailwindIntellisense(tw)
context.subscriptions.push(intellisense)
let watcher = vscode.workspace.createFileSystemWatcher(CONFIG_GLOB)
watcher.onDidChange(onFileChange)
watcher.onDidCreate(onFileChange)
watcher.onDidDelete(onFileChange)
async function onFileChange(event) {
try {
tw = await getTailwind()
} catch (err) {
intellisense.dispose()
return
}
if (!tw) {
intellisense.dispose()
return
}
intellisense.reload(tw)
}
}
async function getTailwind() {
if (!vscode.workspace.name) return
let files = await vscode.workspace.findFiles(
CONFIG_GLOB,
'**/node_modules/**',
1
)
if (!files.length) return null
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', {}))
let states = ['hover', 'focus', 'active', 'group-hover']
if (typeof str !== 'undefined') {
console.log(str)
const pth = str
.replace(
new RegExp(
`^(${[...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)
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 + '/'
}
}
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({
languages,
items,
enable = () => true
}: {
languages?: string[]
items?: vscode.CompletionItem[]
enable?: (text: string) => boolean
} = {}) {
return vscode.languages.registerCompletionItemProvider(
languages,
{
provideCompletionItems: (
document: vscode.TextDocument,
position: vscode.Position
): vscode.CompletionItem[] => {
const range: vscode.Range = new vscode.Range(
new vscode.Position(0, 0),
position
)
const text: string = document.getText(range)
if (!enable(text)) return []
let lines = text.split(/[\n\r]/)
let matches = lines
.slice(-5)
.join('\n')
.match(/config\(["']([^"']*)$/)
if (!matches) return []
let objPath =
matches[1]
.replace(/\.[^.]*$/, '')
.replace('.', '.children.')
.trim() + '.children'
let foo = dlv(items, objPath)
if (foo) {
return Object.keys(foo).map(x => foo[x].item)
}
return Object.keys(items).map(x => items[x].item)
}
},
"'",
'"',
'.'
)
}
function prefixItems(items, str, prefix) {
const addPrefix =
typeof prefix !== 'undefined' && prefix !== '' && str === prefix
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 result
})
}
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
.sort((a, b) => {
return a.length - b.length
})
: []
}
return d !== 0
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('server', 'out', 'server.js'))
let module = '/Users/brad/Code/tailwindcss-language-server/dist/index.js'
let outputChannel: OutputChannel = Window.createOutputChannel(
'lsp-multi-server-example'
)
function didOpenTextDocument(document: TextDocument): 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())) {
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: 'lsp-multi-server-example',
workspaceFolder: folder,
outputChannel: outputChannel
}
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

@ -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"
]
}