CodeChat_client.js - Core code for the CodeChat Client¶
Constants¶
These must match the constants in the server.
A regex to match a percentage: one or more digits, optionally followed by a period and one or more digits, and ending with a percent sign.
Capture the number in front of the percent sign.
Look for one or more digits, …
Optionally followed by a decimal point and one ore more digits. (Don’t include these in a capture group.)
End the capture group, then require a percent sign.
Search globally (for all matches).
Core client¶
Given an ID to use, run the CodeChat Client.
The ID of the CodeChat Client for this window.
The port to use for a websocket connection to the CodeChat Server.
Set up variables used by the functions below¶
Get commonly-used nodes in the DOM.
const status_message = document.getElementById("status_message");
const build_progress = document.getElementById("build_progress");
const status_errors_div = document.getElementById("status_errors");
const outputElement = document.getElementById("output");
const build_div = document.getElementById("build");
const build_contents = document.getElementById("build_contents");
const errors_div = document.getElementById("errors");
True if the output/errors should be cleared on the next result.
True if the most recent reload was caused by the user; false if this was a programmatic reload.
True if there were warnings or errors in the last build. Initialize to true so that the build messages pane is visible initially.
The size of the splitter, with a key of errors_or_warnings
.
Core code¶
Create a websocket to communicate with the CodeChat Server.
If the hosting page uses HTTPS, then use a secure websocket (WSS protocol); otherwise, use an insecure websocket (WS).
Compute the URI for this websocket, dealing with special cases for CoCalc and GitHub Codespaces.
A special case for CoCalc: use a different URL per the CoCalc docs.
The pathname is all but the last one or two elements of the hosting page’s pathname: transform /a/long/path/to/the/client
to /a/long/path/to/the
. For CoCalc, this transforms /f1d3f8ac-39da-48fe-9357-7d5c4ee132de/server/27377/client
into /f1d3f8ac-39da-48fe-9357-7d5c4ee132de/server
.
Transform the hosting page’s URL for the websocket. For example, transform from http://foo.org/client?id=0
into ws://foo.org:27377
.
We’re using GitHub CodeSpaces, where the window.location
is (for example) https://bjones1-organic-goggles-wv46gpj9x4cv5qq-27377.preview.app.github.dev/client?id=0
. The desired URI for this example is wss://bjones1-organic-goggles-wv46gpj9x4cv5qq-27378.preview.app.github.dev
.
Throw away everything after the last hyphen.
Throw away everything before the first period.
Now, build the desired URI.
Identify this client on connection.
Provide logging to help track down errors.
Handle messages.
Save and restore scroll location through the content update, if we can.
See ideas in https://stackoverflow.com/a/16822995. Works for same-domain only.
Only send a message if the navigation occurs in the local server.
Restore the scroll location if we were able to save it.
The programmatic reload is done – anything else that happens now is from the user.
Get new content only after the load finishes. The case to avoid:
One load stores the x, y coordinates and begins to load a new page. This sets the scroll bars to 0.
Before the load finishes, another request comes. The x, y coordinates are saved as 0.
The first load finishes, but scrolls to 0, 0; the second load finish does the same.
Set the new src to (re)load content. At startup, the srcdoc
attribute shows some welcome text. Remove it so that we can now assign the src
attribute.
Windows pathnames don’t begin with a slash; Linux/Mac pathnames do. Avoid repeating the slash on Linux/Mac to get prettier URLs.
The next build output received will apply to the new build, so set the flag.
See comments above – avoid double loads and indicate that the is a programmatic reload.
Exit for now; the callbacks above will continue this function’s operation.
Omit this logging, since it’s usually obvious from the lines below.
This is the start of a new build.
Save the current splitter state.
class building: Show that the current output HTML is old.
Look for a percentage, to update the progress bar.
Update the progress bar with the percentage from the last match.
Scroll to the bottom, to show the content just added.
There was no build output, so just update the errors.
Save the current splitter state.
Scroll to the bottom, to show the content just added.
Update the errors/warnings and splitter position.
[
errors_div.innerHTML,
status_errors_div.innerHTML,
errors_or_warnings,
] = parse_for_errors(errors_div.innerHTML);
set_splitter_percent(splitter_size[errors_or_warnings]);
} else if (result.get_result_type === GetResultType.command) {
console.log(`CodeChat Client: command ${result.text} received.`);
if (result.text === "shutdown") {
Close this window – see https://stackoverflow.com/a/54787080. See close the window.
Stop asking for results.
Close the window – there’s no point in asking for more commands. Note: there are some cases where this fails, although I’ve only seen this once. From the Chrome console, Scripts may close only the windows that were opened by them.
In this case, leave a message asking the user to close the window.
outputElement.srcdoc =
"<html><body><p>CodeChat Client error: lost connection to the CodeChat Server. Close this window and restart the editor plug-in.</p></body></html>";
build_div.textContent = "";
errors_div.textContent = "";
status_message.innerHTML = "Server disconnected";
window.open("", "_self").close();
Stop asking for results.
Provide utility functions for use by the core functions above¶
Return the X and Y coordinates of the scrollbar of the output iframe if possible.
This is only allowed for same-domain origins – for example, if the user clicks on a link in the docs that goes to an external website, then the following lines will raise an exception.
Send a message to the CodeChat server.
A string, identifying the type of message.
Message-specific data to send. Must be JSON-encodable.
These functions define messages sent by the CodeChat Client, which are handled by read_websocket_handler. Defining them this way makes them accessible to the iframe and globally (to functions called outside this function). See below.
Save data to the filesystem. Right now, only used for pretext editing.
The var
statement below makes some of these accessible globally.
Globally-accessible functions (see above). These must be declared as var
(not let
) due to (I assume) scoping rules I don’t understand.
Utilities¶
Get the splitter element.
Set the splitter percentage
Scroll an element to the bottom
This regex parses the error string to determine get the number of warnings and errors.
prettier-ignore
Don’t capture this group (the ?:
).
Common docutils error messages read:
1<string>:1589: (ERROR/3) Unknown interpreted text role "ref".
2X:\ode.py:docstring of sympy:5: (ERROR/3) Unexpected indentation.
Common Sphinx errors read:
1X:\SVM_train.m.rst:2: SEVERE: Title overline & underline mismatch.
2X:\index.rst:None: WARNING: image file not readable: a.jpg
3X:\conf.py.rst:: WARNING: document isn't included in any toctree
4# In Sphinx 1.6.1:
5X:\file.rst: WARNING: document isn't included in any toctree
The CodeChat renderer also produces error messages formatted in a similar way so they’ll be identified by the same regex:
1X:\ode.py:: ERROR: CodeChat renderer - source file older than the html file X:\_build\html\ode.py.
Each error/warning occupies one line. The following regular expression is designed to find the error position (1589/None) and message type (ERROR/WARNING/SEVERE). Extra spaces are added to show which parts of the example string it matches.
Examining the following expression one element at a time:
1 <string>:1589: (ERROR/3)Unknown interpreted text role "ref".
The filename is anything up to the colon. Windows filenames may begin with a drive letter followed by a colon – don’t capture this (the leading ?:
).
Find the first occurrence of a pair of colons, or just a single colon. Between them there can be numbers or “None” or nothing. For example, this expression matches the string “:1589:” or string “:None:” or the string “::” or the string “:”.
Next match the error type, which can only be “WARNING”, “ERROR” or “SEVERE”. Before this error type the message may optionally contain one left parenthesis.
Since one error message occupies one line, a *
quantifier is used along with end-of-line $
to make sure only the first match is used in each line.
Start another non-capturing group (the ?:
).
PreText CLI messages are very simple:
1critical: StartTag: invalid element name, line 8, column 6
2error: stuff stuff
3warning: Using CLI version 2.3.0 but project's `requirements.txt`
The message usually contains multiple lines; search each line for errors and warnings.
The global flag must be present to find all occurrences.
Parse the error output for errors and warnings.
The replacement function is called with the match text then each matched group.
errors_html = errors_html.replace(
error_regex,
function (
match_text,
file_path,
line,
error_string,
pretext_error_string
) {
if (
error_string === "ERROR" ||
error_string === "SEVERE" ||
pretext_error_string === "critical: " ||
pretext_error_string === "error: "
) {
++errNum;
} else if (
error_string === "WARNING" ||
pretext_error_string === "warning: "
) {
++warningNum;
}
Unsanitize the file name.
Make an unspecified filename empty.
Clean up the line.
Create a hyperlink to navigate to the error.
Report these results to the user.