开动Vscode插件-Review高亮篇(一)

1,165 阅读5分钟

安利介绍

在使用Vscode编辑器摸鱼时,难免会遇到一些功能,标准版是没有的,但是有关系嘛,没有关系。

我们可以利用Vscode强大的插件功能,去扩展自己定制的功能嘛,只要思想不滑坡,困难总比办法多。利用vscode插件我们大概可以做以下功能。

  1. **主题:**界面和文本主题色、图标样式。
  2. 通用功能:添加自定义命令,添加右键菜单,获取文本内容,添加自定义快捷键,自定义代码片段等。
  3. 工作区扩展:活动栏项目,自定义提示,添加左侧菜单栏功能,打开webview等。
  4. ....

开始安装

可以下载官方的脚手架进行快速开发:Github地址 最重要的两个文件为以下两个
image.png
package.json部分关键内容如下:

 // 扩展的激活事件
    "activationEvents": [
        "onCommand:extension.sayHello"
    ],
	// 入口文件
    "main": "./src/extension",
	// 贡献点,vscode插件大部分功能配置都在这里
    "contributes": {
      // 代码片段
        "snippets": [
          {
            "language": "javascript",
            "path": "./snippets/snippets.json"
          },
        ],
        // 自定义命令
        "commands": [
             {
          "command": "template-code.clearCD",
          "title": "🤓清空console/debugger"
          },
        ],
        // 自定义快捷键
       "keybindings": [
        {
          "command": "FolderView.add",
          "key": "ctrl+q",
          "mac": "ctrl+q",
          "when": "editorTextFocus"
        },
        {
          "command": "template-code.clearCD",
          "key": "ctrl+d",
          "mac": "ctrl+d",
          "when": "editorTextFocus"
        }
      ],
    }
}

image.png
image.png
extension.js 主要为添加的命令进行激活,在package.json里配置了命令还不行,必须要激活后才会触发。

const vscode = require('vscode');

/**
 * 插件被激活时触发,所有代码总入口
 * @param {*} context 插件上下文
 */
exports.activate = function(context) {
    console.log('恭喜,您的扩展已被激活!');
    // 注册命令
    let disposableCreateFile = vscode.commands.registerCommand("template-code.createFilePath", async (uri) => {
    createTemplateFile(uri);
  });
  context.subscriptions.push(disposableCreateFile);
};

/**
 * 插件被释放时触发
 */
exports.deactivate = function() {
    console.log('您的扩展已被释放!')
};

实战编码篇

看下效果

Reviews.gif

  1. 首先按下@review 会出现设置好的代码片段。
  2. 在reviewType.时会触发三个类型,Bug 、Perf(更好的)Formate(格式问题)
  3. 选择不同的类型会触发不同的颜色
  4. 高亮review人的名字跟review内容
  5. 跟左侧工作区面板的review列表做联动效果

设置代码片段

在插件里自定义代码片段,首先需要在package.json里定义一个代码片段的字段,指定代码片段的文件地址

    "snippets": [
      {
        "language": "javascript",
        "path": "./snippets/snippets.json"
      },
      {
        "language": "typescriptreact",
        "path": "./snippets/snippets.json"
      },
      {
        "language": "typescript",
        "path": "./snippets/snippets.json"
      }
    ],
  • language代表在哪种语言生效
  • path代表存放的路径

snippets.json

{
  "@review": {
    "prefix": "@review",
    "body": ["/** $CURRENT_YEAR年/$CURRENT_MONTH_NAME/$CURRENT_DATE日/$CURRENT_DAY_NAME", "*@reviewType.Perf", "*@reviewContent By Name", "1.","*/"],
    "description": "review template"
  }
}

这个应该一目了然。

自定义触发列表

在前缀为reviewType. 时会触发三个自定义列表。这个需要vscode以下api

大致的意思在指定的文件类型中,要想触发前缀,可以通过 new vscode.CompletionItem()来为这个语言提供指定的item。

const vscode = require("vscode");
const Decoration = require("./decoration");
/**
 * 自动提示实现,
 * @param {*} document
 * @param {*} position
 * @param {*} context
 */
function provideCompletionItems(document, position, context) {
  const line = document.lineAt(position);
  // 只截取到光标位置为止,防止一些特殊情况
  const lineText = line.text.substring(0, position.character);
  if (/.*@reviewType\.$/g.test(lineText)) {
    console.log("字符串注册", lineText);
    //const json = require(`${projectPath}/package.json`);
    const dependencies = ["Perf", "Format", "Bug"]; //Object.keys(json.dependencies || {}).concat(Object.keys(json.devDependencies || {}));
    return dependencies.map((dep) => {
      // vscode.CompletionItemKind 表示提示的类型
      console.log(dep);
      let Com = new vscode.CompletionItem(dep, vscode.CompletionItemKind.Field);
      Com.command = {
        command: "review.click.item",
        title: "review Type",
      };
      return Com;
    });
  }
}

/**
 * 光标选中当前自动补全item时触发动作,一般情况下无需处理
 * @param {*} item
 * @param {*} token
 */
function resolveCompletionItem(item, token) {
  return null;
}

