自制一個自動導入( auto import )的vscode插件

遇到的問題

使用vue時,每次導入組件都十分的麻煩,得先寫明組件標籤,然後在script標籤中import導入,在components中顯式聲明。。。遇到這種勞動重複性問題時,我都會想是否能用腳本完成?有幸使用了vscode,可以自定義打造我們的自動導入插件

開始

使用yo code初始化項目。百度上關於初始化項目一大堆,不重點說了。。

自動提示組件名稱

第一步就是要先能自動提示組件名稱,vscode中關於顯示建議的核心API是( 看API直接ctrl+左鍵點擊const vscode = require('vscode') 即可,d.ts描述文件比官網API文檔都詳細 )

export function registerCompletionItemProvider(selector: DocumentSelector, provider: CompletionItemProvider, ...triggerCharacters: string[]): Disposable;

其中: CompletionItemProvider包含provideCompletionItemsresolveCompletionItem.

provideCompletionItems返回選項集合(暫時這麼叫吧),resolveCompletionItem具體處理你選擇選項時的事件

根目錄下新建autotip.js文件, 具體用法見註釋

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

module.exports = function (context) {
  let rangeline = 0
  let rangeCol = 0

  /**
   * 只支持單文件夾!
   * @param {*} document 
   * @param {*} position 
   */
  function provideCompletionItems(document, position) {
    let arr = [];
    // 遞歸尋找組件名
    let readFile = (fileDir, filter) => {
      let filearr = fs.readdirSync(fileDir)
      filearr.forEach((v) => {
        if (fs.statSync(fileDir + '/' + v).isDirectory()) {
          readFile(fileDir + '/' + v, filter)
        } else {
          if (v.indexOf(filter) !== -1 && v.substring(v.lastIndexOf('.') + 1) === 'vue') {
            arr.push(v.substring(0, v.lastIndexOf('.')))
          }
        }
      })
    }

    // 找到光標所在的一行
    const line = document.lineAt(position);
    const lineText = line.text.substring(0, position.character);

    rangeline = position.line
    rangeCol = position.character

    let workspaceFolders = vscode.workspace.workspaceFolders.map(item => item.uri.path);
    // 找到根目錄
    let rootpath = workspaceFolders[0];
    // 遞歸尋找src目錄下的vue文件
    readFile(rootpath.substring(1) + '/src', lineText.substring(lineText.indexOf('<') + 1));
    // 返回所有vue文件
    return arr.map((v) => {
      return new vscode.CompletionItem(v, vscode.CompletionItemKind.Field);
    })
  }


  function resolveCompletionItem(item) {
    // 把vue文件名處理成xxx-xxx的形式
    let content = item.label[0].toLowerCase() + item.label.substring(1)
    content = content.replace(/[A-Z]/g, (text) => {
      return `-${text.toLowerCase()}`
    })
    item.insertText = content
    return item;
  }

  context.subscriptions.push(vscode.languages.registerCompletionItemProvider('vue', {
    provideCompletionItems,
    resolveCompletionItem
  }, '<')); // 觸發建議的按鍵
};

完成後,在extension.js中引入

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');
const autotip = require('./autptip')

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
function activate(context) {

    // Use the console to output diagnostic information (console.log) and errors (console.error)
    // This line of code will only be executed once when your extension is activated
    console.log('Congratulations, your extension "autotip" is now active!');
    // The command has been defined in the package.json file
    // Now provide the implementation of the command with  registerCommand
    // The commandId parameter must match the command field in package.json

    // 引入
    autotip(context)
}
exports.activate = activate;

// this method is called when your extension is deactivated
function deactivate() {
}
exports.deactivate = deactivate;

package.json中註冊

 "activationEvents": [
        "onLanguage:vue"
 ],

現在你可以按F5測試,是否生成了建議列表

接受建議時觸發動作

按下tab或者enter時觸發以下動作:

  1. 在script標籤中寫入 import xxx from 'xxxx'模板
  2. 在components對象中寫入引用

萬變不離其中,都是用nodejs的IO文件流完成工作

找到script標籤所在位置

先寫幾個通用方法

// 找到過濾文件
let readFile = (fileDir, filter) => {
      let filearr = fs.readdirSync(fileDir)
      filearr.forEach((v) => {
        if (fs.statSync(fileDir + '/' + v).isDirectory()) {
          readFile(fileDir + '/' + v, filter)
        } else {
          let reg = new RegExp(filter, 'i'); // 忽略大小寫
          if (reg.test(v) && v.substring(v.lastIndexOf('.') + 1) === 'vue' && v.length === filter.length + 4) {
            obj.name = v.substring(0, v.lastIndexOf('.'))
            obj.path = fileDir + '/' + v.substring(0, v.lastIndexOf('.'))
          }
        }
      })
    }
