Skip to content

Commit

Permalink
Initial ClojureScript support.
Browse files Browse the repository at this point in the history
This change adds basic ClojureScript support. When connecting to the REPL we
look for a CLJS session and if we find one we store it and use it to eval
ClojureScript code.
  • Loading branch information
alessandrod committed Sep 5, 2017
1 parent 9b99a1a commit 65ddcd8
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 30 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);
}
2 changes: 1 addition & 1 deletion src/clojureFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ export const formatFile = (textEditor: vscode.TextEditor, edit: vscode.TextEdito
});
};
});
}
}
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
10 changes: 10 additions & 0 deletions src/nreplClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ const test = (connectionInfo: CljConnectionInformation): Promise<any[]> => {

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) => {
connection = connection || cljConnection.getConnection();
Expand Down Expand Up @@ -135,4 +144,5 @@ export const nreplClient = {
clone,
test,
close,
listSessions
};

0 comments on commit 65ddcd8

Please sign in to comment.