發現一個小眾但是巨好用的VSCode自動補全外掛:HyperSnips。
作者顯然受到了 這位小哥 的啟發,將 Vim Ultisnips 的大部分功能搬到了VSCode上。並用 JavaScript 重寫了 Python 的部分程式。有了這個外掛,你不需要使用令人望而生畏的Vim,就能愉快地寫 LaTeX/markdown,甚至在數學課上用 markdown/LaTeX 做筆記。
更有趣的是,由於這個外掛具有可程式設計能力,你甚至可以拿到 VSCode 的 API 介面,用這個外掛寫“外掛”,或者幹一些有趣的事情。
你可以實現 這篇文章 中的幾乎所有功能,足以看出其強大:
-
一個自動變大的盒子:
-
可以自動給\(\LaTeX\)的命令加斜槓
\
-
可以自動展開分數(
//
⇨\frac{}{}
、1/
⇨\frac{1}{}
) -
使用字尾片段修改字母樣式(
Ecal
⇨\mathcal{E}
、q_idot
⇨\dot{q_i}
等等);
-
自動生成m*n矩陣:
以上演示使用markdown完成(不是LaTeX,但是LaTeX同樣可以使用該外掛),預覽是Markdown Preview Enhanced外掛。這是非常好用的markdown同步預覽外掛,只是更新有些慢。
使用方法
安裝
- 下載VSCode,在外掛市場安裝 HyperSnips。
注意安裝後右鍵外掛選擇“安裝另一個版本”,安裝0.2.3版本。最新的0.2.4有嚴重的bug,會導致無法使用,暫時不要更新。(2021/12/16)
- 按下 Ctrl+Shift+P ,搜尋
Open Snippets Directory
,開啟存放程式碼片段的資料夾
新建*.hsnips
檔案,如希望在LaTeX補全就建立latex.hsnips
,希望補全markdown就建立markdown.hsnips
。如果希望使用全域性片段,則建立all.hsnips
。
然後就可以輸入片段了。
基本用法
輸入片段的方法基本按照 Vim Ultisnips 的方法,參照幫助文件。這和VSCode自帶的片段是對應的。同樣也可以使用佔位符,可以使用Tab跳來跳去(如$1
表示第一個Tab Stop,$2
第二個,特別的,$0
是最後一個,${1:text}
表示第一個佔位符中有預設的文字)。
舉例:
snippet hello "greeting"
Welcome to ${1:HyperSnips}!
endsnippet
snippet
和endsnippet
標誌了片段的開始和結束,hello是觸發字元。"greeting"是片段名稱(沒什麼用)。第二行就是要展開的片段。如果用 VSCode 內建片段的寫法(json),就是:
{
"greeting":{
"perfix":"hello",
"body":"Welcome to ${1:HyperSnips}!"
}
}
基本的功能都有的。輸入片段(或者其中的幾個字母),按下 Tab ,你可以得到:
自動展開
使用 A 標記,可以自動展開:
snippet hello "greeting" A
Welcome to HyperSnips!
endsnippet
這樣只要輸入觸發字元,整個片段就會立即展開。上面的快速輸入,幾乎都是使用了A
標記。另外還有i
、w
、M
等標記,詳見幫助文件( @infinity1900 翻譯,感謝!):
A:自動展開。預設是按下tab時觸發程式碼段,Flag 設定為 A,程式碼段將在 Trigger 匹配後立即啟用,這對於 regex 正規表示式程式碼段特別有用。
i: 詞內展開*。預設情況下,觸發器僅在前面帶有空格字元時才匹配觸發。如果設定 為 i,則不管前面的字元如何,都會觸發帶有此選項的程式碼段,例如,可以在單詞的中間觸發程式碼段。
w: 單詞邊界*。使用此選項時,觸發器將在單詞邊界處匹配。
M: Multi-line mode - By default, regex matches will only match content on the current line, when this option is enabled the last hsnips.multiLineContext lines will be available for matching.
- 只對非正規表示式有效
內嵌程式碼、動態片段、正則識別
HyperSnips支援在片段中使用JavaScript程式碼。如下面可以返回當前日期:
snippet dategreeting "Gives you the current date!"
Hello from your hsnip at ``rv = new Date().toDateString()``!
endsnippet
其中,JavaScript片段使用四個 ` 括起來,其中rv
表示返回的片段。
- 一開始那個小盒子,是外掛中的示例程式碼:
snippet box "Box" A
``rv = '┌' + '─'.repeat(t[0].length + 2) + '┐'``
│ $1 │
``rv = '└' + '─'.repeat(t[0].length + 2) + '┘'``
endsnippet
同樣支援正規表示式。如“自動數字下標”的片段是這樣寫的:
snippet `(?<=[A-Za-z])(\d)` "auto subscript" iA
_``rv = m[1]``
endsnippet
效果就是:a1
⇨a_1
。m[1]
表示正則識別處的第一個組合。除此之外還有t
變數,用的不多。
- 自動展開分數 會麻煩一些。
//
⇨\frac{}{}
最容易,直接寫就行;
如果要實現1/
⇨\frac{1}{}
:
snippet `((\d+)|(\d*)(\\)?([A-Za-z]+)((\^|_)(\{\d+\}|\d))*)/` "Fraction no ()" A
\frac{``rv = m[1]``}{$1}$0
endsnippet
而如果要實現(1+2)/
⇨\frac{1+2}{}
,可能還需要藉助JavaScript:
snippet `^.*\)/` "Fraction with ()" A
``
let str = m[0];
str = str.slice(0, -1);
let lastIndex = str.length - 1;
let depth = 0;
let i = str.length - 1;
while (true) {
if (str[i] == ')') depth += 1;
if (str[i] == '(') depth -= 1;
if (depth == 0) break;
i -= 1;
}
let results = str.slice(0, i) + "\\frac{" + str.slice(i+1, -1) + "}";
results += "{$1}$0";
rv = results;
``
endsnippet
這和原作者的邏輯是一樣的,但是轉寫成了JavaScript。
全域性程式
在.hsnips
檔案開頭加入如下片段:
global
endglobal
即可在其中定義全域性函式。這裡定義的函式可以在當前檔案中的任何片段中使用。
比如生成矩陣的函式:
// generate matrix
function gen_matrix(nrow, ncol, index) {
let results = "\n";
let order = 1;
for (var i=0; i<nrow; i++){
results += ' ';
for(var j = 0;j <ncol;j++){
results += "$" +(order ).toString() + ((j == ncol -1) ? " \\\\\\" : " & ");
order ++;
}
results += index ? "\n" : "";
}
return results;
}
再使用以下片段:
snippet `(bm|pm|m|vm)at([1-9])([1-9])` "matrix" iA
\begin{``rv = m[1]``atrix}``
rv = gen_matrix(m[2],m[3],1);
``\end{``rv = m[1]``atrix}$0
endsnippet
則bmat33
就能給出一個3*3 的\bmatrix
矩陣:
環境/作用域
有時我們希望片段只有在數學環境中展開,或者只在文字環境中展開。比如,你不希望在正文中寫“green”的時候突然展開成“gr_en"吧(下標自動展開)。HyperSnips提供了這一方法:
在開頭的global 中,定義如下:
function math(context) {
return context.scopes.some(s => s.includes("math"));
}
這個作用是,檢查當前文字的標記和作用域(token & scope),如果其中含有math
,判斷為數學環境並返回true
。然後再我們需要的片段前面加上:
context math(context)
就可以讓片段只在數學環境中展開。如前面片段如果寫成:
context math(context)
snippet // "frac" A
\frac{$1}{$2}
endsnippet
效果:
檢查哪一種環境,可以按下Ctrl+Shift+P ,使用開發人員: 檢查編輯器標記和作用域(Inspect Editor Tokens and Scopes) 命令,得到如下視窗,框中就是作用域(scope)。這個標誌了當前所處的環境。
更多玩法
上述我們提到,HyperSnips支援Javascript,且放在global中的函式可以全域性通用。考慮到VSCode外掛本質上也是JavaScript寫的,那麼一些小外掛能做的事情,HyperSnips為什麼不能做?
在global區域中放置如下程式碼:
const vscode = require("vscode");
var editor=vscode.window.activeTextEditor
var document=editor.document
然後發現,你可以使用 VSCode 的 API 介面了!
可能只需要幾行程式碼,你就能做出一個“外掛”!
好麼,既然能夠程式設計了,那就……沒有什麼好怕的了( ̄︶ ̄*))
- 比如,四行程式碼加在
global
區域中:
setInterval(function(){myTimer()},1000);
function myTimer(){
vscode.window.setStatusBarMessage(new Date().toLocaleTimeString());
}
你就得到了一個在狀態列顯示時間的小外掛!
- 再比如,在global 中加入如下函式
function cycle(arr,str){
let count = 0 ;
while (str!=arr[++count]&&count<arr.length);
return arr[(count+1)%arr.length]
}
然後考慮到分號;在LaTeX中幾乎用不到,而且Tab太容易和其他快捷鍵衝突,於是放一些類似於這樣的片段
snippet `(\\rightarrow|\\Rightarrow|\\longrightarrow|\\Longrightarrow|\\to|\\implies)(| );` "change ⇨" Ai
``
let r=["\\rightarrow","\\Rightarrow","\\longrightarrow","\\Longrightarrow","\\to","\\implies"];
rv=cycle(r,m[1])+m[2];
``
endsnippet
然後有趣的就來了:
按下分號,可以換一個樣式。雖然沒什麼用,但是很好玩啊哈哈哈哈 靈感來自 @張同學
甚至括號也不在話下(當然程式就更長一些):
- 再比如,原生的hyperSnips外掛沒有提供
{VISUAL}
變數(當前選中的文字),不妨利用API寫一個:在global里加上:
// get ActiveTextEditor
vscode.window.onDidChangeActiveTextEditor((e) => {
editor=vscode.window.activeTextEditor;
document=editor.document
});
let selectedText = "";
// get selected text
vscode.window.onDidChangeTextEditorSelection((e) => {
const newSelectedText = e.textEditor.document.getText(e.selections[0]);
if (newSelectedText) {
selectedText = newSelectedText;
}
});
// return text
function VISUAL() {
let sText=selectedText.replace(/\\\\/g,"\\\\\\ ").replace(/\}/g,"\\}");
selectedText="";
return sText;
}
然後比如配置如下的片段:
context math(context)
snippet `Aln` "align" iA
\begin{aligned}
``rv = VISUAL();``$0
\end{aligned}
endsnippet
就可以使用了
儘管有時有bug,但是至少管用啊
所以現在,僅剩的限制就是想象力了吧
Bug
- 當疊加了太多的佔位符時,可能出現多游標。這是VSCode自身的Bug
- 偶爾的環境/作用域識別錯誤,需要重開一下檔案
參考:
-
HyperSnips作者Github:draivin/hsnips
-
@infinity1900 一位師兄的經驗,之前似乎是知乎上唯一一篇關於Hypersnips的介紹,受他的啟發,才發現了這個外掛。
師兄也用JavaScript實現了許多片段,包括上述引用的部分片段。再次表示感謝。infinity1900:【LaTeX 編輯環境搭建】TeX Live+VS code+HyperSnips(for math)外掛
-
@一隻方橙 君的markdown筆記,非常感謝這位同學,提供了大量片段,而且一同折騰了不少奇奇怪怪的HyperSnips使用方法.
一隻方橙:教程向: 在 VSCode 中用 Markdown 做「數字化」學習筆記
- 他的片段配置:
OrangeX4/OrangeX4-HyperSnips - 他之前仿照Hypersnips寫過一個Hypersnips For Math外掛,在原來的外掛上實現了{VISUAL}變數和數學環境支援,但是此後官方也實現了這些。在外掛市場可以很容易搜到這個外掛。
- 他的片段配置:
-
自己的(markdown)配置,有的片段寫得很呆,但也是能用就行。歡迎Star⭐~
歡迎關注、點贊、交流~~~