Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(preset-attributify): autocomplete with prefix (#2643)
- Loading branch information
1 parent
df6894e
commit 83574b8
Showing
3 changed files
with
202 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,94 +1,110 @@ | ||
import type { AutoCompleteExtractor } from '@unocss/core' | ||
import { variantsRE } from './variant' | ||
import type { AttributifyOptions } from '.' | ||
|
||
const elementRE = /(<\w[\w:\.$-]*\s)((?:'[^>]*?'|"[^>]*?"|`[^>]*?`|\{[^>]*?\}|[^>]*?)*)/g | ||
const valuedAttributeRE = /([?]|(?!\d|-{2}|-\d)[a-zA-Z0-9\u00A0-\uFFFF-_:%-]+)(?:=("[^"]*|'[^']*))?/g | ||
const splitterRE = /[\s'"`;>]+/ | ||
|
||
export const autocompleteExtractorAttributify: AutoCompleteExtractor = { | ||
name: 'attributify', | ||
extract: ({ content, cursor }) => { | ||
const matchedElements = content.matchAll(elementRE) | ||
let attrs: string | undefined | ||
let elPos = 0 | ||
for (const match of matchedElements) { | ||
const [, prefix, content] = match | ||
const currentPos = match.index! + prefix.length | ||
if (cursor > currentPos && cursor <= currentPos + content.length) { | ||
elPos = currentPos | ||
attrs = content | ||
break | ||
export function autocompleteExtractorAttributify(options?: AttributifyOptions): AutoCompleteExtractor { | ||
return { | ||
name: 'attributify', | ||
extract: ({ content, cursor }) => { | ||
const matchedElements = content.matchAll(elementRE) | ||
let attrs: string | undefined | ||
let elPos = 0 | ||
for (const match of matchedElements) { | ||
const [, prefix, content] = match | ||
const currentPos = match.index! + prefix.length | ||
if (cursor > currentPos && cursor <= currentPos + content.length) { | ||
elPos = currentPos | ||
attrs = content | ||
break | ||
} | ||
} | ||
} | ||
if (!attrs) | ||
return null | ||
if (!attrs) | ||
return null | ||
|
||
const matchedAttributes = attrs.matchAll(valuedAttributeRE) | ||
let attrsPos = 0 | ||
let attrName: string | undefined | ||
let attrValues: string | undefined | ||
for (const match of matchedAttributes) { | ||
const [matched, name, rawValues] = match | ||
const currentPos = elPos + match.index! | ||
if (cursor > currentPos && cursor <= currentPos + matched.length) { | ||
attrsPos = currentPos | ||
attrName = name | ||
attrValues = rawValues?.slice(1) | ||
break | ||
const matchedAttributes = attrs.matchAll(valuedAttributeRE) | ||
let attrsPos = 0 | ||
let attrName: string | undefined | ||
let attrValues: string | undefined | ||
for (const match of matchedAttributes) { | ||
const [matched, name, rawValues] = match | ||
const currentPos = elPos + match.index! | ||
if (cursor > currentPos && cursor <= currentPos + matched.length) { | ||
attrsPos = currentPos | ||
attrName = name | ||
attrValues = rawValues?.slice(1) | ||
break | ||
} | ||
} | ||
} | ||
if (!attrName) | ||
return null | ||
if (!attrName) | ||
return null | ||
|
||
if (attrName === 'class' || attrName === 'className' || attrName === ':class') | ||
return null | ||
if (attrValues === undefined) { | ||
return { | ||
extracted: attrName, | ||
resolveReplacement(suggestion) { | ||
return { | ||
start: attrsPos, | ||
end: attrsPos + attrName!.length, | ||
replacement: suggestion, | ||
} | ||
}, | ||
} | ||
} | ||
if (attrName === 'class' || attrName === 'className' || attrName === ':class') | ||
return null | ||
|
||
const hasPrefix = !!options?.prefix && attrName.startsWith(options.prefix) | ||
if (options?.prefixedOnly && !hasPrefix) | ||
return null | ||
|
||
const attrValuePos = attrsPos + attrName.length + 2 | ||
const attrNameWithoutPrefix = hasPrefix ? attrName.slice(options.prefix!.length) : attrName | ||
|
||
let matchSplit = splitterRE.exec(attrValues) | ||
let currentPos = 0 | ||
let value: string | undefined | ||
while (matchSplit) { | ||
const [matched] = matchSplit | ||
if (cursor > attrValuePos + currentPos | ||
&& cursor <= attrValuePos + currentPos + matchSplit.index) { | ||
value = attrValues.slice(currentPos, currentPos + matchSplit.index) | ||
break | ||
if (attrValues === undefined) { | ||
return { | ||
extracted: attrNameWithoutPrefix, | ||
transformSuggestions(suggestion) { | ||
if (hasPrefix) | ||
return suggestion.map(s => options.prefix! + s) | ||
else | ||
return suggestion | ||
}, | ||
resolveReplacement(suggestion) { | ||
return { | ||
start: attrsPos, | ||
end: attrsPos + attrName!.length, | ||
replacement: suggestion, | ||
} | ||
}, | ||
} | ||
} | ||
currentPos += matchSplit.index + matched.length | ||
matchSplit = splitterRE.exec(attrValues.slice(currentPos)) | ||
} | ||
if (value === undefined) | ||
value = attrValues.slice(currentPos) | ||
|
||
const [, variants = '', body] = value.match(variantsRE) || [] | ||
const attrValuePos = attrsPos + attrName.length + 2 | ||
|
||
return { | ||
extracted: `${variants}${attrName}-${body}`, | ||
transformSuggestions(suggestions) { | ||
return suggestions | ||
.filter(v => v.startsWith(`${variants}${attrName}-`)) | ||
.map(v => variants + v.slice(variants.length + attrName!.length + 1)) | ||
}, | ||
resolveReplacement(suggestion) { | ||
return { | ||
start: currentPos + attrValuePos, | ||
end: currentPos + attrValuePos + value!.length, | ||
replacement: variants + suggestion.slice(variants.length + attrName!.length + 1), | ||
let matchSplit = splitterRE.exec(attrValues) | ||
let currentPos = 0 | ||
let value: string | undefined | ||
while (matchSplit) { | ||
const [matched] = matchSplit | ||
if (cursor > attrValuePos + currentPos | ||
&& cursor <= attrValuePos + currentPos + matchSplit.index) { | ||
value = attrValues.slice(currentPos, currentPos + matchSplit.index) | ||
break | ||
} | ||
}, | ||
} | ||
}, | ||
currentPos += matchSplit.index + matched.length | ||
matchSplit = splitterRE.exec(attrValues.slice(currentPos)) | ||
} | ||
if (value === undefined) | ||
value = attrValues.slice(currentPos) | ||
|
||
const [, variants = '', body] = value.match(variantsRE) || [] | ||
|
||
return { | ||
extracted: `${variants}${attrNameWithoutPrefix}-${body}`, | ||
transformSuggestions(suggestions) { | ||
return suggestions | ||
.filter(v => v.startsWith(`${variants}${attrNameWithoutPrefix}-`)) | ||
.map(v => variants + v.slice(variants.length + attrNameWithoutPrefix!.length + 1)) | ||
}, | ||
resolveReplacement(suggestion) { | ||
return { | ||
start: currentPos + attrValuePos, | ||
end: currentPos + attrValuePos + value!.length, | ||
replacement: variants + suggestion.slice(variants.length + attrNameWithoutPrefix!.length + 1), | ||
} | ||
}, | ||
} | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters