亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解Monaco?Editor中的Keybinding機制

 更新時間:2022年09月30日 10:38:20   作者:袋鼠云數(shù)棧UED團隊  
這篇文章主要為大家介紹了詳解Monaco?Editor中的Keybinding機制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

一、前言

前段時間碰到了一個 Keybinding 相關的問題,于是探究了一番,首先大家可能會有兩個問題:Monaco Editor 是啥?Keybinding 又是啥?

  • Monaco Editor: 微軟開源的一個代碼編輯器,為 VS Code 的編輯器提供支持,Monaco Editor 核心代碼與 VS Code 是共用的(都在 VS Code github 倉庫中)。
  • Keybinding: Monaco Editor 中實現(xiàn)快捷鍵功能的機制(其實準確來說,應該是部分機制),可以使得通過快捷鍵來執(zhí)行操作,例如打開命令面板、切換主題以及編輯器中的一些快捷操作等。

本文主要是針對 Monaco Editor 的 Keybinding 機制進行介紹,由于源碼完整的邏輯比較龐雜,所以本文中的展示的源碼以及流程會有一定的簡化。

文中使用的代碼版本:

Monaco Editor:0.30.1

VS Code:1.62.1

二、舉個??

這里使用 monaco-editor 創(chuàng)建了一個簡單的例子,后文會基于這個例子來進行介紹。

import React, { useRef, useEffect, useState } from "react";
import * as monaco from "monaco-editor";
import { codeText } from "./help";
const Editor = () => {
    const domRef = useRef<HTMLDivElement>(null);
    const [actionDispose, setActionDispose] = useState<monaco.IDisposable>();
    useEffect(() => {
        const editorIns = monaco.editor.create(domRef.current!, {
            value: codeText,
            language: "typescript",
            theme: "vs-dark",
        });
        const action = {
            id: 'test',
            label: 'test',
            precondition: 'isChrome == true',
            keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyL],
            run: () => {
                window.alert('chrome: cmd + k');
            },
        };
        setActionDispose(editorIns.addAction(action));
        editorIns.focus();
        return () => {
            editorIns.dispose();
        };
    }, []);
    const onClick = () => {
        actionDispose?.dispose();
        window.alert('已卸載');
    };
    return (
        <div>
            <div ref={domRef} className='editor-container' />
            <button className='cancel-button' onClick={onClick}>卸載keybinding</button>
        </div>
    );
};
export default Editor;

三、原理機制

1. 概覽

根據(jù)上面的例子,Keybinding 機制的總體流程可以簡單的分為以下幾步:

  • 初始化:主要是初始化服務以及給 dom 添加監(jiān)聽事件
  • 注冊:注冊 keybinding 和 command
  • 執(zhí)行:通過按快捷鍵觸發(fā)執(zhí)行對應的 keybinding 和 command
  • 卸載:清除注冊的 keybinding 和 command

2. 初始化

回到上面例子中創(chuàng)建 editor 的代碼:

const editorIns = monaco.editor.create(domRef.current!, {
    value: codeText,
    language: "typescript",
    theme: "vs-dark",
});

初始化過程如下:

創(chuàng)建 editor 之前會先初始化 services,通過實例化 DynamicStandaloneServices 類創(chuàng)建服務:

let services = new DynamicStandaloneServices(domElement, override);

在 constructor 函數(shù)中會執(zhí)行以下代碼注冊 keybindingService:

let keybindingService = ensure(IKeybindingService, () =>
    this._register(
        new StandaloneKeybindingService(
            contextKeyService,
            commandService,
            telemetryService,
            notificationService,
            logService,
            domElement
        )
    )
);

其中 this._register 方法和 ensure 方法會分別將 StandaloneKeybindingServices 實例保存到 disposable 對象(用于卸載)和 this._serviceCollection 中(用于執(zhí)行過程查找keybinding)。

實例化 StandaloneKeybindingService,在 constructor 函數(shù)中添加 DOM 監(jiān)聽事件:

