头图

说在前面

最近突然想录制一些视频来分享一些代码,想在视频里展示代码编写的过程,但是又不想重头开始再敲一遍代码,所以便想着能不能写一个插件来帮我们“敲代码”🙂

效果展示

如上图,看着是和手打一样逐字输入的效果,但其实是通过vscode插件来自动输入的,可以用于视频录制时重现代码实现过程。

插件完整演示视频可以看这里:
https://www.bilibili.com/video/BV1MqVnzdECu/?vd_source=a9f51d5fb9dff1a8879cb3a9794b86e9

插件使用

插件安装

插件目前已经发布到了vscode插件应用商城,可以直接在vscode插件中搜索 code-editing-simulation ,如下图:

安装完插件之后,在文件编辑区域右键打开菜单栏,可以看到模拟代码输入这一个选项,鼠标悬浮可以看到子菜单邮有两个功能。

功能介绍

修改配置

选择子菜单栏中的修改配置选项,可以修改插件的相关配置,具体配置如下:

  • 保存标识

在自动输入的过程中,识别到保存标识的时候会触发保存事件,将文件保存,默认保存标识为:@保存@。 比如使用canvas绘制图像的时候,我们可以在画完一笔的时候保存一下,这时候可以直观的看到每一笔的绘制效果。

  • 打印速度(毫秒/字符)

字符输入的速度,默认是50,即每50毫秒输入一个字符。

模拟代码输入

插件核心功能,模拟代码手打逐字输入的效果,使用方式主要有以下两种:

1、在当前光标所在位置全量插入代码
  • (1)首先需要新建一个txt文件,在文件中准备好需要输入的代码,如下图,我们有一个code.txt文件,里面有几行代码。

  • (2)在需要插入代码的地方右键选择模拟代码输入,也可以直接使用快捷键(Ctrl+Alt+C)

  • (3)在弹出的文件选择框中选择需要插入的代码文件,这里选择我们前面准备的code.txt

  • (4)选择好文件后,插件就会将文件中的代码插入到当前光标的位置中去

2、根据标识符在对应位置插入代码
  • (1)同样需要新建一个txt文件,在文件中准备好需要输入的代码,这里的代码格式需要进行调整,具体格式如下,每一段代码都有一个中括号标签包着:

  • (2)中括号里的标识需要与原代码中的标识一致,对应标签中的代码会被插到对应标识之后,如下图:

  • (3)在需要插入代码的文件中右键选择模拟代码输入,也可以直接使用快捷键(Ctrl+Alt+C)

  • (4)在弹出的文件选择框中选择需要插入的代码文件,这里选择我们前面准备的index.txt

  • (5)选择好文件后,插件就会将文件中每个标识标签中的代码插入到对应标识所在的位置中去

插件实现

在光标所在位置全量插入代码

输入参数

  • code:要输入的代码字符串。
  • saveFlag:保存标记,当遇到该标记时保存文件。
  • printSpeed:输入每个字符的时间间隔(毫秒)。
  • editor:VS Code 的文本编辑器对象。
  • position:开始输入的位置,默认为当前光标位置。

检测保存标记

判断当前遍历的字符是不是保存标记,是保存标记的话就执行保存操作,并跳过标记内容。

const flagLen = saveFlag.length;
if(i <= code.length - flagLen && code.slice(i, i + flagLen) === saveFlag){
    i += flagLen - 1;
    await editor.document.save();
    continue; // 不插入标记内容
}

保持编辑区域可视

当在模拟输入代码的过程中遇到换行符时,需要将光标移动到下一行的行首,并且当行数超过一定数量时,自动滚动编辑器视图,让新的行能够显示在合适的位置。

