Skip to content

Commit e94fec0

Browse files
committed
Add nREPL evaluation status bar indicator
1 parent 38b2207 commit e94fec0

File tree

3 files changed

+131
-7
lines changed

3 files changed

+131
-7
lines changed

src/nrepl/events.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
interface Event {
2+
type: string
3+
}
4+
5+
export class NReplEvaluationStartedEvent implements Event {
6+
type = "started"
7+
constructor(public fileName: string, public filePath: string){}
8+
}
9+
10+
export class NReplEvaluationFinishedEvent implements Event {
11+
type = "finished";
12+
constructor(public fileName: string, public filePath: string, public error?:string){}
13+
}

src/nrepl/index.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import { Event, EventEmitter } from "vscode";
12
import * as net from "net";
23
import { BEncoderStream, BDecoderStream } from "./bencode";
34
import * as state from './../state';
45
import * as replWindow from './../repl-window';
56
import * as util from '../utilities';
67
import { prettyPrint } from '../../out/cljs-lib/cljs-lib';
78
import { PrettyPrintingOptions, disabledPrettyPrinter, getServerSidePrinter } from "../printer";
9+
import { NReplEvaluationStartedEvent, NReplEvaluationFinishedEvent } from "./events";
10+
11+
const eventEmitter = new EventEmitter<NReplEvaluationStartedEvent | NReplEvaluationFinishedEvent>();
12+
export const onNReplEvent = eventEmitter.event;
813

