VSCode扩展开发求助:如何识别SCSS中光标所在的父选择器
实现VSCode「HTML to CSS autocompletion」扩展的SCSS &选择器补全功能
一、必须导入的VSCode核心模块
你需要从vscode模块中导入以下核心API,用于文档解析、光标定位和补全逻辑:
import { CompletionItem, CompletionItemProvider, TextDocument, Position, Range, workspace, CompletionItemKind } from 'vscode';
二、识别光标所在的块级父选择器
要获取光标所在的父选择器,核心是逆向解析文档文本,定位最近的代码块:
- 获取当前文档和光标位置的上下文
- 从光标位置向上遍历,找到最近的
{字符,再向前截取到上一个}或文档开头的文本,提取其中的选择器 - 清洗选择器文本,去除多余空格、换行和无效字符,得到纯净的父选择器(如
.description)
示例解析函数:
function getParentSelector(document: TextDocument, position: Position): string | null { let currentPos = position.translate(0, -1); // 向上查找最近的代码块起始符{ while (currentPos.character >= 0) { const char = document.getText(new Range(currentPos, currentPos.translate(0, 1))); if (char === '{') { // 从{的位置向前定位选择器起始点 let selectorStart = currentPos.translate(0, -1); while (selectorStart.character >= 0 && !['}', '\n', ';'].includes(document.getText(new Range(selectorStart, selectorStart.translate(0, 1))))) { selectorStart = selectorStart.translate(0, -1); } // 提取并清洗选择器文本 const selector = document.getText(new Range(selectorStart.translate(0, 1), currentPos)).trim(); return selector || null; } // 换行后继续向上查找 if (currentPos.character === 0) { if (currentPos.line === 0) break; currentPos = document.lineAt(currentPos.line - 1).range.end; } else { currentPos = currentPos.translate(0, -1); } } return null; }
三、实现&触发的补全逻辑
当用户输入&时,执行以下流程:
- 检查光标前的字符是否为
&,触发补全逻辑 - 读取当前工作区所有HTML文件,提取所有class属性值,筛选出以父选择器开头的类名
- 将筛选后的类名替换前缀为
&,生成对应的补全项
示例补全提供者实现:
class SCSSAmpersandCompletionProvider implements CompletionItemProvider { async provideCompletionItems(document: TextDocument, position: Position) { // 判断是否触发&补全 const preCharRange = new Range(position.translate(0, -1), position); const preChar = document.getText(preCharRange); if (preChar !== '&') return []; // 获取父选择器 const parentSelector = getParentSelector(document, position); if (!parentSelector || !parentSelector.startsWith('.')) return []; const parentClass = parentSelector.slice(1); // 去掉前缀.,得到description const completionItems: CompletionItem[] = []; // 遍历工作区所有HTML文件提取类名 const htmlFiles = await workspace.findFiles('**/*.html'); for (const file of htmlFiles) { const doc = await workspace.openTextDocument(file); const text = doc.getText(); // 匹配所有class属性值 const classRegex = /class="([^"]+)"/g; let match; while ((match = classRegex.exec(text)) !== null) { const classes = match[1].split(' '); classes.forEach(cls => { if (cls.startsWith(`${parentClass}__`)) { // 替换前缀为&,生成SCSS选择器 const scssSelector = `&${cls.slice(parentClass.length)}`; const item = new CompletionItem(scssSelector, CompletionItemKind.Property); item.insertText = scssSelector; completionItems.push(item); } }); } } // 去重后返回补全项 return [...new Map(completionItems.map(item => [item.label, item])).values()]; } }
四、注册补全提供者
在扩展的activate方法中注册该提供者,指定触发语言为SCSS:
export function activate(context: vscode.ExtensionContext) { const provider = new SCSSAmpersandCompletionProvider(); const disposable = vscode.languages.registerCompletionItemProvider( 'scss', provider, '&' // 设置触发字符为& ); context.subscriptions.push(disposable); }
内容的提问来源于stack exchange,提问作者Nabs
相关产品推荐
相关产品推荐

