index.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file at
  6. * https://github.com/facebookincubator/create-react-app/blob/master/LICENSE
  7. *
  8. * Modified by Yuxi Evan You
  9. */
  10. const fs = require('fs')
  11. const os = require('os')
  12. const path = require('path')
  13. const colors = require('picocolors')
  14. const childProcess = require('child_process')
  15. const guessEditor = require('./guess')
  16. const getArgumentsForPosition = require('./get-args')
  17. function wrapErrorCallback (cb) {
  18. return (fileName, errorMessage) => {
  19. console.log()
  20. console.log(
  21. colors.red('Could not open ' + path.basename(fileName) + ' in the editor.')
  22. )
  23. if (errorMessage) {
  24. if (errorMessage[errorMessage.length - 1] !== '.') {
  25. errorMessage += '.'
  26. }
  27. console.log(
  28. colors.red('The editor process exited with an error: ' + errorMessage)
  29. )
  30. }
  31. console.log()
  32. if (cb) cb(fileName, errorMessage)
  33. }
  34. }
  35. function isTerminalEditor (editor) {
  36. switch (editor) {
  37. case 'vim':
  38. case 'emacs':
  39. case 'nano':
  40. return true
  41. }
  42. return false
  43. }
  44. const positionRE = /:(\d+)(:(\d+))?$/
  45. function parseFile (file) {
  46. const fileName = file.replace(positionRE, '')
  47. const match = file.match(positionRE)
  48. const lineNumber = match && match[1]
  49. const columnNumber = match && match[3]
  50. return {
  51. fileName,
  52. lineNumber,
  53. columnNumber
  54. }
  55. }
  56. let _childProcess = null
  57. function launchEditor (file, specifiedEditor, onErrorCallback) {
  58. const parsed = parseFile(file)
  59. let { fileName } = parsed
  60. const { lineNumber, columnNumber } = parsed
  61. if (!fs.existsSync(fileName)) {
  62. return
  63. }
  64. if (typeof specifiedEditor === 'function') {
  65. onErrorCallback = specifiedEditor
  66. specifiedEditor = undefined
  67. }
  68. onErrorCallback = wrapErrorCallback(onErrorCallback)
  69. const [editor, ...args] = guessEditor(specifiedEditor)
  70. if (!editor) {
  71. onErrorCallback(fileName, null)
  72. return
  73. }
  74. if (
  75. process.platform === 'linux' &&
  76. fileName.startsWith('/mnt/') &&
  77. /Microsoft/i.test(os.release())
  78. ) {
  79. // Assume WSL / "Bash on Ubuntu on Windows" is being used, and
  80. // that the file exists on the Windows file system.
  81. // `os.release()` is "4.4.0-43-Microsoft" in the current release
  82. // build of WSL, see: https://github.com/Microsoft/BashOnWindows/issues/423#issuecomment-221627364
  83. // When a Windows editor is specified, interop functionality can
  84. // handle the path translation, but only if a relative path is used.
  85. fileName = path.relative('', fileName)
  86. }
  87. if (lineNumber) {
  88. const extraArgs = getArgumentsForPosition(editor, fileName, lineNumber, columnNumber)
  89. args.push.apply(args, extraArgs)
  90. } else {
  91. args.push(fileName)
  92. }
  93. if (_childProcess && isTerminalEditor(editor)) {
  94. // There's an existing editor process already and it's attached
  95. // to the terminal, so go kill it. Otherwise two separate editor
  96. // instances attach to the stdin/stdout which gets confusing.
  97. _childProcess.kill('SIGKILL')
  98. }
  99. if (process.platform === 'win32') {
  100. // On Windows, launch the editor in a shell because spawn can only
  101. // launch .exe files.
  102. _childProcess = childProcess.spawn(
  103. 'cmd.exe',
  104. ['/C', editor].concat(args),
  105. { stdio: 'inherit' }
  106. )
  107. } else {
  108. _childProcess = childProcess.spawn(editor, args, { stdio: 'inherit' })
  109. }
  110. _childProcess.on('exit', function (errorCode) {
  111. _childProcess = null
  112. if (errorCode) {
  113. onErrorCallback(fileName, '(code ' + errorCode + ')')
  114. }
  115. })
  116. _childProcess.on('error', function (error) {
  117. onErrorCallback(fileName, error.message)
  118. })
  119. }
  120. module.exports = launchEditor