在你的 VSCode 里深夜放烟火

9,524 阅读4分钟

我从没有见过极光出现的村落

也没有见过有人 在深夜放烟火

见过有人在 vscode 里深夜放烟火么?

今天我们就一起来放一下:

首先,vscode 是基于 electron 实现的,也就是界面都是网页写的。

而且它有一个 decoration 的 api,可以给 editor 的文本加样式:

参数有 before、after 也就是前后加一个伪元素,还有 border、backgound 等样式。

是不是就和写 css 一样?

因为它本来就是 css。

基于这个 api,我们就可以给某段文本加一段样式。

这就是 vscode 里放烟花的原理。

下面我们来写一下:

全局安装 vscode 插件脚手架:

npm install -g yo generator-code

然后创建 vscode 插件项目:

yo code

点击调试,会出现新的 vscode 窗口,然后输入 hello world 命令,右下角有 hello world 弹窗:

这就说明插件跑成功了。

然后我们开始写具体的逻辑:

在 src/extension.ts 里注册 fireworks 命令:

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {

	const fireworksCommand = vscode.commands.registerCommand('fireworks', () => {
		vscode.window.showInformationMessage('光光光');
	});

	context.subscriptions.push(fireworksCommand);
}

export function deactivate() {}

然后在 package.json 里声明这个命令:

跑起来就是这样的:

然后用下 decaration 的 api:

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {

	const fireworksCommand = vscode.commands.registerCommand('fireworks', () => {
		const editor = vscode.window.activeTextEditor;

		if(editor) {
			const decoration = vscode.window.createTextEditorDecorationType({
				before: {
					contentText: '光光光',
					backgroundColor: 'red'
				},
				border: '10px solid green',
				after: {
					contentText: '东东东',
					backgroundColor: 'blue'
				}
			});

			const position1 = new vscode.Position(1, 2);
			const position2 = new vscode.Position(1, 4);

			const range = new vscode.Range(position1, position2);

			editor.setDecorations(decoration, [range]);
		}
	
	});

	context.subscriptions.push(fireworksCommand);
}

export function deactivate() {}

我们用 vscode.window.activeTextEditor 拿到当前的 editor。

创建 decoration 对象,指定 border 还有 before、after 伪元素的样式。

指定开始和结束的 position,然后给这段 range 添加 decoration。

测试下:

可以看到,执行命令后,第 1 行第 2 到 4 列(vscode 里的行列号是从 0 开始的)的文本被加上了边框了,并且前后加了伪元素。

选中的文本复制之后却没有带上前后伪元素。

这个就是伪元素本身的特性:

比较有意思的一点是样式的值其实可以写任意多个样式:

也就是说,在 vscode 的 editor 里能实现各种用 css 能实现的效果。

那放烟花怎么实现呢?

其实就是用 background-image 加载一个 gif 就好了。

这里我们用内嵌 base64 图片的方式:

创建 src/gifs.ts:

const gifData1= 'xxx';

consg gifData2= 'xxx';

export {
    gifData1,
    gifData2
} 

内容比较多,可以从 github 复制:github.com/QuarkGluonP…

然后设置 background-image:

让元素相对定位,伪元素绝对定位,然后设置宽高、background-image。

let cssStr = `position:absolute;width:100px;height:100px;`
cssStr += `background-image: url(${gifData1});`;
cssStr += 'background-size: contain;';
cssStr+= 'background-repeat: no-repeat;'

const decoration = vscode.window.createTextEditorDecorationType({
        before: {
                contentText: '',
                textDecoration: `none;${cssStr};`
        },
        textDecoration: 'none;position:relative;'
});

效果是这样的:

这样就可以在 vscode 里放烟花了。

当然,现在的烟花位置是固定的,我们可以多加几个,并且位置随机一点:

拿到 editor 的文本,按照换行符分割下:

打个断点,断住以后是这样的:

我们根据这个来计算出在哪里加烟花就好了。

可以随机列、随机行放一个。

封装这样一个方法:

function randomRange(start: number, end: number) {
    return Math.ceil(start + Math.random() * (end - start));
}

随机产生从 xxx 到 yyy 的值。

然后每 10 到 20 行,在那行的随机位置,放一个烟花的装饰,随机从两个 gif 里取一个:

import * as vscode from 'vscode';
import { gifData1, gifData2 } from './gifs';

function randomRange(start: number, end: number) {
	return Math.ceil(start + Math.random() * (end - start));
}