this._register(
    dom.addDisposableListener(
        domNode,
        dom.EventType.KEY_DOWN,
        (e: KeyboardEvent) => {
            const keyEvent = new StandardKeyboardEvent(e);
            const shouldPreventDefault = this._dispatch(
                keyEvent,
                keyEvent.target
            );
            if (shouldPreventDefault) {
                keyEvent.preventDefault();
                keyEvent.stopPropagation();
            }
        }
    )
);

以上代碼中的 dom.addDisposableListener 方法,會通過 addEventListener 的方式,在 domNode 上添加一個 keydown 事件的監(jiān)聽函數(shù),并且返回一個 DomListener 的實例,該實例包含一個用于移除事件監(jiān)聽的 dispose 方法。然后通過 this._register 方法將 DomListener 的實例保存起來。

3. 注冊 keybindings

回到例子中的代碼:

const action = {
    id: 'test',
    label: 'test',
    precondition: 'isChrome == true',
    keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyL],
    run: () => {
        window.alert('chrome: cmd + k');
    },
};
setActionDispose(editorIns.addAction(action));

注冊過程如下:

當通過 editorIns.addAction 來注冊 keybinding 時,會調(diào)用 StandaloneKeybindingServices 實例的 addDynamicKeybinding 方法來注冊 keybinding。

public addDynamicKeybinding(
    commandId: string,
    _keybinding: number,
    handler: ICommandHandler,
    when: ContextKeyExpression | undefined
): IDisposable {
    const keybinding = createKeybinding(_keybinding, OS);
    const toDispose = new DisposableStore();
    if (keybinding) {
        this._dynamicKeybindings.push({
            keybinding: keybinding.parts,
            command: commandId,
            when: when,
            weight1: 1000,
            weight2: 0,
            extensionId: null,
            isBuiltinExtension: false,
        });
        toDispose.add(
            toDisposable(() => {
                for (let i = 0; i < this._dynamicKeybindings.length; i++) {
                    let kb = this._dynamicKeybindings[i];
                    if (kb.command === commandId) {
                        this._dynamicKeybindings.splice(i, 1);
                        this.updateResolver({
                            source: KeybindingSource.Default,
                        });
                        return;
                    }
                }
            })
        );
    }
    toDispose.add(CommandsRegistry.registerCommand(commandId, handler));
    this.updateResolver({ source: KeybindingSource.Default });
    return toDispose;
}

會先根據(jù)傳入的 _keybinding 創(chuàng)建 keybinding 實例,然后連同 command、when 等其他信息存入_dynamicKeybindings 數(shù)組中,同時會注冊對應的 command,當后面觸發(fā) keybinding 時便執(zhí)行對應的 command。返回的 toDispose 實例則用于取消對應的 keybinding 和 command。

回到上面代碼中創(chuàng)建 keybinding 實例的地方,createKeybinding 方法會根據(jù)傳入的 _keybinding 數(shù)字和 OS 類型得到實例,大致結構如下(已省略部分屬性):

{
    parts: [
        {
            ctrlKey: boolean,
            shiftKey: boolean,
            altKey: boolean,
            metaKey: boolean,
            keyCode: KeyCode,
        }
    ],
}

那么,是怎么通過一個 number 得到所有按鍵信息的呢?往下看↓↓↓

4. key的轉換

先看看一開始傳入的 keybinding 是什么:

const action = {
    id: 'test',
    label: 'test',
    precondition: 'isChrome == true',
    keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyL],
    run: () => {
        window.alert('chrome: cmd + k');
    },
};

傳入的 keybinding 就是上面代碼中的 keybindings 數(shù)組中的元素,monaco.KeyMod.CtrlCmd = 2048,monaco.KeyCode.KeyL = 42,對應的數(shù)字是 monaco-editor 中定義的枚舉值,與真實的 keyCode 存在對應關系。所以注冊時傳入的 keybinding 參數(shù)為: 2048 | 42 = 2090

先簡單了解下 JS 中的位運算(操作的是32位帶符號的二進制整數(shù),下面例子中只用8位簡單表示):

按位與(AND)&

對應的位都為1則返回1,否則返回0

例如:

00001010 // 10

