nodejs控制臺打印高亮代碼的實現方法
前言
當代碼運行報錯時,我們會打印錯誤,錯誤中有堆棧信息,可以定位到對應的代碼位置。但有的時候我們希望能夠更直接準確的打印報錯位置的代碼。比如這樣:
這個可以使用 @babel/code-frames 來做到:
const { codeFrameColumns } = require('@babel/code-frame'); const res = codeFrameColumns(code, { start: { line: 2, column: 1 }, end: { line: 3, column: 5 }, }, { highlightCode: true, message: '這裡出錯瞭' }); console.log(res);
有沒有感覺比較神奇,它是怎麼做到的打印出上面的代碼格式的(code frame)?
本文我們就來探究下原理。
主要會解答三個問題:
- 如何打印出標記相應位置代碼的 code frame(就是上圖的打印格式)
- 如何實現語法高亮
- 如何在控制臺打印顏色
如何打印 code frame
我們先不管高亮,實現這樣的格式的打印:
有啥思路沒?
其實也比較容易想到,傳入瞭源代碼、標記開始和結束的行列號,那麼我們就能夠計算出顯示標記(marker)的行是哪些,以及這些行的哪些列,然後依次對每一行代碼做處理,如果本行沒有標記則保持原樣,如果本行有標記的話,那麼就在開始打印一個 marker “>”,並且在下面打印一行 marker “^”,最後一個標記行還要打印錯誤信息。
我們來看一下 @babel/code-frame 的實現:
首先,分割字符串成每一行的數組,然後根據傳入的位置計算出 marker 所在的位置。
比如圖中第二行的第 1 到 12 列,第三行的 0 到 5 列。
然後對每一行做處理,如果本行有標記,則拼成 marker + gutter(行號) + 代碼的格式,下面再打印一行 marker,最後的 marker 行打印 message。沒有標記不處理。
這樣最終拼出的就是這樣的 code frame:
我們實現瞭 code frame 的拼接,暫時忽略瞭高亮,那麼怎麼做語法高亮呢?
如何實現語法高亮
實現語法高亮需要理解代碼,但是如果隻是高亮,詞法分析就足夠瞭。babel 也是這麼做的,@babel/highlight 包裡面完成瞭高亮代碼的邏輯。
先看效果:
const a = 1; const b = 2; console.log(a + b);
上面的源碼被分成瞭 token 數組:
[
[ ‘whitespace’, ‘\n’ ], [ ‘keyword’, ‘const’ ],
[ ‘whitespace’, ‘ ‘ ], [ ‘name’, ‘a’ ],
[ ‘whitespace’, ‘ ‘ ], [ ‘punctuator’, ‘=’ ],
[ ‘whitespace’, ‘ ‘ ], [ ‘number’, ‘1’ ],
[ ‘punctuator’, ‘;’ ], [ ‘whitespace’, ‘\n’ ],
[ ‘keyword’, ‘const’ ], [ ‘whitespace’, ‘ ‘ ],
[ ‘name’, ‘b’ ], [ ‘whitespace’, ‘ ‘ ],
[ ‘punctuator’, ‘=’ ], [ ‘whitespace’, ‘ ‘ ],
[ ‘number’, ‘2’ ], [ ‘punctuator’, ‘;’ ],
[ ‘whitespace’, ‘\n’ ], [ ‘name’, ‘console’ ],
[ ‘punctuator’, ‘.’ ], [ ‘name’, ‘log’ ],
[ ‘bracket’, ‘(‘ ], [ ‘name’, ‘a’ ],
[ ‘whitespace’, ‘ ‘ ], [ ‘punctuator’, ‘+’ ],
[ ‘whitespace’, ‘ ‘ ], [ ‘name’, ‘b’ ],
[ ‘bracket’, ‘)’ ], [ ‘punctuator’, ‘;’ ],
[ ‘whitespace’, ‘\n’ ]
]
token 怎麼分的呢?
一般來說詞法分析就是有限狀態自動機(DFA),但是這裡實現比較簡單,是通過正則匹配的:
js-tokens 這個包暴露出來一個正則,一個函數,正則是用來識別 token 的,其中有很多個分組,而函數裡面是對不同的分組下標返回瞭不同的類型,這樣就能完成 token 的識別和分類。
在 @babel/highlight 包裡也是這樣用的:
(正則來做詞法分析有很多限制條件,比如不能處理遞歸的情況,所以這種方式不是通用的,通用詞法分析還是得用狀態機 DFA。)
有瞭分類之後,不同 token 顯示不同顏色,建立個 map 就行瞭。
@babel/highlight 也是這麼做的:
我們知道瞭怎麼做語法高亮,使用 chalk 的 api 就可以打印顏色,那控制臺打印顏色的原理是什麼呢?
如何在控制臺打印顏色
控制臺打印的是 ASCII 碼,並不是所有的編碼都對應可見字符,ASCII 碼有一部分字符是對應控制字符的,比如 27 是 ESC,就是我們鍵盤上的 ESC 鍵,是 escape 的縮寫,按下它可以完成一些控制功能,這裡我們可以通過打印 ESC 的 ASCII 碼來進入控制打印顏色的狀態。
格式是這樣的:
打印一個 ESC 的 ASCII 碼,之後是 [ 代表開始,m 代表結束,中間是用 ; 分隔的 n 個控制字符,可以控制很多樣式,比如前景色、背景色、加粗、下劃線等等。
ESC 的 ASCII 碼是 27,有好幾種寫法:一種是字符表示的 \e ,一種是 16 進制的 \0x1b(27 對應的 16進制),一種是 8 進制的 \033,這三種都表示 ESC。
我們來試驗一下:1 表示加粗、36 表示前景色為青色、4 表示下劃線,下面三種寫法等價:
\e[36;1;4m \033[36;1;4m \0x1b[36;1;4m
我們來試一下:
圖片都打印瞭正確的樣式!
當然,加瞭樣式還要去掉,可以加一個 \e[0m 就可以瞭(\033[0m,\0x1b[0m 等價)。
chalk(nodejs 的在終端打印顏色的庫)的不同方法就是封裝瞭這些 ASCII 碼的顏色控制字符。
上面每行代碼被高亮過以後的代碼是:
這樣也就實現瞭不同顏色的打印。
總結
至此,我們能實現開頭的效果瞭:支持 code frame 的打印,支持語法高亮,能夠打印顏色
本文我們探究瞭這種效果的實現原理,先從 code frame 是怎麼拼接的,然後每一行的代碼是怎麼做高亮的,之後是高亮具體是怎麼打印顏色的。
不管是 code frame 的打印,還是語法高亮或者控制臺打印顏色,都是特別常見的功能,希望這篇文章能夠幫你徹底掌握這 3 方面的原理。
到此這篇關於nodejs控制臺打印高亮代碼的文章就介紹到這瞭,更多相關nodejs控制臺打印高亮內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 在react中使用highlight.js將頁面上的代碼高亮的方法
- Python利用tkinter和socket實現端口掃描
- 可能是vue中使用axios最詳細教程
- highlight.js 代碼高亮插件的使用詳解
- vue日常開發基礎Axios網絡庫封裝