项目地址:https://github.com/CBDxin/cb-plugin
vscode插件市场搜索css-helper-plugin可试用
前置知识
vscode.languages.registerCompletionItemProvider
我们可以通过vscode.languages.registerCompletionItemProvider方法注册vscode的自动补全。

- selector是要进行关联的文件类型;
 - provider是一个对象,里面必须包含provideCompletionItems和resolveCompletionItem这2个方法,provideCompletionItems用于定义自动补全的item,resolveCompletionItem用于定义光标选中当前自动补全item时触发动作,一般情况下无需处理;
 - triggerCharacters是一个可选的用于触发补全提示的字符列表;
 
vscode.languages.registerCodeLensProvider
codeLens可以在不影响代码结构的前提下在编译器中展示一些与代码相关的信息或者提供一些相关的操作,而我们平时用得最多的就是GitLens了。

通过vscode.languages.registerCodeLensProvider方法,我们可以定制一个属于自己的codeLens。

- selector是要进行关联的文件类型;
 - provider是一个对象,里面必须包含provideCodeLenses和resolveCodeLenses这2个方法,provideCodeLenses用于定义lenses,返回一个lenses列表;
 
vscode.languages.registerHoverProvider
当我们把鼠标悬停在代码中的某个单词上面是,vscode有时会给予我们一些与这个单词相关的信息,

这是通过vscode.languages.registerHoverProvider实现的。

- selector是要进行关联的文件类型;
 - HoverProvider用于定义悬停时产生的具体提示;
 
代码片段
在插件的package.json中添加如下配置:
"contributes": {
    "snippets": [
        {
            "language": "less",//snippets生效的语言类型
            "path": "./src/snippets/css.json"//snippets规则文件路径
        }
    ],
}
css.json文件内容如下:
{
  "cssAlias": {
    "prefix": "ca",
    "body": [
      "/*",
      "\talias:${0:alias}",
      "\tdesc:${1:desc}",
      "*/"
    ],
    "description": "Code snippet for 'setTimeout'"
  }
}
通过上面的配置,当我们在less文件中输入ca时,vscode可以为我们自动生成如下代码:

配置
我们可以定制插件的配置项,插件的配置项会出现在vscode的系统设置的扩展下面:

