-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathnreplController.ts
132 lines (107 loc) · 4.2 KB
/
nreplController.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import 'process';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import * as spawn from 'cross-spawn';
import { ChildProcess, exec } from 'child_process';
import { CljConnectionInformation } from './cljConnection';
const config = vscode.workspace.getConfiguration('clojureVSCode');
const LEIN_ARGS: string[] = [
'update-in',
':dependencies',
'conj',
`[cljfmt "${config.cljfmtVersion}"]`,
'--',
'update-in',
':plugins',
'conj',
`[cider/cider-nrepl "${config.ciderNReplVersion}"]`,
'--',
'repl',
':headless'
];
const R_NREPL_CONNECTION_INFO = /nrepl:\/\/(.*?:.*?(?=[\n\r]))/;
let nreplProcess: ChildProcess | null
const isStarted = () => !!nreplProcess;
// Create a channel in the Output window so that the user
// can view output from the nREPL session.
const nreplChannel = vscode.window.createOutputChannel('nREPL');
const start = (): Promise<CljConnectionInformation> => {
if (isStarted())
return Promise.reject({ nreplError: 'nREPL already started.' });
// Clear any output from previous nREPL sessions to help users focus
// on the current session only.
nreplChannel.clear();
return new Promise((resolve, reject) => {
nreplProcess = spawn('lein', LEIN_ARGS, {
cwd: getCwd(), // see the `getCwd` function documentation!
detached: !(os.platform() === 'win32')
});
nreplProcess.stdout.addListener('data', data => {
const nreplConnectionMatch = data.toString().match(R_NREPL_CONNECTION_INFO);
// Send any stdout messages to the output channel
nreplChannel.append(data.toString());
if (nreplConnectionMatch && nreplConnectionMatch[1]) {
const [host, port] = nreplConnectionMatch[1].split(':');
return resolve({ host, port: Number.parseInt(port) });
}
});
nreplProcess.stderr.on('data', data => {
// Send any stderr messages to the output channel
nreplChannel.append(data.toString());
});
nreplProcess.on('exit', (code) => {
// nREPL process has exited before we were able to read a host / port.
const message = `nREPL exited with code ${code}`
nreplChannel.appendLine(message);
// Bring the output channel to the foreground so that the user can
// use the output to debug the problem.
nreplChannel.show();
return reject({ nreplError: message});
});
});
};
const stop = () => {
if (nreplProcess) {
// Workaround http://azimi.me/2014/12/31/kill-child_process-node-js.html
nreplProcess.removeAllListeners();
try {
// Killing the process will throw an error `kill ESRCH` this method
// is invoked after the nREPL process has exited. This happens when
// we try to gracefully clean up after spawning the nREPL fails.
// We wrap the killing code in `try/catch` to handle this.
if(os.platform() === 'win32'){
exec('taskkill /pid ' + nreplProcess.pid + ' /T /F')
}
else {
process.kill(-nreplProcess.pid);
}
} catch (exception) {
console.error("Error cleaning up nREPL process", exception);
}
nreplProcess = null;
}
};
const dispose = stop;
/**
* It's important to set the current working directory parameter when spawning
* the nREPL process. Without the parameter been set, it can take quite a long
* period of time for the nREPL to start accepting commands. This most likely
* the result of one of the plugins we use doing something with the file
* system (first time I've seen it, I was surprised why VSCode and Java
* constantly asking for the permissions to access folders in my home directory).
*/
const getCwd = () => {
let cwd = vscode.workspace.rootPath;
if (cwd) return cwd;
// Try to get folder name from the active editor.
const document = vscode.window.activeTextEditor?.document;
if (!document) return;
return path.dirname(document.fileName);
}
export const nreplController = {
start,
stop,
isStarted,
dispose,
};