00000110 // 6

------

00000010 // 2

按位或(OR)|

對應的位,只要有一個為1則返回1,否則返回0

00001010 // 10

00000110 // 6

-------

00001110 // 14

左移(Left shift)<<

將二進制數(shù)每一位向左移動指定位數(shù),左側移出的位舍棄,右側補0

00001010 // 10

------- // 10 << 2

00101000 // 40

右移 >>

將二進制數(shù)每位向右移動指定位數(shù),右側移出的位舍棄,左側用原來最左邊的數(shù)補齊

00001010 // 10

------- // 10 >> 2

00000010 // 2

無符號右移 >>>

將二進制數(shù)每位向右移動指定位數(shù),右側移出的位舍棄,左側補0

00001010 // 10

------- // 10 >> 2

00000010 // 2

接下來看下是怎么根據(jù)一個數(shù)字,創(chuàng)建出對應的 keybinding 實例:

export function createKeybinding(keybinding: number, OS: OperatingSystem): Keybinding | null {
    if (keybinding === 0) {
        return null;
    }
    const firstPart = (keybinding & 0x0000FFFF) >>> 0;
    // 處理分兩步的keybinding,例如:shift shift,若無第二部分,則chordPart = 0
    const chordPart = (keybinding & 0xFFFF0000) >>> 16;
    if (chordPart !== 0) {
        return new ChordKeybinding([
            createSimpleKeybinding(firstPart, OS),
            createSimpleKeybinding(chordPart, OS)
        ]);
    }
    return new ChordKeybinding([createSimpleKeybinding(firstPart, OS)]);
}

看下 createSimpleKeybinding 方法做了什么

const enum BinaryKeybindingsMask {
    CtrlCmd = (1 << 11) >>> 0, // 2048
    Shift = (1 << 10) >>> 0,   // 1024
    Alt = (1 << 9) >>> 0,      // 512
    WinCtrl = (1 << 8) >>> 0,  // 256
    KeyCode = 0x000000FF       // 255
}
export function createSimpleKeybinding(keybinding: number, OS: OperatingSystem): SimpleKeybinding {
    const ctrlCmd = (keybinding & BinaryKeybindingsMask.CtrlCmd ? true : false);
    const winCtrl = (keybinding & BinaryKeybindingsMask.WinCtrl ? true : false);
    const ctrlKey = (OS === OperatingSystem.Macintosh ? winCtrl : ctrlCmd);
    const shiftKey = (keybinding & BinaryKeybindingsMask.Shift ? true : false);
    const altKey = (keybinding & BinaryKeybindingsMask.Alt ? true : false);
    const metaKey = (OS === OperatingSystem.Macintosh ? ctrlCmd : winCtrl);
    const keyCode = (keybinding & BinaryKeybindingsMask.KeyCode);
    return new SimpleKeybinding(ctrlKey, shiftKey, altKey, metaKey, keyCode);
}

拿上面的例子:

keybinding = monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyL,即 keybinding = 2048 | 42 = 2090

然后看上面代碼中的:

const ctrlCmd = (keybinding & BinaryKeybindingsMask.CtrlCmd ? true : false);

運算如下:

100000101010 // 2090 -> keybinding

100000000000 // 2048 -> CtrlCmd

----------- // &

100000000000 // 2048 -> CtrlCmd

再看keyCode的運算:

const keyCode = (keybinding & BinaryKeybindingsMask.KeyCode)

100000101010 // 2090 -> keybinding

000011111111 // 255 -> KeyCode

----------- // &

000000101010 // 42 -> KeyL

于是便得到了 ctrlKey,shiftKey,altKey,metaKey,keyCode 這些值,接下來便由這些值生成SimpleKeybinding實例,該實例包含了上面的這些按鍵信息以及一些操作方法。

至此,已經(jīng)完成了 keybinding 的注冊,將 keybinding 實例及相關信息存入了 StandaloneKeybindingService 實例的 _dynamicKeybindings 數(shù)組中,對應的 command 也注冊到了 CommandsRegistry 中。

5.執(zhí)行

