VSCode插件開發:LaTeX Snippets


新博客地址:https://gyrojeff.top,歡迎訪問! 本文為博客自動同步文章,為了更好的閱讀體驗,建議您移步至我的博客👇

本文標題:VSCode插件開發:LaTeX Snippets

文章作者:gyro永不抽風

發布時間:2020年03月04日 - 18:03

最后更新:2020年09月15日 - 07:09

原始鏈接:http://hexo.gyrojeff.moe/2020/03/04/VSCode%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%EF%BC%9ALaTeX-Snippets/

許可協議: 署名-非商業性使用-相同方式共享 4.0 國際 (CC BY-NC-SA 4.0) 轉載請保留原文鏈接及作者!

除夕夜的靜謐 - VSCode插件開發:LaTeX Snippets

寫在前面

這個項目是在20年春節前夕想到的。那時候正在需要非常頻繁地鍵入數學公式,一開始使用的編輯器是Typora,但是越到后面,基礎功能已經不再能滿足需求。我開發出了一套Keyboard Maestro的快捷鍵來加快寫作速度,但是不同模式的頻繁切換反而降低了效率,再加之markdown中LaTeX的限制,便轉身投靠了MacTeX。本想要借助某位國外大神的LaTeX+Vim+Snippets的設計進行自動化寫作,但是在Mac環境下的配置失敗而引發的一系列慘劇讓我終止了這個計划。於是我便使用了VSCode+LaTeX+Plugin的方案。但是很多我想要的Snippets都沒有而且也不能做到個性化的自定義,我便嘗試編寫一個Plugin達到此功能。於是,我前前后后花了五六天的時間完成了這個項目,在大年初二發布了。P.S. 正巧在前些天看了mcy大佬的博客(關於Linux下Vim+LaTeX的配置),准備過兩天拿手邊的rPi去嘗試一下。

在此,特別感謝對此項目提供幫助的yjc大佬(深夜幫忙查js的bug),wyy大佬(對圖標設計提供的意見),以及wsj、wrl、jdh等GitHub的star。

整個項目的工程文件也在我的Github上:https://github.com/JeffersonQin/VSCode-LaTeX-Snippets

另外特別感謝一篇博客:https://www.cnblogs.com/liuxianan/p/vscode-plugin-overview.html
,其給予了我巨大的幫助

插件下載

此項目已經發布在了marketplace上:https://marketplace.visualstudio.com/items?itemName=JeffersonQin.latex-snippets-jeff
,大家可以下載安裝。

項目概述

此項目主要想要完成的功能是對tex文件編輯下的Snippets做一些擴展,即增加一些自動補全功能。同時,還提供了對函數作圖的圖形界面的功能。

准備工作

首先是開發環境的安裝。通過微軟官方的vscode-generator-code來安裝。

首先保證安裝好了npmvscode,打開終端,運行:

1
npm install -g yo generator-code

以此來安裝generator。

生成項目

打開terminal並定位到目標文件夾,運行:

1
yo code

雖然我們開發的是Snippets插件,但是還會需要用到別的功能,所以選擇的時候選擇開發完整的功能,語言我選了JavaScript(TypeScript也差不多)

配置package.json

項目的大部分設置內容都在package.json當中,我們重點需要對以下內容進行配置:

  • displayname:插件在marketplace上顯示的名稱
  • description:插件描述
  • publisher:發布者
  • categories:種類,這里選的是Snippets
  • actionEvents:哪些時間可以激活擴展,我這里是["*"],意思是全局激活
  • main:插件的主入口(一般不需要修改)
  • contributes:插件主要做的一些事,在稍后的部分我們會去修改
  • repository:代碼倉庫,我的配置:
    1
    2
    3
    4
    "repository": {
    "type": "git",
    "url": "https://github.com/JeffersonQin/LaTeX-Snippets-Jeff"
    }
  • homepage:主頁(我是用的是GitHub主頁)
  • icon:圖標路徑(項目中的相對路徑)

編寫Snippets

此項目的本意是要增加一些Snippets,所以我決定先把這個主要工作完成。在項目的根目錄下新建文件夾snippets,並在當中新建文件:latex.json

Snippets就編寫在這個json文件當中,以下為文件的結構:

1
2
3
4
5
6
7
8
9
{
"SNIPPET_NAME": {
"prefix": ["PREFIX_1", "PREFIX_2", ... ],
"body": [
"LINE_1", "LINE_2", ...
],
"description": "DESCRIPTION"
}, ...
}

解釋:

  • SNIPPET_NAME的部分填入Snippet的名稱
  • prefix就是輸入什么才能觸發Snippets的數組
  • body就是代碼片段內容的數組
  • description就是Snippets的描述
  • PREFIX_1等的部分填入各個不同的縮寫
  • LINE_1等部分填入各行內容,順次排列

