Skip to content

Commit a180d46

Browse files
committed
Add commands to run Emacs Lisp from Atom
1 parent 7a31396 commit a180d46

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

index.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"use strict";
2+
3+
const childProcess = require("child_process");
4+
const path = require("path");
5+
6+
7+
class EmacsLisp{
8+
9+
activate(){
10+
const target = "atom-text-editor";
11+
12+
atom.commands.add(target, "language-emacs-lisp:run-selection", e => {
13+
const text = this.getSelection();
14+
if(text && !/^\s+$/.test(text))
15+
this.eval(text)
16+
.then(output => this.showOutput(output))
17+
.catch(output => this.showOutput(output, true));
18+
});
19+
20+
atom.commands.add(target, "language-emacs-lisp:run-file", e => {
21+
const ed = atom.workspace.getActiveTextEditor();
22+
if(ed) this.runFile(ed.getPath())
23+
.then(output => this.showOutput(output))
24+
.catch(output => this.showOutput(output, true));
25+
});
26+
}
27+
28+
29+
/**
30+
* Evaluate a string of Emacs Lisp code.
31+
*
32+
* Returns a {Promise} that resolves to the stringified output.
33+
*
34+
* @public
35+
* @example eval("(+ 5 5)") -> "10"
36+
* @param {String} text
37+
* @return {Promise}
38+
*/
39+
eval(text){
40+
return new Promise((resolve, reject) => {
41+
const emacs = childProcess.spawn("emacs", [
42+
"--batch",
43+
"--eval",
44+
`(message "%s" ${text})`
45+
]);
46+
47+
let output = "";
48+
emacs.stderr.on("data", data => {
49+
if(data) output += data.toString();
50+
});
51+
52+
emacs.on("close", code => {
53+
output = output.replace(/^\n+|\n+$/g, "");
54+
code !== 0
55+
? reject(output)
56+
: resolve(output);
57+
})
58+
});
59+
}
60+
61+
62+
/**
63+
* Run a Lisp file in Emacs.
64+
*
65+
* Returns a {Promise} with the script's output, if any.
66+
*
67+
* @public
68+
* @param {String} file - Path to file
69+
* @return {Promise}
70+
*/
71+
runFile(file){
72+
return new Promise((resolve, reject) => {
73+
const emacs = childProcess.spawn("emacs", ["--script", file]);
74+
75+
let output = "";
76+
emacs.stderr.on("data", data => {
77+
if(data) output += data.toString();
78+
});
79+
80+
emacs.on("close", code => {
81+
output = output.replace(/^\n+|\n+$/g, "");
82+
code !== 0
83+
? reject(output)
84+
: resolve(output);
85+
})
86+
});
87+
}
88+
89+
90+
/**
91+
* Show output in the notifications area.
92+
*
93+
* @param {String} text
94+
* @param {Boolean} error
95+
* @private
96+
*/
97+
showOutput(text, error = false){
98+
const msg = "**Emacs:** " + text;
99+
const opt = {dismissable: true};
100+
error
101+
? atom.notifications.addError(msg, opt)
102+
: atom.notifications.addInfo(msg, opt);
103+
}
104+
105+
106+
/**
107+
* Retrieve an editor's currently-selected text.
108+
*
109+
* If nothing's selected, the scope enclosing the cursor's current
110+
* position is selected and returned instead.
111+
*
112+
* @param {TextEditor} ed - Defaults to current editor
113+
* @private
114+
*/
115+
getSelection(ed){
116+
ed = ed || atom.workspace.getActiveTextEditor();
117+
if(!ed) return;
118+
let text = ed.getSelectedText();
119+
120+
/** If the user hasn't made a selection, make one for 'em */
121+
if(!text){
122+
if(atom.packages.activePackages["bracket-matcher"]){
123+
atom.commands.dispatch(ed.editorElement, "bracket-matcher:select-inside-brackets");
124+
125+
/** Select containing brackets too */
126+
let range = ed.getSelectedBufferRange();
127+
--range.start.column;
128+
++range.end.column;
129+
ed.setSelectedBufferRange(range);
130+
}
131+
132+
/** Fallback if bracket-matcher isn't available */
133+
else ed.selectWordsContainingCursors();
134+
135+
text = ed.getSelectedText();
136+
}
137+
return text;
138+
}
139+
}
140+
141+
module.exports = new EmacsLisp();

tests/run.el

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(message "%s" (+ 2 2))

0 commit comments

Comments
 (0)