Theia快捷鍵實現原理(一):快捷鍵注冊

在theia或vscode中都內置了很多快捷鍵朴爬,可以很方便的用于觸發(fā)一個command(命令)扣癣,我們今天來學習下theia快捷鍵的注冊原理惰帽。

使用

任何東西我們在學習其原理之前,都需要先知道怎么使用父虑,不然就會一頭霧水该酗,不知從何看起。

首先我們看下在theia中如何注冊一個快捷鍵士嚎,例如注冊一個剪切的快捷鍵:

registry.registerKeybinding({
    command: CommonCommands.CUT.id,
    keybinding: 'ctrlcmd+x'
});

可以看到注冊快捷鍵的主入口函數是registerKeybinding呜魄,那么我們再來看下這個函數的定義:

/**
 * Register a default keybinding to the registry.
 *
 * Keybindings registered later have higher priority during evaluation.
 *
 * @param binding the keybinding to be registered
 */
registerKeybinding(binding: common.Keybinding): Disposable {
    return this.doRegisterKeybinding(binding);
}

可以看到這里又調用了另一個方法doRegisterKeybinding,在我們繼續(xù)往下看之前莱衩,我們先來看下這個binding參數主要定義了哪些內容爵嗅。

定義

Keybinding的定義如下:

export interface Keybinding {
    /**
     * Unique command identifier of the command to be triggered by this keybinding.
     */
    command: string;
    /**
     * The key sequence for the keybinding as defined in packages/keymaps/README.md.
     */
    keybinding: string;
    /**
     * The optional keybinding context where this binding belongs to.
     * If not specified, then this keybinding context belongs to the NOOP
     * keybinding context.
     *
     * @deprecated use `when` closure instead
     */
    context?: string;
    /**
     * An optional clause defining the condition when the keybinding is active, e.g. based on the current focus.
     * See {@link https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts} for more details.
     */
    when?: string;

    /**
     * Optional arguments that will be passed to the command when it gets triggered via this keybinding.
     * Needs to be specified when the triggered command expects arguments to be passed to the command handler.
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    args?: any;
}

command:表示快捷鍵需要觸發(fā)的命令的id

keybinding:表示快捷鍵的組合方式

context:表示快捷鍵運行的上下文(已經廢棄了,用when取代)

when:表示快捷鍵執(zhí)行的時機笨蚁,與vscode的快捷鍵里的when定義是一樣的睹晒,可以參考https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts

args:傳給command的參數

注冊

接下來我們繼續(xù)看doRegisterKeybinding的實現邏輯:

protected doRegisterKeybinding(binding: common.Keybinding, scope: KeybindingScope = KeybindingScope.DEFAULT): Disposable {
    try {
        this.resolveKeybinding(binding);
        const scoped = Object.assign(binding, { scope });
        this.insertBindingIntoScope(scoped, scope);
        return Disposable.create(() => {
            const index = this.keymaps[scope].indexOf(scoped);
            if (index !== -1) {
                this.keymaps[scope].splice(index, 1);
            }
        });
    } catch (error) {
        this.logger.warn(`Could not register keybinding:\n  ${common.Keybinding.stringify(binding)}\n${error}`);
        return Disposable.NULL;
    }
}

主要邏輯就是根據scopekeymaps中將快捷鍵存儲起來。

keymaps

根據scope生成的用于存儲快捷鍵的二維數組

protected readonly keymaps: ScopedKeybinding[][] = [...Array(KeybindingScope.length)].map(() => []);
scope

scope用于表示快捷鍵的作用域:

export enum KeybindingScope {
    DEFAULT,
    USER,
    WORKSPACE,
    END
}

DEFAULT表示內置的快捷鍵赚窃,USER表示用戶定義的快捷鍵册招,WORKSPACE表示工作區(qū)的快捷鍵岔激,END只用于遍歷勒极。

優(yōu)先級:WORKSPACE > USER > DEFAULT

resolveKeybinding
/**
 * Ensure that the `resolved` property of the given binding is set by calling the KeyboardLayoutService.
 */
resolveKeybinding(binding: ResolvedKeybinding): KeyCode[] {
    if (!binding.resolved) {
        const sequence = KeySequence.parse(binding.keybinding);
        binding.resolved = sequence.map(code => this.keyboardLayoutService.resolveKeyCode(code));
    }
    return binding.resolved;
}

binding.resolved用于緩存解析結果,如果之前解析過則直接返回虑鼎,這函數主要是兩個功能:

  1. 解析keybinding為KeyCode實例
  2. 根據當前鍵盤布局返回KeyCode辱匿,主要是為了兼容其他非標準布局的鍵盤類型

KeyCode用于表示快捷鍵信息键痛,核心代碼如下:

/**
 * Representation of a pressed key combined with key modifiers.
 */
export class KeyCode {

  public readonly key: Key | undefined;
  public readonly ctrl: boolean;
  public readonly shift: boolean;
  public readonly alt: boolean;
  public readonly meta: boolean;
  public readonly character: string | undefined;