占位符

復雜的代碼片段會使用到占位符,占位符形如:${1:xxx}, ${2:xxx}, …, 順序按照數字順次排列,每個占位符中的xxx為占位符的實例內容,結束位置的占位符為$0

比如我的LaTeX數學環境的Snippets就是:

1
2
3
4
5
6
7
"Centered Math": {
"prefix": ["mathcentered", "\\mathcentered"],
"body": [
"$$ $0 $$"
],
"description": "Insert centered Math Environment."
}

其中用到了結束占位符。

另外,我還是用到了一種較為特殊的占位符—選擇占位符—顧名思義其可以讓用戶在幾個選項中做出選擇。下面就是我用到的一個Snippet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"Create 2D Plot environment": {
"prefix": ["plotenvironment2d", "\\plotenvironment2d"],
"body": [
"\\begin{tikzpicture}",
"\\begin{axis}[",
"legend pos=outer north east,",
"title=${1:Example},",
"axis lines =${2| box, left, middle, center, right, none|},",
"xlabel = \\$x\\$,",
"ylabel = \\$y\\$,",
"variable = t,",
"trig format plots = rad,",
"]",
"$3",
"\\end{axis}",
"\\end{tikzpicture}$0"
],
"description": "Create a 2DPlot Environment of pgfplots. The style declarations are already included in the snippet."
}

其中 ${2| box, left, middle, center, right, none|}便是指用戶可以在 box, left, middle, center, right, none中做出選擇。

還有一些其他的占位符,包括會用到系統的VARIABLE,正則表達式匹配,等。但是在我的項目中並沒有使用到,這一也不再做介紹了。這個是官方的documentation:https://code.visualstudio.com/docs/editor/userdefinedsnippets

接下來我們將要在package.json中做配置:找到之前提到的”contribute”,在”snippets”中(如果沒有就創建)添加:

1
2
3
4
{
"language": "latex",
"path": "./snippets/latex.json"
}

在我這里長這樣:
1
2
3
4
5
6
7
8
9
"contributes": {
"snippets": [
{
"language": "latex",
"path": "./snippets/latex.json"
}
],
...
}

Snippet功能的測試

點擊頁面左側的測試按鈕(形狀像小蟲子),點擊運行就可以進行測試。測試會在一個行的VSCode頁面進行,標題為擴展開發宿主。我們可以在tex文件中進行測試。

編寫函數繪圖輔助工具

因為學校上AP Calc時,老師經常會比較隨性地畫一些曲線來說明,這就給了我靈感來做一個函數繪圖的輔助工具。過程是這樣的:在平面上點擊了一些點之后,選擇次數,就可以進行多項式擬合。原理也比較簡單,就是解高次方程組,或者可以將問題轉化為Linear Regression。

這里使用VSCode的WebView來編寫此功能(HTML5+CSS3+JS)。

因為代碼量肯定不會小,加之第一次使用JS,生怕翻車,我就上GitHub上找到了一個庫:js-polynomial-regression Github Link.

本來我想按照常規的方法npm安裝然后引用的,但是報錯始終修不了,於是我就將其所有代碼全部放在了js文件當中。

DOM事件

在VSCode中,DOM事件入口:

