-
-
Notifications
You must be signed in to change notification settings - Fork 27.1k
Speed up editor detection on Windows #2711
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
cd28840
39289bb
55dca23
5785eaf
5b2f6ae
1a7907f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,10 +11,59 @@ | |
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const child_process = require('child_process'); | ||
| const EventEmitter = require('events').EventEmitter; | ||
| const os = require('os'); | ||
| const chalk = require('chalk'); | ||
| const shellQuote = require('shell-quote'); | ||
|
|
||
| // Inspired by https://github.com/rannn505/node-powershell | ||
| const EOI = 'EOI'; | ||
| class PowerShell extends EventEmitter { | ||
| constructor() { | ||
| super(); | ||
|
|
||
| this._proc = child_process.spawn( | ||
| 'powershell.exe', | ||
| ['-NoLogo', '-NoProfile', '-NoExit', '-Command', '-'], | ||
| { | ||
| stdio: 'pipe', | ||
| } | ||
| ); | ||
|
|
||
| let output = []; | ||
| this._proc.stdout.on('data', data => { | ||
| if (data.indexOf(EOI) !== -1) { | ||
| this.emit('resolve', output.join('')); | ||
| output = []; | ||
| } else { | ||
| output.push(data); | ||
| } | ||
| }); | ||
|
|
||
| this._proc.on('error', () => { | ||
| this._proc = null; | ||
| }); | ||
| } | ||
|
|
||
| invoke(cmd) { | ||
| if (!this._proc) { | ||
| return Promise.resolve(''); | ||
| } | ||
|
|
||
| return new Promise(resolve => { | ||
| this.on('resolve', data => { | ||
| resolve(data); | ||
| this.removeAllListeners('resolve'); | ||
|
||
| }); | ||
|
|
||
| this._proc.stdin.write(cmd); | ||
| this._proc.stdin.write(os.EOL); | ||
| this._proc.stdin.write(`echo ${EOI}`); | ||
| this._proc.stdin.write(os.EOL); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| function isTerminalEditor(editor) { | ||
| switch (editor) { | ||
| case 'vim': | ||
|
|
@@ -104,57 +153,65 @@ function getArgumentsForLineNumber(editor, fileName, lineNumber, workspace) { | |
| return [fileName]; | ||
| } | ||
|
|
||
| function guessEditor() { | ||
| // Explicit config always wins | ||
| if (process.env.REACT_EDITOR) { | ||
| return shellQuote.parse(process.env.REACT_EDITOR); | ||
| let powerShellAgent = null; | ||
| function launchPowerShellAgent() { | ||
| if (!powerShellAgent) { | ||
| powerShellAgent = new PowerShell(); | ||
| } | ||
| } | ||
|
|
||
| // Using `ps x` on OSX or `Get-Process` on Windows we can find out which editor is currently running. | ||
| // Potentially we could use similar technique for Linux | ||
| try { | ||
| if (process.platform === 'darwin') { | ||
| const output = child_process.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 = child_process | ||
| .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; | ||
| function guessEditor() { | ||
| return new Promise(resolve => { | ||
| // Explicit config always wins | ||
| if (process.env.REACT_EDITOR) { | ||
| return resolve(shellQuote.parse(process.env.REACT_EDITOR)); | ||
| } | ||
|
|
||
| // Using `ps x` on OSX or `Get-Process` on Windows we can find out which editor is currently running. | ||
| // Potentially we could use similar technique for Linux | ||
| try { | ||
| if (process.platform === 'darwin') { | ||
| const output = child_process.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 resolve([COMMON_EDITORS_OSX[processName]]); | ||
| } | ||
| } | ||
| } else if (process.platform === 'win32' && powerShellAgent) { | ||
| return powerShellAgent | ||
| .invoke('Get-Process | Select-Object Path') | ||
| .then(output => { | ||
| 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); | ||
| const fullProcessPath = runningProcesses[i].trim(); | ||
| const shortProcessName = path.basename(fullProcessPath); | ||
|
|
||
| if (COMMON_EDITORS_WIN.indexOf(shortProcessName) !== -1) { | ||
| return [fullProcessPath]; | ||
| } | ||
| if (COMMON_EDITORS_WIN.indexOf(shortProcessName) !== -1) { | ||
| return resolve([fullProcessPath]); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| } catch (error) { | ||
| // Ignore... | ||
| } | ||
| } 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]; | ||
| } | ||
| // Last resort, use old skool env vars | ||
| if (process.env.VISUAL) { | ||
| return resolve([process.env.VISUAL]); | ||
| } else if (process.env.EDITOR) { | ||
| return resolve([process.env.EDITOR]); | ||
| } | ||
|
|
||
| return [null]; | ||
| return resolve([null]); | ||
| }); | ||
| } | ||
|
|
||
| function printInstructions(fileName, errorMessage) { | ||
|
|
@@ -195,64 +252,68 @@ function launchEditor(fileName, 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); | ||
| } | ||
| guessEditor().then(([editor, ...args]) => { | ||
| if (!editor) { | ||
| printInstructions(fileName, null); | ||
| return; | ||
| } | ||
|
|
||
| let workspace = null; | ||
| if (lineNumber) { | ||
| args = args.concat( | ||
| getArgumentsForLineNumber(editor, fileName, lineNumber, workspace) | ||
| ); | ||
| } else { | ||
| args.push(fileName); | ||
| } | ||
| 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); | ||
| } | ||
|
|
||
| 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'); | ||
| } | ||
| let workspace = null; | ||
| if (lineNumber) { | ||
| args = args.concat( | ||
| getArgumentsForLineNumber(editor, fileName, lineNumber, workspace) | ||
| ); | ||
| } else { | ||
| args.push(fileName); | ||
| } | ||
|
|
||
| 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 (_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 (errorCode) { | ||
| printInstructions(fileName, '(code ' + errorCode + ')'); | ||
| 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; | ||
|
|
||
| _childProcess.on('error', function(error) { | ||
| printInstructions(fileName, error.message); | ||
| if (errorCode) { | ||
| printInstructions(fileName, '(code ' + errorCode + ')'); | ||
| } | ||
| }); | ||
|
|
||
| _childProcess.on('error', function(error) { | ||
| printInstructions(fileName, error.message); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| module.exports = launchEditor; | ||
| module.exports = { | ||
| launchEditor, | ||
| launchPowerShellAgent, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,9 +8,14 @@ | |
| */ | ||
| 'use strict'; | ||
|
|
||
| const launchEditor = require('react-dev-utils/launchEditor'); | ||
| const { launchEditor } = require('react-dev-utils/launchEditor'); | ||
|
|
||
| module.exports = function createLaunchEditorMiddleware() { | ||
| if (process.platform === 'win32' && !process.env.REACT_EDITOR) { | ||
| const { launchPowerShellAgent } = require('react-dev-utils/launchEditor'); | ||
| launchPowerShellAgent(); | ||
|
||
| } | ||
|
|
||
| return function launchEditorMiddleware(req, res, next) { | ||
| // Keep this in sync with react-error-overlay | ||
| if (req.url.startsWith('/__open-stack-frame-in-editor')) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason we're not just using it?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that module you have an method
addCommandand an methodinvoke.With
addCommandyou can add one to many commands and execute them withinvokeat once, getting the output of all commands. I wanted a clear connection between a command and its result.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could send a pull request to the module, adding an
executeshortcut which callsaddCommandandinvoke?