1<!DOCTYPE html>
2<html>
3<head>
4    <title>Debug</title>
5    <link rel="stylesheet" href="codemirror.css">
6    <link rel="stylesheet" href="codemirror-additions.css">
7    <script src="codemirror.js"></script>
8    <script src="javascript.js"></script>
9    <script src="css.js"></script>
10    <script src="Utilities.js"></script>
11    <script src="Formatter.js"></script>
12    <script src="FormatterDebug.js"></script>
13    <script src="FormatterContentBuilder.js"></script>
14    <script src="CodeMirrorFormatters.js"></script>
15</head>
16<body>
17
18    <h1>Debug Pretty Printing</h1>
19
20    <!-- Controls -->
21    <select id="mode">
22        <option selected value="text/javascript">JavaScript</option>
23        <option value="text/css">CSS</option>
24    </select>
25    <button id="populate">Populate</button>
26    <button id="run-tests">Run Tests</button>
27    <button id="clear">Clear</button>
28    <button id="select-output">Select Output</button>
29    <button id="run-again">Run Again</button>
30    <button id="save-as-url">Save URL</button>
31    <button id="save-local-storage">Save to Storage</button>
32    <button id="clear-local-storage">Clear Storage</button>
33    <small id="time"></small>
34
35    <br><br>
36
37    <!-- Editor -->
38    <textarea id="code" name="code"></textarea>
39
40    <!-- Output -->
41    <pre id="pretty"></pre>
42    <pre id="debug"></pre>
43
44    <script>
45    // Editor.
46    var cm = CodeMirror.fromTextArea(document.getElementById("code"), {
47        lineNumbers: true,
48    });
49
50    // Initial values from URL.
51    var queryParams = {};
52    if (window.location.search.length > 0) {
53        var searchString = window.location.search.substring(1);
54        var groups = searchString.split("&");
55        for (var i = 0; i < groups.length; ++i) {
56            var pair = groups[i].split("=");
57            queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
58        }
59    }
60
61    // Initial mode and string.
62    var mode = "text/javascript";
63    var content = "(function() { var a=1; return a+1; })();";
64    var updatePicker = false;
65    if (queryParams.content || queryParams.mode) {
66        content = queryParams.content || "";
67        mode = queryParams.mode || "text/javascript";
68        updatePicker = true;
69    } else if (localStorage.content || localStorage.mode) {
70        content = localStorage.content || "";
71        mode = localStorage.mode || "text/javascript";
72        updatePicker = true;
73    }
74
75    // Initial mode picker value.
76    var modePicker = document.getElementById("mode");
77    if (updatePicker) {
78        for (var i = 0; i < modePicker.options.length; ++i) {
79            if (modePicker.options[i].value === mode) {
80                modePicker.options[i].selected = true;
81                break;
82            }
83        }
84    }
85
86    // Set on CodeMirror.
87    cm.setValue(content);
88    cm.setOption("mode", mode);
89
90    // Changing the mode.
91    modePicker.addEventListener("change", function(event) {
92        cm.setValue("");
93        cm.setOption("mode", modePicker.value);
94        refresh();
95    });
96
97    // Populate button to populate with some canned content.
98    document.getElementById("populate").addEventListener("click", function(event) {
99        switch (modePicker.value) {
100            case "text/javascript":
101                var url = "populate/jquery.min.js";
102                break;
103            case "text/css":
104                var url = "populate/apple.css";
105                break;
106        }
107
108        var xhr = new XMLHttpRequest;
109        xhr.open("GET", url, true);
110        xhr.onload = function() {
111            cm.setValue(xhr.responseText);
112            setTimeout(refresh);
113        }
114        xhr.send();
115    });
116
117    // Run Tests button.
118    document.getElementById("run-tests").addEventListener("click", function(event) {
119        cm.setValue("Running Tests...");
120        refresh();
121        runTests();
122    });
123
124    // Clear button.
125    document.getElementById("clear").addEventListener("click", function(event) {
126        cm.setValue("");
127        refresh();
128    });
129
130    // Select output button.
131    document.getElementById("select-output").addEventListener("click", function(event) {
132        var range = document.createRange();
133        range.selectNodeContents(document.getElementById("pretty"));
134        var selection = window.getSelection();
135        selection.removeAllRanges();
136        selection.addRange(range);
137    });
138
139    // Run again button.
140    document.getElementById("run-again").addEventListener("click", function(event) {
141        refresh();
142    });
143
144    // Save as URL button.
145    document.getElementById("save-as-url").addEventListener("click", function(event) {
146        var mode = modePicker.value;
147        var content = cm.getValue();
148        window.location.search = "?mode=" + window.encodeURIComponent(mode) + "&content=" + window.encodeURIComponent(content);
149    });
150
151    // Save to localStorage.
152    document.getElementById("save-local-storage").addEventListener("click", function(event) {
153        localStorage.mode = modePicker.value;
154        localStorage.content = cm.getValue();
155    });
156
157    // Clear localStorage.
158    document.getElementById("clear-local-storage").addEventListener("click", function(event) {
159        localStorage.removeItem("mode");
160        localStorage.removeItem("content");
161    });
162
163    // Button helpers.
164    var buttons = ["mode", "populate", "run-tests", "clear", "select-output", "run-again"];
165    function disableButtons() {
166        console.log("disableButtons");
167        buttons.forEach(function(id) {
168            document.getElementById(id).disabled = true;
169        });
170    }
171    function enableButtons() {
172        console.log("enableButtons");
173        buttons.forEach(function(id) {
174            document.getElementById(id).disabled = false;
175        });
176    }
177
178    // Refresh after changes after a short delay.
179    var timer = null;
180    cm.on("change", function(codeMirror, change) {
181        if (timer)
182            clearTimeout(timer)
183        timer = setTimeout(function() {
184            clearTimeout(timer);
185            timer = null;
186            refresh();
187        }, 500);
188    });
189
190    // Output elements.
191    var timeOutput = document.getElementById("time");
192    var prettyPre = document.getElementById("pretty");
193    var debugPre = document.getElementById("debug");
194
195    function refresh() {
196        if (timer)
197            clearTimeout(timer);
198
199        const start = {line: 0, ch: 0};
200        const end = {line: cm.lineCount() - 1};
201
202        // Setup.
203        const indentString = "    ";
204        var originalLineEndings = [];
205        var formattedLineEndings = [];
206        var mapping = {original: [0], formatted: [0]};
207        var builder = new FormatterContentBuilder(mapping, originalLineEndings, formattedLineEndings, 0, 0, indentString);
208        var formatter = new Formatter(cm, builder);
209
210        // Time the formatter.
211        var startTime = Date.now();
212        formatter.format(start, end);
213        var endTime = Date.now();
214
215        // Gather debug information.
216        var debug = formatter.debug(start, end);
217
218        // Output the results.
219        timeOutput.innerText = (endTime - startTime) + "ms";
220        prettyPre.innerText = builder.formattedContent;
221        debugPre.innerText = debug;
222    }
223
224    setTimeout(refresh);
225
226    // Tests.
227    function runTests() {
228        disableButtons();
229        function completedCallback() {
230            enableButtons();
231        }
232
233        if (modePicker.value === "text/javascript")
234            runJavaScriptTests(completedCallback);
235        else
236            runCSSTests(completedCallback);
237    }
238    function runJavaScriptTests(callback) {
239        _runTests(callback, [
240            "js-tests/block-comment.js",
241            "js-tests/single-statement-blocks.js",
242            "js-tests/switch-case-default.js",
243        ]);
244    }
245    function runCSSTests(callback) {
246        _runTests(callback, []);
247    }
248    function _runTests(callback, manifest) {
249        var index = -1;
250        var results = [];
251        setTimeout(runNextTest, 0);
252
253        function runNextTest() {
254            // Next test.
255            index++;
256
257            // Done.
258            if (index >= manifest.length) {
259                if (!index)
260                    results.push("/* No tests for mode: " + modePicker.value);
261                printResults();
262                return;
263            }
264
265            // Load test and expected results.
266            var test = manifest[index];
267            var expected = test.replace(/\.js$/, "-expected.js");
268            var xhr1 = new XMLHttpRequest;
269            xhr1.open("GET", test, false);
270            xhr1.send();
271            var testData = xhr1.responseText;
272            var xhr2 = new XMLHttpRequest;
273            xhr2.open("GET", expected, false);
274            xhr2.send();
275            var expectedData = xhr2.responseText;
276
277            // Run the test.
278            var editor = CodeMirror(document.createElement("div"));
279            editor.setOption("mode", modePicker.value);
280            editor.setValue(testData);
281            const start = {line: 0, ch: 0};
282            const end = {line: editor.lineCount() - 1};
283            const indentString = "    ";
284            var originalLineEndings = [];
285            var formattedLineEndings = [];
286            var mapping = {original: [0], formatted: [0]};
287            var builder = new FormatterContentBuilder(mapping, originalLineEndings, formattedLineEndings, 0, 0, indentString);
288            var formatter = new Formatter(editor, builder);
289            formatter.format(start, end);
290
291            // Compare results.
292            var pass = builder.formattedContent === expectedData;
293            results.push("/* " + (pass ? "PASS" : "FAIL") + ": " + test + " */");
294            runNextTest();
295        }
296
297        function printResults() {
298            cm.setValue(results.join("\n"));
299            cm.refresh();
300            callback();
301        }
302    }
303
304    </script>
305</body>
306</html>
307