  public constructor(schema: KeyCodeSchema) {
      const key = schema.key;
      if (key) {
          if (key.code && key.keyCode && key.easyString) {
              this.key = key as Key;
          } else if (key.code) {
              this.key = Key.getKey(key.code);
          } else if (key.keyCode) {
              this.key = Key.getKey(key.keyCode);
          }
      }
      this.ctrl = !!schema.ctrl;
      this.shift = !!schema.shift;
      this.alt = !!schema.alt;
      this.meta = !!schema.meta;
      this.character = schema.character;
  }
  ...
}
insertBindingIntoScope
/**
 * Ensures that keybindings are inserted in order of increasing length of binding to ensure that if a
 * user triggers a short keybinding (e.g. ctrl+k), the UI won't wait for a longer one (e.g. ctrl+k enter)
 */
protected insertBindingIntoScope(item: common.Keybinding & { scope: KeybindingScope; }, scope: KeybindingScope): void {
    const scopedKeymap = this.keymaps[scope];
    const getNumberOfKeystrokes = (binding: common.Keybinding): number => (binding.keybinding.trim().match(/\s/g)?.length ?? 0) + 1;
    const numberOfKeystrokesInBinding = getNumberOfKeystrokes(item);
    const indexOfFirstItemWithEqualStrokes = scopedKeymap.findIndex(existingBinding => getNumberOfKeystrokes(existingBinding) === numberOfKeystrokesInBinding);
    if (indexOfFirstItemWithEqualStrokes > -1) {
        scopedKeymap.splice(indexOfFirstItemWithEqualStrokes, 0, item);
    } else {
        scopedKeymap.push(item);
    }
}

根據快捷鍵的組合長度來決定插入快捷鍵到keymaps中的位置,長度越大的優(yōu)先級越低例如:如果同時注冊了ctrl+k enter匾七,和ctrl+k絮短,這樣能確保ctrl+k要優(yōu)先執(zhí)行。長度一樣的則插入到該長度序列中的首個位置昨忆,因為越往前優(yōu)先級越高丁频。

概括來說就是:在同一個scope中,前面的優(yōu)先級要大于后面的邑贴,長度短的要大于長度長的席里。

到此就完成了快捷鍵的注冊流程。

總結

整個流程也比較簡單拢驾,就是通過調用registerKeybinding方法奖磁,將binding參數中的keybinding字符串解析為KeyCode實例,并緩存在binding.resolved字段中繁疤,然后再將binding根據scope存入到keymaps數組中咖为。

后續(xù)文章會繼續(xù)講解KeySequence.parse函數,看是如何將字符串解析為KeyCode實例的稠腊,敬請期待躁染。

關注公眾號: 前端家園

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市架忌,隨后出現的幾起案子褐啡,更是在濱河造成了極大的恐慌,老刑警劉巖鳖昌,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件备畦,死亡現場離奇詭異,居然都是意外死亡许昨,警方通過查閱死者的電腦和手機懂盐,發(fā)現死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糕档,“玉大人莉恼,你說我怎么就攤上這事∷倌牵” “怎么了俐银?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長端仰。 經常有香客問我捶惜,道長,這世上最難降的妖魔是什么荔烧? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任吱七,我火速辦了婚禮汽久,結果婚禮上,老公的妹妹穿的比我還像新娘踊餐。我一直安慰自己景醇,他們只是感情好,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布吝岭。 她就那樣靜靜地躺著三痰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窜管。 梳的紋絲不亂的頭發(fā)上酒觅,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音微峰,去河邊找鬼舷丹。 笑死,一個胖子當著我的面吹牛蜓肆,可吹牛的內容都是我干的颜凯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼仗扬,長吁一口氣:“原來是場噩夢啊……” “哼症概!你這毒婦竟也來了?” 一聲冷哼從身側響起早芭,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤彼城,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后退个,有當地人在樹林里發(fā)現了一具尸體募壕,經...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年语盈,在試婚紗的時候發(fā)現自己被綠了舱馅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡刀荒,死狀恐怖代嗤,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情缠借,我是刑警寧澤干毅,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站泼返,受9級特大地震影響硝逢,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一趴捅、第九天 我趴在偏房一處隱蔽的房頂上張望垫毙。 院中可真熱鬧霹疫,春花似錦拱绑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屠阻,卻和暖如春红省,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背国觉。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工吧恃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人麻诀。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓痕寓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蝇闭。 傳聞我的和親對象是個殘疾皇子呻率,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內容

  • 16宿命:用概率思維提高你的勝算 以前的我是風險厭惡者,不喜歡去冒險呻引,但是人生放棄了冒險礼仗,也就放棄了無數的可能。 ...
    yichen大刀閱讀 6,038評論 0 4
  • 公元:2019年11月28日19時42分農歷:二零一九年 十一月 初三日 戌時干支:己亥乙亥己巳甲戌當月節(jié)氣:立冬...
    石放閱讀 6,875評論 0 2
  • 年紀越大逻悠,人的反應就越遲鈍元践,腦子就越不好使,計劃稍有變化童谒,就容易手忙腳亂卢厂,亂了方寸。 “玩壞了”也是如此惠啄,不但會亂...
    玩壞了閱讀 2,127評論 2 1
  • 感動 我在你的眼里的樣子慎恒,就是你的樣子。 相互內化 沒有絕對的善惡 有因必有果 當你以自己的價值觀幸福感去要求其他...
    周粥粥叭閱讀 1,635評論 1 5
  • 昨天考過了阿里規(guī)范撵渡,心里舒坦了好多融柬,敲代碼也猶如神助。早早完成工作回家嘍
    常亞星閱讀 3,033評論 0 1