Skip to content

Commit 665035b

Browse files
committed
Add: simple sync between IDE and Client.
1 parent df5fe5b commit 665035b

File tree

6 files changed

+257
-100
lines changed

6 files changed

+257
-100
lines changed

client/src/CodeChatEditor.mts

Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ import {
5656
CodeMirror_save,
5757
mathJaxTypeset,
5858
mathJaxUnTypeset,
59+
scroll_to_line,
60+
set_CodeMirror_positions,
5961
} from "./CodeMirror-integration.mjs";
6062
import "./EditorComponents.mjs";
6163
import "./graphviz-webcomponent-setup.mts";
@@ -114,8 +116,12 @@ declare global {
114116
interface Window {
115117
CodeChatEditor: {
116118
// Called by the Client Framework.
117-
open_lp: (code_chat_for_web: CodeChatForWeb) => Promise<void>;
119+
open_lp: (
120+
codechat_for_web: CodeChatForWeb,
121+
cursor_position?: number,
122+
) => Promise<void>;
118123
on_save: (_only_if_dirty: boolean) => Promise<void>;
124+
scroll_to_line: (line: number) => void;
119125
show_toast: (text: string) => void;
120126
allow_navigation: boolean;
121127
};
@@ -182,6 +188,7 @@ export const page_init = () => {
182188
window.CodeChatEditor = {
183189
open_lp,
184190
on_save,
191+
scroll_to_line,
185192
show_toast,
186193
allow_navigation: false,
187194
};
@@ -213,10 +220,17 @@ const is_doc_only = () => {
213220
};
214221

215222
// Wait for the DOM to load before opening the file.
216-
const open_lp = async (code_chat_for_web: CodeChatForWeb) =>
217-
on_dom_content_loaded(() => _open_lp(code_chat_for_web));
218-
219-
// Store the HTML sent for CodeChat Editor documents. We can't simply use TinyMCE's [getContent](https://www.tiny.cloud/docs/tinymce/latest/apis/tinymce.editor/#getContent), since this modifies the content based on cleanup rules before returning it -- which causes applying diffs to this unexpectedly modified content to produce incorrect results. This text is the unmodified content sent from the IDE.
223+
const open_lp = async (
224+
codechat_for_web: CodeChatForWeb,
225+
cursor_position?: number,
226+
) => on_dom_content_loaded(() => _open_lp(codechat_for_web, cursor_position));
227+
228+
// Store the HTML sent for CodeChat Editor documents. We can't simply use
229+
// TinyMCE's
230+
// [getContent](https://www.tiny.cloud/docs/tinymce/latest/apis/tinymce.editor/#getContent),
231+
// since this modifies the content based on cleanup rules before returning it --
232+
// which causes applying diffs to this unexpectedly modified content to produce
233+
// incorrect results. This text is the unmodified content sent from the IDE.
220234
let doc_content = "";
221235

222236
// This function is called on page load to "load" a file. Before this point, the
@@ -226,7 +240,8 @@ let doc_content = "";
226240
const _open_lp = async (
227241
// A data structure provided by the server, containing the source and
228242
// associated metadata. See[`AllSource`](#AllSource).
229-
code_chat_for_web: CodeChatForWeb,
243+
codechat_for_web: CodeChatForWeb,
244+
cursor_position?: number,
230245
) => {
231246
// Use[URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
232247
// to parse out the search parameters of this window's URL.
@@ -241,16 +256,18 @@ const _open_lp = async (
241256
const editorMode = EditorMode[urlParams.get("mode") ?? "edit"];
242257

243258
// Get the<code><a href="#current_metadata">current_metadata</a></code> from
244-
// the provided `code_chat_for_web` struct and store it as a global variable.
245-
current_metadata = code_chat_for_web["metadata"];
246-
const source = code_chat_for_web["source"];
259+
// the provided `code_chat_for_web` struct and store it as a global
260+
// variable.
261+
current_metadata = codechat_for_web["metadata"];
262+
const source = codechat_for_web["source"];
247263
const codechat_body = document.getElementById(
248264
"CodeChat-body",
249265
) as HTMLDivElement;
250266
// Disable autosave when updating the document.
251267
autosaveEnabled = false;
252268
clearAutosaveTimer();
253-
// Before calling any MathJax, make sure it's fully loaded and the initial render is finished.
269+
// Before calling any MathJax, make sure it's fully loaded and the initial
270+
// render is finished.
254271
await window.MathJax.startup.promise;
255272
// Per
256273
// the[docs](https://docs.mathjax.org/en/latest/web/typeset.html#updating-previously-typeset-content),
@@ -302,7 +319,12 @@ const _open_lp = async (
302319
}
303320
mathJaxTypeset(codechat_body);
304321
} else {
305-
await CodeMirror_load(codechat_body, source, current_metadata.mode, []);
322+
await CodeMirror_load(
323+
codechat_body,
324+
codechat_for_web,
325+
[],
326+
cursor_position,
327+
);
306328
}
307329
autosaveEnabled = true;
308330

@@ -315,45 +337,48 @@ const _open_lp = async (
315337
}
316338
};
317339

318-
const save_lp = () => {
319-
/// @ts-expect-error
320-
let code_mirror_diffable: CodeMirrorDiffable = {};
321-
if (is_doc_only()) {
322-
// Untypeset all math before saving the document.
323-
const codechat_body = document.getElementById(
324-
"CodeChat-body",
325-
) as HTMLDivElement;
326-
mathJaxUnTypeset(codechat_body);
327-
// To save a document only, simply get the HTML from the only Tiny MCE
328-
// div.
329-
tinymce.activeEditor!.save();
330-
const html = tinymce.activeEditor!.getContent();
331-
(
332-
code_mirror_diffable as {
333-
Plain: CodeMirror;
334-
}
335-
).Plain = {
336-
doc: turndownService.turndown(html),
337-
doc_blocks: [],
338-
};
339-
// Retypeset all math after saving the document.
340-
mathJaxTypeset(codechat_body);
341-
} else {
342-
code_mirror_diffable = CodeMirror_save();
343-
assert("Plain" in code_mirror_diffable);
344-
codechat_html_to_markdown(code_mirror_diffable.Plain.doc_blocks);
345-
}
346-
340+
const save_lp = (is_dirty: boolean) => {
347341
let update: UpdateMessageContents = {
348342
// The Framework will fill in this value.
349343
file_path: "",
350-
contents: {
344+
};
345+
set_CodeMirror_positions(update);
346+
347+
// Add the contents only if the document is dirty.
348+
if (is_dirty) {
349+
/// @ts-expect-error
350+
let code_mirror_diffable: CodeMirrorDiffable = {};
351+
if (is_doc_only()) {
352+
// Untypeset all math before saving the document.
353+
const codechat_body = document.getElementById(
354+
"CodeChat-body",
355+
) as HTMLDivElement;
356+
mathJaxUnTypeset(codechat_body);
357+
// To save a document only, simply get the HTML from the only Tiny MCE
358+
// div.
359+
tinymce.activeEditor!.save();
360+
const html = tinymce.activeEditor!.getContent();
361+
(
362+
code_mirror_diffable as {
363+
Plain: CodeMirror;
364+
}
365+
).Plain = {
366+
doc: turndownService.turndown(html),
367+
doc_blocks: [],
368+
};
369+
// Retypeset all math after saving the document.
370+
mathJaxTypeset(codechat_body);
371+
} else {
372+
code_mirror_diffable = CodeMirror_save();
373+
assert("Plain" in code_mirror_diffable);
374+
codechat_html_to_markdown(code_mirror_diffable.Plain.doc_blocks);
375+
}
376+
update.contents = {
351377
metadata: current_metadata,
352378
source: code_mirror_diffable,
353-
},
354-
scroll_position: null,
355-
cursor_position: null,
356-
};
379+
};
380+
}
381+
357382
return update;
358383
};
359384

@@ -376,7 +401,9 @@ const on_save = async (only_if_dirty: boolean = false) => {
376401
const webSocketComm = parent.window.CodeChatEditorFramework.webSocketComm;
377402
console.log("Sent Update - saving document.");
378403
await new Promise(async (resolve) => {
379-
webSocketComm.send_message({ Update: save_lp() }, () => resolve(0));
404+
webSocketComm.send_message({ Update: save_lp(is_dirty) }, () =>
405+
resolve(0),
406+
);
380407
});
381408
is_dirty = false;
382409
};

client/src/CodeChatEditorFramework.mts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -135,27 +135,32 @@ class WebSocketComm {
135135
this.send_result(id, msg);
136136
break;
137137
}
138-
let result = null;
139138
const contents = current_update.contents;
140-
if (contents !== null && contents !== undefined) {
139+
const cursor_position = current_update.cursor_position;
140+
if (contents !== undefined) {
141141
// If the page is still loading, wait until the load
142142
// completed before updating the editable contents.
143143
if (this.onloading) {
144144
root_iframe!.onload = () => {
145-
set_content(contents);
145+
set_content(
146+
contents,
147+
current_update.cursor_position,
148+
);
146149
this.onloading = false;
147150
};
148151
} else {
149-
set_content(contents);
152+
set_content(
153+
contents,
154+
current_update.cursor_position,
155+
);
150156
}
151-
} else {
152-
// TODO: handle scroll/cursor updates.
153-
report_error(
154-
`Unhandled Update message: ${current_update}`,
157+
} else if (cursor_position !== undefined) {
158+
root_iframe!.contentWindow!.CodeChatEditor.scroll_to_line(
159+
cursor_position,
155160
);
156161
}
157162

158-
this.send_result(id, result);
163+
this.send_result(id, null);
159164
break;
160165

161166
case "CurrentFile":
@@ -313,7 +318,7 @@ const get_client = () => root_iframe?.contentWindow?.CodeChatEditor;
313318

314319
// Assign content to either the Client (if it's loaded) or the webpage (if not)
315320
// in the `root_iframe`.
316-
const set_content = (contents: CodeChatForWeb) => {
321+
const set_content = (contents: CodeChatForWeb, cursor_position?: number) => {
317322
let client = get_client();
318323
if (client === undefined) {
319324
// See if this is the [simple viewer](#Client-simple-viewer). Otherwise,
@@ -329,7 +334,10 @@ const set_content = (contents: CodeChatForWeb) => {
329334
cw.document.write(contents.source.Plain.doc);
330335
cw.document.close();
331336
} else {
332-
root_iframe!.contentWindow!.CodeChatEditor.open_lp(contents);
337+
root_iframe!.contentWindow!.CodeChatEditor.open_lp(
338+
contents,
339+
cursor_position,
340+
);
333341
}
334342
};
335343

0 commit comments

Comments
 (0)