~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/webapp/console/media/console.js

  • Committer: David Coles
  • Date: 2010-07-27 04:52:14 UTC
  • Revision ID: coles.david@gmail.com-20100727045214-p32h1kc0gcv48dpr
Worksheets: Strip off whitespace from the end of exercise attempts.

This solves an issue where accidental whitespace in an attempt will cause 
"IndentationError" syntax error (which don't occur when run in console).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* IVLE - Informatics Virtual Learning Environment
 
2
 * Copyright (C) 2007-2008 The University of Melbourne
 
3
 *
 
4
 * This program is free software; you can redistribute it and/or modify
 
5
 * it under the terms of the GNU General Public License as published by
 
6
 * the Free Software Foundation; either version 2 of the License, or
 
7
 * (at your option) any later version.
 
8
 *
 
9
 * This program is distributed in the hope that it will be useful,
 
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
 * GNU General Public License for more details.
 
13
 *
 
14
 * You should have received a copy of the GNU General Public License
 
15
 * along with this program; if not, write to the Free Software
 
16
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
17
 *
 
18
 * Module: Console (Client-side JavaScript)
 
19
 * Author: Tom Conway, Matt Giuca
 
20
 * Date: 30/1/2008
 
21
 */
 
22
 
 
23
var server_key;
 
24
 
 
25
/* Begin religious debate (tabs vs spaces) here: */
 
26
/* (This string will be inserted in the console when the user presses the Tab
 
27
 * key) */
 
28
TAB_STRING = "    ";
 
29
 
 
30
/* Console DOM objects */
 
31
console_body = null;
 
32
console_filler = null;
 
33
 
 
34
windowpane_mode = false;
 
35
server_started = false;
 
36
 
 
37
interrupted = false;
 
38
 
 
39
 
 
40
function get_console_start_directory()
 
41
{
 
42
    if((typeof(current_path) != 'undefined') && current_file)
 
43
    {
 
44
        // We have a current_path - give a suggestion to the server
 
45
        var path;
 
46
        if (current_file.isdir)
 
47
        {
 
48
            // Browser
 
49
            return path_join("/home", current_path);
 
50
        }
 
51
        else
 
52
        {
 
53
            // Editor - need to chop off filename
 
54
            var tmp_path = current_path.split('/');
 
55
            tmp_path.pop();
 
56
            return path_join("/home", tmp_path.join('/'));
 
57
        }
 
58
    }
 
59
    else
 
60
    {
 
61
        // No current_path - let the server decide
 
62
        return '';
 
63
    }
 
64
}
 
65
 
 
66
/* Starts the console server, if it isn't already.
 
67
 * This can be called any number of times - it only starts the one server.
 
68
 * Note that this is asynchronous. It will return after signalling to start
 
69
 * the server, but there is no guarantee that it has been started yet.
 
70
 * This is a separate step from console_init, as the server is only to be
 
71
 * started once the first command is entered.
 
72
 * Does not return a value. Writes to global variables
 
73
 * server_host, and server_port.
 
74
 *
 
75
 * \param callback Function which will be called after the server has been
 
76
 * started. No parameters are passed. May be null.
 
77
 */
 
78
function start_server(callback)
 
79
{
 
80
    if (server_started)
 
81
    {
 
82
        callback();
 
83
        return;
 
84
    }
 
85
    var callback1 = function(xhr)
 
86
        {
 
87
            var json_text = xhr.responseText;
 
88
            try
 
89
            {
 
90
                server_key = JSON.parse(json_text).key;
 
91
                server_started = true;
 
92
                var args = {
 
93
                    "ivle.op": "chat", "kind": "splash", "key": server_key
 
94
                };
 
95
                var callback2 = function(xhr)
 
96
                {
 
97
                    console_response(null, null, xhr.responseText);
 
98
                    if (callback != null)
 
99
                        callback();
 
100
                };
 
101
                ajax_call(callback2, "console", "service", args, "POST");
 
102
            }
 
103
            catch (e)
 
104
            {
 
105
                alert("An error occured when starting the IVLE console. " +
 
106
                    "Please refresh the page and try again.\n" +
 
107
                    "Details have been logged for further examination.")
 
108
            }
 
109
        }
 
110
 
 
111
    $("#console_output").append(
 
112
        '<span class="console_message">IVLE console starting up...</span>\n');
 
113
    console_maximize(true);
 
114
    ajax_call(
 
115
        callback1, "console", "service",
 
116
        {"ivle.op": "start", "cwd": get_console_start_directory()}, 
 
117
        "POST");
 
118
}
 
119
 
 
120
/** Start up the console backend before the user has entered text.
 
121
 * This will disable the text input, start a backend, and enable the input
 
122
 * again.
 
123
 */
 
124
function start_server_early()
 
125
{
 
126
    var inputbox = document.getElementById("console_inputText");
 
127
    inputbox.setAttribute("disabled", "disabled");
 
128
    var callback = function(xhr)
 
129
    {
 
130
        inputbox.removeAttribute("disabled")
 
131
    }
 
132
    start_server(callback);
 
133
}
 
134
 
 
135
/** Initialises the console. All apps which import console are required to
 
136
 * call this function.
 
137
 * Optional "windowpane" (bool), if true, will cause the console to go into
 
138
 * "window pane" mode which will allow it to be opened and closed, and float
 
139
 * over the page.
 
140
 * (Defaults to closed).
 
141
 */
 
142
function console_init(windowpane)
 
143
{
 
144
    /* Set up the console as a floating pane */
 
145
    console_body = document.getElementById("console_body");
 
146
    /* If there is no console body, don't worry.
 
147
     * (This lets us import console.js even on pages without a console box */
 
148
    if (console_body == null) return;
 
149
    console_filler = document.getElementById("console_filler");
 
150
    if (windowpane)
 
151
    {
 
152
        windowpane_mode = true;
 
153
        console_minimize();
 
154
    }
 
155
}
 
156
 
 
157
/** Hide the main console panel, so the console minimizes to just an input box
 
158
 *  at the page bottom. */
 
159
function console_minimize()
 
160
{
 
161
    if (!windowpane_mode) return;
 
162
    console_body.setAttribute("class", "console_body windowpane minimal");
 
163
    console_filler.setAttribute("class", "windowpane minimal");
 
164
}
 
165
 
 
166
/** Show the main console panel, so it enlarges out to its full size.
 
167
 */
 
168
function console_maximize(do_not_start)
 
169
{
 
170
    if (!windowpane_mode) return;
 
171
    if (!do_not_start && !server_started) start_server_early();
 
172
    console_body.setAttribute("class", "console_body windowpane maximal");
 
173
    console_filler.setAttribute("class", "windowpane maximal");
 
174
}
 
175
 
 
176
/* current_text is the string currently on the command line.
 
177
 * If non-empty, it will be stored at the bottom of the history.
 
178
 */
 
179
function historyUp(current_text)
 
180
{
 
181
    /* Remember the changes made to this item */
 
182
    this.edited[this.cursor] = current_text;
 
183
    if (this.cursor > 0)
 
184
    {
 
185
        this.cursor--;
 
186
    }
 
187
    this.earliestCursor = this.cursor;
 
188
}
 
189
 
 
190
function historyDown(current_text)
 
191
{
 
192
    /* Remember the changes made to this item */
 
193
    this.edited[this.cursor] = current_text;
 
194
    if (this.cursor < this.items.length - 1)
 
195
    {
 
196
        this.cursor++;
 
197
    }
 
198
}
 
199
 
 
200
function historyCurr()
 
201
{
 
202
    return this.edited[this.cursor];
 
203
}
 
204
 
 
205
function historySubmit(text)
 
206
{
 
207
    /* Copy the selected item's "edited" version over the permanent version of
 
208
     * the last item. */
 
209
    this.items[this.items.length-1] = text;
 
210
    /* Add a new blank item */
 
211
    this.items[this.items.length] = "";
 
212
    this.cursor = this.items.length-1;
 
213
    /* Blow away all the edited versions, replacing them with the existing
 
214
     * items set.
 
215
     * Not the whole history - just start from the earliest edited one.
 
216
     * (This avoids slowdown over extended usage time).
 
217
     */
 
218
    for (var i=this.earliestCursor; i<=this.cursor; i++)
 
219
        this.edited[i] = this.items[i];
 
220
    this.earliestCursor = this.cursor;
 
221
}
 
222
 
 
223
function historyShow()
 
224
{
 
225
    var res = "";
 
226
    for (var i = 0; i < this.items.length; i++)
 
227
    {
 
228
        if (i == this.cursor)
 
229
        {
 
230
            res += "["
 
231
        }
 
232
        res += this.items[i].toString();
 
233
        if (i == this.cursor)
 
234
        {
 
235
            res += "]"
 
236
        }
 
237
        res += " "
 
238
    }
 
239
    if (this.cursor == this.items.length)
 
240
    {
 
241
        res += "[]";
 
242
    }
 
243
    return res;
 
244
}
 
245
 
 
246
/* How history works
 
247
 * This is a fairly complex mechanism due to complications when editing
 
248
 * history items. We store two arrays. "items" is the permanent history of
 
249
 * each item. "edited" is a "volatile" version of items - the edits made to
 
250
 * the history between now and last time you hit "enter".
 
251
 * This is because the user can go back and edit any of the previous items,
 
252
 * and the edits are remembered until they hit enter.
 
253
 *
 
254
 * When hitting enter, the "edited" version of the currently selected item
 
255
 * replaces the "item" version of the last item in the list.
 
256
 * Then a new blank item is created, for the new line of input.
 
257
 * Lastly, all the "edited" versions are replaced with their stable versions.
 
258
 *
 
259
 * Cursor never points to an invalid location.
 
260
 */
 
261
function History()
 
262
{
 
263
    this.items = new Array("");
 
264
    this.edited = new Array("");
 
265
    this.cursor = 0;
 
266
    this.earliestCursor = 0;
 
267
    this.up = historyUp;
 
268
    this.down = historyDown;
 
269
    this.curr = historyCurr;
 
270
    this.submit = historySubmit;
 
271
    this.show = historyShow;
 
272
}
 
273
 
 
274
var hist = new History();
 
275
 
 
276
function set_interrupt()
 
277
{
 
278
    interrupted = true;
 
279
}
 
280
 
 
281
function clear_output()
 
282
{
 
283
    var output = document.getElementById("console_output");
 
284
    while (output.firstChild)
 
285
    {
 
286
        output.removeChild(output.firstChild);
 
287
    }
 
288
}
 
289
 
 
290
/** Send a line of text to the Python server, wait for its return, and react
 
291
 * to its response by writing to the output box.
 
292
 * Also maximize the console window if not already.
 
293
 */
 
294
function console_enter_line(inputbox, which)
 
295
{
 
296
    interrupted = false;
 
297
 
 
298
    // Open up the console so we can see the output
 
299
    console_maximize();
 
300
 
 
301
    if (typeof(inputbox) == "string")
 
302
    {
 
303
        var inputline = inputbox + "\n";
 
304
        inputbox = null;
 
305
    }
 
306
    else
 
307
    {
 
308
        /* Disable the text box */
 
309
        inputbox.setAttribute("disabled", "disabled");
 
310
 
 
311
        var inputline = inputbox.value + "\n";
 
312
    }
 
313
    var output = document.getElementById("console_output");
 
314
    {
 
315
        // Print ">>>" span
 
316
        var span = document.createElement("span");
 
317
        span.setAttribute("class", "inputPrompt");
 
318
        span.appendChild(document.createTextNode(
 
319
              document.getElementById("console_prompt").firstChild.nodeValue)
 
320
                        );
 
321
        output.appendChild(span);
 
322
        // Print input line itself in a span
 
323
        var span = document.createElement("span");
 
324
        span.setAttribute("class", "inputMsg");
 
325
        span.appendChild(document.createTextNode(inputline));
 
326
        output.appendChild(span);
 
327
        divScroll.activeScroll();
 
328
    }
 
329
    var args = {
 
330
        "ivle.op": "chat", "kind": which, "key": server_key,
 
331
        "text": inputline, "cwd": get_console_start_directory()
 
332
        };
 
333
    var callback = function(xhr)
 
334
        {
 
335
            console_response(inputbox, inputline, xhr.responseText);
 
336
        }
 
337
    ajax_call(callback, "console", "service", args, "POST");
 
338
}
 
339
 
 
340
function console_response(inputbox, inputline, responseText)
 
341
{
 
342
    try
 
343
    {
 
344
        var res = JSON.parse(responseText);
 
345
    }
 
346
    catch (e)
 
347
    {
 
348
        alert("An internal error occurred in the python console.");
 
349
        return;
 
350
    }
 
351
    var output = document.getElementById("console_output");
 
352
    if (res.hasOwnProperty('okay'))
 
353
    {
 
354
        // Success!
 
355
        if (res.okay)
 
356
        {
 
357
            output.appendChild(document.createTextNode(res.okay + "\n"));
 
358
            output.appendChild(span);
 
359
            divScroll.activeScroll();
 
360
        }
 
361
        // set the prompt to >>>
 
362
        set_prompt(">>>");
 
363
    }
 
364
    else if (res.hasOwnProperty('exc'))
 
365
    {
 
366
        // Failure!
 
367
        // print out the error message (res.exc)
 
368
        print_error(res.exc);
 
369
        
 
370
        // set the prompt to >>>
 
371
        set_prompt(">>>");
 
372
    }
 
373
    else if (res.hasOwnProperty('restart') && res.hasOwnProperty('key'))
 
374
    {
 
375
        // Server has indicated that the console should be restarted
 
376
        
 
377
        // Get the new key (host, port, magic)
 
378
        server_key = res.key;
 
379
 
 
380
        // Print a reason to explain why we'd do such a horrible thing
 
381
        // (console timeout, server error etc.)
 
382
        print_error(
 
383
            "IVLE console restarted: " + res.restart, "console_message");
 
384
        
 
385
        // set the prompt to >>>
 
386
        set_prompt(">>>");
 
387
    }
 
388
    else if (res.hasOwnProperty('more'))
 
389
    {
 
390
        // Need more input, so set the prompt to ...
 
391
        set_prompt("...");
 
392
    }
 
393
    else if (res.hasOwnProperty('output'))
 
394
    {
 
395
        if (res.output.length > 0)
 
396
        {
 
397
            output.appendChild(document.createTextNode(res.output));
 
398
            divScroll.activeScroll();
 
399
        }
 
400
        var callback = function(xhr)
 
401
            {
 
402
                console_response(inputbox, null, xhr.responseText);
 
403
            }
 
404
        if (interrupted)
 
405
        {
 
406
            var kind = "interrupt";
 
407
        }
 
408
        else
 
409
        {
 
410
            var kind = "chat";
 
411
        }
 
412
        var args = {
 
413
            "ivle.op": "chat", "kind": kind, "key": server_key,
 
414
            "text": '', "cwd": get_console_start_directory()
 
415
            };
 
416
        ajax_call(callback, "console", "service", args, "POST");
 
417
 
 
418
        // Open up the console so we can see the output
 
419
        console_maximize();
 
420
 
 
421
        // Return early, so we don't re-enable the input box.
 
422
        return;
 
423
    }
 
424
    else if (res.hasOwnProperty('input'))
 
425
    {
 
426
        set_prompt("+++");
 
427
    }
 
428
    else
 
429
    {
 
430
        alert("An internal error occurred in the python console.");
 
431
        return;
 
432
    }
 
433
 
 
434
    if (inputbox != null)
 
435
    {
 
436
        /* Re-enable the text box */
 
437
        inputbox.removeAttribute("disabled");
 
438
        interrupted = false;
 
439
    }
 
440
 
 
441
    /* Auto-scrolling */
 
442
    divScroll.activeScroll();
 
443
 
 
444
    // Focus the input box by default
 
445
    document.getElementById("console_output").focus();
 
446
    document.getElementById("console_inputText").focus();
 
447
}
 
448
 
 
449
function catch_input(key)
 
450
{
 
451
    var inp = document.getElementById('console_inputText');
 
452
    switch (key)
 
453
    {
 
454
    case 9:                 /* Tab key */
 
455
        var selstart = inp.selectionStart;
 
456
        var selend = inp.selectionEnd;
 
457
        if (selstart == selend)
 
458
        {
 
459
            /* No selection, just a carat. Insert a tab here. */
 
460
            inp.value = inp.value.substr(0, selstart)
 
461
                + TAB_STRING + inp.value.substr(selstart);
 
462
        }
 
463
        else
 
464
        {
 
465
            /* Text is selected. Just indent the whole line
 
466
             * by inserting a tab at the start */
 
467
            inp.value = TAB_STRING + inp.value;
 
468
        }
 
469
        /* Update the selection so the same characters as before are selected
 
470
         */
 
471
        inp.selectionStart = selstart + TAB_STRING.length;
 
472
        inp.selectionEnd = inp.selectionStart + (selend - selstart);
 
473
        /* Cancel the event, so the TAB key doesn't move focus away from this
 
474
         * box */
 
475
        return false;
 
476
        /* Note: If it happens that some browsers don't support event
 
477
         * cancelling properly, this hack might work instead:
 
478
        setTimeout(
 
479
            "document.getElementById('console_inputText').focus()",
 
480
            0);
 
481
         */
 
482
        break;
 
483
    case 13:                /* Enter key */
 
484
        var callback = function()
 
485
        {
 
486
            /* Send the line of text to the server */
 
487
            console_enter_line(inp, "chat");
 
488
            hist.submit(inp.value);
 
489
            inp.value = hist.curr();
 
490
        }
 
491
 
 
492
        /* Disable the text box. This will be redone by
 
493
         * console_enter_line, but we do it here too in case start_server
 
494
         * takes a while.
 
495
         */
 
496
        inp.setAttribute("disabled", "disabled");
 
497
        /* Start the server if it hasn't already been started */
 
498
        start_server(callback);
 
499
        break;
 
500
    case 38:                /* Up arrow */
 
501
        hist.up(inp.value);
 
502
        inp.value = hist.curr();
 
503
        /* Inhibit further responses to this event, or WebKit moves the
 
504
         * cursor to the start. */
 
505
        return false;
 
506
        break;
 
507
    case 40:                /* Down arrow */
 
508
        hist.down(inp.value);
 
509
        inp.value = hist.curr();
 
510
        return false;
 
511
        break;
 
512
    }
 
513
}
 
514
 
 
515
/** Resets the console by signalling the old console to expire and starting a 
 
516
 * new one.
 
517
 */
 
518
function console_reset()
 
519
{
 
520
    // FIXME: We show some feedback here - either disable input or at very 
 
521
    // least the reset button.
 
522
 
 
523
    // Restart the console
 
524
    if(!server_started)
 
525
    {
 
526
        start_server(null);
 
527
    }
 
528
    else
 
529
    {
 
530
        xhr = ajax_call(null, "console", "service", {"ivle.op": "chat", "kind": "terminate", "key": server_key, "cwd": get_console_start_directory()}, "POST");
 
531
        console_response(null, null, xhr.responseText);
 
532
    }
 
533
}
 
534
 
 
535
/** Prints an error line in the console **/
 
536
function print_error(error, cls)
 
537
{
 
538
    if (!cls)
 
539
        cls = "errorMsg";
 
540
 
 
541
    var output = document.getElementById("console_output");
 
542
  
 
543
    // Create text block
 
544
    var span = document.createElement("span");
 
545
    span.setAttribute("class", cls);
 
546
    span.appendChild(document.createTextNode(error + "\n"));
 
547
    output.appendChild(span);
 
548
 
 
549
    // Autoscroll
 
550
    divScroll.activeScroll();
 
551
}
 
552
 
 
553
/** Sets the prompt text **/
 
554
function set_prompt(prompt_text)
 
555
{
 
556
    var prompt = document.getElementById("console_prompt");
 
557
    prompt.replaceChild(document.createTextNode(prompt_text + " "), prompt.firstChild);
 
558
}
 
559
 
 
560
/**** Following Code modified from ******************************************/
 
561
/**** http://radio.javaranch.com/pascarello/2006/08/17/1155837038219.html ***/
 
562
/****************************************************************************/
 
563
var chatscroll = new Object();
 
564
 
 
565
chatscroll.Pane = function(scrollContainerId)
 
566
{
 
567
    this.scrollContainerId = scrollContainerId;
 
568
}
 
569
 
 
570
chatscroll.Pane.prototype.activeScroll = function()
 
571
{
 
572
    var scrollDiv = document.getElementById(this.scrollContainerId);
 
573
    var currentHeight = 0;
 
574
        
 
575
    if (scrollDiv.scrollHeight > 0)
 
576
        currentHeight = scrollDiv.scrollHeight;
 
577
    else if (scrollDiv.offsetHeight > 0)
 
578
        currentHeight = scrollDiv.offsetHeight;
 
579
 
 
580
    scrollDiv.scrollTop = currentHeight;
 
581
 
 
582
    scrollDiv = null;
 
583
}
 
584
 
 
585
var divScroll = new chatscroll.Pane('console_output');