1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | 'use strict';
|
8 |
|
9 | const fs = require('fs');
|
10 | const path = require('path');
|
11 | const child_process = require('child_process');
|
12 | const os = require('os');
|
13 | const chalk = require('chalk');
|
14 | const shellQuote = require('shell-quote');
|
15 |
|
16 | function isTerminalEditor(editor) {
|
17 | switch (editor) {
|
18 | case 'vim':
|
19 | case 'emacs':
|
20 | case 'nano':
|
21 | return true;
|
22 | }
|
23 | return false;
|
24 | }
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | const COMMON_EDITORS_OSX = {
|
30 | '/Applications/Atom.app/Contents/MacOS/Atom': 'atom',
|
31 | '/Applications/Atom Beta.app/Contents/MacOS/Atom Beta':
|
32 | '/Applications/Atom Beta.app/Contents/MacOS/Atom Beta',
|
33 | '/Applications/Brackets.app/Contents/MacOS/Brackets': 'brackets',
|
34 | '/Applications/Sublime Text.app/Contents/MacOS/Sublime Text':
|
35 | '/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl',
|
36 | '/Applications/Sublime Text Dev.app/Contents/MacOS/Sublime Text':
|
37 | '/Applications/Sublime Text Dev.app/Contents/SharedSupport/bin/subl',
|
38 | '/Applications/Sublime Text 2.app/Contents/MacOS/Sublime Text 2':
|
39 | '/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl',
|
40 | '/Applications/Visual Studio Code.app/Contents/MacOS/Electron': 'code',
|
41 | '/Applications/Visual Studio Code - Insiders.app/Contents/MacOS/Electron':
|
42 | 'code-insiders',
|
43 | '/Applications/AppCode.app/Contents/MacOS/appcode':
|
44 | '/Applications/AppCode.app/Contents/MacOS/appcode',
|
45 | '/Applications/CLion.app/Contents/MacOS/clion':
|
46 | '/Applications/CLion.app/Contents/MacOS/clion',
|
47 | '/Applications/IntelliJ IDEA.app/Contents/MacOS/idea':
|
48 | '/Applications/IntelliJ IDEA.app/Contents/MacOS/idea',
|
49 | '/Applications/PhpStorm.app/Contents/MacOS/phpstorm':
|
50 | '/Applications/PhpStorm.app/Contents/MacOS/phpstorm',
|
51 | '/Applications/PyCharm.app/Contents/MacOS/pycharm':
|
52 | '/Applications/PyCharm.app/Contents/MacOS/pycharm',
|
53 | '/Applications/PyCharm CE.app/Contents/MacOS/pycharm':
|
54 | '/Applications/PyCharm CE.app/Contents/MacOS/pycharm',
|
55 | '/Applications/RubyMine.app/Contents/MacOS/rubymine':
|
56 | '/Applications/RubyMine.app/Contents/MacOS/rubymine',
|
57 | '/Applications/WebStorm.app/Contents/MacOS/webstorm':
|
58 | '/Applications/WebStorm.app/Contents/MacOS/webstorm',
|
59 | '/Applications/MacVim.app/Contents/MacOS/MacVim':
|
60 | 'mvim',
|
61 | };
|
62 |
|
63 | const COMMON_EDITORS_LINUX = {
|
64 | atom: 'atom',
|
65 | Brackets: 'brackets',
|
66 | code: 'code',
|
67 | 'code-insiders': 'code-insiders',
|
68 | emacs: 'emacs',
|
69 | 'idea.sh': 'idea',
|
70 | 'phpstorm.sh': 'phpstorm',
|
71 | 'pycharm.sh': 'pycharm',
|
72 | 'rubymine.sh': 'rubymine',
|
73 | sublime_text: 'sublime_text',
|
74 | vim: 'vim',
|
75 | 'webstorm.sh': 'webstorm',
|
76 | };
|
77 |
|
78 | const COMMON_EDITORS_WIN = [
|
79 | 'Brackets.exe',
|
80 | 'Code.exe',
|
81 | 'Code - Insiders.exe',
|
82 | 'atom.exe',
|
83 | 'sublime_text.exe',
|
84 | 'notepad++.exe',
|
85 | 'clion.exe',
|
86 | 'clion64.exe',
|
87 | 'idea.exe',
|
88 | 'idea64.exe',
|
89 | 'phpstorm.exe',
|
90 | 'phpstorm64.exe',
|
91 | 'pycharm.exe',
|
92 | 'pycharm64.exe',
|
93 | 'rubymine.exe',
|
94 | 'rubymine64.exe',
|
95 | 'webstorm.exe',
|
96 | 'webstorm64.exe',
|
97 | ];
|
98 |
|
99 | function addWorkspaceToArgumentsIfExists(args, workspace) {
|
100 | if (workspace) {
|
101 | args.unshift(workspace);
|
102 | }
|
103 | return args;
|
104 | }
|
105 |
|
106 | function getArgumentsForLineNumber(
|
107 | editor,
|
108 | fileName,
|
109 | lineNumber,
|
110 | colNumber,
|
111 | workspace
|
112 | ) {
|
113 | const editorBasename = path.basename(editor).replace(/\.(exe|cmd|bat)$/i, '');
|
114 | switch (editorBasename) {
|
115 | case 'atom':
|
116 | case 'Atom':
|
117 | case 'Atom Beta':
|
118 | case 'subl':
|
119 | case 'sublime':
|
120 | case 'sublime_text':
|
121 | return [fileName + ':' + lineNumber + ':' + colNumber];
|
122 | case 'wstorm':
|
123 | case 'charm':
|
124 | return [fileName + ':' + lineNumber];
|
125 | case 'notepad++':
|
126 | return ['-n' + lineNumber, '-c' + colNumber, fileName];
|
127 | case 'vim':
|
128 | case 'mvim':
|
129 | case 'joe':
|
130 | return ['+' + lineNumber, fileName];
|
131 | case 'emacs':
|
132 | case 'emacsclient':
|
133 | return ['+' + lineNumber + ':' + colNumber, fileName];
|
134 | case 'rmate':
|
135 | case 'mate':
|
136 | case 'mine':
|
137 | return ['--line', lineNumber, fileName];
|
138 | case 'code':
|
139 | case 'Code':
|
140 | case 'code-insiders':
|
141 | case 'Code - Insiders':
|
142 | return addWorkspaceToArgumentsIfExists(
|
143 | ['-g', fileName + ':' + lineNumber + ':' + colNumber],
|
144 | workspace
|
145 | );
|
146 | case 'appcode':
|
147 | case 'clion':
|
148 | case 'clion64':
|
149 | case 'idea':
|
150 | case 'idea64':
|
151 | case 'phpstorm':
|
152 | case 'phpstorm64':
|
153 | case 'pycharm':
|
154 | case 'pycharm64':
|
155 | case 'rubymine':
|
156 | case 'rubymine64':
|
157 | case 'webstorm':
|
158 | case 'webstorm64':
|
159 | return addWorkspaceToArgumentsIfExists(
|
160 | ['--line', lineNumber, fileName],
|
161 | workspace
|
162 | );
|
163 | }
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | return [fileName];
|
169 | }
|
170 |
|
171 | function guessEditor() {
|
172 |
|
173 | if (process.env.REACT_EDITOR) {
|
174 | return shellQuote.parse(process.env.REACT_EDITOR);
|
175 | }
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | try {
|
181 | if (process.platform === 'darwin') {
|
182 | const output = child_process.execSync('ps x').toString();
|
183 | const processNames = Object.keys(COMMON_EDITORS_OSX);
|
184 | for (let i = 0; i < processNames.length; i++) {
|
185 | const processName = processNames[i];
|
186 | if (output.indexOf(processName) !== -1) {
|
187 | return [COMMON_EDITORS_OSX[processName]];
|
188 | }
|
189 | }
|
190 | } else if (process.platform === 'win32') {
|
191 | const output = child_process
|
192 | .execSync('powershell -Command "Get-Process | Select-Object Path"', {
|
193 | stdio: ['pipe', 'pipe', 'ignore'],
|
194 | })
|
195 | .toString();
|
196 | const runningProcesses = output.split('\r\n');
|
197 | for (let i = 0; i < runningProcesses.length; i++) {
|
198 |
|
199 | if (!runningProcesses[i]) {
|
200 | continue;
|
201 | }
|
202 |
|
203 | const fullProcessPath = runningProcesses[i].trim();
|
204 | const shortProcessName = path.basename(fullProcessPath);
|
205 |
|
206 | if (COMMON_EDITORS_WIN.indexOf(shortProcessName) !== -1) {
|
207 | return [fullProcessPath];
|
208 | }
|
209 | }
|
210 | } else if (process.platform === 'linux') {
|
211 |
|
212 |
|
213 |
|
214 | const output = child_process
|
215 | .execSync('ps x --no-heading -o comm --sort=comm')
|
216 | .toString();
|
217 | const processNames = Object.keys(COMMON_EDITORS_LINUX);
|
218 | for (let i = 0; i < processNames.length; i++) {
|
219 | const processName = processNames[i];
|
220 | if (output.indexOf(processName) !== -1) {
|
221 | return [COMMON_EDITORS_LINUX[processName]];
|
222 | }
|
223 | }
|
224 | }
|
225 | } catch (error) {
|
226 |
|
227 | }
|
228 |
|
229 |
|
230 | if (process.env.VISUAL) {
|
231 | return [process.env.VISUAL];
|
232 | } else if (process.env.EDITOR) {
|
233 | return [process.env.EDITOR];
|
234 | }
|
235 |
|
236 | return [null];
|
237 | }
|
238 |
|
239 | function printInstructions(fileName, errorMessage) {
|
240 | console.log();
|
241 | console.log(
|
242 | chalk.red('Could not open ' + path.basename(fileName) + ' in the editor.')
|
243 | );
|
244 | if (errorMessage) {
|
245 | if (errorMessage[errorMessage.length - 1] !== '.') {
|
246 | errorMessage += '.';
|
247 | }
|
248 | console.log(
|
249 | chalk.red('The editor process exited with an error: ' + errorMessage)
|
250 | );
|
251 | }
|
252 | console.log();
|
253 | console.log(
|
254 | 'To set up the editor integration, add something like ' +
|
255 | chalk.cyan('REACT_EDITOR=atom') +
|
256 | ' to the ' +
|
257 | chalk.green('.env.local') +
|
258 | ' file in your project folder ' +
|
259 | 'and restart the development server. Learn more: ' +
|
260 | chalk.green('https://goo.gl/MMTaZt')
|
261 | );
|
262 | console.log();
|
263 | }
|
264 |
|
265 | let _childProcess = null;
|
266 | function launchEditor(fileName, lineNumber, colNumber) {
|
267 | if (!fs.existsSync(fileName)) {
|
268 | return;
|
269 | }
|
270 |
|
271 |
|
272 |
|
273 |
|
274 | if (!(Number.isInteger(lineNumber) && lineNumber > 0)) {
|
275 | return;
|
276 | }
|
277 |
|
278 |
|
279 |
|
280 | if (!(Number.isInteger(colNumber) && colNumber > 0)) {
|
281 | colNumber = 1;
|
282 | }
|
283 |
|
284 | let [editor, ...args] = guessEditor();
|
285 |
|
286 | if (!editor) {
|
287 | printInstructions(fileName, null);
|
288 | return;
|
289 | }
|
290 |
|
291 | if (editor.toLowerCase() === 'none') {
|
292 | return;
|
293 | }
|
294 |
|
295 | if (
|
296 | process.platform === 'linux' &&
|
297 | fileName.startsWith('/mnt/') &&
|
298 | /Microsoft/i.test(os.release())
|
299 | ) {
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 | fileName = path.relative('', fileName);
|
307 | }
|
308 |
|
309 | let workspace = null;
|
310 | if (lineNumber) {
|
311 | args = args.concat(
|
312 | getArgumentsForLineNumber(
|
313 | editor,
|
314 | fileName,
|
315 | lineNumber,
|
316 | colNumber,
|
317 | workspace
|
318 | )
|
319 | );
|
320 | } else {
|
321 | args.push(fileName);
|
322 | }
|
323 |
|
324 | if (_childProcess && isTerminalEditor(editor)) {
|
325 |
|
326 |
|
327 |
|
328 | _childProcess.kill('SIGKILL');
|
329 | }
|
330 |
|
331 | if (process.platform === 'win32') {
|
332 |
|
333 |
|
334 | _childProcess = child_process.spawn(
|
335 | 'cmd.exe',
|
336 | ['/C', editor].concat(args),
|
337 | { stdio: 'inherit' }
|
338 | );
|
339 | } else {
|
340 | _childProcess = child_process.spawn(editor, args, { stdio: 'inherit' });
|
341 | }
|
342 | _childProcess.on('exit', function(errorCode) {
|
343 | _childProcess = null;
|
344 |
|
345 | if (errorCode) {
|
346 | printInstructions(fileName, '(code ' + errorCode + ')');
|
347 | }
|
348 | });
|
349 |
|
350 | _childProcess.on('error', function(error) {
|
351 | printInstructions(fileName, error.message);
|
352 | });
|
353 | }
|
354 |
|
355 | module.exports = launchEditor;
|