Skip to content

Commit 709be68

Browse files
authored
feat(vscode): allow invoking Dart Frog daemon (#925)
1 parent 89c18a9 commit 709be68

File tree

2 files changed

+442
-4
lines changed

2 files changed

+442
-4
lines changed

extensions/vscode/src/daemon/dart-frog-daemon.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1+
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
2+
import {
3+
DaemonEvent,
4+
DaemonMessage,
5+
DaemonRequest,
6+
DaemonResponse,
7+
isDaemonEvent,
8+
isDaemonRequest,
9+
isDaemonResponse,
10+
isReadyDaemonEvent,
11+
} from "./protocol";
12+
import { EventEmitter } from "events";
13+
14+
/**
15+
* The types of events that are emitted by the {@link DartFrogDaemon}.
16+
*
17+
* The possible types of events are:
18+
* - "request": When a request is sent to the Dart Frog daemon. The
19+
* {@link DaemonRequest} is passed as an argument to the event handler.
20+
* - "response": When a response is received from the Dart Frog daemon. The
21+
* {@link DaemonResponse} is passed as an argument to the event handler.
22+
* - "event": When an event is received from the Dart Frog daemon. The
23+
* {@link DaemonEvent} is passed as an argument to the event handler.
24+
*/
25+
export enum DartFrogDaemonEventEmitterTypes {
26+
request = "request",
27+
response = "response",
28+
event = "event",
29+
}
30+
131
/**
232
* The Dart Frog daemon is a long-running process that is responsible for
333
* managing a single or multiple Dart Frog projects simultaneously.
@@ -15,4 +45,128 @@ export class DartFrogDaemon {
1545
public static get instance() {
1646
return this._instance || (this._instance = new this());
1747
}
48+
49+
private daemonMessagesEventEmitter = new EventEmitter();
50+
51+
/**
52+
* The process that is running the Dart Frog daemon.
53+
*
54+
* Undefined until the Dart Frog daemon is {@link invoke}d.
55+
*/
56+
private process: ChildProcessWithoutNullStreams | undefined;
57+
58+
private _isReady: boolean = false;
59+
60+
/**
61+
* Whether the Dart Frog daemon is ready to accept requests.
62+
*
63+
* The Dart Frog daemon is ready to accept requests when it has emmitted
64+
* the "ready" event after being {@link invoke}d.
65+
*
66+
* @see {@link invoke} to invoke the Dart Frog daemon.
67+
*/
68+
public get isReady(): boolean {
69+
return this._isReady;
70+
}
71+
72+
/**
73+
* Invokes the Dart Frog daemon.
74+
*
75+
* If the Dart Frog daemon is already running, this method will immediately
76+
* return.
77+
*
78+
* After invoking the Dart Frog daemon, it will be ready to accept requests.
79+
*
80+
* @param workingDirectory The working directory of the Dart Frog daemon,
81+
* usually the root directory of the Dart Frog project.
82+
*/
83+
public async invoke(workingDirectory: string): Promise<void> {
84+
if (this.isReady || this.process) {
85+
return Promise.resolve();
86+
}
87+
88+
const readyPromise = new Promise<void>((resolve) => {
89+
const readyEventListener = (message: DaemonEvent) => {
90+
if (!this._isReady && isReadyDaemonEvent(message)) {
91+
this._isReady = true;
92+
resolve();
93+
this.off(DartFrogDaemonEventEmitterTypes.event, readyEventListener);
94+
}
95+
};
96+
this.on(
97+
DartFrogDaemonEventEmitterTypes.event,
98+
readyEventListener.bind(this)
99+
);
100+
});
101+
102+
this.process = spawn("dart_frog", ["daemon"], {
103+
cwd: workingDirectory,
104+
});
105+
this.process.stdout.on("data", this.stdoutDataListener.bind(this));
106+
107+
return readyPromise;
108+
}
109+
110+
/**
111+
* Decodes the stdout and emits events accordingly via the
112+
* {@link daemonMessagesEventEmitter}.
113+
*
114+
* @param data The data that was received from the stdout of the Dart Frog
115+
* Daemon.
116+
* @see {@link daemonMessagesEventEmitter} for listening to the events that
117+
* are emitted.
118+
*/
119+
private stdoutDataListener(data: Buffer): void {
120+
const daemonMessages = DaemonMessage.decode(data);
121+
for (const message of daemonMessages) {
122+
if (isDaemonEvent(message)) {
123+
this.daemonMessagesEventEmitter.emit(
124+
DartFrogDaemonEventEmitterTypes.event,
125+
message
126+
);
127+
} else if (isDaemonResponse(message)) {
128+
this.daemonMessagesEventEmitter.emit(
129+
DartFrogDaemonEventEmitterTypes.response,
130+
message
131+
);
132+
} else if (isDaemonRequest(message)) {
133+
this.daemonMessagesEventEmitter.emit(
134+
DartFrogDaemonEventEmitterTypes.request,
135+
message
136+
);
137+
}
138+
}
139+
}
140+
141+
/**
142+
* Starts listening to events related to this Dart Frog daemon.
143+
*
144+
* @returns A reference to this Dart Frog daemon, so that calls can be
145+
* chained.
146+
* @see {@link DartFrogDaemonEventEmitterTypes} for the types of events that
147+
* are emitted.
148+
*/
149+
public on(
150+
type: DartFrogDaemonEventEmitterTypes,
151+
listener: (...args: any[]) => void
152+
): DartFrogDaemon {
153+
this.daemonMessagesEventEmitter.on(type, listener);
154+
return this;
155+
}
156+
157+
/**
158+
* Unsubscribes a listener from events related to this Dart Frog daemon.
159+
*
160+
* @param type The type of event to unsubscribe from.
161+
* @param listener The listener to unsubscribe.
162+
* @returns A reference to this Dart Frog daemon, so that calls can be
163+
* chained.
164+
*/
165+
public off(
166+
type: DartFrogDaemonEventEmitterTypes,
167+
listener: (...args: any[]) => void
168+
): DartFrogDaemon {
169+
this.daemonMessagesEventEmitter.off(type, listener);
170+
return this;
171+
}
18172
}

0 commit comments

Comments
 (0)