Skip to content

Commit

Permalink
Switch from SyntaxHighlighter to Prism (syntax highlighting) (MarkUsP…
Browse files Browse the repository at this point in the history
  • Loading branch information
david-yz-liu authored and AinaMerch committed Jul 11, 2024
1 parent 518d660 commit f60721a
Show file tree
Hide file tree
Showing 42 changed files with 283 additions and 1,555 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
- Upgrade pdfjs-dist to v4.3.136 (#7113)
- Fixed formatting of documentation repository on the PR template (#7120)
- Switch from rails-sassc to cssbundling-rails for CSS asset management (#7121)
- Switch from SyntaxHighlighter to Prism for syntax highlighting (#7122)

## [v2.4.11]

Expand Down
17 changes: 11 additions & 6 deletions app/assets/javascripts/Annotations/text_annotation_manager.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* AnnotationManager subclass for plaintext files. Its constructor is given a list of DOM elements
* (one for each line of text in the file), that have been transformed through SyntaxHighlighter.
* (one for each line of text in the file), that have been transformed through syntax highlighting.
*/
class TextAnnotationManager extends AnnotationManager {
constructor(source_nodes) {
Expand Down Expand Up @@ -120,7 +120,7 @@ class TextAnnotationManager extends AnnotationManager {
);

// If the entire line was selected through a triple-click, highlight the entire line.
if (mouse_anchor.nodeName === "LI" && mouse_focus.nodeName === "LI") {
if (mouse_anchor === mouse_focus && this.isSourceLineElement(mouse_focus)) {
return {
line_start: line_start,
line_end: line_end,
Expand All @@ -130,10 +130,10 @@ class TextAnnotationManager extends AnnotationManager {
}

// If we selected an entire line the above returns + 1, a fix follows
if (mouseSelection.anchorNode.nodeName === "LI") {
if (this.isSourceLineElement(mouseSelection.anchorNode)) {
line_start--;
}
if (mouseSelection.focusNode.nodeName === "LI") {
if (this.isSourceLineElement(mouseSelection.focusNode)) {
line_end--;
}

Expand Down Expand Up @@ -213,13 +213,18 @@ class TextAnnotationManager extends AnnotationManager {
};
}

// Given some node, traverses upwards until it finds the LI element that represents a line of code in SyntaxHighlighter.
// Given some node, traverses upwards until it finds the span element that represents a line of code.
// This is useful for figuring out what text is currently selected, using window.getSelection().anchorNode / focusNode
getRootFromSelection(node) {
let current_node = node;
while (current_node !== null && current_node.tagName !== "LI") {
while (current_node !== null && !this.isSourceLineElement(current_node)) {
current_node = current_node.parentNode;
}
return current_node;
}

// Returns whether node is an element representing a source line (after syntax highlighting has been applied).
isSourceLineElement(node) {
return node.tagName === "SPAN" && node.className.includes("source-line");
}
}
97 changes: 73 additions & 24 deletions app/assets/javascripts/Components/Result/text_viewer.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import {render} from "react-dom";
import Prism from "prismjs";

export class TextViewer extends React.PureComponent {
constructor(props) {
Expand All @@ -14,6 +15,7 @@ export class TextViewer extends React.PureComponent {
}

componentDidMount() {
this.highlight_root = this.raw_content.current.parentNode;
if (this.props.content) {
this.ready_annotations();
}
Expand Down Expand Up @@ -42,36 +44,88 @@ export class TextViewer extends React.PureComponent {
* 3. Scroll to line numbered this.props.focusLine
*/
ready_annotations = () => {
if (this.highlight_root !== null) {
this.highlight_root.remove();
}
this.run_syntax_highlighting();

if (this.annotation_manager !== null) {
this.annotation_manager.annotation_text_displayer.hide();
}

const preElementName = this.raw_content.current.getAttribute("name");
dp.SyntaxHighlighter.HighlightAll(
preElementName,
true /* showGutter */,
false /* showControls */
);
this.highlight_root =
this.raw_content.current.parentNode.getElementsByClassName("dp-highlighter")[0];
this.highlight_root.style.font_size = this.state.fontSize + "em";

if (this.props.resultView) {
window.annotation_type = ANNOTATION_TYPES.CODE;

window.annotation_manager = new TextAnnotationManager(
this.highlight_root.children[1].children
);
window.annotation_manager = new TextAnnotationManager(this.raw_content.current.children);
this.annotation_manager = window.annotation_manager;
}

this.props.annotations.forEach(this.display_annotation);
this.scrollToLine(this.props.focusLine);
};

run_syntax_highlighting = () => {
Prism.highlightElement(this.raw_content.current, false);
let nodeLines = [];
let currLine = document.createElement("span");
currLine.classList.add("source-line");
for (let node of this.raw_content.current.childNodes) {
if (node.nodeType === Node.TEXT_NODE) {
// SourceCodeLine.glow assumes text nodes are wrapped in <span> elements
let textContainer = document.createElement("span");
let textNode = null;
if (node.textContent.includes("\n")) {
const splits = node.textContent.split("\n");
for (let i = 0; i < splits.length - 1; i++) {
textNode = document.createTextNode(splits[i] + "\n");
textContainer.appendChild(textNode);
currLine.appendChild(textContainer);
nodeLines.push(currLine);
currLine = document.createElement("span");
currLine.classList.add("source-line");
textContainer = document.createElement("span");
}
textNode = document.createTextNode(splits[splits.length - 1]);
} else {
textNode = node.cloneNode(true);
}
textContainer.appendChild(textNode);
currLine.appendChild(textContainer);
} else {
if (node.textContent.includes("\n")) {
const splits = node.textContent.split("\n");
let textContainer = document.createElement("span");
textContainer.className = node.className;
let textNode = null;
for (let i = 0; i < splits.length - 1; i++) {
textNode = document.createTextNode(splits[i] + "\n");
textContainer.appendChild(textNode);
currLine.appendChild(textContainer);
nodeLines.push(currLine);
currLine = document.createElement("span");
currLine.classList.add("source-line");
textContainer = document.createElement("span");
textContainer.className = node.className;
}
textNode = document.createTextNode(splits[splits.length - 1]);
textContainer.appendChild(textNode);
currLine.appendChild(textContainer);
} else {
currLine.appendChild(node.cloneNode(true));
}
}
}
if (currLine.textContent.length > 0) {
nodeLines.appendChild(currLine);
}
nodeLines.push(this.raw_content.current.lastChild.cloneNode(true));
while (this.raw_content.current.firstChild) {
this.raw_content.current.removeChild(this.raw_content.current.lastChild);
}
for (let n of nodeLines) {
this.raw_content.current.appendChild(n);
}
};

change_font_size = delta => {
this.setState({font_size: Math.max(this.state.font_size + delta, 0.25)});
};
Expand Down Expand Up @@ -109,16 +163,12 @@ export class TextViewer extends React.PureComponent {
return;
}

const line = this.highlight_root.querySelector(`li:nth-of-type(${lineNumber})`);
const line = this.highlight_root.querySelector(`span.source-line:nth-of-type(${lineNumber})`);
if (line) {
line.scrollIntoView();
}
};

componentWillUnmount() {
document.querySelectorAll(".dp-highlighter").forEach(node => node.remove());
}

copyToClipboard = () => {
navigator.clipboard.writeText(this.props.content).then(() => {
this.setState({copy_success: true});
Expand All @@ -142,13 +192,12 @@ export class TextViewer extends React.PureComponent {
<a href="#" onClick={() => this.change_font_size(-0.25)}>
-A
</a>
<a href="#" onClick={() => dp.sh.Toolbar.Command("About", this.highlight_root)}>
?
</a>
</div>
</div>
<pre name={preElementName} ref={this.raw_content} className={this.props.type}>
{this.props.content}
<pre name={preElementName} className={`line-numbers`}>
<code ref={this.raw_content} className={`language-${this.props.type}`}>
{this.props.content}
</code>
</pre>
</React.Fragment>
);
Expand Down
24 changes: 24 additions & 0 deletions app/assets/javascripts/Components/__tests__/text_viewer.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";
import {render, screen, cleanup} from "@testing-library/react";
import {TextViewer} from "../Result/text_viewer";

describe("TextViewer", () => {
let props;

beforeEach(() => {
props = {
content: "def f(n: int) -> int:\n return n + 1\n",
annotations: [],
focusLine: null,
submission_file_id: 1,
};

render(<TextViewer {...props} />);
});

afterEach(cleanup);

it("should render its text content", () => {
expect(screen.getByText("def f(n: int) -> int:")).toBeInTheDocument();
});
});
22 changes: 12 additions & 10 deletions app/assets/javascripts/Results/context_menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ var annotation_context_menu = {
title: I18n.t("edit"),
cmd: "edit_annotation",
action: function (event, ui) {
var clicked_element = $(ui.target);
var clicked_element = ui.target[0];
var annot_id = get_annotation_id(clicked_element);
if (annot_id !== null && annot_id.length !== 0) {
resultComponent.editAnnotation(annot_id);
Expand All @@ -64,7 +64,7 @@ var annotation_context_menu = {
title: I18n.t("delete"),
cmd: "delete_annotation",
action: function (event, ui) {
var clicked_element = $(ui.target);
var clicked_element = $(ui.target)[0];
var annot_id = get_annotation_id(clicked_element);
if (annot_id !== null && annot_id.length !== 0) {
resultComponent.removeAnnotation(annot_id);
Expand Down Expand Up @@ -94,18 +94,20 @@ var annotation_context_menu = {
};

function get_annotation_id(clicked_element) {
var annot_id = "";
if (annotation_type === ANNOTATION_TYPES.CODE) {
$.each(clicked_element[0].attributes, function (index, attr) {
if (attr.nodeName.toLowerCase().indexOf("data-annotationid") != -1) {
annot_id = attr.value;
// Continue iteration in case of multiple annotations
let curr = clicked_element;
while (curr !== null && curr.tagName === "SPAN") {
for (let attr in curr.dataset) {
if (attr.toLowerCase().startsWith("annotationid")) {
return curr.dataset[attr];
}
}
});
curr = curr.parentNode;
}
return "";
} else {
annot_id = clicked_element.attr("id").replace("annotation_holder_", "");
return clicked_element.attr("id").replace("annotation_holder_", "");
}
return annot_id;
}

$(document).contextmenu({
Expand Down
3 changes: 0 additions & 3 deletions app/assets/javascripts/Results/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
//= require DropDownMenu/DropDownMenu
//= require jquery.ui-contextmenu.min

// Syntax highlighting
//= require syntaxhighlighter

// Source code highlighting
//= require Annotations/globals
//= require Annotations/annotation_manager
Expand Down
101 changes: 0 additions & 101 deletions app/assets/stylesheets/common/SyntaxHighlighter.scss

This file was deleted.

Loading

0 comments on commit f60721a

Please sign in to comment.