in src/services/htmlSelectionRange.ts [132:207]
function getAttributeLevelRanges(document: TextDocument, currNode: Node, currOffset: number) : [number, number][] {
const currNodeRange = Range.create(document.positionAt(currNode.start), document.positionAt(currNode.end));
const currNodeText = document.getText(currNodeRange);
const relativeOffset = currOffset - currNode.start;
/**
* Tag level semantic selection
*/
const scanner = createScanner(currNodeText);
let token = scanner.scan();
/**
* For text like
* <div class="foo">bar</div>
*/
const positionOffset = currNode.start;
const result = [];
let isInsideAttribute = false;
let attrStart = -1;
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.AttributeName: {
if (relativeOffset < scanner.getTokenOffset()) {
isInsideAttribute = false;
break;
}
if (relativeOffset <= scanner.getTokenEnd()) {
// `class`
result.unshift([scanner.getTokenOffset(), scanner.getTokenEnd()]);
}
isInsideAttribute = true;
attrStart = scanner.getTokenOffset();
break;
}
case TokenType.AttributeValue: {
if (!isInsideAttribute) {
break;
}
const valueText = scanner.getTokenText();
if (relativeOffset < scanner.getTokenOffset()) {
// `class="foo"`
result.push([attrStart, scanner.getTokenEnd()]);
break;
}
if (relativeOffset >= scanner.getTokenOffset() && relativeOffset <= scanner.getTokenEnd()) {
// `"foo"`
result.unshift([scanner.getTokenOffset(), scanner.getTokenEnd()]);
// `foo`
if ((valueText[0] === `"` && valueText[valueText.length - 1] === `"`) || (valueText[0] === `'` && valueText[valueText.length - 1] === `'`)) {
if (relativeOffset >= scanner.getTokenOffset() + 1 && relativeOffset <= scanner.getTokenEnd() - 1) {
result.unshift([scanner.getTokenOffset() + 1, scanner.getTokenEnd() - 1]);
}
}
// `class="foo"`
result.push([attrStart, scanner.getTokenEnd()]);
}
break;
}
}
token = scanner.scan();
}
return result.map(pair => {
return [pair[0] + positionOffset, pair[1] + positionOffset];
});
}