const newLine = Math.min(position.line + 1, maxLine);
position = new vscode.Position(newLine, 0);
if(newLine > 20){
    const scrollPosition = new vscode.Range(
        Math.max(0, newLine - 20), 
        position.character,
        newLine,
        position.character
    );
    editor.revealRange(
        scrollPosition,
        vscode.TextEditorRevealType.AtTop
    );
}
1. 计算新的行号并移动光标到下一行行首
const newLine = Math.min(position.line + 1, maxLine);
position = new vscode.Position(newLine, 0);
  • position.line 代表当前光标的行号。position.line + 1 表示下一行的行号。
  • maxLine 是编辑器文档的最大行号(editor.document.lineCount - 1)。使用 Math.min(position.line + 1, maxLine) 确保新的行号不会超过文档的最大行号,避免出现越界错误。
  • new vscode.Position(newLine, 0) 创建了一个新的 Position 对象,该对象代表新的光标位置,其中 newLine 是行号,0 表示列号(即行首)。最后将新的位置赋值给 position 变量,从而移动光标到下一行的行首。
2. 判断是否需要滚动编辑器视图
if(newLine > 20) {
    // ...
}

当新的行号 newLine 大于 20 时,说明当前行已经比较靠下,可能需要滚动编辑器视图,以便用户能够看到新输入的内容。

3. 计算滚动范围
const scrollPosition = new vscode.Range(
    Math.max(0, newLine - 20),  // 向上滚动一行
    position.character,
    newLine,
    position.character
);
  • vscode.Range 用于表示编辑器中的一个范围,它由起始位置和结束位置组成。
  • Math.max(0, newLine - 20) 计算滚动范围的起始行号。确保起始行号不会小于 0,避免出现负数行号的错误。这里 newLine - 20 表示从当前行向上数 20 行的位置,作为滚动范围的起始行。
  • position.character 表示列号,由于我们只关注行的滚动,所以起始列号和结束列号都使用当前光标的列号。
  • newLine 是滚动范围的结束行号,即当前行。
4. 滚动编辑器视图
editor.revealRange(
    scrollPosition,
    vscode.TextEditorRevealType.AtTop
);
  • editor.revealRange 是 VS Code 编辑器提供的一个方法,用于将指定的范围滚动到可见区域。
  • scrollPosition 是要滚动到可见区域的范围。
  • vscode.TextEditorRevealType.AtTop 是一个枚举值,表示将指定范围滚动到编辑器视图的顶部,这样可以确保新输入的行显示在视图的顶部,方便用户查看。

在模拟输入代码遇到换行符时,移动光标到下一行行首,并在行数较多时自动滚动编辑器视图,保证能够清晰看到正在输入的内容。

逐字输入代码

const currentLine = editor.document.lineAt(Math.min(position.line,maxLine));

await editor.edit(editBuilder => {
    // 清空光标右侧内容
    const deleteRange = new vscode.Range(
        position,
        currentLine.range.end
    );
    editBuilder.delete(deleteRange);

    // 插入新字符
    editBuilder.insert(position, code[i]);
});
// 更新光标位置
position = position.translate(0, 1);
// 控制输入速度
await new Promise(resolve => 
    setTimeout(resolve, printSpeed));
1. 获取当前行信息
const currentLine = editor.document.lineAt(Math.min(position.line, maxLine));
  • editor.document 代表当前在编辑器中打开的文档。
  • lineAtDocument 对象的一个方法,用于获取指定行号的 TextLine 对象。
  • position.line 是当前光标的行号。
  • maxLine 是文档的最大行号(editor.document.lineCount - 1)。使用 Math.min(position.line, maxLine) 确保传入 lineAt 方法的行号不会超出文档的最大行号,避免出现越界错误。
  • 最终 currentLine 存储的是当前光标所在行的 TextLine 对象,该对象包含了当前行的文本内容、范围等信息。
2. 执行编辑操作
await editor.edit(editBuilder => {
    // ...
});
  • editor.edit 是 VS Code 编辑器提供的一个方法,用于执行一系列的编辑操作。它接受一个回调函数作为参数,在回调函数中可以使用 editBuilder 对象来定义具体的编辑操作。
  • await 关键字用于等待编辑操作完成,确保后续代码在编辑操作执行完毕后再继续执行。
