vscode插件开发实践

2020-09-20  本文已影响0人  CBDxin

项目地址:https://github.com/CBDxin/cb-plugin
vscode插件市场搜索css-helper-plugin可试用

前置知识

vscode.languages.registerCompletionItemProvider

我们可以通过vscode.languages.registerCompletionItemProvider方法注册vscode的自动补全。

image.png

vscode.languages.registerCodeLensProvider

codeLens可以在不影响代码结构的前提下在编译器中展示一些与代码相关的信息或者提供一些相关的操作,而我们平时用得最多的就是GitLens了。

image.png

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

image.png

vscode.languages.registerHoverProvider

当我们把鼠标悬停在代码中的某个单词上面是,vscode有时会给予我们一些与这个单词相关的信息,


image.png

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

image.png

代码片段

在插件的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可以为我们自动生成如下代码:


image.png

配置

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


image.png

为了定制插件的配置,我们需要在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().getvscode.workspace.getConfiguration().update方法你可以获取和设置你的配置项。

className自动补全

有时候,我们提前定义好一些全局或者是各个组件的样式文件,然后再到jsx中去填写className。这时经常会出现一些令人十分痛苦的情况,例如忘记了定义好的className,或者是拼写错误导致样式不生效。如果当我们在jsx的className时能够vscode能够进行补全提示,那就真的是太好了!通过vscode.languages.registerCompletionItemProvider,我们就可以实现这样的功能。

定义provideCompletionItems

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在页面中给用户一些提示信息,并为用户提供点击使用变量替换原来的值的功能。


2020-09-21 10-08-17 00_00_00-00_00_30.gif

定义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`
    });
  }
}

上一篇下一篇

猜你喜欢

热点阅读