export function activate(context: vscode.ExtensionContext) {

	const fireworksCommand = vscode.commands.registerCommand('fireworks', () => {
		const editor = vscode.window.activeTextEditor;

		if(editor) {
			let cssStr = `position:absolute;width:150px;height:100px;`
			cssStr += 'background-size: contain;';
			cssStr+= 'background-repeat: no-repeat;'

			const text = editor.document.getText();
			const lines = text.split('\n');


			for(let i = 0; i < lines.length; i+= randomRange(10, 20)) {
				const line = lines[i];

				cssStr += `background-image: url(${Math.random() > 0.5 ? gifData1 : gifData2});`;

				const j = Math.random() * lines[i].length;

				const position1 = new vscode.Position(i, j);
				const position2 = new vscode.Position(i, j + 1);
		
				const range = new vscode.Range(position1, position2);

				const decoration = vscode.window.createTextEditorDecorationType({
					before: {
						contentText: '',
						textDecoration: `none;${cssStr};`
					},
					textDecoration: 'none;position:relative;'
				});
	
				editor.setDecorations(decoration, [range]);

			}
		}
	
	});

	context.subscriptions.push(fireworksCommand);
}

export function deactivate() {}

效果是这样的:

放烟火之后如何去掉呢?

其实 decoration 有个 deispose 方法是用来做这个的:

所以我们把 decoration 收集起来,注册个新 command 用来去掉烟花,并且在每次放烟花前把之前的去掉:

import * as vscode from 'vscode';
import { gifData1, gifData2 } from './gifs';

function randomRange(start: number, end: number) {
	return Math.ceil(start + Math.random() * (end - start));
}

let decorations: vscode.TextEditorDecorationType[] = [];

export function activate(context: vscode.ExtensionContext) {
	function disposeAllDecorations() {
		decorations.forEach(item => {
			item.dispose();
		});
		decorations = [];
	}
	const stopFireworksCommand = vscode.commands.registerCommand('stop-fireworks', disposeAllDecorations);

	context.subscriptions.push(stopFireworksCommand);

	const fireworksCommand = vscode.commands.registerCommand('fireworks', () => {
		const editor = vscode.window.activeTextEditor;

		if(editor) {
			let cssStr = `position:absolute;width:150px;height:100px;`
			cssStr += 'background-size: contain;';
			cssStr+= 'background-repeat: no-repeat;'

			const text = editor.document.getText();
			const lines = text.split('\n');

			disposeAllDecorations();
			for(let i = 0; i < lines.length; i+= randomRange(10, 20)) {
				const line = lines[i];

				cssStr += `background-image: url(${Math.random() > 0.5 ? gifData1 : gifData2});`;

				const j = Math.random() * lines[i].length;

				const position1 = new vscode.Position(i, j);
				const position2 = new vscode.Position(i, j + 1);
		
				const range = new vscode.Range(position1, position2);

				const decoration = vscode.window.createTextEditorDecorationType({
					before: {
						contentText: '',
						textDecoration: `none;${cssStr};`
					},
					textDecoration: 'none;position:relative;'
				});
	
				editor.setDecorations(decoration, [range]);

				decorations.push(decoration);
			}
		}
	
	});

	context.subscriptions.push(fireworksCommand);
}

当然,在 package.json 里也得注册下这个 command:

测试下:

确实去掉了。

然后我们加个快捷键:

"keybindings": [
  {
    "command": "fireworks",
    "key": "ctrl+y",
    "mac": "cmd+y"
  },
  {
    "command": "stop-fireworks",
    "key": "ctrl+shift+y",
    "mac": "cmd+shift+y"
  }
]

在 windows 下是 ctrl + y 在 mac 下是 cmd + y。

测试下:

这样就可以方便的放烟花和停止了。

代码上传了 github:github.com/QuarkGluonP…

总结

我们实现了在 vsocde 里放烟花的效果。

vscode 是基于 electron 实现的,看到的界面都是网页,并且 vscode 提供了 decoration 的 api 可以给一段文本加装饰。

这个装饰可以写任意的 css。

我们注册了 fireworks 和 stop-fireworks 两个命令。

在 fireworks 的时候拿到文本,给随机行列加上一个装饰,也就是通过 before 伪元素加上 background-image 设置一个 gif。

然后 stop-fireworks 的时候调用 dispose 去掉装饰。

这样,深夜里就可以在 vscode 里自己放烟火了。