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

« back to all changes in this revision

Viewing changes to www/media/console/console.js

Dispatch now generates an index for each plugin type, allowing plugins to
be written which are aware of other plugins, and other plugin types.

All view plugins now subclass from ivle.webapp.base.plugins.ViewPlugin,
as opposed to subclassing BasePlugin directly. This will allow us to
easily re-write console as an OverlayPlugin, and allow future new
plugins types to be created.

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