const TYPES = ["vue", "css", "less", "scss", "sass", "stylus", "javascript", "javascriptreact", "typescriptreact", "typescript"];
module.exports = function (context) {
  // 注册代码建议提示,只有当按下“.”时才触发
  new Decoration();
  TYPES.forEach((el) => {
    let providerDisposable = vscode.languages.registerCompletionItemProvider(
      {
        scheme: "file",
        language: el,
      },
      {
        provideCompletionItems,
        resolveCompletionItem,
      },
      "."
    );
    context.subscriptions.push(providerDisposable);
  });
};

注册完成后就可以实现前缀为 reviewType.时 触发三个自定义的itme啦

实现文字背景高亮

  1. 准备每一个类型对应的颜色数据
  2. 获取当前编辑器的文本内容
  3. 通过正则找到符合要求的文本区域
  4. 拿到符合要求的文本位置信息
  5. 将所有文本的位置信息给他加上背景颜色。


1.首先定一个包含颜色类型的对象,这里可以扩展出去给到用户自己去设置。

/**
 * 每一个主题的所对应的颜色
 */
const DecorationTypes = {
  Perf: {
    border: "1px",
    borderColor: "#08AEEA",
    backgroundColor: "#08AEEA",
    color: "white",
    rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
  },
  Bug: {
    border: "1px",
    borderColor: "#F76B1C",
    backgroundColor: "#F76B1C",
    color: "white",
    rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
  },
  Format: {
    border: "1px",
    borderColor: "#FBAB7E",
    backgroundColor: "#ffcc00",
    color: "#1f1f1f",
    rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
  },
};

2.获取当前编辑里的文本内容:

 // 获取当前文档的全部信息
    const editor = window.activeTextEditor // 当前活动的编辑器
    const doc = editor.document;
    const text = doc.getText()

text就是当前编辑的内容。

3.匹配对应代码片段里的正则为

  this.regType = [/(?<=\*@reviewType)\.(Perf|Bug|Format)?/g, /(?<=\*@reviewContent(\s\S)?)([\S\s]*?)(?=\*\/)/g];

数组的第一项为寻找 类型的正则,数组的第二项为找内容的正则
通过while循环找到每一个符合要求的文本信息,并记录行和列的信息,方便拿到位置信息设置背景

  // 根据正则指定的内容
  findEditeContent() {
    // 获取当前文档的全部信息
    let doc = this.editor.document;
    let text = doc.getText();
    let match;
    let empty = true;
    this.regType.forEach((reg, index) => {
      let type = "Perf"; // 默认类型
      while ((match = reg.exec(text))) {
        // 获取数字开始和结束的位置
        const startPos = doc.positionAt(match.index + 1);
        let endLength = match.index + match[0].length;
        empty = false;
        const endPos = doc.positionAt(endLength);
        const line = new Range(startPos, endPos);
        if (index === 0) type = doc.getText(line);
        const decoration = {
          range: line,
          hoverMessage: "主题颜色" + type,
        };
        if (type) {
          const review = {
            type: type,
            decoration,
          };
          this.reviewDecoration.push(review);
        }
      }
    });
    if (empty) this.reviewDecoration = [];
    console.log("收集的review", this.reviewDecoration);
  }

得到的review数组里的内容:
image.png
主要是拿到range里的信息,才知道该给哪个区域里加背景颜色。

最后就是根据所有的位置信息,去设置背景颜色,需要用到vscode以下api:
image.png
TexTeditor就是上面说的 window.activeTextEditor, 第一个参数需要 vscode.TextEditorDecorationType 类型,那么在上面push到reviewDecoration数组的时候 需要通过 一个循环去遍历一次,为每个item添加decorationType属性,并生成新的一个数组专门用来存放背景的decorationList。

    for (let index = 0; index < this.reviewDecoration.length; index ++) {
     try {
      const element = this.reviewDecoration[index];
      const next = this.reviewDecoration[index + 1];
      const tempObj = {
        type: element.type,
        decoration: [element.decoration, next.decoration],
        decorationType: window.createTextEditorDecorationType(DecorationTypes[element.type]),
      };
      this.decorationList.push(tempObj.decorationType);
      tempArray.push(tempObj);
     } catch (error) {
       console.log(error,'错误');
     }
    }

这里用新的数组decorationList来存放位置信息是因为,每一次文本改动的时候都会去重新找一次,那么就需要在找之前,将上一次设置的背景,给清除掉,这点很重要,不然会导致一个文字区域有多层的背景。

 clearDecorationToData() {
    this.reviewDecoration = [];
    this.decorationList.forEach((el) => {
      this.editor.setDecorations(el, []);
    });
    this.decorationList = [];
  }

  • 清空之前的背景,需要设置一个空数组来完成。


最后在找完之后遍历每一项进行背景的设置。

  setDecorationToData() {
    let tempArray = this.ConversionData();
    tempArray.forEach((el) => {
      this.editor.setDecorations(el.decorationType, el.decoration);
    });
    this.updateTreeReview(tempArray);
  }

这样根据代码片段高亮背景颜色就完成了,还有一个需要跟左侧扩展区联动的效果,篇幅很长,下篇在讲。 后续还扩展了四个实用的小功能,都是对个人或者前端团队适用的,年末了准备晋升的可以拿去用用

总结

  • 设置代码片段的需要在package.json里配置
  • window.activeTextEditor为当前编辑器的对象,可以拿到跟当前文本的所有信息,比如获取选中内容,清空内容等
  • window.createTextEditorDecorationType()创建文本类型装饰器
  • editor.setDecorations()给指定的文本rang范围设置装饰器 第二篇地址