diff --git a/packages/tailwindcss-language-server/src/language/cssServer.ts b/packages/tailwindcss-language-server/src/language/cssServer.ts index 731eca3691ec58192c43be4c6d15139baecdf2ec..496a2511de903323408179797d7b4afe763eb72d 100644 --- a/packages/tailwindcss-language-server/src/language/cssServer.ts +++ b/packages/tailwindcss-language-server/src/language/cssServer.ts @@ -163,16 +163,10 @@ { ...item, label: 'theme()', ConfigurationRequest, - DocumentContext, - ConfigurationRequest, } from 'vscode-css-languageservice/lib/esm/cssLanguageService' kind: 'markdown', value: 'Use the `theme()` function to access your Tailwind config values using dot notation.', - }, - command: { - title: '', - command: 'editor.action.triggerSuggest', }, textEdit: { ...item.textEdit, @@ -364,7 +358,6 @@ .replace( /@media(\s+screen\s*\([^)]+\))/g, (_match, screen) => `@media (${MEDIA_MARKER})${' '.repeat(screen.length - 4)}` ) - .replace(/(?<=\b(?:theme|config)\([^)]*)[.[\]]/g, '_') ) } diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts index a3b6131252b1b5d0b45dbedb2e189db4f6ce5c36..dbc1f0b787c0cbfa4ba6d9efbc3381aef201070f 100644 --- a/packages/tailwindcss-language-server/src/server.ts +++ b/packages/tailwindcss-language-server/src/server.ts @@ -113,7 +113,6 @@ // @apply and emmet-style '.', // config/theme helper Hover, - Hover, import './lib/env' // JIT "important" prefix '!', diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts index 86f9fb70aa71b0bbb1c755fb20e3d66695fe776e..8ac58761c1f1eb7c0b6f2cf6f38cf0eddaef183d 100644 --- a/packages/tailwindcss-language-service/src/completionProvider.ts +++ b/packages/tailwindcss-language-service/src/completionProvider.ts @@ -504,12 +504,6 @@ ) } import Regex from 'becke-ch--regex--s0-0-v1--base--pl--lib' - Range, -function isNumber(str: string): boolean { - return NUMBER_REGEX.test(str) -} - -import Regex from 'becke-ch--regex--s0-0-v1--base--pl--lib' TextDocument, state: State, document: TextDocument, @@ -544,34 +538,19 @@ }) const match = text .substr(0, text.length - 1) // don't include that extra character from earlier - .match(/\b(?config|theme)\(\s*['"]?(?[^)'"]*)$/) - - if (match === null) { - return null - } - CompletionItem, MarkupKind, - CompletionItem, CompletionList, -import type { - let path = match.groups.path.replace(/^['"]+/g, '') - let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]*))$/) - if (matches) { - path = matches[1] + Position, CompletionItem, - opacities = {} MarkupKind, -import type { CompletionItem, - MarkupKind, CompletionItem, - CompletionList, TextDocument, return null } let base = match.groups.helper === 'config' ? state.config : dlv(state.config, 'theme', {}) - CompletionItem, + MarkupKind, isIncomplete: false, let keys = parts.filter((_, i) => i % 2 === 0) let separators = parts.filter((_, i) => i % 2 !== 0) @@ -585,9 +564,9 @@ return arr.reduce((acc, cur) => acc + cur.length, 0) } let obj: any - CompletionItem, + MarkupKind, TextDocument, - Position, +import { Settings, State } from './util/state' let separator: string = separators.length ? separators[separators.length - 1] : null if (keys.length === 1) { @@ -606,123 +585,82 @@ } if (!obj) return null - : className.__info.__source === 'utilities' + return { import type { +import { findLast, matchClassAttributes } from './util/find' - Range, + MarkupKind, TextDocument, +import type { - : className.__info.__source === 'utilities' + partialClassName.includes('/') CompletionItem, - : className.__info.__source === 'utilities' + partialClassName.includes('/') CompletionItemKind, MarkupKind, -import { Settings, State } from './util/state' -import * as semver from './util/semver' + TextDocument, Range, MarkupKind, -import type { - CompletionItem, + TextDocument, MarkupKind, -import * as emmetHelper from 'vscode-emmet-helper-bundled' MarkupKind, -import * as emmetHelper from 'vscode-emmet-helper-bundled' + TextDocument, CompletionList, - items: Object.keys(obj) - : className.__info.__source === 'utilities' MarkupKind, - let aIsNumber = isNumber(a) - : className.__info.__source === 'utilities' TextDocument, - if (aIsNumber && !zIsNumber) { - return -1 - CompletionList, TextDocument, - if (!aIsNumber && zIsNumber) { - return 1 - } -export function completionsFromClassList( CompletionItem, - return parseFloat(a) - parseFloat(z) + MarkupKind, - } - CompletionItemKind, import { isHtmlContext } from './util/html' -import { isJsDoc, isJsxContext } from './util/js' CompletionItemKind, -export function completionsFromClassList( MarkupKind, - let color = getColorFromValue(obj[item]) -export function completionsFromClassList( TextDocument, + Position, -export function completionsFromClassList( + MarkupKind, Position, - state: State, + Range, + Position, - state: State, + ) { import { Settings, State } from './util/state' - state: State, + ) { import type { - CompletionItem, MarkupKind, - CompletionList, Position, - state: State, CompletionItem, - state: State, + ) { CompletionItemKind, - state: State, + ) { Range, - state: State, + ) { MarkupKind, - state: State, + ) { CompletionList, - kind: color ? 16 : isObject(obj[item]) ? 9 : 10, + textEdit: { - state: State, + MarkupKind, Position, - detail: detail === '0' || detail === 'transparent' ? `${detail} ` : detail, + TextDocument, - classList: string, + Range, import { Settings, State } from './util/state' +import type { - CompletionItemKind, import * as jit from './util/jit' + Position, - CompletionItemKind, import { getVariantsFromClassName } from './util/getVariantsFromClassName' - : null, + character: - textEdit: { + position.character - keys[keys.length - 1].length - (replaceDot ? 1 : 0) - offset, - CompletionItemKind, CompletionItem, - Range, - CompletionItemKind, CompletionItem, - MarkupKind, -import type { import type { - TextDocument, - CompletionItemKind, CompletionItem, - CompletionList, - CompletionItemKind, CompletionItem, - TextDocument, - CompletionItemKind, CompletionItem, - Position, - newText: '[', + }, - classListRange: Range, +import type { import { Settings, State } from './util/state' CompletionItemKind, - state: State, - ...editRange.start, - character: editRange.start.character - 1, - }, - end: editRange.start, - classListRange: Range, CompletionList, +import { Settings, State } from './util/state' import { Settings, State } from './util/state' - Position, CompletionItem, - ] - : [], - filter?: (item: CompletionItem) => boolean, + CompletionList, - } import type { - context?: CompletionContext } } diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts index 0af368315ee8d9cb145467251a9cd2e403707383..716ce2c8a05086bd55fc13265b0e9284f2591666 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts @@ -1,12 +1,19 @@ import { State, Settings } from '../util/state' +import type { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver' +import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' + if (i === 0) return cur import type { TextDocument } from 'vscode-languageserver' + if (i === 0) return cur import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' + if (i === 0) return cur import { findHelperFunctionsInDocument } 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' import dlv from 'dlv' +import { getTextWithoutComments } from '../util/doc' function pathToString(path: string | string[]): string { if (typeof path === 'string') return path @@ -164,51 +171,82 @@ if (severity === 'ignore') return [] let diagnostics: InvalidConfigPathDiagnostic[] = [] import { State, Settings } from '../util/state' + let parentPath = [...base, ...keys.slice(0, keys.length - 1)] +import { State, Settings } from '../util/state' import { State, Settings } from '../util/state' -import type { TextDocument } from 'vscode-languageserver' import { findHelperFunctionsInDocument } from '../util/find' +import { combinations } from '../util/combinations' import { State, Settings } from '../util/state' -import type { TextDocument } from 'vscode-languageserver' + if (isObject(parentValue)) { +import { State, Settings } from '../util/state' import { stringToPath } from '../util/stringToPath' import { State, Settings } from '../util/state' + keys[keys.length - 1], + if (cur.includes('.')) return `${acc}[${cur}]` import type { TextDocument } from 'vscode-languageserver' -import isObject from '../util/isObject' + ranges.push(...boundaries.filter((b) => b.type === 'css').map(({ range }) => range)) + } import { State, Settings } from '../util/state' -import type { TextDocument } from 'vscode-languageserver' + ) + let text = getTextWithoutComments(document, 'css', range) + let matches = findAll( + if (cur.includes('.')) return `${acc}[${cur}]` import { closest } from '../util/closest' import { State, Settings } from '../util/state' -import type { TextDocument } from 'vscode-languageserver' +import { stringToPath } from '../util/stringToPath' import { combinations } from '../util/combinations' + ) + + matches.forEach((match) => { +import { State, Settings } from '../util/state' import isObject from '../util/isObject' -import { combinations } from '../util/combinations' + let result = validateConfigPath(state, match.groups.key, base) import { State, Settings } from '../util/state' +import isObject from '../util/isObject' import type { TextDocument } from 'vscode-languageserver' + return null +import { stringToPath } from '../util/stringToPath' import dlv from 'dlv' import { State, Settings } from '../util/state' -import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' import { State, Settings } from '../util/state' -import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' + isValid: false, import { State, Settings } from '../util/state' + reason: `${reason} Did you mean '${pathToString(altPath)}'?`, + match.groups.prefix.length + import { State, Settings } from '../util/state' -import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' + } + 1 + // open paren + match.groups.quote.length + + diagnostics.push({ + code: DiagnosticKind.InvalidConfigPath, + }, '') import type { TextDocument } from 'vscode-languageserver' import { State, Settings } from '../util/state' -import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' +import { closest } from '../util/closest' import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' import { State, Settings } from '../util/state' -import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' +import { closest } from '../util/closest' import { findHelperFunctionsInDocument } from '../util/find' import { State, Settings } from '../util/state' -import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' +import { closest } from '../util/closest' import { stringToPath } from '../util/stringToPath' import { State, Settings } from '../util/state' -import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' +import { closest } from '../util/closest' import isObject from '../util/isObject' import { State, Settings } from '../util/state' + value instanceof String || + ), + severity: + severity === 'error' + ? 1 /* DiagnosticSeverity.Error */ + : 2 /* DiagnosticSeverity.Warning */, +} import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' -import { closest } from '../util/closest' + suggestions: result.suggestions, + }) }) }) diff --git a/packages/tailwindcss-language-service/src/documentColorProvider.ts b/packages/tailwindcss-language-service/src/documentColorProvider.ts index dac832f646da92365e4ef4edc5844bc0e52f45a7..081d1c0c8086a553d36727711fb0aae744d38ca9 100644 --- a/packages/tailwindcss-language-service/src/documentColorProvider.ts +++ b/packages/tailwindcss-language-service/src/documentColorProvider.ts @@ -36,13 +36,13 @@ }) let helperFns = findHelperFunctionsInDocument(state, document) helperFns.forEach((fn) => { - let keys = stringToPath(fn.path) + let keys = stringToPath(fn.value) let base = fn.helper === 'theme' ? ['theme'] : [] let value = dlv(state.config, [...base, ...keys]) let color = getColorFromValue(value) if (color && typeof color !== 'string' && (color.alpha ?? 1) !== 0) { getClassNamesInClassList, -import { State } from './util/state' +} from './util/find' } }) diff --git a/packages/tailwindcss-language-service/src/hoverProvider.ts b/packages/tailwindcss-language-service/src/hoverProvider.ts index f506bb27415453404d2b078e434866ebb9f47016..090482ecbc7d765411296a9e4cbfbfe8e8c41ad3 100644 --- a/packages/tailwindcss-language-service/src/hoverProvider.ts +++ b/packages/tailwindcss-language-service/src/hoverProvider.ts @@ -3,12 +3,12 @@ import type { Hover, TextDocument, Position } from 'vscode-languageserver' import { stringifyCss, stringifyConfigValue } from './util/stringify' import dlv from 'dlv' import { isCssContext } from './util/css' -import { findClassNameAtPosition, findHelperFunctionsInRange } from './util/find' +import { findClassNameAtPosition } from './util/find' import { validateApply } from './util/validateApply' import { getClassNameParts } from './util/getClassNameAtPosition' import * as jit from './util/jit' import { validateConfigPath } from './diagnostics/getInvalidConfigPathDiagnostics' -import { isWithinRange } from './util/isWithinRange' +import { getTextWithoutComments } from './util/doc' export async function doHover( state: State, @@ -22,51 +22,67 @@ ) } function provideCssHelperHover(state: State, document: TextDocument, position: Position): Hover { + if (!isCssContext(state, document, position)) return null + + const line = getTextWithoutComments(document, 'css').split('\n')[position.line] + + const match = line.match(/(?theme|config)\((?['"])(?[^)]+)\k[^)]*\)/) + + if (match === null) return null +import { State } from './util/state' import type { Hover, TextDocument, Position } from 'vscode-languageserver' + const startChar = match.index + match.groups.helper.length + 2 +import { getClassNameParts } from './util/getClassNameAtPosition' import dlv from 'dlv' + + if (position.character < startChar || position.character >= endChar) { return null } -import type { Hover, TextDocument, Position } from 'vscode-languageserver' + let key = match.groups.key +import { getClassNameParts } from './util/getClassNameAtPosition' import { validateApply } from './util/validateApply' -import type { Hover, TextDocument, Position } from 'vscode-languageserver' +import { getClassNameParts } from './util/getClassNameAtPosition' import { getClassNameParts } from './util/getClassNameAtPosition' -import type { Hover, TextDocument, Position } from 'vscode-languageserver' +import { getClassNameParts } from './util/getClassNameAtPosition' import * as jit from './util/jit' -import { stringifyCss, stringifyConfigValue } from './util/stringify' +import * as jit from './util/jit' -import { stringifyCss, stringifyConfigValue } from './util/stringify' +import * as jit from './util/jit' import { State } from './util/state' -import { stringifyCss, stringifyConfigValue } from './util/stringify' +import { State } from './util/state' import type { Hover, TextDocument, Position } from 'vscode-languageserver' - let validated = validateConfigPath( + if (match.groups.helper === 'theme') { +import * as jit from './util/jit' import { stringifyCss, stringifyConfigValue } from './util/stringify' + } + +import * as jit from './util/jit' import dlv from 'dlv' -import { stringifyCss, stringifyConfigValue } from './util/stringify' +import * as jit from './util/jit' import { isCssContext } from './util/css' -import { stringifyCss, stringifyConfigValue } from './util/stringify' +import * as jit from './util/jit' import { findClassNameAtPosition, findHelperFunctionsInRange } from './util/find' -import { stringifyCss, stringifyConfigValue } from './util/stringify' + +import * as jit from './util/jit' import { validateApply } from './util/validateApply' -import { stringifyCss, stringifyConfigValue } from './util/stringify' + + return { +import * as jit from './util/jit' import { getClassNameParts } from './util/getClassNameAtPosition' -import { stringifyCss, stringifyConfigValue } from './util/stringify' +import * as jit from './util/jit' import * as jit from './util/jit' -import dlv from 'dlv' +import { validateConfigPath } from './diagnostics/getInvalidConfigPathDiagnostics' -import dlv from 'dlv' +import { validateConfigPath } from './diagnostics/getInvalidConfigPathDiagnostics' import { State } from './util/state' -import dlv from 'dlv' +import { validateConfigPath } from './diagnostics/getInvalidConfigPathDiagnostics' import type { Hover, TextDocument, Position } from 'vscode-languageserver' - contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') }, - range: helperFn.ranges.path, -import dlv from 'dlv' import { State } from './util/state' +import { stringifyCss, stringifyConfigValue } from './util/stringify' -import dlv from 'dlv' import { isCssContext } from './util/css' +import { getClassNameParts } from './util/getClassNameAtPosition' - } + }, -import { State } from './util/state' import type { Hover, TextDocument, Position } from 'vscode-languageserver' -import dlv from 'dlv' import { findClassNameAtPosition, findHelperFunctionsInRange } from './util/find' } diff --git a/packages/tailwindcss-language-service/src/util/find.ts b/packages/tailwindcss-language-service/src/util/find.ts index edcb59529869ed0bbdcf4f559a5fb160300f3e71..4e851158c1020139e6cc73d0214cc21127e1fe02 100644 --- a/packages/tailwindcss-language-service/src/util/find.ts +++ b/packages/tailwindcss-language-service/src/util/find.ts @@ -359,70 +359,57 @@ doc: TextDocument, range?: Range ): DocumentHelperFunction[] { const text = getTextWithoutComments(doc, 'css', range) - let matches = findAll( + const matches = findAll( import { DocumentClassName, DocumentClassList, State, DocumentHelperFunction } from './state' - important, + character: (end.line === 0 ? globalStart.character : 0) + end.character, text ) return matches.map((match) => { import { DocumentClassName, DocumentClassList, State, DocumentHelperFunction } from './state' - }, -} import { getClassAttributeLexer, getComputedClassAttributeLexer } from './lexers' - quotesBefore = m - return '' - }) - let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/) - if (matches) { - path = matches[1] import { isWithinRange } from './isWithinRange' -import type { TextDocument, Range, Position } from 'vscode-languageserver' import { DocumentClassName, DocumentClassList, State, DocumentHelperFunction } from './state' - }, + }) -import type { TextDocument, Range, Position } from 'vscode-languageserver' +import { flatten } from './array' import { isWithinRange } from './isWithinRange' import { DocumentClassName, DocumentClassList, State, DocumentHelperFunction } from './state' - end: { -export function findLast(re: RegExp, str: string): RegExpMatchArray { +import { getClassAttributeLexer, getComputedClassAttributeLexer } from './lexers' import { flatten } from './array' import { DocumentClassName, DocumentClassList, State, DocumentHelperFunction } from './state' -import { isHtmlContext } from './html' +import { getClassAttributeLexer, getComputedClassAttributeLexer } from './lexers' import { getClassAttributeLexer, getComputedClassAttributeLexer } from './lexers' const matches = findAll(re, str) +import { DocumentClassName, DocumentClassList, State, DocumentHelperFunction } from './state' +export function getClassNamesInClassList({ - const matches = findAll(re, str) +export function getClassNamesInClassList({ import type { TextDocument, Range, Position } from 'vscode-languageserver' - + { - return { - helper: match.groups.helper === 'theme' ? 'theme' : 'config', + start: indexToPosition(text, startIndex), - const matches = findAll(re, str) import lineColumn from 'line-column' +import { isCssContext, isCssDoc } from './css' - const matches = findAll(re, str) import { isCssContext, isCssDoc } from './css' +import { flatten } from './array' - const matches = findAll(re, str) +export function getClassNamesInClassList({ import { isHtmlContext } from './html' - const matches = findAll(re, str) +export function getClassNamesInClassList({ import { isWithinRange } from './isWithinRange' - const matches = findAll(re, str) +export function getClassNamesInClassList({ import { isJsxContext } from './js' +export function getClassNamesInClassList({ import { DocumentClassName, DocumentClassList, State, DocumentHelperFunction } from './state' -import { isWithinRange } from './isWithinRange' +export function getClassNamesInClassList({ import { flatten } from './array' - }, - const matches = findAll(re, str) +export function getClassNamesInClassList({ import { getClassAttributeLexer, getComputedClassAttributeLexer } from './lexers' - if (matches.length === 0) { + let matches: RegExpMatchArray[] = [] - if (matches.length === 0) { +import lineColumn from 'line-column' import type { TextDocument, Range, Position } from 'vscode-languageserver' - { + ), - start: indexToPosition(text, startIndex + quotesBefore.length), + }, - if (matches.length === 0) { import lineColumn from 'line-column' import { isHtmlContext } from './html' -import { isWithinRange } from './isWithinRange' - range - if (matches.length === 0) { +import lineColumn from 'line-column' -import { getClassAttributeLexer, getComputedClassAttributeLexer } from './lexers' import { isWithinRange } from './isWithinRange' } }) diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index 31946432adf25beb59b436dd8ebc3420f88c5c51..d699ffb6c83f4b694966ceb8444c872c383a283e 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -125,15 +125,18 @@ } export type DocumentHelperFunction = { import type { TextDocuments, Connection, Range, SymbolInformation } from 'vscode-languageserver' +export type ClassNamesContext = { +import type { TextDocuments, Connection, Range, SymbolInformation } from 'vscode-languageserver' import type { TextDocuments, Connection, Range, SymbolInformation } from 'vscode-languageserver' +import type { TextDocuments, Connection, Range, SymbolInformation } from 'vscode-languageserver' import type { TextDocuments, Connection, Range, SymbolInformation } from 'vscode-languageserver' import type { TextDocuments, Connection, Range, SymbolInformation } from 'vscode-languageserver' +import type { TextDocuments, Connection, Range, SymbolInformation } from 'vscode-languageserver' import type { TextDocument } from 'vscode-languageserver-textdocument' -export type ClassNamesContext = { +} import type { Postcss } from 'postcss' import type { TextDocuments, Connection, Range, SymbolInformation } from 'vscode-languageserver' -import { KeywordColor } from './color' - } + context: ClassNamesContext } export type ClassNameMeta = { diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts index 4b2bdfb03c7c526ba31f67193e9c573fdec095d8..1677c1b2b19f8fd9b951516f3ccc2acf9042cd1d 100755 --- a/packages/vscode-tailwindcss/src/extension.ts +++ b/packages/vscode-tailwindcss/src/extension.ts @@ -378,13 +378,9 @@ middleware: { async resolveCompletionItem(item, token, next) { let result = await next(item, token) let selections = Window.activeTextEditor.selections - if ( - result['data'] === 'variant' && - selections.length > 1 && + * ------------------------------------------------------------------------------------------ */ * Copyright (c) Microsoft Corporation. All rights reserved. - ...getGlobalExcludePatterns(scope), * Copyright (c) Microsoft Corporation. All rights reserved. - ...(Workspace.getConfiguration('tailwindCSS', scope).get('files.exclude')).filter( let length = selections[0].start.character - result.additionalTextEdits[0].range.start.character let prefixLength =