为了定制插件的配置,我们需要在package.json的属性中添加如下配置:
"configuration": {
    "type": "object",
    "title": "cb-pligin",
    "properties": {
        "cb-plugin.lessVariablesPath": {
        "type": "string",
        "default": "client\\common\\style\\variables.less",
        "description": "less变量文件路径"
    },
    "cb-plugin.globalCssPath": {
        "type": "string",
        "default": "client\\common\\style\\index.less",
        "description": "全局css样式文件路径"
    }
}
通过vscode.workspace.getConfiguration().get和vscode.workspace.getConfiguration().update方法你可以获取和设置你的配置项。
className自动补全
有时候,我们提前定义好一些全局或者是各个组件的样式文件,然后再到jsx中去填写className。这时经常会出现一些令人十分痛苦的情况,例如忘记了定义好的className,或者是拼写错误导致样式不生效。如果当我们在jsx的className时能够vscode能够进行补全提示,那就真的是太好了!通过vscode.languages.registerCompletionItemProvider,我们就可以实现这样的功能。
const classMatchReg = /className=["|']/;
function provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
  const start: vscode.Position = new vscode.Position(position.line, 0);
  const range: vscode.Range = new vscode.Range(start, position);
  const text: string = document.getText(range);
  const rawClasses = classMatchReg.test(text);
  if (!rawClasses) {
    return [];
  }
  const globalCssPath = path.join(vscode.workspace.workspaceFolders[0].uri._fsPath, vscode.workspace.getConfiguration().get('cb-plugin.globalCssPath'));
  const classNames = findCssClassNames(globalCssPath);
  return classNames.map((className) => {
    const completionItem = new vscode.CompletionItem(className, vscode.CompletionItemKind.Variable);
    completionItem.detail = className;
    return completionItem;
  });
}
首先通过正则/className=["|']/匹配当前是否在填写className,若匹配通过,则通过findCssClassNames方法获取全局样式文件中的全部className。
import * as fs from 'fs';
const CLSAANAME_REG =  /[\s]*\.([^:\s]+)[\s]*{/g;
export default function findClassNames(lessPath: string){
  const classNames:string[] = [];
  if(fs.existsSync(lessPath)){
    const content = fs.readFileSync(lessPath, 'utf-8');
    let matched;
    while((matched = CLSAANAME_REG.exec(content)) !== null){
      classNames.push(matched[1])
    }
  }
  return classNames;
}
然后将各个className作为vscode.CompletionItem返回。
通过别名补全className
但有时候,一些类名难以记住,我们需要通过别名识别某个类名,
const classMatchReg = /className=["|']/;
function provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
  const start: vscode.Position = new vscode.Position(position.line, 0);
  const range: vscode.Range = new vscode.Range(start, position);
  const text: string = document.getText(range);
  const rawClasses = classMatchReg.test(text);
  if (!rawClasses) {
    return [];
  }
  const globalCssPath = path.join(vscode.workspace.workspaceFolders[0].uri._fsPath, vscode.workspace.getConfiguration().get('cb-plugin.globalCssPath'));
  const aliases = findCssAlias(globalCssPath);
  return Object.keys(aliases).map((alias) => {
    const aliasValue = aliases[alias].value;
    const aliasDesc = aliases[alias].desc;
    const completionItem = new vscode.CompletionItem(aliasValue, vscode.CompletionItemKind.Variable);
    completionItem.detail = aliasDesc;
    completionItem.filterText = `${aliasValue}: ${alias};`;
    return completionItem;
  });
}
通过findCssAlias从
/*
  alias:alias
  desc:desc
*/
查找出alias、desc和对应className,并生成对应的vscode.CompletionItem
less变量codeLens
有时候我们定义了一些全局的less变量,但由于种种原因,如项目成员不清楚已有这样的一堆变量或者对这些变量不熟悉,直接从交互稿上直接复制一些css代码等,导致定义好的变量没有被用到,这种情况我们可以通过codeLens在页面中给用户一些提示信息,并为用户提供点击使用变量替换原来的值的功能。
定义CodeLensProvider:
export class CodelensProvider implements vscode.CodeLensProvider {
    private codeLenses: vscode.CodeLens[] = [];
    private regex: RegExp;
    private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
    public readonly onDidChangeCodeLenses: vscode.Event<void> = this._onDidChangeCodeLenses.event;
    constructor() {
        this.regex = /.:[\s]*([^:\s;]+)/g;
        vscode.workspace.onDidChangeConfiguration((_) => {
            this._onDidChangeCodeLenses.fire();
        });
    }
    public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> {
        if (vscode.workspace.getConfiguration("codelens-sample").get("enableCodeLens", true)) {
            this.codeLenses = [];
            const regex = new RegExp(this.regex);
            const text = document.getText();
            let matches, matchedAlias;
            //@ts-ignore
            const lessVariablesPath = path.join(vscode.workspace.workspaceFolders[0].uri._fsPath, vscode.workspace.getConfiguration().get('cb-plugin.lessVariablesPath'));
            const lessVariables = Object.assign({}, findVariables(lessVariablesPath));
            while ((matches = regex.exec(text)) !== null ) {
                matchedAlias = matchLessVariable(lessVariables, matches[1])
                if(matchedAlias){
                    const line = document.lineAt(document.positionAt(matches.index).line);
                    const indexOf = line.text.indexOf(matches[1]);
                    const position = new vscode.Position(line.lineNumber, indexOf);
                    const range = document.getWordRangeAtPosition(position, new RegExp(/([^:\s;]+)/g));
                    if (range) {
                        this.codeLenses.push(new tipCodeLens(document.fileName, range, matchedAlias, matches[1]));
                    }
                }
            }
            return this.codeLenses;
        }
        return [];
    }
    public resolveCodeLens(codeLens: vscode.CodeLens, token: vscode.CancellationToken) {
        return null;
    }
}
通过/.:[\s]*([^:\s;]+)/g匹配文件中的各个css值,通过matchLessVariable方法检查各个css值是否已经被定义:
function matchLessVariable(lessVariables: any, targetValue: string){
    for (const key in lessVariables) {
        if(lessVariables[key].toLocaleLowerCase() === targetValue.toLocaleLowerCase()){
            return key;
        }
    }
}
若已定义,则通过tipCodeLens生成对应codelens:
import { CodeLens, Range } from "vscode";
export default class TipCodeLens extends CodeLens {
  constructor(
    fileName: string,
    range: Range,
    alias: string,
    value: string
  ) {
    super(range, {
      arguments: [alias, value, fileName, range],
      command: "cb-plugin.codelensAction",
      title: `${value} can be replaced by ${alias},click to replace`
    });
  }
}