1
2
3
window.addEventListener('DOMContentLoaded', () => {

}

和頁面相關的內容全部寫在這個接口內。

VSCode與WebView的通訊

這里我使用了封裝好的接口

在網頁的js文件當中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const testMode = false; // 為true時可以在瀏覽器打開不報錯
// vscode webview 網頁和普通網頁的唯一區別:多了一個acquireVsCodeApi方法
const vscode = testMode ? {} : acquireVsCodeApi();

/**
* 調用vscode原生api
* @param data 可以是類似 {cmd: 'xxx', param1: 'xxx'},也可以直接是 cmd 字符串
* @param cb 可選的回調函數
*/
function callVscode(data, cb) {
if (typeof data === 'string') {
data = { cmd: data };
}
if (cb) {
// 時間戳加上5位隨機數
const cbid = Date.now() + '' + Math.round(Math.random() * 100000);
callbacks[cbid] = cb;
data.cbid = cbid;
}
vscode.postMessage(data);
}

在extension.js當中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');
const fs = require('fs');
const path = require('path');
const util = require('./util');
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed

/**
* 從某個HTML文件讀取能被Webview加載的HTML內容
* @param {*} context 上下文
* @param {*} templatePath 相對於插件根目錄的html文件相對路徑
*/
function getWebViewContent(context, templatePath) {
const resourcePath = util.getExtensionFileAbsolutePath(context, templatePath);
const dirPath = path.dirname(resourcePath);
let html = fs.readFileSync(resourcePath, 'utf-8');
// vscode不支持直接加載本地資源,需要替換成其專有路徑格式,這里只是簡單的將樣式和JS的路徑替換
html = html.replace(/(<link.+?href="|<script.+?src="|<img.+?src=")(.+?)"/g, (m, $1, $2) => {
return $1 + vscode.Uri.file(path.resolve(dirPath, $2)).with({ scheme: 'vscode-resource' }).toString() + '"';
});
return html;
}

/**
* 執行回調函數
* @param {*} panel
* @param {*} message
* @param {*} resp
*/
function invokeCallback(panel, message, resp) {
console.log('Invoke call back: ', resp);
// 錯誤碼在400-600之間的,默認彈出錯誤提示
if (typeof resp == 'object' && resp.code && resp.code >= 400 && resp.code < 600) {
util.showError(resp.message || 'Unknown error occurred!');
}
panel.webview.postMessage({cmd: 'vscodeCallback', cbid: message.cbid, data: resp});
}
/**
* 存放所有消息回調函數,根據 message.cmd 來決定調用哪個方法
*/
const messageHandler = {
// 彈出提示
alert(message) {
util.showInfo(message.info);
},
// 顯示錯誤提示
error(message) {
util.showError(message.info);
},
// 獲取工程名
getProjectName(global, message) {
invokeCallback(global.panel, message, util.getProjectName(global.projectPath));
},
openFileInFinder(global, message) {
util.openFileInFinder(`${global.projectPath}/${message.path}`);
// 這里的回調其實是假的,並沒有真正判斷是否成功
invokeCallback(global.panel, message, {code: 0, text: '成功'});
},
openFileInVscode(global, message) {
util.openFileInVscode(`${global.projectPath}/${message.path}`, message.text);
invokeCallback(global.panel, message, {code: 0, text: '成功'});
},
openUrlInBrowser(global, message) {
util.openUrlInBrowser(message.url);
invokeCallback(global.panel, message, {code: 0, text: '成功'});
}
};

/**
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
context.subscriptions.push(vscode.commands.registerCommand('extension.openWebview', function (uri) {
const panel = vscode.window.createWebviewPanel(
'testWebview', // viewType
"Plot", // 視圖標題
vscode.ViewColumn.Beside, // 顯示在編輯器的哪個部位
{
enableScripts: true, // 啟用JS,默認禁用
retainContextWhenHidden: true, // webview被隱藏時保持狀態,避免被重置
}
);
// let global = { projectPath, panel};
panel.webview.html = getWebViewContent(context, 'src/plotDots.html');
panel.webview.onDidReceiveMessage(message => {
// 通訊入口
if (messageHandler[message.cmd]) {
messageHandler[message.cmd](message);
} else {
util.showError(`Invoking method named ${message.cmd} not found!`);
}
}, undefined, context.subscriptions);
}));

}

這里還用到了輔助文件util.js來自文章開頭所引用的博客,確實幫助我們減輕了很多工作量,具體代碼在我的GitHub上可以看到。

在package.json中完成注冊

最終,package.jsoncontributes變成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"contributes": {
"snippets": [
{
"language": "latex",
"path": "./snippets/latex.json"
}
],
"commands": [
{
"title": "LaTeX Plotting Tool",
"command": "extension.openWebview"
}
]
},

發布

發布和打包需要用到vsce

安裝工具

1
npm i vsce -g

創建Azure賬號

可以使用Microsoft賬號或者GitHub賬號登錄:https://aka.ms/SignupAzureDevOps

創建Personal Access Token

點擊頁面上方的人形圖標(右下角有齒輪形狀),找到「Personal Access Tokens」並點擊。創建Personal Access Tokens,注意:

  • name:vsce
  • Organization:All Accessible Organizations
  • Scopes: Full Access
    之后你需要把這個Token記下來,網站是不會保存的。

使用vsce創建新的發布者

1
vsce create-publisher your-publisher-name

其中,your-publisher-name是你要創建的發布者的名字。之后會要求填入:

  • Publisher Human-friendly name: 發布者的顯示名稱
  • E-mail: 郵箱
  • Personal Access Token: 之前的Token

發布

1
vsce publish

注意事項

README.md文件默認會顯示在插件主頁;
README.md中的資源必須全部是HTTPS的,如果是HTTP會發布失敗;
CHANGELOG.md會顯示在變更選項卡;
如果代碼是放在git倉庫並且設置了repository字段,發布前必須先提交git,否則會提示Git working directory not clean;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM