1. 程式人生 > >VSCode外掛開發全攻略(五)跳轉到定義、自動補全、懸停提示

VSCode外掛開發全攻略(五)跳轉到定義、自動補全、懸停提示

更多文章請戳VSCode外掛開發全攻略系列目錄導航

跳轉到定義

跳轉到定義其實很簡單,通過vscode.languages.registerDefinitionProvider註冊一個provider,這個provider如果返回了new vscode.Location()就表示當前游標所在單詞支援跳轉,並且跳轉到對應location。

為了示例更加有意義,我在這裡寫了一個支援package.jsondependenciesdevDependencies跳轉到對應依賴包的例子jump-to-definition.js(當然我們這裡只是很簡單的實現,沒有考慮特殊情況,直接從node_modules

資料夾下面去找):

W618xH405

程式碼如下:

/**
 * 跳轉到定義示例,本示例支援package.json中dependencies、devDependencies跳轉到對應依賴包。
 */
const vscode = require('vscode');
const path = require('path');
const fs = require('fs');
const util = require('./util');

/**
 * 查詢檔案定義的provider,匹配到了就return一個location,否則不做處理
 * 最終效果是,當按住Ctrl鍵時,如果return了一個location,字串就會變成一個可以點選的連結,否則無任何效果
 * @param {*} document 
 * @param {*} position 
 * @param {*} token 
 */
function provideDefinition(document, position, token) {
    const fileName    = document.fileName;
    const workDir     = path.dirname(fileName);
    const word        = document.getText(document.getWordRangeAtPosition(position));
    const line        = document.lineAt(position);
    const projectPath = util.getProjectPath(document);

    console.log('====== 進入 provideDefinition 方法 ======');
    console.log('fileName: ' + fileName); // 當前檔案完整路徑
    console.log('workDir: ' + workDir); // 當前檔案所在目錄
    console.log('word: ' + word); // 當前游標所在單詞
    console.log('line: ' + line.text); // 當前游標所在行
    console.log('projectPath: ' + projectPath); // 當前工程目錄
    // 只處理package.json檔案
    if (/\/package\.json$/.test(fileName)) {
        console.log(word, line.text);
        const json = document.getText();
        if (new RegExp(`"(dependencies|devDependencies)":\\s*?\\{[\\s\\S]*?${word.replace(/\//g, '\\/')}[\\s\\S]*?\\}`, 'gm').test(json)) {
            let destPath = `${workDir}/node_modules/${word.replace(/"/g, '')}/package.json`;
            if (fs.existsSync(destPath)) {
                // new vscode.Position(0, 0) 表示跳轉到某個檔案的第一行第一列
                return new vscode.Location(vscode.Uri.file(destPath), new vscode.Position(0, 0));
            }
        }
    }
}

module.exports = function(context) {
    // 註冊如何實現跳轉到定義,第一個引數表示僅對json檔案生效
    context.subscriptions.push(vscode.languages.registerDefinitionProvider(['json'], {
        provideDefinition
    }));
};

注意不要忘了修改activationEvents

"activationEvents": [
    "onLanguage:json"
],

new vscode.Location接收2個引數,第一個是要跳轉到檔案的路徑,第二個是跳轉之後游標所在位置,接收Range或者Position物件,Position物件的初始化接收2個引數,行row和列col

高亮顯示範圍

這裡有一個問題我一直沒找到解決方案,如下圖所示:

W944xH278

當按住Ctrl跳轉的時候,雖然可以控制跳轉目標位置,但是卻無法控制高亮顯示的範圍,下圖我本應該讓page/video/list.html全部變成藍色的,但是預設卻只能以單詞為粒度變色,這個問題我找了很久官方文件就是沒找到解決辦法,如果大家有知道的歡迎評論指出。

自動補全

通過vscode.languages.registerCompletionItemProvider方法註冊自動完成實現,接收3個引數:

  • 第一個是要關聯的檔案型別;
  • 第二個是一個物件,裡面必須包含provideCompletionItemsresolveCompletionItem這2個方法;
  • 第三個是一個可選的觸發提示的字元列表;

這裡我們實現這樣一個例子,當輸入this.dependencies.xxx時自動把package.json中的依賴全部帶出來,包括dependenciesdevDependencies,就像這樣:

W1082xH412

實現程式碼如下:

const vscode = require('vscode');
const util = require('./util');

/**
 * 自動提示實現,這裡模擬一個很簡單的操作
 * 當輸入 this.dependencies.xxx時自動把package.json中的依賴帶出來
 * 當然這個例子沒啥實際意義,僅僅是為了演示如何實現功能
 * @param {*} document 
 * @param {*} position 
 * @param {*} token 
 * @param {*} context 
 */
function provideCompletionItems(document, position, token, context) {
    const line        = document.lineAt(position);
    const projectPath = util.getProjectPath(document);

    // 只擷取到游標位置為止,防止一些特殊情況
    const lineText = line.text.substring(0, position.character);
    // 簡單匹配,只要當前游標前的字串為`this.dependencies.`都自動帶出所有的依賴
    if(/(^|=| )\w+\.dependencies\.$/g.test(lineText)) {
        const json = require(`${projectPath}/package.json`);
        const dependencies = Object.keys(json.dependencies || {}).concat(Object.keys(json.devDependencies || {}));
        return dependencies.map(dep => {
            // vscode.CompletionItemKind 表示提示的型別
            return new vscode.CompletionItem(dep, vscode.CompletionItemKind.Field);
        })
    }
}

/**
 * 游標選中當前自動補全item時觸發動作,一般情況下無需處理
 * @param {*} item 
 * @param {*} token 
 */
function resolveCompletionItem(item, token) {
    return null;
}

module.exports = function(context) {
    // 註冊程式碼建議提示,只有當按下“.”時才觸發
    context.subscriptions.push(vscode.languages.registerCompletionItemProvider('javascript', {
        provideCompletionItems,
        resolveCompletionItem
    }, '.'));
};

懸停提示

從上面的跳轉到定義我們可以看到,雖然我們只是定義瞭如何調整,到按住Ctrl鍵但是不點選的時候,vscode預設就會幫我們預覽一部分內容作為提示,除此之外,如果想獲得更多的提示,我們還可以通過vscode.languages.registerHoverProvider命令來實現。

下面我們依然通過package.json中依賴跳轉的例子來演示如何實現一個自定義hover,如下標紅的內容是我們自己實現的,當滑鼠停在package.json的dependencies或者devDependencies時,自動顯示對應包的名稱、版本號和許可協議:

W828xH716

如何實現的呢?也很簡單,我們直接上程式碼:

const vscode = require('vscode');
const path = require('path');
const fs = require('fs');

/**
 * 滑鼠懸停提示,當滑鼠停在package.json的dependencies或者devDependencies時,
 * 自動顯示對應包的名稱、版本號和許可協議
 * @param {*} document 
 * @param {*} position 
 * @param {*} token 
 */
function provideHover(document, position, token) {
    const fileName    = document.fileName;
    const workDir     = path.dirname(fileName);
    const word        = document.getText(document.getWordRangeAtPosition(position));

    if (/\/package\.json$/.test(fileName)) {
        console.log('進入provideHover方法');
        const json = document.getText();
        if (new RegExp(`"(dependencies|devDependencies)":\\s*?\\{[\\s\\S]*?${word.replace(/\//g, '\\/')}[\\s\\S]*?\\}`, 'gm').test(json)) {
            let destPath = `${workDir}/node_modules/${word.replace(/"/g, '')}/package.json`;
            if (fs.existsSync(destPath)) {
                const content = require(destPath);
                console.log('hover已生效');
                // hover內容支援markdown語法
                return new vscode.Hover(`* **名稱**:${content.name}\n* **版本**:${content.version}\n* **許可協議**:${content.license}`);
            }
        }
    }
}

module.exports = function(context) {
    // 註冊滑鼠懸停提示
    context.subscriptions.push(vscode.languages.registerHoverProvider('json', {
        provideHover
    }));
};

有些時候某個欄位可能本身已經有提示內容了,如果我們仍然給它註冊了hover的實現的話,那麼vscode會自動將多個hover內容合併一起顯示。