// 找到某個字符串在編輯器中的行號
    let getLineNumber = (filter) => {
      let scriptline = -1;
      for (let i = 0; i < document.lineCount; i++) {
        let str = document.lineAt(i).text;
        if (str.trim().indexOf(filter) !== -1) {
          scriptline = i;
          break;
        }
      }
      return scriptline;
    }

找到script標籤的位置如下:

    let scriptline = getLineNumber('<script>');

完成文件模板寫入

complementtag.js具體代碼

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

module.exports = function (context) {
  let rangeline = 0
  let rangeCol = 0

  function provideCompletionItems(document, position) {
    let obj = {};
    let readFile = (fileDir, filter) => {
      let filearr = fs.readdirSync(fileDir)
      filearr.forEach((v) => {
        if (fs.statSync(fileDir + '/' + v).isDirectory()) {
          readFile(fileDir + '/' + v, filter)
        } else {
          let reg = new RegExp(filter, 'i');
          if (reg.test(v) && v.substring(v.lastIndexOf('.') + 1) === 'vue' && v.length === filter.length + 4) {
            obj.name = v.substring(0, v.lastIndexOf('.'))
            obj.path = fileDir + '/' + v.substring(0, v.lastIndexOf('.'))
          }
        }
      })
    }

    let getLineNumber = (filter) => {
      let scriptline = -1;
      for (let i = 0; i < document.lineCount; i++) {
        let str = document.lineAt(i).text;
        if (str.trim().indexOf(filter) !== -1) {
          scriptline = i;
          break;
        }
      }
      return scriptline;
    }

    rangeline = position.line
    rangeCol = position.character

    let item = document.getText(new vscode.Range(new vscode.Position(rangeline, 0), new vscode.Position(rangeline, rangeCol)));
    item = item.trim()
    item = item.substring(1, item.length - 1)
    item = item.replace(/-/g, '')

    let workspaceFolders = vscode.workspace.workspaceFolders.map(item => item.uri.path);
    let rootpath = workspaceFolders[0];

    readFile(rootpath.substring(1) + '/src', item)

    let scriptline = getLineNumber('<script>');

    // 完成import部分
    vscode.window.activeTextEditor.edit(editBuilder => {
      const text = `import ${obj.name} from '${obj.path.substring(obj.path.indexOf('src'))}'`
      editBuilder.insert(new vscode.Position(scriptline + 1, 0), text + os.EOL);
      // 尋找components字符所在行
      let componentsLine = getLineNumber('components');
      if (componentsLine !== -1) {
        let old = document.lineAt(componentsLine).text
        console.log(old.trim().indexOf('{'))
        console.log('總長度' + old.trim().length - 1)
        // 存在components選項
        let i = document.lineAt(componentsLine).text.trim().indexOf('components')
        if (i === 0) {
          if (old.trim().indexOf('{') === old.trim().length - 1) {
            // components: { 格式
            const text = `${obj.name},${os.EOL}`;
            editBuilder.insert(new vscode.Position(componentsLine + 1, 0), text)
          } else {
            // components: {  }, 格式
            const text = old.substring(0, old.length - 2) + ',' + obj.name + '},' + os.EOL;
            editBuilder.replace(new vscode.Range(new vscode.Position(componentsLine, 0), new vscode.Position(componentsLine + 1, 0)), text)
          }
        }
      } else {
        // 沒有components選項
        const text = `components: { ${obj.name} },${os.EOL}`
        editBuilder.replace(new vscode.Range(new vscode.Position(componentsLine, 0), new vscode.Position(componentsLine + 1, 0)), text)
      }
    });
  }


  function resolveCompletionItem(item) {
    return item;
  }

  context.subscriptions.push(vscode.languages.registerCompletionItemProvider('vue', {
    provideCompletionItems,
    resolveCompletionItem
  }, '>'));
};

如法炮製,在extension.js引入

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');
const autotip = require('./autptip')
const complementtag = require('./complementtag')

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
function activate(context) {

    // Use the console to output diagnostic information (console.log) and errors (console.error)
    // This line of code will only be executed once when your extension is activated
    console.log('Congratulations, your extension "autotip" is now active!');
    // The command has been defined in the package.json file
    // Now provide the implementation of the command with  registerCommand
    // The commandId parameter must match the command field in package.json

    autotip(context)

    complementtag(context)
}
exports.activate = activate;

// this method is called when your extension is deactivated
function deactivate() {
}
exports.deactivate = deactivate;

最後的話

可以直接在vscode中搜索autotip這個插件,當然這個插件有很多不完善的地方,但是最重要的怎麼自己學會去寫插件,自己寫插件自己覺得方便就好。別人寫插件考慮的自然是自己工作中的問題。

因爲是根據components這個字符串作爲判斷依據的,只要你其他出現了components這個字眼,那麼這個插件就有問題,還有這個插件只支持單文件夾工作空間,儘管有這麼多問題但是我卻用的很舒服,因爲我工作中不會遇到這些問題,如果有一天遇到了就再改一下嘛。插件這東西方便就好,所以自己寫適合自己的插件是多麼重要啊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章