diff --git a/.github/autocomplete.png b/.github/autocomplete.png
deleted file mode 100644
index 7857187025e47936aa933cb2cfefc187ac4ef10d..0000000000000000000000000000000000000000
Binary files a/.github/autocomplete.png and /dev/null differ
diff --git a/.github/banner.png b/.github/banner.png
deleted file mode 100644
index 1f927163a27fffb33f9be3bb52928f69bf020242..0000000000000000000000000000000000000000
Binary files a/.github/banner.png and /dev/null differ
diff --git a/.github/hover.png b/.github/hover.png
deleted file mode 100644
index e46a8efaa5dfdd9aeb52facc08a3a7687b6634b3..0000000000000000000000000000000000000000
Binary files a/.github/hover.png and /dev/null differ
diff --git a/.github/linting.png b/.github/linting.png
deleted file mode 100644
index 0c118825152d9335690fc59702cb4b842663e652..0000000000000000000000000000000000000000
Binary files a/.github/linting.png and /dev/null differ
diff --git a/.vscodeignore b/.vscodeignore
index 591602a20e25e0f55f0c73215f8494ca8a21fd2a..23a60828e8f0b03542bc2d6c396023fd6f92343e 100755
--- a/.vscodeignore
+++ b/.vscodeignore
@@ -8,4 +8,4 @@ contributing.md
node_modules/**
src/**
tests/**
-.github/**
+img/**
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b3f32a981d6b114a3a3fb93fc044ce328c95b90..f8bf3f3ba96f9cb15680ba52eb982625599c7293 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,22 +1,5 @@
# Changelog
-## 0.4.0
-
-- Added linting and quick fixes for both CSS and markup
-- Updated module resolution for compatibility with pnpm (#128)
-- The extension now ignores the `purge` option when extracting class names (#131)
-- Fixed hover offsets for class names which appear after interpolations
-
-## 0.3.1
-
-- Fixed class attribute completions not showing when using the following Pug syntax (#125):
- ```
- div(class="")
- ```
-- Fixed hover previews not showing when using a computed class attribute in Vue templates
-- Restore missing readme images
-- Update settings descriptions to use markdown
-
## 0.3.0
### General
diff --git a/README.md b/README.md
index da341e65e5e2eb36d7069a87e9958b35a632318b..27cc6e8c480bc703fa2c9a987bd60aac0b73250a 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,60 @@
-
+# Tailwind CSS IntelliSense
+
+> [Tailwind CSS](https://tailwindcss.com/) class name completion for VS Code
-Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting.
+**[Get it from the VS Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)**
-## Installation
+
-**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)**
+## Requirements
-In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace.
+This extension requires:
+- a `tailwind.config.js` file to be [present in your project folder](https://github.com/bradlc/vscode-tailwindcss/blob/master/package.json#L38). You can create it with `npx tailwind init`.
+- `tailwindcss` to be installed (present in project `node_modules/`)
## Features
-### Autocomplete
+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)
-Intelligent suggestions for class names, as well as [CSS functions and directives](https://tailwindcss.com/docs/functions-and-directives/).
+### CSS
-
+- [Suggestions when using `@apply` and config helpers](#suggestions-when-using-apply-and-config)
+- Suggestions when using the `@screen` directive
+- Suggestions when using the `@variants` directive
+- [Improves syntax highlighting when using `@apply` and config helpers](#improves-syntax-highlighting-when-using-apply-and-config-helpers)
-### Linting
+## Examples
-Highlights errors and potential bugs in both your CSS and your markup.
+#### Class name suggestions, including support for Emmet syntax
-
+
-### Hover Preview
+#### CSS preview when hovering over class names
-See the complete CSS for a Tailwind class name by hovering over it.
+
-
+#### Suggestions when using `@apply` and config helpers
-### CSS Syntax Highlighting
+
-Provides syntax definitions so that Tailwind features are highlighted correctly.
+#### Improves syntax highlighting when using `@apply` and config helpers
+
+Before:
+
+
+
+After:
+
+
## Settings
@@ -46,7 +70,7 @@ }
}
```
-### `tailwindCSS.emmetCompletions`
+### `tailwindcss.emmetCompletions`
Enable completions when using [Emmet](https://emmet.io/)-style syntax, for example `div.bg-red-500.uppercase`. **Default: `false`**
@@ -55,43 +79,3 @@ {
"tailwindCSS.emmetCompletions": true
}
```
-
-### `tailwindCSS.validate`
-
-Enable linting. Rules can be configured individually using the `tailwindcss.lint` settings:
-
-- `ignore`: disable lint rule entirely
-- `warning`: rule violations will be considered "warnings," typically represented by a yellow underline
-- `error`: rule violations will be considered "errors," typically represented by a red underline
-
-#### `tailwindCSS.lint.invalidScreen`
-
-Unknown screen name used with the [`@screen` directive](https://tailwindcss.com/docs/functions-and-directives/#screen). **Default: `error`**
-
-#### `tailwindCSS.lint.invalidVariant`
-
-Unknown variant name used with the [`@variants` directive](https://tailwindcss.com/docs/functions-and-directives/#variants). **Default: `error`**
-
-#### `tailwindCSS.lint.invalidTailwindDirective`
-
-Unknown value used with the [`@tailwind` directive](https://tailwindcss.com/docs/functions-and-directives/#tailwind). **Default: `error`**
-
-#### `tailwindCSS.lint.invalidApply`
-
-Unsupported use of the [`@apply` directive](https://tailwindcss.com/docs/functions-and-directives/#apply). **Default: `error`**
-
-#### `tailwindCSS.lint.invalidConfigPath`
-
-Unknown or invalid path used with the [`theme` helper](https://tailwindcss.com/docs/functions-and-directives/#theme). **Default: `error`**
-
-#### `tailwindCSS.lint.cssConflict`
-
-Class names on the same HTML element which apply the same CSS property or properties. **Default: `warning`**
-
-## Troubleshooting
-
-If you’re having issues getting the IntelliSense features to activate, there are a few things you can check:
-
-- Ensure that you have a Tailwind config file in your workspace and that this is named `tailwind.config.js` or `tailwind.js`. Check out the Tailwind documentation for details on [creating a config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional).
-- Ensure that the `tailwindcss` module is installed in your workspace, via `npm`, `yarn`, or `pnpm`. Tailwind CSS IntelliSense does not currently support Yarn Plug'n'Play.
-- If you installed `tailwindcss` or created your config file while your project was already open in Visual Studio Code you may need to reload the editor. You can either restart VS Code entirely, or use the `Developer: Reload Window` command which can be found in the command palette.
diff --git a/img/css-highlighting-after.png b/img/css-highlighting-after.png
new file mode 100755
index 0000000000000000000000000000000000000000..86a12fb8596e001e2c68d171733cb7950d66335c
Binary files /dev/null and b/img/css-highlighting-after.png differ
diff --git a/img/css-highlighting-before.png b/img/css-highlighting-before.png
new file mode 100755
index 0000000000000000000000000000000000000000..2eeffdc19d7293136e52f9426eb66456d0c1abb4
Binary files /dev/null and b/img/css-highlighting-before.png differ
diff --git a/img/css.gif b/img/css.gif
new file mode 100755
index 0000000000000000000000000000000000000000..e81904e7863349207daf4e368aae489a2f9374d8
Binary files /dev/null and b/img/css.gif differ
diff --git a/img/html-hover.gif b/img/html-hover.gif
new file mode 100755
index 0000000000000000000000000000000000000000..94540b86a062f0274f9ae1d3d755898f749fed43
Binary files /dev/null and b/img/html-hover.gif differ
diff --git a/img/html.gif b/img/html.gif
new file mode 100755
index 0000000000000000000000000000000000000000..8ffc7ffa9962333d28c60136b1f7ad50edf09883
Binary files /dev/null and b/img/html.gif differ
diff --git a/package-lock.json b/package-lock.json
index 9184c0f6d4d60cb918f95cc528656182719d94a9..91c1aca81ac946c1fd36e4fd59904914710f8008 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -2026,12 +2026,6 @@ "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz",
"integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=",
"dev": true
},
- "detect-indent": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz",
- "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==",
- "dev": true
- },
"detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -6089,12 +6083,6 @@ "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
"dev": true,
"optional": true
- },
- "sift-string": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/sift-string/-/sift-string-0.0.2.tgz",
- "integrity": "sha1-G7ArEhslu4sHRwQr+afh2s+PuJw=",
- "dev": true
},
"signal-exit": {
"version": "3.0.3",
diff --git a/package.json b/package.json
index c209d4be4eee26ae6b6a3744e74f540a95d5624b..e234600cea927baa23013a2664e31a4b32f7e355 100755
--- a/package.json
+++ b/package.json
@@ -1,19 +1,19 @@
{
"name": "vscode-tailwindcss",
"displayName": "Tailwind CSS IntelliSense",
- "description": "Intelligent Tailwind CSS tooling for VS Code",
+ "description": "Tailwind CSS class name completion",
"preview": true,
"author": "Brad Cornes ",
"license": "MIT",
"version": "0.3.1",
- "homepage": "https://github.com/tailwindcss/intellisense",
+ "homepage": "https://github.com/bradlc/vscode-tailwindcss",
"bugs": {
- "url": "https://github.com/tailwindcss/intellisense/issues",
+ "url": "https://github.com/bradlc/vscode-tailwindcss/issues",
"email": "hello@bradley.dev"
},
"repository": {
"type": "git",
- "url": "https://github.com/tailwindcss/intellisense.git"
+ "url": "https://github.com/bradlc/vscode-tailwindcss.git"
},
"publisher": "bradlc",
"keywords": [
@@ -28,11 +28,10 @@ "engines": {
"vscode": "^1.33.0"
},
"categories": [
- "Linters",
"Other"
],
"galleryBanner": {
- "color": "#f9fafb"
+ "color": "#f1f5f8"
},
"icon": "media/icon.png",
"activationEvents": [
@@ -70,78 +69,6 @@ "type": "string"
},
"default": {},
"markdownDescription": "Enable features in languages that are not supported by default. Add a mapping here between the new language and an already supported language.\n E.g.: `{\"plaintext\": \"html\"}`"
- },
- "tailwindCSS.validate": {
- "type": "boolean",
- "default": true,
- "markdownDescription": "Enable linting",
- "scope": "language-overridable"
- },
- "tailwindCSS.lint.cssConflict": {
- "type": "string",
- "enum": [
- "ignore",
- "warning",
- "error"
- ],
- "default": "warning",
- "markdownDescription": "Class names on the same HTML element which apply the same CSS property or properties",
- "scope": "language-overridable"
- },
- "tailwindCSS.lint.invalidApply": {
- "type": "string",
- "enum": [
- "ignore",
- "warning",
- "error"
- ],
- "default": "error",
- "markdownDescription": "Unsupported use of the [`@apply` directive](https://tailwindcss.com/docs/functions-and-directives/#apply)",
- "scope": "language-overridable"
- },
- "tailwindCSS.lint.invalidScreen": {
- "type": "string",
- "enum": [
- "ignore",
- "warning",
- "error"
- ],
- "default": "error",
- "markdownDescription": "Unknown screen name used with the [`@screen` directive](https://tailwindcss.com/docs/functions-and-directives/#screen)",
- "scope": "language-overridable"
- },
- "tailwindCSS.lint.invalidVariant": {
- "type": "string",
- "enum": [
- "ignore",
- "warning",
- "error"
- ],
- "default": "error",
- "markdownDescription": "Unknown variant name used with the [`@variants` directive](https://tailwindcss.com/docs/functions-and-directives/#variants)",
- "scope": "language-overridable"
- },
- "tailwindCSS.lint.invalidConfigPath": {
- "type": "string",
- "enum": [
- "ignore",
- "warning",
- "error"
- ],
- "default": "error",
- "markdownDescription": "Unknown or invalid path used with the [`theme` helper](https://tailwindcss.com/docs/functions-and-directives/#theme)",
- "scope": "language-overridable"
- },
- "tailwindCSS.lint.invalidTailwindDirective": {
- "type": "string",
- "enum": [
- "ignore",
- "warning",
- "error"
- ],
- "default": "error",
- "markdownDescription": "Unknown value used with the [`@tailwind` directive](https://tailwindcss.com/docs/functions-and-directives/#tailwind)",
- "scope": "language-overridable"
}
}
}
@@ -166,7 +93,6 @@ "callsite": "^1.0.0",
"chokidar": "^3.3.1",
"concurrently": "^5.1.0",
"css.escape": "^1.5.1",
- "detect-indent": "^6.0.0",
"dlv": "^1.1.3",
"dset": "^2.0.1",
"esm": "^3.2.25",
@@ -185,7 +111,6 @@ "postcss-selector-parser": "^6.0.2",
"resolve-from": "^5.0.0",
"rimraf": "^3.0.2",
"semver": "^7.3.2",
- "sift-string": "0.0.2",
"stack-trace": "0.0.10",
"terser": "^4.6.12",
"tiny-invariant": "^1.1.0",
diff --git a/src/class-names/index.js b/src/class-names/index.js
index 0ddd81c9b9ed00ff1a357129203ce973b23911c1..ca8ddec1e8696085fcb79738f7bdb42fe08beb72 100644
--- a/src/class-names/index.js
+++ b/src/class-names/index.js
@@ -141,10 +141,6 @@ resolvedConfig,
postcss,
browserslist,
}),
- modules: {
- tailwindcss,
- postcss,
- },
}
}
diff --git a/src/extension.ts b/src/extension.ts
index 279ec969590ba1e30120474c179722fa450c238d..be1fe479cac88456c5893e28a533d1c3c2ec81ca 100755
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -11,7 +11,6 @@ TextDocument,
OutputChannel,
WorkspaceFolder,
Uri,
- ConfigurationScope,
} from 'vscode'
import {
LanguageClient,
@@ -23,7 +22,6 @@ import { DEFAULT_LANGUAGES } from './lib/languages'
import isObject from './util/isObject'
import { dedupe, equal } from './util/array'
import { createEmitter } from './lib/emitter'
-import { onMessage } from './lsp/notifications'
const CLIENT_ID = 'tailwindcss-intellisense'
const CLIENT_NAME = 'Tailwind CSS IntelliSense'
@@ -152,9 +150,6 @@
client.onReady().then(() => {
let emitter = createEmitter(client)
registerConfigErrorHandler(emitter)
- onMessage(client, 'getConfiguration', async (scope) => {
- return Workspace.getConfiguration('tailwindCSS', scope)
- })
})
client.start()
diff --git a/src/lib/emitter.ts b/src/lib/emitter.ts
index 3ad6f05f010872359a4d0a2ef289456f37d74490..d177c2cf355a60b5c265fb5df12b01e0feb0ffb6 100644
--- a/src/lib/emitter.ts
+++ b/src/lib/emitter.ts
@@ -1,7 +1,6 @@
import mitt from 'mitt'
import { LanguageClient } from 'vscode-languageclient'
import crypto from 'crypto'
-import { Connection } from 'vscode-languageserver'
export interface NotificationEmitter {
on: (name: string, handler: (args: any) => void) => void
@@ -9,9 +8,7 @@ off: (name: string, handler: (args: any) => void) => void
emit: (name: string, args: any) => Promise
}
-export function createEmitter(
- client: LanguageClient | Connection
-): NotificationEmitter {
+export function createEmitter(client: LanguageClient): NotificationEmitter {
const emitter = mitt()
const registered: string[] = []
@@ -29,7 +26,7 @@ const off = (name: string, handler: (args: any) => void) => {
emitter.off(name, handler)
}
- const emit = (name: string, params: Record = {}) => {
+ const emit = (name: string, params: any) => {
return new Promise((resolve, _reject) => {
const id = crypto.randomBytes(16).toString('hex')
on(`${name}Response`, (result) => {
diff --git a/src/lsp/notifications.ts b/src/lsp/notifications.ts
index e8f4ab856ac0c1a8e4809e78761fb53640488742..bb4e60dc496922fe1221147ff41a690e5f60d344 100644
--- a/src/lsp/notifications.ts
+++ b/src/lsp/notifications.ts
@@ -1,10 +1,9 @@
import { Connection } from 'vscode-languageserver'
-import { LanguageClient } from 'vscode-languageclient'
export function onMessage(
- connection: LanguageClient | Connection,
+ connection: Connection,
name: string,
- handler: (params: any) => Thenable>
+ handler: (params: any) => any
): void {
connection.onNotification(`tailwindcss/${name}`, async (params: any) => {
const { _id, ...rest } = params
diff --git a/src/lsp/providers/codeActions/codeActionProvider.ts b/src/lsp/providers/codeActions/codeActionProvider.ts
deleted file mode 100644
index 19420f68ca37f549bac335634c81d8b5d3119af1..0000000000000000000000000000000000000000
--- a/src/lsp/providers/codeActions/codeActionProvider.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { CodeAction, CodeActionParams } from 'vscode-languageserver'
-import { State } from '../../util/state'
-import { getDiagnostics } from '../diagnostics/diagnosticsProvider'
-import { rangesEqual } from '../../util/rangesEqual'
-import {
- DiagnosticKind,
- isInvalidApplyDiagnostic,
- AugmentedDiagnostic,
- isCssConflictDiagnostic,
- isInvalidConfigPathDiagnostic,
- isInvalidTailwindDirectiveDiagnostic,
- isInvalidScreenDiagnostic,
- isInvalidVariantDiagnostic,
-} from '../diagnostics/types'
-import { flatten, dedupeBy } from '../../../util/array'
-import { provideCssConflictCodeActions } from './provideCssConflictCodeActions'
-import { provideInvalidApplyCodeActions } from './provideInvalidApplyCodeActions'
-import { provideSuggestionCodeActions } from './provideSuggestionCodeActions'
-
-async function getDiagnosticsFromCodeActionParams(
- state: State,
- params: CodeActionParams,
- only?: DiagnosticKind[]
-): Promise {
- let document = state.editor.documents.get(params.textDocument.uri)
- let diagnostics = await getDiagnostics(state, document, only)
-
- return params.context.diagnostics
- .map((diagnostic) => {
- return diagnostics.find((d) => {
- return (
- d.code === diagnostic.code &&
- d.message === diagnostic.message &&
- rangesEqual(d.range, diagnostic.range)
- )
- })
- })
- .filter(Boolean)
-}
-
-export async function provideCodeActions(
- state: State,
- params: CodeActionParams
-): Promise {
- let diagnostics = await getDiagnosticsFromCodeActionParams(
- state,
- params,
- params.context.diagnostics
- .map((diagnostic) => diagnostic.code)
- .filter(Boolean) as DiagnosticKind[]
- )
-
- return Promise.all(
- diagnostics.map((diagnostic) => {
- if (isInvalidApplyDiagnostic(diagnostic)) {
- return provideInvalidApplyCodeActions(state, params, diagnostic)
- }
-
- if (isCssConflictDiagnostic(diagnostic)) {
- return provideCssConflictCodeActions(state, params, diagnostic)
- }
-
- if (
- isInvalidConfigPathDiagnostic(diagnostic) ||
- isInvalidTailwindDirectiveDiagnostic(diagnostic) ||
- isInvalidScreenDiagnostic(diagnostic) ||
- isInvalidVariantDiagnostic(diagnostic)
- ) {
- return provideSuggestionCodeActions(state, params, diagnostic)
- }
-
- return []
- })
- )
- .then(flatten)
- .then((x) => dedupeBy(x, (item) => JSON.stringify(item.edit)))
-}
diff --git a/src/lsp/providers/codeActions/provideCssConflictCodeActions.ts b/src/lsp/providers/codeActions/provideCssConflictCodeActions.ts
deleted file mode 100644
index f6a6edb159000f7e53aae3c88e6b7fef94d08b70..0000000000000000000000000000000000000000
--- a/src/lsp/providers/codeActions/provideCssConflictCodeActions.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { State } from '../../util/state'
-import {
- CodeActionParams,
- CodeAction,
- CodeActionKind,
-} from 'vscode-languageserver'
-import { CssConflictDiagnostic } from '../diagnostics/types'
-import { joinWithAnd } from '../../util/joinWithAnd'
-import { removeRangesFromString } from '../../util/removeRangesFromString'
-
-export async function provideCssConflictCodeActions(
- _state: State,
- params: CodeActionParams,
- diagnostic: CssConflictDiagnostic
-): Promise {
- return [
- {
- title: `Delete ${joinWithAnd(
- diagnostic.otherClassNames.map(
- (otherClassName) => `'${otherClassName.className}'`
- )
- )}`,
- kind: CodeActionKind.QuickFix,
- diagnostics: [diagnostic],
- edit: {
- changes: {
- [params.textDocument.uri]: [
- {
- range: diagnostic.className.classList.range,
- newText: removeRangesFromString(
- diagnostic.className.classList.classList,
- diagnostic.otherClassNames.map(
- (otherClassName) => otherClassName.relativeRange
- )
- ),
- },
- ],
- },
- },
- },
- ]
-}
diff --git a/src/lsp/providers/codeActions/provideInvalidApplyCodeActions.ts b/src/lsp/providers/codeActions/provideInvalidApplyCodeActions.ts
deleted file mode 100644
index ec3d483b6234cb1508465dfbdaabf9154173ff2a..0000000000000000000000000000000000000000
--- a/src/lsp/providers/codeActions/provideInvalidApplyCodeActions.ts
+++ /dev/null
@@ -1,266 +0,0 @@
-import {
- CodeAction,
- CodeActionParams,
- CodeActionKind,
- TextEdit,
- Range,
-} from 'vscode-languageserver'
-import { State } from '../../util/state'
-import { InvalidApplyDiagnostic } from '../diagnostics/types'
-import { getClassNameParts } from '../../util/getClassNameAtPosition'
-import { getLanguageBoundaries } from '../../util/getLanguageBoundaries'
-import { isCssDoc } from '../../util/css'
-import { isWithinRange } from '../../util/isWithinRange'
-const dlv = require('dlv')
-import type { Root, NodeSource } from 'postcss'
-import { absoluteRange } from '../../util/absoluteRange'
-import { removeRangesFromString } from '../../util/removeRangesFromString'
-import detectIndent from 'detect-indent'
-import isObject from '../../../util/isObject'
-import { cssObjToAst } from '../../util/cssObjToAst'
-import dset from 'dset'
-import selectorParser from 'postcss-selector-parser'
-import { flatten } from '../../../util/array'
-import { getClassNameMeta } from '../../util/getClassNameMeta'
-import { validateApply } from '../../util/validateApply'
-
-export async function provideInvalidApplyCodeActions(
- state: State,
- params: CodeActionParams,
- diagnostic: InvalidApplyDiagnostic
-): Promise {
- let document = state.editor.documents.get(params.textDocument.uri)
- let documentText = document.getText()
- let cssRange: Range
- let cssText = documentText
- const { postcss } = state.modules
- let changes: TextEdit[] = []
-
- let totalClassNamesInClassList = diagnostic.className.classList.classList.split(
- /\s+/
- ).length
-
- let className = diagnostic.className.className
- let classNameParts = getClassNameParts(state, className)
- let classNameInfo = dlv(state.classNames.classNames, classNameParts)
-
- if (Array.isArray(classNameInfo)) {
- return []
- }
-
- if (!isCssDoc(state, document)) {
- let languageBoundaries = getLanguageBoundaries(state, document)
- if (!languageBoundaries) return []
- cssRange = languageBoundaries.css.find((range) =>
- isWithinRange(diagnostic.range.start, range)
- )
- if (!cssRange) return []
- cssText = document.getText(cssRange)
- }
-
- try {
- await postcss([
- postcss.plugin('', (_options = {}) => {
- return (root: Root) => {
- root.walkRules((rule) => {
- if (changes.length) return false
-
- rule.walkAtRules('apply', (atRule) => {
- let atRuleRange = postcssSourceToRange(atRule.source)
- if (cssRange) {
- atRuleRange = absoluteRange(atRuleRange, cssRange)
- }
-
- if (!isWithinRange(diagnostic.range.start, atRuleRange))
- return true
-
- let ast = classNameToAst(
- state,
- classNameParts,
- rule.selector,
- diagnostic.className.classList.important
- )
-
- if (!ast) return false
-
- rule.after(ast.nodes)
- let insertedRule = rule.next()
- if (!insertedRule) return false
-
- if (totalClassNamesInClassList === 1) {
- atRule.remove()
- } else {
- changes.push({
- range: diagnostic.className.classList.range,
- newText: removeRangesFromString(
- diagnostic.className.classList.classList,
- diagnostic.className.relativeRange
- ),
- })
- }
-
- let ruleRange = postcssSourceToRange(rule.source)
- if (cssRange) {
- ruleRange = absoluteRange(ruleRange, cssRange)
- }
-
- let outputIndent: string
- let documentIndent = detectIndent(cssText)
-
- changes.push({
- range: ruleRange,
- newText:
- rule.toString() +
- (insertedRule.raws.before || '\n\n') +
- insertedRule
- .toString()
- .replace(/\n\s*\n/g, '\n')
- .replace(/(@apply [^;\n]+)$/gm, '$1;')
- .replace(/([^\s^]){$/gm, '$1 {')
- .replace(/^\s+/gm, (m: string) => {
- if (typeof outputIndent === 'undefined') outputIndent = m
- return m.replace(
- new RegExp(outputIndent, 'g'),
- documentIndent.indent
- )
- })
- .replace(/^(\s+)(.*?[^{}]\n)([^\s}])/gm, '$1$2$1$3'),
- })
-
- return false
- })
- })
- }
- }),
- ]).process(cssText, { from: undefined })
- } catch (_) {
- return []
- }
-
- if (!changes.length) {
- return []
- }
-
- return [
- {
- title: 'Extract to new rule',
- kind: CodeActionKind.QuickFix,
- diagnostics: [diagnostic],
- edit: {
- changes: {
- [params.textDocument.uri]: changes,
- },
- },
- },
- ]
-}
-
-function postcssSourceToRange(source: NodeSource): Range {
- return {
- start: {
- line: source.start.line - 1,
- character: source.start.column - 1,
- },
- end: {
- line: source.end.line - 1,
- character: source.end.column,
- },
- }
-}
-
-function classNameToAst(
- state: State,
- classNameParts: string[],
- selector: string,
- important: boolean = false
-) {
- const baseClassName = classNameParts[classNameParts.length - 1]
- const validatedBaseClassName = validateApply(state, [baseClassName])
- if (
- validatedBaseClassName === null ||
- validatedBaseClassName.isApplyable === false
- ) {
- return null
- }
- const meta = getClassNameMeta(state, classNameParts)
- if (Array.isArray(meta)) return null
- let context = meta.context
- let pseudo = meta.pseudo
- const globalContexts = state.classNames.context
- let screens = dlv(
- state.config,
- 'theme.screens',
- dlv(state.config, 'screens', {})
- )
- if (!isObject(screens)) screens = {}
- screens = Object.keys(screens)
- const path = []
-
- for (let i = 0; i < classNameParts.length - 1; i++) {
- let part = classNameParts[i]
- let common = globalContexts[part]
- if (!common) return null
- if (screens.includes(part)) {
- path.push(`@screen ${part}`)
- context = context.filter((con) => !common.includes(con))
- }
- }
-
- path.push(...context)
-
- let obj = {}
- for (let i = 1; i <= path.length; i++) {
- dset(obj, path.slice(0, i), {})
- }
-
- selector = appendPseudosToSelector(selector, pseudo)
- if (selector === null) return null
-
- let rule = {
- [selector]: {
- [`@apply ${baseClassName}${important ? ' !important' : ''}`]: '',
- },
- }
- if (path.length) {
- dset(obj, path, rule)
- } else {
- obj = rule
- }
-
- return cssObjToAst(obj, state.modules.postcss)
-}
-
-function appendPseudosToSelector(
- selector: string,
- pseudos: string[]
-): string | null {
- if (pseudos.length === 0) return selector
-
- let canTransform = true
-
- let transformedSelector = selectorParser((selectors) => {
- flatten(selectors.split((_) => true)).forEach((sel) => {
- // @ts-ignore
- for (let i = sel.nodes.length - 1; i >= 0; i--) {
- // @ts-ignore
- if (sel.nodes[i].type !== 'pseudo') {
- break
- // @ts-ignore
- } else if (pseudos.includes(sel.nodes[i].value)) {
- canTransform = false
- break
- }
- }
- if (canTransform) {
- pseudos.forEach((p) => {
- // @ts-ignore
- sel.append(selectorParser.pseudo({ value: p }))
- })
- }
- })
- }).processSync(selector)
-
- if (!canTransform) return null
-
- return transformedSelector
-}
diff --git a/src/lsp/providers/codeActions/provideSuggestionCodeActions.ts b/src/lsp/providers/codeActions/provideSuggestionCodeActions.ts
deleted file mode 100644
index 9da5fcb58f4863d4f6edc2643319263f95c22aa6..0000000000000000000000000000000000000000
--- a/src/lsp/providers/codeActions/provideSuggestionCodeActions.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { State } from '../../util/state'
-import {
- CodeActionParams,
- CodeAction,
- CodeActionKind,
-} from 'vscode-languageserver'
-import {
- InvalidConfigPathDiagnostic,
- InvalidTailwindDirectiveDiagnostic,
- InvalidScreenDiagnostic,
- InvalidVariantDiagnostic,
-} from '../diagnostics/types'
-
-export function provideSuggestionCodeActions(
- _state: State,
- params: CodeActionParams,
- diagnostic:
- | InvalidConfigPathDiagnostic
- | InvalidTailwindDirectiveDiagnostic
- | InvalidScreenDiagnostic
- | InvalidVariantDiagnostic
-): CodeAction[] {
- return diagnostic.suggestions.map((suggestion) => ({
- title: `Replace with '${suggestion}'`,
- kind: CodeActionKind.QuickFix,
- diagnostics: [diagnostic],
- edit: {
- changes: {
- [params.textDocument.uri]: [
- {
- range: diagnostic.range,
- newText: suggestion,
- },
- ],
- },
- },
- }))
-}
diff --git a/src/lsp/providers/completionProvider.ts b/src/lsp/providers/completionProvider.ts
index ec93d4449fe796128d72d2e9397e2cbd82ee828b..6dbb1e7a8de391c327c00dee9b74fb0b1fe08c87 100644
--- a/src/lsp/providers/completionProvider.ts
+++ b/src/lsp/providers/completionProvider.ts
@@ -618,10 +618,10 @@ async function provideEmmetCompletions(
state: State,
{ position, textDocument }: CompletionParams
): Promise {
- let doc = state.editor.documents.get(textDocument.uri)
-
- let settings = await getDocumentSettings(state, doc)
+ let settings = await getDocumentSettings(state, textDocument.uri)
if (settings.emmetCompletions !== true) return null
+
+ let doc = state.editor.documents.get(textDocument.uri)
const syntax = isHtmlContext(state, doc, position)
? 'html'
diff --git a/src/lsp/providers/diagnostics/diagnosticsProvider.ts b/src/lsp/providers/diagnostics/diagnosticsProvider.ts
deleted file mode 100644
index a22f8460e8c1eef13c3db52052b37d051a58797a..0000000000000000000000000000000000000000
--- a/src/lsp/providers/diagnostics/diagnosticsProvider.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { TextDocument } from 'vscode-languageserver'
-import { State } from '../../util/state'
-import { getDocumentSettings } from '../../util/getDocumentSettings'
-import { DiagnosticKind, AugmentedDiagnostic } from './types'
-import { getCssConflictDiagnostics } from './getCssConflictDiagnostics'
-import { getInvalidApplyDiagnostics } from './getInvalidApplyDiagnostics'
-import { getInvalidScreenDiagnostics } from './getInvalidScreenDiagnostics'
-import { getInvalidVariantDiagnostics } from './getInvalidVariantDiagnostics'
-import { getInvalidConfigPathDiagnostics } from './getInvalidConfigPathDiagnostics'
-import { getInvalidTailwindDirectiveDiagnostics } from './getInvalidTailwindDirectiveDiagnostics'
-
-export async function getDiagnostics(
- state: State,
- document: TextDocument,
- only: DiagnosticKind[] = [
- DiagnosticKind.CssConflict,
- DiagnosticKind.InvalidApply,
- DiagnosticKind.InvalidScreen,
- DiagnosticKind.InvalidVariant,
- DiagnosticKind.InvalidConfigPath,
- DiagnosticKind.InvalidTailwindDirective,
- ]
-): Promise {
- const settings = await getDocumentSettings(state, document)
-
- return settings.validate
- ? [
- ...(only.includes(DiagnosticKind.CssConflict)
- ? getCssConflictDiagnostics(state, document, settings)
- : []),
- ...(only.includes(DiagnosticKind.InvalidApply)
- ? getInvalidApplyDiagnostics(state, document, settings)
- : []),
- ...(only.includes(DiagnosticKind.InvalidScreen)
- ? getInvalidScreenDiagnostics(state, document, settings)
- : []),
- ...(only.includes(DiagnosticKind.InvalidVariant)
- ? getInvalidVariantDiagnostics(state, document, settings)
- : []),
- ...(only.includes(DiagnosticKind.InvalidConfigPath)
- ? getInvalidConfigPathDiagnostics(state, document, settings)
- : []),
- ...(only.includes(DiagnosticKind.InvalidTailwindDirective)
- ? getInvalidTailwindDirectiveDiagnostics(state, document, settings)
- : []),
- ]
- : []
-}
-
-export async function provideDiagnostics(state: State, document: TextDocument) {
- state.editor.connection.sendDiagnostics({
- uri: document.uri,
- diagnostics: await getDiagnostics(state, document),
- })
-}
-
-export function clearDiagnostics(state: State, document: TextDocument): void {
- state.editor.connection.sendDiagnostics({
- uri: document.uri,
- diagnostics: [],
- })
-}
-
-export function clearAllDiagnostics(state: State): void {
- state.editor.documents.all().forEach((document) => {
- clearDiagnostics(state, document)
- })
-}
-
-export function updateAllDiagnostics(state: State): void {
- state.editor.documents.all().forEach((document) => {
- provideDiagnostics(state, document)
- })
-}
diff --git a/src/lsp/providers/diagnostics/getCssConflictDiagnostics.ts b/src/lsp/providers/diagnostics/getCssConflictDiagnostics.ts
deleted file mode 100644
index 73da4869da22efd201be30ab434b9f61a0a5b3b6..0000000000000000000000000000000000000000
--- a/src/lsp/providers/diagnostics/getCssConflictDiagnostics.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { joinWithAnd } from '../../util/joinWithAnd'
-import { State, Settings } from '../../util/state'
-import { TextDocument, DiagnosticSeverity } from 'vscode-languageserver'
-import { CssConflictDiagnostic, DiagnosticKind } from './types'
-import {
- findClassListsInDocument,
- getClassNamesInClassList,
-} from '../../util/find'
-import { getClassNameDecls } from '../../util/getClassNameDecls'
-import { getClassNameMeta } from '../../util/getClassNameMeta'
-import { equal } from '../../../util/array'
-
-export function getCssConflictDiagnostics(
- state: State,
- document: TextDocument,
- settings: Settings
-): CssConflictDiagnostic[] {
- let severity = settings.lint.cssConflict
- if (severity === 'ignore') return []
-
- let diagnostics: CssConflictDiagnostic[] = []
- const classLists = findClassListsInDocument(state, document)
-
- classLists.forEach((classList) => {
- const classNames = getClassNamesInClassList(classList)
-
- classNames.forEach((className, index) => {
- let decls = getClassNameDecls(state, className.className)
- if (!decls) return
-
- let properties = Object.keys(decls)
- let meta = getClassNameMeta(state, className.className)
-
- let otherClassNames = classNames.filter((_className, i) => i !== index)
-
- let conflictingClassNames = otherClassNames.filter((otherClassName) => {
- let otherDecls = getClassNameDecls(state, otherClassName.className)
- if (!otherDecls) return false
-
- let otherMeta = getClassNameMeta(state, otherClassName.className)
-
- return (
- equal(properties, Object.keys(otherDecls)) &&
- !Array.isArray(meta) &&
- !Array.isArray(otherMeta) &&
- equal(meta.context, otherMeta.context) &&
- equal(meta.pseudo, otherMeta.pseudo)
- )
- })
-
- if (conflictingClassNames.length === 0) return
-
- diagnostics.push({
- code: DiagnosticKind.CssConflict,
- className,
- otherClassNames: conflictingClassNames,
- range: className.range,
- severity:
- severity === 'error'
- ? DiagnosticSeverity.Error
- : DiagnosticSeverity.Warning,
- message: `'${className.className}' applies the same CSS ${
- properties.length === 1 ? 'property' : 'properties'
- } as ${joinWithAnd(
- conflictingClassNames.map(
- (conflictingClassName) => `'${conflictingClassName.className}'`
- )
- )}.`,
- relatedInformation: conflictingClassNames.map(
- (conflictingClassName) => {
- return {
- message: conflictingClassName.className,
- location: {
- uri: document.uri,
- range: conflictingClassName.range,
- },
- }
- }
- ),
- })
- })
- })
-
- return diagnostics
-}
diff --git a/src/lsp/providers/diagnostics/getInvalidApplyDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidApplyDiagnostics.ts
deleted file mode 100644
index e829b5735e74900f08d5d7161c5d561eebd7b89f..0000000000000000000000000000000000000000
--- a/src/lsp/providers/diagnostics/getInvalidApplyDiagnostics.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { findClassNamesInRange } from '../../util/find'
-import { InvalidApplyDiagnostic, DiagnosticKind } from './types'
-import { Settings, State } from '../../util/state'
-import { TextDocument, DiagnosticSeverity } from 'vscode-languageserver'
-import { validateApply } from '../../util/validateApply'
-
-export function getInvalidApplyDiagnostics(
- state: State,
- document: TextDocument,
- settings: Settings
-): InvalidApplyDiagnostic[] {
- let severity = settings.lint.invalidApply
- if (severity === 'ignore') return []
-
- const classNames = findClassNamesInRange(document, undefined, 'css')
-
- let diagnostics: InvalidApplyDiagnostic[] = classNames.map((className) => {
- let result = validateApply(state, className.className)
-
- if (result === null || result.isApplyable === true) {
- return null
- }
-
- return {
- code: DiagnosticKind.InvalidApply,
- severity:
- severity === 'error'
- ? DiagnosticSeverity.Error
- : DiagnosticSeverity.Warning,
- range: className.range,
- message: result.reason,
- className,
- }
- })
-
- return diagnostics.filter(Boolean)
-}
diff --git a/src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts
deleted file mode 100644
index ccf52fcb1aa3b88bd1b73aa9963e0e7729b29cca..0000000000000000000000000000000000000000
--- a/src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts
+++ /dev/null
@@ -1,230 +0,0 @@
-import { State, Settings } from '../../util/state'
-import { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver'
-import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types'
-import { isCssDoc } from '../../util/css'
-import { getLanguageBoundaries } from '../../util/getLanguageBoundaries'
-import { findAll, indexToPosition } from '../../util/find'
-import { stringToPath } from '../../util/stringToPath'
-import isObject from '../../../util/isObject'
-import { closest } from '../../util/closest'
-import { absoluteRange } from '../../util/absoluteRange'
-import { combinations } from '../../util/combinations'
-const dlv = require('dlv')
-
-function pathToString(path: string | string[]): string {
- if (typeof path === 'string') return path
- return path.reduce((acc, cur, i) => {
- if (i === 0) return cur
- if (cur.includes('.')) return `${acc}[${cur}]`
- return `${acc}.${cur}`
- }, '')
-}
-
-function validateConfigPath(
- state: State,
- path: string | string[],
- base: string[] = []
-):
- | { isValid: true; value: any }
- | { isValid: false; reason: string; suggestions: string[] } {
- let keys = Array.isArray(path) ? path : stringToPath(path)
- let value = dlv(state.config, [...base, ...keys])
- let suggestions: string[] = []
-
- function findAlternativePath(): string[] {
- let points = combinations('123456789'.substr(0, keys.length - 1)).map((x) =>
- x.split('').map((x) => parseInt(x, 10))
- )
-
- let possibilities: string[][] = points
- .map((p) => {
- let result = []
- let i = 0
- p.forEach((x) => {
- result.push(keys.slice(i, x).join('.'))
- i = x
- })
- result.push(keys.slice(i).join('.'))
- return result
- })
- .slice(1) // skip original path
-
- return possibilities.find(
- (possibility) => validateConfigPath(state, possibility, base).isValid
- )
- }
-
- if (typeof value === 'undefined') {
- let reason = `'${pathToString(path)}' does not exist in your theme config.`
- let parentPath = [...base, ...keys.slice(0, keys.length - 1)]
- let parentValue = dlv(state.config, parentPath)
-
- if (isObject(parentValue)) {
- let closestValidKey = closest(
- keys[keys.length - 1],
- Object.keys(parentValue).filter(
- (key) => validateConfigPath(state, [...parentPath, key]).isValid
- )
- )
- if (closestValidKey) {
- suggestions.push(
- pathToString([...keys.slice(0, keys.length - 1), closestValidKey])
- )
- reason += ` Did you mean '${suggestions[0]}'?`
- }
- } else {
- let altPath = findAlternativePath()
- if (altPath) {
- return {
- isValid: false,
- reason: `${reason} Did you mean '${pathToString(altPath)}'?`,
- suggestions: [pathToString(altPath)],
- }
- }
- }
-
- return {
- isValid: false,
- reason,
- suggestions,
- }
- }
-
- if (
- !(
- typeof value === 'string' ||
- typeof value === 'number' ||
- value instanceof String ||
- value instanceof Number ||
- Array.isArray(value)
- )
- ) {
- let reason = `'${pathToString(
- path
- )}' was found but does not resolve to a string.`
-
- if (isObject(value)) {
- let validKeys = Object.keys(value).filter(
- (key) => validateConfigPath(state, [...keys, key], base).isValid
- )
- if (validKeys.length) {
- suggestions.push(
- ...validKeys.map((validKey) => pathToString([...keys, validKey]))
- )
- reason += ` Did you mean something like '${suggestions[0]}'?`
- }
- }
- return {
- isValid: false,
- reason,
- suggestions,
- }
- }
-
- // The value resolves successfully, but we need to check that there
- // wasn't any funny business. If you have a theme object:
- // { msg: 'hello' } and do theme('msg.0')
- // this will resolve to 'h', which is probably not intentional, so we
- // check that all of the keys are object or array keys (i.e. not string
- // indexes)
- let isValid = true
- for (let i = keys.length - 1; i >= 0; i--) {
- let key = keys[i]
- let parentValue = dlv(state.config, [...base, ...keys.slice(0, i)])
- if (/^[0-9]+$/.test(key)) {
- if (!isObject(parentValue) && !Array.isArray(parentValue)) {
- isValid = false
- break
- }
- } else if (!isObject(parentValue)) {
- isValid = false
- break
- }
- }
- if (!isValid) {
- let reason = `'${pathToString(path)}' does not exist in your theme config.`
-
- let altPath = findAlternativePath()
- if (altPath) {
- return {
- isValid: false,
- reason: `${reason} Did you mean '${pathToString(altPath)}'?`,
- suggestions: [pathToString(altPath)],
- }
- }
-
- return {
- isValid: false,
- reason,
- suggestions: [],
- }
- }
-
- return {
- isValid: true,
- value,
- }
-}
-
-export function getInvalidConfigPathDiagnostics(
- state: State,
- document: TextDocument,
- settings: Settings
-): InvalidConfigPathDiagnostic[] {
- let severity = settings.lint.invalidConfigPath
- if (severity === 'ignore') return []
-
- let diagnostics: InvalidConfigPathDiagnostic[] = []
- let ranges: Range[] = []
-
- if (isCssDoc(state, document)) {
- ranges.push(undefined)
- } else {
- let boundaries = getLanguageBoundaries(state, document)
- if (!boundaries) return []
- ranges.push(...boundaries.css)
- }
-
- ranges.forEach((range) => {
- let text = document.getText(range)
- let matches = findAll(
- /(?\s|^)(?config|theme)\((?['"])(?[^)]+)\k\)/g,
- text
- )
-
- matches.forEach((match) => {
- let base = match.groups.helper === 'theme' ? ['theme'] : []
- let result = validateConfigPath(state, match.groups.key, base)
-
- if (result.isValid === true) {
- return null
- }
-
- let startIndex =
- match.index +
- match.groups.prefix.length +
- match.groups.helper.length +
- 1 + // open paren
- match.groups.quote.length
-
- diagnostics.push({
- code: DiagnosticKind.InvalidConfigPath,
- range: absoluteRange(
- {
- start: indexToPosition(text, startIndex),
- end: indexToPosition(text, startIndex + match.groups.key.length),
- },
- range
- ),
- severity:
- severity === 'error'
- ? DiagnosticSeverity.Error
- : DiagnosticSeverity.Warning,
- message: result.reason,
- suggestions: result.suggestions,
- })
- })
- })
-
- return diagnostics
-}
diff --git a/src/lsp/providers/diagnostics/getInvalidScreenDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidScreenDiagnostics.ts
deleted file mode 100644
index b0e4252438ea6bb7dd47071189c3881aae219225..0000000000000000000000000000000000000000
--- a/src/lsp/providers/diagnostics/getInvalidScreenDiagnostics.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { State, Settings } from '../../util/state'
-import { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver'
-import { InvalidScreenDiagnostic, DiagnosticKind } from './types'
-import { isCssDoc } from '../../util/css'
-import { getLanguageBoundaries } from '../../util/getLanguageBoundaries'
-import { findAll, indexToPosition } from '../../util/find'
-import { closest } from '../../util/closest'
-import { absoluteRange } from '../../util/absoluteRange'
-const dlv = require('dlv')
-
-export function getInvalidScreenDiagnostics(
- state: State,
- document: TextDocument,
- settings: Settings
-): InvalidScreenDiagnostic[] {
- let severity = settings.lint.invalidScreen
- if (severity === 'ignore') return []
-
- let diagnostics: InvalidScreenDiagnostic[] = []
- let ranges: Range[] = []
-
- if (isCssDoc(state, document)) {
- ranges.push(undefined)
- } else {
- let boundaries = getLanguageBoundaries(state, document)
- if (!boundaries) return []
- ranges.push(...boundaries.css)
- }
-
- ranges.forEach((range) => {
- let text = document.getText(range)
- let matches = findAll(/(?:\s|^)@screen\s+(?[^\s{]+)/g, text)
-
- let screens = Object.keys(
- dlv(state.config, 'theme.screens', dlv(state.config, 'screens', {}))
- )
-
- matches.forEach((match) => {
- if (screens.includes(match.groups.screen)) {
- return null
- }
-
- let message = `The screen '${match.groups.screen}' does not exist in your theme config.`
- let suggestions: string[] = []
- let suggestion = closest(match.groups.screen, screens)
-
- if (suggestion) {
- suggestions.push(suggestion)
- message += ` Did you mean '${suggestion}'?`
- }
-
- diagnostics.push({
- code: DiagnosticKind.InvalidScreen,
- range: absoluteRange(
- {
- start: indexToPosition(
- text,
- match.index + match[0].length - match.groups.screen.length
- ),
- end: indexToPosition(text, match.index + match[0].length),
- },
- range
- ),
- severity:
- severity === 'error'
- ? DiagnosticSeverity.Error
- : DiagnosticSeverity.Warning,
- message,
- suggestions,
- })
- })
- })
-
- return diagnostics
-}
diff --git a/src/lsp/providers/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts
deleted file mode 100644
index 9b88bdb06e0e5c3754c6c5eecbf23af7c342a21d..0000000000000000000000000000000000000000
--- a/src/lsp/providers/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { State, Settings } from '../../util/state'
-import { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver'
-import { InvalidTailwindDirectiveDiagnostic, DiagnosticKind } from './types'
-import { isCssDoc } from '../../util/css'
-import { getLanguageBoundaries } from '../../util/getLanguageBoundaries'
-import { findAll, indexToPosition } from '../../util/find'
-import semver from 'semver'
-import { closest } from '../../util/closest'
-import { absoluteRange } from '../../util/absoluteRange'
-
-export function getInvalidTailwindDirectiveDiagnostics(
- state: State,
- document: TextDocument,
- settings: Settings
-): InvalidTailwindDirectiveDiagnostic[] {
- let severity = settings.lint.invalidTailwindDirective
- if (severity === 'ignore') return []
-
- let diagnostics: InvalidTailwindDirectiveDiagnostic[] = []
- let ranges: Range[] = []
-
- if (isCssDoc(state, document)) {
- ranges.push(undefined)
- } else {
- let boundaries = getLanguageBoundaries(state, document)
- if (!boundaries) return []
- ranges.push(...boundaries.css)
- }
-
- ranges.forEach((range) => {
- let text = document.getText(range)
- let matches = findAll(/(?:\s|^)@tailwind\s+(?[^;]+)/g, text)
-
- let valid = [
- 'utilities',
- 'components',
- 'screens',
- semver.gte(state.version, '1.0.0-beta.1') ? 'base' : 'preflight',
- ]
-
- matches.forEach((match) => {
- if (valid.includes(match.groups.value)) {
- return null
- }
-
- let message = `'${match.groups.value}' is not a valid group.`
- let suggestions: string[] = []
-
- if (match.groups.value === 'preflight') {
- suggestions.push('base')
- message += ` Did you mean 'base'?`
- } else {
- let suggestion = closest(match.groups.value, valid)
- if (suggestion) {
- suggestions.push(suggestion)
- message += ` Did you mean '${suggestion}'?`
- }
- }
-
- diagnostics.push({
- code: DiagnosticKind.InvalidTailwindDirective,
- range: absoluteRange(
- {
- start: indexToPosition(
- text,
- match.index + match[0].length - match.groups.value.length
- ),
- end: indexToPosition(text, match.index + match[0].length),
- },
- range
- ),
- severity:
- severity === 'error'
- ? DiagnosticSeverity.Error
- : DiagnosticSeverity.Warning,
- message,
- suggestions,
- })
- })
- })
-
- return diagnostics
-}
diff --git a/src/lsp/providers/diagnostics/getInvalidVariantDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidVariantDiagnostics.ts
deleted file mode 100644
index 006740103c2776d4d68d6452dd5f808b8100a5d2..0000000000000000000000000000000000000000
--- a/src/lsp/providers/diagnostics/getInvalidVariantDiagnostics.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { State, Settings } from '../../util/state'
-import { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver'
-import { InvalidVariantDiagnostic, DiagnosticKind } from './types'
-import { isCssDoc } from '../../util/css'
-import { getLanguageBoundaries } from '../../util/getLanguageBoundaries'
-import { findAll, indexToPosition } from '../../util/find'
-import { closest } from '../../util/closest'
-import { absoluteRange } from '../../util/absoluteRange'
-
-export function getInvalidVariantDiagnostics(
- state: State,
- document: TextDocument,
- settings: Settings
-): InvalidVariantDiagnostic[] {
- let severity = settings.lint.invalidVariant
- if (severity === 'ignore') return []
-
- let diagnostics: InvalidVariantDiagnostic[] = []
- let ranges: Range[] = []
-
- if (isCssDoc(state, document)) {
- ranges.push(undefined)
- } else {
- let boundaries = getLanguageBoundaries(state, document)
- if (!boundaries) return []
- ranges.push(...boundaries.css)
- }
-
- ranges.forEach((range) => {
- let text = document.getText(range)
- let matches = findAll(/(?:\s|^)@variants\s+(?[^{]+)/g, text)
-
- matches.forEach((match) => {
- let variants = match.groups.variants.split(/(\s*,\s*)/)
- let listStartIndex =
- match.index + match[0].length - match.groups.variants.length
-
- for (let i = 0; i < variants.length; i += 2) {
- let variant = variants[i].trim()
- if (state.variants.includes(variant)) {
- continue
- }
-
- let message = `The variant '${variant}' does not exist.`
- let suggestions: string[] = []
- let suggestion = closest(variant, state.variants)
-
- if (suggestion) {
- suggestions.push(suggestion)
- message += ` Did you mean '${suggestion}'?`
- }
-
- let variantStartIndex =
- listStartIndex + variants.slice(0, i).join('').length
-
- diagnostics.push({
- code: DiagnosticKind.InvalidVariant,
- range: absoluteRange(
- {
- start: indexToPosition(text, variantStartIndex),
- end: indexToPosition(text, variantStartIndex + variant.length),
- },
- range
- ),
- severity:
- severity === 'error'
- ? DiagnosticSeverity.Error
- : DiagnosticSeverity.Warning,
- message,
- suggestions,
- })
- }
- })
- })
-
- return diagnostics
-}
diff --git a/src/lsp/providers/diagnostics/types.ts b/src/lsp/providers/diagnostics/types.ts
deleted file mode 100644
index 1cfd0e2b4d4b26b543776be2c4810661b799f6c2..0000000000000000000000000000000000000000
--- a/src/lsp/providers/diagnostics/types.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Diagnostic } from 'vscode-languageserver'
-import { DocumentClassName, DocumentClassList } from '../../util/state'
-
-export enum DiagnosticKind {
- CssConflict = 'cssConflict',
- InvalidApply = 'invalidApply',
- InvalidScreen = 'invalidScreen',
- InvalidVariant = 'invalidVariant',
- InvalidConfigPath = 'invalidConfigPath',
- InvalidTailwindDirective = 'invalidTailwindDirective',
-}
-
-export type CssConflictDiagnostic = Diagnostic & {
- code: DiagnosticKind.CssConflict
- className: DocumentClassName
- otherClassNames: DocumentClassName[]
-}
-
-export function isCssConflictDiagnostic(
- diagnostic: AugmentedDiagnostic
-): diagnostic is CssConflictDiagnostic {
- return diagnostic.code === DiagnosticKind.CssConflict
-}
-
-export type InvalidApplyDiagnostic = Diagnostic & {
- code: DiagnosticKind.InvalidApply
- className: DocumentClassName
-}
-
-export function isInvalidApplyDiagnostic(
- diagnostic: AugmentedDiagnostic
-): diagnostic is InvalidApplyDiagnostic {
- return diagnostic.code === DiagnosticKind.InvalidApply
-}
-
-export type InvalidScreenDiagnostic = Diagnostic & {
- code: DiagnosticKind.InvalidScreen
- suggestions: string[]
-}
-
-export function isInvalidScreenDiagnostic(
- diagnostic: AugmentedDiagnostic
-): diagnostic is InvalidScreenDiagnostic {
- return diagnostic.code === DiagnosticKind.InvalidScreen
-}
-
-export type InvalidVariantDiagnostic = Diagnostic & {
- code: DiagnosticKind.InvalidVariant
- suggestions: string[]
-}
-
-export function isInvalidVariantDiagnostic(
- diagnostic: AugmentedDiagnostic
-): diagnostic is InvalidVariantDiagnostic {
- return diagnostic.code === DiagnosticKind.InvalidVariant
-}
-
-export type InvalidConfigPathDiagnostic = Diagnostic & {
- code: DiagnosticKind.InvalidConfigPath
- suggestions: string[]
-}
-
-export function isInvalidConfigPathDiagnostic(
- diagnostic: AugmentedDiagnostic
-): diagnostic is InvalidConfigPathDiagnostic {
- return diagnostic.code === DiagnosticKind.InvalidConfigPath
-}
-
-export type InvalidTailwindDirectiveDiagnostic = Diagnostic & {
- code: DiagnosticKind.InvalidTailwindDirective
- suggestions: string[]
-}
-
-export function isInvalidTailwindDirectiveDiagnostic(
- diagnostic: AugmentedDiagnostic
-): diagnostic is InvalidTailwindDirectiveDiagnostic {
- return diagnostic.code === DiagnosticKind.InvalidTailwindDirective
-}
-
-export type AugmentedDiagnostic =
- | CssConflictDiagnostic
- | InvalidApplyDiagnostic
- | InvalidScreenDiagnostic
- | InvalidVariantDiagnostic
- | InvalidConfigPathDiagnostic
- | InvalidTailwindDirectiveDiagnostic
diff --git a/src/lsp/providers/hoverProvider.ts b/src/lsp/providers/hoverProvider.ts
index 2084ec42933cbf2cfc02ead3b476698727a2d89e..a9010a3142cb4e9700f63e3bd06426a144b9afa0 100644
--- a/src/lsp/providers/hoverProvider.ts
+++ b/src/lsp/providers/hoverProvider.ts
@@ -1,11 +1,10 @@
import { State } from '../util/state'
import { Hover, TextDocumentPositionParams } from 'vscode-languageserver'
+import { getClassNameParts } from '../util/getClassNameAtPosition'
import { stringifyCss, stringifyConfigValue } from '../util/stringify'
const dlv = require('dlv')
import { isCssContext } from '../util/css'
import { findClassNameAtPosition } from '../util/find'
-import { validateApply } from '../util/validateApply'
-import { getClassNameParts } from '../util/getClassNameAtPosition'
export function provideHover(
state: State,
@@ -81,13 +80,6 @@ if (className === null) return null
const parts = getClassNameParts(state, className.className)
if (!parts) return null
-
- if (isCssContext(state, doc, position)) {
- let validated = validateApply(state, parts)
- if (validated === null || validated.isApplyable === false) {
- return null
- }
- }
return {
contents: {
diff --git a/src/lsp/server.ts b/src/lsp/server.ts
index cc85ae4662a1e6cbc4a8af47df121dd7acdb8f31..b28b1bad9d226222f5d9e24472c9d8d698df6187 100644
--- a/src/lsp/server.ts
+++ b/src/lsp/server.ts
@@ -16,8 +16,6 @@ CompletionList,
Hover,
TextDocumentPositionParams,
DidChangeConfigurationNotification,
- CodeActionParams,
- CodeAction,
} from 'vscode-languageserver'
import getTailwindState from '../class-names/index'
import { State, Settings, EditorState } from './util/state'
@@ -28,43 +26,24 @@ } from './providers/completionProvider'
import { provideHover } from './providers/hoverProvider'
import { URI } from 'vscode-uri'
import { getDocumentSettings } from './util/getDocumentSettings'
-import {
- provideDiagnostics,
- updateAllDiagnostics,
- clearAllDiagnostics,
-} from './providers/diagnostics/diagnosticsProvider'
-import { createEmitter } from '../lib/emitter'
-import { provideCodeActions } from './providers/codeActions/codeActionProvider'
+let state: State = { enabled: false }
let connection = createConnection(ProposedFeatures.all)
-let state: State = { enabled: false, emitter: createEmitter(connection) }
let documents = new TextDocuments()
let workspaceFolder: string | null
const defaultSettings: Settings = {
emmetCompletions: false,
includeLanguages: {},
- validate: true,
- lint: {
- cssConflict: 'warning',
- invalidApply: 'error',
- invalidScreen: 'error',
- invalidVariant: 'error',
- invalidConfigPath: 'error',
- invalidTailwindDirective: 'error',
- },
}
let globalSettings: Settings = defaultSettings
let documentSettings: Map = new Map()
documents.onDidOpen((event) => {
- getDocumentSettings(state, event.document)
+ getDocumentSettings(state, event.document.uri)
})
documents.onDidClose((event) => {
documentSettings.delete(event.document.uri)
-})
-documents.onDidChangeContent((change) => {
- provideDiagnostics(state, change.document)
})
documents.listen(connection)
@@ -85,10 +64,6 @@ : {},
capabilities: {
configuration:
capabilities.workspace && !!capabilities.workspace.configuration,
- diagnosticRelatedInformation:
- capabilities.textDocument &&
- capabilities.textDocument.publishDiagnostics &&
- capabilities.textDocument.publishDiagnostics.relatedInformation,
},
}
@@ -98,24 +73,14 @@ {
// @ts-ignore
onChange: (newState: State): void => {
if (newState && !newState.error) {
- state = {
- ...newState,
- enabled: true,
- emitter: state.emitter,
- editor: editorState,
- }
+ state = { ...newState, enabled: true, editor: editorState }
connection.sendNotification('tailwindcss/configUpdated', [
state.configPath,
state.config,
state.plugins,
])
- updateAllDiagnostics(state)
} else {
- state = {
- enabled: false,
- emitter: state.emitter,
- editor: editorState,
- }
+ state = { enabled: false, editor: editorState }
if (newState && newState.error) {
const payload: {
message: string
@@ -130,7 +95,6 @@ payload.line = parseInt(match.groups.line, 10)
}
connection.sendNotification('tailwindcss/configError', [payload])
}
- clearAllDiagnostics(state)
// TODO
// connection.sendNotification('tailwindcss/configUpdated', [null])
}
@@ -139,14 +103,9 @@ }
)
if (tailwindState) {
- state = {
- enabled: true,
- emitter: state.emitter,
- editor: editorState,
- ...tailwindState,
- }
+ state = { enabled: true, editor: editorState, ...tailwindState }
} else {
- state = { enabled: false, emitter: state.emitter, editor: editorState }
+ state = { enabled: false, editor: editorState }
}
return {
@@ -174,7 +133,6 @@ typeof state.separator === 'undefined' ? ':' : state.separator,
],
},
hoverProvider: true,
- codeActionProvider: true,
},
}
}
@@ -206,7 +164,9 @@ (change.settings.tailwindCSS || defaultSettings)
)
}
- updateAllDiagnostics(state)
+ state.editor.documents
+ .all()
+ .forEach((doc) => getDocumentSettings(state, doc.uri))
})
connection.onCompletion(
@@ -227,13 +187,6 @@ connection.onHover(
(params: TextDocumentPositionParams): Hover => {
if (!state.enabled) return null
return provideHover(state, params)
- }
-)
-
-connection.onCodeAction(
- (params: CodeActionParams): Promise => {
- if (!state.enabled) return null
- return provideCodeActions(state, params)
}
)
diff --git a/src/lsp/util/absoluteRange.ts b/src/lsp/util/absoluteRange.ts
deleted file mode 100644
index 9250e4fb9ec9b13ff3124072808f2013ef5009a0..0000000000000000000000000000000000000000
--- a/src/lsp/util/absoluteRange.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Range } from 'vscode-languageserver'
-
-export function absoluteRange(range: Range, reference?: Range) {
- return {
- start: {
- line: (reference?.start.line || 0) + range.start.line,
- character:
- (range.end.line === 0 ? reference?.start.character || 0 : 0) +
- range.start.character,
- },
- end: {
- line: (reference?.start.line || 0) + range.end.line,
- character:
- (range.end.line === 0 ? reference?.start.character || 0 : 0) +
- range.end.character,
- },
- }
-}
diff --git a/src/lsp/util/closest.ts b/src/lsp/util/closest.ts
deleted file mode 100644
index ebdfacc270c2c5467d501b4ff4642aef04f6277e..0000000000000000000000000000000000000000
--- a/src/lsp/util/closest.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import sift from 'sift-string'
-
-export function closest(input: string, options: string[]): string | undefined {
- return options.concat([]).sort((a, b) => sift(input, a) - sift(input, b))[0]
-}
diff --git a/src/lsp/util/combinations.ts b/src/lsp/util/combinations.ts
deleted file mode 100644
index 2c9868b207b01b60cc0789b800058285257e187a..0000000000000000000000000000000000000000
--- a/src/lsp/util/combinations.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export function combinations(str: string): string[] {
- let fn = function (active: string, rest: string, a: string[]) {
- if (!active && !rest) return
- if (!rest) {
- a.push(active)
- } else {
- fn(active + rest[0], rest.slice(1), a)
- fn(active, rest.slice(1), a)
- }
- return a
- }
- return fn('', str, [])
-}
diff --git a/src/lsp/util/cssObjToAst.ts b/src/lsp/util/cssObjToAst.ts
deleted file mode 100644
index 42826f7526c514588519a63a8d2e6723a9ebfdd2..0000000000000000000000000000000000000000
--- a/src/lsp/util/cssObjToAst.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
-This is a modified version of the postcss-js 'parse' function which accepts the
-postcss module as an argument. License below:
-
-The MIT License (MIT)
-
-Copyright 2015 Andrey Sitnik
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*/
-
-var IMPORTANT = /\s*!important\s*$/i
-
-var unitless = {
- 'box-flex': true,
- 'box-flex-group': true,
- 'column-count': true,
- flex: true,
- 'flex-grow': true,
- 'flex-positive': true,
- 'flex-shrink': true,
- 'flex-negative': true,
- 'font-weight': true,
- 'line-clamp': true,
- 'line-height': true,
- opacity: true,
- order: true,
- orphans: true,
- 'tab-size': true,
- widows: true,
- 'z-index': true,
- zoom: true,
- 'fill-opacity': true,
- 'stroke-dashoffset': true,
- 'stroke-opacity': true,
- 'stroke-width': true,
-}
-
-function dashify(str) {
- return str
- .replace(/([A-Z])/g, '-$1')
- .replace(/^ms-/, '-ms-')
- .toLowerCase()
-}
-
-function decl(parent, name, value, postcss) {
- if (value === false || value === null) return
-
- name = dashify(name)
- if (typeof value === 'number') {
- if (value === 0 || unitless[name]) {
- value = value.toString()
- } else {
- value = value.toString() + 'px'
- }
- }
-
- if (name === 'css-float') name = 'float'
-
- if (IMPORTANT.test(value)) {
- value = value.replace(IMPORTANT, '')
- parent.push(postcss.decl({ prop: name, value: value, important: true }))
- } else {
- parent.push(postcss.decl({ prop: name, value: value }))
- }
-}
-
-function atRule(parent, parts, value, postcss) {
- var node = postcss.atRule({ name: parts[1], params: parts[3] || '' })
- if (typeof value === 'object') {
- node.nodes = []
- parse(value, node, postcss)
- }
- parent.push(node)
-}
-
-function parse(obj, parent, postcss) {
- var name, value, node, i
- for (name in obj) {
- if (obj.hasOwnProperty(name)) {
- value = obj[name]
- if (value === null || typeof value === 'undefined') {
- continue
- } else if (name[0] === '@') {
- var parts = name.match(/@([^\s]+)(\s+([\w\W]*)\s*)?/)
- if (Array.isArray(value)) {
- for (i = 0; i < value.length; i++) {
- atRule(parent, parts, value[i], postcss)
- }
- } else {
- atRule(parent, parts, value, postcss)
- }
- } else if (Array.isArray(value)) {
- for (i = 0; i < value.length; i++) {
- decl(parent, name, value[i], postcss)
- }
- } else if (typeof value === 'object') {
- node = postcss.rule({ selector: name })
- parse(value, node, postcss)
- parent.push(node)
- } else {
- decl(parent, name, value, postcss)
- }
- }
- }
-}
-
-export function cssObjToAst(obj, postcss) {
- var root = postcss.root()
- parse(obj, root, postcss)
- return root
-}
diff --git a/src/lsp/util/find.ts b/src/lsp/util/find.ts
index b642534d012dc3816516f0284de71b1ecbf31954..5eaef42ef1b5c41b8eeef529d00fb4109cd481bd 100644
--- a/src/lsp/util/find.ts
+++ b/src/lsp/util/find.ts
@@ -5,12 +5,10 @@ import { isCssContext, isCssDoc } from './css'
import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html'
import { isWithinRange } from './isWithinRange'
import { isJsContext, isJsDoc } from './js'
-import { flatten } from '../../util/array'
import {
getClassAttributeLexer,
getComputedClassAttributeLexer,
} from './lexers'
-import { getLanguageBoundaries } from './getLanguageBoundaries'
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
let match: RegExpMatchArray
@@ -29,63 +27,44 @@ }
return matches[matches.length - 1]
}
-export function getClassNamesInClassList({
- classList,
- range,
- important,
-}: DocumentClassList): DocumentClassName[] {
- const parts = classList.split(/(\s+)/)
- const names: DocumentClassName[] = []
- let index = 0
- for (let i = 0; i < parts.length; i++) {
- if (i % 2 === 0) {
- const start = indexToPosition(classList, index)
- const end = indexToPosition(classList, index + parts[i].length)
- names.push({
- className: parts[i],
- classList: {
- classList,
- range,
- important,
- },
- relativeRange: {
- start,
- end,
- },
- range: {
- start: {
- line: range.start.line + start.line,
- character:
- (end.line === 0 ? range.start.character : 0) + start.character,
- },
- end: {
- line: range.start.line + end.line,
- character:
- (end.line === 0 ? range.start.character : 0) + end.character,
- },
- },
- })
- }
- index += parts[i].length
- }
- return names
-}
-
export function findClassNamesInRange(
doc: TextDocument,
range?: Range,
mode?: 'html' | 'css'
): DocumentClassName[] {
const classLists = findClassListsInRange(doc, range, mode)
- return flatten(classLists.map(getClassNamesInClassList))
-}
-
-export function findClassNamesInDocument(
- state: State,
- doc: TextDocument
-): DocumentClassName[] {
- const classLists = findClassListsInDocument(state, doc)
- return flatten(classLists.map(getClassNamesInClassList))
+ return [].concat.apply(
+ [],
+ classLists.map(({ classList, range }) => {
+ const parts = classList.split(/(\s+)/)
+ const names: DocumentClassName[] = []
+ let index = 0
+ for (let i = 0; i < parts.length; i++) {
+ if (i % 2 === 0) {
+ const start = indexToPosition(classList, index)
+ const end = indexToPosition(classList, index + parts[i].length)
+ names.push({
+ className: parts[i],
+ range: {
+ start: {
+ line: range.start.line + start.line,
+ character:
+ (end.line === 0 ? range.start.character : 0) +
+ start.character,
+ },
+ end: {
+ line: range.start.line + end.line,
+ character:
+ (end.line === 0 ? range.start.character : 0) + end.character,
+ },
+ },
+ })
+ }
+ index += parts[i].length
+ }
+ return names
+ })
+ )
}
export function findClassListsInCssRange(
@@ -93,10 +72,7 @@ doc: TextDocument,
range?: Range
): DocumentClassList[] {
const text = doc.getText(range)
- const matches = findAll(
- /(@apply\s+)(?[^;}]+?)(?\s*!important)?\s*[;}]/g,
- text
- )
+ const matches = findAll(/(@apply\s+)(?[^;}]+)[;}]/g, text)
const globalStart: Position = range ? range.start : { line: 0, character: 0 }
return matches.map((match) => {
@@ -107,7 +83,6 @@ match.index + match[1].length + match.groups.classList.length
)
return {
classList: match.groups.classList,
- important: Boolean(match.groups.important),
range: {
start: {
line: globalStart.line + start.line,
@@ -126,7 +101,7 @@ }
export function findClassListsInHtmlRange(
doc: TextDocument,
- range?: Range
+ range: Range
): DocumentClassList[] {
const text = doc.getText(range)
const matches = findAll(/(?:\b|:)class(?:Name)?=['"`{]/g, text)
@@ -205,16 +180,15 @@ return {
classList: value.substr(beforeOffset, value.length + afterOffset),
range: {
start: {
- line: (range?.start.line || 0) + start.line,
+ line: range.start.line + start.line,
character:
- (end.line === 0 ? range?.start.character || 0 : 0) +
+ (end.line === 0 ? range.start.character : 0) +
start.character,
},
end: {
- line: (range?.start.line || 0) + end.line,
+ line: range.start.line + end.line,
character:
- (end.line === 0 ? range?.start.character || 0 : 0) +
- end.character,
+ (end.line === 0 ? range.start.character : 0) + end.character,
},
},
}
@@ -228,8 +202,8 @@ }
export function findClassListsInRange(
doc: TextDocument,
- range?: Range,
- mode?: 'html' | 'css'
+ range: Range,
+ mode: 'html' | 'css'
): DocumentClassList[] {
if (mode === 'css') {
return findClassListsInCssRange(doc, range)
@@ -245,16 +219,73 @@ if (isCssDoc(state, doc)) {
return findClassListsInCssRange(doc)
}
- let boundaries = getLanguageBoundaries(state, doc)
- if (!boundaries) return []
+ if (isVueDoc(doc)) {
+ let text = doc.getText()
+ let blocks = findAll(
+ /<(?template|style|script)\b[^>]*>.*?(<\/\k>|$)/gis,
+ text
+ )
+ let htmlRanges: Range[] = []
+ let cssRanges: Range[] = []
+ for (let i = 0; i < blocks.length; i++) {
+ let range = {
+ start: indexToPosition(text, blocks[i].index),
+ end: indexToPosition(text, blocks[i].index + blocks[i][0].length),
+ }
+ if (blocks[i].groups.type === 'style') {
+ cssRanges.push(range)
+ } else {
+ htmlRanges.push(range)
+ }
+ }
+ return [].concat.apply(
+ [],
+ [
+ ...htmlRanges.map((range) => findClassListsInHtmlRange(doc, range)),
+ ...cssRanges.map((range) => findClassListsInCssRange(doc, range)),
+ ]
+ )
+ }
- return flatten([
- ...boundaries.html.map((range) => findClassListsInHtmlRange(doc, range)),
- ...boundaries.css.map((range) => findClassListsInCssRange(doc, range)),
- ])
+ if (isHtmlDoc(state, doc) || isJsDoc(state, doc) || isSvelteDoc(doc)) {
+ let text = doc.getText()
+ let styleBlocks = findAll(/