# vue-tools

# 学习流程

  1. 安装 vue-devtools 对应vue3版本
  2. 克隆项目,按照文章中流程,先跑起来
    1. 本文仓库地址 (opens new window):git clone https://github.com/lxchuan12/open-in-editor.git,本文最佳阅读方式,克隆仓库自己动手调试,容易吸收消化。
  3. 调试全流程
  4. 记录笔记

# 环境准备

  1. 安装 vue-devtools 对应 vue3版本 链接: https://pan.baidu.com/s/1cV4IvrZJkJmV_nGpUj9xyg 提取码: udgm (已解压)
  2. 克隆 git clone https://github.com/lxchuan12/open-in-editor.git, 或者创建一个 新的 vue3 project(这里的版本需要与vue-devtools的版本对应)

# 定点调试

打开克隆好的文件夹,使用 vscode 搜索下'launch-editor-middleware'这个中间件,vscode搜索文件一般会屏蔽 node_modules, 需要手动进行配置一下。

# launch-editor-middleware

搜索出来的,launch-editor-middleware 中 index.js 的源码:

const url = require('url')
const path = require('path')
const launch = require('launch-editor')

module.exports = (specifiedEditor, srcRoot, onErrorCallback) => {
  if (typeof specifiedEditor === 'function') {
    onErrorCallback = specifiedEditor
    specifiedEditor = undefined
  }

  if (typeof srcRoot === 'function') {
    onErrorCallback = srcRoot
    srcRoot = undefined
  }

  srcRoot = srcRoot || process.cwd()

  return function launchEditorMiddleware (req, res, next) {
    const { file } = url.parse(req.url, true).query || {}
    if (!file) {
      res.statusCode = 500
      res.end(`launch-editor-middleware: required query param "file" is missing.`)
    } else {
      launch(path.resolve(srcRoot, file), specifiedEditor, onErrorCallback)
      res.end()
    }
  }
}

返回一个函数,对传入的路径进行判断,如果路径中解析出一个文件路径,则调用launch打开

# launch-editor

nodejs_modules中launch-editor的 index.js文件

这里只梳理大致的函数逻辑:

const fs = require('fs')
const os = require('os')
const path = require('path')
const chalk = require('chalk')
const childProcess = require('child_process')

const guessEditor = require('./guess')
const getArgumentsForPosition = require('./get-args')

let _childProcess = null
// 主体函数
function launchEditor (file, specifiedEditor, onErrorCallback) {
  const parsed = parseFile(file)
  let { fileName } = parsed
  const { lineNumber, columnNumber } = parsed

  // 文件不存在,直接返回
  if (!fs.existsSync(fileName)) {
    return
  }

  if (typeof specifiedEditor === 'function') {
    onErrorCallback = specifiedEditor
    specifiedEditor = undefined
  }

  onErrorCallback = wrapErrorCallback(onErrorCallback)
  // 猜测编辑器
  const [editor, ...args] = guessEditor(specifiedEditor)
  // 如果编辑器为空,则调用错误回调函数
  if (!editor) {
    onErrorCallback(fileName, null)
    return
  }
  // linux mnt 
  if (
    process.platform === 'linux' &&
    fileName.startsWith('/mnt/') &&
    /Microsoft/i.test(os.release())
  ) {
    fileName = path.relative('', fileName)
  }

  if (lineNumber) {
    const extraArgs = getArgumentsForPosition(editor, fileName, lineNumber, columnNumber)
    args.push.apply(args, extraArgs)
  } else {
    args.push(fileName)
  }

  if (_childProcess && isTerminalEditor(editor)) {
    _childProcess.kill('SIGKILL')
  }

  if (process.platform === 'win32') {
    _childProcess = childProcess.spawn(
      'cmd.exe',
      ['/C', editor].concat(args),
      { stdio: 'inherit' }
    )
  } else {
    _childProcess = childProcess.spawn(editor, args, { stdio: 'inherit' })
  }
  _childProcess.on('exit', function (errorCode) {
    _childProcess = null

    if (errorCode) {
      onErrorCallback(fileName, '(code ' + errorCode + ')')
    }
  })

  _childProcess.on('error', function (error) {
    onErrorCallback(fileName, error.message)
  })
}

# guess.js

如果编辑器有配置,则直接返回。

如果没有配置,则根据操作系统的不同进行猜测编辑器,猜测编辑器的做法:

  • 配置特定操作系统可能出现的编辑器
  • 使用 子进程 执行对应操作系统的命令来 获取已在活跃的进程
  • 将上一步获取的数据与对应操作系统进行对比,如果存在,则返回编辑器。
  • 如果当中出现错误或者编辑器找不到,则返回空。
const path = require('path')
const shellQuote = require('shell-quote')
const childProcess = require('child_process')


// 三个操作系统中的编辑器配置。
const COMMON_EDITORS_OSX = require('./editor-info/osx')
const COMMON_EDITORS_LINUX = require('./editor-info/linux')
const COMMON_EDITORS_WIN = require('./editor-info/windows')

module.exports = function guessEditor (specifiedEditor) {
  if (specifiedEditor) {
    return shellQuote.parse(specifiedEditor)
  }
  try {
    if (process.platform === 'darwin') {
      const output = childProcess.execSync('ps x').toString()
      const processNames = Object.keys(COMMON_EDITORS_OSX)
      for (let i = 0; i < processNames.length; i++) {
        const processName = processNames[i]
        if (output.indexOf(processName) !== -1) {
          return [COMMON_EDITORS_OSX[processName]]
        }
      }
    } else if (process.platform === 'win32') {
      const output = childProcess
        .execSync('powershell -Command "Get-Process | Select-Object Path"', {
          stdio: ['pipe', 'pipe', 'ignore']
        })
        .toString()
      const runningProcesses = output.split('\r\n')
      for (let i = 0; i < runningProcesses.length; i++) {
        // `Get-Process` sometimes returns empty lines
        if (!runningProcesses[i]) {
          continue
        }

        const fullProcessPath = runningProcesses[i].trim()
        const shortProcessName = path.basename(fullProcessPath)

        if (COMMON_EDITORS_WIN.indexOf(shortProcessName) !== -1) {
          return [fullProcessPath]
        }
      }
    } else if (process.platform === 'linux') {
      const output = childProcess
        .execSync('ps x --no-heading -o comm --sort=comm')
        .toString()
      const processNames = Object.keys(COMMON_EDITORS_LINUX)
      for (let i = 0; i < processNames.length; i++) {
        const processName = processNames[i]
        if (output.indexOf(processName) !== -1) {
          return [COMMON_EDITORS_LINUX[processName]]
        }
      }
    }
  } catch (error) {
  }
  if (process.env.VISUAL) {
    return [process.env.VISUAL]
  } else if (process.env.EDITOR) {
    return [process.env.EDITOR]
  }
  return [null]
}

# parseFIle

const positionRE = /:(\d+)(:(\d+))?$/
function parseFile (file) {
  const fileName = file.replace(positionRE, '')
  const match = file.match(positionRE)
  const lineNumber = match && match[1]
  const columnNumber = match && match[3]
  return {
    fileName,
    lineNumber,
    columnNumber
  }
}

# 参考资料

  1. 语雀源码阅读-第一期 (opens new window)
  2. 语雀源码阅读-第一期 若川 掘金地址 (opens new window)