3. 清空光标右侧内容
const deleteRange = new vscode.Range(
    position,
    currentLine.range.end
);
editBuilder.delete(deleteRange);
  • vscode.Range 用于表示编辑器中的一个范围,由起始位置和结束位置组成。
  • position 是当前光标的位置,作为删除范围的起始位置。
  • currentLine.range.end 是当前行的结束位置,作为删除范围的结束位置。
  • editBuilder.delete(deleteRange) 用于删除指定范围内的文本内容,即清空从当前光标位置到当前行末尾的所有内容。
4. 插入新字符
editBuilder.insert(position, code[i]);
  • editBuilder.insert 用于在指定位置插入文本内容。
  • position 是插入的位置,即当前光标的位置。
  • code[i] 是要插入的字符,code 是要输入的代码字符串,i 是当前遍历到的字符索引。
5. 更新光标位置
position = position.translate(0, 1);
  • position.translatePosition 对象的一个方法,用于创建一个新的 Position 对象,该对象相对于当前位置在指定的行和列上进行偏移。
  • 0 表示在行方向上不进行偏移。
  • 1 表示在列方向上向右偏移 1 个位置,即更新光标位置到新插入字符的右侧。
6. 控制输入速度
await new Promise(resolve => 
    setTimeout(resolve, printSpeed));
  • new Promise 用于创建一个 Promise 对象,resolve 是 Promise 的解决函数。
  • setTimeout 是 JavaScript 的一个全局函数,用于在指定的时间后执行一个回调函数。
  • printSpeed 是输入每个字符的时间间隔(毫秒)。
  • await 关键字用于等待 setTimeout 的回调函数执行完毕,从而实现控制输入速度的效果,即每隔 printSpeed 毫秒插入一个字符。

根据标识符在对应位置插入代码

标识标签设计

这里我们使用两层中括号将一段字符串包起来,作为一个标签,两个一样的标签中间包含的代码就是我们要插入的代码。

将带标识标签的文本转为json格式

function stringToObject(inputString: string) {
  const regex = /\[\[([^\]]+)\]\]([\s\S]*?)\[\[\1\]\]/g;
  const result: Record<string, string> = {};
  let match;
  while ((match = regex.exec(inputString))!== null) {
      const key = match[1].trim();
      const value = match[2];
      //去除value开头和结尾的换行符,保留空格
      if(value.startsWith('\n')){
        result[key] = value.slice(1);
      }else{
        result[key] = value;
       }
      if(value.endsWith('\n')){
        result[key] = result[key].slice(0,-1); 
      }
  }
  return result;
}

找到标识所在位置并插入代码

async function typingCodeByKey(key:string,code:string,saveFlag:string,printSpeed:number,editor:vscode.TextEditor){
   //获取key字符串所在的行数
   const lineNum = editor.document.getText().split('\n').findIndex((line) => line.trim() === key);
   if(lineNum === -1){return;}
    let position = new vscode.Position(lineNum, editor.document.lineAt(lineNum).text.length);
    await editor.edit(editBuilder => {
        editBuilder.insert(position, '\n');
    });
    await typingCode(code,saveFlag,printSpeed,editor,position);
}

源码地址

gitee

https://gitee.com/zheng_yongtao/code-editing-simulation.git

github

https://github.com/yongtaozheng/code-editing-simulation.git


  • 🌟 觉得有帮助的可以点个 star~
  • 🖊 有什么问题或错误可以指出,欢迎 pr~
  • 📬 有什么想要实现的功能或想法可以联系我~

公众号

关注公众号『 前端也能这么有趣 』,获取更多有趣内容。

发送 加群 还可以加入群聊,一起来学习(摸鱼)吧~

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

JYeontu
15 声望0 粉丝