vscode 高亮 原理

demo思路

  • 需求:語法高亮本質(zhì)是把源文件中的關(guān)鍵字等具有語法意義的特殊字符序列渲染出來。
  • 思路:
  1. 從源文件中把關(guān)鍵字識別出來
  2. 如何渲染識別出來的高亮部分
  • 解決方案
  1. 去識別關(guān)鍵字:直接基于正則掃描 (目前眾包的實現(xiàn)) / 基于AST直接渲染
  2. 基于 html element 的方案
    基于 svg 的方案
    基于 類似 ace editor的編輯器組件,開箱即用,只需要傳遞需要渲染的文本和高亮規(guī)則

高亮

語法高亮由兩部分工作組成:

  • 根據(jù)語法將文本解析成符號和作用域
  • 然后根據(jù)這份作用域映射應(yīng)用對應(yīng)的顏色和樣式

語法高亮其實是有兩種實現(xiàn)方案,

  1. 一種是基于正則,原文直接匹配,匹配的結(jié)果直接替換成富文本(例如帶樣式的html標簽),最終會得到一個關(guān)鍵字被高亮的富文本。
  2. 第二種,源文件調(diào)用paser 處理成AST,然后用AST去渲染,生成富文本。

第一種方案更適合于語法簡單,不包含上下文關(guān)鍵字的情況,例如在c#中這類情況 add , group這類情況在非linq的上下文,是不應(yīng)該被渲染成關(guān)鍵字。
第二種方案可以完美解決這種情況,AST中包含了上下文信息,有助于判斷是否應(yīng)該是關(guān)鍵字的情況

AST

AST(Abstract Syntax Tree 抽象語法樹) , 它是源代碼語法結(jié)構(gòu)的一種抽象標表示,它以樹狀的形式表現(xiàn)編程語言的語法結(jié)構(gòu),樹上的每個節(jié)點都表示源代碼中的一種結(jié)構(gòu)。

.用處
  • 最初是為了實現(xiàn)某種編程語言的編輯器所需要的語法中介
  • 編輯器的錯誤提示,代碼高亮,自動補全,
  • eslint等對代碼風(fēng)格和格式的檢查
  • webpack通過babel轉(zhuǎn)譯js語法
AST 如何生成

編譯器執(zhí)行的第一步是讀取文件中的字符流,然后通過詞法分析生成 token,之后再通過語法分析( Parser )生成 AST,最后生成機器碼執(zhí)行。

  • 分詞:將整個代碼字符串分割成最小語法單元數(shù)組
  • 語法分析:在分詞基礎(chǔ)上建立分析語法單元之間的關(guān)系
詞法分析

詞法分析:也稱之為掃描(scanner),簡單來說就是調(diào)用 next() 方法,一個一個字母的來讀取字符,然后與定義好的關(guān)鍵字符做比較,生成對應(yīng)的Token。Token 是一個不可分割的最小單元:
詞法分析器里,每個關(guān)鍵字是一個 Token ,每個標識符是一個 Token,每個操作符是一個 Token,每個標點符號也都是一個 Token。除此之外,還會過濾掉源程序中的注釋和空白字符(換行符、空格、制表符等。)
最終,整個代碼將被分割進一個tokens列表(或者說一維數(shù)組)。

語法分析

語法分析會將詞法分析出來的 Token 轉(zhuǎn)化成有語法含義的抽象語法樹結(jié)構(gòu)。同時,驗證語法,語法如果有錯的話,拋出語法錯誤。

demo 演示

LSP( LSP (Language Server Protocol))

LSP 是 微軟為解決 IDE 語言服務(wù)和調(diào)試適配器 M x N 問題, 傳統(tǒng)的每個 IDE 都要自行開發(fā)一套某個語言的語言服務(wù)程序和調(diào)試適配器, 而這些語言服務(wù)程序都使用不同的接口, 完全無法復(fù)用, 造成各大 IDE 開發(fā)成本過高的問題.

通俗的講就是語言服務(wù)單獨運行在一個進程里,通過 JSON RPC 作為協(xié)議與客戶端通信,為其提供如跳轉(zhuǎn)定義、自動補全等通用語言功能,例如 ts 的類型檢查、類型跳轉(zhuǎn)、自動補全等都需要有對應(yīng)的 ts 語言服務(wù)端實現(xiàn)并與 Client 端通信。

language-server-protocol.png

使用語言服務(wù)器協(xié)議的語言服務(wù)器。它的實現(xiàn)方式如下:

  • 一個為JS同時提供語言客戶端和語言服務(wù)器的插件
  • 語言客戶端就像普通插件一樣,運行于Node.js插件主機環(huán)境中。這個插件激活后,會啟動另一個進程——語言服務(wù)器,然后兩者通過語言服務(wù)器協(xié)議進行通信。
  • 你懸停到JS代碼上
  • VS Code通知語言客戶端
  • 語言客戶端向語言服務(wù)器發(fā)起請求,索要懸停的返回結(jié)果,最后再送回給VS Code
  • VS Code將結(jié)果展示在懸浮框中

這個過程可能看起來有些復(fù)雜,但是這么做主要有兩個好處:

  • 語言服務(wù)器可以用任何語言實現(xiàn)
  • 語言服務(wù)器可以被多個編輯器重用,提供更加智能的編輯體驗

language server

Language Server翻譯為“語言服務(wù)器”,并不是說它真的是一個服務(wù)器,而是它把語言相關(guān)的特性和功能從IDE中解耦出來,作為一個獨立的程序單獨運行,提供了例如引用查詢(Find All References)等功能的具體實現(xiàn),Client是編輯器或IDE,例如Atom、VScode等。
更加確切的解釋是,Language Server是某語言的Language Server Protocol具體實現(xiàn)。