當用戶在鍵盤上按下快捷鍵時,便會觸發(fā) keybinding 對應 command 的執(zhí)行,執(zhí)行過程如下:

回到 StandaloneKeybindingServices 初始化的時候,在 domNode 上綁定了 keydown 事件監(jiān)聽函數(shù):

(e: KeyboardEvent) => {
    const keyEvent = new StandardKeyboardEvent(e);
    const shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);
    if (shouldPreventDefault) {
        keyEvent.preventDefault();
        keyEvent.stopPropagation();
    }
};

當 keydown 事件觸發(fā)后,便會執(zhí)行這個監(jiān)聽函數(shù),首先會實例化一個 StandardKeyboardEvent 實例,該實例包含了一些按鍵信息和方法,大致結構如下(已省略部分屬性):

{
    target: HTMLElement,
    ctrlKey: boolean,
    shiftKey: boolean,
    altKey: boolean,
    metaKey: boolean,
    keyCode: KeyCode,
}

其中 keyCode 是經(jīng)過處理后得到的,由原始鍵盤事件的 keyCode 轉換為 monoco-editor 中的 keyCode,轉換過程主要就是兼容一些不同的瀏覽器,并根據(jù)映射關系得到最終的 keyCode。準換方法如下:

function extractKeyCode(e: KeyboardEvent): KeyCode {
    if (e.charCode) {
        // "keypress" events mostly
        let char = String.fromCharCode(e.charCode).toUpperCase();
        return KeyCodeUtils.fromString(char);
    }
    const keyCode = e.keyCode;
    // browser quirks
    if (keyCode === 3) {
        return KeyCode.PauseBreak;
    } else if (browser.isFirefox) {
        if (keyCode === 59) {
            return KeyCode.Semicolon;
        } else if (keyCode === 107) {
            return KeyCode.Equal;
        } else if (keyCode === 109) {
            return KeyCode.Minus;
        } else if (platform.isMacintosh && keyCode === 224) {
            return KeyCode.Meta;
        }
    } else if (browser.isWebKit) {
        if (keyCode === 91) {
            return KeyCode.Meta;
        } else if (platform.isMacintosh && keyCode === 93) {
            // the two meta keys in the Mac have different key codes (91 and 93)
            return KeyCode.Meta;
        } else if (!platform.isMacintosh && keyCode === 92) {
            return KeyCode.Meta;
        }
    }
    // cross browser keycodes:
    return EVENT_KEY_CODE_MAP[keyCode] || KeyCode.Unknown;
}

得到了 keyEvent 實例對象后,便通過 this._dispatch(keyEvent, keyEvent.target) 執(zhí)行。

protected _dispatch(
    e: IKeyboardEvent,
    target: IContextKeyServiceTarget
): boolean {
    return this._doDispatch(
        this.resolveKeyboardEvent(e),
        target,
        /*isSingleModiferChord*/ false
    );
}

直接調(diào)用了 this._doDispatch 方法,通過 this.resolveKeyboardEvent(e) 方法處理傳入的 keyEvent,得到一個包含了許多 keybinding 操作方法的實例。

接下來主要看下 _doDispatch 方法主要干了啥(以下僅展示了部分代碼):

private _doDispatch(
    keybinding: ResolvedKeybinding,
    target: IContextKeyServiceTarget,
    isSingleModiferChord = false
): boolean {
    const resolveResult = this._getResolver().resolve(
        contextValue,
        currentChord,
        firstPart
    );
    if (resolveResult && resolveResult.commandId) {
        if (typeof resolveResult.commandArgs === 'undefined') {
            this._commandService
                .executeCommand(resolveResult.commandId)
                .then(undefined, (err) =>
                    this._notificationService.warn(err)
                );
        } else {
            this._commandService
                .executeCommand(
                    resolveResult.commandId,
                    resolveResult.commandArgs
                )
                .then(undefined, (err) =>
                    this._notificationService.warn(err)
                );
        }
    }
}

