launchEditor.js 6.18 KiB
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
'use strict';
var fs = require('fs');
var path = require('path');
var child_process = require('child_process');
var os = require('os');
var chalk = require('chalk');
var shellQuote = require('shell-quote');
function isTerminalEditor(editor) {
  switch (editor) {
    case 'vim':
    case 'emacs':
    case 'nano':
      return true;
  return false;
// Map from full process name to binary that starts the process
// We can't just re-use full process name, because it will spawn a new instance
// of the app every time
var COMMON_EDITORS = {
  '/Applications/Atom.app/Contents/MacOS/Atom': 'atom',
  '/Applications/Atom Beta.app/Contents/MacOS/Atom Beta': '/Applications/Atom Beta.app/Contents/MacOS/Atom Beta',
  '/Applications/Sublime Text.app/Contents/MacOS/Sublime Text': '/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl',
  '/Applications/Sublime Text 2.app/Contents/MacOS/Sublime Text 2': '/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl',
  '/Applications/Visual Studio Code.app/Contents/MacOS/Electron': 'code',
function addWorkspaceToArgumentsIfExists(args, workspace) {
  if (workspace) {
    args.unshift(workspace);
  return args;
function getArgumentsForLineNumber(editor, fileName, lineNumber, workspace) {
  var editorBasename = path.basename(editor).replace(/\.(exe|cmd|bat)$/i, '');
  switch (editorBasename) {
    case 'vim':
    case 'mvim':
      return [fileName, '+' + lineNumber];
    case 'atom':
    case 'Atom':
    case 'Atom Beta':
    case 'subl':
    case 'sublime':
    case 'wstorm':
    case 'appcode':
    case 'charm':
    case 'idea':
      return [fileName + ':' + lineNumber];
    case 'joe':
    case 'emacs':
    case 'emacsclient':
      return ['+' + lineNumber, fileName];
    case 'rmate':
    case 'mate':
    case 'mine':
      return ['--line', lineNumber, fileName];
    case 'code':
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
return addWorkspaceToArgumentsIfExists( ['-g', fileName + ':' + lineNumber], workspace ); } // For all others, drop the lineNumber until we have // a mapping above, since providing the lineNumber incorrectly // can result in errors or confusing behavior. return [fileName]; } function guessEditor() { // Explicit config always wins if (process.env.REACT_EDITOR) { return shellQuote.parse(process.env.REACT_EDITOR); } // Using `ps x` on OSX we can find out which editor is currently running. // Potentially we could use similar technique for Windows and Linux if (process.platform === 'darwin') { try { var output = child_process.execSync('ps x').toString(); var processNames = Object.keys(COMMON_EDITORS); for (var i = 0; i < processNames.length; i++) { var processName = processNames[i]; if (output.indexOf(processName) !== -1) { return [COMMON_EDITORS[processName]]; } } } catch (error) { // Ignore... } } // Last resort, use old skool env vars if (process.env.VISUAL) { return [process.env.VISUAL]; } else if (process.env.EDITOR) { return [process.env.EDITOR]; } return [null]; } function printInstructions(fileName, errorMessage) { console.log(); console.log( chalk.red('Could not open ' + path.basename(fileName) + ' in the editor.') ); if (errorMessage) { if (errorMessage[errorMessage.length - 1] !== '.') { errorMessage += '.'; } console.log( chalk.red('The editor process exited with an error: ' + errorMessage) ); } console.log(); console.log( 'To set up the editor integration, add something like ' + chalk.cyan('REACT_EDITOR=atom') + ' to the ' + chalk.green('.env.local') + ' file in your project folder ' + 'and restart the development server.' ); console.log(); }
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
var _childProcess = null; function launchEditor(fileName, lineNumber) { if (!fs.existsSync(fileName)) { return; } // Sanitize lineNumber to prevent malicious use on win32 // via: https://github.com/nodejs/node/blob/c3bb4b1aa5e907d489619fb43d233c3336bfc03d/lib/child_process.js#L333 if (lineNumber && isNaN(lineNumber)) { return; } let [editor, ...args] = guessEditor(); if (!editor) { printInstructions(fileName, null); return; } if ( process.platform === 'linux' && fileName.startsWith('/mnt/') && /Microsoft/i.test(os.release()) ) { // Assume WSL / "Bash on Ubuntu on Windows" is being used, and // that the file exists on the Windows file system. // `os.release()` is "4.4.0-43-Microsoft" in the current release // build of WSL, see: https://github.com/Microsoft/BashOnWindows/issues/423#issuecomment-221627364 // When a Windows editor is specified, interop functionality can // handle the path translation, but only if a relative path is used. fileName = path.relative('', fileName); } var workspace = null; if (lineNumber) { args = args.concat( getArgumentsForLineNumber(editor, fileName, lineNumber, workspace) ); } else { args.push(fileName); } if (_childProcess && isTerminalEditor(editor)) { // There's an existing editor process already and it's attached // to the terminal, so go kill it. Otherwise two separate editor // instances attach to the stdin/stdout which gets confusing. _childProcess.kill('SIGKILL'); } if (process.platform === 'win32') { // On Windows, launch the editor in a shell because spawn can only // launch .exe files. _childProcess = child_process.spawn( 'cmd.exe', ['/C', editor].concat(args), { stdio: 'inherit' } ); } else { _childProcess = child_process.spawn(editor, args, { stdio: 'inherit' }); } _childProcess.on('exit', function(errorCode) { _childProcess = null; if (errorCode) { printInstructions(fileName, '(code ' + errorCode + ')'); } }); _childProcess.on('error', function(error) { printInstructions(fileName, error.message); });
211212213214
} module.exports = launchEditor;