Skip to content

Commit b01839a

Browse files
committed
Merge remote-tracking branch 'origin/master' into jupyter-llm-8500
2 parents 84e0482 + c19ad9b commit b01839a

File tree

10 files changed

+319
-128
lines changed

10 files changed

+319
-128
lines changed

src/packages/frontend/frame-editors/terminal-editor/conat-terminal.ts

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
SIZE_TIMEOUT_MS,
1111
createBrowserClient,
1212
} from "@cocalc/conat/service/terminal";
13-
import { CONAT_OPEN_FILE_TOUCH_INTERVAL } from "@cocalc/util/conat";
1413
import { until } from "@cocalc/util/async-utils";
1514

1615
type State = "disconnected" | "init" | "running" | "closed";
@@ -24,7 +23,7 @@ export class ConatTerminal extends EventEmitter {
2423
private terminalResize;
2524
private openPaths;
2625
private closePaths;
27-
private api: TerminalServiceApi;
26+
public readonly api: TerminalServiceApi;
2827
private service?;
2928
private options?;
3029
private writeQueue: string = "";
@@ -58,7 +57,6 @@ export class ConatTerminal extends EventEmitter {
5857
this.path = path;
5958
this.termPath = termPath;
6059
this.options = options;
61-
this.touchLoop({ project_id, path: termPath });
6260
this.sizeLoop(measureSize);
6361
this.api = createTerminalClient({ project_id, termPath });
6462
this.createBrowserService();
@@ -94,6 +92,7 @@ export class ConatTerminal extends EventEmitter {
9492
await this.init();
9593
return;
9694
}
95+
9796
if (typeof data != "string") {
9897
if (data.cmd == "size") {
9998
const { rows, cols, kick } = data;
@@ -136,29 +135,8 @@ export class ConatTerminal extends EventEmitter {
136135
await this.api.write(this.writeQueue + data);
137136
this.writeQueue = "";
138137
} catch {
139-
if (data) {
140-
this.writeQueue += data;
141-
}
142-
}
143-
}
144-
};
145-
146-
touchLoop = async ({ project_id, path }) => {
147-
while (this.state != ("closed" as State)) {
148-
try {
149-
// this marks the path as being of interest for editing and starts
150-
// the service; it doesn't actually create a file on disk.
151-
await webapp_client.touchOpenFile({
152-
project_id,
153-
path,
154-
});
155-
} catch (err) {
156-
console.warn(err);
138+
this.close();
157139
}
158-
if (this.state == ("closed" as State)) {
159-
break;
160-
}
161-
await delay(CONAT_OPEN_FILE_TOUCH_INTERVAL);
162140
}
163141
};
164142

@@ -169,7 +147,7 @@ export class ConatTerminal extends EventEmitter {
169147
}
170148
};
171149

172-
close = async () => {
150+
close = () => {
173151
webapp_client.conat_client.removeListener(
174152
"connected",
175153
this.clearWriteQueue,
@@ -183,12 +161,14 @@ export class ConatTerminal extends EventEmitter {
183161
this.service?.close();
184162
delete this.service;
185163
this.setState("closed");
186-
try {
187-
await this.api.close(webapp_client.browser_id);
188-
} catch {
189-
// we did our best to quickly tell that we're closed, but if it times out or fails,
190-
// it is the responsibility of the project to stop worrying about this browser.
191-
}
164+
(async () => {
165+
try {
166+
await this.api.close(webapp_client.browser_id);
167+
} catch {
168+
// we did our best to quickly tell that we're closed, but if it times out or fails,
169+
// it is the responsibility of the project to stop worrying about this browser.
170+
}
171+
})();
192172
};
193173

194174
end = () => {
@@ -270,7 +250,7 @@ export class ConatTerminal extends EventEmitter {
270250
return;
271251
}
272252
const initData = this.stream.getAll().join("");
273-
this.emit("init", initData);
253+
this.emit("initialize", initData);
274254
this.stream.on("change", this.handleStreamData);
275255
};
276256

src/packages/frontend/frame-editors/terminal-editor/connected-terminal.ts

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,21 @@ declare const $: any;
4646
const SCROLLBACK = 5000;
4747
const MAX_HISTORY_LENGTH = 100 * SCROLLBACK;
4848

49-
const MAX_DELAY = 15000;
49+
const ENABLE_WEBGL = true;
5050

51-
const ENABLE_WEBGL = false;
51+
// ephemeral = faster, less load on servers, but if project and browser all
52+
// close, the history is gone... which may be good and less confusing.
53+
const EPHEMERAL = true;
5254

5355
interface Path {
5456
file?: string;
5557
directory?: string;
5658
}
5759

60+
type State = "ready" | "closed";
61+
5862
export class Terminal<T extends CodeEditorState = CodeEditorState> {
59-
private state: string = "ready";
63+
private state: State = "ready";
6064
private actions: Actions<T> | ConnectedTerminalInterface;
6165
private account_store: any;
6266
private project_actions: ProjectActions;
@@ -71,9 +75,9 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
7175
private pauseKeyCount: number = 0;
7276
private keyhandler_initialized: boolean = false;
7377
// last time user typed something
74-
private lastSend = 0;
78+
// private lastSend = 0;
7579
// last time we received data back from project
76-
private lastReceive = 0;
80+
// private lastReceive = 0;
7781
/* We initially have to ignore when rendering the initial history.
7882
To TEST this, do this in a terminal, then reconnect:
7983
printf "\E[c\n" ; sleep 1 ; echo
@@ -118,7 +122,6 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
118122
workingDir?: string,
119123
) {
120124
this.actions = actions;
121-
122125
this.account_store = redux.getStore("account");
123126
this.project_actions = redux.getProjectActions(actions.project_id);
124127
if (this.account_store == null) {
@@ -181,7 +184,7 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
181184
this.init_settings();
182185
this.init_touch();
183186
this.set_connection_status("disconnected");
184-
this.reconnectIfNotResponding();
187+
// this.reconnectIfNotResponding();
185188

186189
// The docs https://xtermjs.org/docs/api/terminal/classes/terminal/#resize say
187190
// "It’s best practice to debounce calls to resize, this will help ensure that
@@ -191,6 +194,8 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
191194
// this.terminal_resize = debounce(this.terminal_resize, 2000);
192195
}
193196

197+
isClosed = () => (this.state ?? "closed") === "closed";
198+
194199
private get_xtermjs_options = (): any => {
195200
const rendererType = this.rendererType;
196201
const settings = this.account_store.get("terminal");
@@ -216,13 +221,13 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
216221
};
217222

218223
private assert_not_closed = (): void => {
219-
if (this.state === "closed") {
224+
if (this.isClosed()) {
220225
throw Error("BUG -- Terminal is closed.");
221226
}
222227
};
223228

224229
close = (): void => {
225-
if (this.state === "closed") {
230+
if (this.isClosed()) {
226231
return;
227232
}
228233
this.set_connection_status("disconnected");
@@ -279,7 +284,7 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
279284
connect = async () => {
280285
try {
281286
if (this.conn != null) {
282-
this.conn.removeListener("close", this.connect); // avoid infinite loop
287+
this.conn.removeListener("closed", this.connect); // avoid infinite loop
283288
this.conn.close();
284289
delete this.conn;
285290
}
@@ -309,15 +314,16 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
309314
cwd: this.workingDir,
310315
env: this.actions.get_term_env(),
311316
},
317+
ephemeral: EPHEMERAL,
312318
});
313319
this.conn = conn as any;
314-
conn.on("close", this.connect);
320+
conn.once("closed", this.connect);
315321
conn.on("kick", this.close_request);
316-
conn.on("cwd", (cwd) => {
317-
this.actions.set_terminal_cwd(this.id, cwd);
318-
});
319322
conn.on("data", this.handleDataFromProject);
320-
conn.on("init", this.render);
323+
conn.once("initialize", (data) => {
324+
this.terminal.clear();
325+
this.render(data);
326+
});
321327
conn.once("ready", () => {
322328
delete this.last_geom;
323329
this.ignore_terminal_data = false;
@@ -362,19 +368,19 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
362368
return;
363369
}
364370
this.conn.write(data);
365-
this.lastSend = Date.now();
371+
//this.lastSend = Date.now();
366372
};
367373

368374
// this should never ever be necessary. It's a just-in-case things
369375
// were myseriously totally broken measure...
370-
private reconnectIfNotResponding = async () => {
371-
while (this.state != "closed") {
372-
if (this.lastSend - this.lastReceive >= MAX_DELAY) {
373-
await this.connect();
374-
}
375-
await delay(MAX_DELAY / 2);
376-
}
377-
};
376+
// private reconnectIfNotResponding = async () => {
377+
// while (this.state != "closed") {
378+
// if (this.lastSend - this.lastReceive >= MAX_DELAY) {
379+
// await this.connect();
380+
// }
381+
// await delay(MAX_DELAY / 2);
382+
// }
383+
// };
378384

379385
private handleDataFromProject = (data: any): void => {
380386
this.assert_not_closed();
@@ -390,15 +396,14 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
390396
};
391397

392398
private activity = () => {
393-
this.lastReceive = Date.now();
399+
//this.lastReceive = Date.now();
394400
this.project_actions.flag_file_activity(this.path);
395401
};
396402

397403
private render = async (data: string): Promise<void> => {
398-
if (data == null) {
404+
if (data == null || this.isClosed()) {
399405
return;
400406
}
401-
this.assert_not_closed();
402407
this.history += data;
403408
if (this.history.length > MAX_HISTORY_LENGTH) {
404409
this.history = this.history.slice(
@@ -420,7 +425,7 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
420425
await delay(0);
421426
this.ignoreData--;
422427
}
423-
if (this.state == "done") return;
428+
if (this.isClosed()) return;
424429
// tell anyone who waited for output coming back about this
425430
while (this.render_done.length > 0) {
426431
this.render_done.pop()?.();
@@ -438,7 +443,7 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
438443
this.terminal.onTitleChange((title) => {
439444
if (title != null) {
440445
this.actions.set_title(this.id, title);
441-
this.ask_for_cwd();
446+
this.update_cwd();
442447
}
443448
});
444449
};
@@ -450,7 +455,7 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
450455
};
451456

452457
touch = async () => {
453-
if (this.state === "closed") return;
458+
if (this.isClosed()) return;
454459
if (Date.now() - this.last_active < 70000) {
455460
if (this.project_actions.isTabClosed()) {
456461
return;
@@ -464,7 +469,7 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
464469
};
465470

466471
init_keyhandler = (): void => {
467-
if (this.state === "closed") {
472+
if (this.isClosed()) {
468473
return;
469474
}
470475
if (this.keyhandler_initialized) {
@@ -575,7 +580,7 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
575580
// Stop ignoring terminal data... but ONLY once
576581
// the render buffer is also empty.
577582
no_ignore = async (): Promise<void> => {
578-
if (this.state === "closed") {
583+
if (this.isClosed()) {
579584
return;
580585
}
581586
const g = (cb) => {
@@ -587,7 +592,7 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
587592
}
588593
// cause render to actually appear now.
589594
await delay(0);
590-
if (this.state === "closed") {
595+
if (this.isClosed()) {
591596
return;
592597
}
593598
try {
@@ -720,9 +725,23 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
720725
this.render_buffer = "";
721726
};
722727

723-
ask_for_cwd = debounce((): void => {
724-
this.conn_write({ cmd: "cwd" });
725-
});
728+
update_cwd = debounce(
729+
async () => {
730+
if (this.isClosed()) return;
731+
let cwd;
732+
try {
733+
cwd = await this.conn?.api.cwd();
734+
} catch {
735+
return;
736+
}
737+
if (this.isClosed()) return;
738+
if (cwd != null) {
739+
this.actions.set_terminal_cwd(this.id, cwd);
740+
}
741+
},
742+
1000,
743+
{ leading: true, trailing: true },
744+
);
726745

727746
kick_other_users_out(): void {
728747
// @ts-ignore
@@ -760,14 +779,14 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
760779
}
761780

762781
focus(): void {
763-
if (this.state === "closed") {
782+
if (this.isClosed()) {
764783
return;
765784
}
766785
this.terminal.focus();
767786
}
768787

769788
refresh(): void {
770-
if (this.state === "closed") {
789+
if (this.isClosed()) {
771790
return;
772791
}
773792
this.terminal.refresh(0, this.terminal.rows - 1);
@@ -777,7 +796,7 @@ export class Terminal<T extends CodeEditorState = CodeEditorState> {
777796
try {
778797
await open_init_file(this.actions._get_project_actions(), this.termPath);
779798
} catch (err) {
780-
if (this.state === "closed") {
799+
if (this.isClosed()) {
781800
return;
782801
}
783802
this.actions.set_error(`Problem opening init file -- ${err}`);

src/packages/frontend/jupyter/cell-list.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { Cell } from "./cell";
3737
import HeadingTagComponent from "./heading-tag";
3838

3939
interface StableHtmlContextType {
40+
enabled?: boolean;
4041
cellListDivRef?: MutableRefObject<any>;
4142
scrollOrResize?: { [key: string]: () => void };
4243
}
@@ -571,7 +572,9 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
571572

572573
if (use_windowed_list) {
573574
body = (
574-
<StableHtmlContext.Provider value={{ cellListDivRef, scrollOrResize }}>
575+
<StableHtmlContext.Provider
576+
value={{ cellListDivRef, scrollOrResize, enabled: true }}
577+
>
575578
<div ref={cellListDivRef} className="smc-vfill">
576579
<Virtuoso
577580
ref={virtuosoRef}

0 commit comments

Comments
 (0)