vscode

Visual Studio Code(簡稱VSCode) 是開源免費的IDE編輯器,原本是微軟內(nèi)部使用的云編輯器(Monaco)。
git倉庫地址: https://github.com/microsoft/vscode
通過Eletron集成了桌面應(yīng)用,可以跨平臺使用,開發(fā)語言主要采用微軟自家的TypeScript。 整個項目結(jié)構(gòu)比較清晰,方便閱讀代碼理解。成為了最流行跨平臺的桌面IDE應(yīng)用
微軟希望VSCode在保持核心輕量級的基礎(chǔ)上,增加項目支持,智能感知,編譯調(diào)試。

  • TypeScript是一種由微軟開發(fā)的自由和開源的編程語言。它是JavaScript的一個超集,而且本質(zhì)上向這個語言添加了可選的靜態(tài)類型和基于類的面向?qū)ο缶幊?/li>
├── build         # gulp編譯構(gòu)建腳本
├── extensions    # 內(nèi)置插件
├── product.json  # App meta信息
├── resources     # 平臺相關(guān)靜態(tài)資源
├── scripts       # 工具腳本,開發(fā)/測試
├── src           # 源碼目錄
└── typings       # 函數(shù)語法補全定義
└── vs
    ├── base        # 通用工具/協(xié)議和UI庫
    │   ├── browser # 基礎(chǔ)UI組件,DOM操作
    │   ├── common  # diff描述,markdown解析器,worker協(xié)議,各種工具函數(shù)
    │   ├── node    # Node工具函數(shù)
    │   ├── parts   # IPC協(xié)議(Electron、Node),quickopen、tree組件
    │   ├── test    # base單測用例
    │   └── worker  # Worker factory和main Worker(運行IDE Core:Monaco)
    ├── code        # VSCode主運行窗口
    ├── editor        # IDE代碼編輯器
    |   ├── browser     # 代碼編輯器核心
    |   ├── common      # 代碼編輯器核心
    |   ├── contrib     # vscode 與獨立 IDE共享的代碼
    |   └── standalone  # 獨立 IDE 獨有的代碼
    ├── platform      # 支持注入服務(wù)和平臺相關(guān)基礎(chǔ)服務(wù)(文件、剪切板、窗體、狀態(tài)欄)
    ├── workbench     # 工作區(qū)UI布局,功能主界面
    │   ├── api              # 
    │   ├── browser          # 
    │   ├── common           # 
    │   ├── contrib          # 
    │   ├── electron-browser # 
    │   ├── services         # 
    │   └── test             # 
    ├── css.build.js  # 用于插件構(gòu)建的CSS loader
    ├── css.js        # CSS loader
    ├── editor        # 對接IDE Core(讀取編輯/交互狀態(tài)),提供命令、上下文菜單、hover、snippet等支持
    ├── loader.js     # AMD loader(用于異步加載AMD模塊)
    ├── nls.build.js  # 用于插件構(gòu)建的NLS loader
    └── nls.js        # NLS(National Language Support)多語言loader

核心層

  • base: 提供通用服務(wù)和構(gòu)建用戶界面
  • platform: 注入服務(wù)和基礎(chǔ)服務(wù)代碼
  • editor: 微軟Monaco編輯器,也可獨立運行使用
  • wrokbench: 配合Monaco并且給viewlets提供框架:如:瀏覽器狀態(tài)欄,菜單欄利用electron實現(xiàn)桌面程序

核心環(huán)境

整個項目完全使用typescript實現(xiàn),electron中運行主進程和渲染進程,使用的api有所不同,所以在core中每個目錄組織也是按照使用的api來安排, 運行的環(huán)境分為幾類:

  • common: 只使用javascritp api的代碼,能在任何環(huán)境下運行
  • browser: 瀏覽器api, 如操作dom; 可以調(diào)用common
  • node: 需要使用node的api,比如文件io操作
  • electron-brower: 渲染進程api, 可以調(diào)用common, brower, node, 依賴electron renderer-process API

vscode事件分發(fā)

src/vs/base/common/event.ts
程序中常見使用once方法進行事件綁定, 給定一個事件,返回一個只觸發(fā)一次的事件,放在匿名函數(shù)返回

export function once<T>(event: Event<T>): Event<T> {
    return (listener, thisArgs = null, disposables?) => {
        // 設(shè)置次變量,防止事件重復(fù)觸發(fā)造成事件污染
        let didFire = false;
        let result: IDisposable;
        result = event(e => {
            if (didFire) {
                return;
            } else if (result) {
                result.dispose();
            } else {
                didFire = true;
            }
            return listener.call(thisArgs, e);
        }, null, disposables);
        if (didFire) {
            result.dispose();
        }
        return result;
    };
}

循環(huán)派發(fā)了所有注冊的事件, 事件會存儲到一個事件隊列,通過fire方法觸發(fā)事件

private _deliveryQueue?: LinkedList<[Listener, T]>;//事件存儲隊列

fire(event: T): void {
    if (this._listeners) {
        // 將所有事件傳入 delivery queue
        // 內(nèi)部/嵌套方式通過emit發(fā)出.
        // this調(diào)用事件驅(qū)動
        if (!this._deliveryQueue) {
            this._deliveryQueue = new LinkedList();
        }
        for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
            this._deliveryQueue.push([e.value, event]);
        }
        while (this._deliveryQueue.size > 0) {
            const [listener, event] = this._deliveryQueue.shift()!;
            try {
                if (typeof listener === 'function') {
                    listener.call(undefined, event);
                } else {
                    listener[0].call(listener[1], event);
                }
            } catch (e) {
                onUnexpectedError(e);
            }
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容