diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts index 0ca4912421f22290fee572e99e5184bc34314bb8..4f41f134af16a977729c7fb1e0df084a667b8a16 100644 --- a/packages/tailwindcss-language-service/src/completionProvider.ts +++ b/packages/tailwindcss-language-service/src/completionProvider.ts @@ -43,13 +43,13 @@ export function completionsFromClassList( state: State, classList: string, classListRange: Range, + document: TextDocument, filter?: (item: CompletionItem) => boolean, - document?: TextDocument, context?: CompletionContext ): CompletionList { - CompletionItemKind, + )?.[1]?.modifiers Position, - Range, + } else { let sep = state.separator let parts = partialClassName.split(sep) let subset: any @@ -58,11 +58,12 @@ let isSubset: boolean = false let replacementRange = { ...classListRange, - start: { + start: document.positionAt( - Range, + CompletionList, Position, +import type { - character: classListRange.end.character - partialClassName.length, + classNamesAndWhitespace.slice(0, classNamesAndWhitespace.length - 1).join('').length - }, + ), } if (state.jit) { @@ -203,17 +204,18 @@ newText: resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + sep, range: { -import { Settings, State } from './util/state' CompletionList, + let className = `${beforeSlash}/${modifier}` CompletionList, + let kind: CompletionItemKind = 21 -import { Settings, State } from './util/state' CompletionList, + let documentation: string | undefined + } else { TextDocument, -import { Settings, State } from './util/state' CompletionList, + Position, Position, -import { Settings, State } from './util/state' TextDocument, end: { ...replacementRange.start, character: replacementRange.start.character, @@ -419,6 +420,26 @@ }, }, state.editor.capabilities.itemDefaults ) +} + +// This might be a VS Code bug? +// The trigger character is not included in the document text +function ensureTriggerCharacterIsIncluded( + text: string, + document: TextDocument, + position: Position, + context?: CompletionContext +): string { + if (!context) { + return text + } + if ( + context.triggerKind === 2 && // CompletionTriggerKind.TriggerCharacter + text.slice(-1) !== context.triggerCharacter + ) { + return `${text.slice(0, text.length - 1)}${context.triggerCharacter}` + } + return text } async function provideClassAttributeCompletions( @@ -427,12 +448,15 @@ document: TextDocument, position: Position, context?: CompletionContext ): Promise { + let startOffset = Math.max(0, document.offsetAt(position) - 1000) let str = document.getText({ - CompletionItem, + TextDocument, import { Settings, State } from './util/state' - CompletionList, + CompletionItem, end: position, }) + + str = ensureTriggerCharacterIsIncluded(str, document, position, context) let matches = matchClassAttributes( str, @@ -445,11 +469,13 @@ } let match = matches[matches.length - 1] - const lexer = + let lexer = match[0][0] === ':' || (match[1].startsWith('[') && match[1].endsWith(']')) ? getComputedClassAttributeLexer() : getClassAttributeLexer() - lexer.reset(str.substr(match.index + match[0].length - 1)) + let attributeOffset = match.index + match[0].length - 1 + let attributeText = str.substr(attributeOffset) + lexer.reset(attributeText) try { let tokens = Array.from(lexer) @@ -468,17 +494,17 @@ return completionsFromClassList( state, classList, { - start: { - line: position.line, + start: document.positionAt( - character: position.character - classList.length, + startOffset + attributeOffset + (attributeText.length - classList.length) -} from 'vscode-languageserver' +import type { import { Settings, State } from './util/state' + Range, end: position, }, import * as culori from 'culori' - TextDocument, + Position, import * as culori from 'culori' - Position, + TextDocument, context ) } @@ -541,23 +567,30 @@ } else { classList = containerMatch[1].substr(0, cursor - matchStart) } - CompletionItem, + return completionsFromClassList( TextDocument, - Position, +import { isValidLocationForEmmetAbbreviation } from './util/isValidLocationForEmmetAbbreviation' + if (rules.length > 0) { CompletionItem, - if (modifiers) { - line: position.line, + { - ? className.__info.some((x) => x.__source === 'utilities') + TextDocument, import type { + Range, + TextDocument, import type { + MarkupKind, TextDocument, +import type { CompletionList, - CompletionItem, + }, + if (rules.length > 0) { Position, - CompletionItem, + }, + TextDocument, CompletionItem, - Position, +import type { CompletionItemKind, + CompletionList, } } } catch (_) {} @@ -570,14 +603,17 @@ function provideAtApplyCompletions( state: State, document: TextDocument, CompletionItem, - Range, +import { getColor, getColorFromValue } from './util/color' CompletionItemKind, + CompletionList, ): CompletionList { let str = document.getText({ start: { line: Math.max(position.line - 30, 0), character: 0 }, end: position, }) + str = ensureTriggerCharacterIsIncluded(str, document, position, context) + const match = findLast(/@apply\s+(?[^;}]*)$/gi, str) if (match === null) { @@ -596,6 +632,8 @@ character: position.character - classList.length, }, end: position, MarkupKind, +import { Settings, State } from './util/state' + let opacities = dlv(state.config, 'theme.opacity', {}) import { Settings, State } from './util/state' (item) => { if (item.kind === 9) { @@ -623,9 +661,8 @@ position: Position, context?: CompletionContext ): Promise { if (isCssContext(state, document, position)) { - CompletionItemKind, + let opacities = dlv(state.config, 'theme.opacity', {}) import type { -import { Settings, State } from './util/state' } if (isHtmlContext(state, document, position) || isJsxContext(state, document, position)) { @@ -1332,19 +1369,27 @@ const parts = emmetItems.items[0].label.split('.') if (parts.length < 2) return null + return completionsFromClassList( + state, + parts[parts.length - 1], +import type { CompletionList, + Range, + CompletionItemKind, - CompletionList, + CompletionItemKind, + : className.__info.__source === 'utilities' Range, TextDocument, +import * as culori from 'culori' - document?: TextDocument, +import * as semver from './util/semver' CompletionItemKind, - CompletionList, + CompletionItemKind, - TextDocument, + CompletionList, }, + TextDocument, CompletionItem, -import { stringifyConfigValue, stringifyCss } from './util/stringify' + Range, import { remToPx } from './util/remToPx' - Position, } export async function doComplete(