914
/** An nRREPL client */
1015
export class NReplClient {
@@ -29,6 +34,7 @@ export class NReplClient {
2934
ns: string = "user";
3035

3136
private constructor(socket: net.Socket) {
37+
// TODO: Emit client connection events
3238
this.socket = socket;
3339
this.socket.on("error", e => {
3440
console.error(e);
@@ -305,14 +311,27 @@ export class NReplSession {
305311
stderr?: (x: string) => void,
306312
stdout?: (x: string) => void,
307313
pprintOptions: PrettyPrintingOptions
308-
} = {
314+
} = {
309315
pprintOptions: disabledPrettyPrinter
310316
}) {
311317

318+
eventEmitter.fire(new NReplEvaluationStartedEvent(opts.fileName, opts.filePath));
319+
320+
const fireEventOnFin = (
321+
finalize: ((response: string) => any),
322+
createEvent: ((response: string) => any)
323+
) => (response: string) => {
324+
eventEmitter.fire(createEvent(response));
325+
finalize(response);
326+
}
327+
312328
let id = this.client.nextId;
313329
let evaluation = new NReplEvaluation(id, this, opts.stderr, opts.stdout, null, new Promise((resolve, reject) => {
314330
this.messageHandlers[id] = (msg) => {
315-
evaluation.setHandlers(resolve, reject);
331+
evaluation.setHandlers(
332+
fireEventOnFin(resolve, _ => new NReplEvaluationFinishedEvent(opts.fileName, opts.filePath)),
333+
fireEventOnFin(reject, (reason: string) => new NReplEvaluationFinishedEvent(opts.fileName, opts.filePath, reason))
334+
);
316335
if (evaluation.onMessage(msg, opts.pprintOptions)) {
317336
return true;
318337
}
@@ -762,4 +781,4 @@ export class NReplEvaluation {
762781
});
763782
return (num);
764783
}
765-
}
784+
}

src/statusbar/file-type.ts

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,38 @@
11
import { window, StatusBarAlignment, StatusBarItem } from "vscode";
22
import { activeReplWindow } from '../repl-window';
3+
4+
import { onNReplEvent } from "../nrepl";
5+
import { NReplEvaluationStartedEvent, NReplEvaluationFinishedEvent } from "../nrepl/events";
6+
37
import configReader from "../configReader";
48
import * as state from '../state';
59
import * as util from '../utilities';
610

11+
export interface EvaluationError {
12+
fileName: string;
13+
filePath: string;
14+
message: string;
15+
}
16+
717
export class FileTypeStatusBarItem {
818
private statusBarItem: StatusBarItem;
19+
20+
private activeEvals = new Array<string>();
21+
22+
private errors = new Array<EvaluationError>();
23+
24+
private get isEvaluating() {
25+
return this.activeEvals.length > 0;
26+
}
27+
28+
private get hasErrors(){
29+
return this.errors.length > 0;
30+
}
31+
932
constructor(alignment: StatusBarAlignment) {
1033
this.statusBarItem = window.createStatusBarItem(alignment);
34+
// TODO: Event handling and resulting state should be moved to global state
35+
onNReplEvent(this.handleNreplEvent);
1136
}
1237

1338
update() {
@@ -23,19 +48,22 @@ export class FileTypeStatusBarItem {
2348

2449
if(connected) {
2550
if (fileType == 'cljc' && sessionType !== null && !activeReplWindow()) {
26-
text = "cljc/" + sessionType;
51+
text = this.statusTextDecorator("cljc/" + sessionType);
2752
if (util.getSession('clj') !== null && util.getSession('cljs') !== null) {
2853
command = "calva.toggleCLJCSession";
2954
tooltip = `Click to use ${(sessionType === 'clj' ? 'cljs' : 'clj')} REPL for cljc`;
3055
}
3156
} else if (sessionType === 'cljs') {
32-
text = "cljs";
57+
text = this.statusTextDecorator("cljs");
3358
tooltip = "Connected to ClojureScript REPL";
3459
} else if (sessionType === 'clj') {
35-
text = "clj";
60+
text = this.statusTextDecorator("clj");
3661
tooltip = "Connected to Clojure REPL";
3762
}
38-
color = configReader.colors.typeStatus;
63+
color = this.connectedStatusColor();
64+
if(this.hasErrors) {
65+
tooltip = this.errorTooltip();
66+
}
3967
}
4068

4169
this.statusBarItem.command = command;
@@ -45,6 +73,70 @@ export class FileTypeStatusBarItem {
4573
this.statusBarItem.show();
4674
}
4775

76+
private statusTextDecorator(text): string {
77+
if(this.hasErrors) {
78+
const c = this.errors.length;
79+
text = `${text} $(alert) ${c > 1 ? c : ""}`
80+
}
81+
if(this.isEvaluating) {
82+
text = text + " $(gear~spin)";
83+
}
84+
return text;
85+
}
86+
87+
private connectedStatusColor(): string {
88+
const c = configReader.colors;
89+
if(this.hasErrors) {
90+
return c.error;
91+
} else if (this.isEvaluating) {
92+
return c.launching;
93+
}
94+
return c.typeStatus;
95+
}
96+
97+
private errorTooltip(): string {
98+
if(this.errors.length > 1){
99+
return "There are errors in multiple files";
100+
}
101+
const err = this.errors[0];
102+
return `Error: ${err.fileName}: ${err.message}`;
103+
}
104+
105+
private handleNreplEvent = (e: NReplEvaluationStartedEvent | NReplEvaluationFinishedEvent) => {
106+
switch(e.type){
107+
case "started":
108+
this.removeError(e.filePath);
109+
this.activeEvals.push(e.filePath);
110+
break;
111+
case "finished":
112+
this.removeActive(e.filePath);
113+
const fe = <NReplEvaluationFinishedEvent> e;
114+
if(fe.error) {
115+
this.errors.push({
116+
fileName: fe.fileName,
117+
filePath: fe.filePath,
118+
message: fe.error
119+
});
120+
}
121+
break;
122+
}
123+
this.update();
124+
}
125+
126+
private removeActive(filePath: string) {
127+
const idx = this.activeEvals.indexOf(filePath);
128+
if(idx !== -1) {
129+
this.activeEvals.splice(idx, 1);
130+
}
131+
}
132+
133+
private removeError(filePath: string) {
134+
const idx = this.errors.findIndex(e => e.filePath === filePath);
135+
if(idx !== -1) {
136+
this.errors.splice(idx, 1);
137+
}
138+
}
139+
48140
dispose() {
49141
this.statusBarItem.dispose();
50142
}

0 commit comments

Comments
 (0)