主要是找到 keybinding 對應的 command 并執(zhí)行,_getResolver 方法會拿到已注冊的 keybinding,然后通過 resolve 方法找到對應的 keybinding 及 command 信息。而執(zhí)行 command 則會從 CommandsRegistry 中找到對應已注冊的 command,然后執(zhí)行 command 的 handler 函數(shù)(即keybinding 的回調(diào)函數(shù))。

6.卸載

先看看一開始的例子中的代碼:

const onClick = () => {
    actionDispose?.dispose();
    window.alert('已卸載');
};

卸載過程如下:

回到剛開始注冊時:setActionDispose(editorIns.addAction(action)),addAction 方法會返回一個 disposable 對象,setActionDispose 將該對象保存了起來。通過調(diào)用該對象的 dispose 方法:actionDispose.dispose(),便可卸載該 action,對應的 command 和 keybinding 便都會被卸載。

四、結語

對 Monaco Editor 的 Keybinding 機制進行簡單描述,就是通過監(jiān)聽用戶的鍵盤輸入,找到對應注冊的 keybinding 和 command,然后執(zhí)行對應的回調(diào)函數(shù)。但仔細探究的話,每個過程都有很多處理邏輯,本文也只是對其做了一個大體的介紹,實際上還有許多相關的細節(jié)沒有講到,感興趣的同學可以探索探索。

以上就是詳解Monaco Editor中的Keybinding機制的詳細內(nèi)容,更多關于Monaco Editor Keybinding的資料請關注腳本之家其它相關文章!

相關文章

  • AngularJS中$http服務常用的應用及參數(shù)

    AngularJS中$http服務常用的應用及參數(shù)

    大家都知道,AngularJS中的$http有很多參數(shù)和調(diào)用方法,所以本文只記錄了比較常用的應用及參數(shù),方便大家以后使用的時候參考學習,下面一起來看看吧。
    2016-08-08
  • angularjs 中$apply,$digest,$watch詳解

    angularjs 中$apply,$digest,$watch詳解

    這篇文章主要介紹了angularjs 中$apply,$digest,$watch詳解的相關資料,需要的朋友可以參考下
    2016-10-10
  • 基于AngularJs select綁定數(shù)字類型的問題

    基于AngularJs select綁定數(shù)字類型的問題

    今天小編就為大家分享一篇基于AngularJs select綁定數(shù)字類型的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-10-10
  • angularjs請求數(shù)據(jù)的方法示例

    angularjs請求數(shù)據(jù)的方法示例

    這篇文章主要給大家介紹了關于angularjs請求數(shù)據(jù)的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用angularjs具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-08-08
  • Angular(5.2->6.1)升級小結

    Angular(5.2->6.1)升級小結

    今天小編就為大家分享一篇關于Angular(5.2->6.1)升級小結,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • Angular4實現(xiàn)動態(tài)添加刪除表單輸入框功能

    Angular4實現(xiàn)動態(tài)添加刪除表單輸入框功能

    這篇文章主要介紹了Angular4實現(xiàn)動態(tài)添加刪除表單輸入框功能,需要的朋友可以參考下
    2017-08-08
  • 基于Angularjs實現(xiàn)分頁功能

    基于Angularjs實現(xiàn)分頁功能

    這篇文章主要介紹了基于Angularjs實現(xiàn)分頁功能的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-05-05
  • Angular4綁定html內(nèi)容出現(xiàn)警告的處理方法

    Angular4綁定html內(nèi)容出現(xiàn)警告的處理方法

    這篇文章主要給大家介紹了關于Angular4綁定html內(nèi)容出現(xiàn)警告的處理方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2017-11-11
  • 詳解Angular6 熱加載配置方案

    詳解Angular6 熱加載配置方案

    這篇文章主要介紹了詳解Angular6 熱加載配置方案,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • AngularJS ng-repeat指令及Ajax的應用實例分析

    AngularJS ng-repeat指令及Ajax的應用實例分析

    這篇文章主要介紹了AngularJS ng-repeat指令及Ajax的應用,結合實例形式分析了ng-repeat指令的功能及ajax請求交互相關操作技巧,需要的朋友可以參考下
    2017-07-07

最新評論