Skip to content

Commit

Permalink
Merge pull request #32 from alessandrod/master
Browse files Browse the repository at this point in the history
Initial ClojureScript support
  • Loading branch information
avli authored Sep 23, 2017
2 parents 9c74e0a + 65ddcd8 commit 69fecc7
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 35 deletions.
53 changes: 53 additions & 0 deletions src/cljConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ export interface CljConnectionInformation {
host: string;
port: number;
}
export interface REPLSession {
type: 'ClojureScript' | 'Clojure';
id: string;
}

const CONNECTION_STATE_KEY: string = 'CLJ_CONNECTION';
const DEFAULT_LOCAL_IP: string = '127.0.0.1';
const CLJS_SESSION_KEY: string = 'CLJS_SESSION';
const connectionIndicator: vscode.StatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);

let cljContext: vscode.ExtensionContext;
Expand All @@ -33,6 +38,7 @@ const saveConnection = (connection: CljConnectionInformation): void => {

const saveDisconnection = (showMessage: boolean = true): void => {
cljContext.workspaceState.update(CONNECTION_STATE_KEY, undefined);
cljContext.workspaceState.update(CLJS_SESSION_KEY, undefined);

connectionIndicator.text = '';
connectionIndicator.show();
Expand Down Expand Up @@ -165,11 +171,58 @@ const getLocalNReplPort = (): number => {

const getPortFromFS = (path: string): number => fs.existsSync(path) ? Number.parseInt(fs.readFileSync(path, 'utf-8')) : NaN;

const findClojureScriptSession = (sessions: string[]): Promise<string> => {
if (sessions.length == 0)
return Promise.reject(null);

let base_session = sessions.shift();
return nreplClient.evaluate('(js/parseInt "42")', base_session).then(results => {
let { session, value } = results[0];
nreplClient.close(session);
if (value == 42) {
return Promise.resolve(base_session);
}

return findClojureScriptSession(sessions);
});
}

const discoverSessions = (): Promise<string> => {
return nreplClient.listSessions().then(sessions => {
return findClojureScriptSession(sessions).then(cljs_session => {
console.log("found ClojureScript session", cljs_session);
cljContext.workspaceState.update(CLJS_SESSION_KEY, cljs_session);
return cljs_session;
}).catch(reason => {
cljContext.workspaceState.update(CLJS_SESSION_KEY, undefined);
throw reason;
});
});
}

const sessionForFilename = (filename: string): Promise<REPLSession> => {
return new Promise((resolve, reject) => {
const sessionType = filename.endsWith('.cljs') ? "ClojureScript" : "Clojure";
if (sessionType == "Clojure") {
// Assume that the default session is Clojure. This is always the case with cider.
return resolve({ type: sessionType, id: undefined });
}

const session_id = cljContext.workspaceState.get<string>(CLJS_SESSION_KEY);
if (session_id)
return resolve({ type: sessionType, id: session_id });
return discoverSessions().then(session_id => {
resolve({ type: sessionType, id: session_id });
});
});
}

export const cljConnection = {
setCljContext,
getConnection,
isConnected,
manuallyConnect,
startNRepl,
disconnect,
sessionForFilename
};
18 changes: 10 additions & 8 deletions src/clojureDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ export class ClojureDefinitionProvider implements vscode.DefinitionProvider {
const currentWord: string = document.lineAt(position.line).text.slice(wordRange.start.character, wordRange.end.character);
const ns = cljParser.getNamespace(document.getText());

return nreplClient.info(currentWord, ns).then(info => {
if (!info.file)
return Promise.reject('No word definition found.');

let uri = vscode.Uri.parse(info.file);
let pos = new vscode.Position(info.line - 1, info.column)
let definition = new vscode.Location(uri, pos);
return Promise.resolve(definition);
return cljConnection.sessionForFilename(document.fileName).then(session => {
return nreplClient.info(currentWord, ns, session.id).then(info => {
if (!info.file)
return Promise.reject('No word definition found.');

let uri = vscode.Uri.parse(info.file);
let pos = new vscode.Position(info.line - 1, info.column)
let definition = new vscode.Location(uri, pos);
return Promise.resolve(definition);
});
});
}

Expand Down
29 changes: 20 additions & 9 deletions src/clojureEval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,30 @@ function evaluate(outputChannel: vscode.OutputChannel, showResults: boolean): vo

const editor = vscode.window.activeTextEditor;
const selection = editor.selection;

let text: string = editor.document.getText();
let text = editor.document.getText();
if (!selection.isEmpty) {
const ns: string = cljParser.getNamespace(text);
text = `(ns ${ns})\n${editor.document.getText(selection)}`;
}

const filename = editor.document.fileName;

nreplClient.evaluateFile(text, filename)
.then(respObjs => {
cljConnection.sessionForFilename(editor.document.fileName).then(session => {
let response;
if (!selection.isEmpty && session.type == 'ClojureScript') {
// Piggieback's evalFile() ignores the text sent as part of the request
// and just loads the whole file content from disk. So we use eval()
// here, which as a drawback will give us a random temporary filename in
// the stacktrace should an exception occur.
response = nreplClient.evaluate(text, session.id);
} else {
response = nreplClient.evaluateFile(text, editor.document.fileName, session.id);
}
response.then(respObjs => {
if (!!respObjs[0].ex)
return handleError(outputChannel, selection, showResults, respObjs[0].session);

return handleSuccess(outputChannel, showResults, respObjs);
})
.then(() => nreplClient.close());
});
}

function handleError(outputChannel: vscode.OutputChannel, selection: vscode.Selection, showResults: boolean, session: string): Promise<void> {
Expand All @@ -47,8 +54,8 @@ function handleError(outputChannel: vscode.OutputChannel, selection: vscode.Sele
.then(stacktraceObjs => {
const stacktraceObj = stacktraceObjs[0];

let errLine = stacktraceObj.line - 1;
let errChar = stacktraceObj.column - 1;
let errLine = stacktraceObj.line !== undefined ? stacktraceObj.line - 1 : 0;
let errChar = stacktraceObj.column !== undefined ? stacktraceObj.column - 1 : 0;

if (!selection.isEmpty) {
errLine += selection.start.line;
Expand All @@ -64,6 +71,7 @@ function handleError(outputChannel: vscode.OutputChannel, selection: vscode.Sele
});

outputChannel.show();
nreplClient.close(session);
});
}

Expand All @@ -74,9 +82,12 @@ function handleSuccess(outputChannel: vscode.OutputChannel, showResults: boolean
respObjs.forEach(respObj => {
if (respObj.out)
outputChannel.append(respObj.out);
if (respObj.err)
outputChannel.append(respObj.err);
if (respObj.value)
outputChannel.appendLine(`=> ${respObj.value}`);
outputChannel.show();
});
}
nreplClient.close(respObjs[0].session);
}
12 changes: 7 additions & 5 deletions src/clojureHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ export class ClojureHoverProvider implements vscode.HoverProvider {
currentWord = document.lineAt(position.line).text.slice(wordRange.start.character, wordRange.end.character);
const ns = cljParser.getNamespace(document.getText());

return nreplClient.info(currentWord, ns).then(info => {
if (info.doc) {
return Promise.resolve(new vscode.Hover(info.doc));
}
return Promise.reject(undefined);
return cljConnection.sessionForFilename(document.fileName).then(session => {
return nreplClient.info(currentWord, ns, session.id).then(info => {
if (info.doc) {
return Promise.resolve(new vscode.Hover(info.doc));
}
return Promise.reject(undefined);
});
});
}

Expand Down
15 changes: 8 additions & 7 deletions src/clojureSignature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@ export class ClojureSignatureProvider implements vscode.SignatureHelpProvider {
return Promise.reject('No expression found.');

const ns = cljParser.getNamespace(document.getText());
return nreplClient.info(exprInfo.functionName, ns).then(info => {
if (!info.name) // sometimes info brings just a list of suggestions (example: .MAX_VALUE)
return Promise.reject('No signature info found.');
return cljConnection.sessionForFilename(document.fileName).then(session => {
return nreplClient.info(exprInfo.functionName, ns, session.id).then(info => {
if (!info.name) // sometimes info brings just a list of suggestions (example: .MAX_VALUE)
return Promise.reject('No signature info found.');

if (!!info['special-form'])
return Promise.resolve(getSpecialFormSignatureHelp(info, exprInfo.parameterPosition));
if (!!info['special-form'])
return Promise.resolve(getSpecialFormSignatureHelp(info, exprInfo.parameterPosition));

return Promise.resolve(getFunctionSignatureHelp(info, exprInfo.parameterPosition));
return Promise.resolve(getFunctionSignatureHelp(info, exprInfo.parameterPosition));
});
});
}

}

function getSpecialFormSignatureHelp(info: any, parameterPosition: number): vscode.SignatureHelp {
Expand Down
26 changes: 20 additions & 6 deletions src/nreplClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface nREPLInfoMessage {
op: string;
symbol: string;
ns: string;
session: string;
}

interface nREPLEvalMessage {
Expand All @@ -36,37 +37,39 @@ interface nREPLStacktraceMessage {

interface nREPLCloneMessage {
op: string;
session?: string;
}

interface nREPLCloseMessage {
op: string;
session?: string;
}

const complete = (symbol: string, ns: string): Promise<any> => {
const msg: nREPLCompleteMessage = { op: 'complete', symbol, ns };
return send(msg).then(respObjs => respObjs[0]);
};

const info = (symbol: string, ns: string): Promise<any> => {
const msg: nREPLInfoMessage = { op: 'info', symbol, ns };
const info = (symbol: string, ns: string, session?: string): Promise<any> => {
const msg: nREPLInfoMessage = { op: 'info', symbol, ns, session };
return send(msg).then(respObjs => respObjs[0]);
};

const evaluate = (code: string): Promise<any[]> => clone().then((new_session) => {
const evaluate = (code: string, session?: string): Promise<any[]> => clone(session).then((new_session) => {
const session_id = new_session['new-session'];
const msg: nREPLSingleEvalMessage = { op: 'eval', code: code, session: session_id };
return send(msg);
});

const evaluateFile = (code: string, filepath: string): Promise<any[]> => clone().then((new_session) => {
const evaluateFile = (code: string, filepath: string, session?: string): Promise<any[]> => clone(session).then((new_session) => {
const session_id = new_session['new-session'];
const msg: nREPLEvalMessage = { op: 'load-file', file: code, 'file-path': filepath, session: session_id };
return send(msg);
});

const stacktrace = (session: string): Promise<any> => send({ op: 'stacktrace', session: session });

const clone = (): Promise<any[]> => send({ op: 'clone' }).then(respObjs => respObjs[0]);
const clone = (session?: string): Promise<any[]> => send({ op: 'clone', session: session }).then(respObjs => respObjs[0]);

const test = (connectionInfo: CljConnectionInformation): Promise<any[]> => {
return send({ op: 'clone' }, connectionInfo)
Expand All @@ -77,7 +80,16 @@ const test = (connectionInfo: CljConnectionInformation): Promise<any[]> => {
});
};

const close = (): Promise<any[]> => send({ op: 'close' });
const close = (session?: string): Promise<any[]> => send({ op: 'close', session: session });

const listSessions = (): Promise<[string]> => {
return send({op: 'ls-sessions'}).then(respObjs => {
const response = respObjs[0];
if (response.status[0] == "done") {
return Promise.resolve(response.sessions);
}
});
}

const send = (msg: nREPLCompleteMessage | nREPLInfoMessage | nREPLEvalMessage | nREPLStacktraceMessage | nREPLCloneMessage | nREPLCloseMessage | nREPLSingleEvalMessage, connection?: CljConnectionInformation): Promise<any[]> => {
return new Promise<any[]>((resolve, reject) => {
Expand All @@ -87,6 +99,7 @@ const send = (msg: nREPLCompleteMessage | nREPLInfoMessage | nREPLEvalMessage |
return reject('No connection found.');

const client = net.createConnection(connection.port, connection.host);
Object.keys(msg).forEach(key => msg[key] === undefined && delete msg[key]);
client.write(bencodeUtil.encode(msg), 'binary');

client.on('error', error => {
Expand Down Expand Up @@ -131,4 +144,5 @@ export const nreplClient = {
clone,
test,
close,
listSessions
};

0 comments on commit 69fecc7

Please sign in to comment.