用過谷歌擴展vimium的同窗,確定都知道鍵盤流的好處。另外在 vim 編輯模式下體會光標快速遊走是何等舒服的一件事。這一切,就像農藥裏的韓信,誰用誰知道...接下來,不是講vim,而是一步一步帶領你們carry vscode 擴展開發,最終實現 vscode 中 光標快速遊走。git
能夠在 vscode 擴展安裝中搜索 jumpy 來安裝...github
PS:vscode 擴展推薦使用 Typescript 來開發,一來多一門js超集來強身,二來vscode中的智能提示神馬的都跟 Typescript 有着密切的關係。typescript
Jump源碼分析npm
主要從如下三個方面深刻:編程
cnpm install -g yo generator-code
yo code
複製代碼
兩個比較重要的文件:json
package.json
用於描述你的插件及命令。extension.js
入口文件,用於提供命令代碼。這個文件暴露了一個函數 activate
,當插件激活時執行。經過 registerCommand
註冊命令。F5
會打開一個新窗口來運行你的插件。Ctrl+Shift+P
or Cmd+Shift+P
on Mac)extension.js
文件中打斷點來調試。VS Code 會在我的擴展文件夾中.vscode/extensions
來尋找擴展組件。不一樣的平臺其文件夾所在的位置也不一樣:vim
%USERPROFILE%\.vscode\extensions
~/.vscode/extensions
~/.vscode/extensions
若是你想在 VS Code 每次啓動都可以加載你本身的擴展或者定製化信息,那麼就須要在.vscode/extensions
文件夾下新建一個文件夾,並把項目文件放進去。例如:~/.vscode/extensions/myextension
。api
package.json
詳解package.json
extension manifest 中 contribution
選項的全部可用字段。數組
configuration
選項會被暴露給用戶。用戶可以在「用戶設置」或「工做區設置」面板中設置這些配置選項。commands
提供了一個由 commands 和 title 字段組成的條目,用於在 命令面板 中調用。keybindings
menus
languages
提供一種語言的定義。這會引入一門新的語言或者提高 VS Code 關於一門語言的認知。debuggers
爲 VS Code 設置一個調試器。 調試器能夠具備如下屬性。grammars
設置一種語言的 TextMate 語法。您必須提供此語法適用的 language 字段,語法和文件路徑的 TextMate scopeName。themes
snippets
爲某一個具體的語言提供代碼片斷。jsonValidation
爲特定類型的 json 文件貢獻一個驗證模式。 url 值能夠是包含在擴展中的模式文件的本地路徑,也能夠是遠程服務器 URL(如 json 模式存儲)。提供 如下 activation events:bash
onLanguage:${language}
指定 Language 後觸發onCommand:${command}
指定 命令執行後觸發workspaceContains:${toplevelfilename}
指定文件打開後觸發*
vscode 啓動後觸發(慎用)插件包含了如下組件的支持:
API 按命名空間組織,全局命名空間以下:
commands
執行/註冊命令,IDE 自身的和其它插件註冊的命令均可以,如 executeCommanddebug
調試相關 API,好比 startDebuggingenv
IDE 相關的環境信息,好比 machineId, sessionIdextensions
跨插件 API 調用,extensionDependency 聲明插件依賴languages
編程語言相關 API,如 createDiagnosticCollection, registerDocumentFormattingEditProviderscm
源碼版本控制 API,如 createSourceControlwindow
編輯器窗體相關 API,如 onDidChangeTextEditorSelection, createTerminal, showTextDocumentworkspace
工做空間級 API(打開了文件夾纔有工做空間),如 findFiles, openTextDocument, saveAll本次jump源碼中涉及的Typescript知識點並很少,主要是添加了類型檢查的功能。其他跟Es6相似的功能這裏不作延伸。
let isDone:boolean = false
let decLiteral:number = 6
let name:string = "bob"
數組 有兩種方式能夠定義數組
let list:number[] = [1,2,3]
let list:Array<number> = [1,2,3]
元組 Tuple
容許表示一個已知元素數量和類型的數組,各元素的類型沒必要相同 let x:[string,number]
當訪問一個越界的元素,會使用聯合類型替代。x[3] = 'world'
字符串能夠賦值給 (string|number) 類型
enum Color {Red,Green,Blue}
枚舉類型供的一個便利是你能夠由枚舉的值獲得它的名字。例如,咱們知道數值 2,可是不肯定它隱射到 Color 裏的哪一個名字。
enum Color {
Red = 1,
Green,
Blue
}
let colorName: string = Color[2];
複製代碼
這些值可能來自於動態的內容 let notSure:any = 4
Object 有類似的做用,可是 Object 類型的變量只是容許你給它賦任意值,可是卻不可以在它上面調用任意的方法,即使它真的有這些方法
let notSoure: any = 4;
notSoure.toFixed(); // ok
let prettySure: Object = 4;
prettySure.toFixed(); // Error
複製代碼
當你只知道一部分數據的類型時,any 類型也是有用的。
let list: any[] = [1, true, 'free'];
list[1] = 100;
複製代碼
類型與 any 類型相反,它表示沒有任何類型。當一個函數沒有返回值時,你一般會見到其返回值類型時 void
function warnUser(): void {
alert('This is my warning message');
}
複製代碼
聲明一個 void 類型的變量沒有什麼大用,由於你只能爲它賦予 undefined 和 null
Typescript 裏,undefined 和 null 二者各自有本身的類型分別叫作 undefined 和 null。和 void 類似,它們的自己的類型用處不是很大
默認狀況下 null 和 undefined 是全部類型的子類型。就是說你能夠把 null 和 undefined 賦值給 number 類型的變量。
Never 類型 表示的是那些永不存在的值得類型。例如,never 類型 是哪些老是會拋出異常或者根本就不會有返回值的函數表達式或箭頭函數表達式的返回值類型。never 類型是任何類型的子類型,也能夠賦值給任何類型,即便 any 也不能夠賦值給 never。
function error(message: string): never {
throw new Error(message);
}
複製代碼
類型斷言有兩種形式,其一是"尖括號"語法:
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;
複製代碼
另外一個爲 as 語法:
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;
複製代碼
兩種形式是等價的。至於使用哪一個大多數狀況下是憑我的喜愛;然而,當你在 typescript 裏使用 JSX 時,只有 as 語法斷言是被容許的。
Typescript 的核心原則之一是對值所具備的結構進行類型檢查。它有時被稱作"鴨式辯型法"或者"結構性子類型化"。在 TypeScript 裏,接口的做用就是爲這些類型命名和爲你的代碼或第三方代碼定義契約。
接口初探
下面經過一個簡單示例來觀察接口是如何工做的。
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: 'size 10 Object' };
printLabel(myObj);
複製代碼
類型檢查器會查看 printLabel 的調用。printLabe 有一個參數,並要求這個對象參數有一個名爲 label 類型爲 string 的屬性。
須要注意的是,咱們傳入的對象參數實際上會包含不少屬性,可是編譯器只會檢查那些必需的屬性是否存在,而且其類型是否匹配然而,有些時候 Typescript 卻並不會這麼寬鬆
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: 'size 10 Object' };
printLabel(myobj);
複製代碼
"option bags"例子
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
let newSquare = { color: 'white', area: 100 };
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({ color: 'black' });
複製代碼
帶有可選屬性的接口與普通的接口定義差很少,只是在可選屬性名字定義的後面加了一個?符號。
可選屬性的好處之一是能夠對可能存在的屬性進行預約義,好處之二是能夠捕獲引用了不存在的屬性時的錯誤。
一些對象屬性只能在對象剛剛建立的時候修改其值。你能夠在屬性名前用 readonly
來指定只讀屬性
interface Point {
readonly x: number;
readonly y: number;
}
複製代碼
你能夠經過賦值一個對象字面量來構造一個 Point。賦值後,x 和 y 不再能改變了。
let p1: Point = { x: 10, y: 20 };
p1.x = 5;
複製代碼
TypeScript 具備 ReadonlyArray類型,它與 Array類似,只是把全部可變方法去掉了,所以能夠確保數組建立後不再能被修改。
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
複製代碼
就算把整個 ReadonlyArray 賦值到一個普通數組也是不能夠的。可是你能夠用類型斷言重寫
a = ro as number[]
最簡單判斷改用 readonly 仍是 const 的方法是看要把它做爲變量使用仍是做爲一個屬性。做爲變量使用的話用 const,若做爲屬性則使用 readonly
爲了使用接口表示函數類型,咱們須要給接口定義一個調用簽名。它就像是一個只有參數列表和返回值類型的函數定義。參數列表裏的每一個參數都須要名字和類型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
複製代碼
與使用接口描述函數類型差很少,咱們也能夠描述那些可以"經過索引獲得"的類型
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ['Bob', 'Fred'];
let myStr: string = myArray[0];
複製代碼
jump 主要用於光標定位跳轉,步驟以下:
一、生成由字母 a-z
組合的標識符
const numCharCodes = 26;
const codeArray = createCodeArray();
function createCodeArray(): string[] {
const codeArray = new Array(numCharCodes * numCharCodes);
let codeIndex = 0;
for (let i = 0; i < numCharCodes; i++) {
for (let j = 0; j < numCharCodes; j++) {
codeArray[codeIndex++] =
String.fromCharCode(97 + i) + String.fromCharCode(97 + j);
}
}
return codeArray;
}
複製代碼
二、轉爲可用於在編輯器上展現的 svg 圖標,分別有黑白兩個主題樣式。
let darkDataUriCache: { [index: string]: vscode.Uri } = {};
let lightDataUriCache: { [index: string]: vscode.Uri } = {};
createDataUriCaches(codeArray);
function createDataUriCaches(codeArray: string[]) {
codeArray.forEach(
code => (darkDataUriCache[code] = getSvgDataUri(code, 'white', 'black'))
);
codeArray.forEach(
code => (lightDataUriCache[code] = getSvgDataUri(code, 'black', 'white'))
);
}
function getSvgDataUri( code: string, backgroundColor: string, fontColor: string ) {
const width = code.length * 7;
return vscode.Uri.parse(
`data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} 13" height="13" width="${width}"><rect width="${width}" height="13" rx="2" ry="2" style="fill: ${backgroundColor};"></rect><text font-family="Consolas" font-size="11px" fill="${fontColor}" x="1" y="10">${code}</text></svg>`
);
}
複製代碼
在以前的基礎上,已經有對應的 svg URI 數組了。接下來實現如何把這些標識符顯示在編輯器上。
首先在 vscode 中,打開的編輯器稱爲 window.activeTextEditor
,該實例有個用於修改編輯器修飾的方法 setDecorations
。
const editor = window.activeTextEditor;
const decorationType = window.createTextEditorDecorationType({});
const decorations = [
{
range: new vscode.Range(line, startCharacter, line, endCharacter),
renderOptions: {
dark: {
before: {
contentIconPath: darkDataUriCache[code]
}
},
light: {
before: {
contentIconPath: lightDataUriCache[code]
}
}
}
}
];
editor.setDecorations(decorationType, decorations);
複製代碼
vscode 文檔中這麼 描述 [setDecorations](https://code.visualstudio.com/docs/extensionAPI/vscode-api#_commands)
Adds a set of decorations to the text editor. If a set of decorations already exists with the given decoration type, they will be replaced.
第二個參數比較重要的 選項 range
A range represents an ordered pair of two positions. It is guaranteed that start.isBeforeOrEqual(end)
Range objects are immutable. Use the with, intersection, or union methods to derive new ranges from an existing range.
表示在哪一個區域添加什麼修飾。這裏的做用是在找出匹配後的位置,在contentIconPath
添加帶有字符組合 svg 圖標。這裏會用到兩個方法 getLines
和 getPosition
分別獲取new vscode.Range(line, startCharacter, line, endCharacter)
中對應的參數值。
function getPosition( maxDecorations: number, firstLineNumber: number, lines: string[], regexp: RegExp ): JumpPosition[] {
let positionIndex = 0;
const positions: JumpPosition[] = [];
// 獲取匹配後的字符所在 的位置,包含 行和字符所在位置。
// 能夠想象成 距離編輯器左上角 x ,y 座標
for (let i = 0; i < lines.length && positionIndex < maxDecorations; i++) {
let lineText = lines[i];
let word: RegExpExecArray;
while (!!(word = regexp.exec(lineText)) && positionIndex < maxDecorations) {
positions.push({
line: i + firstLineNumber,
character: word.index
});
}
}
return positions;
}
// 因爲編輯存在滾動條,這裏獲取的行主要用於表示從 哪一個行開始展現 修飾標識符
function getLines( editor: vscode.TextEditor ): { firstLineNumber: number; lines: string[] } {
const document = editor.document;
const activePosition = editor.selection.active;
const startLine =
activePosition.line < plusMinusLines
? 0
: activePosition.line - plusMinusLines;
const endLine =
document.lineCount - activePosition.line < plusMinusLines
? document.lineCount
: activePosition.line + plusMinusLines;
const lines: string[] = [];
for (let i = startLine; i < endLine; i++) {
lines.push(document.lineAt(i).text);
}
return {
firstLineNumber: startLine,
lines
};
}
複製代碼
至此 字符匹配後生產對應的標識符 已實現。
交互流程:標識符出現後,鍵入對應的字符組合後光標跳轉到對應位置。
須要作兩件事。
一、啓動 jump 模式,攔截編輯器 type 輸入 二、鍵入對應字母組合後跳轉至對應位置
let isJumpMode: boolean = false;
let firstKeyOfCode: string = null;
// 模式設置方法
function setJumpMode(value: boolean) {
isJumpMode = value;
commands.executeCommand('setContext', 'jump.isJumpMode', value);
}
// 在執行jump命令後,開啓jump模式
setJumpMode(true);
// 註冊 type 命令來修改 type 後的邏輯,type的意思是在編輯器中輸入字符
const jumpTypeDisposable = commands.registerCommand('type', args => {
if (!isJumpMode) {
// 若是不是 jump 模式,恢復 type 默認模式
commands.executeCommand('default:type', args);
return;
}
const editor = window.activeTextEditor;
const text: string = args.text;
// 若是鍵入的字符 不是 以前 字母組合中的 則退出 jump 模式。
if (text.search(/[a-z]/i) === -1) {
exitJumpMode();
return;
}
// 因爲 標識符 都是 兩個字母,因此須要記錄第一個字母
if (!firstKeyOfCode) {
firstKeyOfCode = text;
return;
}
const code = firstKeyOfCode + text;
const position = positions[getCodeIndex(code.toLowerCase())];
const { line, character } = position;
// 清空編輯器上的 字母組合標識符
editor.setDecorations(decorationType, []);
// 經過 selection 來實現光標的移動。
window.activeTextEditor.selection = new Selection(
line,
character,
line,
character
);
const reviewType: TextEditorRevealType = TextEditorRevealType.Default;
window.activeTextEditor.revealRange(
window.activeTextEditor.selection,
reviewType
);
setJumpMode(false);
});
複製代碼
至此一個簡單的光標快速跳轉插件的核心代碼已經講解完畢,謝謝!