PageSourceSearch

https://thefrontiers.org/nayanadb/js/get_scripts.js.php?scripts[]=…&scripts[]=codemirror/mode/mysql/mysql.js

js thefrontiers.org collected 2026-09-26 17:41:19 UTC 371,515 bytes, 10,060 lines download raw bytes

1/**
2 * Debounce and throttle function's decorator plugin 1.0.5
3 *
4 * Copyright (c) 2009 Filatov Dmitry ([email protected])
5 * Dual licensed under the MIT and GPL licenses:
6 * http://www.opensource.org/licenses/mit-license.php
7 * http://www.gnu.org/licenses/gpl.html
8 *
9 */
10
11(function($) {
12
13$.extend({
14
15	debounce : function(fn, timeout, invokeAsap, ctx) {
16
17		if(arguments.length == 3 && typeof invokeAsap != 'boolean') {
18			ctx = invokeAsap;
19			invokeAsap = false;
20		}
21
22		var timer;
23
24		return function() {
25
26			var args = arguments;
27            ctx = ctx || this;
28
29			invokeAsap && !timer && fn.apply(ctx, args);
30
31			clearTimeout(timer);
32
33			timer = setTimeout(function() {
34				!invokeAsap && fn.apply(ctx, args);
35				timer = null;
36			}, timeout);
37
38		};
39
40	},
41
42	throttle : function(fn, timeout, ctx) {
43
44		var timer, args, needInvoke;
45
46		return function() {
47
48			args = arguments;
49			needInvoke = true;
50			ctx = ctx || this;
51
52			if(!timer) {
53				(function() {
54					if(needInvoke) {
55						fn.apply(ctx, args);
56						needInvoke = false;
57						timer = setTimeout(arguments.callee, timeout);
58					}
59					else {
60						timer = null;
61					}
62				})();
63			}
64
65		};
66
67	}
68
69});
70
71})(jQuery);;
72
73/* vim: set expandtab sw=4 ts=4 sts=4: */
74/**
75 * Handles the resizing of a menu according to the available screen width
76 *
77 * Uses themes/original/css/resizable-menu.css.php
78 *
79 * To initialise:
80 * $('#myMenu').menuResizer(function () {
81 *     // This function will be called to find out how much
82 *     // available horizontal space there is for the menu
83 *     return $('body').width() - 5; // Some extra margin for good measure
84 * });
85 *
86 * To trigger a resize operation:
87 * $('#myMenu').menuResizer('resize'); // Bind this to $(window).resize()
88 *
89 * To restore the menu to a state like before it was initialized:
90 * $('#myMenu').menuResizer('destroy');
91 *
92 * @package PhpMyAdmin
93 */
94(function ($) {
95    function MenuResizer($container, widthCalculator) {
96        var self = this;
97        self.$container = $container;
98        self.widthCalculator = widthCalculator;
99        // create submenu container
100        var link = $('<a />', {href: '#', 'class': 'tab nowrap'})
101            .text(PMA_messages['strMore'])
102            .bind('click', false); // same as event.preventDefault()
103        var img = $container.find('li img');
104        if (img.length) {
105            $(PMA_getImage('b_more.png').toString()).prependTo(link);
106        }
107        var $submenu = $('<li />', {'class': 'submenu'})
108            .append(link)
109            .append($('<ul />'))
110            .mouseenter(function() {
111                if ($(this).find('ul .tabactive').length == 0) {
112                    $(this)
113                    .addClass('submenuhover')
114                    .find('> a')
115                    .addClass('tabactive');
116                }
117            })
118            .mouseleave(function() {
119                if ($(this).find('ul .tabactive').length == 0) {
120                    $(this)
121                    .removeClass('submenuhover')
122                    .find('> a')
123                    .removeClass('tabactive');
124                }
125            });
126        $container.append($submenu);
127        setTimeout(function () {
128            self.resize();
129        }, 4);
130    }
131    MenuResizer.prototype.resize = function () {
132        var wmax = this.widthCalculator.call(this.$container);
133        var $submenu = this.$container.find('.submenu:last');
134        var submenu_w = $submenu.outerWidth(true);
135        var $submenu_ul = $submenu.find('ul');
136        var $li = this.$container.find('> li');
137        var $li2 = $submenu_ul.find('li');
138        var more_shown = $li2.length > 0;
139        // Calculate the total width used by all the shown tabs
140        var total_len = more_shown ? submenu_w : 0;
141        var l = $li.length - 1;
142        for (var i = 0; i < l; i++) {
143            total_len += $($li[i]).outerWidth(true);
144        }
145        // Now hide menu elements that don't fit into the menubar
146        var hidden = false; // Whether we have hidden any tabs
147        while (total_len >= wmax && --l >= 0) { // Process the tabs backwards
148            hidden = true;
149            var el = $($li[l]);
150            var el_width = el.outerWidth(true);
151            el.data('width', el_width);
152            if (! more_shown) {
153                total_len -= el_width;
154                el.prependTo($submenu_ul);
155                total_len += submenu_w;
156                more_shown = true;
157            } else {
158                total_len -= el_width;
159                el.prependTo($submenu_ul);
160            }
161        }
162        // If we didn't hide any tabs, then there might be some space to show some
163        if (! hidden) {
164            // Show menu elements that do fit into the menubar
165            for (var i = 0, l = $li2.length; i < l; i++) {
166                total_len += $($li2[i]).data('width');
167                // item fits or (it is the last item
168                // and it would fit if More got removed)
169                if (total_len < wmax
170                    || (i == $li2.length - 1 && total_len - submenu_w < wmax)
171                ) {
172                    $($li2[i]).insertBefore($submenu);
173                } else {
174                    break;
175                }
176            }
177        }
178        // Show/hide the "More" tab as needed
179        if ($submenu_ul.find('li').length > 0) {
180            $submenu.addClass('shown');
181        } else {
182            $submenu.removeClass('shown');
183        }
184        if (this.$container.find('> li').length == 1) {
185            // If there is only the "More" tab left, then we need
186            // to align the submenu to the left edge of the tab
187            $submenu_ul.removeClass().addClass('only');
188        } else {
189            // Otherwise we align the submenu to the right edge of the tab
190            $submenu_ul.removeClass().addClass('notonly');
191        }
192        if ($submenu.find('.tabactive').length) {
193            $submenu
194            .addClass('active')
195            .find('> a')
196            .removeClass('tab')
197            .addClass('tabactive');
198        } else {
199            $submenu
200            .removeClass('active')
201            .find('> a')
202            .addClass('tab')
203            .removeClass('tabactive');
204        }
205    };
206    MenuResizer.prototype.destroy = function () {
207        var $submenu = this.$container.find('li.submenu').removeData();
208        $submenu.find('li').appendTo(this.$container);
209        $submenu.remove();
210    };
211
212    /** Public API */
213    var methods = {
214        init: function(widthCalculator) {
215            return this.each(function () {
216                var $this = $(this);
217                if (! $this.data('menuResizer')) {
218                    $this.data(
219                        'menuResizer',
220                        new MenuResizer($this, widthCalculator)
221                    );
222                 }
223            });
224        },
225        resize: function () {
226            return this.each(function () {
227                var self = $(this).data('menuResizer');
228                if (self) {
229                    self.resize();
230                }
231            });
232        },
233        destroy: function () {
234            return this.each(function () {
235                var self = $(this).data('menuResizer');
236                if (self) {
237                    self.destroy();
238                }
239            });
240        }
241    };
242
243    /** Extend jQuery */
244    $.fn.menuResizer = function(method) {
245        if (methods[method]) {
246            return methods[method].call(this);
247        } else if (typeof method === 'function') {
248            return methods.init.apply(this, [method]);
249        } else {
250            $.error('Method ' +  method + ' does not exist on jQuery.menuResizer');
251        }
252    };
253})(jQuery);
254;
255
256/* vim: set expandtab sw=4 ts=4 sts=4: */
257/**
258 * Conditionally included if framing is not allowed
259 */
260if(self == top) {
261    document.documentElement.style.display = 'block' ;
262} else {
263    top.location = self.location ;
264}
265;
266
267/* vim: set expandtab sw=4 ts=4 sts=4: */
268/**
269 * JavaScript functionality for Routines, Triggers and Events.
270 *
271 * @package PhpMyadmin
272 */
273/**
274 * @var RTE Contains all the JavaScript functionality
275 *          for Routines, Triggers and Events
276 */
277var RTE = {
278    /**
279     * Construct for the object that provides the
280     * functionality for Routines, Triggers and Events
281     */
282    object: function (type) {
283        $.extend(this, RTE.COMMON);
284        switch (type) {
285            case 'routine':
286                $.extend(this, RTE.ROUTINE);
287                break;
288            case 'trigger':
289                // nothing extra yet for triggers
290                break;
291            case 'event':
292                $.extend(this, RTE.EVENT);
293                break;
294            default:
295                break;
296        }
297    },
298    /**
299     * @var string param_template Template for a row in the routine editor
300     */
301    param_template: ''
302};
303
304/**
305 * @var RTE.COMMON a JavaScript namespace containing the functionality
306 *                 for Routines, Triggers and Events
307 *
308 *                 This namespace is extended by the functionality required
309 *                 to handle a specific item (a routine, trigger or event)
310 *                 in the relevant javascript files in this folder
311 */
312RTE.COMMON = {
313    /**
314     * @var $ajaxDialog Query object containing the reference to the
315     *                  dialog that contains the editor
316     */
317    $ajaxDialog: null,
318    /**
319     * @var syntaxHiglighter Reference to the codemirror editor
320     */
321    syntaxHiglighter: null,
322    /**
323     * @var buttonOptions Object containing options for
324     *                    the jQueryUI dialog buttons
325     */
326    buttonOptions: {},
327    /**
328     * Validate editor form fields.
329     */
330    validate: function () {
331        /**
332         * @var $elm a jQuery object containing the reference
333         *           to an element that is being validated
334         */
335        var $elm = null;
336        // Common validation. At the very least the name
337        // and the definition must be provided for an item
338        $elm = $('table.rte_table').last().find('input[name=item_name]');
339        if ($elm.val() === '') {
340            $elm.focus();
341            alert(PMA_messages['strFormEmpty']);
342            return false;
343        }
344        $elm = $('table.rte_table').find('textarea[name=item_definition]');
345        if ($elm.val() === '') {
346            if (this.syntaxHiglighter !== null) {
347                this.syntaxHiglighter.focus();
348            }
349            else {
350                $('textarea[name=item_definition]').last().focus();
351            }
352            alert(PMA_messages['strFormEmpty']);
353            return false;
354        }
355        // The validation has so far passed, so now
356        // we can validate item-specific fields.
357        return this.validateCustom();
358    }, // end validate()
359    /**
360     * Validate custom editor form fields.
361     * This function can be overridden by
362     * other files in this folder
363     */
364    validateCustom: function () {
365        return true;
366    }, // end validateCustom()
367    /**
368     * Execute some code after the ajax
369     * dialog for the ditor is shown.
370     * This function can be overridden by
371     * other files in this folder
372     */
373    postDialogShow: function () {
374        // Nothing by default
375    }, // end postDialogShow()
376
377    exportDialog: function ($this) {
378        var $msg = PMA_ajaxShowMessage();
379        // Fire the ajax request straight away
380        $.get($this.attr('href'), {'ajax_request': true}, function (data) {
381            if (data.success === true) {
382                PMA_ajaxRemoveMessage($msg);
383                /**
384                 * @var button_options Object containing options
385                 *                     for jQueryUI dialog buttons
386                 */
387                var button_options = {};
388                button_options[PMA_messages['strClose']] = function () {
389                    $(this).dialog("close").remove();
390                };
391                /**
392                 * Display the dialog to the user
393                 */
394                var $ajaxDialog = $('<div>' + data.message + '</div>').dialog({
395                                      width: 500,
396                                      buttons: button_options,
397                                      title: data.title
398                                  });
399                // Attach syntax highlited editor to export dialog
400                /**
401                 * @var $elm jQuery object containing the reference
402                 *           to the Export textarea.
403                 */
404                var $elm = $ajaxDialog.find('textarea');
405                /**
406                 * @var opts Options to pass to the codemirror editor
407                 */
408                var opts = {
409                    lineNumbers: true,
410                    matchBrackets: true,
411                    indentUnit: 4,
412                    mode: "text/x-mysql"
413                };
414                CodeMirror.fromTextArea($elm[0], opts);
415            } else {
416                PMA_ajaxShowMessage(data.error, false);
417            }
418        }); // end $.get()
419    },  // end exportDialog()
420    editorDialog: function (is_new, $this) {
421        var that = this;
422        /**
423         * @var $edit_row jQuery object containing the reference to
424         *                the row of the the item being edited
425         *                from the list of items
426         */
427        var $edit_row = null;
428        if ($this.hasClass('edit_anchor')) {
429            // Remeber the row of the item being edited for later,
430            // so that if the edit is successful, we can replace the
431            // row with info about the modified item.
432            $edit_row = $this.parents('tr');
433        }
434        /**
435         * @var $msg jQuery object containing the reference to
436         *           the AJAX message shown to the user
437         */
438        var $msg = PMA_ajaxShowMessage();
439        $.get($this.attr('href'), {'ajax_request': true}, function (data) {
440            if (data.success === true) {
441                // We have successfully fetched the editor form
442                PMA_ajaxRemoveMessage($msg);
443                // Now define the function that is called when
444                // the user presses the "Go" button
445                that.buttonOptions[PMA_messages['strGo']] = function () {
446                    // Move the data from the codemirror editor back to the
447                    // textarea, where it can be used in the form submission.
448                    if (typeof CodeMirror != 'undefined') {
449                        that.syntaxHiglighter.save();
450                    }
451                    // Validate editor and submit request, if passed.
452                    if (that.validate()) {
453                        /**
454                         * @var data Form data to be sent in the AJAX request
455                         */
456                        var data = $('form.rte_form').last().serialize();
457                        $msg = PMA_ajaxShowMessage(
458                            PMA_messages['strProcessingRequest']
459                        );
460                        var url = $('form.rte_form').last().attr('action');
461                        $.post(url, data, function (data) {
462                            if (data.success === true) {
463                                // Item created successfully
464                                PMA_ajaxRemoveMessage($msg);
465                                PMA_slidingMessage(data.message);
466                                that.$ajaxDialog.dialog('close');
467                                // If we are in 'edit' mode, we must
468                                // remove the reference to the old row.
469                                if (mode === 'edit') {
470                                    $edit_row.remove();
471                                }
472                                // Sometimes, like when moving a trigger from
473                                // a table to another one, the new row should
474                                // not be inserted into the list. In this case
475                                // "data.insert" will be set to false.
476                                if (data.insert) {
477                                    // Insert the new row at the correct
478                                    // location in the list of items
479                                    /**
480                                     * @var text Contains the name of an item from
481                                     *           the list that is used in comparisons
482                                     *           to find the correct location where
483                                     *           to insert a new row.
484                                     */
485                                    var text = '';
486                                    /**
487                                     * @var inserted Whether a new item has been
488                                     *               inserted in the list or not
489                                     */
490                                    var inserted = false;
491                                    $('table.data').find('tr').each(function () {
492                                        text = $(this)
493                                                .children('td')
494                                                .eq(0)
495                                                .find('strong')
496                                                .text()
497                                                .toUpperCase();
498                                        text = $.trim(text);
499                                        if (text !== '' && text > data.name) {
500                                            $(this).before(data.new_row);
501                                            inserted = true;
502                                            return false;
503                                        }
504                                    });
505                                    if (! inserted) {
506                                        // If we didn't manage to insert the row yet,
507                                        // it must belong at the end of the list,
508                                        // so we insert it there.
509                                        $('table.data').append(data.new_row);
510                                    }
511                                    // Fade-in the new row
512                                    $('tr.ajaxInsert')
513                                        .show('slow')
514                                        .removeClass('ajaxInsert');
515                                } else if ($('table.data').find('tr').has('td').length === 0) {
516                                    // If we are not supposed to insert the new row,
517                                    // we will now check if the table is empty and
518                                    // needs to be hidden. This will be the case if
519                                    // we were editing the only item in the list,
520                                    // which we removed and will not be inserting
521                                    // something else in its place.
522                                    $('table.data').hide("slow", function () {
523                                        $('#nothing2display').show("slow");
524                                    });
525                                }
526                                // Now we have inserted the row at the correct
527                                // position, but surely at least some row classes
528                                // are wrong now. So we will itirate throught
529                                // all rows and assign correct classes to them
530                                /**
531                                 * @var ct Count of processed rows
532                                 */
533                                var ct = 0;
534                                /**
535                                 * @var rowclass Class to be attached to the row
536                                 *               that is being processed
537                                 */
538                                var rowclass = '';
539                                $('table.data').find('tr').has('td').each(function () {
540                                    rowclass = (ct % 2 === 0) ? 'odd' : 'even';
541                                    $(this).removeClass().addClass(rowclass);
542                                    ct++;
543                                });
544                                // If this is the first item being added, remove
545                                // the "No items" message and show the list.
546                                if ($('table.data').find('tr').has('td').length > 0
547                                    && $('#nothing2display').is(':visible')) {
548                                    $('#nothing2display').hide("slow", function () {
549                                        $('table.data').show("slow");
550                                    });
551                                }
552                                PMA_reloadNavigation();
553                            } else {
554                                PMA_ajaxShowMessage(data.error, false);
555                            }
556                        }); // end $.post()
557                    } // end "if (that.validate())"
558                }; // end of function that handles the submission of the Editor
559                that.buttonOptions[PMA_messages['strClose']] = function () {
560                    $(this).dialog("close");
561                };
562                /**
563                 * Display the dialog to the user
564                 */
565                that.$ajaxDialog = $('<div>' + data.message + '</div>').dialog({
566                                width: 700,
567                                minWidth: 500,
568                                buttons: that.buttonOptions,
569                                title: data.title,
570                                modal: true,
571                                close: function () {
572                                    $(this).remove();
573                                }
574                        });
575                that.$ajaxDialog.find('input[name=item_name]').focus();
576                that.$ajaxDialog.find('input.datefield, input.datetimefield').each(function () {
577                    PMA_addDatepicker($(this).css('width', '95%'));
578                });
579                /**
580                 * @var mode Used to remeber whether the editor is in
581                 *           "Edit" or "Add" mode
582                 */
583                var mode = 'add';
584                if ($('input[name=editor_process_edit]').length > 0) {
585                    mode = 'edit';
586                }
587                // Attach syntax highlited editor to the definition
588                /**
589                 * @var elm jQuery object containing the reference to
590                 *                 the Definition textarea.
591                 */
592                var $elm = $('textarea[name=item_definition]').last();
593                /**
594                 * @var opts Options to pass to the codemirror editor
595                 */
596                var opts = {
597                    lineNumbers: true,
598                    matchBrackets: true,
599                    indentUnit: 4,
600                    mode: "text/x-mysql"
601                };
602                if (typeof CodeMirror != 'undefined') {
603                    that.syntaxHiglighter = CodeMirror.fromTextArea($elm[0], opts);
604                }
605                // Execute item-specific code
606                that.postDialogShow(data);
607            } else {
608                PMA_ajaxShowMessage(data.error, false);
609            }
610        }); // end $.get()
611    },
612
613    dropDialog: function ($this) {
614       /**
615         * @var $curr_row Object containing reference to the current row
616         */
617        var $curr_row = $this.parents('tr');
618        /**
619         * @var question String containing the question to be asked for c
619onfirmation
620         */
621        var question = $('<div/>').text(
622            $curr_row.children('td').children('.drop_sql').html()
623        );
624        // We ask for confirmation first here, before submitting the ajax request
625        $this.PMA_confirm(question, $this.attr('href'), function (url) {
626            /**
627             * @var msg jQuery object containing the reference to
628             *          the AJAX message shown to the user
629             */
630            var $msg = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']);
631            $.get(url, {'is_js_confirmed': 1, 'ajax_request': true}, function (data) {
632                if (data.success === true) {
633                    /**
634                     * @var $table Object containing reference
635                     *             to the main list of elements
636                     */
637                    var $table = $curr_row.parent();
638                    // Check how many rows will be left after we remove
639                    // the one that the user has requested us to remove
640                    if ($table.find('tr').length === 3) {
641                        // If there are two rows left, it means that they are
642                        // the header of the table and the rows that we are
643                        // about to remove, so after the removal there will be
644                        // nothing to show in the table, so we hide it.
645                        $table.hide("slow", function () {
646                            $(this).find('tr.even, tr.odd').remove();
647                            $('#nothing2display').show("slow");
648                        });
649                    } else {
650                        $curr_row.hide("slow", function () {
651                            $(this).remove();
652                            // Now we have removed the row from the list, but maybe
653                            // some row classes are wrong now. So we will itirate
654                            // throught all rows and assign correct classes to them.
655                            /**
656                             * @var ct Count of processed rows
657                             */
658                            var ct = 0;
659                            /**
660                             * @var rowclass Class to be attached to the row
661                             *               that is being processed
662                             */
663                            var rowclass = '';
664                            $table.find('tr').has('td').each(function () {
665                                rowclass = (ct % 2 === 0) ? 'odd' : 'even';
666                                $(this).removeClass().addClass(rowclass);
667                                ct++;
668                            });
669                        });
670                    }
671                    // Get rid of the "Loading" message
672                    PMA_ajaxRemoveMessage($msg);
673                    // Show the query that we just executed
674                    PMA_slidingMessage(data.sql_query);
675                    PMA_reloadNavigation();
676                } else {
677                    PMA_ajaxShowMessage(data.error, false);
678                }
679            }); // end $.get()
680        }); // end $.PMA_confirm()
681    }
682}; // end RTE namespace
683
684/**
685 * @var RTE.EVENT JavaScript functionality for events
686 */
687RTE.EVENT = {
688    validateCustom: function () {
689        /**
690         * @var elm a jQuery object containing the reference
691         *          to an element that is being validated
692         */
693        var $elm = null;
694        if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'RECURRING') {
695            // The interval field must not be empty for recurring events
696            $elm = this.$ajaxDialog.find('input[name=item_interval_value]');
697            if ($elm.val() === '') {
698                $elm.focus();
699                alert(PMA_messages['strFormEmpty']);
700                return false;
701            }
702        } else {
703            // The execute_at field must not be empty for "once off" events
704            $elm = this.$ajaxDialog.find('input[name=item_execute_at]');
705            if ($elm.val() === '') {
706                $elm.focus();
707                alert(PMA_messages['strFormEmpty']);
708                return false;
709            }
710        }
711        return true;
712    }
713};
714
715/**
716 * @var RTE.ROUTINE JavaScript functionality for routines
717 */
718RTE.ROUTINE = {
719    /**
720     * Overriding the postDialogShow() function defined in common.js
721     *
722     * @param data JSON-encoded data from the ajax request
723     */
724    postDialogShow: function (data) {
725        // Cache the template for a parameter table row
726        RTE.param_template = data.param_template;
727        var that = this;
728        // Make adjustments in the dialog to make it AJAX compatible
729        $('td.routine_param_remove').show();
730        $('input[name=routine_removeparameter]').remove();
731        $('input[name=routine_addparameter]').css('width', '100%');
732        // Enable/disable the 'options' dropdowns for parameters as necessary
733        $('table.routine_params_table').last().find('th[colspan=2]').attr('colspan', '1');
734        $('table.routine_params_table').last().find('tr').has('td').each(function () {
735            that.setOptionsForParameter(
736                $(this).find('select[name^=item_param_type]'),
737                $(this).find('input[name^=item_param_length]'),
738                $(this).find('select[name^=item_param_opts_text]'),
739                $(this).find('select[name^=item_param_opts_num]')
740            );
741        });
742        // Enable/disable the 'options' dropdowns for
743        // function return value as necessary
744        this.setOptionsForParameter(
745            $('table.rte_table').last().find('select[name=item_returntype]'),
746            $('table.rte_table').last().find('input[name=item_returnlength]'),
747            $('table.rte_table').last().find('select[name=item_returnopts_text]'),
748            $('table.rte_table').last().find('select[name=item_returnopts_num]')
749        );
750    },
751    /**
752     * Overriding the validateCustom() function defined in common.js
753     */
754    validateCustom: function () {
755        /**
756         * @var isSuccess Stores the outcome of the validation
757         */
758        var isSuccess = true;
759        /**
760         * @var inputname The value of the "name" attribute for
761         *                the field that is being processed
762         */
763        var inputname = '';
764        this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () {
765            // Every parameter of a routine must have
766            // a non-empty direction, name and type
767            if (isSuccess) {
768                $(this).find(':input').each(function () {
769                    inputname = $(this).attr('name');
770                    if (inputname.substr(0, 14) === 'item_param_dir' ||
771                        inputname.substr(0, 15) === 'item_param_name' ||
772                        inputname.substr(0, 15) === 'item_param_type') {
773                        if ($(this).val() === '') {
774                            $(this).focus();
775                            isSuccess = false;
776                            return false;
777                        }
778                    }
779                });
780            } else {
781                return false;
782            }
783        });
784        if (! isSuccess) {
785            alert(PMA_messages['strFormEmpty']);
786            return false;
787        }
788        this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () {
789            // SET, ENUM, VARCHAR and VARBINARY fields must have length/values
790            var $inputtyp = $(this).find('select[name^=item_param_type]');
791            var $inputlen = $(this).find('input[name^=item_param_length]');
792            if ($inputtyp.length && $inputlen.length) {
793                if (($inputtyp.val() === 'ENUM' || $inputtyp.val() === 'SET' || $inputtyp.val().substr(0, 3) === 'VAR')
794                   && $inputlen.val() === '') {
795                    $inputlen.focus();
796                    isSuccess = false;
797                    return false;
798                }
799            }
800        });
801        if (! isSuccess) {
802            alert(PMA_messages['strFormEmpty']);
803            return false;
804        }
805        if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'FUNCTION') {
806            // The length/values of return variable for functions must
807            // be set, if the type is SET, ENUM, VARCHAR or VARBINARY.
808            var $returntyp = this.$ajaxDialog.find('select[name=item_returntype]');
809            var $returnlen = this.$ajaxDialog.find('input[name=item_returnlength]');
810            if (($returntyp.val() === 'ENUM' || $returntyp.val() === 'SET' || $returntyp.val().substr(0, 3) === 'VAR')
811               && $returnlen.val() === '') {
812                $returnlen.focus();
813                alert(PMA_messages['strFormEmpty']);
814                return false;
815            }
816        }
817        if ($('select[name=item_type]').find(':selected').val() === 'FUNCTION') {
818            // A function must contain a RETURN statement in its definition
819            if (this.$ajaxDialog.find('table.rte_table').find('textarea[name=item_definition]').val().toUpperCase().indexOf('RETURN') < 0) {
820                this.syntaxHiglighter.focus();
821                alert(PMA_messages['MissingReturn']);
822                return false;
823            }
824        }
825        return true;
826    },
827    /**
828     * Enable/disable the "options" dropdown and "length" input for
829     * parameters and the return variable in the routine editor
830     * as necessary.
831     *
832     * @param type a jQuery object containing the reference
833     *             to the "Type" dropdown box
834     * @param len  a jQuery object containing the reference
835     *             to the "Length" input box
836     * @param text a jQuery object containing the reference
837     *             to the dropdown box with options for
838     *             parameters of text type
839     * @param num  a jQuery object containing the reference
840     *             to the dropdown box with options for
841     *             parameters of numeric type
842     */
843    setOptionsForParameter: function ($type, $len, $text, $num) {
844        /**
845         * @var no_opts a jQuery object containing the reference
846         *              to an element to be displayed when no
847         *              options are available
848         */
849        var $no_opts = $text.parent().parent().find('.no_opts');
850        /**
851         * @var no_len a jQuery object containing the reference
852         *             to an element to be displayed when no
853         *             "length/values" field is available
854         */
855        var $no_len  = $len.parent().parent().find('.no_len');
856
857        // Process for parameter options
858        switch ($type.val()) {
859        case 'TINYINT':
860        case 'SMALLINT':
861        case 'MEDIUMINT':
862        case 'INT':
863        case 'BIGINT':
864        case 'DECIMAL':
865        case 'FLOAT':
866        case 'DOUBLE':
867        case 'REAL':
868            $text.parent().hide();
869            $num.parent().show();
870            $no_opts.hide();
871            break;
872        case 'TINYTEXT':
873        case 'TEXT':
874        case 'MEDIUMTEXT':
875        case 'LONGTEXT':
876        case 'CHAR':
877        case 'VARCHAR':
878        case 'SET':
879        case 'ENUM':
880            $text.parent().show();
881            $num.parent().hide();
882            $no_opts.hide();
883            break;
884        default:
885            $text.parent().hide();
886            $num.parent().hide();
887            $no_opts.show();
888            break;
889        }
890        // Process for parameter length
891        switch ($type.val()) {
892        case 'DATE':
893        case 'DATETIME':
894        case 'TIME':
895        case 'TINYBLOB':
896        case 'TINYTEXT':
897        case 'BLOB':
898        case 'TEXT':
899        case 'MEDIUMBLOB':
900        case 'MEDIUMTEXT':
901        case 'LONGBLOB':
902        case 'LONGTEXT':
903            $text.closest('tr').find('a:first').hide();
904            $len.parent().hide();
905            $no_len.show();
906            break;
907        default:
908            if ($type.val() == 'ENUM' || $type.val() == 'SET') {
909                $text.closest('tr').find('a:first').show();
910            } else {
911                $text.closest('tr').find('a:first').hide();
912            }
913            $len.parent().show();
914            $no_len.hide();
915            break;
916        }
917    },
918    executeDialog: function ($this) {
919        var that = this;
920        /**
921         * @var msg jQuery object containing the reference to
922         *          the AJAX message shown to the user
923         */
924        var $msg = PMA_ajaxShowMessage();
925        $.get($this.attr('href'), {'ajax_request': true}, function (data) {
926            if (data.success === true) {
927                PMA_ajaxRemoveMessage($msg);
928                // If 'data.dialog' is true we show a dialog with a form
929                // to get the input parameters for routine, otherwise
930                // we just show the results of the query
931                if (data.dialog) {
932                    // Define the function that is called when
933                    // the user presses the "Go" button
934                    that.buttonOptions[PMA_messages['strGo']] = function () {
935                        /**
936                         * @var data Form data to be sent in the AJAX request
937                         */
938                        var data = $('form.rte_form').last().serialize();
939                        $msg = PMA_ajaxShowMessage(
940                            PMA_messages['strProcessingRequest']
941                        );
942                        $.post('db_routines.php', data, function (data) {
943                            if (data.success === true) {
944                                // Routine executed successfully
945                                PMA_ajaxRemoveMessage($msg);
946                                PMA_slidingMessage(data.message);
947                                $ajaxDialog.dialog('close');
948                            } else {
949                                PMA_ajaxShowMessage(data.error, false);
950                            }
951                        });
952                    };
953                    that.buttonOptions[PMA_messages['strClose']] = function () {
954                        $(this).dialog("close");
955                    };
956                    /**
957                     * Display the dialog to the user
958                     */
959                    $ajaxDialog = $('<div>' + data.message + '</div>').dialog({
960                        width: 650,
961                        buttons: that.buttonOptions,
962                        title: data.title,
963                        modal: true,
964                        close: function () {
965                            $(this).remove();
966                        }
967                    });
968                    $ajaxDialog.find('input[name^=params]').first().focus();
969                    /**
970                     * Attach the datepickers to the relevant form fields
971                     */
972                    $ajaxDialog.find('input.datefield, input.datetimefield').each(function () {
973                        PMA_addDatepicker($(this).css('width', '95%'));
974                    });
975                } else {
976                    // Routine executed successfully
977                    PMA_slidingMessage(data.message);
978                }
979            } else {
980                PMA_ajaxShowMessage(data.error, false);
981            }
982        }); // end $.get()
983    }
984};
985
986/**
987 * Attach Ajax event handlers for the Routines, Triggers and Events editor
988 *
989 * FIXME: submit on ENTER keypress in dialog
990 */
991$(function () {
992    /**
993     * Attach Ajax event handlers for the Add/Edit functionality.
994     */
995    $('a.ajax.add_anchor, a.ajax.edit_anchor').live('click', function (event) {
996        event.preventDefault();
997        var type = $(this).attr('href').substr(0, $(this).attr('href').indexOf('?'));
998        if (type.indexOf('routine') != -1) {
999            type = 'routine';
1000        } else if (type.indexOf('trigger') != -1) {
1001            type = 'trigger';
1002        } else if (type.indexOf('event') != -1) {
1003            type = 'event';
1004        } else {
1005            type = '';
1006        }
1007        var dialog = new RTE.object(type);
1008        dialog.editorDialog($(this).hasClass('add_anchor'), $(this));
1009    }); // end $.live()
1010
1011    /**
1012     * Attach Ajax event handlers for the Execute routine functionality
1013     */
1014    $('a.ajax.exec_anchor').live('click', function (event) {
1015        event.preventDefault();
1016        var dialog = new RTE.object('routine');
1017        dialog.executeDialog($(this));
1018    }); // end $.live()
1019
1020    /**
1021     * Attach Ajax event handlers for Export of Routines, Triggers and Events
1022     */
1023    $('a.ajax.export_anchor').live('click', function (event) {
1024        event.preventDefault();
1025        var dialog = new RTE.object();
1026        dialog.exportDialog($(this));
1027    }); // end $.live()
1028
1029    /**
1030     * Attach Ajax event handlers for Drop functionality
1031     * of Routines, Triggers and Events.
1032     */
1033    $('a.ajax.drop_anchor').live('click', function (event) {
1034        event.preventDefault();
1035         var dialog = new RTE.object();
1036        dialog.dropDialog($(this));
1037    }); // end $.live()
1038
1039    /**
1040     * Attach Ajax event handlers for the "Change event/routine type"
1041     * functionality in the events editor, so that the correct
1042     * rows are shown in the editor when changing the event type
1043     */
1044    $('select[name=item_type]').live('change', function () {
1045        $(this)
1046        .closest('table')
1047        .find('tr.recurring_event_row, tr.onetime_event_row, tr.routine_return_row, td.routine_direction_cell')
1048        .toggle();
1049    }); // end $.live()
1050
1051    /**
1052     * Attach Ajax event handlers for the "Change parameter type"
1053     * functionality in the routines editor, so that the correct
1054     * option/length fields, if any, are shown when changing
1055     * a parameter type
1056     */
1057    $('select[name^=item_param_type]').live('change', function () {
1058        /**
1059         * @var row jQuery object containing the reference to
1060         *          a row in the routine parameters table
1061         */
1062        var $row = $(this).parents('tr').first();
1063        var rte = new RTE.object('routine');
1064        rte.setOptionsForParameter(
1065            $row.find('select[name^=item_param_type]'),
1066            $row.find('input[name^=item_param_length]'),
1067            $row.find('select[name^=item_param_opts_text]'),
1068            $row.find('select[name^=item_param_opts_num]')
1069        );
1070    }); // end $.live()
1071
1072    /**
1073     * Attach Ajax event handlers for the "Change the type of return
1074     * variable of function" functionality, so that the correct fields,
1075     * if any, are shown when changing the function return type type
1076     */
1077    $('select[name=item_returntype]').live('change', function () {
1078        var rte = new RTE.object('routine');
1079        var $table = $(this).closest('table.rte_table');
1080        rte.setOptionsForParameter(
1081            $table.find('select[name=item_returntype]'),
1082            $table.find('input[name=item_returnlength]'),
1083            $table.find('select[name=item_returnopts_text]'),
1084            $table.find('select[name=item_returnopts_num]')
1085        );
1086    }); // end $.live()
1087
1088    /**
1089     * Attach Ajax event handlers for the "Add parameter to routine" functionality
1090     */
1091    $('input[name=routine_addparameter]').live('click', function (event) {
1092        event.preventDefault();
1093        /**
1094         * @var routine_params_table jQuery object containing the reference
1095         *                           to the routine parameters table
1096         */
1097        var $routine_params_table = $(this).closest('div.ui-dialog').find('.routine_params_table');
1098        /**
1099         * @var new_param_row A string containing the HTML code for the
1100         *                    new row for the routine paramaters table
1101         */
1102        var new_param_row = RTE.param_template.replace(/%s/g, $routine_params_table.find('tr').length - 1);
1103        // Append the new row to the parameters table
1104        $routine_params_table.append(new_param_row);
1105        // Make sure that the row is correctly shown according to the type of routine
1106        if ($(this).closest('div.ui-dialog').find('table.rte_table select[name=item_type]').val() === 'FUNCTION') {
1107            $('tr.routine_return_row').show();
1108            $('td.routine_direction_cell').hide();
1109        }
1110        /**
1111         * @var newrow jQuery object containing the reference to the newly
1112         *             inserted row in the routine parameters table
1113         */
1114        var $newrow = $(this).closest('div.ui-dialog').find('table.routine_params_table').find('tr').has('td').last();
1115        // Enable/disable the 'options' dropdowns for parameters as necessary
1116        var rte = new RTE.object('routine');
1117        rte.setOptionsForParameter(
1118            $newrow.find('select[name^=item_param_type]'),
1119            $newrow.find('input[name^=item_param_length]'),
1120            $newrow.find('select[name^=item_param_opts_text]'),
1121            $newrow.find('select[name^=item_param_opts_num]')
1122        );
1123    }); // end $.live()
1124
1125    /**
1126     * Attach Ajax event handlers for the
1127     * "Remove parameter from routine" functionality
1128     */
1129    $('a.routine_param_remove_anchor').live('click', function (event) {
1130        event.preventDefault();
1131        $(this).parent().parent().remove();
1132        // After removing a parameter, the indices of the name attributes in
1133        // the input fields lose the correct order and need to be reordered.
1134        /**
1135         * @var index Counter used for reindexing the input
1136         *            fields in the routine parameters table
1137         */
1138        var index = 0;
1139        $(this).closest('div.ui-dialog').find('table.routine_params_table').find('tr').has('td').each(function () {
1140            $(this).find(':input').each(function () {
1141                /**
1142                 * @var inputname The value of the name attribute of
1143                 *                the input field being reindexed
1144                 */
1145                var inputname = $(this).attr('name');
1146                if (inputname.substr(0, 14) === 'item_param_dir') {
1147                    $(this).attr('name', inputname.substr(0, 14) + '[' + index + ']');
1148                } else if (inputname.substr(0, 15) === 'item_param_name') {
1149                    $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']');
1150                } else if (inputname.substr(0, 15) === 'item_param_type') {
1151                    $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']');
1152                } else if (inputname.substr(0, 17) === 'item_param_length') {
1153                    $(this).attr('name', inputname.substr(0, 17) + '[' + index + ']');
1154                    $(this).attr('id', 'item_param_length_' + index);
1155                } else if (inputname.substr(0, 20) === 'item_param_opts_text') {
1156                    $(this).attr('name', inputname.substr(0, 20) + '[' + index + ']');
1157                } else if (inputname.substr(0, 19) === 'item_param_opts_num') {
1158                    $(this).attr('name', inputname.substr(0, 19) + '[' + index + ']');
1159                }
1160            });
1161            index++;
1162        });
1163    }); // end $.live()
1164}); // end of $()
1165;
1166
1167/* vim: set expandtab sw=4 ts=4 sts=4: */
1168/**
1169 * general function, usually for data manipulation pages
1170 *
1171 */
1172
1173/**
1174 * @var $table_clone reference to the action links on the tbl_structure page
1175 */
1176var $table_clone = false;
1177
1178/**
1179 * @var sql_box_locked lock for the sqlbox textarea in the querybox/querywindow
1180 */
1181var sql_box_locked = false;
1182
1183/**
1184 * @var array holds elements which content should only selected once
1185 */
1186var only_once_elements = [];
1187
1188/**
1189 * @var   int   ajax_message_count   Number of AJAX messages shown since page load
1190 */
1191var ajax_message_count = 0;
1192
1193/**
1194 * @var codemirror_editor object containing CodeMirror editor of the query editor in SQL tab
1195 */
1196var codemirror_editor = false;
1197
1198/**
1199 * @var codemirror_editor object containing CodeMirror editor of the inline query editor
1200 */
1201var codemirror_inline_editor = false;
1202
1203/**
1204 * @var chart_activeTimeouts object active timeouts that refresh the charts. When disabling a realtime chart, this can be used to stop the continuous ajax requests
1205 */
1206var chart_activeTimeouts = {};
1207
1208/**
1209 * Make sure that ajax requests will not be cached
1210 * by appending a random variable to their parameters
1211 */
1212$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
1213    var nocache = new Date().getTime() + "" + Math.floor(Math.random() * 1000000);
1214    if (typeof options.data == "string") {
1215        options.data += "&_nocache=" + nocache;
1216    } else if (typeof options.data == "object") {
1217        options.data = $.extend(originalOptions.data, {'_nocache':nocache});
1218    }
1219});
1220
1221/**
1222 * Add a hidden field to the form to indicate that this will be an
1223 * Ajax request (only if this hidden field does not exist)
1224 *
1225 * @param object   the form
1226 */
1227function PMA_prepareForAjaxRequest($form)
1228{
1229    if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
1230        $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
1231    }
1232}
1233
1234/**
1235 * Generate a new password and copy it to the password input areas
1236 *
1237 * @param object   the form that holds the password fields
1238 *
1239 * @return boolean  always true
1240 */
1241function suggestPassword(passwd_form)
1242{
1243    // restrict the password to just letters and numbers to avoid problems:
1244    // "editors and viewers regard the password as multiple words and
1245    // things like double click no longer work"
1246    var pwchars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWYXZ";
1247    var passwordlength = 16;    // do we want that to be dynamic?  no, keep it simple :)
1248    var passwd = passwd_form.generated_pw;
1249    var randomWords = new Int32Array(passwordlength);
1250
1251    passwd.value = '';
1252
1253    // First we're going to try to use a built-in CSPRNG
1254    if (window.crypto && window.crypto.getRandomValues) {
1255        window.crypto.getRandomValues(randomWords);
1256    }
1257    // Because of course IE calls it msCrypto instead of being standard
1258    else if (window.msCrypto && window.msCrypto.getRandomValues) {
1259        window.msCrypto.getRandomValues(randomWords);
1260    } else {
1261        // Fallback to Math.random
1262        for (var i = 0; i < passwordlength; i++) {
1263            randomWords[i] = Math.floor(Math.random() * pwchars.length);
1264        }
1265    }
1266
1267    for (var i = 0; i < passwordlength; i++) {
1268        passwd.value += pwchars.charAt(Math.abs(randomWords[i]) % pwchars.length);
1269    }
1270
1271    passwd_form.text_pma_pw.value = passwd.value;
1272    passwd_form.text_pma_pw2.value = passwd.value;
1273    return true;
1274}
1275
1276/**
1277 * Version string to integer conversion.
1278 */
1279function parseVersionString (str)
1280{
1281    if (typeof(str) != 'string') { return false; }
1282    var add = 0;
1283    // Parse possible alpha/beta/rc/
1284    var state = str.split('-');
1285    if (state.length >= 2) {
1286        if (state[1].substr(0, 2) == 'rc') {
1287            add = - 20 - parseInt(state[1].substr(2));
1288        } else if (state[1].substr(0, 4) == 'beta') {
1289            add =  - 40 - parseInt(state[1].substr(4));
1290        } else if (state[1].substr(0, 5) == 'alpha') {
1291            add =  - 60 - parseInt(state[1].substr(5));
1292        } else if (state[1].substr(0, 3) == 'dev') {
1293            /* We don't handle dev, it's git snapshot */
1294            add = 0;
1295        }
1296    }
1297    // Parse version
1298    var x = str.split('.');
1299    // Use 0 for non existing parts
1300    var maj = parseInt(x[0]) || 0;
1301    var min = parseInt(x[1]) || 0;
1302    var pat = parseInt(x[2]) || 0;
1303    var hotfix = parseInt(x[3]) || 0;
1304    return  maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add;
1305}
1306
1307/**
1308 * Indicates current available version on main page.
1309 */
1310function PMA_current_version(data)
1311{
1312    if (data && data.version && data.date) {
1313        var current = parseVersionString(pmaversion);
1314        var latest = parseVersionString(data['version']);
1315        var version_information_message = PMA_messages['strLatestAvailable'] + ' ' + escapeHtml(data['version']);
1316        if (latest > current) {
1317            var message = $.sprintf(
1318                PMA_messages['strNewerVersion'],
1319                escapeHtml(data['version']),
1320                escapeHtml(data['date'])
1321            );
1322            if (Math.floor(latest / 10000) === Math.floor(current / 10000)) {
1323                /* Security update */
1324                var klass = 'error';
1325            } else {
1326                var klass = 'notice';
1327            }
1328            $('#maincontainer').after('<div class="' + klass + '">' + message + '</div>');
1329        }
1330        if (latest === current) {
1331            version_information_message = ' (' + PMA_messages['strUpToDate'] + ')';
1332        }
1333        $('#li_pma_version').append(version_information_message);
1334    }
1335}
1336
1337/**
1338 * Loads Git revision data from ajax for index.php
1339 */
1340function PMA_display_git_revision()
1341{
1342    $('#is_git_revision').remove();
1343    $.get(
1344        "index.php",
1345        {
1346            "server": PMA_commonParams.get('server'),
1347            "token": PMA_commonParams.get('token'),
1348            "git_revision": true,
1349            "ajax_request": true
1350        },
1351        function (data) {
1352            if (data.success == true) {
1353                $(data.message).insertAfter('#li_pma_version');
1354            }
1355        }
1356    );
1357}
1358
1359/**
1360 * for libraries/display_change_password.lib.php
1361 *     libraries/user_password.php
1362 *
1363 */
1364
1365function displayPasswordGenerateButton()
1366{
1367    $('#tr_element_before_generate_password').parent().append('<tr class="vmiddle"><td>' + PMA_messages['strGeneratePassword'] + '</td><td><input type="button" class="button" id="button_generate_password" value="' + PMA_messages['strGenerate'] + '" onclick="suggestPassword(this.form)" /><input type="text" name="generated_pw" id="generated_pw" /></td></tr>');
1368    $('#div_element_before_generate_password').parent().append('<div class="item"><label for="button_generate_password">' + PMA_messages['strGeneratePassword'] + ':</label><span class="options"><input type="button" class="button" id="button_generate_password" value="' + PMA_messages['strGenerate'] + '" onclick="suggestPassword(this.form)" /></span><input type="text" name="generated_pw" id="generated_pw" /></div>');
1369}
1370
1371/*
1372 * Adds a date/time picker to an element
1373 *
1374 * @param object  $this_element   a jQuery object pointing to the element
1375 */
1376function PMA_addDatepicker($this_element, options)
1377{
1378    var showTimeOption = false;
1379    if ($this_element.is('.datetimefield')) {
1380        showTimeOption = true;
1381    }
1382
1383    var defaultOptions = {
1384        showOn: 'button',
1385        buttonImage: themeCalendarImage, // defined in js/messages.php
1386        buttonImageOnly: true,
1387        stepMinutes: 1,
1388        stepHours: 1,
1389        showSecond: true,
1390        showTimepicker: showTimeOption,
1391        showButtonPanel: false,
1392        dateFormat: 'yy-mm-dd', // yy means year with four digits
1393        timeFormat: 'HH:mm:ss',
1394        altFieldTimeOnly: false,
1395        showAnim: '',
1396        beforeShow: function(input, inst) {
1397            // Remember that we came from the datepicker; this is used
1398            // in tbl_change.js by verificationsAfterFieldChange()
1399            $this_element.data('comes_from', 'datepicker');
1400
1401            // Fix wrong timepicker z-index, doesn't work without timeout
1402            setTimeout(function() {
1403                $('#ui-timepicker-div').css('z-index',$('#ui-datepicker-div').css('z-index'));
1404            }, 0);
1405        },
1406        onClose: function(dateText, dp_inst) {
1407            // The value is no more from the date picker
1408            $this_element.data('comes_from', '');
1409        }
1410    };
1411    if ( showTimeOption || (typeof(options) != 'undefined'  && options.showTimepicker) ) {
1412        $this_element.datetimepicker($.extend(defaultOptions, options));
1413    } else {
1414        $this_element.datepicker($.extend(defaultOptions, options));
1415    }
1416}
1417
1418/**
1419 * selects the content of a given object, f.e. a textarea
1420 *
1421 * @param object  element     element of which the content will be selected
1422 * @param var     lock        variable which holds the lock for this element
1423 *                              or true, if no lock exists
1424 * @param boolean only_once   if true this is only done once
1425 *                              f.e. only on first focus
1426 */
1427function selectContent( element, lock, only_once )
1428{
1429    if ( only_once && only_once_elements[element.name] ) {
1430        return;
1431    }
1432
1433    only_once_elements[element.name] = true;
1434
1435    if ( lock  ) {
1436        return;
1437    }
1438
1439    element.select();
1440}
1441
1442/**
1443 * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query.
1444 * This function is called while clicking links
1445 *
1446 * @param object   the link
1447 * @param object   the sql query to submit
1448 *
1449 * @return boolean  whether to run the query or not
1450 */
1451function confirmLink(theLink, theSqlQuery)
1452{
1453    // Confirmation is not required in the configuration file
1454    // or browser is Opera (crappy js implementation)
1455    if (PMA_messages['strDoYouReally'] == '' || typeof(window.opera) != 'undefined') {
1456        return true;
1457    }
1458
1459    var is_confirmed = confirm($.sprintf(PMA_messages['strDoYouReally'], theSqlQuery));
1460    if (is_confirmed) {
1461        if ( $(theLink).hasClass('formLinkSubmit') ) {
1462            var name = 'is_js_confirmed';
1463            if ($(theLink).attr('href').indexOf('usesubform') != -1) {
1464                name = 'subform[' + $(theLink).attr('href').substr('#').match(/usesubform\[(\d+)\]/i)[1] + '][is_js_confirmed]';
1465            }
1466
1467            $(theLink).parents('form').append('<input type="hidden" name="' + name + '" value="1" />');
1468        } else if ( typeof(theLink.href) != 'undefined' ) {
1469            theLink.href += '&is_js_confirmed=1';
1470        } else if ( typeof(theLink.form) != 'undefined' ) {
1471            theLink.form.action += '?is_js_confirmed=1';
1472        }
1473    }
1474
1475    return is_confirmed;
1476} // end of the 'confirmLink()' function
1477
1478/**
1479 * Displays an error message if a "DROP DATABASE" statement is submitted
1480 * while it isn't allowed, else confirms a "DROP/DELETE/ALTER" query before
1481 * sumitting it if required.
1482 * This function is called by the 'checkSqlQuery()' js function.
1483 *
1484 * @param object   the form
1485 * @param object   the sql query textarea
1486 *
1487 * @return boolean  whether to run the query or not
1488 *
1489 * @see     checkSqlQuery()
1490 */
1491function confirmQuery(theForm1, sqlQuery1)
1492{
1493    // Confirmation is not required in the configuration file
1494    if (PMA_messages['strDoYouReally'] == '') {
1495        return true;
1496    }
1497
1498    // "DROP DATABASE" statement isn't allowed
1499    if (PMA_messages['strNoDropDatabases'] != '') {
1500        var drop_re = new RegExp('(^|;)\\s*DROP\\s+(IF EXISTS\\s+)?DATABASE\\s', 'i');
1501        if (drop_re.test(sqlQuery1.value)) {
1502            alert(PMA_messages['strNoDropDatabases']);
1503            theForm1.reset();
1504            sqlQuery1.focus();
1505            return false;
1506        } // end if
1507    } // end if
1508
1509    // Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement
1510    //
1511    // TODO: find a way (if possible) to use the parser-analyser
1512    // for this kind of verification
1513    // For now, I just added a ^ to check for the statement at
1514    // beginning of expression
1515
1516    var do_confirm_re_0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|DATABASE|PROCEDURE)\\s', 'i');
1517    var do_confirm_re_1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i');
1518    var do_confirm_re_2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
1519    var do_confirm_re_3 = new RegExp('^\\s*TRUNCATE\\s', 'i');
1520
1521    if (do_confirm_re_0.test(sqlQuery1.value)
1522        || do_confirm_re_1.test(sqlQuery1.value)
1523        || do_confirm_re_2.test(sqlQuery1.value)
1524        || do_confirm_re_3.test(sqlQuery1.value)) {
1525        var message      = (sqlQuery1.value.length > 100)
1526                         ? sqlQuery1.value.substr(0, 100) + '\n    ...'
1527                         : sqlQuery1.value;
1528        var is_confirmed = confirm($.sprintf(PMA_messages['strDoYouReally'], message));
1529        // statement is confirmed -> update the
1530        // "is_js_confirmed" form field so the confirm test won't be
1531        // run on the server side and allows to submit the form
1532        if (is_confirmed) {
1533            theForm1.elements['is_js_confirmed'].value = 1;
1534            return true;
1535        }
1536        // statement is rejected -> do not submit the form
1537        else {
1538            window.focus();
1539            sqlQuery1.focus();
1540            return false;
1541        } // end if (handle confirm box result)
1542    } // end if (display confirm box)
1543
1544    return true;
1545} // end of the 'confirmQuery()' function
1546
1547/**
1548 * Displays an error message if the user submitted the sql query form with no
1549 * sql query, else checks for "DROP/DELETE/ALTER" statements
1550 *
1551 * @param object   the form
1552 *
1553 * @return boolean  always false
1554 *
1555 * @see     confirmQuery()
1556 */
1557function checkSqlQuery(theForm)
1558{
1559
1560    // First check if codemirror is active.
1561    if (codemirror_editor) {
1562        theForm.elements['sql_query'].value = codemirror_editor.getValue();
1563    }
1564
1565    var sqlQuery = theForm.elements['sql_query'];
1566
1567    var isEmpty  = 1;
1568
1569    var space_re = new RegExp('\\s+');
1570    if (typeof(theForm.elements['sql_file']) != 'undefined' &&
1571            theForm.elements['sql_file'].value.replace(space_re, '') != '') {
1572        return true;
1573    }
1574    if (typeof(theForm.elements['sql_localfile']) != 'undefined' &&
1575            theForm.elements['sql_localfile'].value.replace(space_re, '') != '') {
1576        return true;
1577    }
1578    if (isEmpty && typeof(theForm.elements['id_bookmark']) != 'undefined' &&
1579            (theForm.elements['id_bookmark'].value != null || theForm.elements['id_bookmark'].value != '') &&
1580            theForm.elements['id_bookmark'].selectedIndex != 0
1581            ) {
1582        return true;
1583    }
1584    // Checks for "DROP/DELETE/ALTER" statements
1585    if (sqlQuery.value.replace(space_re, '') != '') {
1586        if (confirmQuery(theForm, sqlQuery)) {
1587            return true;
1588        } else {
1589            return false;
1590        }
1591    }
1592    theForm.reset();
1593    isEmpty = 1;
1594
1595    if (isEmpty) {
1596        sqlQuery.select();
1597        alert(PMA_messages['strFormEmpty']);
1598        sqlQuery.focus();
1599        return false;
1600    }
1601
1602    return true;
1603} // end of the 'checkSqlQuery()' function
1604
1605/**
1606 * Check if a form's element is empty.
1607 * An element containing only spaces is also considered empty
1608 *
1609 * @param object   the form
1610 * @param string   the name of the form field to put the focus on
1611 *
1612 * @return boolean  whether the form field is empty or not
1613 */
1614function emptyCheckTheField(theForm, theFieldName)
1615{
1616    var theField = theForm.elements[theFieldName];
1617    var space_re = new RegExp('\\s+');
1618    return (theField.value.replace(space_re, '') == '') ? 1 : 0;
1619} // end of the 'emptyCheckTheField()' function
1620
1621
1622/**
1623 * Check whether a form field is empty or not
1624 *
1625 * @param object   the form
1626 * @param string   the name of the form field to put the focus on
1627 *
1628 * @return boolean  whether the form field is empty or not
1629 */
1630function emptyFormElements(theForm, theFieldName)
1631{
1632    var theField = theForm.elements[theFieldName];
1633    var isEmpty = emptyCheckTheField(theForm, theFieldName);
1634
1635
1636    return isEmpty;
1637} // end of the 'emptyFormElements()' function
1638
1639
1640/**
1641 * Ensures a value submitted in a form is numeric and is in a range
1642 *
1643 * @param object   the form
1644 * @param string   the name of the form field to check
1645 * @param integer  the minimum authorized value
1646 * @param integer  the maximum authorized value
1647 *
1648 * @return boolean  whether a valid number has been submitted or not
1649 */
1650function checkFormElementInRange(theForm, theFieldName, message, min, max)
1651{
1652    var theField         = theForm.elements[theFieldName];
1653    var val              = parseInt(theField.value);
1654
1655    if (typeof(min) == 'undefined') {
1656        min = 0;
1657    }
1658    if (typeof(max) == 'undefined') {
1659        max = Number.MAX_VALUE;
1660    }
1661
1662    // It's not a number
1663    if (isNaN(val)) {
1664        theField.select();
1665        alert(PMA_messages['strNotNumber']);
1666        theField.focus();
1667        return false;
1668    }
1669    // It's a number but it is not between min and max
1670    else if (val < min || val > max) {
1671        theField.select();
1672        alert($.sprintf(message, val));
1673        theField.focus();
1674        return false;
1675    }
1676    // It's a valid number
1677    else {
1678        theField.value = val;
1679    }
1680    return true;
1681
1682} // end of the 'checkFormElementInRange()' function
1683
1684
1685function checkTableEditForm(theForm, fieldsCnt)
1686{
1687    // TODO: avoid sending a message if user just wants to add a line
1688    // on the form but has not completed at least one field name
1689
1690    var atLeastOneField = 0;
1691    var i, elm, elm2, elm3, val, id;
1692
1693    for (i=0; i<fieldsCnt; i++)
1694    {
1695        id = "#field_" + i + "_2";
1696        elm = $(id);
1697        val = elm.val();
1698        if (val == 'VARCHAR' || val == 'CHAR' || val == 'BIT' || val == 'VARBINARY' || val == 'BINARY') {
1699            elm2 = $("#field_" + i + "_3");
1700            val = parseInt(elm2.val());
1701            elm3 = $("#field_" + i + "_1");
1702            if (isNaN(val) && elm3.val() != "") {
1703                elm2.select();
1704                alert(PMA_messages['strNotNumber']);
1705                elm2.focus();
1706                return false;
1707            }
1708        }
1709
1710        if (atLeastOneField == 0) {
1711            id = "field_" + i + "_1";
1712            if (!emptyCheckTheField(theForm, id)) {
1713                atLeastOneField = 1;
1714            }
1715        }
1716    }
1717    if (atLeastOneField == 0) {
1718        var theField = theForm.elements["field_0_1"];
1719        alert(PMA_messages['strFormEmpty']);
1720        theField.focus();
1721        return false;
1722    }
1723
1724    // at least this section is under jQuery
1725    if ($("input.textfield[name='table']").val() == "") {
1726        alert(PMA_messages['strFormEmpty']);
1727        $("input.textfield[name='table']").focus();
1728        return false;
1729    }
1730
1731
1732    return true;
1733} // enf of the 'checkTableEditForm()' function
1734
1735/**
1736 * Unbind all event handlers before tearing down a page
1737 */
1738AJAX.registerTeardown('functions.js', function() {
1739    $('input:checkbox.checkall').die('click');
1740});
1741AJAX.registerOnload('functions.js', function() {
1742    /**
1743     * Row marking in horizontal mode (use "live" so that it works also for
1744     * next pages reached via AJAX); a tr may have the class noclick to remove
1745     * this behavior.
1746     */
1747
1748    $('input:checkbox.checkall').live('click', function(e) {
1749        var $tr = $(this).closest('tr');
1750
1751        // make the table unselectable (to prevent default highlighting when shift+click)
1752        //$tr.parents('table').noSelect();
1753
1754        if (!e.shiftKey || last_clicked_row == -1) {
1755            // usual click
1756
1757            // XXX: FF fires two click events for <label> (label and checkbox), so we need to handle this differently
1758            var $checkbox = $tr.find(':checkbox');
1759            if ($checkbox.length) {
1760                // checkbox in a row, add or remove class depending on checkbox state
1761                var checked = $checkbox.prop('checked');
1762                if (!$(e.target).is(':checkbox, label')) {
1763                    checked = !checked;
1764                    $checkbox.prop('checked', checked).trigger('change');
1765                }
1766                if (checked) {
1767                    $tr.addClass('marked');
1768                } else {
1769                    $tr.removeClass('marked');
1770                }
1771                last_click_checked = checked;
1772            } else {
1773                // normal data table, just toggle class
1774                $tr.toggleClass('marked');
1775                last_click_checked = false;
1776            }
1777
1778            // remember the last clicked row
1779            last_clicked_row = last_click_checked ? $('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr) : -1;
1780            last_shift_clicked_row = -1;
1781        } else {
1782            // handle the shift click
1783            PMA_clearSelection();
1784            var start, end;
1785
1786            // clear last shift click result
1787            if (last_shift_clicked_row >= 0) {
1788                if (last_shift_clicked_row >= last_clicked_row) {
1789                    start = last_clicked_row;
1790                    end = last_shift_clicked_row;
1791                } else {
1792                    start = last_shift_clicked_row;
1793                    end = last_clicked_row;
1794                }
1795                $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
1796                    .slice(start, end + 1)
1797                    .removeClass('marked')
1798                    .find(':checkbox')
1799                    .prop('checked', false)
1800                    .trigger('change');
1801            }
1802
1803            // handle new shift click
1804            var curr_row = $('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr);
1805            if (curr_row >= last_clicked_row) {
1806                start = last_clicked_row;
1807                end = curr_row;
1808            } else {
1809                start = curr_row;
1810                end = last_clicked_row;
1811            }
1812            $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
1813                .slice(start, end + 1)
1814                .addClass('marked')
1815                .find(':checkbox')
1816                .prop('checked', true)
1817                .trigger('change');
1818
1819            // remember the last shift clicked row
1820            last_shift_clicked_row = curr_row;
1821        }
1822    });
1823
1824    addDateTimePicker();
1825
1826    /**
1827     * Add attribute to text boxes for iOS devices (based on bugID: 3508912)
1828     */
1829    if (navigator.userAgent.match(/(iphone|ipod|ipad)/i)) {
1830        $('input[type=text]').attr('autocapitalize','off').attr('autocorrect','off');
1831    }
1832});
1833
1834/**
1835 * True if last click is to check a row.
1836 */
1837var last_click_checked = false;
1838
1839/**
1840 * Zero-based index of last clicked row.
1841 * Used to handle the shift + click event in the code above.
1842 */
1843var last_clicked_row = -1;
1844
1845/**
1846 * Zero-based index of last shift clicked row.
1847 */
1848var last_shift_clicked_row = -1;
1849
1850/**
1851 * Row highlighting in horizontal mode (use "live"
1852 * so that it works also for pages reached via AJAX)
1853 */
1854/*AJAX.registerOnload('functions.js', function() {
1855    $('tr.odd, tr.even').live('hover',function(event) {
1856        var $tr = $(this);
1857        $tr.toggleClass('hover',event.type=='mouseover');
1858        $tr.children().toggleClass('hover',event.type=='mouseover');
1859    });
1860})*/
1861
1862/**
1863 * This array is used to remember mark status of rows in browse mode
1864 */
1865var marked_row = [];
1866
1867/**
1868 * marks all rows and selects its first checkbox inside the given element
1869 * the given element is usaly a table or a div containing the table or tables
1870 *
1871 * @param container    DOM element
1872 */
1873function markAllRows(container_id)
1874{
1875
1876    $("#" + container_id).find("input:checkbox:enabled").prop('checked', true)
1877    .trigger("change")
1878    .parents("tr").addClass("marked");
1879    return true;
1880}
1881
1882/**
1883 * marks all rows and selects its first checkbox inside the given element
1884 * the given element is usaly a table or a div containing the table or tables
1885 *
1886 * @param container    DOM element
1887 */
1888function unMarkAllRows(container_id)
1889{
1890
1891    $("#" + container_id).find("input:checkbox:enabled").prop('checked', false)
1892    .trigger("change")
1893    .parents("tr").removeClass("marked");
1894    return true;
1895}
1896
1897/**
1898 * Checks/unchecks all checkbox in given conainer (f.e. a form, fieldset or div)
1899 *
1900 * @param string   container_id  the container id
1901 * @param boolean  state         new value for checkbox (true or false)
1902 * @return boolean  always true
1903 */
1904function setCheckboxes(container_id, state)
1905{
1906
1907    $("#" + container_id).find("input:checkbox").prop('checked', state);
1908    return true;
1909} // end of the 'setCheckboxes()' function
1910
1911/**
1912  * Checks/unchecks all options of a <select> element
1913  *
1914  * @param string   the form name
1915  * @param string   the element name
1916  * @param boolean  whether to check or to uncheck options
1917  *
1918  * @return boolean  always true
1919  */
1920function setSelectOptions(the_form, the_select, do_check)
1921{
1922    $("form[name='"+ the_form +"'] select[name='"+the_select+"']").find("option").prop('selected', do_check);
1923    return true;
1924} // end of the 'setSelectOptions()' function
1925
1926/**
1927 * Sets current value for query box.
1928 */
1929function setQuery(query)
1930{
1931    if (codemirror_editor) {
1932        codemirror_editor.setValue(query);
1933        codemirror_editor.focus();
1934    } else {
1935        document.sqlform.sql_query.value = query;
1936        document.sqlform.sql_query.focus();
1937    }
1938}
1939
1940
1941/**
1942  * Create quick sql statements.
1943  *
1944  */
1945function insertQuery(queryType)
1946{
1947    if (queryType == "clear") {
1948        setQuery('');
1949        return;
1950    }
1951
1952    var query = "";
1953    var myListBox = document.sqlform.dummy;
1954    var table = document.sqlform.table.value;
1955
1956    if (myListBox.options.length > 0) {
1957        sql_box_locked = true;
1958        var chaineAj = "";
1959        var valDis = "";
1960        var editDis = "";
1961        var NbSelect = 0;
1962        for (var i=0; i < myListBox.options.length; i++) {
1963            NbSelect++;
1964            if (NbSelect > 1) {
1965                chaineAj += ", ";
1966                valDis += ",";
1967                editDis += ",";
1968            }
1969            chaineAj += myListBox.options[i].value;
1970            valDis += "[value-" + NbSelect + "]";
1971            editDis += myListBox.options[i].value + "=[value-" + NbSelect + "]";
1972        }
1973        if (queryType == "selectall") {
1974            query = "SELECT * FROM `" + table + "` WHERE 1";
1975        } else if (queryType == "select") {
1976            query = "SELECT " + chaineAj + " FROM `" + table + "` WHERE 1";
1977        } else if (queryType == "insert") {
1978               query = "INSERT INTO `" + table + "`(" + chaineAj + ") VALUES (" + valDis + ")";
1979        } else if (queryType == "update") {
1980            query = "UPDATE `" + table + "` SET " + editDis + " WHERE 1";
1981        } else if (queryType == "delete") {
1982            query = "DELETE FROM `" + table + "` WHERE 1";
1983        }
1984        setQuery(query);
1985        sql_box_locked = false;
1986    }
1987}
1988
1989
1990/**
1991  * Inserts multiple fields.
1992  *
1993  */
1994function insertValueQuery()
1995{
1996    var myQuery = document.sqlform.sql_query;
1997    var myListBox = document.sqlform.dummy;
1998
1999    if (myListBox.options.length > 0) {
2000        sql_box_locked = true;
2001        var chaineAj = "";
2002        var NbSelect = 0;
2003        for (var i=0; i<myListBox.options.length; i++) {
2004            if (myListBox.options[i].selected) {
2005                NbSelect++;
2006                if (NbSelect > 1) {
2007                    chaineAj += ", ";
2008                }
2009                chaineAj += myListBox.options[i].value;
2010            }
2011        }
2012
2013        /* CodeMirror support */
2014        if (codemirror_editor) {
2015            codemirror_editor.replaceSelection(chaineAj);
2016        //IE support
2017        } else if (document.selection) {
2018            myQuery.focus();
2019            var sel = document.selection.createRange();
2020            sel.text = chaineAj;
2021            document.sqlform.insert.focus();
2022        }
2023        //MOZILLA/NETSCAPE support
2024        else if (document.sqlform.sql_query.selectionStart || document.sqlform.sql_query.selectionStart == "0") {
2025            var startPos = document.sqlform.sql_query.selectionStart;
2026            var endPos = document.sqlform.sql_query.selectionEnd;
2027            var chaineSql = document.sqlform.sql_query.value;
2028
2029            myQuery.value = chaineSql.substring(0, startPos) + chaineAj + chaineSql.substring(endPos, chaineSql.length);
2030        } else {
2031            myQuery.value += chaineAj;
2032        }
2033        sql_box_locked = false;
2034    }
2035}
2036
2037/**
2038 * Add a date/time picker to each element that needs it
2039 * (only when jquery-ui-timepicker-addon.js is loaded)
2040 */
2041function addDateTimePicker() {
2042    if ($.timepicker != undefined) {
2043        $('input.datefield, input.datetimefield').each(function() {
2044            PMA_addDatepicker($(this));
2045        });
2046    }
2047}
2048
2049/**
2050  * Refresh/resize the WYSIWYG scratchboard
2051  */
2052function refreshLayout()
2053{
2054    var $elm = $('#pdflayout');
2055    var orientation = $('#orientation_opt').val();
2056    if ($('#paper_opt').length==1) {
2057        var paper = $('#paper_opt').val();
2058    }else{
2059        var paper = 'A4';
2060    }
2061    if (orientation == 'P') {
2062        var posa = 'x';
2063        var posb = 'y';
2064    } else {
2065        var posa = 'y';
2066        var posb = 'x';
2067    }
2068    $elm.css('width', pdfPaperSize(paper, posa) + 'px');
2069    $elm.css('height', pdfPaperSize(paper, posb) + 'px');
2070}
2071
2072/**
2073 * Initializes positions of elements.
2074 */
2075function TableDragInit() {
2076    $('.pdflayout_table').each(function () {
2077        var $this = $(this);
2078        var number = $this.data('number');
2079        var x = $('#c_table_' + number + '_x').val();
2080        var y = $('#c_table_' + number + '_y').val();
2081        $this.css('left', x + 'px');
2082        $this.css('top', y + 'px');
2083        /* Make elements draggable */
2084        $this.draggable({
2085            containment: "parent",
2086            drag: function (evt, ui) {
2087                var number = $this.data('number');
2088                $('#c_table_' + number + '_x').val(parseInt(ui.position.left));
2089                $('#c_table_' + number + '_y').val(parseInt(ui.position.top));
2090            }
2091        });
2092    });
2093}
2094
2095/**
2096 * Resets drag and drop positions.
2097 */
2098function resetDrag() {
2099    $('.pdflayout_table').each(function () {
2100        var $this = $(this);
2101        var x = $this.data('x');
2102        var y = $this.data('y');
2103        $this.css('left', x + 'px');
2104        $this.css('top', y + 'px');
2105    });
2106}
2107
2108/**
2109 * User schema handlers.
2110 */
2111$(function () {
2112    /* Move in scratchboard on manual change */
2113    $('.position-change').live('change', function () {
2114        var $this = $(this);
2115        var $elm = $('#table_' + $this.data('number'));
2116        $elm.css($this.data('axis'), $this.val() + 'px');
2117    });
2118    /* Refresh on paper size/orientation change */
2119    $('.paper-change').live('change', function () {
2120        var $elm = $('#pdflayout');
2121        if ($elm.css('visibility') == 'visible') {
2122            refreshLayout();
2123            TableDragInit();
2124        }
2125    });
2126    /* Show/hide the WYSIWYG scratchboard */
2127    $('#toggle-dragdrop').live('click', function () {
2128        var $elm = $('#pdflayout');
2129        if ($elm.css('visibility') == 'hidden') {
2130            refreshLayout();
2131            TableDragInit();
2132            $elm.css('visibility', 'visible');
2133            $elm.css('display', 'block');
2134            $('#showwysiwyg').val('1');
2135        } else {
2136            $elm.css('visibility', 'hidden');
2137            $elm.css('display', 'none');
2138            $('#showwysiwyg').val('0');
2139        }
2140    });
2141    /* Reset scratchboard */
2142    $('#reset-dragdrop').live('click', function () {
2143        resetDrag();
2144    });
2145});
2146
2147/**
2148 * Returns paper sizes for a given format
2149 */
2150function pdfPaperSize(format, axis)
2151{
2152    switch (format.toUpperCase()) {
2153        case '4A0':
2154            if (axis == 'x') {
2155                return 4767.87;
2156            } else {
2157                return 6740.79;
2158            }
2159            break;
2160        case '2A0':
2161            if (axis == 'x') {
2162                return 3370.39;
2163            } else {
2164                return 4767.87;
2165            }
2166            break;
2167        case 'A0':
2168            if (axis == 'x') {
2169                return 2383.94;
2170            } else {
2171                return 3370.39;
2172            }
2173            break;
2174        case 'A1':
2175            if (axis == 'x') {
2176                return 1683.78;
2177            } else {
2178                return 2383.94;
2179            }
2180            break;
2181        case 'A2':
2182            if (axis == 'x') {
2183                return 1190.55;
2184            } else {
2185                return 1683.78;
2186            }
2187            break;
2188        case 'A3':
2189            if (axis == 'x') {
2190                return 841.89;
2191            } else {
2192                return 1190.55;
2193            }
2194            break;
2195        case 'A4':
2196            if (axis == 'x') {
2197                return 595.28;
2198            } else {
2199                return 841.89;
2200            }
2201            break;
2202        case 'A5':
2203            if (axis == 'x') {
2204                return 419.53;
2205            } else {
2206                return 595.28;
2207            }
2208            break;
2209        case 'A6':
2210            if (axis == 'x') {
2211                return 297.64;
2212            } else {
2213                return 419.53;
2214            }
2215            break;
2216        case 'A7':
2217            if (axis == 'x') {
2218                return 209.76;
2219            } else {
2220                return 297.64;
2221            }
2222            break;
2223        case 'A8':
2224            if (axis == 'x') {
2225                return 147.40;
2226            } else {
2227                return 209.76;
2228            }
2229            break;
2230        case 'A9':
2231            if (axis == 'x') {
2232                return 104.88;
2233            } else {
2234                return 147.40;
2235            }
2236            break;
2237        case 'A10':
2238            if (axis == 'x') {
2239                return 73.70;
2240            } else {
2241                return 104.88;
2242            }
2243            break;
2244        case 'B0':
2245            if (axis == 'x') {
2246                return 2834.65;
2247            } else {
2248                return 4008.19;
2249            }
2250            break;
2251        case 'B1':
2252            if (axis == 'x') {
2253                return 2004.09;
2254            } else {
2255                return 2834.65;
2256            }
2257            break;
2258        case 'B2':
2259            if (axis == 'x') {
2260                return 1417.32;
2261            } else {
2262                return 2004.09;
2263            }
2264            break;
2265        case 'B3':
2266            if (axis == 'x') {
2267                return 1000.63;
2268            } else {
2269                return 1417.32;
2270            }
2271            break;
2272        case 'B4':
2273            if (axis == 'x') {
2274                return 708.66;
2275            } else {
2276                return 1000.63;
2277            }
2278            break;
2279        case 'B5':
2280            if (axis == 'x') {
2281                return 498.90;
2282            } else {
2283                return 708.66;
2284            }
2285            break;
2286        case 'B6':
2287            if (axis == 'x') {
2288                return 354.33;
2289            } else {
2290                return 498.90;
2291            }
2292            break;
2293        case 'B7':
2294            if (axis == 'x') {
2295                return 249.45;
2296            } else {
2297                return 354.33;
2298            }
2299            break;
2300        case 'B8':
2301            if (axis == 'x') {
2302                return 175.75;
2303            } else {
2304                return 249.45;
2305            }
2306            break;
2307        case 'B9':
2308            if (axis == 'x') {
2309                return 124.72;
2310            } else {
2311                return 175.75;
2312            }
2313            break;
2314        case 'B10':
2315            if (axis == 'x') {
2316                return 87.87;
2317            } else {
2318                return 124.72;
2319            }
2320            break;
2321        case 'C0':
2322            if (axis == 'x') {
2323                return 2599.37;
2324            } else {
2325                return 3676.54;
2326            }
2327            break;
2328        case 'C1':
2329            if (axis == 'x') {
2330                return 1836.85;
2331            } else {
2332                return 2599.37;
2333            }
2334            break;
2335        case 'C2':
2336            if (axis == 'x') {
2337                return 1298.27;
2338            } else {
2339                return 1836.85;
2340            }
2341            break;
2342        case 'C3':
2343            if (axis == 'x') {
2344                return 918.43;
2345            } else {
2346                return 1298.27;
2347            }
2348            break;
2349        case 'C4':
2350            if (axis == 'x') {
2351                return 649.13;
2352            } else {
2353                return 918.43;
2354            }
2355            break;
2356        case 'C5':
2357            if (axis == 'x') {
2358                return 459.21;
2359            } else {
2360                return 649.13;
2361            }
2362            break;
2363        case 'C6':
2364            if (axis == 'x') {
2365                return 323.15;
2366            } else {
2367                return 459.21;
2368            }
2369            break;
2370        case 'C7':
2371            if (axis == 'x') {
2372                return 229.61;
2373            } else {
2374                return 323.15;
2375            }
2376            break;
2377        case 'C8':
2378            if (axis == 'x') {
2379                return 161.57;
2380            } else {
2381                return 229.61;
2382            }
2383            break;
2384        case 'C9':
2385            if (axis == 'x') {
2386                return 113.39;
2387            } else {
2388                return 161.57;
2389            }
2390            break;
2391        case 'C10':
2392            if (axis == 'x') {
2393                return 79.37;
2394            } else {
2395                return 113.39;
2396            }
2397            break;
2398        case 'RA0':
2399            if (axis == 'x') {
2400                return 2437.80;
2401            } else {
2402                return 3458.27;
2403            }
2404            break;
2405        case 'RA1':
2406            if (axis == 'x') {
2407                return 1729.13;
2408            } else {
2409                return 2437.80;
2410            }
2411            break;
2412        case 'RA2':
2413            if (axis == 'x') {
2414                return 1218.90;
2415            } else {
2416                return 1729.13;
2417            }
2418            break;
2419        case 'RA3':
2420            if (axis == 'x') {
2421                return 864.57;
2422            } else {
2423                return 1218.90;
2424            }
2425            break;
2426        case 'RA4':
2427            if (axis == 'x') {
2428                return 609.45;
2429            } else {
2430                return 864.57;
2431            }
2432            break;
2433        case 'SRA0':
2434            if (axis == 'x') {
2435                return 2551.18;
2436            } else {
2437                return 3628.35;
2438            }
2439            break;
2440        case 'SRA1':
2441            if (axis == 'x') {
2442                return 1814.17;
2443            } else {
2444                return 2551.18;
2445            }
2446            break;
2447        case 'SRA2':
2448            if (axis == 'x') {
2449                return 1275.59;
2450            } else {
2451                return 1814.17;
2452            }
2453            break;
2454        case 'SRA3':
2455            if (axis == 'x') {
2456                return 907.09;
2457            } else {
2458                return 1275.59;
2459            }
2460            break;
2461        case 'SRA4':
2462            if (axis == 'x') {
2463                return 637.80;
2464            } else {
2465                return 907.09;
2466            }
2467            break;
2468        case 'LETTER':
2469            if (axis == 'x') {
2470                return 612.00;
2471            } else {
2472                return 792.00;
2473            }
2474            break;
2475        case 'LEGAL':
2476            if (axis == 'x') {
2477                return 612.00;
2478            } else {
2479                return 1008.00;
2480            }
2481            break;
2482        case 'EXECUTIVE':
2483            if (axis == 'x') {
2484                return 521.86;
2485            } else {
2486                return 756.00;
2487            }
2488            break;
2489        case 'FOLIO':
2490            if (axis == 'x') {
2491                return 612.00;
2492            } else {
2493                return 936.00;
2494            }
2495            break;
2496    } // end switch
2497
2498    return 0;
2499}
2500
2501/**
2502 * Unbind all event handlers before tearing down a page
2503 */
2504AJAX.registerTeardown('functions.js', function() {
2505    $("a.inline_edit_sql").die('click');
2506    $("input#sql_query_edit_save").die('click');
2507    $("input#sql_query_edit_discard").die('click');
2508    $('input.sqlbutton').unbind('click');
2509    $("#export_type").unbind('change');
2510    $('#sqlquery').unbind('keydown');
2511    $('#sql_query_edit').unbind('keydown');
2512
2513    if (codemirror_inline_editor) {
2514        // Copy the sql query to the text area to preserve it.
2515        $('#sql_query_edit').text(codemirror_inline_editor.getValue());
2516        $(codemirror_inline_editor.getWrapperElement()).unbind('keydown');
2517        codemirror_inline_editor.toTextArea();
2518        codemirror_inline_editor = false;
2519    }
2520    if (codemirror_editor) {
2521        $(codemirror_editor.getWrapperElement()).unbind('keydown');
2522    }
2523});
2524
2525/**
2526 * Jquery Coding for inline editing SQL_QUERY
2527 */
2528AJAX.registerOnload('functions.js', function() {
2529    // If we are coming back to the page by clicking forward button
2530    // of the browser, bind the code mirror to inline query editor.
2531    bindCodeMirrorToInlineEditor();
2532    $("a.inline_edit_sql").live('click', function() {
2533        if ($('#sql_query_edit').length) {
2534            // An inline query editor is already open,
2535            // we don't want another copy of it
2536            return false;
2537        }
2538
2539        var $form = $(this).prev('form');
2540        var sql_query  = $form.find("input[name='sql_query']").val();
2541        var $inner_sql = $(this).parent().prev().find('.inner_sql');
2542        var old_text   = $inner_sql.html();
2543
2544        var new_content = "<textarea name=\"sql_query_edit\" id=\"sql_query_edit\">" + escapeHtml(sql_query) + "</textarea>\n";
2545        new_content    += "<input type=\"submit\" id=\"sql_query_edit_save\" class=\"button btnSave\" value=\"" + PMA_messages['strGo'] + "\"/>\n";
2546        new_content    += "<input type=\"button\" id=\"sql_query_edit_discard\" class=\"button btnDiscard\" value=\"" + PMA_messages['strCancel'] + "\"/>\n";
2547        var $editor_area = $('div#inline_editor');
2548        if ($editor_area.length == 0) {
2549            $editor_area = $('<div id="inline_editor_outer"></div>');
2550            $editor_area.insertBefore($inner_sql);
2551        }
2552        $editor_area.html(new_content);
2553        $inner_sql.hide();
2554
2555        bindCodeMirrorToInlineEditor();
2556        return false;
2557    });
2558
2559    $("input#sql_query_edit_save").live('click', function() {
2560        if (codemirror_inline_editor) {
2561            var sql_query = codemirror_inline_editor.getValue();
2562        } else {
2563            var sql_query = $(this).prev().val();
2564        }
2565
2566        var $form = $("a.inline_edit_sql").prev('form');
2567        var $fake_form = $('<form>', {action: 'import.php', method: 'post'})
2568                .append($form.find("input[name=server], input[name=db], input[name=table], input[name=token]").clone())
2569                .append($('<input/>', {type: 'hidden', name: 'show_query', value: 1}))
2570                .append($('<input/>', {type: 'hidden', name: 'is_js_confirmed', value: 0}))
2571                .append($('<input/>', {type: 'hidden', name: 'sql_query', value: sql_query}));
2572        if (! checkSqlQuery($fake_form[0])) {
2573            return false;
2574        }
2575        $fake_form.appendTo($('body')).submit();
2576    });
2577
2578    $("input#sql_query_edit_discard").live('click', function() {
2579        $('div#inline_editor_outer')
2580            .empty()
2581            .siblings('.inner_sql').show();
2582    });
2583
2584    $('input.sqlbutton').click(function(evt) {
2585        insertQuery(evt.target.id);
2586        return false;
2587    });
2588
2589    $("#export_type").change(function() {
2590        if ($("#export_type").val()=='svg') {
2591            $("#show_grid_opt").prop("disabled",true);
2592            $("#orientation_opt").prop("disabled",true);
2593            $("#with_doc").prop("disabled",true);
2594            $("#show_table_dim_opt").removeProp("disabled");
2595            $("#all_tables_same_width").removeProp("disabled");
2596            $("#paper_opt").removeProp("disabled");
2597            $("#show_color_opt").removeProp("disabled");
2598            //$(this).css("background-color","yellow");
2599        } else if ($("#export_type").val()=='dia') {
2600            $("#show_grid_opt").prop("disabled",true);
2601            $("#with_doc").prop("disabled",true);
2602            $("#show_table_dim_opt").prop("disabled",true);
2603            $("#all_tables_same_width").prop("disabled",true);
2604            $("#paper_opt").removeProp("disabled");
2605            $("#show_color_opt").removeProp("disabled");
2606            $("#orientation_opt").removeProp("disabled");
2607        } else if ($("#export_type").val()=='eps') {
2608            $("#show_grid_opt").prop("disabled",true);
2609            $("#orientation_opt").removeProp("disabled");
2610            $("#with_doc").prop("disabled",true);
2611            $("#show_table_dim_opt").prop("disabled",true);
2612            $("#all_tables_same_width").prop("disabled",true);
2613            $("#paper_opt").prop("disabled",true);
2614            $("#show_color_opt").prop("disabled",true);
2615        } else if ($("#export_type").val()=='pdf') {
2616            $("#show_grid_opt").removeProp("disabled");
2617            $("#orientation_opt").removeProp("disabled");
2618            $("#with_doc").removeProp("disabled");
2619            $("#show_table_dim_opt").removeProp("disabled");
2620            $("#all_tables_same_width").removeProp("disabled");
2621            $("#paper_opt").removeProp("disabled");
2622            $("#show_color_opt").removeProp("disabled");
2623        } else {
2624            // nothing
2625        }
2626    });
2627
2628    if ($('#input_username')) {
2629        if ($('#input_username').val() == '') {
2630            $('#input_username').focus();
2631        } else {
2632            $('#input_password').focus();
2633        }
2634    }
2635});
2636
2637/**
2638 * Binds the CodeMirror to the text area used to inline edit a query.
2639 */
2640function bindCodeMirrorToInlineEditor() {
2641    var $inline_editor = $('#sql_query_edit');
2642    if ($inline_editor.length > 0) {
2643        if (typeof CodeMirror !== 'undefined') {
2644            var height = $('#sql_query_edit').css('height');
2645            codemirror_inline_editor = CodeMirror.fromTextArea($inline_editor[0], {
2646                lineNumbers: true,
2647                matchBrackets: true,
2648                indentUnit: 4,
2649                mode: "text/x-mysql",
2650                lineWrapping: true
2651            });
2652            codemirror_inline_editor.getScrollerElement().style.height = height;
2653            codemirror_inline_editor.refresh();
2654            codemirror_inline_editor.focus();
2655            $(codemirror_inline_editor.getWrapperElement()).bind(
2656                'keydown',
2657                catchKeypressesFromSqlTextboxes
2658            );
2659        } else {
2660            $inline_editor.focus().bind(
2661                'keydown',
2662                catchKeypressesFromSqlTextboxes
2663            );
2664        }
2665    }
2666}
2667
2668function catchKeypressesFromSqlTextboxes(event) {
2669    // ctrl-enter is 10 in chrome and ie, but 13 in ff
2670    if (event.ctrlKey && (event.keyCode == 13 || event.keyCode == 10)) {
2671        if ($('#sql_query_edit').length > 0) {
2672            $("#sql_query_edit_save").trigger('click');
2673        } else if ($('#sqlquery').length > 0) {
2674            $("#button_submit_query").trigger('click');
2675        }
2676    }
2677}
2678
2679/**
2680 * Show a message on the top of the page for an Ajax request
2681 *
2682 * Sample usage:
2683 *
2684 * 1) var $msg = PMA_ajaxShowMessage();
2685 * This will show a message that reads "Loading...". Such a message will not
2686 * disappear automatically and cannot be dismissed by the user. To remove this
2687 * message either the PMA_ajaxRemoveMessage($msg) function must be called or
2688 * another message must be show with PMA_ajaxShowMessage() function.
2689 *
2690 * 2) var $msg = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']);
2691 * This is a special case. The behaviour is same as above,
2692 * just with a different message
2693 *
2694 * 3) var $msg = PMA_ajaxShowMessage('The operation was successful');
2695 * This will show a message that will disappear automatically and it can also
2696 * be dismissed by the user.
2697 *
2698 * 4) var $msg = PMA_ajaxShowMessage('Some error', false);
2699 * This will show a message that will not disappear automatically, but it
2700 * can be dismissed by the user after he has finished reading it.
2701 *
2702 * @param string  message     string containing the message to be shown.
2703 *                              optional, defaults to 'Loading...'
2704 * @param mixed   timeout     number of milliseconds for the message to be visible
2705 *                              optional, defaults to 5000. If set to 'false', the
2706 *                              notification will never disappear
2707 * @return jQuery object       jQuery Element that holds the message div
2708 *                              this object can be passed to PMA_aja
2708xRemoveMessage()
2709 *                              to remove the notification
2710 */
2711function PMA_ajaxShowMessage(message, timeout)
2712{
2713    /**
2714     * @var self_closing Whether the notification will automatically disappear
2715     */
2716    var self_closing = true;
2717    /**
2718     * @var dismissable Whether the user will be able to remove
2719     *                  the notification by clicking on it
2720     */
2721    var dismissable = true;
2722    // Handle the case when a empty data.message is passed.
2723    // We don't want the empty message
2724    if (message == '') {
2725        return true;
2726    } else if (! message) {
2727        // If the message is undefined, show the default
2728        message = PMA_messages['strLoading'];
2729        dismissable = false;
2730        self_closing = false;
2731    } else if (message == PMA_messages['strProcessingRequest']) {
2732        // This is another case where the message should not disappear
2733        dismissable = false;
2734        self_closing = false;
2735    }
2736    // Figure out whether (or after how long) to remove the notification
2737    if (timeout == undefined) {
2738        timeout = 5000;
2739    } else if (timeout === false) {
2740        self_closing = false;
2741    }
2742    // Create a parent element for the AJAX messages, if necessary
2743    if ($('#loading_parent').length == 0) {
2744        $('<div id="loading_parent"></div>')
2745        .prependTo("body");
2746    }
2747    // Update message count to create distinct message elements every time
2748    ajax_message_count++;
2749    // Remove all old messages, if any
2750    $("span.ajax_notification[id^=ajax_message_num]").remove();
2751    /**
2752     * @var    $retval    a jQuery object containing the reference
2753     *                    to the created AJAX message
2754     */
2755    var $retval = $(
2756            '<span class="ajax_notification" id="ajax_message_num_'
2757            + ajax_message_count +
2758            '"></span>'
2759    )
2760    .hide()
2761    .appendTo("#loading_parent")
2762    .html(message)
2763    .show();
2764    // If the notification is self-closing we should create a callback to remove it
2765    if (self_closing) {
2766        $retval
2767        .delay(timeout)
2768        .fadeOut('medium', function() {
2769            if ($(this).is('.dismissable')) {
2770                $(this).tooltip('destroy');
2771            }
2772            // Remove the notification
2773            $(this).remove();
2774        });
2775    }
2776    // If the notification is dismissable we need to add the relevant class to it
2777    // and add a tooltip so that the users know that it can be removed
2778    if (dismissable) {
2779        $retval.addClass('dismissable').css('cursor', 'pointer');
2780        /**
2781         * Add a tooltip to the notification to let the user know that (s)he
2782         * can dismiss the ajax notification by clicking on it.
2783         */
2784        PMA_tooltip(
2785            $retval,
2786            'span',
2787            PMA_messages['strDismiss']
2788        );
2789    }
2790
2791    return $retval;
2792}
2793
2794/**
2795 * Removes the message shown for an Ajax operation when it's completed
2796 *
2797 * @param jQuery object   jQuery Element that holds the notification
2798 *
2799 * @return nothing
2800 */
2801function PMA_ajaxRemoveMessage($this_msgbox)
2802{
2803    if ($this_msgbox != undefined && $this_msgbox instanceof jQuery) {
2804        $this_msgbox
2805        .stop(true, true)
2806        .fadeOut('medium');
2807        if ($this_msgbox.is('.dismissable')) {
2808            if ($.isFunction($this_msgbox.tooltip)) {
2809                $this_msgbox.tooltip('destroy');
2810            }
2811        } else {
2812            $this_msgbox.remove();
2813        }
2814    }
2815}
2816
2817// This event only need to be fired once after the initial page load
2818$(function() {
2819    /**
2820     * Allows the user to dismiss a notification
2821     * created with PMA_ajaxShowMessage()
2822     */
2823    $('span.ajax_notification.dismissable').live('click', function () {
2824        PMA_ajaxRemoveMessage($(this));
2825    });
2826    /**
2827     * The below two functions hide the "Dismiss notification" tooltip when a user
2828     * is hovering a link or button that is inside an ajax message
2829     */
2830    $('span.ajax_notification a, span.ajax_notification button, span.ajax_notification input')
2831    .live('mouseover', function () {
2832        $(this).parents('span.ajax_notification').tooltip('disable');
2833    })
2834    .live('mouseout', function () {
2835        $(this).parents('span.ajax_notification').tooltip('enable');
2836    });
2837});
2838
2839/**
2840 * Hides/shows the "Open in ENUM/SET editor" message, depending on the data type of the column currently selected
2841 */
2842function PMA_showNoticeForEnum(selectElement)
2843{
2844    var enum_notice_id = selectElement.attr("id").split("_")[1];
2845    enum_notice_id += "_" + (parseInt(selectElement.attr("id").split("_")[2]) + 1);
2846    var selectedType = selectElement.val();
2847    if (selectedType == "ENUM" || selectedType == "SET") {
2848        $("p#enum_notice_" + enum_notice_id).show();
2849    } else {
2850        $("p#enum_notice_" + enum_notice_id).hide();
2851    }
2852}
2853
2854/*
2855 * Creates a Profiling Chart with jqplot. Used in sql.js
2856 * and in server_status_monitor.js
2857 */
2858function PMA_createProfilingChartJqplot(target, data)
2859{
2860    return $.jqplot(target, [data],
2861        {
2862            seriesDefaults: {
2863                renderer: $.jqplot.PieRenderer,
2864                rendererOptions: {
2865                    showDataLabels:  true
2866                }
2867            },
2868            legend: {
2869                show: true,
2870                location: 'e'
2871            },
2872            // from http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette
2873            seriesColors: [
2874             '#fce94f',
2875             '#fcaf3e',
2876             '#e9b96e',
2877             '#8ae234',
2878             '#729fcf',
2879             '#ad7fa8',
2880             '#ef2929',
2881             '#eeeeec',
2882             '#888a85',
2883             '#c4a000',
2884             '#ce5c00',
2885             '#8f5902',
2886             '#4e9a06',
2887             '#204a87',
2888             '#5c3566',
2889             '#a40000',
2890             '#babdb6',
2891             '#2e3436'
2892            ]
2893        }
2894    );
2895}
2896
2897/**
2898 * Formats a profiling duration nicely (in us and ms time). Used in server_status.js
2899 *
2900 * @param  integer    Number to be formatted, should be in the range of microsecond to second
2901 * @param  integer    Accuracy, how many numbers right to the comma should be
2902 * @return string     The formatted number
2903 */
2904function PMA_prettyProfilingNum(num, acc)
2905{
2906    if (!acc) {
2907        acc = 2;
2908    }
2909    acc = Math.pow(10, acc);
2910    if (num * 1000 < 0.1) {
2911        num = Math.round(acc * (num * 1000 * 1000)) / acc + 'µ';
2912    } else if (num < 0.1) {
2913        num = Math.round(acc * (num * 1000)) / acc + 'm';
2914    } else {
2915        num = Math.round(acc * num) / acc;
2916    }
2917
2918    return num + 's';
2919}
2920
2921
2922/**
2923 * Formats a SQL Query nicely with newlines and indentation. Depends on Codemirror and MySQL Mode!
2924 *
2925 * @param string      Query to be formatted
2926 * @return string      The formatted query
2927 */
2928function PMA_SQLPrettyPrint(string)
2929{
2930    if (typeof CodeMirror == 'undefined') {
2931        return string;
2932    }
2933
2934    var mode = CodeMirror.getMode({},"text/x-mysql");
2935    var stream = new CodeMirror.StringStream(string);
2936    var state = mode.startState();
2937    var token, tokens = [];
2938    var output = '';
2939    var tabs = function(cnt) {
2940        var ret = '';
2941        for (var i=0; i<4*cnt; i++) {
2942            ret += " ";
2943        }
2944        return ret;
2945    };
2946
2947    // "root-level" statements
2948    var statements = {
2949        'select': ['select', 'from','on','where','having','limit','order by','group by'],
2950        'update': ['update', 'set','where'],
2951        'insert into': ['insert into', 'values']
2952    };
2953    // don't put spaces before these tokens
2954    var spaceExceptionsBefore = { ';':true, ',': true, '.': true, '(': true };
2955    // don't put spaces after these tokens
2956    var spaceExceptionsAfter = { '.': true };
2957
2958    // Populate tokens array
2959    var str='';
2960    while (! stream.eol()) {
2961        stream.start = stream.pos;
2962        token = mode.token(stream, state);
2963        if (token != null) {
2964            tokens.push([token, stream.current().toLowerCase()]);
2965        }
2966    }
2967
2968    var currentStatement = tokens[0][1];
2969
2970    if (! statements[currentStatement]) {
2971        return string;
2972    }
2973    // Holds all currently opened code blocks (statement, function or generic)
2974    var blockStack = [];
2975    // Holds the type of block from last iteration (the current is in blockStack[0])
2976    var previousBlock;
2977    // If a new code block is found, newBlock contains its type for one iteration and vice versa for endBlock
2978    var newBlock, endBlock;
2979    // How much to indent in the current line
2980    var indentLevel = 0;
2981    // Holds the "root-level" statements
2982    var statementPart, lastStatementPart = statements[currentStatement][0];
2983
2984    blockStack.unshift('statement');
2985
2986    // Iterate through every token and format accordingly
2987    for (var i = 0; i < tokens.length; i++) {
2988        previousBlock = blockStack[0];
2989
2990        // New block => push to stack
2991        if (tokens[i][1] == '(') {
2992            if (i < tokens.length - 1 && tokens[i+1][0] == 'statement-verb') {
2993                blockStack.unshift(newBlock = 'statement');
2994            } else if (i > 0 && tokens[i-1][0] == 'builtin') {
2995                blockStack.unshift(newBlock = 'function');
2996            } else {
2997                blockStack.unshift(newBlock = 'generic');
2998            }
2999        } else {
3000            newBlock = null;
3001        }
3002
3003        // Block end => pop from stack
3004        if (tokens[i][1] == ')') {
3005            endBlock = blockStack[0];
3006            blockStack.shift();
3007        } else {
3008            endBlock = null;
3009        }
3010
3011        // A subquery is starting
3012        if (i > 0 && newBlock == 'statement') {
3013            indentLevel++;
3014            output += "\n" + tabs(indentLevel) + tokens[i][1] + ' ' + tokens[i+1][1].toUpperCase() + "\n" + tabs(indentLevel + 1);
3015            currentStatement = tokens[i+1][1];
3016            i++;
3017            continue;
3018        }
3019
3020        // A subquery is ending
3021        if (endBlock == 'statement' && indentLevel > 0) {
3022            output += "\n" + tabs(indentLevel);
3023            indentLevel--;
3024        }
3025
3026        // One less indentation for statement parts (from, where, order by, etc.) and a newline
3027        statementPart = statements[currentStatement].indexOf(tokens[i][1]);
3028        if (statementPart != -1) {
3029            if (i > 0) {
3030                output += "\n";
3031            }
3032            output += tabs(indentLevel) + tokens[i][1].toUpperCase();
3033            output += "\n" + tabs(indentLevel + 1);
3034            lastStatementPart = tokens[i][1];
3035        }
3036        // Normal indentatin and spaces for everything else
3037        else {
3038            if (! spaceExceptionsBefore[tokens[i][1]]
3039               && ! (i > 0 && spaceExceptionsAfter[tokens[i-1][1]])
3040               && output.charAt(output.length -1) != ' ' ) {
3041                    output += " ";
3042            }
3043            if (tokens[i][0] == 'keyword') {
3044                output += tokens[i][1].toUpperCase();
3045            } else {
3046                output += tokens[i][1];
3047            }
3048        }
3049
3050        // split columns in select and 'update set' clauses, but only inside statements blocks
3051        if (( lastStatementPart == 'select' || lastStatementPart == 'where'  || lastStatementPart == 'set')
3052            && tokens[i][1]==',' && blockStack[0] == 'statement') {
3053
3054            output += "\n" + tabs(indentLevel + 1);
3055        }
3056
3057        // split conditions in where clauses, but only inside statements blocks
3058        if (lastStatementPart == 'where'
3059            && (tokens[i][1]=='and' || tokens[i][1]=='or' || tokens[i][1]=='xor')) {
3060
3061            if (blockStack[0] == 'statement') {
3062                output += "\n" + tabs(indentLevel + 1);
3063            }
3064            // Todo: Also split and or blocks in newlines & identation++
3065            //if (blockStack[0] == 'generic')
3066             //   output += ...
3067        }
3068    }
3069    return output;
3070}
3071
3072/**
3073 * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not
3074 *  return a jQuery object yet and hence cannot be chained
3075 *
3076 * @param string      question
3077 * @param string      url         URL to be passed to the callbackFn to make
3078 *                                  an Ajax call to
3079 * @param function    callbackFn  callback to execute after user clicks on OK
3080 */
3081
3082jQuery.fn.PMA_confirm = function(question, url, callbackFn) {
3083    var confirmState = PMA_commonParams.get('confirm');
3084    // when the Confirm directive is set to false in config.inc.php
3085    // and not changed in user prefs, confirmState is ""
3086    // when it's unticked in user prefs, confirmState is 1
3087    if (confirmState === "" || confirmState === "1") {
3088        // user does not want to confirm
3089        if ($.isFunction(callbackFn)) {
3090            callbackFn.call(this, url);
3091            return true;
3092        }
3093    }
3094    if (PMA_messages['strDoYouReally'] == '') {
3095        return true;
3096    }
3097
3098    /**
3099     * @var    button_options  Object that stores the options passed to jQueryUI
3100     *                          dialog
3101     */
3102    var button_options = {};
3103    button_options[PMA_messages['strOK']] = function() {
3104        $(this).dialog("close");
3105
3106        if ($.isFunction(callbackFn)) {
3107            callbackFn.call(this, url);
3108        }
3109    };
3110    button_options[PMA_messages['strCancel']] = function() {
3111        $(this).dialog("close");
3112    };
3113
3114    $('<div/>', {'id':'confirm_dialog'})
3115    .prepend(question)
3116    .dialog({
3117        buttons: button_options,
3118        close: function () {
3119            $(this).remove();
3120        },
3121        modal: true
3122    });
3123};
3124
3125/**
3126 * jQuery function to sort a table's body after a new row has been appended to it.
3127 * Also fixes the even/odd classes of the table rows at the end.
3128 *
3129 * @param string      text_selector   string to select the sortKey's text
3130 *
3131 * @return jQuery Object for chaining purposes
3132 */
3133jQuery.fn.PMA_sort_table = function(text_selector) {
3134    return this.each(function() {
3135
3136        /**
3137         * @var table_body  Object referring to the table's <tbody> element
3138         */
3139        var table_body = $(this);
3140        /**
3141         * @var rows    Object referring to the collection of rows in {@link table_body}
3142         */
3143        var rows = $(this).find('tr').get();
3144
3145        //get the text of the field that we will sort by
3146        $.each(rows, function(index, row) {
3147            row.sortKey = $.trim($(row).find(text_selector).text().toLowerCase());
3148        });
3149
3150        //get the sorted order
3151        rows.sort(function(a, b) {
3152            if (a.sortKey < b.sortKey) {
3153                return -1;
3154            }
3155            if (a.sortKey > b.sortKey) {
3156                return 1;
3157            }
3158            return 0;
3159        });
3160
3161        //pull out each row from the table and then append it according to it's order
3162        $.each(rows, function(index, row) {
3163            $(table_body).append(row);
3164            row.sortKey = null;
3165        });
3166
3167        //Re-check the classes of each row
3168        $(this).find('tr:odd')
3169        .removeClass('even').addClass('odd')
3170        .end()
3171        .find('tr:even')
3172        .removeClass('odd').addClass('even');
3173    });
3174};
3175
3176/**
3177 * Unbind all event handlers before tearing down a page
3178 */
3179AJAX.registerTeardown('functions.js', function() {
3180    $("#create_table_form_minimal.ajax").die('submit');
3181    $("form.create_table_form.ajax").die('submit');
3182    $("form.create_table_form.ajax input[name=submit_num_fields]").die('click');
3183    $("form.create_table_form.ajax input").die('keyup');
3184});
3185
3186/**
3187 * jQuery coding for 'Create Table'.  Used on db_operations.php,
3188 * db_structure.php and db_tracking.php (i.e., wherever
3189 * libraries/display_create_table.lib.php is used)
3190 *
3191 * Attach Ajax Event handlers for Create Table
3192 */
3193AJAX.registerOnload('functions.js', function() {
3194    /**
3195     * Attach event handler for submission of create table form (save)
3196     */
3197    $("form.create_table_form.ajax").live('submit', function(event) {
3198        event.preventDefault();
3199
3200        /**
3201         * @var    the_form    object referring to the create table form
3202         */
3203        var $form = $(this);
3204
3205        /*
3206         * First validate the form; if there is a problem, avoid submitting it
3207         *
3208         * checkTableEditForm() needs a pure element and not a jQuery object,
3209         * this is why we pass $form[0] as a parameter (the jQuery object
3210         * is actually an array of DOM elements)
3211         */
3212
3213        if (checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) {
3214            PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']);
3215            PMA_prepareForAjaxRequest($form);
3216            //User wants to submit the form
3217            $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function(data) {
3218                if (data.success == true) {
3219                    $('#properties_message')
3220                     .removeClass('error')
3221                     .html('');
3222                    PMA_ajaxShowMessage(data.message);
3223                    // Only if the create table dialog (distinct panel) exists
3224                    if ($("#create_table_dialog").length > 0) {
3225                        $("#create_table_dialog").dialog("close").remove();
3226                    }
3227                    $('#tableslistcontainer').before(data.formatted_sql);
3228
3229                    /**
3230                     * @var tables_table    Object referring to the <tbody> element that holds the list of tables
3231                     */
3232                    var tables_table = $("#tablesForm").find("tbody").not("#tbl_summary_row");
3233                    // this is the first table created in this db
3234                    if (tables_table.length == 0) {
3235                        PMA_commonActions.refreshMain(
3236                            PMA_commonParams.get('opendb_url')
3237                        );
3238                    } else {
3239                        /**
3240                         * @var curr_last_row   Object referring to the last <tr> element in {@link tables_table}
3241                         */
3242                        var curr_last_row = $(tables_table).find('tr:last');
3243                        /**
3244                         * @var curr_last_row_index_string   String containing the index of {@link curr_last_row}
3245                         */
3246                        var curr_last_row_index_string = $(curr_last_row).find('input:checkbox').attr('id').match(/\d+/)[0];
3247                        /**
3248                         * @var curr_last_row_index Index of {@link curr_last_row}
3249                         */
3250                        var curr_last_row_index = parseFloat(curr_last_row_index_string);
3251                        /**
3252                         * @var new_last_row_index   Index of the new row to be appended to {@link tables_table}
3253                         */
3254                        var new_last_row_index = curr_last_row_index + 1;
3255                        /**
3256                         * @var new_last_row_id String containing the id of the row to be appended to {@link tables_table}
3257                         */
3258                        var new_last_row_id = 'checkbox_tbl_' + new_last_row_index;
3259
3260                        data.new_table_string = data.new_table_string.replace(/checkbox_tbl_/, new_last_row_id);
3261                        //append to table
3262                        $(data.new_table_string)
3263                         .appendTo(tables_table);
3264
3265                        //Sort the table
3266                        $(tables_table).PMA_sort_table('th');
3267
3268                        // Adjust summary row
3269                        PMA_adjustTotals();
3270                    }
3271
3272                    //Refresh navigation as a new table has been added
3273                    PMA_reloadNavigation();
3274                } else {
3275                    PMA_ajaxShowMessage(
3276                        '<div class="error">' + data.error + '</div>',
3277                        false
3278                    );
3279                }
3280            }); // end $.post()
3281        } // end if (checkTableEditForm() )
3282    }); // end create table form (save)
3283
3284    /**
3285     * Attach event handler for create table form (add fields)
3286     */
3287    $("form.create_table_form.ajax input[name=submit_num_fields]").live('click', function(event) {
3288        event.preventDefault();
3289        /**
3290         * @var    the_form    object referring to the create table form
3291         */
3292        var $form = $(this).closest('form');
3293
3294        var $msgbox = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']);
3295        PMA_prepareForAjaxRequest($form);
3296
3297        //User wants to add more fields to the table
3298        $.post($form.attr('action'), $form.serialize() + "&submit_num_fields=1", function(data) {
3299            if (data.success) {
3300                $("#page_content").html(data.message);
3301                PMA_verifyColumnsProperties();
3302                PMA_ajaxRemoveMessage($msgbox);
3303            } else {
3304                PMA_ajaxShowMessage(data.error);
3305            }
3306        }); //end $.post()
3307    }); // end create table form (add fields)
3308
3309    $("form.create_table_form.ajax input").live('keydown', function (event) {
3310        if (event.keyCode == 13) {
3311            event.preventDefault();
3312            event.stopImmediatePropagation();
3313            $(this)
3314                .closest('form')
3315                .append('<input type="hidden" name="do_save_data" value="1" />')
3316                .submit();
3317        }
3318    });
3319});
3320
3321
3322/**
3323 * Unbind all event handlers before tearing down a page
3324 */
3325AJAX.registerTeardown('functions.js', function() {
3326    $("#copyTable.ajax").die('submit');
3327    $("#moveTableForm").die('submit');
3328    $("#tableOptionsForm").die('submit');
3329    $("#tbl_maintenance li a.maintain_action.ajax").die('click');
3330});
3331/**
3332 * jQuery coding for 'Table operations'.  Used on tbl_operations.php
3333 * Attach Ajax Event handlers for Table operations
3334 */
3335AJAX.registerOnload('functions.js', function() {
3336    /**
3337     *Ajax action for submitting the "Copy table"
3338    **/
3339    $("#copyTable.ajax").live('submit', function(event) {
3340        event.preventDefault();
3341        var $form = $(this);
3342        PMA_prepareForAjaxRequest($form);
3343        $.post($form.attr('action'), $form.serialize()+"&submit_copy=Go", function(data) {
3344            if (data.success == true) {
3345                if ($form.find("input[name='switch_to_new']").prop('checked')) {
3346                    PMA_commonParams.set(
3347                        'db',
3348                        data.db
3349                    );
3350                    PMA_commonParams.set(
3351                        'table',
3352                        $form.find("input[name='new_name']").val()
3353                    );
3354                    PMA_commonActions.refreshMain(false, function () {
3355                        PMA_ajaxShowMessage(data.message);
3356                    });
3357                } else {
3358                    PMA_ajaxShowMessage(data.message);
3359                }
3360                // Refresh navigation when the table is copied
3361                PMA_reloadNavigation();
3362            } else {
3363                PMA_ajaxShowMessage(data.error, false);
3364            }
3365        }); // end $.post()
3366    });//end of copyTable ajax submit
3367
3368    /**
3369     *Ajax action for submitting the "Move table"
3370     */
3371    $("#moveTableForm").live('submit', function(event) {
3372        event.preventDefault();
3373        var $form = $(this);
3374        var db = $form.find('select[name=target_db]').val();
3375        var tbl = $form.find('input[name=new_name]').val();
3376        PMA_prepareForAjaxRequest($form);
3377        $.post($form.attr('action'), $form.serialize()+"&submit_move=1", function(data) {
3378            if (data.success == true) {
3379                PMA_commonParams.set('db', db);
3380                PMA_commonParams.set('table', tbl);
3381                PMA_commonActions.refreshMain(false, function () {
3382                    PMA_ajaxShowMessage(data.message);
3383                });
3384                // Refresh navigation when the table is copied
3385                PMA_reloadNavigation();
3386            } else {
3387                PMA_ajaxShowMessage(data.error, false);
3388            }
3389        }); // end $.post()
3390    });
3391
3392    /**
3393     * Ajax action for submitting the "Table options"
3394     */
3395    $("#tableOptionsForm").live('submit', function(event) {
3396        event.preventDefault();
3397        event.stopPropagation();
3398        var $form = $(this);
3399        var $tblNameField = $form.find('input[name=new_name]');
3400        if ($tblNameField.val() !== $tblNameField[0].defaultValue) {
3401            // reload page and navigation if the table has been renamed
3402            PMA_prepareForAjaxRequest($form);
3403            var tbl = $tblNameField.val();
3404            $.post($form.attr('action'), $form.serialize(), function(data) {
3405                if (data.success == true) {
3406                    PMA_commonParams.set('table', tbl);
3407                    PMA_commonActions.refreshMain(false, function() {
3408                        $('#page_content').html(data.message);
3409                    });
3410                } else {
3411                    PMA_ajaxShowMessage(data.error, false);
3412                }
3413            }); // end $.post()
3414        } else {
3415            $form.removeClass('ajax').submit().addClass('ajax');
3416        }
3417    });
3418
3419    /**
3420     *Ajax events for actions in the "Table maintenance"
3421    **/
3422    $("#tbl_maintenance li a.maintain_action.ajax").live('click', function(event) {
3423        event.preventDefault();
3424        if ($("#sqlqueryresults").length != 0) {
3425            $("#sqlqueryresults").remove();
3426        }
3427        if ($("#result_query").length != 0) {
3428            $("#result_query").remove();
3429        }
3430        //variables which stores the common attributes
3431        $.post($(this).attr('href'), { ajax_request: 1 }, function(data) {
3432            function scrollToTop() {
3433                $('html, body').animate({ scrollTop: 0 });
3434            }
3435            if (data.success == true && data.sql_query != undefined) {
3436                PMA_ajaxShowMessage(data.message);
3437                $("<div id='sqlqueryresults' class='ajax'></div>").prependTo("#page_content");
3438                $("#sqlqueryresults").html(data.sql_query);
3439                scrollToTop();
3440            } else if (data.success == true) {
3441                var $temp_div = $("<div id='temp_div'></div>");
3442                $temp_div.html(data.message);
3443                var $success = $temp_div.find("#result_query .success");
3444                PMA_ajaxShowMessage($success);
3445                $("<div id='sqlqueryresults' class='ajax'></div>").prependTo("#page_content");
3446                $("#sqlqueryresults").html(data.message);
3447                PMA_init_slider();
3448                $("#sqlqueryresults").children("fieldset,br").remove();
3449                scrollToTop();
3450            } else {
3451                var $temp_div = $("<div id='temp_div'></div>");
3452                $temp_div.html(data.error);
3453                var $error = $temp_div.find("code").addClass("error");
3454                PMA_ajaxShowMessage($error, false);
3455            }
3456        }); // end $.post()
3457    });
3457//end of table maintanance ajax click
3458}); //end $(document).ready for 'Table operations'
3459
3460/**
3461 * Unbind all event handlers before tearing down a page
3462 */
3463AJAX.registerTeardown('functions.js', function() {
3464    $("#drop_db_anchor.ajax").die('click');
3465});
3466/**
3467 * Attach Ajax event handlers for Drop Database. Moved here from db_structure.js
3468 * as it was also required on db_create.php
3469 */
3470AJAX.registerOnload('functions.js', function() {
3471    $("#drop_db_anchor.ajax").live('click', function(event) {
3472        event.preventDefault();
3473        /**
3474         * @var question    String containing the question to be asked for confirmation
3475         */
3476        var question = PMA_messages.strDropDatabaseStrongWarning + ' ';
3477        question += $.sprintf(
3478            PMA_messages.strDoYouReally,
3479            'DROP DATABASE ' + escapeHtml(PMA_commonParams.get('db'))
3480        );
3481        $(this).PMA_confirm(question, $(this).attr('href'), function(url) {
3482            PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']);
3483            $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function(data) {
3484                if (data.success) {
3485                    //Database deleted successfully, refresh both the frames
3486                    PMA_reloadNavigation();
3487                    PMA_commonParams.set('db', '');
3488                    PMA_commonActions.refreshMain(
3489                        'server_databases.php',
3490                        function () {
3491                            PMA_ajaxShowMessage(data.message);
3492                        }
3493                    );
3494                } else {
3495                    PMA_ajaxShowMessage(data.error, false);
3496                }
3497            });
3498        });
3499    });
3500}); // end of $() for Drop Database
3501
3502/**
3503 * Validates the password field in a form
3504 *
3505 * @see    PMA_messages['strPasswordEmpty']
3506 * @see    PMA_messages['strPasswordNotSame']
3507 * @param  object $the_form The form to be validated
3508 * @return bool
3509 */
3510function PMA_checkPassword($the_form)
3511{
3512    // Did the user select 'no password'?
3513    if ($the_form.find('#nopass_1').is(':checked')) {
3514        return true;
3515    } else {
3516        var $pred = $the_form.find('#select_pred_password');
3517        if ($pred.length && ($pred.val() == 'none' || $pred.val() == 'keep')) {
3518            return true;
3519        }
3520    }
3521
3522    var $password = $the_form.find('input[name=pma_pw]');
3523    var $password_repeat = $the_form.find('input[name=pma_pw2]');
3524    var alert_msg = false;
3525
3526    if ($password.val() == '') {
3527        alert_msg = PMA_messages['strPasswordEmpty'];
3528    } else if ($password.val() != $password_repeat.val()) {
3529        alert_msg = PMA_messages['strPasswordNotSame'];
3530    }
3531
3532    if (alert_msg) {
3533        alert(alert_msg);
3534        $password.val('');
3535        $password_repeat.val('');
3536        $password.focus();
3537         return false;
3538    }
3539    return true;
3540}
3541
3542/**
3543 * Unbind all event handlers before tearing down a page
3544 */
3545AJAX.registerTeardown('functions.js', function() {
3546    $('#change_password_anchor.ajax').die('click');
3547});
3548/**
3549 * Attach Ajax event handlers for 'Change Password' on index.php
3550 */
3551AJAX.registerOnload('functions.js', function() {
3552
3553    /**
3554     * Attach Ajax event handler on the change password anchor
3555     */
3556    $('#change_password_anchor.ajax').live('click', function(event) {
3557        event.preventDefault();
3558
3559        var $msgbox = PMA_ajaxShowMessage();
3560
3561        /**
3562         * @var button_options  Object containing options to be passed to jQueryUI's dialog
3563         */
3564        var button_options = {};
3565        button_options[PMA_messages['strGo']] = function() {
3566
3567            event.preventDefault();
3568
3569            /**
3570             * @var $the_form    Object referring to the change password form
3571             */
3572            var $the_form = $("#change_password_form");
3573
3574            if (! PMA_checkPassword($the_form)) {
3575                return false;
3576            }
3577
3578            /**
3579             * @var this_value  String containing the value of the submit button.
3580             * Need to append this for the change password form on Server Privileges
3581             * page to work
3582             */
3583            var this_value = $(this).val();
3584
3585            var $msgbox = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']);
3586            $the_form.append('<input type="hidden" name="ajax_request" value="true" />');
3587
3588            $.post($the_form.attr('action'), $the_form.serialize() + '&change_pw='+ this_value, function(data) {
3589                if (data.success == true) {
3590                    $("#page_content").prepend(data.message);
3591                    $("#change_password_dialog").hide().remove();
3592                    $("#edit_user_dialog").dialog("close").remove();
3593                    PMA_ajaxRemoveMessage($msgbox);
3594                }
3595                else {
3596                    PMA_ajaxShowMessage(data.error, false);
3597                }
3598            }); // end $.post()
3599        };
3600
3601        button_options[PMA_messages['strCancel']] = function() {
3602            $(this).dialog('close');
3603        };
3604        $.get($(this).attr('href'), {'ajax_request': true}, function(data) {
3605            if (data.success) {
3606                $('<div id="change_password_dialog"></div>')
3607                .dialog({
3608                    title: PMA_messages['strChangePassword'],
3609                    width: 600,
3610                    close: function(ev, ui) {
3611                        $(this).remove();
3612                    },
3613                    buttons : button_options,
3614                    modal: true
3615                })
3616                .append(data.message);
3617                // for this dialog, we remove the fieldset wrapping due to double headings
3618                $("fieldset#fieldset_change_password")
3619                .find("legend").remove().end()
3620                .find("table.noclick").unwrap().addClass("some-margin")
3621                .find("input#text_pma_pw").focus();
3622                displayPasswordGenerateButton();
3623                $('#fieldset_change_password_footer').hide();
3624                PMA_ajaxRemoveMessage($msgbox);
3625                $('#change_password_form').bind('submit', function (e) {
3626                    e.preventDefault();
3627                    $(this)
3628                        .closest('.ui-dialog')
3629                        .find('.ui-dialog-buttonpane .ui-button')
3630                        .first()
3631                        .click();
3632                });
3633            } else {
3634                PMA_ajaxShowMessage(data.error, false);
3635            }
3636        }); // end $.get()
3637    }); // end handler for change password anchor
3638}); // end $() for Change Password
3639
3640/**
3641 * Unbind all event handlers before tearing down a page
3642 */
3643AJAX.registerTeardown('functions.js', function() {
3644    $("select.column_type").die('change');
3645    $("select.default_type").die('change');
3646    $('input.allow_null').die('change');
3647});
3648/**
3649 * Toggle the hiding/showing of the "Open in ENUM/SET editor" message when
3650 * the page loads and when the selected data type changes
3651 */
3652AJAX.registerOnload('functions.js', function() {
3653    // is called here for normal page loads and also when opening
3654    // the Create table dialog
3655    PMA_verifyColumnsProperties();
3656    //
3657    // needs live() to work also in the Create Table dialog
3658    $("select.column_type").live('change', function() {
3659        PMA_showNoticeForEnum($(this));
3660    });
3661    $("select.default_type").live('change', function() {
3662        PMA_hideShowDefaultValue($(this));
3663    });
3664    $('input.allow_null').live('change', function() {
3665        PMA_validateDefaultValue($(this));
3666    });
3667});
3668
3669function PMA_verifyColumnsProperties()
3670{
3671    $("select.column_type").each(function() {
3672        PMA_showNoticeForEnum($(this));
3673    });
3674    $("select.default_type").each(function() {
3675        PMA_hideShowDefaultValue($(this));
3676    });
3677}
3678
3679/**
3680 * Hides/shows the default value input field, depending on the default type
3681 * Ticks the NULL checkbox if NULL is chosen as default value.
3682 */
3683function PMA_hideShowDefaultValue($default_type)
3684{
3685    if ($default_type.val() == 'USER_DEFINED') {
3686        $default_type.siblings('.default_value').show().focus();
3687    } else {
3688        $default_type.siblings('.default_value').hide();
3689        if ($default_type.val() == 'NULL') {
3690            var $null_checkbox = $default_type.closest('tr').find('.allow_null');
3691            $null_checkbox.prop('checked', true);
3692        }
3693    }
3694}
3695
3696/**
3697 * If the column does not allow NULL values, makes sure that default is not NULL
3698 */
3699function PMA_validateDefaultValue($null_checkbox)
3700{
3701    if (! $null_checkbox.prop('checked')) {
3702        var $default = $null_checkbox.closest('tr').find('.default_t
3702ype');
3703        if ($default.val() == 'NULL') {
3704            $default.val('NONE');
3705        }
3706    }
3707}
3708
3709
3710/**
3711 * Unbind all event handlers before tearing down a page
3712 */
3713AJAX.registerTeardown('functions.js', function() {
3714    $("a.open_enum_editor").die('click');
3715    $("input.add_value").die('click');
3716    $("#enum_editor td.drop").die('click');
3717});
3718/**
3719 * @var $enum_editor_dialog An object that points to the jQuery
3720 *                          dialog of the ENUM/SET editor
3721 */
3722var $enum_editor_dialog = null;
3723/**
3724 * Opens the ENUM/SET editor and controls its functions
3725 */
3726AJAX.registerOnload('functions.js', function() {
3727    $("a.open_enum_editor").live('click', function() {
3728        // Get the name of the column that is being edited
3729        var colname = $(this).closest('tr').find('input:first').val();
3730        // And use it to make up a title for the page
3731        if (colname.length < 1) {
3732            var title = PMA_messages['enum_newColumnVals'];
3733        } else {
3734            var title = PMA_messages['enum_columnVals'].replace(
3735                /%s/,
3736                '"' + escapeHtml(decodeURIComponent(colname)) + '"'
3737            );
3738        }
3739        // Get the values as a string
3740        var inputstring = $(this)
3741            .closest('td')
3742            .find("input")
3743            .val();
3744        // Escape html entities
3745        inputstring = $('<div/>')
3746            .text(inputstring)
3747            .html();
3748        // Parse the values, escaping quotes and
3749        // slashes on the fly, into an array
3750        var values = [];
3751        var in_string = false;
3752        var curr, next, buffer = '';
3753        for (var i=0; i<inputstring.length; i++) {
3754            curr = inputstring.charAt(i);
3755            next = i == inputstring.length ? '' : inputstring.charAt(i+1);
3756            if (! in_string && curr == "'") {
3757                in_string = true;
3758            } else if (in_string && curr == "\\" && next == "\\") {
3759                buffer += "&#92;";
3760                i++;
3761            } else if (in_string && next == "'" && (curr == "'" || curr == "\\")) {
3762                buffer += "&#39;";
3763                i++;
3764            } else if (in_string && curr == "'") {
3765                in_string = false;
3766                values.push(buffer);
3767                buffer = '';
3768            } else if (in_string) {
3769                 buffer += curr;
3770            }
3771        }
3772        if (buffer.length > 0) {
3773            // The leftovers in the buffer are the last value (if any)
3774            values.push(buffer);
3775        }
3776        var fields = '';
3777        // If there are no values, maybe the user is about to make a
3778        // new list so we add a few for him/her to get started with.
3779        if (values.length == 0) {
3780            values.push('','','','');
3781        }
3782        // Add the parsed values to the editor
3783        var drop_icon = PMA_getImage('b_drop.png');
3784        for (var i=0; i<values.length; i++) {
3785            fields += "<tr><td>"
3786                   + "<input type='text' value='" + values[i] + "'/>"
3787                   + "</td><td class='drop'>"
3788                   + drop_icon
3789                   + "</td></tr>";
3790        }
3791        /**
3792         * @var dialog HTML code for the ENUM/SET dialog
3793         */
3794        var dialog = "<div id='enum_editor'>"
3795                   + "<fieldset>"
3796                   + "<legend>" + title + "</legend>"
3797                   + "<p>" + PMA_getImage('s_notice.png')
3798                   + PMA_messages['enum_hint'] + "</p>"
3799                   + "<table class='values'>" + fields + "</table>"
3800                   + "</fieldset><fieldset class='tblFooters'>"
3801                   + "<table class='add'><tr><td>"
3802                   + "<div class='slider'></div>"
3803                   + "</td><td>"
3804                   + "<form><div><input type='submit' class='add_value' value='"
3805                   + $.sprintf(PMA_messages['enum_addValue'], 1)
3806                   + "'/></div></form>"
3807                   + "</td></tr></table>"
3808                   + "<input type='hidden' value='" // So we know which column's data is being edited
3809                   + $(this).closest('td').find("input").attr("id")
3810                   + "' />"
3811                   + "</fieldset>"
3812                   + "</div>";
3813        /**
3814         * @var  Defines functions to be called when the buttons in
3815         * the buttonOptions jQuery dialog bar are pressed
3816         */
3817        var buttonOptions = {};
3818        buttonOptions[PMA_messages['strGo']] = function () {
3819            // When the submit button is clicked,
3820            // put the data back into the original form
3821            var value_array = [];
3822            $(this).find(".values input").each(function(index, elm) {
3823                var val = elm.value.replace(/\\/g, '\\\\').replace(/'/g, "''");
3824                value_array.push("'" + val + "'");
3825            });
3826            // get the Length/Values text field where this value belongs
3827            var values_id = $(this).find("input[type='hidden']").val();
3828            $("input#" + values_id).val(value_array.join(","));
3829            $(this).dialog("close");
3830        };
3831        buttonOptions[PMA_messages['strClose']] = function () {
3832            $(this).dialog("close");
3833        };
3834        // Show the dialog
3835        var width = parseInt(
3836            (parseInt($('html').css('font-size'), 10)/13)*340,
3837            10
3838        );
3839        if (! width) {
3840            width = 340;
3841        }
3842        $enum_editor_dialog = $(dialog).dialog({
3843            minWidth: width,
3844            modal: true,
3845            title: PMA_messages['enum_editor'],
3846            buttons: buttonOptions,
3847            open: function() {
3848                // Focus the "Go" button after opening the dialog
3849                $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus();
3850            },
3851            close: function() {
3852                $(this).remove();
3853            }
3854        });
3855        // slider for choosing how many fields to add
3856        $enum_editor_dialog.find(".slider").slider({
3857            animate: true,
3858            range: "min",
3859            value: 1,
3860            min: 1,
3861            max: 9,
3862            slide: function( event, ui ) {
3863                $(this).closest('table').find('input[type=submit]').val(
3864                    $.sprintf(PMA_messages['enum_addValue'], ui.value)
3865                );
3866            }
3867        });
3868        // Focus the slider, otherwise it looks nearly transparent
3869        $('a.ui-slider-handle').addClass('ui-state-focus');
3870        return false;
3871    });
3872
3873    // When "add a new value" is clicked, append an empty text field
3874    $("input.add_value").live('click', function(e) {
3875        e.preventDefault();
3876        var num_new_rows = $enum_editor_dialog.find("div.slider").slider('value');
3877        while (num_new_rows--) {
3878            $enum_editor_dialog.find('.values')
3879                .append(
3880                    "<tr style='display: none;'><td>"
3881                  + "<input type='text' />"
3882                  + "</td><td class='drop'>"
3883                  + PMA_getImage('b_drop.png')
3884                  + "</td></tr>"
3885                )
3886                .find('tr:last')
3887                .show('fast');
3888        }
3889    });
3890
3891    // Removes the specified row from the enum editor
3892    $("#enum_editor td.drop").live('click', function() {
3893        $(this).closest('tr').hide('fast', function () {
3894            $(this).remove();
3895        });
3896    });
3897});
3898
3899/**
3900 * Ensures indexes names are valid according to their type and, for a primary
3901 * key, lock index name to 'PRIMARY'
3902 * @param string   form_id  Variable which parses the form name as
3903 *                            the input
3904 * @return boolean  false    if there is no index form, true else
3905 */
3906function checkIndexName(form_id)
3907{
3908    if ($("#"+form_id).length == 0) {
3909        return false;
3910    }
3911
3912    // Gets the elements pointers
3913    var $the_idx_name = $("#input_index_name");
3914    var $the_idx_type = $("#select_index_type");
3915
3916    // Index is a primary key
3917    if ($the_idx_type.find("option:selected").val() == 'PRIMARY') {
3918        $the_idx_name.val('PRIMARY');
3919        $the_idx_name.prop("disabled", true);
3920    }
3921
3922    // Other cases
3923    else {
3924        if ($the_idx_name.val() == 'PRIMARY') {
3925            $the_idx_name.val("");
3926        }
3927        $the_idx_name.prop("disabled", false);
3928    }
3929
3930    return true;
3931} // end of the 'checkIndexName()' function
3932
3933AJAX.registerTeardown('functions.js', function() {
3934    $('#index_frm input[type=submit]').die('click');
3935});
3936AJAX.registerOnload('functions.js', function() {
3937    /**
3938     * Handler for adding more columns to an index in the editor
3939     */
3940    $('#index_frm input[type=submit]').live('click', function(event) {
3941        event.preventDefault();
3942        var rows_to_add = $(this)
3943            .closest('fieldset')
3944            .find('.slider')
3945            .slider('value');
3946        while (rows_to_add--) {
3947            var $newrow = $('#index_columns')
3948                .find('tbody > tr:first')
3949                .clone()
3950                .appendTo(
3951                    $('#index_columns').find('tbody')
3952                );
3953            $newrow.find(':input').each(function() {
3954                $(this).val('');
3955            });
3956            // focus index size input on column picked
3957            $newrow.find('select').change(function() {
3958                if ($(this).find("option:selected").val() == '') {
3959                    return true;
3960                }
3961                $(this).closest("tr").find("input").focus();
3962            });
3963        }
3964    });
3965});
3966
3967function indexEditorDialog(url, title, callback_success, callback_failure)
3968{
3969    /*Remove the hidden dialogs if there are*/
3970    if ($('#edit_index_dialog').length != 0) {
3971        $('#edit_index_dialog').remove();
3972    }
3973    var $div = $('<div id="edit_index_dialog"></div>');
3974
3975    /**
3976     * @var button_options Object that stores the options
3977     *                     passed to jQueryUI dialog
3978     */
3979    var button_options = {};
3980    button_options[PMA_messages['strGo']] = function() {
3981        /**
3982         * @var    the_form    object referring to the export form
3983         */
3984        var $form = $("#index_frm");
3985        var $msgbox = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']);
3986        PMA_prepareForAjaxRequest($form);
3987        //User wants to submit the form
3988        $.post($form.attr('action'), $form.serialize()+"&do_save_data=1", function(data) {
3989            if ($("#sqlqueryresults").length != 0) {
3990                $("#sqlqueryresults").remove();
3991            }
3992            if (data.success == true) {
3993                PMA_ajaxShowMessage(data.message);
3994                if ($('#result_query').length) {
3995                    $('#result_query').remove();
3996                }
3997                if (data.sql_query) {
3998                    $('<div id="result_query"></div>')
3999                        .html(data.sql_query)
4000                        .prependTo('#page_content');
4001                }
4002                $("#result_query .notice").remove();
4003                $("#result_query").prepend(data.message);
4004                /*Reload the field form*/
4005                $("#table_index").remove();
4006                var $temp_div = $("<div id='temp_div'><div>").append(data.index_table);
4007                $temp_div.find("#table_index").insertAfter("#index_header");
4008                if ($("#edit_index_dialog").length > 0) {
4009                    $("#edit_index_dialog").dialog("close");
4010                }
4011                $('div.no_indexes_defined').hide();
4012                if (callback_success) {
4013                    callback_success();
4014                }
4015                PMA_reloadNavigation();
4016            } else {
4017                var $temp_div = $("<div id='temp_div'><div>").append(data.error);
4018                if ($temp_div.find(".error code").length != 0) {
4019                    var $error = $temp_div.find(".error code").addClass("error");
4020                } else {
4021                    var $error = $temp_div;
4022                }
4023                if (callback_failure) {
4024                    callback_failure();
4025                }
4026                PMA_ajaxShowMessage($error, false);
4027            }
4028        }); // end $.post()
4029    };
4030    button_options[PMA_messages['strCancel']] = function() {
4031        $(this).dialog('close');
4032    };
4033    var $msgbox = PMA_ajaxShowMessage();
4034    $.get("tbl_indexes.php", url, function(data) {
4035        if (data.success == false) {
4036            //in the case of an error, show the error message returned.
4037            PMA_ajaxShowMessage(data.error, false);
4038        } else {
4039            PMA_ajaxRemoveMessage($msgbox);
4040            // Show dialog if the request was successful
4041            $div
4042            .append(data.message)
4043            .dialog({
4044                title: title,
4045                width: 450,
4046                open: PMA_verifyColumnsProperties,
4047                modal: true,
4048                buttons: button_options,
4049                close: function () {
4050                    $(this).remove();
4051                }
4052            });
4053            checkIndexType();
4054            checkIndexName("index_frm");
4055            PMA_showHints($div);
4056            // Add a slider for selecting how many columns to add to the index
4057            $div.find('.slider').slider({
4058                animate: true,
4059                value: 1,
4060                min: 1,
4061                max: 16,
4062                slide: function( event, ui ) {
4063                    $(this).closest('fieldset').find('input[type=submit]').val(
4064                        $.sprintf(PMA_messages['strAddToIndex'], ui.value)
4065                    );
4066                }
4067            });
4068            // focus index size input on column picked
4069            $div.find('table#index_columns select').change(function() {
4070                if ($(this).find("option:selected").val() == '') {
4071                    return true;
4072                }
4073                $(this).closest("tr").find("input").focus();
4074            });
4075            // Focus the slider, otherwise it looks nearly transparent
4076            $('a.ui-slider-handle').addClass('ui-state-focus');
4077            // set focus on index name input, if empty
4078            var input = $div.find('input#input_index_name');
4079            input.val() || input.focus();
4080        }
4081    }); // end $.get()
4082}
4083
4084/**
4085 * Function to display tooltips that were
4086 * generated on the PHP side by PMA_Util::showHint()
4087 *
4088 * @param object $div a div jquery object which specifies the
4089 *                    domain for searching for tooltips. If we
4090 *                    omit this parameter the function searches
4091 *                    in the whole body
4092 **/
4093function PMA_showHints($div)
4094{
4095    if ($div == undefined || ! $div instanceof jQuery || $div.length == 0) {
4096        $div = $("body");
4097    }
4098    $div.find('.pma_hint').each(function () {
4099        PMA_tooltip(
4100            $(this).children('img'),
4101            'img',
4102            $(this).children('span').html()
4103        );
4104    });
4105}
4106
4107AJAX.registerOnload('functions.js', function() {
4108    PMA_showHints();
4109});
4110
4111function PMA_mainMenuResizerCallback() {
4112    // 5 px margin for jumping menu in Chrome
4113    return $(document.body).width() - 5;
4114}
4115// This must be fired only once after the inital page load
4116$(function() {
4117    // Initialise the menu resize plugin
4118    $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
4119    // register resize event
4120    $(window).resize(function (){
4121        $('#topmenu').menuResizer('resize');
4122    });
4123});
4124
4125/**
4126 * Get the row number from the classlist (for example, row_1)
4127 */
4128function PMA_getRowNumber(classlist)
4129{
4130    return parseInt(classlist.split(/\s+row_/)[1]);
4131}
4132
4133/**
4134 * Changes status of slider
4135 */
4136function PMA_set_status_label($element)
4137{
4138    var text = $element.css('display') == 'none'
4139        ? '+ '
4140        : '- ';
4141    $element.closest('.slide-wrapper').prev().find('span').text(text);
4142}
4143
4144/**
4145 * var  toggleButton  This is a function that creates a toggle
4146 *                    sliding button given a jQuery reference
4147 *                    to the correct DOM element
4148 */
4149var toggleButton = function ($obj) {
4150    // In rtl mode the toggle switch is flipped horizontally
4151    // so we need to take that into account
4152    if ($('span.text_direction', $obj).text() == 'ltr') {
4153        var right = 'right';
4154    } else {
4155        var right = 'left';
4156    }
4157    /**
4158     *  var  h  Height of the button, used to scale the
4159     *          background image and position the layers
4160     */
4161    var h = $obj.height();
4162    $('img', $obj).height(h);
4163    $('table', $obj).css('bottom', h-1);
4164    /**
4165     *  var  on   Width of the "ON" part of the toggle switch
4166     *  var  off  Width of the "OFF" part of the toggle switch
4167     */
4168    var on  = $('td.toggleOn', $obj).width();
4169    var off = $('td.toggleOff', $obj).width();
4170    // Make the "ON" and "OFF" parts of the switch the same size
4171    // + 2 pixels to avoid overflowed
4172    $('td.toggleOn > div', $obj).width(Math.max(on, off) + 2);
4173    $('td.toggleOff > div', $obj).width(Math.max(on, off) + 2);
4174    /**
4175     *  var  w  Width of the central part of the switch
4176     */
4177    var w = parseInt(($('img', $obj).height() / 16) * 22, 10);
4178    // Resize the central part of the switch on the top
4179    // layer to match the background
4180    $('table td:nth-child(2) > div', $obj).width(w);
4181    /**
4182     *  var  imgw    Width of the background image
4183     *  var  tblw    Width of the foreground layer
4184     *  var  offset  By how many pixels to move the background
4185     *               image, so that it matches the top layer
4186     */
4187    var imgw = $('img', $obj).width();
4188    var tblw = $('table', $obj).width();
4189    var offset = parseInt(((imgw - tblw) / 2), 10);
4190    // Move the background to match the layout of the top layer
4191    $obj.find('img').css(right, offset);
4192    /**
4193     *  var  offw    Outer width of the "ON" part of the toggle switch
4194     *  var  btnw    Outer width of the central part of the switch
4195     */
4196    var offw = $('td.toggleOff', $obj).outerWidth();
4197    var btnw = $('table td:nth-child(2)', $obj).outerWidth();
4198    // Resize the main div so that exactly one side of
4199    // the switch plus the central part fit into it.
4200    $obj.width(offw + btnw + 2);
4201    /**
4202     *  var  move  How many pixels to move the
4203     *             switch by when toggling
4204     */
4205    var move = $('td.toggleOff', $obj).outerWidth();
4206    // If the switch is initialized to the
4207    // OFF state we need to move it now.
4208    if ($('div.container', $obj).hasClass('off')) {
4209        if (right == 'right') {
4210            $('div.container', $obj).animate({'left': '-=' + move + 'px'}, 0);
4211        } else {
4212            $('div.container', $obj).animate({'left': '+=' + move + 'px'}, 0);
4213        }
4214    }
4215    // Attach an 'onclick' event to the switch
4216    $('div.container', $obj).click(function () {
4217        if ($(this).hasClass('isActive')) {
4218            return false;
4219        } else {
4220            $(this).addClass('isActive');
4221        }
4222        var $msg = PMA_ajaxShowMessage();
4223        var $container = $(this);
4224        var callback = $('span.callback', this).text();
4225        // Perform the actual toggle
4226        if ($(this).hasClass('on')) {
4227            if (right == 'right') {
4228                var operator = '-=';
4229            } else {
4230                var operator = '+=';
4231            }
4232            var url = $(this).find('td.toggleOff > span').text();
4233            var removeClass = 'on';
4234            var addClass = 'off';
4235        } else {
4236            if (right == 'right') {
4237                var operator = '+=';
4238            } else {
4239                var operator = '-=';
4240            }
4241            var url = $(this).find('td.toggleOn > span').text();
4242            var removeClass = 'off';
4243            var addClass = 'on';
4244        }
4245        $.post(url, {'ajax_request': true}, function(data) {
4246            if (data.success == true) {
4247                PMA_ajaxRemoveMessage($msg);
4248                $container
4249                .removeClass(removeClass)
4250                .addClass(addClass)
4251                .animate({'left': operator + move + 'px'}, function () {
4252                    $container.removeClass('isActive');
4253                });
4254                eval(callback);
4255            } else {
4256                PMA_ajaxShowMessage(data.error, false);
4257                $container.removeClass('isActive');
4258            }
4259        });
4260    });
4261};
4262
4263/**
4264 * Unbind all event handlers before tearing down a page
4265 */
4266AJAX.registerTeardown('functions.js', function() {
4267    $('div.container').unbind('click');
4268});
4269/**
4270 * Initialise all toggle buttons
4271 */
4272AJAX.registerOnload('functions.js', function () {
4273    $('div.toggleAjax').each(function () {
4274        var $button = $(this).show();
4275        $button.find('img').each(function() {
4276            if (this.complete) {
4277                toggleButton($button);
4278            } else {
4279                $(this).load(function () {
4280                    toggleButton($button);
4281                });
4282            }
4283        });
4284    });
4285});
4286
4287/**
4288 * Unbind all event handlers before tearing down a page
4289 */
4290AJAX.registerTeardown('functions.js', function() {
4291    $('.vpointer').die('hover');
4292    $('.vmarker').die('click');
4293    $('#pageselector').die('change');
4294    $('a.formLinkSubmit').die('click');
4295    $('#update_recent_tables').unbind('ready');
4296});
4297/**
4298 * Vertical pointer
4299 */
4300AJAX.registerOnload('functions.js', function() {
4301    $('.vpointer').live('hover',
4302        //handlerInOut
4303        function(e) {
4304            var $this_td = $(this);
4305            var row_num = PMA_getRowNumber($this_td.attr('class'));
4306            // for all td of the same vertical row, toggle hover
4307            $('.vpointer').filter('.row_' + row_num).toggleClass('hover');
4308        }
4309    );
4310
4311
4312    /**
4313     * Vertical marker
4314     */
4315    $('.vmarker').live('click', function(e) {
4316        // do not trigger when clicked on anchor
4317        if ($(e.target).is('a, img, a *')) {
4318            return;
4319        }
4320
4321        var $this_td = $(this);
4322        var row_num = PMA_getRowNumber($this_td.attr('class'));
4323
4324        // XXX: FF fires two click events for <label> (label and checkbox), so we need to handle this differently
4325        var $checkbox = $('.vmarker').filter('.row_' + row_num + ':first').find(':checkbox');
4326        if ($checkbox.length) {
4327            // checkbox in a row, add or remove class depending on checkbox state
4328            var checked = $checkbox.prop('checked');
4329            if (!$(e.target).is(':checkbox, label')) {
4330                checked = !checked;
4331                $checkbox.prop('checked', checked);
4332            }
4333            // for all td of the same vertical row, toggle the marked class
4334            if (checked) {
4335                $('.vmarker').filter('.row_' + row_num).addClass('marked');
4336            } else {
4337                $('.vmarker').filter('.row_' + row_num).removeClass('marked');
4338            }
4339        } else {
4340            // normaln data table, just toggle class
4341            $('.vmarker').filter('.row_' + row_num).toggleClass('marked');
4342        }
4343    });
4344
4345    /**
4346     * Autosubmit page selector
4347     */
4348    $('select.pageselector').live('change', function(event) {
4349        event.stopPropagation();
4350        // Check where to load the new content
4351        if ($(this).closest("#pma_navigation").length == 0) {
4352            // For the main page we don't need to do anything,
4353            $(this).closest("form").submit();
4354        } else {
4355            // but for the navigation we need to manually replace the content
4356            PMA_navigationTreePagination($(this));
4357        }
4358    });
4359
4360    /**
4361     * Load version information asynchronously.
4362     */
4363    if ($('li.jsversioncheck').length > 0) {
4364        $.ajax({
4365            dataType: "json",
4366            url: 'version_check.php',
4367            method: "POST",
4368            data: {
4369                "server": PMA_commonParams.get('server'),
4370                "token": PMA_commonParams.get('token'),
4371            },
4372            success: PMA_current_version
4373        });
4374    }
4375
4376    if ($('#is_git_revision').length > 0) {
4377        setTimeout(PMA_display_git_revision, 10);
4378    }
4379
4380    /**
4381     * Slider effect.
4382     */
4383    PMA_init_slider();
4384
4385    /**
4386     * Enables the text generated by PMA_Util::linkOrButton() to be clickable
4387     */
4388    $('a.formLinkSubmit').live('click', function(e) {
4389
4390        if ($(this).attr('href').indexOf('=') != -1) {
4391            var data = $(this).attr('href').substr($(this).attr('href').indexOf('#')+1).split('=', 2);
4392            $(this).parents('form').append('<input type="hidden" name="' + data[0] + '" value="' + data[1] + '"/>');
4393        }
4394        $(this).parents('form').submit();
4395        return false;
4396    });
4397
4398    if ($('#update_recent_tables').length) {
4399        $.get(
4400            $('#update_recent_tables').attr('href'),
4401            function (data) {
4402                if (data.success == true) {
4403                    $('#recentTable').html(data.options);
4404                }
4405            }
4406        );
4407    }
4408
4409}); // end of $()
4410
4411
4412/**
4413 * Initializes slider effect.
4414 */
4415function PMA_init_slider()
4416{
4417    $('div.pma_auto_slider').each(function() {
4418        var $this = $(this);
4419        if ($this.data('slider_init_done')) {
4420            return;
4421        }
4422        var $wrapper = $('<div>', {'class': 'slide-wrapper'});
4423        $wrapper.toggle($this.is(':visible'));
4424        $('<a>', {href: '#'+this.id, "class":'ajax'})
4425            .text(this.title)
4426            .prepend($('<span>'))
4427            .insertBefore($this)
4428            .click(function() {
4429                var $wrapper = $this.closest('.slide-wrapper');
4430                var visible = $this.is(':visible');
4431                if (!visible) {
4432                    $wrapper.show();
4433                }
4434                $this[visible ? 'hide' : 'show']('blind', function() {
4435                    $wrapper.toggle(!visible);
4436                    PMA_set_status_label($this);
4437                });
4438                return false;
4439            });
4440        $this.wrap($wrapper);
4441        PMA_set_status_label($this);
4442        $this.data('slider_init_done', 1);
4443    });
4444}
4445
4446/**
4447 * Initializes slider effect.
4448 */
4449AJAX.registerOnload('functions.js', function() {
4450    PMA_init_slider();
4451});
4452
4453/**
4454 * Restores sliders to the state they were in before initialisation.
4455 */
4456AJAX.registerTeardown('functions.js', function() {
4457    $('div.pma_auto_slider').each(function() {
4458        var $this = $(this);
4459        $this.removeData();
4460        $this.parent().replaceWith($this);
4461        $this.parent().children('a').remove();
4462    });
4463});
4464
4465/**
4466 * Creates a message inside an object with a sliding effect
4467 *
4468 * @param msg    A string containing the text to display
4469 * @param $obj   a jQuery object containing the reference
4470 *                 to the element where to put the message
4471 *                 This is optional, if no element is
4472 *                 provided, one will be created below the
4473 *                 navigation links at the top of the page
4474 *
4475 * @return bool   True on success, false on failure
4476 */
4477function PMA_slidingMessage(msg, $obj)
4478{
4479    if (msg == undefined || msg.length == 0) {
4480        // Don't show an empty message
4481        return false;
4482    }
4483    if ($obj == undefined || ! $obj instanceof jQuery || $obj.length == 0) {
4484        // If the second argument was not supplied,
4485        // we might have to create a new DOM node.
4486        if ($('#PMA_slidingMessage').length == 0) {
4487            $('#page_content').prepend(
4488                '<span id="PMA_slidingMessage" '
4489                 + 'style="display: inline-block;"></span>'
4490            );
4491        }
4492        $obj = $('#PMA_slidingMessage');
4493    }
4494    if ($obj.has('div').length > 0) {
4495        // If there already is a message inside the
4496        // target object, we must get rid of it
4497        $obj
4498        .find('div')
4499        .first()
4500        .fadeOut(function () {
4501            $obj
4502            .children()
4503            .remove();
4504            $obj
4505            .append('<div style="display: none;">' + msg + '</div>')
4506            .animate({
4507                height: $obj.find('div').first().height()
4508            })
4509            .find('div')
4510            .first()
4511            .fadeIn();
4512        });
4513    } else {
4514        // Object does not already have a message
4515        // inside it, so we simply slide it down
4516        var h = $obj
4517                .width('100%')
4518                .html('<div style="display: none;">' + msg + '</div>')
4519                .find('div')
4520                .first()
4521                .height();
4522        $obj
4523        .find('div')
4524        .first()
4525        .css('height', 0)
4526        .show()
4527        .animate({
4528                height: h
4529            }, function() {
4530            // Set the height of the parent
4531            // to the height of the child
4532            $obj
4533            .height(
4534                $obj
4535                .find('div')
4536                .first()
4537                .height()
4538            );
4539        });
4540    }
4541    return true;
4542} // end PMA_slidingMessage()
4543
4544/**
4545 * Unbind all event handlers before tearing down a page
4546 */
4547AJAX.registerTeardown('functions.js', function() {
4548    $("#drop_tbl_anchor.ajax").die('click');
4549    $("#truncate_tbl_anchor.ajax").die('click');
4550});
4551/**
4552 * Attach Ajax event handlers for Drop Table.
4553 */
4554AJAX.registerOnload('functions.js', function() {
4555    $("#drop_tbl_anchor.ajax").live('click', function(event) {
4556        event.preventDefault();
4557        /**
4558         * @var question    String containing the question to be asked for c
4558onfirmation
4559         */
4560        var question = PMA_messages.strDropTableStrongWarning + ' ';
4561        question += $.sprintf(
4562            PMA_messages.strDoYouReally,
4563            'DROP TABLE ' + escapeHtml(PMA_commonParams.get('table'))
4564        );
4565
4566        $(this).PMA_confirm(question, $(this).attr('href'), function(url) {
4567
4568            var $msgbox = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']);
4569            $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function(data) {
4570                if (data.success == true) {
4571                    PMA_ajaxRemoveMessage($msgbox);
4572                    // Table deleted successfully, refresh both the frames
4573                    PMA_reloadNavigation();
4574                    PMA_commonParams.set('table', '');
4575                    PMA_commonActions.refreshMain(
4576                        PMA_commonParams.get('opendb_url'),
4577                        function () {
4578                            PMA_ajaxShowMessage(data.message);
4579                        }
4580                    );
4581                } else {
4582                    PMA_ajaxShowMessage(data.error, false);
4583                }
4584            }); // end $.get()
4585        }); // end $.PMA_confirm()
4586    }); //end of Drop Table Ajax action
4587
4588    $("#truncate_tbl_anchor.ajax").live('click', function(event) {
4589        event.preventDefault();
4590        /**
4591         * @var question    String containing the question to be asked for c
4591onfirmation
4592         */
4593        var question = PMA_messages.strTruncateTableStrongWarning + ' ';
4594        question += $.sprintf(
4595            PMA_messages.strDoYouReally,
4596            'TRUNCATE ' + escapeHtml(PMA_commonParams.get('table'))
4597        );
4598        $(this).PMA_confirm(question, $(this).attr('href'), function(url) {
4599            PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']);
4600            $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function(data) {
4601                if ($("#sqlqueryresults").length != 0) {
4602                    $("#sqlqueryresults").remove();
4603                }
4604                if ($("#result_query").length != 0) {
4605                    $("#result_query").remove();
4606                }
4607                if (data.success == true) {
4608                    PMA_ajaxShowMessage(data.message);
4609                    $("<div id='sqlqueryresults'></div>").prependTo("#page_content");
4610                    $("#sqlqueryresults").html(data.sql_query);
4611                } else {
4612                    PMA_ajaxShowMessage(data.error, false);
4613                }
4614            }); // end $.get()
4615        }); // end $.PMA_confirm()
4616    }); //end of Truncate Table Ajax action
4617}); // end of $() for Truncate Table
4618
4619/**
4620 * Attach CodeMirror2 editor to SQL edit area.
4621 */
4622AJAX.registerOnload('functions.js', function() {
4623    var $elm = $('#sqlquery');
4624    if ($elm.length > 0) {
4625        if (typeof CodeMirror != 'undefined') {
4626            // for codemirror
4627            codemirror_editor = CodeMirror.fromTextArea($elm[0], {
4628                lineNumbers: true,
4629                matchBrackets: true,
4630                indentUnit: 4,
4631                mode: "text/x-mysql",
4632                lineWrapping: true
4633            });
4634            codemirror_editor.focus();
4635            $(codemirror_editor.getWrapperElement()).bind(
4636                'keydown',
4637                catchKeypressesFromSqlTextboxes
4638            );
4639        } else {
4640            // without codemirror
4641            $elm.focus().bind('keydown', catchKeypressesFromSqlTextboxes);
4642        }
4643    }
4644});
4645AJAX.registerTeardown('functions.js', function() {
4646    if (codemirror_editor) {
4647        $('#sqlquery').text(codemirror_editor.getValue());
4648        codemirror_editor.toTextArea();
4649        codemirror_editor = false;
4650    }
4651});
4652
4653/**
4654 * jQuery plugin to cancel selection in HTML code.
4655 */
4656(function ($) {
4657    $.fn.noSelect = function (p) { //no select plugin by Paulo P.Marinas
4658        var prevent = (p == null) ? true : p;
4659        if (prevent) {
4660            return this.each(function () {
4661                if ($.browser.msie || $.browser.safari) {
4662                    $(this).bind('selectstart', function () {
4663                        return false;
4664                    });
4665                } else if ($.browser.mozilla) {
4666                    $(this).css('MozUserSelect', 'none');
4667                    $('body').trigger('focus');
4668                } else if ($.browser.opera) {
4669                    $(this).bind('mousedown', function () {
4670                        return false;
4671                    });
4672                } else {
4673                    $(this).attr('unselectable', 'on');
4674                }
4675            });
4676        } else {
4677            return this.each(function () {
4678                if ($.browser.msie || $.browser.safari) {
4679                    $(this).unbind('selectstart');
4680                } else if ($.browser.mozilla) {
4681                    $(this).css('MozUserSelect', 'inherit');
4682                } else if ($.browser.opera) {
4683                    $(this).unbind('mousedown');
4684                } else {
4685                    $(this).removeAttr('unselectable');
4686                }
4687            });
4688        }
4689    }; //end noSelect
4690})(jQuery);
4691
4692/**
4693 * jQuery plugin to correctly filter input fields by value, needed
4694 * because some nasty values may break selector syntax
4695 */
4696(function ($) {
4697    $.fn.filterByValue = function (value) {
4698        return this.filter(function () {
4699            return $(this).val() === value;
4700        });
4701    };
4702})(jQuery);
4703
4704/**
4705 * Create a jQuery UI tooltip
4706 *
4707 * @param $elements     jQuery object representing the elements
4708 * @param item          the item
4709 *                      (see http://api.jqueryui.com/tooltip/#option-items)
4710 * @param myContent     content of the tooltip
4711 * @param additionalOptions to override the default options
4712 *
4713 */
4714function PMA_tooltip($elements, item, myContent, additionalOptions)
4715{
4716    if ($('#no_hint').length > 0) {
4717        return;
4718    }
4719
4720    var defaultOptions = {
4721        content: myContent,
4722        items:  item,
4723        tooltipClass: "tooltip",
4724        track: true,
4725        show: false,
4726        hide: false
4727    };
4728
4729    $elements.tooltip($.extend(true, defaultOptions, additionalOptions));
4730}
4731
4732/**
4733 * Return value of a cell in a table.
4734 */
4735function PMA_getCellValue(td) {
4736    var $td = $(td);
4737    if ($td.is('.null')) {
4738        return '';
4739    } else if (! $td.is('.to_be_saved') && $td.data('original_data')) {
4740        return $td.data('original_data');
4741    } else {
4742        return $td.text();
4743    }
4744}
4745
4746/**
4747 * Unbind all event handlers before tearing down a page
4748 */
4749AJAX.registerTeardown('functions.js', function() {
4750    $('a.themeselect').die('click');
4751    $('.autosubmit').die('change');
4752    $('a.take_theme').unbind('click');
4753});
4754
4755AJAX.registerOnload('functions.js', function() {
4756    /**
4757     * Theme selector.
4758     */
4759    $('a.themeselect').live('click', function(e) {
4760        window.open(
4761            e.target,
4762            'themes',
4763            'left=10,top=20,width=510,height=350,scrollbars=yes,status=yes,resizable=yes'
4764            );
4765        return false;
4766    });
4767
4768    /**
4769     * Automatic form submission on change.
4770     */
4771    $('.autosubmit').live('change', function(e) {
4772        $(this).closest('form').submit();
4773    });
4774
4775    /**
4776     * Theme changer.
4777     */
4778    $('a.take_theme').click(function(e) {
4779        var what = this.name;
4780        if (window.opener && window.opener.document.forms['setTheme'].elements['set_theme']) {
4781            window.opener.document.forms['setTheme'].elements['set_theme'].value = what;
4782            window.opener.document.forms['setTheme'].submit();
4783            window.close();
4784            return false;
4785        }
4786        return true;
4787    });
4788});
4789
4790/**
4791 * Clear text selection
4792 */
4793function PMA_clearSelection() {
4794    if (document.selection && document.selection.empty) {
4795        document.selection.empty();
4796    } else if (window.getSelection) {
4797        var sel = window.getSelection();
4798        if (sel.empty) {
4799            sel.empty();
4800        }
4801        if (sel.removeAllRanges) {
4802            sel.removeAllRanges();
4803        }
4804    }
4805}
4806
4807/**
4808 * HTML escaping
4809 */
4810function escapeHtml(unsafe) {
4811    return unsafe
4812        .replace(/&/g, "&amp;")
4813        .replace(/</g, "&lt;")
4814        .replace(/>/g, "&gt;")
4815        .replace(/"/g, "&quot;")
4816        .replace(/'/g, "&#039;");
4817}
4818
4819/**
4820 * Print button
4821 */
4822function printPage()
4823{
4824    // Do print the page
4825    if (typeof(window.print) != 'undefined') {
4826        window.print();
4827    }
4828}
4829
4830/**
4831 * Unbind all event handlers before tearing down a page
4832 */
4833AJAX.registerTeardown('functions.js', function() {
4834    $('input#print').unbind('click');
4835    $('span a.create_view.ajax').die('click');
4836    $('#createViewDialog').find('input, select').die('keydown');
4837});
4838
4839AJAX.registerOnload('functions.js', function() {
4840    $('input#print').click(printPage);
4841    /**
4842     * Ajaxification for the "Create View" action
4843     */
4844    $('span a.create_view.ajax').live('click', function (e) {
4845        e.preventDefault();
4846        PMA_createViewDialog($(this));
4847    });
4848    /**
4849     * Attach Ajax event handlers for input fields in the editor
4850     * and used to submit the Ajax request when the ENTER key is pressed.
4851     */
4852    $('#createViewDialog').find('input, select').live('keydown', function (e) {
4853        if (e.which === 13) { // 13 is the ENTER key
4854            e.preventDefault();
4855            $(this).closest('.ui-dialog').find('.ui-button:first').click();
4856        }
4857    }); // end $.live()
4858});
4859
4860function PMA_createViewDialog($this)
4861{
4862    var $msg = PMA_ajaxShowMessage();
4863    var syntaxHighlighter = null;
4864    $.get($this.attr('href') + '&ajax_request=1', function (data) {
4865        if (data.success == true) {
4866            PMA_ajaxRemoveMessage($msg);
4867            var buttonOptions = {};
4868            buttonOptions[PMA_messages['strGo']] = function () {
4869                if (typeof CodeMirror !== 'undefined') {
4870                    syntaxHighlighter.save();
4871                }
4872                $msg = PMA_ajaxShowMessage();
4873                $.get('view_create.php', $('#createViewDialog').find('form').serialize(), function (data) {
4874                    PMA_ajaxRemoveMessage($msg);
4875                    if (data.success === true) {
4876                        $('#createViewDialog').dialog("close");
4877                        $('#result_query').html(data.message);
4878                        PMA_reloadNavigation();
4879                    } else {
4880                        PMA_ajaxShowMessage(data.error, false);
4881                    }
4882                });
4883            };
4884            buttonOptions[PMA_messages['strClose']] = function () {
4885                $(this).dialog("close");
4886            };
4887            var $dialog = $('<div/>').attr('id', 'createViewDialog').append(data.message).dialog({
4888                width: 500,
4889                minWidth: 300,
4890                maxWidth: 620,
4891                modal: true,
4892                buttons: buttonOptions,
4893                title: $('legend', $(data.message)).html(),
4894                close: function () {
4895                    $(this).remove();
4896                }
4897            });
4898            $dialog.find('legend').remove();
4899            // Attach syntax highlited editor
4900            if (typeof CodeMirror !== 'undefined') {
4901                var $elm = $dialog.find('textarea');
4902                var opts = {lineNumbers: true, matchBrackets: true, indentUnit: 4, mode: "text/x-mysql"};
4903                syntaxHighlighter = CodeMirror.fromTextArea($elm[0], opts);
4904            }
4905            $('input:visible[type=text]', $dialog).first().focus();
4906        } else {
4907            PMA_ajaxShowMessage(data.error);
4908        }
4909    });
4910}
4911
4912/**
4913 * Makes the breadcrumbs and the menu bar float at the top of the viewport
4914 */
4915$(function () {
4916    if ($("#floating_menubar").length && $('#PMA_disable_floating_menubar').length == 0) {
4917        var left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
4918        $("#floating_menubar")
4919            .css('margin-' + left, $('#pma_navigation').width() + $('#pma_navigation_resizer').width())
4920            .css(left, 0)
4921            .css({
4922                'position': 'fixed',
4923                'top': 0,
4924                'width': '100%',
4925                'z-index': 500
4926            })
4927            .append($('#serverinfo'))
4928            .append($('#topmenucontainer'));
4929        // Allow the DOM to render, then adjust the padding on the body
4930        setTimeout(function () {
4931            $('body').css(
4932                'padding-top',
4933                $('#floating_menubar').outerHeight(true)
4934            );
4935            $('#topmenu').menuResizer('resize');
4936        }, 4);
4937    }
4938});
4939
4940/**
4941 * Scrolls the page to the top if clicking the serverinfo bar
4942 */
4943$(function () {
4944    $(document).delegate("#serverinfo, #goto_pagetop", "click", function (event) {
4945        event.preventDefault();
4946        $('html, body').animate({scrollTop: 0}, 'fast');
4947    });
4948});
4949
4950/**
4951 * Watches checkboxes in a form to set the checkall box accordingly
4952 */
4953var checkboxes_sel = "input.checkall:checkbox:enabled";
4954$(checkboxes_sel).live("change", function () {
4955    var $form = $(this.form);
4956    // total number of checkboxes in current form
4957    var total_boxes = $form.find(checkboxes_sel).length;
4958    // number of checkboxes checked in current form
4959    var checked_boxes = $form.find(checkboxes_sel + ":checked").length;
4960    var $checkall = $form.find("input#checkall");
4961    if (total_boxes == checked_boxes) {
4962        $checkall.prop({checked: true, indeterminate: false});
4963    }
4964    else if (checked_boxes > 0) {
4965        $checkall.prop({checked: true, indeterminate: true});
4966    }
4967    else {
4968        $checkall.prop({checked: false, indeterminate: false});
4969    }
4970});
4971$("input#checkall").live("change", function() {
4972    var is_checked = $(this).is(":checked");
4973    $(this.form).find(checkboxes_sel).prop("checked", is_checked)
4974    .parents("tr").toggleClass("marked", is_checked);
4975});
4976
4977/**
4978 * Toggles row colors of a set of 'tr' elements starting from a given element
4979 *
4980 * @param $start Starting element
4981 */
4982function toggleRowColors($start)
4983{
4984    for (var $curr_row = $start; $curr_row.length > 0; $curr_row = $curr_row.next()) {
4985        if ($curr_row.hasClass('odd')) {
4986            $curr_row.removeClass('odd').addClass('even');
4987        } else if ($curr_row.hasClass('even')) {
4988            $curr_row.removeClass('even').addClass('odd');
4989        }
4990    }
4991}
4992
4993/**
4994 * Formats a byte number to human-readable form
4995 *
4996 * @param bytes the bytes to format
4997 * @param optional subdecimals the number of digits after the point
4998 * @param optional pointchar the char to use as decimal point
4999 */
5000function formatBytes(bytes, subdecimals, pointchar) {
5001    if (!subdecimals) {
5002        var subdecimals = 0;
5003    }
5004    if (!pointchar) {
5005        var pointchar = '.';
5006    }
5007    var units = ['B', 'KiB', 'MiB', 'GiB'];
5008    for (var i = 0; bytes > 1024 && i < units.length; i++) {
5009        bytes /= 1024;
5010    }
5011    var factor = Math.pow(10, subdecimals);
5012    bytes = Math.round(bytes * factor) / factor;
5013    bytes = bytes.toString().split('.').join(pointchar);
5014    return bytes + ' ' + units[i];
5015}
5016
5017AJAX.registerOnload('functions.js', function () {
5018    /**
5019     * Opens pma more themes link in themes browser, in new window instead of popup
5020     * This way, we don't break HTML validity
5021     */
5022    $("a._blank").prop("target", "_blank");
5023    /**
5024     * Reveal the login form to users with JS enabled
5025     * and focus the appropriate input field
5026     */
5027    var $loginform = $('#loginform');
5028    if ($loginform.length) {
5029        $loginform.find('.js-show').show();
5030        if ($('#input_username').val()) {
5031            $('#input_password').focus();
5032        } else {
5033            $('#input_username').focus();
5034        }
5035    }
5036});
5037
5038/**
5039 * When user gets an ajax session expiry message, we show a login link
5040 */
5041$('a.login-link').live('click', function(e) {
5042    e.preventDefault();
5043    window.location.reload(true);
5044});
5045
5046/**
5047 * Formats timestamp for display
5048 */
5049function PMA_formatDateTime(date, seconds) {
5050    var result = $.datepicker.formatDate('yy-mm-dd', date);
5051    var timefmt = 'HH:mm';
5052    if (seconds) {
5053        timefmt = 'HH:mm:ss';
5054    }
5055    return result + ' ' + $.datepicker.formatTime(
5056        timefmt, {
5057            hour: date.getHours(),
5058            minute: date.getMinutes(),
5059            second: date.getSeconds()
5060        }
5061    );
5062}
5063;
5064
5065/* vim: set expandtab sw=4 ts=4 sts=4: */
5066/**
5067 * function used in or for navigation panel
5068 *
5069 * @package phpMyAdmin-Navigation
5070 */
5071
5072/**
5073 * Executed on page load
5074 */
5075$(function() {
5076    if (! $('#pma_navigation').length) {
5077        // Don't bother running any code if the navigation is not even on the page
5078        return;
5079    }
5080
5081    // Do not let the page reload on submitting the fast filter
5082    $(document).on('submit', '.fast_filter', function(event) {
5083        event.preventDefault();
5084    });
5085
5086    // Fire up the resize handlers
5087    new ResizeHandler();
5088
5089    /**
5090     * opens/closes (hides/shows) tree elements
5091     * loads data via ajax
5092     */
5093    $('#pma_navigation_tree a.expander').live('click', function(event) {
5094        event.preventDefault();
5095        event.stopImmediatePropagation();
5096        var $icon = $(this).find('img');
5097        if ($icon.is('.ic_b_plus')) {
5098            expandTreeNode($(this));
5099        } else {
5100            collapseTreeNode($(this));
5101        }
5102    });
5103
5104    /**
5105     * Register event handler for click on the reload
5106     * navigation icon at the top of the panel
5107     */
5108    $('#pma_navigation_reload').live('click', function (event) {
5109        event.preventDefault();
5110        $('#pma_navigation .throbber')
5111            .first()
5112            .css('visibility', 'visible');
5113        PMA_reloadNavigation();
5114    });
5115
5116    /**
5117     * Bind all "fast filter" events
5118     */
5119    $('#pma_navigation_tree li.fast_filter span')
5120        .live('click', PMA_fastFilter.events.clear);
5121    $('#pma_navigation_tree li.fast_filter input.searchClause')
5122        .live('focus', PMA_fastFilter.events.focus)
5123        .live('blur', PMA_fastFilter.events.blur)
5124        .live('keyup', PMA_fastFilter.events.keyup);
5125
5126    /**
5127     * Ajax handler for pagination
5128     */
5129    $('#pma_navigation_tree div.pageselector a.ajax').live('click', function (event) {
5130        event.preventDefault();
5131        PMA_navigationTreePagination($(this));
5132    });
5133
5134    /**
5135     * Node highlighting
5136     */
5137    $('#pma_navigation_tree.highlight li:not(.fast_filter)').live(
5138        'mouseover',
5139        function () {
5140            if ($('li:visible', this).length == 0) {
5141                $(this).addClass('activePointer');
5142            }
5143        }
5144    );
5145    $('#pma_navigation_tree.highlight li:not(.fast_filter)').live(
5146        'mouseout',
5147        function () {
5148            $(this).removeClass('activePointer');
5149        }
5150    );
5151
5152    /**
5153     * Jump to recent table
5154     */
5155    $('#recentTable').live('change', function() {
5156        if (this.value != '') {
5157            var arr = jQuery.parseJSON(this.value);
5158            var $form = $(this).closest('form');
5159            $form.find('input[name=db]').val(arr['db']);
5160            $form.find('input[name=table]').val(arr['table']);
5161            $form.submit();
5162        }
5163    });
5164
5165    /** Create a Routine, Trigger or Event */
5166    $('li.new_procedure a.ajax, li.new_function a.ajax').live('click', function (event) {
5167        event.preventDefault();
5168        var dialog = new RTE.object('routine');
5169        dialog.editorDialog(1, $(this))
5170    });
5171    $('li.new_trigger a.ajax').live('click', function (event) {
5172        event.preventDefault();
5173        var dialog = new RTE.object('trigger');
5174        dialog.editorDialog(1, $(this))
5175    });
5176    $('li.new_event a.ajax').live('click', function (event) {
5177        event.preventDefault();
5178        var dialog = new RTE.object('event');
5179        dialog.editorDialog(1, $(this))
5180    });
5181
5182    /** Edit Routines, Triggers and Events */
5183    $('li.procedure > a.ajax, li.function > a.ajax').live('click', function (event) {
5184        event.preventDefault();
5185        var dialog = new RTE.object('routine');
5186        dialog.editorDialog(0, $(this))
5187    });
5188    $('li.trigger > a.ajax').live('click', function (event) {
5189        event.preventDefault();
5190        var dialog = new RTE.object('trigger');
5191        dialog.editorDialog(0, $(this))
5192    });
5193    $('li.event > a.ajax').live('click', function (event) {
5194        event.preventDefault();
5195        var dialog = new RTE.object('event');
5196        dialog.editorDialog(0, $(this))
5197    });
5198
5199    /** Export Routines, Triggers and Events */
5200    $('li.procedure a.ajax img, li.function a.ajax img, li.trigger a.ajax img, li.event a.ajax img').live('click', function (event) {
5201        event.preventDefault();
5202        var dialog = new RTE.object();
5203        dialog.exportDialog($(this).parent())
5204    });
5205
5206    /** New index */
5207    $('li.new_index a.ajax').live('click', function (event) {
5208        event.preventDefault();
5209        var url = $(this).attr('href').substr(
5210            $(this).attr('href').indexOf('?') + 1
5211        ) + '&ajax_request=true';
5212        var title = PMA_messages['strAddIndex'];
5213        indexEditorDialog(url, title);
5214    });
5215
5216    /** Edit index */
5217    $('li.index a.ajax').live('click', function (event) {
5218        event.preventDefault();
5219        var url = $(this).attr('href').substr(
5220            $(this).attr('href').indexOf('?') + 1
5221        ) + '&ajax_request=true';
5222        var title = PMA_messages['strEditIndex'];
5223        indexEditorDialog(url, title);
5224    });
5225
5226    /** New view */
5227    $('li.new_view a.ajax').live('click', function (event) {
5228        event.preventDefault();
5229        PMA_createViewDialog($(this));
5230    });
5231
5232    PMA_showCurrentNavigation();
5233});
5234
5235/**
5236 * Expands a node in navigation tree.
5237 *
5238 * @param $expandElem expander
5239 * @param callback    callback function
5240 *
5241 * @returns void
5242 */
5243function expandTreeNode($expandElem, callback)
5244{
5245    var $children = $expandElem.closest('li').children('div.list_container');
5246    var $icon = $expandElem.find('img');
5247    if ($expandElem.hasClass('loaded')) {
5248        if ($icon.is('.ic_b_plus')) {
5249            $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
5250            $children.show('fast');
5251        }
5252        if (callback && typeof callback == 'function') {
5253            callback.call();
5254        }
5255    } else {
5256        var $throbber = $('#pma_navigation .throbber')
5257            .first()
5258            .clone()
5259            .css('visibility', 'visible')
5260            .click(false);
5261        $icon.hide();
5262        $throbber.insertBefore($icon);
5263
5264        loadChildNodes($expandElem, function(data) {
5265            if (data.success === true) {
5266                var $destination = $expandElem.closest('li');
5267                $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
5268                $destination
5269                    .children('div.list_container')
5270                    .show('fast');
5271                if ($destination.find('ul > li').length == 1) {
5272                    $destination.find('ul > li')
5273                        .find('a.expander.container')
5274                        .click();
5275                }
5276                if (callback && typeof callback == 'function') {
5277                   callback.call();
5278                }
5279            } else {
5280                PMA_ajaxShowMessage(data.error, false);
5281            }
5282            $icon.show();
5283            $throbber.remove();
5284        });
5285    }
5286    $expandElem.blur();
5287}
5288
5289/**
5290 * Auto-scrolls the newly chosen database
5291 *
5292 * @param  object   $element    The element to set to view
5293 * @param  object   $container  The container srollable element
5294 *
5295 */
5296function scrollToView($element, $container) {
5297    var elementOffset = $element.offset(),
5298        containerOffset = $container.offset();
5299    if(elementOffset != undefined && containerOffset != undefined) {
5300        var pushToOffset = elementOffset.top - containerOffset.top + $container.scrollTop();
5301        $('#pma_navigation_tree_content').stop().animate({
5302            scrollTop: pushToOffset
5303        });
5304    }
5305}
5306
5307/**
5308 * Collapses a node in navigation tree.
5309 *
5310 * @param $expandElem expander
5311 *
5312 * @returns void
5313 */
5314function collapseTreeNode($expandElem) {
5315    var $children = $expandElem.closest('li').children('div.list_container');
5316    var $icon = $expandElem.find('img');
5317    if ($expandElem.hasClass('loaded')) {
5318        if ($icon.is('.ic_b_minus')) {
5319            $icon.removeClass('ic_b_minus').addClass('ic_b_plus');
5320            $children.hide('fast');
5321        }
5322    }
5323    $expandElem.blur();
5324}
5325
5326/**
5327 * Loads child items of a node and executes a given callback
5328 *
5329 * @param $expandElem expander
5330 * @param callback    callback function
5331 *
5332 * @returns void
5333 */
5334function loadChildNodes($expandElem, callback) {
5335    var $destination = $expandElem.closest('li');
5336
5337    var searchClause = PMA_fastFilter.getSearchClause();
5338    var searchClause2 = PMA_fastFilter.getSearchClause2($expandElem);
5339
5340    var params = {
5341        aPath: $expandElem.find('span.aPath').text(),
5342        vPath: $expandElem.find('span.vPath').text(),
5343        pos: $expandElem.find('span.pos').text(),
5344        pos2_name: $expandElem.find('span.pos2_name').text(),
5345        pos2_value: $expandElem.find('span.pos2_value').text(),
5346        searchClause: searchClause,
5347        searchClause2: searchClause2
5348    };
5349
5350    var url = $('#pma_navigation').find('a.navigation_url').attr('href');
5351    $.get(url, params, function (data) {
5352        if (data.success === true) {
5353            $expandElem.addClass('loaded');
5354            $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there
5355            $destination.append(data.message);
5356            if (callback && typeof callback == 'function') {
5357               callback(data);
5358            }
5359        }
5360    });
5361}
5362
5363/**
5364 * Expand the navigation and highlight the current database or table/view
5365 *
5366 * @returns void
5367 */
5368function PMA_showCurrentNavigation()
5369{
5370   var db = PMA_commonParams.get('db');
5371   var table = PMA_commonParams.get('table');
5372   $('#pma_navigation_tree')
5373       .find('li.selected')
5374       .removeClass('selected');
5375   if (db && table) { // if we are at the table/view level
5376       // open the database in the tree
5377       var $dbItem = highlightLoadedItem(
5378           $('#pma_navigation_tree > div'), db, 'database', false, false
5379       );
5380       if ($dbItem) {
5381           // open the table in the tree and select it
5382           var $expander = $dbItem.children('div:first').children('a.expander');
5383           // if not loaded or loaded but collapsed
5384           if (! $expander.hasClass('loaded')
5385               || $expander.find('img').is('.ic_b_plus')
5386           ) {
5387               expandTreeNode($expander, function() {
5388                   loadAndHighlightTableOrView($dbItem, table);
5389               });
5390           } else {
5391               loadAndHighlightTableOrView($dbItem, table);
5392           }
5393       }
5394   } else if (db) { // if we are at the database level
5395       // open in the tree and select the database
5396       highlightLoadedItem(
5397           $('#pma_navigation_tree > div'), db, 'database', true, true
5398       );
5399   }
5400
5401   function highlightLoadedItem($container, name, clazz, doSelect, doOpen) {
5402       var ret = false;
5403       $container.children('ul').children('li').each(function() {
5404           var $li = $(this);
5405           // this is a navigation group, recurse
5406           if ($li.is('.navGroup')) {
5407               var $container = $li.children('div.list_container');
5408               var $childRet = highlightLoadedItem(
5409                   $container, name, clazz, doSelect, doOpen
5410               );
5411               if ($childRet) {
5412                   ret = $childRet;
5413                   return false;
5414               }
5415           } else { // this is a real navigation item
5416               // name and class matches
5417               if ($li.is('.' + clazz) && $li.children('a').text() == name) {
5418                   if (doSelect) {
5419                       $li.addClass('selected');
5420                       if (! doOpen) { // if the node will be opened no point scrolling now
5421                           scrollToView($li, $('#pma_navigation_tree_content'));
5422                       }
5423                   }
5424                   if (doOpen) {
5425                       var $expander = $li.find('div:first').children('a.expander');
5426                       if ($expander.length > 0) {
5427                           expandTreeNode($expander, function() {
5428                               scrollToView($li, $('#pma_navigation_tree_content'));
5429                           });
5430                       }
5431                   }
5432                   // taverse up and expand and parent navigation groups
5433                   $li.parents('.navGroup').each(function() {
5434                       $cont = $(this).children('div.list_container');
5435                       if (! $cont.is(':visible')) {
5436                           $(this)
5437                               .children('div:first')
5438                               .children('a.expander')
5439                               .click();
5440                       }
5441                   });
5442                   ret = $li;
5443                   return false;
5444               }
5445           }
5446       });
5447       return ret;
5448   }
5449
5450   function loadAndHighlightTableOrView($dbItem, table) {
5451       var $container = $dbItem.children('div.list_container');
5452       var $tableContainer = $container
5453           .children('ul')
5454           .children('li.tableContainer');
5455       var $viewContainer = $container
5456           .children('ul')
5457           .children('li.viewContainer');
5458
5459       if ($tableContainer.length > 0) {
5460           var $expander = $tableContainer
5461               .children('div:first')
5462               .children('a.expander');
5463
5464           if (! $expander.hasClass('loaded') ) {
5465               loadChildNodes($expander, function(data) {
5466                   highlightTableOrView($tableContainer, $viewContainer, table);
5467               });
5468           } else {
5469               highlightTableOrView($tableContainer, $viewContainer, table);
5470           }
5471       } else if ($viewContainer.length > 0) {
5472           highlightView($viewContainer, table);
5473       } else {
5474           // no containers, highlight the item
5475           highlightLoadedItem($container, table, 'table', true, false);
5476       }
5477   }
5478
5479   function highlightTableOrView($tableContainer, $viewContainer, table)
5480   {
5481       if (isItemInContainer($tableContainer, table, 'table')) {
5482           var $expander = $tableContainer
5483               .children('div:first')
5484               .children('a.expander');
5485           if ($expander.find('img').is('.ic_b_plus')) {
5486               expandTreeNode($expander);
5487           }
5488           highlightLoadedItem(
5489               $tableContainer.children('div.list_container'),
5490               table, 'table', true, false
5491           );
5492       } else if ($viewContainer.length > 0) {
5493           highlightView($viewContainer, table);
5494       }
5495   }
5496
5497   function isItemInContainer($container, name, clazz)
5498   {
5499       $items = $container.find('li.' + clazz);
5500       var found = false;
5501       $items.each(function() {
5502           if ($(this).children('a').text() == name) {
5503               found = true;
5504               return false;
5505           }
5506       });
5507       return found;
5508   }
5509
5510   function highlightView($viewContainer, view) {
5511       var $expander = $viewContainer
5512           .children('div:first')
5513           .children('a.expander');
5514       if (! $expander.hasClass('loaded')
5515           || $expander.find('img').is('.ic_b_plus')
5516       ) {
5517           expandTreeNode($expander, function() {
5518               highlightLoadedItem(
5519                   $viewContainer.children('div.list_container'),
5520                   view, 'view', true, false
5521               );
5522           });
5523       } else {
5524           highlightLoadedItem(
5525               $viewContainer.children('div.list_container'),
5526               view, 'view', true, false
5527           );
5528       }
5529   }
5530}
5531
5532/**
5533 * Reloads the whole navigation tree while preserving its state
5534 *
5535 * @param  function     the callback function
5536 * @return void
5537 */
5538function PMA_reloadNavigation(callback) {
5539    var params = {
5540        reload: true,
5541        pos: $('#pma_navigation_tree').find('a.expander:first > span.pos').text()
5542    };
5543    // Traverse the navigation tree backwards to generate all the actual
5544    // and virtual paths, as well as the positions in the pagination at
5545    // various levels, if necessary.
5546    var count = 0;
5547    $('#pma_navigation_tree').find('a.expander:visible').each(function () {
5548        if ($(this).find('img').is('.ic_b_minus')
5549            && $(this).closest('li').find('div.list_container .ic_b_minus').length == 0
5550        ) {
5551            params['n' + count + '_aPath'] = $(this).find('span.aPath').text();
5552            params['n' + count + '_vPath'] = $(this).find('span.vPath').text();
5553
5554            var pos2_name = $(this).find('span.pos2_name').text();
5555            if (! pos2_name) {
5556                pos2_name = $(this)
5557                    .parent()
5558                    .parent()
5559                    .find('span.pos2_name:last')
5560                    .text();
5561            }
5562            var pos2_value = $(this).find('span.pos2_value').text();
5563            if (! pos2_value) {
5564                pos2_value = $(this)
5565                    .parent()
5566                    .parent()
5567                    .find('span.pos2_value:last')
5568                    .text();
5569            }
5570
5571            params['n' + count + '_pos2_name'] = pos2_name;
5572            params['n' + count + '_pos2_value'] = pos2_value;
5573
5574            params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text();
5575            params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text();
5576            count++;
5577        }
5578    });
5579    var url = $('#pma_navigation').find('a.navigation_url').attr('href');
5580    $.post(url, params, function (data) {
5581        // Hide throbber if it's visible
5582        $('#pma_navigation .throbber')
5583            .first()
5584            .css('visibility', 'hidden');
5585        if (data.success) {
5586            $('#pma_navigation_tree').html(data.message).children('div').show();
5587            PMA_showCurrentNavigation();
5588            // Fire the callback, if any
5589            if (typeof callback === 'function') {
5590                callback.call();
5591            }
5592        } else {
5593            PMA_ajaxShowMessage(data.error);
5594        }
5595    });
5596}
5597
5598/**
5599 * Handles any requests to change the page in a branch of a tree
5600 *
5601 * This can be called from link click or select change event handlers
5602 *
5603 * @param object $this A jQuery object that points to the element that
5604 * initiated the action of changing the page
5605 *
5606 * @return void
5607 */
5608function PMA_navigationTreePagination($this)
5609{
5610    var $msgbox = PMA_ajaxShowMessage();
5611    var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
5612    if ($this[0].tagName == 'A') {
5613        var url = $this.attr('href');
5614        var params = 'ajax_request=true';
5615    } else { // tagName == 'SELECT'
5616        var url = 'navigation.php';
5617        var params = $this.closest("form").serialize() + '&ajax_request=true';
5618    }
5619    var searchClause = PMA_fastFilter.getSearchClause();
5620    if (searchClause) {
5621        params += '&searchClause=' + encodeURIComponent(searchClause);
5622    }
5623    if (isDbSelector) {
5624        params += '&full=true';
5625    } else {
5626        var searchClause2 = PMA_fastFilter.getSearchClause2($this);
5627        if (searchClause2) {
5628            params += '&searchClause2=' + encodeURIComponent(searchClause2);
5629        }
5630    }
5631    $.post(url, params, function (data) {
5632        PMA_ajaxRemoveMessage($msgbox);
5633        if (data.success) {
5634            if (isDbSelector) {
5635                var val = PMA_fastFilter.getSearchClause();
5636                $('#pma_navigation_tree')
5637                    .html(data.message)
5638                    .children('div')
5639                    .show();
5640                if (val) {
5641                    $('#pma_navigation_tree')
5642                        .find('li.fast_filter input.searchClause')
5643                        .val(val);
5644                }
5645            } else {
5646                var $parent = $this.closest('div.list_container').parent();
5647                var val = PMA_fastFilter.getSearchClause2($this);
5648                $this.closest('div.list_container').html(
5649                    $(data.message).children().show()
5650                );
5651                if (val) {
5652                    $parent.find('li.fast_filter input.searchClause').val(val);
5653                }
5654                $parent.find('span.pos2_value:first').text(
5655                    $parent.find('span.pos2_value:last').text()
5656                );
5657                $parent.find('span.pos3_value:first').text(
5658                    $parent.find('span.pos3_value:last').text()
5659                );
5660            }
5661        } else {
5662            PMA_ajaxShowMessage(data.error);
5663        }
5664    });
5665}
5666
5667/**
5668 * @var ResizeHandler Custom object that manages the resizing of the navigation
5669 *
5670 * XXX: Must only be ever instanciated once
5671 * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler'
5672 */
5673var ResizeHandler = function () {
5674    /**
5675     * Whether the user has initiated a resize operation
5676     */
5677    this.active = false;
5678    /**
5679     * @var int panel_width Used by the collapser to know where to go
5680     *                      back to when uncollapsing the panel
5681     */
5682    this.panel_width = 0;
5683    /**
5684     * @var string left Used to provide support for RTL languages
5685     */
5686    this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
5687    /**
5688     * Adjusts the width of the navigation panel to the specified value
5689     *
5690     * @param int pos Navigation width in pixels
5691     *
5692     * @return void
5693     */
5694    this.setWidth = function (pos) {
5695        var $resizer = $('#pma_navigation_resizer');
5696        var resizer_width = $resizer.width();
5697        var $collapser = $('#pma_navigation_collapser');
5698        $('#pma_navigation').width(pos);
5699        $('body').css('margin-' + this.left, pos + 'px');
5700        $("#floating_menubar")
5701            .css('margin-' + this.left, (pos + resizer_width) + 'px');
5702        $resizer.css(this.left, pos + 'px');
5703        if (pos === 0) {
5704            $collapser
5705                .css(this.left, pos + resizer_width)
5706                .html(this.getSymbol(pos))
5707                .prop('title', PMA_messages['strShowPanel']);
5708        } else {
5709            $collapser
5710                .css(this.left, pos)
5711                .html(this.getSymbol(pos))
5712                .prop('title', PMA_messages['strHidePanel']);
5713        }
5714        setTimeout(function (){
5715            $(window).trigger('resize');
5716        }, 4);
5717    };
5718    /**
5719     * Returns the horizontal position of the mouse,
5720     * relative to the outer side of the navigation panel
5721     *
5722     * @param int pos Navigation width in pixels
5723     *
5724     * @return void
5725     */
5726    this.getPos = function (event) {
5727        var pos = event.pageX;
5728        var windowWidth = $(window).width();
5729        if (this.left != 'left') {
5730            pos = windowWidth - event.pageX;
5731        }
5732        if (pos < 0) {
5733            pos = 0;
5734        } else if (pos + 100 >= windowWidth) {
5735            pos = windowWidth - 100;
5736        } else {
5737            this.panel_width = 0;
5738        }
5739        return pos;
5740    };
5741    /**
5742     * Returns the HTML code for the arrow symbol used in the collapser
5743     *
5744     * @param int width The width of the panel
5745     *
5746     * @return string
5747     */
5748    this.getSymbol = function (width) {
5749        if (this.left == 'left') {
5750            if (width == 0) {
5751                return '&rarr;';
5752            } else {
5753                return '&larr;';
5754            }
5755        } else {
5756            if (width == 0) {
5757                return '&larr;';
5758            } else {
5759                return '&rarr;';
5760            }
5761        }
5762    };
5763    /**
5764     * Event handler for initiating a resize of the panel
5765     *
5766     * @param object e Event data (contains a reference to resizeHandler)
5767     *
5768     * @return void
5769     */
5770    this.mousedown = function (event) {
5771        event.preventDefault();
5772        event.data.resize_handler.active = true;
5773        $('body').css('cursor', 'col-resize');
5774    };
5775    /**
5776     * Event handler for terminating a resize of the panel
5777     *
5778     * @param object e Event data (contains a reference to resizeHandler)
5779     *
5780     * @return void
5781     */
5782    this.mouseup = function (event) {
5783        if (event.data.resize_handler.active) {
5784            event.data.resize_handler.active = false;
5785            $('body').css('cursor', '');
5786            $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
5787            $('#topmenu').menuResizer('resize');
5788        }
5789    };
5790    /**
5791     * Event handler for updating the panel during a resize operation
5792     *
5793     * @param object e Event data (contains a reference to resizeHandler)
5794     *
5795     * @return void
5796     */
5797    this.mousemove = function (event) {
5798        if (event.data && event.data.resize_handler && event.data.resize_handler.active) {
5799            event.preventDefault();
5800            var pos = event.data.resize_handler.getPos(event);
5801            event.data.resize_handler.setWidth(pos);
5802        }
5803    };
5804    /**
5805     * Event handler for collapsing the panel
5806     *
5807     * @param object e Event data (contains a reference to resizeHandler)
5808     *
5809     * @return void
5810     */
5811    this.collapse = function (event) {
5812        event.preventDefault();
5813        event.data.active = false;
5814        var panel_width = event.data.resize_handler.panel_width;
5815        var width = $('#pma_navigation').width();
5816        if (width === 0 && panel_width === 0) {
5817            panel_width = 240;
5818        }
5819        event.data.resize_handler.setWidth(panel_width);
5820        event.data.resize_handler.panel_width = width;
5821    };
5822    /**
5823     * Even thandler for resizing the navigation tree height on window resize
5824     *
5825     * @return void
5826     */
5827    this.treeResize = function (event) {
5828        var $nav        = $("#pma_navigation"),
5829            $nav_tree   = $("#pma_navigation_tree"),
5830            $nav_header = $("#pma_navigation_header"),
5831            $nav_tree_content = $("#pma_navigation_tree_content");
5832        $nav_tree.height($nav.height() - $nav_header.height());
5833        $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top);
5834    };
5835    /* Initialisation section begins here */
5836    if ($.cookie('pma_navi_width')) {
5837        // If we have a cookie, set the width of the panel to its value
5838        var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0);
5839        this.setWidth(pos);
5840        $('#topmenu').menuResizer('resize');
5841    }
5842    // Register the events for the resizer and the collapser
5843    $('#pma_navigation_resizer')
5844        .live('mousedown', {'resize_handler':this}, this.mousedown);
5845    $(document)
5846        .bind('mouseup', {'resize_handler':this}, this.mouseup)
5847        .bind('mousemove', {'resize_handler':this}, $.throttle(this.mousemove, 4));
5848    var $collapser = $('#pma_navigation_collapser');
5849    $collapser.live('click', {'resize_handler':this}, this.collapse);
5850    // Add the correct arrow symbol to the collapser
5851    $collapser.html(this.getSymbol($('#pma_navigation').width()));
5852    // Fix navigation tree height
5853    $(window).on('resize', this.treeResize);
5854    // need to call this now and then, browser might decide
5855    // to show/hide horizontal scrollbars depending on page content width
5856    setInterval(this.treeResize, 2000);
5857}; // End of ResizeHandler
5858
5859/**
5860 * @var object PMA_fastFilter Handles the functionality that allows filtering
5861 *                            of the items in a branch of the navigation tree
5862 */
5863var PMA_fastFilter = {
5864    /**
5865     * Construct for the asynchronous fast filter functionality
5866     *
5867     * @param object $this        A jQuery object pointing to the list container
5868     *                            which is the nearest parent of the fast filter
5869     * @param string searchClause The query string for the filter
5870     *
5871     * @return new PMA_fastFilter.filter object
5872     */
5873    filter: function ($this, searchClause) {
5874        /**
5875         * @var object $this A jQuery object pointing to the list container
5876         *                   which is the nearest parent of the fast filter
5877         */
5878        this.$this = $this;
5879        /**
5880         * @var bool searchClause The query string for the filter
5881         */
5882        this.searchClause = searchClause;
5883        /**
5884         * @var object $clone A clone of the original contents
5885         *                    of the navigation branch before
5886         *                    the fast filter was applied
5887         */
5888        this.$clone = $this.clone();
5889        /**
5890         * @var bool swapped Whether the user clicked on the "N other results" link
5891         */
5892        this.swapped = false;
5893        /**
5894         * @var object xhr A reference to the ajax request that is currently running
5895         */
5896        this.xhr = null;
5897        /**
5898         * @var int timeout Used to delay the request for asynchronous search
5899         */
5900        this.timeout = null;
5901
5902        var $filterInput = $this.find('li.fast_filter input.searchClause');
5903        if (   $filterInput.length != 0
5904            && $filterInput.val() != ''
5905            && $filterInput.val() != $filterInput[0].defaultValue
5906        ) {
5907            this.request();
5908        }
5909    },
5910    /**
5911     * Gets the query string from the database fast filter form
5912     *
5913     * @return string
5914     */
5915    getSearchClause: function () {
5916        var retval = '';
5917        var $input = $('#pma_navigation_tree')
5918            .find('li.fast_filter.db_fast_filter input.searchClause');
5919        if ($input.length && $input.val() != $input[0].defaultValue) {
5920            retval = $input.val();
5921        }
5922        return retval;
5923    },
5924    /**
5925     * Gets the query string from a second level item's fast filter form
5926     * The retrieval is done by trasversing the navigation tree backwards
5927     *
5928     * @return string
5929     */
5930    getSearchClause2: function ($this) {
5931        var $filterContainer = $this.closest('div.list_container');
5932        var $filterInput = $([]);
5933        while (1) {
5934            if ($filterContainer.find('li.fast_filter:not(.db_fast_filter) input.searchClause').length != 0) {
5935                $filterInput = $filterContainer.find('li.fast_filter:not(.db_fast_filter) input.searchClause');
5936                break;
5937            } else if (! $filterContainer.is('div.list_container')) {
5938                break;
5939            }
5940            $filterContainer = $filterContainer
5941                .parent()
5942                .closest('div.list_container');
5943        }
5944        var searchClause2 = '';
5945        if ($filterInput.length != 0
5946            && $filterInput.first().val() != $filterInput[0].defaultValue
5947        ) {
5948            searchClause2 = $filterInput.val();
5949        }
5950        return searchClause2;
5951    },
5952    /**
5953     * @var hash events A list of functions that are bound to DOM events
5954     *                  at the top of this file
5955     */
5956    events: {
5957        focus: function (event) {
5958            var $obj = $(this).closest('div.list_container');
5959            if (! $obj.data('fastFilter')) {
5960                $obj.data(
5961                    'fastFilter',
5962                    new PMA_fastFilter.filter($obj, $(this).val())
5963                );
5964            }
5965            if ($(this).val() == this.defaultValue) {
5966                $(this).val('');
5967            } else {
5968                $(this).select();
5969            }
5970        },
5971        blur: function (event) {
5972            if ($(this).val() == '') {
5973                $(this).val(this.defaultValue);
5974            }
5975            var $obj = $(this).closest('div.list_container');
5976            if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
5977                $obj.data('fastFilter').restore();
5978            }
5979        },
5980        keyup: function (event) {
5981            var $obj = $(this).closest('div.list_container');
5982            var str = '';
5983            if ($(this).val() != this.defaultValue && $(this).val() != '') {
5984                $obj.find('div.pageselector').hide();
5985                str = $(this).val().toLowerCase();
5986            }
5987            $obj.find('li > a').not('.container').each(function () {
5988                if ($(this).text().toLowerCase().indexOf(str) != -1) {
5989                    $(this).parent().show().removeClass('hidden');
5990                } else {
5991                    $(this).parent().hide().addClass('hidden');
5992                }
5993            });
5994            var container_filter = function ($curr, str) {
5995                $curr.children('ul').children('li.navGroup').each(function() {
5996                    var $group = $(this);
5997                    $group.children('div.list_container').each(function() {
5998                        container_filter($(this)); // recursive
5999                    });
6000                    $group.show().removeClass('hidden');
6001                    if ($group.children('div.list_container').children('ul')
6002                            .children('li').not('.hidden').length === 0) {
6003                        $group.hide().addClass('hidden');
6004                    }
6005                });
6006            };
6007            if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
6008                container_filter($('#pma_navigation_tree_content'), str);
6009            } else {
6010                container_filter($obj, str);
6011            }
6012            if ($(this).val() != this.defaultValue && $(this).val() != '') {
6013                if (! $obj.data('fastFilter')) {
6014                    $obj.data(
6015                        'fastFilter',
6016                        new PMA_fastFilter.filter($obj, $(this).val())
6017                    );
6018                } else {
6019                    $obj.data('fastFilter').update($(this).val());
6020                }
6021            } else if ($obj.data('fastFilter')) {
6022                $obj.data('fastFilter').restore(true);
6023            }
6024        },
6025        clear: function (event) {
6026            event.stopPropagation();
6027            // Clear the input and apply the fast filter with empty input
6028            var filter = $(this).closest('div.list_container').data('fastFilter');
6029            if (filter) {
6030                filter.restore();
6031            }
6032            var value = $(this).prev()[0].defaultValue;
6033            $(this).prev().val(value).trigger('keyup');
6034        }
6035    }
6036};
6037/**
6038 * Handles a change in the search clause
6039 *
6040 * @param string searchClause The query string for the filter
6041 *
6042 * @return void
6043 */
6044PMA_fastFilter.filter.prototype.update = function (searchClause)
6045{
6046    if (this.searchClause != searchClause) {
6047        this.searchClause = searchClause;
6048        this.$this.find('.moreResults').remove();
6049        this.request();
6050    }
6051};
6052/**
6053 * After a delay of 250mS, initiates a request to retrieve search results
6054 * Multiple calls to this function will always abort the previous request
6055 *
6056 * @return void
6057 */
6058PMA_fastFilter.filter.prototype.request = function ()
6059{
6060    var self = this;
6061    clearTimeout(self.timeout);
6062    if (self.$this.find('li.fast_filter').find('img.throbber').length == 0) {
6063        self.$this.find('li.fast_filter').append(
6064            $('<div class="throbber"></div>').append(
6065                $('#pma_navigation_content')
6066                    .find('img.throbber')
6067                    .clone()
6068                    .css('visibility', 'visible')
6069            )
6070        );
6071    }
6072    self.timeout = setTimeout(function () {
6073        if (self.xhr) {
6074            self.xhr.abort();
6075        }
6076        var url = $('#pma_navigation').find('a.navigation_url').attr('href');
6077        var results = self.$this.find('li:not(.hidden):not(.fast_filter):not(.navGroup)').not('[class^=new]').length;
6078        var params = self.$this.find('> ul > li > form.fast_filter').first().serialize() + "&results=" + results;
6079        if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length == 0) {
6080            var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause');
6081            if ($input.length && $input.val() != $input[0].defaultValue) {
6082                params += '&searchClause=' + encodeURIComponent($input.val());
6083            }
6084        }
6085        self.xhr = $.ajax({
6086            url: url,
6087            type: 'post',
6088            dataType: 'json',
6089            data: params,
6090            complete: function (jqXHR) {
6091                var data = $.parseJSON(jqXHR.responseText);
6092                self.$this.find('li.fast_filter').find('div.throbber').remove();
6093                if (data && data.results) {
6094                    var $listItem = $('<li />', {'class':'moreResults'})
6095                        .appendTo(self.$this.find('li.fast_filter'));
6096                    var $link = $('<a />', {href:'#'})
6097                        .text(data.results)
6098                        .appendTo($listItem)
6099                        .click(function (event) {
6100                            event.preventDefault();
6101                            self.swap.apply(self, [data.message]);
6102                        });
6103                }
6104            }
6105        });
6106    }, 250);
6107};
6108/**
6109 * Replaces the contents of the navigation branch with the search results
6110 *
6111 * @param string list The search results
6112 *
6113 * @return void
6114 */
6115PMA_fastFilter.filter.prototype.swap = function (list)
6116{
6117    this.swapped = true;
6118    this.$this
6119        .html($(list).html())
6120        .children()
6121        .show()
6122        .end()
6123        .find('li.fast_filter input.searchClause')
6124        .val(this.searchClause);
6125    this.$this.data('fastFilter', this);
6126};
6127/**
6128 * Restores the navigation to the original state after the fast filter is cleared
6129 *
6130 * @param bool focus Whether to also focus the input box of the fast filter
6131 *
6132 * @return void
6133 */
6134PMA_fastFilter.filter.prototype.restore = function (focus)
6135{
6136    if (this.swapped) {
6137        this.swapped = false;
6138        this.$this.html(this.$clone.html()).children().show();
6139        this.$this.data('fastFilter', this);
6140        if (focus) {
6141            this.$this.find('li.fast_filter input.searchClause').focus();
6142        }
6143    }
6144    this.searchClause = '';
6145    this.$this.find('.moreResults').remove();
6146    this.$this.find('div.pageselector').show();
6147    this.$this.find('div.throbber').remove();
6148};
6149;
6150
6151/* vim: set expandtab sw=4 ts=4 sts=4: */
6152/**
6153 * @fileoverview    function used for index manipulation pages
6154 * @name            Table Structure
6155 *
6156 * @requires    jQuery
6157 * @requires    jQueryUI
6158 * @required    js/functions.js
6159 */
6160
6161/**
6162 * Hides/shows the inputs and submits appropriately depending
6163 * on whether the index type chosen is 'SPATIAL' or not.
6164 */
6165function checkIndexType()
6166{
6167    /**
6168     * @var Object Dropdown to select the index type.
6169     */
6170    $select_index_type = $('#select_index_type');
6171    /**
6172     * @var Object Table header for the size column.
6173     */
6174    $size_header = $('#index_columns thead tr th:nth-child(2)');
6175    /**
6176     * @var Object Inputs to specify the columns for the index.
6177     */
6178    $column_inputs = $('select[name="index[columns][names][]"]');
6179    /**
6180     * @var Object Inputs to specify sizes for columns of the index.
6181     */
6182    $size_inputs = $('input[name="index[columns][sub_parts][]"]');
6183    /**
6184     * @var Object Footer containg the controllers to add more columns
6185     */
6186    $add_more = $('#index_frm .tblFooters');
6187
6188    if ($select_index_type.val() == 'SPATIAL') {
6189        // Disable and hide the size column
6190        $size_header.hide();
6191        $size_inputs.each(function(){
6192            $(this)
6193                .prop('disabled', true)
6194                .parent('td').hide();
6195        });
6196
6197        // Disable and hide the columns of the index other than the first one
6198        var initial = true;
6199        $column_inputs.each(function() {
6200            $column_input = $(this);
6201            if (! initial) {
6202                $column_input
6203                    .prop('disabled', true)
6204                    .parent('td').hide();
6205            } else {
6206                initial = false;
6207            }
6208        });
6209
6210        // Hide controllers to add more columns
6211        $add_more.hide();
6212    } else {
6213        // Enable and show the size column
6214        $size_header.show();
6215        $size_inputs.each(function() {
6216            $(this)
6217                .prop('disabled', false)
6218                .parent('td').show();
6219        });
6220
6221        // Enable and show the columns of the index
6222        $column_inputs.each(function() {
6223            $(this)
6224                .prop('disabled', false)
6225                .parent('td').show();
6226        });
6227
6228        // Show controllers to add more columns
6229        $add_more.show();
6230    }
6231}
6232
6233/**
6234 * Unbind all event handlers before tearing down a page
6235 */
6236AJAX.registerTeardown('indexes.js', function() {
6237    $('#select_index_type').die('change');
6238    $('a.drop_primary_key_index_anchor.ajax').die('click');
6239    $("#table_index tbody tr td.edit_index.ajax, #indexes .add_index.ajax").die('click');
6240    $('#index_frm input[type=submit]').die('click');
6241});
6242
6243/**
6244 * @description <p>Ajax scripts for table index page</p>
6245 *
6246 * Actions ajaxified here:
6247 * <ul>
6248 * <li>Showing/hiding inputs depending on the index type chosen</li>
6249 * <li>create/edit/drop indexes</li>
6250 * </ul>
6251 */
6252AJAX.registerOnload('indexes.js', function() {
6253    checkIndexType();
6254    checkIndexName("index_frm");
6255    $('#select_index_type').live('change', function(event){
6256        event.preventDefault();
6257        checkIndexType();
6258        checkIndexName("index_frm");
6259    });
6260
6261    /**
6262     * Ajax Event handler for 'Drop Index'
6263     */
6264    $('a.drop_primary_key_index_anchor.ajax').live('click', function(event) {
6265        event.preventDefault();
6266        var $anchor = $(this);
6267        /**
6268         * @var $curr_row    Object containing reference to the current field's row
6269         */
6270        var $curr_row = $anchor.parents('tr');
6271        /** @var    Number of columns in the key */
6272        var rows = $anchor.parents('td').attr('rowspan') || 1;
6273        /** @var    Rows that should be hidden */
6274        var $rows_to_hide = $curr_row;
6275        for (var i = 1, $last_row = $curr_row.next(); i < rows; i++, $last_row = $last_row.next()) {
6276            $rows_to_hide = $rows_to_hide.add($last_row);
6277        }
6278
6279        var question = escapeHtml(
6280            $curr_row.children('td')
6281                .children('.drop_primary_key_index_msg')
6282                .val()
6283        );
6284
6285        $anchor.PMA_confirm(question, $anchor.attr('href'), function(url) {
6286            var $msg = PMA_ajaxShowMessage(PMA_messages['strDroppingPrimaryKeyIndex'], false);
6287            $.get(url, {'is_js_confirmed': 1, 'ajax_request': true}, function(data) {
6288                if (data.success == true) {
6289                    PMA_ajaxRemoveMessage($msg);
6290                    var $table_ref = $rows_to_hide.closest('table');
6291                    if ($rows_to_hide.length == $table_ref.find('tbody > tr').length) {
6292                        // We are about to remove all rows from the table
6293                        $table_ref.hide('medium', function() {
6294                            $('div.no_indexes_defined').show('medium');
6295                            $rows_to_hide.remove();
6296                        });
6297                        $table_ref.siblings('div.notice').hide('medium');
6298                    } else {
6299                        // We are removing some of the rows only
6300                        toggleRowColors($rows_to_hide.last().next());
6301                        $rows_to_hide.hide("medium", function () {
6302                            $(this).remove();
6303                        });
6304                }
6305                        if ($('#result_query').length) {
6306                        $('#result_query').remove();
6307                    }
6308                    if (data.sql_query) {
6309                        $('<div id="result_query"></div>')
6310                            .html(data.sql_query)
6311                            .prependTo('#page_content');
6312                    }
6313                    PMA_commonActions.refreshMain(false, function() {
6314                        $("a.ajax[href^=#indexes]").click();
6315                    });
6316                    PMA_reloadNavigation();
6317                } else {
6318                    PMA_ajaxShowMessage(PMA_messages['strErrorProcessingRequest'] + " : " + data.error, false);
6319                }
6320            }); // end $.get()
6321        }); // end $.PMA_confirm()
6322    }); //end Drop Primary Key/Index
6323
6324    /**
6325     *Ajax event handler for index edit
6326    **/
6327    $("#table_index tbody tr td.edit_index.ajax, #indexes .add_index.ajax").live('click', function(event) {
6328        event.preventDefault();
6329        if ($(this).find("a").length == 0) {
6330            // Add index
6331            var valid = checkFormElementInRange(
6332                $(this).closest('form')[0],
6333                'added_fields',
6334                'Column count has to be larger than zero.'
6335            );
6336            if (! valid) {
6337                return;
6338            }
6339            var url = $(this).closest('form').serialize();
6340            var title = PMA_messages['strAddIndex'];
6341        } else {
6342            // Edit index
6343            var url = $(this).find("a").attr("href");
6344            if (url.substring(0, 16) == "tbl_indexes.php?") {
6345                url = url.substring(16, url.length);
6346            }
6347            var title = PMA_messages['strEditIndex'];
6348        }
6349        url += "&ajax_request=true";
6350        indexEditorDialog(url, title, function() {
6351            // refresh the page using ajax
6352            PMA_commonActions.refreshMain(false, function() {
6353                $("a.ajax[href^=#indexes]").click();
6354            });
6355        });
6356    });
6357});
6358;
6359
6360/* vim: set expandtab sw=4 ts=4 sts=4: */
6361/**
6362 * Functionality for communicating with the querywindow
6363 */
6364$(function () {
6365    /**
6366     * Event handler for click on the open query window link
6367     * in the top menu of the navigation panel
6368     */
6369    $('#pma_open_querywindow').click(function (event) {
6370        event.preventDefault();
6371        PMA_querywindow.focus();
6372    });
6373});
6374
6375/**
6376 * Holds common parameters such as server, db, table, etc
6377 *
6378 * The content for this is normally loaded from Header.class.php or
6379 * Response.class.php and executed by ajax.js
6380 */
6381var PMA_commonParams = (function () {
6382    /**
6383     * @var hash params An associative array of key value pairs
6384     * @access private
6385     */
6386    var params = {};
6387    // The returned object is the public part of the module
6388    return {
6389        /**
6390         * Saves all the key value pair that
6391         * are provided in the input array
6392         *
6393         * @param hash obj The input array
6394         *
6395         * @return void
6396         */
6397        setAll: function (obj) {
6398            var reload = false;
6399            var updateNavigation = false;
6400            for (var i in obj) {
6401                if (params[i] !== undefined && params[i] !== obj[i]) {
6402                    reload = true;
6403                    if (i == 'db' || i == 'table') {
6404                        updateNavigation = true;
6405                    }
6406                }
6407                params[i] = obj[i];
6408            }
6409            if (updateNavigation) {
6410                PMA_showCurrentNavigation();
6411            }
6412            if (reload) {
6413                PMA_querywindow.refresh();
6414            }
6415        },
6416        /**
6417         * Retrieves a value given its key
6418         * Returns empty string for undefined values
6419         *
6420         * @param string name The key
6421         *
6422         * @return string
6423         */
6424        get: function (name) {
6425            return params[name] || '';
6426        },
6427        /**
6428         * Saves a single key value pair
6429         *
6430         * @param string name  The key
6431         * @param string value The value
6432         *
6433         * @return self For chainability
6434         */
6435        set: function (name, value) {
6436            var updateNavigation = false;
6437            if (params[name] !== undefined && params[name] !== value) {
6438                PMA_querywindow.refresh();
6439                if (name == 'db' || name == 'table') {
6440                    updateNavigation = true;
6441                }
6442            }
6443            params[name] = value;
6444            if (updateNavigation) {
6445                PMA_showCurrentNavigation();
6446            }
6447            return this;
6448        },
6449        /**
6450         * Returns the url query string using the saved parameters
6451         *
6452         * @return string
6453         */
6454        getUrlQuery: function () {
6455            return $.sprintf(
6456                '?%s&server=%s&db=%s&table=%s',
6457                this.get('common_query'),
6458                encodeURIComponent(this.get('server')),
6459                encodeURIComponent(this.get('db')),
6460                encodeURIComponent(this.get('table'))
6461            );
6462        }
6463    };
6464})();
6465
6466/**
6467 * Holds common parameters such as server, db, table, etc
6468 *
6469 * The content for this is normally loaded from Header.class.php or
6470 * Response.class.php and executed by ajax.js
6471 */
6472var PMA_commonActions = {
6473    /**
6474     * Saves the database name when it's changed
6475     * and reloads the query window, if necessary
6476     *
6477     * @param string new_db The name of the new database
6478     *
6479     * @return void
6480     */
6481    setDb: function (new_db) {
6482        if (new_db != PMA_commonParams.get('db')) {
6483            PMA_commonParams.setAll({'db': new_db, 'table': ''});
6484        }
6485    },
6486    /**
6487     * Opens a database in the main part of the page
6488     *
6489     * @param string new_db The name of the new database
6490     *
6491     * @return void
6492     */
6493    openDb: function (new_db) {
6494        PMA_commonParams
6495            .set('db', new_db)
6496            .set('table', '');
6497        PMA_querywindow.refresh();
6498        this.refreshMain(
6499            PMA_commonParams.get('opendb_url')
6500        );
6501    },
6502    /**
6503     * Refreshes the main frame
6504     *
6505     * @param mixed url Undefined to refresh to the same page
6506     *                  String to go to a different page, e.g: 'index.php'
6507     *
6508     * @return void
6509     */
6510    refreshMain: function (url, callback) {
6511        if (! url) {
6512            url = $('#selflink a').attr('href');
6513            url = url.substring(0, url.indexOf('?'));
6514        }
6515        url += PMA_commonParams.getUrlQuery();
6516        $('<a />', {href: url})
6517            .appendTo('body')
6518            .click()
6519            .remove();
6520        AJAX._callback = callback;
6521    }
6522};
6523
6524/**
6525 * Common functions used for communicating with the querywindow
6526 */
6527var PMA_querywindow = (function ($, window) {
6528    /**
6529     * @var Object querywindow Reference to the window
6530     *                         object of the querywindow
6531     * @access private
6532     */
6533    var querywindow = {};
6534    /**
6535     * @var string queryToLoad Stores the SQL query that is to be displayed
6536     *                         in the querywindow when it is ready
6537     * @access private
6538     */
6539    var queryToLoad = '';
6540    // The returned object is the public part of the module
6541    return {
6542        /**
6543         * Opens the query window
6544         *
6545         * @param mixed url Undefined to open the default page
6546         *                  String to go to a different
6547         *
6548         * @return void
6549         */
6550        open: function (url, sql_query) {
6551            if (! url) {
6552                url = 'querywindow.php' + PMA_commonParams.getUrlQuery();
6553            }
6554            if (sql_query) {
6555                url += '&sql_query=' + encodeURIComponent(sql_query);
6556            }
6557
6558            if (! querywindow.closed && querywindow.location) {
6559                var href = querywindow.location.href;
6560                if (href != url
6561                    && href != PMA_commonParams.get('pma_absolute_uri') + url
6562                ) {
6563                    if (PMA_commonParams.get('safari_browser')) {
6564                        querywindow.location.href = targeturl;
6565                    } else {
6566                        querywindow.location.replace(targeturl);
6567                    }
6568                    querywindow.focus();
6569                }
6570            } else {
6571                querywindow = window.open(
6572                    url + '&init=1',
6573                    '',
6574                    'toolbar=0,location=0,directories=0,status=1,'
6575                    + 'menubar=0,scrollbars=yes,resizable=yes,'
6576                    + 'width=' + PMA_commonParams.get('querywindow_width') + ','
6577                    + 'height=' + PMA_commonParams.get('querywindow_height')
6578                );
6579            }
6580            if (! querywindow.opener) {
6581               querywindow.opener = window.window;
6582            }
6583            if (window.focus) {
6584                querywindow.focus();
6585            }
6586        },
6587        /**
6588         * Opens, if necessary, focuses the query window
6589         * and displays an SQL query.
6590         *
6591         * @param string sql_query The SQL query to display in
6592         *                         the query window
6593         *
6594         * @return void
6595         */
6596        focus: function (sql_query) {
6597            if (! querywindow || querywindow.closed || ! querywindow.location) {
6598                // we need first to open the window and cannot pass the query with it
6599                // as we dont know if the query exceeds max url length
6600                queryToLoad = sql_query;
6601                this.open(false, sql_query);
6602            } else {
6603                //var querywindow = querywindow;
6604                var hiddenqueryform = querywindow
6605                    .document
6606                    .getElementById('hiddenqueryform');
6607                if (hiddenqueryform.querydisplay_tab != 'sql' ) {
6608                    hiddenqueryform.querydisplay_tab.value = "sql";
6609                    hiddenqueryform.sql_query.value = sql_query;
6610                    $(hiddenqueryform).addClass('disableAjax');
6611                    hiddenqueryform.submit();
6612                    querywindow.focus();
6613                } else {
6614                    querywindow.focus();
6615                }
6616            }
6617        },
6618        /**
6619         * Refreshes the query window given a url
6620         *
6621         * @param string url Where to go to
6622         *
6623         * @return void
6624         */
6625        refresh: function (url) {
6626            if (! querywindow.closed && querywindow.location) {
6627                var $form = $(querywindow.document).find('#sqlqueryform');
6628                if ($form.find('#checkbox_lock:checked').length == 0) {
6629                    PMA_querywindow.open(url);
6630                }
6631            }
6632        },
6633        /**
6634         * Reloads the query window given the details
6635         * of a db, a table and an sql_query
6636         *
6637         * @param string db        The name of the database
6638         * @param string table     The name of the table
6639         * @param string sql_query The SQL query to be displayed
6640         *
6641         * @return void
6642         */
6643        reload: function (db, table, sql_query) {
6644            if (! querywindow.closed && querywindow.location) {
6645                var $form = $(querywindow.document).find('#sqlqueryform');
6646                if ($form.find('#checkbox_lock:checked').length == 0) {
6647                    var $hiddenform = $(querywindow.document)
6648                        .find('#hiddenqueryform');
6649                    $hiddenform.find('input[name=db]').val(db);
6650                    $hiddenform.find('input[name=table]').val(table);
6651                    if (sql_query) {
6652                        $hiddenform.find('input[name=sql_query]').val(sql_query);
6653                    }
6654                    $hiddenform.addClass('disableAjax').submit();
6655                }
6656            }
6657        }
6658    };
6659})(jQuery, window);
6660;
6661
6662// CodeMirror version 2.38
6663//
6664// All functions that need access to the editor's state live inside
vendor: 4,285 bytes, lines 6665-6732
6665// the CodeMirror function. Below that, at the bottom of the file,
6666// some utilities are defined.
6667
6668// CodeMirror is the only global var we claim
6669window.CodeMirror = (function() {
6670  "use strict";
6671  // This is the function that produces an editor instance. Its
6672  // closure is used to store the editor state.
6673  function CodeMirror(place, givenOptions) {
6674    // Determine effective options based on given values and defaults.
6675    var options = {}, defaults = CodeMirror.defaults;
6676    for (var opt in defaults)
6677      if (defaults.hasOwnProperty(opt))
6678        options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
6679
6680    var input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em");
6681    input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
6682    // Wraps and hides input textarea
6683    var inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
6684    // The empty scrollbar content, used solely for managing the scrollbar thumb.
6685    var scrollbarInner = elt("div", null, "CodeMirror-scrollbar-inner");
6686    // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself.
6687    var scrollbar = elt("div", [scrollbarInner], "CodeMirror-scrollbar");
6688    // DIVs containing the selection and the actual code
6689    var lineDiv = elt("div"), selectionDiv = elt("div", null, null, "position: relative; z-index: -1");
6690    // Blinky cursor, and element used to ensure cursor fits at the end of a line
6691    var cursor = elt("pre", "\u00a0", "CodeMirror-cursor"), widthForcer = elt("pre", "\u00a0", "CodeMirror-cursor", "visibility: hidden");
6692    // Used to measure text size
6693    var measure = elt("div", null, null, "position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;");
6694    var lineSpace = elt("div", [measure, cursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0");
6695    var gutterText = elt("div", null, "CodeMirror-gutter-text"), gutter = elt("div", [gutterText], "CodeMirror-gutter");
6696    // Moved around its parent to cover visible view
6697    var mover = elt("div", [gutter, elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative");
6698    // Set to the height of the text, causes scrolling
6699    var sizer = elt("div", [mover], null, "position: relative");
6700    // Provides scrolling
6701    var scroller = elt("div", [sizer], "CodeMirror-scroll");
6702    scroller.setAttribute("tabIndex", "-1");
6703    // The element in which the editor lives.
6704    var wrapper = elt("div", [inputDiv, scrollbar, scroller], "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""));
6705    if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
6706
6707    themeChanged(); keyMapChanged();
6708    // Needed to hide big blue blinking cursor on Mobile Safari
6709    if (ios) input.style.width = "0px";
6710    if (!webkit) scroller.draggable = true;
6711    lineSpace.style.outline = "none";
6712    if (options.tabindex != null) input.tabIndex = options.tabindex;
6713    if (options.autofocus) focusInput();
6714    if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
6715    // Needed to handle Tab key in KHTML
6716    if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
6717
6718    // Check for OS X >= 10.7. This has transparent scrollbars, so the
6719    // overlaying of one scrollbar with another won't work. This is a
6720    // temporary hack to simply turn off the overlay scrollbar. See
6721    // issue #727.
6722    if (mac_geLion) { scrollbar.style.zIndex = -2; scrollbar.style.visibility = "hidden"; }
6723    // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
6724    else if (ie_lt8) scrollbar.style.minWidth = "18px";
6725
6726    // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
6727    var poll = new Delayed(), highlight = new Delayed(), blinker;
6728
6729    // mode holds a mode API object. doc is the tree of Line objects,
6730    // frontier is the point up to which the content has been parsed,
6731    // and history the undo history (instance of History constructor).
6732    var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), frontier = 0, focused;
6733    loadMode();
6734    // The selection. These are always maintained to point at valid
6735    // positions. Inverted is used to remember that the user is
6736    // selecting bottom-to-top.
6737    var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
6738    // Selection-related flags. shiftSelecting obviously tracks
6739    // whether the user is holding shift.
6740    var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, draggingText,
6741        overwrite = false, suppressEdits = false, pasteIncoming = false;
6742    // Variables used by startOperation/endOperation to track what
6743    // happened during the operation.
6744    var updateInput, userSelChange, changes, textChanged, selectionChanged,
6745        gutterDirty, callbacks;
6746    // Current visible range (may be bigger than the view window).
6747    var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
6748    // bracketHighlighted is used to remember that a bracket has been
6749    // marked.
6750    var bracketHighlighted;
6751    // Tracks the maximum line length so that the horizontal scrollbar
6752    // can be kept static when scrolling.
6753    var maxLine = getLine(0), updateMaxLine = false, maxLineChanged = true;
6754    var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
6755    var goalColumn = null;
6756
6757    // Initialize the content.
6758    operation(function(){setValue(options.value || ""); updateInput = false;})();
6759    var history = new History();
6760
6761    // Register our event handlers.
6762    connect(scroller, "mousedown", operation(onMouseDown));
6763    connect(scroller, "dblclick", operation(onDoubleClick));
6764    connect(lineSpace, "selectstart", e_preventDefault);
6765    // Gecko browsers fire contextmenu *after* opening the menu, at
6766    // which point we can't mess with it anymore. Context menu is
6767    // handled in onMouseDown for Gecko.
6768    if (!gecko) connect(scroller, "contextmenu", onContextMenu);
6769    connect(scroller, "scroll", onScrollMain);
6770    connect(scrollbar, "scroll", onScrollBar);
6771    connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);});
6772    var resizeHandler = connect(window, "resize", function() {
6773      if (wrapper.parentNode) updateDisplay(true);
6774      else resizeHandler();
6775    }, true);
6776    connect(input, "keyup", operation(onKeyUp));
6777    connect(input, "input", fastPoll);
6778    connect(input, "keydown", operation(onKeyDown));
6779    connect(input, "keypress", operation(onKeyPress));
6780    connect(input, "focus", onFocus);
6781    connect(input, "blur", onBlur);
6782
6783    function drag_(e) {
6784      if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
6785      e_stop(e);
6786    }
6787    if (options.dragDrop) {
6788      connect(scroller, "dragstart", onDragStart);
6789      connect(scroller, "dragenter", drag_);
6790      connect(scroller, "dragover", drag_);
6791      connect(scroller, "drop", operation(onDrop));
6792    }
6793    connect(scroller, "paste", function(){focusInput(); fastPoll();});
6794    connect(input, "paste", function(){pasteIncoming = true; fastPoll();});
6795    connect(input, "cut", operation(function(){
6796      if (!options.readOnly) replaceSelection("");
6797    }));
6798
6799    // Needed to handle Tab key in KHTML
6800    if (khtml) connect(sizer, "mouseup", function() {
6801        if (document.activeElement == input) input.blur();
6802        focusInput();
6803    });
6804
6805    // IE throws unspecified error in certain cases, when
6806    // trying to access activeElement before onload
6807    var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { }
6808    if (hasFocus || options.autofocus) setTimeout(onFocus, 20);
6809    else onBlur();
6810
6811    function isLine(l) {return l >= 0 && l < doc.size;}
6812    // The instance object that we'll return. Mostly calls out to
6813    // local functions in the CodeMirror function. Some do some extra
6814    // range checking and/or clipping. operation is used to wrap the
6815    // call so that changes it makes are tracked, and the display is
6816    // updated afterwards.
6817    var instance = wrapper.CodeMirror = {
6818      getValue: getValue,
6819      setValue: operation(setValue),
6820      getSelection: getSelection,
6821      replaceSelection: operation(replaceSelection),
6822      focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();},
6823      setOption: function(option, value) {
6824        var oldVal = options[option];
6825        options[option] = value;
6826        if (option == "mode" || option == "indentUnit") loadMode();
6827        else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
6828        else if (option == "readOnly" && !value) {resetInput(true);}
6829        else if (option == "theme") themeChanged();
6830        else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
6831        else if (option == "tabSize") updateDisplay(true);
6832        else if (option == "keyMap") keyMapChanged();
6833        else if (option == "tabindex") input.tabIndex = value;
6834        else if (option == "showCursorWhenSelecting") updateSelection();
6835        if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" ||
6836            option == "theme" || option == "lineNumberFormatter") {
6837          gutterChanged();
6838          updateDisplay(true);
6839        }
6840      },
6841      getOption: function(option) {return options[option];},
6842      getMode: function() {return mode;},
6843      undo: operation(undo),
6844      redo: operation(redo),
6845      indentLine: operation(function(n, dir) {
6846        if (typeof dir != "string") {
6847          if (dir == null) dir = options.smartIndent ? "smart" : "prev";
6848          else dir = dir ? "add" : "subtract";
6849        }
6850        if (isLine(n)) indentLine(n, dir);
6851      }),
6852      indentSelection: operation(indentSelected),
6853      historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
6854      clearHistory: function() {history = new History();},
6855      setHistory: function(histData) {
6856        history = new History();
6857        history.done = histData.done;
6858        history.undone = histData.undone;
6859      },
6860      getHistory: function() {
6861        function cp(arr) {
6862          for (var i = 0, nw = [], nwelt; i < arr.length; ++i) {
6863            nw.push(nwelt = []);
6864            for (var j = 0, elt = arr[i]; j < elt.length; ++j) {
6865              var old = [], cur = elt[j];
6866              nwelt.push({start: cur.start, added: cur.added, old: old});
6867              for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k]));
6868            }
6869          }
6870          return nw;
6871        }
6872        return {done: cp(history.done), undone: cp(history.undone)};
6873      },
6874      matchBrackets: operation(function(){matchBrackets(true);}),
6875      getTokenAt: operation(function(pos) {
6876        pos = clipPos(pos);
6877        return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), options.tabSize, pos.ch);
6878      }),
6879      getStateAfter: function(line) {
6880        line = clipLine(line == null ? doc.size - 1: line);
6881        return getStateBefore(line + 1);
6882      },
6883      cursorCoords: function(start, mode) {
6884        if (start == null) start = sel.inverted;
6885        return this.charCoords(start ? sel.from : sel.to, mode);
6886      },
6887      charCoords: function(pos, mode) {
6888        pos = clipPos(pos);
6889        if (mode == "local") return localCoords(pos, false);
6890        if (mode == "div") return localCoords(pos, true);
6891        return pageCoords(pos);
6892      },
6893      coordsChar: function(coords) {
6894        var off = eltOffset(lineSpace);
6895        return coordsChar(coords.x - off.left, coords.y - off.top);
6896      },
6897      defaultTextHeight: function() { return textHeight(); },
6898      markText: operation(markText),
6899      setBookmark: setBookmark,
6900      findMarksAt: findMarksAt,
6901      setMarker: operation(addGutterMarker),
6902      clearMarker: operation(removeGutterMarker),
6903      setLineClass: operation(setLineClass),
6904      hideLine: operation(function(h) {return setLineHidden(h, true);}),
6905      showLine: operation(function(h) {return setLineHidden(h, false);}),
6906      onDeleteLine: function(line, f) {
6907        if (typeof line == "number") {
6908          if (!isLine(line)) return null;
6909          line = getLine(line);
6910        }
6911        (line.handlers || (line.handlers = [])).push(f);
6912        return line;
6913      },
6914      lineInfo: lineInfo,
6915      getViewport: function() { return {from: showingFrom, to: showingTo};},
6916      addWidget: function(pos, node, scroll, vert, horiz) {
6917        pos = localCoords(clipPos(pos));
6918        var top = pos.yBot, left = pos.x;
6919        node.style.position = "absolute";
6920        sizer.appendChild(node);
6921        if (vert == "over") top = pos.y;
6922        else if (vert == "near") {
6923          var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
6924              hspace = Math.max(sizer.clientWidth, lineSpace.clientWidth) - paddingLeft();
6925          if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
6926            top = pos.y - node.offsetHeight;
6927          if (left + node.offsetWidth > hspace)
6928            left = hspace - node.offsetWidth;
6929        }
6930        node.style.top = (top + paddingTop()) + "px";
6931        node.style.left = node.style.right = "";
6932        if (horiz == "right") {
6933          left = sizer.clientWidth - node.offsetWidth;
6934          node.style.right = "0px";
6935        } else {
6936          if (horiz == "left") left = 0;
6937          else if (horiz == "middle") left = (sizer.clientWidth - node.offsetWidth) / 2;
6938          node.style.left = (left + paddingLeft()) + "px";
6939        }
6940        if (scroll)
6941          scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
6942      },
6943
6944      lineCount: function() {return doc.size;},
6945      clipPos: clipPos,
6946      getCursor: function(start) {
6947        if (start == null || start == "head") start = sel.inverted;
6948        if (start == "anchor") start = !sel.inverted;
6949        if (start == "end") start = false;
6950        return copyPos(start ? sel.from : sel.to);
6951      },
6952      somethingSelected: function() {return !posEq(sel.from, sel.to);},
6953      setCursor: operation(function(line, ch, user) {
6954        if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
6955        else setCursor(line, ch, user);
6956      }),
6957      setSelection: operation(function(from, to, user) {
6958        (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
6959      }),
6960      getLine: function(line) {if (isLine(line)) return getLine(line).text;},
6961      getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
6962      setLine: operation(function(line, text) {
6963        if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
6964      }),
6965      removeLine: operation(function(line) {
6966        if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
6967      }),
6968      replaceRange: operation(replaceRange),
6969      getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);},
6970
6971      triggerOnKeyDown: operation(onKeyDown),
6972      execCommand: function(cmd) {return commands[cmd](instance);},
6973      // Stuff used by commands, probably not much use to outside code.
6974      moveH: operation(moveH),
6975      deleteH: operation(deleteH),
6976      moveV: operation(moveV),
6977      toggleOverwrite: function() {
6978        if(overwrite){
6979          overwrite = false;
6980          cursor.className = cursor.className.replace(" CodeMirror-overwrite", "");
6981        } else {
6982          overwrite = true;
6983          cursor.className += " CodeMirror-overwrite";
6984        }
6985      },
6986
6987      posFromIndex: function(off) {
6988        var lineNo = 0, ch;
6989        doc.iter(0, doc.size, function(line) {
6990          var sz = line.text.length + 1;
6991          if (sz > off) { ch = off; return true; }
6992          off -= sz;
6993          ++lineNo;
6994        });
6995        return clipPos({line: lineNo, ch: ch});
6996      },
6997      indexFromPos: function (coords) {
6998        if (coords.line < 0 || coords.ch < 0) return 0;
6999        var index = coords.ch;
7000        doc.iter(0, coords.line, function (line) {
7001          index += line.text.length + 1;
7002        });
7003        return index;
7004      },
7005      scrollTo: function(x, y) {
7006        if (x != null) scroller.scrollLeft = x;
7007        if (y != null) scrollbar.scrollTop = scroller.scrollTop = y;
7008        updateDisplay([]);
7009      },
7010      getScrollInfo: function() {
7011        return {x: scroller.scrollLeft, y: scrollbar.scrollTop,
7012                height: scrollbar.scrollHeight, width: scroller.scrollWidth};
7013      },
7014      scrollIntoView: function(pos) {
7015        var coords = localCoords(pos ? clipPos(pos) : sel.inverted ? sel.from : sel.to);
7016        scrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
7017      },
7018
7019      setSize: function(width, height) {
7020        function interpret(val) {
7021          val = String(val);
7022          return /^\d+$/.test(val) ? val + "px" : val;
7023        }
7024        if (width != null) wrapper.style.width = interpret(width);
7025        if (height != null) scroller.style.height = interpret(height);
7026        instance.refresh();
7027      },
7028
7029      operation: function(f){return operation(f)();},
7030      compoundChange: function(f){return compoundChange(f);},
7031      refresh: function(){
7032        updateDisplay(true, null, lastScrollTop);
7033        if (scrollbar.scrollHeight > lastScrollTop)
7034          scrollbar.scrollTop = lastScrollTop;
7035      },
7036      getInputField: function(){return input;},
7037      getWrapperElement: function(){return wrapper;},
7038      getScrollerElement: function(){return scroller;},
7039      getGutterElement: function(){return gutter;}
7040    };
7041
7042    function getLine(n) { return getLineAt(doc, n); }
7043    function updateLineHeight(line, height) {
7044      gutterDirty = true;
7045      var diff = height - line.height;
7046      for (var n = line; n; n = n.parent) n.height += diff;
7047    }
7048
7049    function lineContent(line, wrapAt) {
7050      if (!line.styles)
7051        line.highlight(mode, line.stateAfter = getStateBefore(lineNo(line)), options.tabSize);
7052      return line.getContent(options.tabSize, wrapAt, options.lineWrapping);
7053    }
7054
7055    function setValue(code) {
7056      var top = {line: 0, ch: 0};
7057      updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
7058                  splitLines(code), top, top);
7059      updateInput = true;
7060    }
7061    function getValue(lineSep) {
7062      var text = [];
7063      doc.iter(0, doc.size, function(line) { text.push(line.text); });
7064      return text.join(lineSep || "\n");
7065    }
7066
7067    function onScrollBar(e) {
7068      if (Math.abs(scrollbar.scrollTop - lastScrollTop) > 1) {
7069        lastScrollTop = scroller.scrollTop = scrollbar.scrollTop;
7070        updateDisplay([]);
7071      }
7072    }
7073
7074    function onScrollMain(e) {
7075      if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px")
7076        gutter.style.left = scroller.scrollLeft + "px";
7077      if (Math.abs(scroller.scrollTop - lastScrollTop) > 1) {
7078        lastScrollTop = scroller.scrollTop;
7079        if (scrollbar.scrollTop != lastScrollTop)
7080          scrollbar.scrollTop = lastScrollTop;
7081        updateDisplay([]);
7082      }
7083      if (options.onScroll) options.onScroll(instance);
7084    }
7085
7086    function onMouseDown(e) {
7087      setShift(e_prop(e, "shiftKey"));
7088      // Check whether this is a click in a widget
7089      for (var n = e_target(e); n != wrapper; n = n.parentNode)
7090        if (n.parentNode == sizer && n != mover) return;
7091
7092      // See if this is a click in the gutter
7093      for (var n = e_target(e); n != wrapper; n = n.parentNode)
7094        if (n.parentNode == gutterText) {
7095          if (options.onGutterClick)
7096            options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
7097          return e_preventDefault(e);
7098        }
7099
7100      var start = posFromMouse(e);
7101
7102      switch (e_button(e)) {
7103      case 3:
7104        if (gecko) onContextMenu(e);
7105        return;
7106      case 2:
7107        if (start) setCursor(start.line, start.ch, true);
7108        setTimeout(focusInput, 20);
7109        e_preventDefault(e);
7110        return;
7111      }
7112      // For button 1, if it was clicked inside the editor
7113      // (posFromMouse returning non-null), we have to adjust the
7114      // selection.
7115      if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
7116
7117      if (!focused) onFocus();
7118
7119      var now = +new Date, type = "single";
7120      if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
7121        type = "triple";
7122        e_preventDefault(e);
7123        setTimeout(focusInput, 20);
7124        selectLine(start.line);
7125      } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
7126        type = "double";
7127        lastDoubleClick = {time: now, pos: start};
7128        e_preventDefault(e);
7129        var word = findWordAt(start);
7130        setSelectionUser(word.from, word.to);
7131      } else { lastClick = {time: now, pos: start}; }
7132
7133      function dragEnd(e2) {
7134        if (webkit) scroller.draggable = false;
7135        draggingText = false;
7136        up(); drop();
7137        if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
7138          e_preventDefault(e2);
7139          setCursor(start.line, start.ch, true);
7140          focusInput();
7141        }
7142      }
7143      var last = start, going;
7144      if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
7145          !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
7146        // Let the drag handler handle this.
7147        if (webkit) scroller.draggable = true;
7148        var up = connect(document, "mouseup", operation(dragEnd), true);
7149        var drop = connect(scroller, "drop", operation(dragEnd), true);
7150        draggingText = true;
7151        // IE's approach to draggable
7152        if (scroller.dragDrop) scroller.dragDrop();
7153        return;
7154      }
7155      e_preventDefault(e);
7156      if (type == "single") setCursor(start.line, start.ch, true);
7157
7158      var startstart = sel.from, startend = sel.to;
7159
7160      function doSelect(cur) {
7161        if (type == "single") {
7162          setSelectionUser(clipPos(start), cur);
7163          return;
7164        }
7165        startstart = clipPos(startstart);
7166        startend = clipPos(startend);
7167        if (type == "double") {
7168          var word = findWordAt(cur);
7169          if (posLess(cur, startstart)) setSelectionUser(word.from, startend);
7170          else setSelectionUser(startstart, word.to);
7171        } else if (type == "triple") {
7172          if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0}));
7173          else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0}));
7174        }
7175      }
7176
7177      function extend(e) {
7178        var cur = posFromMouse(e, true);
7179        if (cur && !posEq(cur, last)) {
7180          if (!focused) onFocus();
7181          last = cur;
7182          doSelect(cur);
7183          updateInput = false;
7184          var visible = visibleLines();
7185          if (cur.line >= visible.to || cur.line < visible.from)
7186            going = setTimeout(operation(function(){extend(e);}), 150);
7187        }
7188      }
7189
7190      function done(e) {
7191        clearTimeout(going);
7192        var cur = posFromMouse(e);
7193        if (cur) doSelect(cur);
7194        e_preventDefault(e);
7195        focusInput();
7196        updateInput = true;
7197        move(); up();
7198      }
7199      var move = connect(document, "mousemove", operation(function(e) {
7200        clearTimeout(going);
7201        e_preventDefault(e);
7202        if (!ie && !e_button(e)) done(e);
7203        else extend(e);
7204      }), true);
7205      var up = connect(document, "mouseup", operation(done), true);
7206    }
7207    function onDoubleClick(e) {
7208      for (var n = e_target(e); n != wrapper; n = n.parentNode)
7209        if (n.parentNode == gutterText) return e_preventDefault(e);
7210      e_preventDefault(e);
7211    }
7212    function onDrop(e) {
7213      if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
7214      e_preventDefault(e);
7215      var pos = posFromMouse(e, true), files = e.dataTransfer.files;
7216      if (!pos || options.readOnly) return;
7217      if (files && files.length && window.FileReader && window.File) {
7218        var n = files.length, text = Array(n), read = 0;
7219        var loadFile = function(file, i) {
7220          var reader = new FileReader;
7221          reader.onload = function() {
7222            text[i] = reader.result;
7223            if (++read == n) {
7224              pos = clipPos(pos);
7225              operation(function() {
7226                var end = replaceRange(text.join(""), pos, pos);
7227                setSelectionUser(pos, end);
7228              })();
7229            }
7230          };
7231          reader.readAsText(file);
7232        };
7233        for (var i = 0; i < n; ++i) loadFile(files[i], i);
7234      } else {
7235        // Don't do a replace if the drop happened inside of the selected text.
7236        if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return;
7237        try {
7238          var text = e.dataTransfer.getData("Text");
7239          if (text) {
7240            compoundChange(function() {
7241              var curFrom = sel.from, curTo = sel.to;
7242              setSelectionUser(pos, pos);
7243              if (draggingText) replaceRange("", curFrom, curTo);
7244              replaceSelection(text);
7245              focusInput();
7246            });
7247          }
7248        }
7249        catch(e){}
7250      }
7251    }
7252    function onDragStart(e) {
7253      var txt = getSelection();
7254      e.dataTransfer.setData("Text", txt);
7255
7256      // Use dummy image instead of default browsers image.
7257      if (e.dataTransfer.setDragImage)
7258        e.dataTransfer.setDragImage(elt('img'), 0, 0);
7259    }
7260
7261    function doHandleBinding(bound, dropShift) {
7262      if (typeof bound == "string") {
7263        bound = commands[bound];
7264        if (!bound) return false;
7265      }
7266      var prevShift = shiftSelecting;
7267      try {
7268        if (options.readOnly) suppressEdits = true;
7269        if (dropShift) shiftSelecting = null;
7270        bound(instance);
7271      } catch(e) {
7272        if (e != Pass) throw e;
7273        return false;
7274      } finally {
7275        shiftSelecting = prevShift;
7276        suppressEdits = false;
7277      }
7278      return true;
7279    }
7280    var maybeTransition;
7281    function handleKeyBinding(e) {
7282      // Handle auto keymap transitions
7283      var startMap = getKeyMap(options.keyMap), next = startMap.auto;
7284      clearTimeout(maybeTransition);
7285      if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
7286        if (getKeyMap(options.keyMap) == startMap) {
7287          options.keyMap = (next.call ? next.call(null, instance) : next);
7288        }
7289      }, 50);
7290
7291      var name = keyNames[e_prop(e, "keyCode")], handled = false;
7292      if (name == null || e.altGraphKey) return false;
7293      if (e_prop(e, "altKey")) name = "Alt-" + name;
7294      if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name;
7295      if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name;
7296
7297      var stopped = false;
7298      function stop() { stopped = true; }
7299
7300      if (e_prop(e, "shiftKey")) {
7301        handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap,
7302                            function(b) {return doHandleBinding(b, true);}, stop)
7303               || lookupKey(name, options.extraKeys, options.keyMap, function(b) {
7304                 if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b);
7305               }, stop);
7306      } else {
7307        handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop
7307);
7308      }
7309      if (stopped) handled = false;
7310      if (handled) {
7311        e_preventDefault(e);
7312        restartBlink();
7313        if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
7314      }
7315      return handled;
7316    }
7317    function handleCharBinding(e, ch) {
7318      var handled = lookupKey("'" + ch + "'", options.extraKeys,
7319                              options.keyMap, function(b) { return doHandleBinding(b, true); });
7320      if (handled) {
7321        e_preventDefault(e);
7322        restartBlink();
7323      }
7324      return handled;
7325    }
7326
7327    var lastStoppedKey = null;
7328    function onKeyDown(e) {
7329      if (!focused) onFocus();
7330      if (ie && e.keyCode == 27) { e.returnValue = false; }
7331      if (pollingFast) { if (readInput()) pollingFast = false; }
7332      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
7333      var code = e_prop(e, "keyCode");
7334      // IE does strange things with escape.
7335      setShift(code == 16 || e_prop(e, "shiftKey"));
7336      // First give onKeyEvent option a chance to handle this.
7337      var handled = handleKeyBinding(e);
7338      if (opera) {
7339        lastStoppedKey = handled ? code : null;
7340        // Opera has no cut event... we try to at least catch the key combo
7341        if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey"))
7342          replaceSelection("");
7343      }
7344    }
7345    function onKeyPress(e) {
7346      if (pollingFast) readInput();
7347      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
7348      var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
7349      if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
7350      if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return;
7351      var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
7352      if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) {
7353        if (mode.electricChars.indexOf(ch) > -1)
7354          setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
7355      }
7356      if (handleCharBinding(e, ch)) return;
7357      fastPoll();
7358    }
7359    function onKeyUp(e) {
7360      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
7361      if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
7362    }
7363
7364    function onFocus() {
7365      if (options.readOnly == "nocursor") return;
7366      if (!focused) {
7367        if (options.onFocus) options.onFocus(instance);
7368        focused = true;
7369        if (scroller.className.search(/\bCodeMirror-focused\b/) == -1)
7370          scroller.className += " CodeMirror-focused";
7371      }
7372      slowPoll();
7373      restartBlink();
7374    }
7375    function onBlur() {
7376      if (focused) {
7377        if (options.onBlur) options.onBlur(instance);
7378        focused = false;
7379        if (bracketHighlighted)
7380          operation(function(){
7381            if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
7382          })();
7383        scroller.className = scroller.className.replace(" CodeMirror-focused", "");
7384      }
7385      clearInterval(blinker);
7386      setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
7387    }
7388
7389    // Replace the range from from to to by the strings in newText.
7390    // Afterwards, set the selection to selFrom, selTo.
7391    function updateLines(from, to, newText, selFrom, selTo) {
7392      if (suppressEdits) return;
7393      var old = [];
7394      doc.iter(from.line, to.line + 1, function(line) {
7395        old.push(newHL(line.text, line.markedSpans));
7396      });
7397      if (history) {
7398        history.addChange(from.line, newText.length, old);
7399        while (history.done.length > options.undoDepth) history.done.shift();
7400      }
7401      var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText);
7402      updateLinesNoUndo(from, to, lines, selFrom, selTo);
7403    }
7404    function unredoHelper(from, to) {
7405      if (!from.length) return;
7406      var set = from.pop(), out = [];
7407      for (var i = set.length - 1; i >= 0; i -= 1) {
7408        var change = set[i];
7409        var replaced = [], end = change.start + change.added;
7410        doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); });
7411        out.push({start: change.start, added: change.old.length, old: replaced});
7412        var pos = {line: change.start + change.old.length - 1,
7413                   ch: editEnd(hlText(lst(replaced)), hlText(lst(change.old)))};
7414        updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length},
7415                          change.old, pos, pos);
7416      }
7417      updateInput = true;
7418      to.push(out);
7419    }
7420    function undo() {unredoHelper(history.done, history.undone);}
7421    function redo() {unredoHelper(history.undone, history.done);}
7422
7423    function updateLinesNoUndo(from, to, lines, selFrom, selTo) {
7424      if (suppressEdits) return;
7425      var recomputeMaxLength = false, maxLineLength = maxLine.text.length;
7426      if (!options.lineWrapping)
7427        doc.iter(from.line, to.line + 1, function(line) {
7428          if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
7429        });
7430      if (from.line != to.line || lines.length > 1) gutterDirty = true;
7431
7432      var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
7433      var lastHL = lst(lines);
7434
7435      // First adjust the line structure
7436      if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") {
7437        // This is a whole-line replace. Treated specially to make
7438        // sure line objects move the way they are supposed to.
7439        var added = [], prevLine = null;
7440        for (var i = 0, e = lines.length - 1; i < e; ++i)
7441          added.push(new Line(hlText(lines[i]), hlSpans(lines[i])));
7442        lastLine.update(lastLine.text, hlSpans(lastHL));
7443        if (nlines) doc.remove(from.line, nlines, callbacks);
7444        if (added.length) doc.insert(from.line, added);
7445      } else if (firstLine == lastLine) {
7446        if (lines.length == 1) {
7447          firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + firstLine.text.slice(to.ch), hlSpans(lines[0]));
7448        } else {
7449          for (var added = [], i = 1, e = lines.length - 1; i < e; ++i)
7450            added.push(new Line(hlText(lines[i]), hlSpans(lines[i])));
7451          added.push(new Line(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL)));
7452          firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
7453          doc.insert(from.line + 1, added);
7454        }
7455      } else if (lines.length == 1) {
7456        firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + lastLine.text.slice(to.ch), hlSpans(lines[0]));
7457        doc.remove(from.line + 1, nlines, callbacks);
7458      } else {
7459        var added = [];
7460        firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
7461        lastLine.update(hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL));
7462        for (var i = 1, e = lines.length - 1; i < e; ++i)
7463          added.push(new Line(hlText(lines[i]), hlSpans(lines[i])));
7464        if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
7465        doc.insert(from.line + 1, added);
7466      }
7467      if (options.lineWrapping) {
7468        var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3);
7469        doc.iter(from.line, from.line + lines.length, function(line) {
7470          if (line.hidden) return;
7471          var guess = Math.ceil(line.text.length / perLine) || 1;
7472          if (guess != line.height) updateLineHeight(line, guess);
7473        });
7474      } else {
7475        doc.iter(from.line, from.line + lines.length, function(line) {
7476          var l = line.text;
7477          if (!line.hidden && l.length > maxLineLength) {
7478            maxLine = line; maxLineLength = l.length; maxLineChanged = true;
7479            recomputeMaxLength = false;
7480          }
7481        });
7482        if (recomputeMaxLength) updateMaxLine = true;
7483      }
7484
7485      // Adjust frontier, schedule worker
7486      frontier = Math.min(frontier, from.line);
7487      startWorker(400);
7488
7489      var lendiff = lines.length - nlines - 1;
7490      // Remember that these lines changed, for updating the display
7491      changes.push({from: from.line, to: to.line + 1, diff: lendiff});
7492      if (options.onChange) {
7493        // Normalize lines to contain only strings, since that's what
7494        // the change event handler expects
7495        for (var i = 0; i < lines.length; ++i)
7496          if (typeof lines[i] != "string") lines[i] = lines[i].text;
7497        var changeObj = {from: from, to: to, text: lines};
7498        if (textChanged) {
7499          for (var cur = textChanged; cur.next; cur = cur.next) {}
7500          cur.next = changeObj;
7501        } else textChanged = changeObj;
7502      }
7503
7504      // Update the selection
7505      function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
7506      setSelection(clipPos(selFrom), clipPos(selTo),
7507                   updateLine(sel.from.line), updateLine(sel.to.line));
7508    }
7509
7510    function needsScrollbar() {
7511      var realHeight = doc.height * textHeight() + 2 * paddingTop();
7512      return realHeight * .99 > scroller.offsetHeight ? realHeight : false;
7513    }
7514
7515    function updateVerticalScroll(scrollTop) {
7516      var scrollHeight = needsScrollbar();
7517      scrollbar.style.display = scrollHeight ? "block" : "none";
7518      if (scrollHeight) {
7519        scrollbarInner.style.height = sizer.style.minHeight = scrollHeight + "px";
7520        scrollbar.style.height = scroller.clientHeight + "px";
7521        if (scrollTop != null) {
7522          scrollbar.scrollTop = scroller.scrollTop = scrollTop;
7523          // 'Nudge' the scrollbar to work around a Webkit bug where,
7524          // in some situations, we'd end up with a scrollbar that
7525          // reported its scrollTop (and looked) as expected, but
7526          // *behaved* as if it was still in a previous state (i.e.
7527          // couldn't scroll up, even though it appeared to be at the
7528          // bottom).
7529          if (webkit) setTimeout(function() {
7530            if (scrollbar.scrollTop != scrollTop) return;
7531            scrollbar.scrollTop = scrollTop + (scrollTop ? -1 : 1);
7532            scrollbar.scrollTop = scrollTop;
7533          }, 0);
7534        }
7535      } else {
7536        sizer.style.minHeight = "";
7537      }
7538      // Position the mover div to align with the current virtual scroll position
7539      mover.style.top = displayOffset * textHeight() + "px";
7540    }
7541
7542    function computeMaxLength() {
7543      maxLine = getLine(0); maxLineChanged = true;
7544      var maxLineLength = maxLine.text.length;
7545      doc.iter(1, doc.size, function(line) {
7546        var l = line.text;
7547        if (!line.hidden && l.length > maxLineLength) {
7548          maxLineLength = l.length; maxLine = line;
7549        }
7550      });
7551      updateMaxLine = false;
7552    }
7553
7554    function replaceRange(code, from, to) {
7555      from = clipPos(from);
7556      if (!to) to = from; else to = clipPos(to);
7557      code = splitLines(code);
7558      function adjustPos(pos) {
7559        if (posLess(pos, from)) return pos;
7560        if (!posLess(to, pos)) return end;
7561        var line = pos.line + code.length - (to.line - from.line) - 1;
7562        var ch = pos.ch;
7563        if (pos.line == to.line)
7564          ch += lst(code).length - (to.ch - (to.line == from.line ? from.ch : 0));
7565        return {line: line, ch: ch};
7566      }
7567      var end;
7568      replaceRange1(code, from, to, function(end1) {
7569        end = end1;
7570        return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
7571      });
7572      return end;
7573    }
7574    function replaceSelection(code, collapse) {
7575      replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
7576        if (collapse == "end") return {from: end, to: end};
7577        else if (collapse == "start") return {from: sel.from, to: sel.from};
7578        else return {from: sel.from, to: end};
7579      });
7580    }
7581    function replaceRange1(code, from, to, computeSel) {
7582      var endch = code.length == 1 ? code[0].length + from.ch : lst(code).length;
7583      var newSel = computeSel({line: from.line + code.length - 1, ch: endch});
7584      updateLines(from, to, code, newSel.from, newSel.to);
7585    }
7586
7587    function getRange(from, to, lineSep) {
7588      var l1 = from.line, l2 = to.line;
7589      if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
7590      var code = [getLine(l1).text.slice(from.ch)];
7591      doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
7592      code.push(getLine(l2).text.slice(0, to.ch));
7593      return code.join(lineSep || "\n");
7594    }
7595    function getSelection(lineSep) {
7596      return getRange(sel.from, sel.to, lineSep);
7597    }
7598
7599    function slowPoll() {
7600      if (pollingFast) return;
7601      poll.set(options.pollInterval, function() {
7602        readInput();
7603        if (focused) slowPoll();
7604      });
7605    }
7606    function fastPoll() {
7607      var missed = false;
7608      pollingFast = true;
7609      function p() {
7610        var changed = readInput();
7611        if (!changed && !missed) {missed = true; poll.set(60, p);}
7612        else {pollingFast = false; slowPoll();}
7613      }
7614      poll.set(20, p);
7615    }
7616
7617    // Previnput is a hack to work with IME. If we reset the textarea
7618    // on every change, that breaks IME. So we look for changes
7619    // compared to the previous content instead. (Modern browsers have
7620    // events that indicate IME taking place, but these are not widely
7621    // supported or compatible enough yet to rely on.)
7622    var prevInput = "";
7623    function readInput() {
7624      if (!focused || hasSelection(input) || options.readOnly) return false;
7625      var text = input.value;
7626      if (text == prevInput) return false;
7627      if (!nestedOperation) startOperation();
7628      shiftSelecting = null;
7629      var same = 0, l = Math.min(prevInput.length, text.length);
7630      while (same < l && prevInput[same] == text[same]) ++same;
7631      if (same < prevInput.length)
7632        sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
7633      else if (overwrite && posEq(sel.from, sel.to) && !pasteIncoming)
7634        sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
7635      replaceSelection(text.slice(same), "end");
7636      if (text.length > 1000) { input.value = prevInput = ""; }
7637      else prevInput = text;
7638      if (!nestedOperation) endOperation();
7639      pasteIncoming = false;
7640      return true;
7641    }
7642    function resetInput(user) {
7643      if (!posEq(sel.from, sel.to)) {
7644        prevInput = "";
7645        input.value = getSelection();
7646        if (focused) selectInput(input);
7647      } else if (user) prevInput = input.value = "";
7648    }
7649
7650    function focusInput() {
7651      if (options.readOnly != "nocursor" && (ie_lt9 || document.activeElement != input))
7652        input.focus();
7653    }
7654
7655    function scrollCursorIntoView() {
7656      var coords = calculateCursorCoords();
7657      scrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
7658      if (!focused) return;
7659      var box = sizer.getBoundingClientRect(), doScroll = null;
7660      if (coords.y + box.top < 0) doScroll = true;
7661      else if (coords.y + box.top + textHeight() > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
7662      if (doScroll != null) {
7663        var hidden = cursor.style.display == "none";
7664        if (hidden) {
7665          cursor.style.display = "";
7666          cursor.style.left = coords.x + "px";
7667          cursor.style.top = (coords.y - displayOffset) + "px";
7668        }
7669        cursor.scrollIntoView(doScroll);
7670        if (hidden) cursor.style.display = "none";
7671      }
7672    }
7673    function calculateCursorCoords() {
7674      var cursor = localCoords(sel.inverted ? sel.from : sel.to);
7675      var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
7676      return {x: x, y: cursor.y, yBot: cursor.yBot};
7677    }
7678    function scrollIntoView(x1, y1, x2, y2) {
7679      var scrollPos = calculateScrollPos(x1, y1, x2, y2);
7680      if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft;}
7681      if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scroller.scrollTop = scrollPos.scrollTop;}
7682    }
7683    function calculateScrollPos(x1, y1, x2, y2) {
7684      var pl = paddingLeft(), pt = paddingTop();
7685      y1 += pt; y2 += pt; x1 += pl; x2 += pl;
7686      var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {};
7687      var docBottom = needsScrollbar() || Infinity;
7688      var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
7689      if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
7690      else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
7691
7692      var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
7693      var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
7694      var atLeft = x1 < gutterw + pl + 10;
7695      if (x1 < screenleft + gutterw || atLeft) {
7696        if (atLeft) x1 = 0;
7697        result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
7698      } else if (x2 > screenw + screenleft - 3) {
7699        result.scrollLeft = x2 + 10 - screenw;
7700      }
7701      return result;
7702    }
7703
7704    function visibleLines(scrollTop) {
7705      var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop();
7706      var fromHeight = Math.max(0, Math.floor(top / lh));
7707      var toHeight = Math.ceil((top + scroller.clientHeight) / lh);
7708      return {from: lineAtHeight(doc, fromHeight),
7709              to: lineAtHeight(doc, toHeight)};
7710    }
7711    // Uses a set of changes plus the current scroll position to
7712    // determine which DOM updates have to be made, and makes the
7713    // updates.
7714    function updateDisplay(changes, suppressCallback, scrollTop) {
7715      if (!scroller.clientWidth) {
7716        showingFrom = showingTo = displayOffset = 0;
7717        return;
7718      }
7719      // Compute the new visible window
7720      // If scrollTop is specified, use that to determine which lines
7721      // to render instead of the current scrollbar position.
7722      var visible = visibleLines(scrollTop);
7723      // Bail out if the visible area is already rendered and nothing changed.
7724      if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) {
7725        updateVerticalScroll(scrollTop);
7726        return;
7727      }
7728      var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
7729      if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
7730      if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
7731
7732      // Create a range of theoretically intact lines, and punch holes
7733      // in that using the change info.
7734      var intact = changes === true ? [] :
7735        computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
7736      // Clip off the parts that won't be visible
7737      var intactLines = 0;
7738      for (var i = 0; i < intact.length; ++i) {
7739        var range = intact[i];
7740        if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
7741        if (range.to > to) range.to = to;
7742        if (range.from >= range.to) intact.splice(i--, 1);
7743        else intactLines += range.to - range.from;
7744      }
7745      if (intactLines == to - from && from == showingFrom && to == showingTo) {
7746        updateVerticalScroll(scrollTop);
7747        return;
7748      }
7749      intact.sort(function(a, b) {return a.domStart - b.domStart;});
7750
7751      var th = textHeight(), gutterDisplay = gutter.style.display;
7752      lineDiv.style.display = "none";
7753      patchDisplay(from, to, intact);
7754      lineDiv.style.display = gutter.style.display = "";
7755
7756      var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
7757      // This is just a bogus formula that detects when the editor is
7758      // resized or the font size changes.
7759      if (different) lastSizeC = scroller.clientHeight + th;
7760      if (from != showingFrom || to != showingTo && options.onViewportChange)
7761        setTimeout(function(){
7762          if (options.onViewportChange) options.onViewportChange(instance, from, to);
7763        });
7764      showingFrom = from; showingTo = to;
7765      displayOffset = heightAtLine(doc, from);
7766      startWorker(100);
7767
7768      // Since this is all rather error prone, it is honoured with the
7769      // only assertion in the whole file.
7770      if (lineDiv.childNodes.length != showingTo - showingFrom)
7771        throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
7772                        " nodes=" + lineDiv.childNodes.length);
7773
7774      function checkHeights() {
7775        var curNode = lineDiv.firstChild, heightChanged = false;
7776        doc.iter(showingFrom, showingTo, function(line) {
7777          // Work around bizarro IE7 bug where, sometimes, our curNode
7778          // is magically replaced with a new node in the DOM, leaving
7779          // us with a reference to an orphan (nextSibling-less) node.
7780          if (!curNode) return;
7781          if (!line.hidden) {
7782            var height = Math.round(curNode.offsetHeight / th) || 1;
7783            if (line.height != height) {
7784              updateLineHeight(line, height);
7785              gutterDirty = heightChanged = true;
7786            }
7787          }
7788          curNode = curNode.nextSibling;
7789        });
7790        return heightChanged;
7791      }
7792
7793      if (options.lineWrapping) checkHeights();
7794
7795      gutter.style.display = gutterDisplay;
7796      if (different || gutterDirty) {
7797        // If the gutter grew in size, re-check heights. If those changed, re-draw gutter.
7798        updateGutter() && options.lineWrapping && checkHeights() && updateGutter();
7799      }
7800      updateVerticalScroll(scrollTop);
7801      updateSelection();
7802      if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
7803      return true;
7804    }
7805
7806    function computeIntact(intact, changes) {
7807      for (var i = 0, l = changes.length || 0; i < l; ++i) {
7808        var change = changes[i], intact2 = [], diff = change.diff || 0;
7809        for (var j = 0, l2 = intact.length; j < l2; ++j) {
7810          var range = intact[j];
7811          if (change.to <= range.from && change.diff)
7812            intact2.push({from: range.from + diff, to: range.to + diff,
7813                          domStart: range.domStart});
7814          else if (change.to <= range.from || change.from >= range.to)
7815            intact2.push(range);
7816          else {
7817            if (change.from > range.from)
7818              intact2.push({from: range.from, to: change.from, domStart: range.domStart});
7819            if (change.to < range.to)
7820              intact2.push({from: change.to + diff, to: range.to + diff,
7821                            domStart: range.domStart + (change.to - range.from)});
7822          }
7823        }
7824        intact = intact2;
7825      }
7826      return intact;
7827    }
7828
7829    function patchDisplay(from, to, intact) {
7830      function killNode(node) {
7831        var tmp = node.nextSibling;
7832        node.parentNode.removeChild(node);
7833        return tmp;
7834      }
7835      // The first pass removes the DOM nodes that aren't intact.
7836      if (!intact.length) removeChildren(lineDiv);
7837      else {
7838        var domPos = 0, curNode = lineDiv.firstChild, n;
7839        for (var i = 0; i < intact.length; ++i) {
7840          var cur = intact[i];
7841          while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
7842          for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
7843        }
7844        while (curNode) curNode = killNode(curNode);
7845      }
7846      // This pass fills in the lines that actually changed.
7847      var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
7848      doc.iter(from, to, function(line) {
7849        if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
7850        if (!nextIntact || nextIntact.from > j) {
7851          if (line.hidden) var lineElement = elt("pre");
7852          else {
7853            var lineElement = lineContent(line);
7854            if (line.className) lineElement.className = line.className;
7855            // Kludge to make sure the styled element lies behind the selection (by z-index)
7856            if (line.bgClassName) {
7857              var pre = elt("pre", "\u00a0", line.bgClassName, "position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2");
7858              lineElement = elt("div", [pre, lineElement], null, "position: relative");
7859            }
7860          }
7861          lineDiv.insertBefore(lineElement, curNode);
7862        } else {
7863          curNode = curNode.nextSibling;
7864        }
7865        ++j;
7866      });
7867    }
7868
7869    function updateGutter() {
7870      if (!options.gutter && !options.lineNumbers) return;
7871      var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
7872      gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
7873      var fragment = document.createDocumentFragment(), i = showingFrom, normalNode;
7874      doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
7875        if (line.hidden) {
7876          fragment.appendChild(elt("pre"));
7877        } else {
7878          var marker = line.gutterMarker;
7879          var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null;
7880          if (marker && marker.text)
7881            text = marker.text.replace("%N%", text != null ? text : "");
7882          else if (text == null)
7883            text = "\u00a0";
7884          var markerElement = fragment.appendChild(elt("pre", null, marker && marker.style));
7885          markerElement.innerHTML = text;
7886          for (var j = 1;
7886 j < line.height; ++j) {
7887            markerElement.appendChild(elt("br"));
7888            markerElement.appendChild(document.createTextNode("\u00a0"));
7889          }
7890          if (!marker) normalNode = i;
7891        }
7892        ++i;
7893      });
7894      gutter.style.display = "none";
7895      removeChildrenAndAdd(gutterText, fragment);
7896      // Make sure scrolling doesn't cause number gutter size to pop
7897      if (normalNode != null && options.lineNumbers) {
7898        var node = gutterText.childNodes[normalNode - showingFrom];
7899        var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = "";
7900        while (val.length + pad.length < minwidth) pad += "\u00a0";
7901        if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild);
7902      }
7903      gutter.style.display = "";
7904      var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2;
7905      lineSpace.style.marginLeft = gutter.offsetWidth + "px";
7906      gutterDirty = false;
7907      return resized;
7908    }
7909    function updateSelection() {
7910      var collapsed = posEq(sel.from, sel.to);
7911      var fromPos = localCoords(sel.from, true);
7912      var toPos = collapsed ? fromPos : localCoords(sel.to, true);
7913      var headPos = sel.inverted ? fromPos : toPos, th = textHeight();
7914      var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
7915      inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
7916      inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";
7917      if (collapsed || options.showCursorWhenSelecting) {
7918        cursor.style.top = headPos.y + "px";
7919        cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px";
7920        cursor.style.display = "";
7921      } else {
7922        cursor.style.display = "none";
7923      }
7924      if (!collapsed) {
7925        var sameLine = fromPos.y == toPos.y, fragment = document.createDocumentFragment();
7926        var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth;
7927        var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight;
7928        var add = function(left, top, right, height) {
7929          var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px"
7930                                  : "right: " + (right - 1) + "px";
7931          fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
7932                                   "px; top: " + top + "px; " + rstyle + "; height: " + height + "px"));
7933        };
7934        if (sel.from.ch && fromPos.y >= 0) {
7935          var right = sameLine ? clientWidth - toPos.x : 0;
7936          add(fromPos.x, fromPos.y, right, th);
7937        }
7938        var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0));
7939        var middleHeight = Math.min(toPos.y, clientHeight) - middleStart;
7940        if (middleHeight > 0.2 * th)
7941          add(0, middleStart, 0, middleHeight);
7942        if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th)
7943          add(0, toPos.y, clientWidth - toPos.x, th);
7944        removeChildrenAndAdd(selectionDiv, fragment);
7945        selectionDiv.style.display = "";
7946      } else {
7947        selectionDiv.style.display = "none";
7948      }
7949    }
7950
7951    function setShift(val) {
7952      if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
7953      else shiftSelecting = null;
7954    }
7955    function setSelectionUser(from, to) {
7956      var sh = shiftSelecting && clipPos(shiftSelecting);
7957      if (sh) {
7958        if (posLess(sh, from)) from = sh;
7959        else if (posLess(to, sh)) to = sh;
7960      }
7961      setSelection(from, to);
7962      userSelChange = true;
7963    }
7964    // Update the selection. Last two args are only used by
7965    // updateLines, since they have to be expressed in the line
7966    // numbers before the update.
7967    function setSelection(from, to, oldFrom, oldTo) {
7968      goalColumn = null;
7969      if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
7970      if (posEq(sel.from, from) && posEq(sel.to, to)) return;
7971      if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
7972
7973      // Skip over hidden lines.
7974      if (from.line != oldFrom) {
7975        var from1 = skipHidden(from, oldFrom, sel.from.ch);
7976        // If there is no non-hidden line left, force visibility on current line
7977        if (!from1) setLineHidden(from.line, false);
7978        else from = from1;
7979      }
7980      if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
7981
7982      if (posEq(from, to)) sel.inverted = false;
7983      else if (posEq(from, sel.to)) sel.inverted = false;
7984      else if (posEq(to, sel.from)) sel.inverted = true;
7985
7986      if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) {
7987        var head = sel.inverted ? from : to;
7988        if (head.line != sel.from.line && sel.from.line < doc.size) {
7989          var oldLine = getLine(sel.from.line);
7990          if (/^\s+$/.test(oldLine.text))
7991            setTimeout(operation(function() {
7992              if (oldLine.parent && /^\s+$/.test(oldLine.text)) {
7993                var no = lineNo(oldLine);
7994                replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length});
7995              }
7996            }, 10));
7997        }
7998      }
7999
8000      sel.from = from; sel.to = to;
8001      selectionChanged = true;
8002    }
8003    function skipHidden(pos, oldLine, oldCh) {
8004      function getNonHidden(dir) {
8005        var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
8006        while (lNo != end) {
8007          var line = getLine(lNo);
8008          if (!line.hidden) {
8009            var ch = pos.ch;
8010            if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length;
8011            return {line: lNo, ch: ch};
8012          }
8013          lNo += dir;
8014        }
8015      }
8016      var line = getLine(pos.line);
8017      var toEnd = pos.ch == line.text.length && pos.ch != oldCh;
8018      if (!line.hidden) return pos;
8019      if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
8020      else return getNonHidden(-1) || getNonHidden(1);
8021    }
8022    function setCursor(line, ch, user) {
8023      var pos = clipPos({line: line, ch: ch || 0});
8024      (user ? setSelectionUser : setSelection)(pos, pos);
8025    }
8026
8027    function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
8028    function clipPos(pos) {
8029      if (pos.line < 0) return {line: 0, ch: 0};
8030      if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
8031      var ch = pos.ch, linelen = getLine(pos.line).text.length;
8032      if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
8033      else if (ch < 0) return {line: pos.line, ch: 0};
8034      else return pos;
8035    }
8036
8037    function findPosH(dir, unit) {
8038      var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
8039      var lineObj = getLine(line);
8040      function findNextLine() {
8041        for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
8042          var lo = getLine(l);
8043          if (!lo.hidden) { line = l; lineObj = lo; return true; }
8044        }
8045      }
8046      function moveOnce(boundToLine) {
8047        if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
8048          if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
8049          else return false;
8050        } else ch += dir;
8051        return true;
8052      }
8053      if (unit == "char") moveOnce();
8054      else if (unit == "column") moveOnce(true);
8055      else if (unit == "word") {
8056        var sawWord = false;
8057        for (;;) {
8058          if (dir < 0) if (!moveOnce()) break;
8059          if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
8060          else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
8061          if (dir > 0) if (!moveOnce()) break;
8062        }
8063      }
8064      return {line: line, ch: ch};
8065    }
8066    function moveH(dir, unit) {
8067      var pos = dir < 0 ? sel.from : sel.to;
8068      if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
8069      setCursor(pos.line, pos.ch, true);
8070    }
8071    function deleteH(dir, unit) {
8072      if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
8073      else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
8074      else replaceRange("", sel.from, findPosH(dir, unit));
8075      userSelChange = true;
8076    }
8077    function moveV(dir, unit) {
8078      var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
8079      if (goalColumn != null) pos.x = goalColumn;
8080      if (unit == "page") {
8081        var screen = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight);
8082        var target = coordsChar(pos.x, pos.y + screen * dir);
8083      } else if (unit == "line") {
8084        var th = textHeight();
8085        var target = coordsChar(pos.x, pos.y + .5 * th + dir * th);
8086      }
8087      if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y;
8088      setCursor(target.line, target.ch, true);
8089      goalColumn = pos.x;
8090    }
8091
8092    function findWordAt(pos) {
8093      var line = getLine(pos.line).text;
8094      var start = pos.ch, end = pos.ch;
8095      if (line) {
8096        if (pos.after === false || end == line.length) --start; else ++end;
8097        var startChar = line.charAt(start);
8098        var check = isWordChar(startChar) ? isWordChar :
8099                    /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} :
8100                    function(ch) {return !/\s/.test(ch) && isWordChar(ch);};
8101        while (start > 0 && check(line.charAt(start - 1))) --start;
8102        while (end < line.length && check(line.charAt(end))) ++end;
8103      }
8104      return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}};
8105    }
8106    function selectLine(line) {
8107      setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0}));
8108    }
8109    function indentSelected(mode) {
8110      if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
8111      var e = sel.to.line - (sel.to.ch ? 0 : 1);
8112      for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
8113    }
8114
8115    function indentLine(n, how) {
8116      if (!how) how = "add";
8117      if (how == "smart") {
8118        if (!mode.indent) how = "prev";
8119        else var state = getStateBefore(n);
8120      }
8121
8122      var line = getLine(n), curSpace = line.indentation(options.tabSize),
8123          curSpaceString = line.text.match(/^\s*/)[0], indentation;
8124      if (how == "smart") {
8125        indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
8126        if (indentation == Pass) how = "prev";
8127      }
8128      if (how == "prev") {
8129        if (n) indentation = getLine(n-1).indentation(options.tabSize);
8130        else indentation = 0;
8131      }
8132      else if (how == "add") indentation = curSpace + options.indentUnit;
8133      else if (how == "subtract") indentation = curSpace - options.indentUnit;
8134      indentation = Math.max(0, indentation);
8135      var diff = indentation - curSpace;
8136
8137      var indentString = "", pos = 0;
8138      if (options.indentWithTabs)
8139        for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
8140      if (pos < indentation) indentString += spaceStr(indentation - pos);
8141
8142      if (indentString != curSpaceString)
8143        replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
8144      line.stateAfter = null;
8145    }
8146
8147    function loadMode() {
8148      mode = CodeMirror.getMode(options, options.mode);
8149      doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
8150      frontier = 0;
8151      startWorker(100);
8152    }
8153    function gutterChanged() {
8154      var visible = options.gutter || options.lineNumbers;
8155      gutter.style.display = visible ? "" : "none";
8156      if (visible) gutterDirty = true;
8157      else lineDiv.parentNode.style.marginLeft = 0;
8158    }
8159    function wrappingChanged(from, to) {
8160      if (options.lineWrapping) {
8161        wrapper.className += " CodeMirror-wrap";
8162        var perLine = scroller.clientWidth / charWidth() - 3;
8163        doc.iter(0, doc.size, function(line) {
8164          if (line.hidden) return;
8165          var guess = Math.ceil(line.text.length / perLine) || 1;
8166          if (guess != 1) updateLineHeight(line, guess);
8167        });
8168        lineSpace.style.minWidth = widthForcer.style.left = "";
8169      } else {
8170        wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
8171        computeMaxLength();
8172        doc.iter(0, doc.size, function(line) {
8173          if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
8174        });
8175      }
8176      changes.push({from: 0, to: doc.size});
8177    }
8178    function themeChanged() {
8179      scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") +
8180        options.theme.replace(/(^|\s)\s*/g, " cm-s-");
8181    }
8182    function keyMapChanged() {
8183      var style = keyMap[options.keyMap].style;
8184      wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
8185        (style ? " cm-keymap-" + style : "");
8186    }
8187
8188    function TextMarker(type, style) { this.lines = []; this.type = type; if (style) this.style = style; }
8189    TextMarker.prototype.clear = operation(function() {
8190      var min, max;
8191      for (var i = 0; i < this.lines.length; ++i) {
8192        var line = this.lines[i];
8193        var span = getMarkedSpanFor(line.markedSpans, this);
8194        if (span.from != null) min = lineNo(line);
8195        if (span.to != null) max = lineNo(line);
8196        line.markedSpans = removeMarkedSpan(line.markedSpans, span);
8197      }
8198      if (min != null) changes.push({from: min, to: max + 1});
8199      this.lines.length = 0;
8200      this.explicitlyCleared = true;
8201    });
8202    TextMarker.prototype.find = function() {
8203      var from, to;
8204      for (var i = 0; i < this.lines.length; ++i) {
8205        var line = this.lines[i];
8206        var span = getMarkedSpanFor(line.markedSpans, this);
8207        if (span.from != null || span.to != null) {
8208          var found = lineNo(line);
8209          if (span.from != null) from = {line: found, ch: span.from};
8210          if (span.to != null) to = {line: found, ch: span.to};
8211        }
8212      }
8213      if (this.type == "bookmark") return from;
8214      return from && {from: from, to: to};
8215    };
8216
8217    function markText(from, to, className, options) {
8218      from = clipPos(from); to = clipPos(to);
8219      var marker = new TextMarker("range", className);
8220      if (options) for (var opt in options) if (options.hasOwnProperty(opt))
8221        marker[opt] = options[opt];
8222      var curLine = from.line;
8223      doc.iter(curLine, to.line + 1, function(line) {
8224        var span = {from: curLine == from.line ? from.ch : null,
8225                    to: curLine == to.line ? to.ch : null,
8226                    marker: marker};
8227        line.markedSpans = (line.markedSpans || []).concat([span]);
8228        marker.lines.push(line);
8229        ++curLine;
8230      });
8231      changes.push({from: from.line, to: to.line + 1});
8232      return marker;
8233    }
8234
8235    function setBookmark(pos) {
8236      pos = clipPos(pos);
8237      var marker = new TextMarker("bookmark"), line = getLine(pos.line);
8238      history.addChange(pos.line, 1, [newHL(line.text, line.markedSpans)], true);
8239      var span = {from: pos.ch, to: pos.ch, marker: marker};
8240      line.markedSpans = (line.markedSpans || []).concat([span]);
8241      marker.lines.push(line);
8242      return marker;
8243    }
8244
8245    function findMarksAt(pos) {
8246      pos = clipPos(pos);
8247      var markers = [], spans = getLine(pos.line).markedSpans;
8248      if (spans) for (var i = 0; i < spans.length; ++i) {
8249        var span = spans[i];
8250        if ((span.from == null || span.from <= pos.ch) &&
8251            (span.to == null || span.to >= pos.ch))
8252          markers.push(span.marker);
8253      }
8254      return markers;
8255    }
8256
8257    function addGutterMarker(line, text, className) {
8258      if (typeof line == "number") line = getLine(clipLine(line));
8259      line.gutterMarker = {text: text, style: className};
8260      gutterDirty = true;
8261      return line;
8262    }
8263    function removeGutterMarker(line) {
8264      if (typeof line == "number") line = getLine(clipLine(line));
8265      line.gutterMarker = null;
8266      gutterDirty = true;
8267    }
8268
8269    function changeLine(handle, op) {
8270      var no = handle, line = handle;
8271      if (typeof handle == "number") line = getLine(clipLine(handle));
8272      else no = lineNo(handle);
8273      if (no == null) return null;
8274      if (op(line, no)) changes.push({from: no, to: no + 1});
8275      else return null;
8276      return line;
8277    }
8278    function setLineClass(handle, className, bgClassName) {
8279      return changeLine(handle, function(line) {
8280        if (line.className != className || line.bgClassName != bgClassName) {
8281          line.className = className;
8282          line.bgClassName = bgClassName;
8283          return true;
8284        }
8285      });
8286    }
8287    function setLineHidden(handle, hidden) {
8288      return changeLine(handle, function(line, no) {
8289        if (line.hidden != hidden) {
8290          line.hidden = hidden;
8291          if (!options.lineWrapping) {
8292            if (hidden && line.text.length == maxLine.text.length) {
8293              updateMaxLine = true;
8294            } else if (!hidden && line.text.length > maxLine.text.length) {
8295              maxLine = line; updateMaxLine = false;
8296            }
8297          }
8298          updateLineHeight(line, hidden ? 0 : 1);
8299          var fline = sel.from.line, tline = sel.to.line;
8300          if (hidden && (fline == no || tline == no)) {
8301            var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from;
8302            var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to;
8303            // Can't hide the last visible line, we'd have no place to put the cursor
8304            if (!to) return;
8305            setSelection(from, to);
8306          }
8307          return (gutterDirty = true);
8308        }
8309      });
8310    }
8311
8312    function lineInfo(line) {
8313      if (typeof line == "number") {
8314        if (!isLine(line)) return null;
8315        var n = line;
8316        line = getLine(line);
8317        if (!line) return null;
8318      } else {
8319        var n = lineNo(line);
8320        if (n == null) return null;
8321      }
8322      var marker = line.gutterMarker;
8323      return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
8324              markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName};
8325    }
8326
8327    function measureLine(line, ch) {
8328      if (ch == 0) return {top: 0, left: 0};
8329      var pre = lineContent(line, ch);
8330      removeChildrenAndAdd(measure, pre);
8331      var anchor = pre.anchor;
8332      var top = anchor.offsetTop, left = anchor.offsetLeft;
8333      // Older IEs report zero offsets for spans directly after a wrap
8334      if (ie && top == 0 && left == 0) {
8335        var backup = elt("span", "x");
8336        anchor.parentNode.insertBefore(backup, anchor.nextSibling);
8337        top = backup.offsetTop;
8338      }
8339      return {top: top, left: left};
8340    }
8341    function localCoords(pos, inLineWrap) {
8342      var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
8343      if (pos.ch == 0) x = 0;
8344      else {
8345        var sp = measureLine(getLine(pos.line), pos.ch);
8346        x = sp.left;
8347        if (options.lineWrapping) y += Math.max(0, sp.top);
8348      }
8349      return {x: x, y: y, yBot: y + lh};
8350    }
8351    // Coords must be lineSpace-local
8352    function coordsChar(x, y) {
8353      var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
8354      if (heightPos < 0) return {line: 0, ch: 0};
8355      var lineNo = lineAtHeight(doc, heightPos);
8356      if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
8357      var lineObj = getLine(lineNo), text = lineObj.text;
8358      var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
8359      if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
8360      var wrongLine = false;
8361      function getX(len) {
8362        var sp = measureLine(lineObj, len);
8363        if (tw) {
8364          var off = Math.round(sp.top / th);
8365          wrongLine = off != innerOff;
8366          return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
8367        }
8368        return sp.left;
8369      }
8370      var from = 0, fromX = 0, to = text.length, toX;
8371      // Guess a suitable upper bound for our search.
8372      var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
8373      for (;;) {
8374        var estX = getX(estimated);
8375        if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
8376        else {toX = estX; to = estimated; break;}
8377      }
8378      if (x > toX) return {line: lineNo, ch: to};
8379      // Try to guess a suitable lower bound as well.
8380      estimated = Math.floor(to * 0.8); estX = getX(estimated);
8381      if (estX < x) {from = estimated; fromX = estX;}
8382      // Do a binary search between these bounds.
8383      for (;;) {
8384        if (to - from <= 1) {
8385          var after = x - fromX < toX - x;
8386          return {line: lineNo, ch: after ? from : to, after: after};
8387        }
8388        var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
8389        if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; }
8390        else {from = middle; fromX = middleX;}
8391      }
8392    }
8393    function pageCoords(pos) {
8394      var local = localCoords(pos, true), off = eltOffset(lineSpace);
8395      return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
8396    }
8397
8398    var cachedHeight, cachedHeightFor, measurePre;
8399    function textHeight() {
8400      if (measurePre == null) {
8401        measurePre = elt("pre");
8402        for (var i = 0; i < 49; ++i) {
8403          measurePre.appendChild(document.createTextNode("x"));
8404          measurePre.appendChild(elt("br"));
8405        }
8406        measurePre.appendChild(document.createTextNode("x"));
8407      }
8408      var offsetHeight = lineDiv.clientHeight;
8409      if (offsetHeight == cachedHeightFor) return cachedHeight;
8410      cachedHeightFor = offsetHeight;
8411      removeChildrenAndAdd(measure, measurePre.cloneNode(true));
8412      cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
8413      removeChildren(measure);
8414      return cachedHeight;
8415    }
8416    var cachedWidth, cachedWidthFor = 0;
8417    function charWidth() {
8418      if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
8419      cachedWidthFor = scroller.clientWidth;
8420      var anchor = elt("span", "x");
8421      var pre = elt("pre", [anchor]);
8422      removeChildrenAndAdd(measure, pre);
8423      return (cachedWidth = anchor.offsetWidth || 10);
8424    }
8425    function paddingTop() {return lineSpace.offsetTop;}
8426    function paddingLeft() {return lineSpace.offsetLeft;}
8427
8428    function posFromMouse(e, liberal) {
8429      var offW = eltOffset(scroller, true), x, y;
8430      // Fails unpredictably on IE[67] when mouse is dragged around quickly.
8431      try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
8432      // This is a mess of a heuristic to try and determine whether a
8433      // scroll-bar was clicked or not, and to return null if one was
8434      // (and !liberal).
8435      if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
8436        return null;
8437      var offL = eltOffset(lineSpace, true);
8438      return coordsChar(x - offL.left, y - offL.top);
8439    }
8440    var detectingSelectAll;
8441    function onContextMenu(e) {
8442      var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop;
8443      if (!pos || opera) return; // Opera is difficult.
8444      if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
8445        operation(setCursor)(pos.line, pos.ch);
8446
8447      var oldCSS = input.style.cssText;
8448      inputDiv.style.position = "absolute";
8449      input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
8450        "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
8451        "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
8452      focusInput();
8453      resetInput(true);
8454      // Adds "Select all" to context menu in FF
8455      if (posEq(sel.from, sel.to)) input.value = prevInput = " ";
8456
8457      function rehide() {
8458        inputDiv.style.position = "relative";
8459        input.style.cssText = oldCSS;
8460        if (ie_lt9) scrollbar.scrollTop = scrollPos;
8461        slowPoll();
8462
8463        // Try to detect the user choosing select-all 
8464        if (input.selectionStart != null) {
8465          clearTimeout(detectingSelectAll);
8466          var extval = input.value = " " + (posEq(sel.from, sel.to) ? "" : input.value), i = 0;
8467          prevInput = " ";
8468          input.selectionStart = 1; input.selectionEnd = extval.length;
8469          detectingSelectAll = setTimeout(function poll(){
8470            if (prevInput == " " && input.selectionStart == 0)
8471              operation(commands.selectAll)(instance);
8472            else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
8473            else resetInput();
8474          }, 200);
8475        }
8476      }
8477
8478      if (gecko) {
8479        e_stop(e);
8480        var mouseup = connect(window, "mouseup", function() {
8481          mouseup();
8482          setTimeout(rehide, 20);
8483        }, true);
8484      } else {
8485        setTimeout(rehide, 50);
8486      }
8487    }
8488
8489    // Cursor-blinking
8490    function restartBlink() {
8491      clearInterval(blinker);
8492      var on = true;
8493      cursor.style.visibility = "";
8494      blinker = setInterval(function() {
8495        cursor.style.visibility = (on = !on) ? "" : "hidden";
8496      }, options.cursorBlinkRate);
8497    }
8498
8499    var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
8500    function matchBrackets(autoclear) {
8501      var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
8502      var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
8503      if (!match) return;
8504      var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
8505      for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)
8506        if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}
8507
8508      var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
8509      function scan(line, from, to) {
8510        if (!line.text) return;
8511        var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;
8512        for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {
8513          var text = st[i];
8514          if (st[i+1] != style) {pos += d * text.length; continue;}
8515          for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {
8516            if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {
8517              var match = matching[cur];
8518              if (match.charAt(1) == ">" == forward) stack.push(cur);
8519              else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
8520              else if (!stack.length) return {pos: pos, match: true};
8521            }
8522          }
8523        }
8524      }
8525      for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
8526        var line = getLine(i), first = i == head.line;
8527        var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
8528        if (found) break;
8529      }
8530      if (!found) found = {pos: null, match: false};
8531      var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
8532      var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
8533          two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
8534      var clear = operation(function(){one.clear(); two && two.clear();});
8535      if (autoclear) setTimeout(clear, 800);
8536      else bracketHighlighted = clear;
8537    }
8538
8539    // Finds the line to start with when starting a parse. Tries to
8540    // find a line with a stateAfter, so that it can start with a
8541    // valid state. If that fails, it returns the line with the
8542    // smallest indentation, which tends to need the least context to
8543    // parse correctly.
8544    function findStartLine(n) {
8545      var minindent, minline;
8546      for (var search = n, lim = n - 40; search > lim; --search) {
8547        if (search == 0) return 0;
8548        var line = getLine(search-1);
8549        if (line.stateAfter) return search;
8550        var indented = line.indentation(options.tabSize);
8551        if (minline == null || minindent > indented) {
8552          minline = search - 1;
8553          minindent = indented;
8554        }
8555      }
8556      return minline;
8557    }
8558    function getStateBefore(n) {
8559      var pos = findStartLine(n), state = pos && getLine(pos-1).stateAfter;
8560      if (!state) state = startState(mode);
8561      else state = copyState(mode, state);
8562      doc.iter(pos, n, function(line) {
8563        line.process(mode, state, options.tabSize);
8564        line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(mode, state) : null;
8565      });
8566      return state;
8567    }
8568    function highlightWorker() {
8569      if (frontier >= showingTo) return;
8570      var end = +new Date + options.workTime, state = copyState(mode, get
8570StateBefore(frontier));
8571      var startFrontier = frontier;
8572      doc.iter(frontier, showingTo, function(line) {
8573        if (frontier >= showingFrom) { // Visible
8574          line.highlight(mode, state, options.tabSize);
8575          line.stateAfter = copyState(mode, state);
8576        } else {
8577          line.process(mode, state, options.tabSize);
8578          line.stateAfter = frontier % 5 == 0 ? copyState(mode, state) : null;
8579        }
8580        ++frontier;
8581        if (+new Date > end) {
8582          startWorker(options.workDelay);
8583          return true;
8584        }
8585      });
8586      if (showingTo > startFrontier && frontier >= showingFrom)
8587        operation(function() {changes.push({from: startFrontier, to: frontier});})();
8588    }
8589    function startWorker(time) {
8590      if (frontier < showingTo)
8591        highlight.set(time, highlightWorker);
8592    }
8593
8594    // Operations are used to wrap changes in such a way that each
8595    // change won't have to update the cursor and display (which would
8596    // be awkward, slow, and error-prone), but instead updates are
8597    // batched and then all combined and executed at once.
8598    function startOperation() {
8599      updateInput = userSelChange = textChanged = null;
8600      changes = []; selectionChanged = false; callbacks = [];
8601    }
8602    function endOperation() {
8603      if (updateMaxLine) computeMaxLength();
8604      if (maxLineChanged && !options.lineWrapping) {
8605        var cursorWidth = widthForcer.offsetWidth, left = measureLine(maxLine, maxLine.text.length).left;
8606        if (!ie_lt8) {
8607          widthForcer.style.left = left + "px";
8608          lineSpace.style.minWidth = (left + cursorWidth) + "px";
8609        }
8610        maxLineChanged = false;
8611      }
8612      var newScrollPos, updated;
8613      if (selectionChanged) {
8614        var coords = calculateCursorCoords();
8615        newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot);
8616      }
8617      if (changes.length || newScrollPos && newScrollPos.scrollTop != null)
8618        updated = updateDisplay(changes, true, newScrollPos && newScrollPos.scrollTop);
8619      if (!updated) {
8620        if (selectionChanged) updateSelection();
8621        if (gutterDirty) updateGutter();
8622      }
8623      if (newScrollPos) scrollCursorIntoView();
8624      if (selectionChanged) restartBlink();
8625
8626      if (focused && (updateInput === true || (updateInput !== false && selectionChanged)))
8627        resetInput(userSelChange);
8628
8629      if (selectionChanged && options.matchBrackets)
8630        setTimeout(operation(function() {
8631          if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
8632          if (posEq(sel.from, sel.to)) matchBrackets(false);
8633        }), 20);
8634      var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks
8635      if (textChanged && options.onChange && instance)
8636        options.onChange(instance, textChanged);
8637      if (sc && options.onCursorActivity)
8638        options.onCursorActivity(instance);
8639      for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
8640      if (updated && options.onUpdate) options.onUpdate(instance);
8641    }
8642    var nestedOperation = 0;
8643    function operation(f) {
8644      return function() {
8645        if (!nestedOperation++) startOperation();
8646        try {var result = f.apply(this, arguments);}
8647        finally {if (!--nestedOperation) endOperation();}
8648        return result;
8649      };
8650    }
8651
8652    function compoundChange(f) {
8653      history.startCompound();
8654      try { return f(); } finally { history.endCompound(); }
8655    }
8656
8657    for (var ext in extensions)
8658      if (extensions.propertyIsEnumerable(ext) &&
8659          !instance.propertyIsEnumerable(ext))
8660        instance[ext] = extensions[ext];
8661    for (var i = 0; i < initHooks.length; ++i) initHooks[i](instance);
8662    return instance;
8663  } // (end of function CodeMirror)
8664
8665  // The default configuration options.
8666  CodeMirror.defaults = {
8667    value: "",
8668    mode: null,
8669    theme: "default",
8670    indentUnit: 2,
8671    indentWithTabs: false,
8672    smartIndent: true,
8673    tabSize: 4,
8674    keyMap: "default",
8675    extraKeys: null,
8676    electricChars: true,
8677    autoClearEmptyLines: false,
8678    onKeyEvent: null,
8679    onDragEvent: null,
8680    lineWrapping: false,
8681    lineNumbers: false,
8682    gutter: false,
8683    fixedGutter: false,
8684    firstLineNumber: 1,
8685    showCursorWhenSelecting: false,
8686    readOnly: false,
8687    dragDrop: true,
8688    onChange: null,
8689    onCursorActivity: null,
8690    onViewportChange: null,
8691    onGutterClick: null,
8692    onUpdate: null,
8693    onFocus: null, onBlur: null, onScroll: null,
8694    matchBrackets: false,
8695    cursorBlinkRate: 530,
8696    workTime: 100,
8697    workDelay: 200,
8698    pollInterval: 100,
8699    undoDepth: 40,
8700    tabindex: null,
8701    autofocus: null,
8702    lineNumberFormatter: function(integer) { return integer; }
8703  };
8704
8705  var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
8706  var mac = ios || /Mac/.test(navigator.platform);
8707  var win = /Win/.test(navigator.platform);
8708
8709  // Known modes, by name and by MIME
8710  var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
8711  CodeMirror.defineMode = function(name, mode) {
8712    if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
8713    if (arguments.length > 2) {
8714      mode.dependencies = [];
8715      for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
8716    }
8717    modes[name] = mode;
8718  };
8719  CodeMirror.defineMIME = function(mime, spec) {
8720    mimeModes[mime] = spec;
8721  };
8722  CodeMirror.resolveMode = function(spec) {
8723    if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
8724      spec = mimeModes[spec];
8725    else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
8726      return CodeMirror.resolveMode("application/xml");
8727    if (typeof spec == "string") return {name: spec};
8728    else return spec || {name: "null"};
8729  };
8730  CodeMirror.getMode = function(options, spec) {
8731    var spec = CodeMirror.resolveMode(spec);
8732    var mfactory = modes[spec.name];
8733    if (!mfactory) return CodeMirror.getMode(options, "text/plain");
8734    var modeObj = mfactory(options, spec);
8735    if (modeExtensions.hasOwnProperty(spec.name)) {
8736      var exts = modeExtensions[spec.name];
8737      for (var prop in exts) {
8738        if (!exts.hasOwnProperty(prop)) continue;
8739        if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
8740        modeObj[prop] = exts[prop];
8741      }
8742    }
8743    modeObj.name = spec.name;
8744    return modeObj;
8745  };
8746  CodeMirror.listModes = function() {
8747    var list = [];
8748    for (var m in modes)
8749      if (modes.propertyIsEnumerable(m)) list.push(m);
8750    return list;
8751  };
8752  CodeMirror.listMIMEs = function() {
8753    var list = [];
8754    for (var m in mimeModes)
8755      if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
8756    return list;
8757  };
8758
8759  var extensions = CodeMirror.extensions = {};
8760  CodeMirror.defineExtension = function(name, func) {
8761    extensions[name] = func;
8762  };
8763
8764  var initHooks = [];
8765  CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
8766
8767  var modeExtensions = CodeMirror.modeExtensions = {};
8768  CodeMirror.extendMode = function(mode, properties) {
8769    var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
8770    for (var prop in properties) if (properties.hasOwnProperty(prop))
8771      exts[prop] = properties[prop];
8772  };
8773
8774  var commands = CodeMirror.commands = {
8775    selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
8776    killLine: function(cm) {
8777      var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
8778      if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0});
8779      else cm.replaceRange("", from, sel ? to : {line: from.line});
8780    },
8781    deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
8782    undo: function(cm) {cm.undo();},
8783    redo: function(cm) {cm.redo();},
8784    goDocStart: function(cm) {cm.setCursor(0, 0, true);},
8785    goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
8786    goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
8787    goLineStartSmart: function(cm) {
8788      var cur = cm.getCursor();
8789      var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
8790      cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
8791    },
8792    goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
8793    goLineUp: function(cm) {cm.moveV(-1, "line");},
8794    goLineDown: function(cm) {cm.moveV(1, "line");},
8795    goPageUp: function(cm) {cm.moveV(-1, "page");},
8796    goPageDown: function(cm) {cm.moveV(1, "page");},
8797    goCharLeft: function(cm) {cm.moveH(-1, "char");},
8798    goCharRight: function(cm) {cm.moveH(1, "char");},
8799    goColumnLeft: function(cm) {cm.moveH(-1, "column");},
8800    goColumnRight: function(cm) {cm.moveH(1, "column");},
8801    goWordLeft: function(cm) {cm.moveH(-1, "word");},
8802    goWordRight: function(cm) {cm.moveH(1, "word");},
8803    delCharLeft: function(cm) {cm.deleteH(-1, "char");},
8804    delCharRight: function(cm) {cm.deleteH(1, "char");},
8805    delWordLeft: function(cm) {cm.deleteH(-1, "word");},
8806    delWordRight: function(cm) {cm.deleteH(1, "word");},
8807    indentAuto: function(cm) {cm.indentSelection("smart");},
8808    indentMore: function(cm) {cm.indentSelection("add");},
8809    indentLess: function(cm) {cm.indentSelection("subtract");},
8810    insertTab: function(cm) {cm.replaceSelection("\t", "end");},
8811    defaultTab: function(cm) {
8812      if (cm.somethingSelected()) cm.indentSelection("add");
8813      else cm.replaceSelection("\t", "end");
8814    },
8815    transposeChars: function(cm) {
8816      var cur = cm.getCursor(), line = cm.getLine(cur.line);
8817      if (cur.ch > 0 && cur.ch < line.length - 1)
8818        cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
8819                        {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
8820    },
8821    newlineAndIndent: function(cm) {
8822      cm.replaceSelection("\n", "end");
8823      cm.indentLine(cm.getCursor().line);
8824    },
8825    toggleOverwrite: function(cm) {cm.toggleOverwrite();}
8826  };
8827
8828  var keyMap = CodeMirror.keyMap = {};
8829  keyMap.basic = {
8830    "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
8831    "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
8832    "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
8833    "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
8834  };
8835  // Note that the save and find-related commands aren't defined by
8836  // default. Unknown commands are simply ignored.
8837  keyMap.pcDefault = {
8838    "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
8839    "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
8840    "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
8841    "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find",
8842    "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
8843    "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
8844    fallthrough: "basic"
8845  };
8846  keyMap.macDefault = {
8847    "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
8848    "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt
8848-Left": "goWordLeft",
8849    "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
8850    "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find",
8851    "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
8852    "Cmd-[": "indentLess", "Cmd-]": "indentMore",
8853    fallthrough: ["basic", "emacsy"]
8854  };
8855  keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
8856  keyMap.emacsy = {
8857    "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
8858    "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
8859    "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
8860    "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
8861  };
8862
8863  function getKeyMap(val) {
8864    if (typeof val == "string") return keyMap[val];
8865    else return val;
8866  }
8867  function lookupKey(name, extraMap, map, handle, stop) {
8868    function lookup(map) {
8869      map = getKeyMap(map);
8870      var found = map[name];
8871      if (found === false) {
8872        if (stop) stop();
8873        return true;
8874      }
8875      if (found != null && handle(found)) return true;
8876      if (map.nofallthrough) {
8877        if (stop) stop();
8878        return true;
8879      }
8880      var fallthrough = map.fallthrough;
8881      if (fallthrough == null) return false;
8882      if (Object.prototype.toString.call(fallthrough) != "[object Array]")
8883        return lookup(fallthrough);
8884      for (var i = 0, e = fallthrough.length; i < e; ++i) {
8885        if (lookup(fallthrough[i])) return true;
8886      }
8887      return false;
8888    }
8889    if (extraMap && lookup(extraMap)) return true;
8890    return lookup(map);
8891  }
8892  function isModifierKey(event) {
8893    var name = keyNames[e_prop(event, "keyCode")];
8894    return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
8895  }
8896  CodeMirror.isModifierKey = isModifierKey;
8897
8898  CodeMirror.fromTextArea = function(textarea, options) {
8899    if (!options) options = {};
8900    options.value = textarea.value;
8901    if (!options.tabindex && textarea.tabindex)
8902      options.tabindex = textarea.tabindex;
8903    // Set autofocus to true if this textarea is focused, or if it has
8904    // autofocus and no other element is focused.
8905    if (options.autofocus == null) {
8906      var hasFocus = document.body;
8907      // doc.activeElement occasionally throws on IE
8908      try { hasFocus = document.activeElement; } catch(e) {}
8909      options.autofocus = hasFocus == textarea ||
8910        textarea.getAttribute("autofocus") != null && hasFocus == document.body;
8911    }
8912
8913    function save() {textarea.value = instance.getValue();}
8914    if (textarea.form) {
8915      // Deplorable hack to make the submit method do the right thing.
8916      var rmSubmit = connect(textarea.form, "submit", save, true);
8917      var form = textarea.form, realSubmit = form.submit;
8918      textarea.form.submit = function wrappedSubmit() {
8919        save();
8920        form.submit = realSubmit;
8921        form.submit();
8922        form.submit = wrappedSubmit;
8923      };
8924    }
8925
8926    textarea.style.display = "none";
8927    var instance = CodeMirror(function(node) {
8928      textarea.parentNode.insertBefore(node, textarea.nextSibling);
8929    }, options);
8930    instance.save = save;
8931    instance.getTextArea = function() { return textarea; };
8932    instance.toTextArea = function() {
8933      save();
8934      textarea.parentNode.removeChild(instance.getWrapperElement());
8935      textarea.style.display = "";
8936      if (textarea.form) {
8937        rmSubmit();
8938        if (typeof textarea.form.submit == "function")
8939          textarea.form.submit = realSubmit;
8940      }
8941    };
8942    return instance;
8943  };
8944
8945  var gecko = /gecko\/\d/i.test(navigator.userAgent);
8946  var ie = /MSIE \d/.test(navigator.userAgent);
8947  var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
8948  var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
8949  var quirksMode = ie && document.documentMode == 5;
8950  var webkit = /WebKit\//.test(navigator.userAgent);
8951  var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
8952  var chrome = /Chrome\//.test(navigator.userAgent);
8953  var opera = /Opera\//.test(navigator.userAgent);
8954  var safari = /Apple Computer/.test(navigator.vendor);
8955  var khtml = /KHTML\//.test(navigator.userAgent);
8956  var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent);
8957
8958  var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
8959  if (opera_version) opera_version = Number(opera_version[1]);
8960  // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
8961  var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
8962
8963  // Utility functions for working with state. Exported because modes
8964  // sometimes need to do this.
8965  function copyState(mode, state) {
8966    if (state === true) return state;
8967    if (mode.copyState) return mode.copyState(state);
8968    var nstate = {};
8969    for (var n in state) {
8970      var val = state[n];
8971      if (val instanceof Array) val = val.concat([]);
8972      nstate[n] = val;
8973    }
8974    return nstate;
8975  }
8976  CodeMirror.copyState = copyState;
8977  function startState(mode, a1, a2) {
8978    return mode.startState ? mode.startState(a1, a2) : true;
8979  }
8980  CodeMirror.startState = startState;
8981  CodeMirror.innerMode = function(mode, state) {
8982    while (mode.innerMode) {
8983      var info = mode.innerMode(state);
8984      state = info.state;
8985      mode = info.mode;
8986    }
8987    return info || {mode: mode, state: state};
8988  };
8989
8990  // The character stream used by a mode's parser.
8991  function StringStream(string, tabSize) {
8992    this.pos = this.start = 0;
8993    this.string = string;
8994    this.tabSize = tabSize || 8;
8995  }
8996  StringStream.prototype = {
8997    eol: function() {return this.pos >= this.string.length;},
8998    sol: function() {return this.pos == 0;},
8999    peek: function() {return this.string.charAt(this.pos) || undefined;},
9000    next: function() {
9001      if (this.pos < this.string.length)
9002        return this.string.charAt(this.pos++);
9003    },
9004    eat: function(match) {
9005      var ch = this.string.charAt(this.pos);
9006      if (typeof match == "string") var ok = ch == match;
9007      else var ok = ch && (match.test ? match.test(ch) : match(ch));
9008      if (ok) {++this.pos; return ch;}
9009    },
9010    eatWhile: function(match) {
9011      var start = this.pos;
9012      while (this.eat(match)){}
9013      return this.pos > start;
9014    },
9015    eatSpace: function() {
9016      var start = this.pos;
9017      while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
9018      return this.pos > start;
9019    },
9020    skipToEnd: function() {this.pos = this.string.length;},
9021    skipTo: function(ch) {
9022      var found = this.string.indexOf(ch, this.pos);
9023      if (found > -1) {this.pos = found; return true;}
9024    },
9025    backUp: function(n) {this.pos -= n;},
9026    column: function() {return countColumn(this.string, this.start, this.tabSize);},
9027    indentation: function() {return countColumn(this.string, null, this.tabSize);},
9028    match: function(pattern, consume, caseInsensitive) {
9029      if (typeof pattern == "string") {
9030        var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
9031        if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
9032          if (consume !== false) this.pos += pattern.length;
9033          return true;
9034        }
9035      } else {
9036        var match = this.string.slice(this.pos).match(pattern);
9037        if (match && match.index > 0) return null;
9038        if (match && consume !== false) this.pos += match[0].length;
9039        return match;
9040      }
9041    },
9042    current: function(){return this.string.slice(this.start, this.pos);}
9043  };
9044  CodeMirror.StringStream = StringStream;
9045
9046  function MarkedSpan(from, to, marker) {
9047    this.from = from; this.to = to; this.marker = marker;
9048  }
9049
9050  function getMarkedSpanFor(spans, marker) {
9051    if (spans) for (var i = 0; i < spans.length; ++i) {
9052      var span = spans[i];
9053      if (span.marker == marker) return span;
9054    }
9055  }
9056
9057  function removeMarkedSpan(spans, span) {
9058    var r;
9059    for (var i = 0; i < spans.length; ++i)
9060      if (spans[i] != span) (r || (r = [])).push(spans[i]);
9061    return r;
9062  }
9063
9064  function markedSpansBefore(old, startCh, endCh) {
9065    if (old) for (var i = 0, nw; i < old.length; ++i) {
9066      var span = old[i], marker = span.marker;
9067      var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
9068      if (startsBefore || marker.type == "bookmark" && span.from == startCh && span.from != endCh) {
9069        var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
9070        (nw || (nw = [])).push({from: span.from,
9071                                to: endsAfter ? null : span.to,
9072                                marker: marker});
9073      }
9074    }
9075    return nw;
9076  }
9077
9078  function markedSpansAfter(old, endCh) {
9079    if (old) for (var i = 0, nw; i < old.length; ++i) {
9080      var span = old[i], marker = span.marker;
9081      var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
9082      if (endsAfter || marker.type == "bookmark" && span.from == endCh) {
9083        var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
9084        (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
9085                                to: span.to == null ? null : span.to - endCh,
9086                                marker: marker});
9087      }
9088    }
9089    return nw;
9090  }
9091
9092  function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) {
9093    if (!oldFirst && !oldLast) return newText;
9094    // Get the spans that 'stick out' on both sides
9095    var first = markedSpansBefore(oldFirst, startCh);
9096    var last = markedSpansAfter(oldLast, endCh);
9097
9098    // Next, merge those two ends
9099    var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0);
9100    if (first) {
9101      // Fix up .to properties of first
9102      for (var i = 0; i < first.length; ++i) {
9103        var span = first[i];
9104        if (span.to == null) {
9105          var found = getMarkedSpanFor(last, span.marker);
9106          if (!found) span.to = startCh;
9107          else if (sameLine) span.to = found.to == null ? null : found.to + offset;
9108        }
9109      }
9110    }
9111    if (last) {
9112      // Fix up .from in last (or move them into first in case of sameLine)
9113      for (var i = 0; i < last.length; ++i) {
9114        var span = last[i];
9115        if (span.to != null) span.to += offset;
9116        if (span.from == null) {
9117          var found = getMarkedSpanFor(first, span.marker);
9118          if (!found) {
9119            span.from = offset;
9120            if (sameLine) (first || (first = [])).push(span);
9121          }
9122        } else {
9123          span.from += offset;
9124          if (sameLine) (first || (first = [])).push(span);
9125        }
9126      }
9127    }
9128
9129    var newMarkers = [newHL(newText[0], first)];
9130    if (!sameLine) {
9131      // Fill gap with whole-line-spans
9132      var gap = newText.length - 2, gapMarkers;
9133      if (gap > 0 && first)
9134        for (var i = 0; i < first.length; ++i)
9135          if (first[i].to == null)
9136            (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});
9137      for (var i = 0; i < gap; ++i)
9138        newMarkers.push(newHL(newText[i+1], gapMarkers));
9139      newMarkers.push(newHL(lst(newText), last));
9140    }
9141    return newMarkers;
9142  }
9143
9144  // hl stands for history-line, a data structure that can be either a
9145  // string (line without markers) or a {text, markedSpans} object.
9146  function hlText(val) { return typeof val == "string" ? val : val.text; }
9147  function hlSpans(val) {
9148    if (typeof val == "string") return null;
9149    var spans = val.markedSpans, out = null;
9150    for (var i = 0; i < spans.length; ++i) {
9151      if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
9152      else if (out) out.push(spans[i]);
9153    }
9154    return !out ? spans : out.length ? out : null;
9155  }
9156  function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; }
9157
9158  function detachMarkedSpans(line) {
9159    var spans = line.markedSpans;
9160    if (!spans) return;
9161    for (var i = 0; i < spans.length; ++i) {
9162      var lines = spans[i].marker.lines;
9163      var ix = indexOf(lines, line);
9164      lines.splice(ix, 1);
9165    }
9166    line.markedSpans = null;
9167  }
9168
9169  function attachMarkedSpans(line, spans) {
9170    if (!spans) return;
9171    for (var i = 0; i < spans.length; ++i)
9172      var marker = spans[i].marker.lines.push(line);
9173    line.markedSpans = spans;
9174  }
9175
9176  // When measuring the position of the end of a line, different
9177  // browsers require different approaches. If an empty span is added,
9178  // many browsers report bogus offsets. Of those, some (Webkit,
9179  // recent IE) will accept a space without moving the whole span to
9180  // the next line when wrapping it, others work with a zero-width
9181  // space.
9182  var eolSpanContent = " ";
9183  if (gecko || (ie && !ie_lt8)) eolSpanContent = "\u200b";
9184  else if (opera) eolSpanContent = "";
9185
9186  // Line objects. These hold state related to a line, including
9187  // highlighting info (the styles array).
9188  function Line(text, markedSpans) {
9189    this.text = text;
9190    this.height = 1;
9191    attachMarkedSpans(this, markedSpans);
9192  }
9193  Line.prototype = {
9194    update: function(text, markedSpans) {
9195      this.text = text;
9196      this.stateAfter = this.styles = null;
9197      detachMarkedSpans(this);
9198      attachMarkedSpans(this, markedSpans);
9199    },
9200    // Run the given mode's parser over a line, update the styles
9201    // array, which contains alternating fragments of text and CSS
9202    // classes.
9203    highlight: function(mode, state, tabSize) {
9204      var stream = new StringStream(this.text, tabSize), st = this.styles || (this.styles = []);
9205      var pos = st.length = 0;
9206      if (this.text == "" && mode.blankLine) mode.blankLine(state);
9207      while (!stream.eol()) {
9208        var style = mode.token(stream, state), substr = stream.current();
9209        stream.start = stream.pos;
9210        if (pos && st[pos-1] == style) {
9211          st[pos-2] += substr;
9212        } else if (substr) {
9213          st[pos++] = substr; st[pos++] = style;
9214        }
9215        // Give up when line is ridiculously long
9216        if (stream.pos > 5000) {
9217          st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
9218          break;
9219        }
9220      }
9221    },
9222    process: function(mode, state, tabSize) {
9223      var stream = new StringStream(this.text, tabSize);
9224      if (this.text == "" && mode.blankLine) mode.blankLine(state);
9225      while (!stream.eol() && stream.pos <= 5000) {
9226        mode.token(stream, state);
9227        stream.start = stream.pos;
9228      }
9229    },
9230    // Fetch the parser token for a given character. Useful for hacks
9231    // that want to inspect the mode state (say, for completion).
9232    getTokenAt: function(mode, state, tabSize, ch) {
9233      var txt = this.text, stream = new StringStream(txt, tabSize);
9234      while (stream.pos < ch && !stream.eol()) {
9235        stream.start = stream.pos;
9236        var style = mode.token(stream, state);
9237      }
9238      return {start: stream.start,
9239              end: stream.pos,
9240              string: stream.current(),
9241              className: style || null,
9242              state: state};
9243    },
9244    indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
9245    // Produces an HTML fragment for the line, taking selection,
9246    // marking, and highlighting into account.
9247    getContent: function(tabSize, wrapAt, compensateForWrapping) {
9248      var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
9249      var pre = elt("pre");
9250      function span_(html, text, style) {
9251        if (!text) return;
9252        // Work around a bug where, in some compat modes, IE ignores leading spaces
9253        if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
9254        first = false;
9255        if (!specials.test(text)) {
9256          col += text.length;
9257          var content = document.createTextNode(text);
9258        } else {
9259          var content = document.createDocumentFragment(), pos = 0;
9260          while (true) {
9261            specials.lastIndex = pos;
9262            var m = specials.exec(text);
9263            var skipped = m ? m.index - pos : text.length - pos;
9264            if (skipped) {
9265              content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
9266              col += skipped;
9267            }
9268            if (!m) break;
9269            pos += skipped + 1;
9270            if (m[0] == "\t") {
9271              var tabWidth = tabSize - col % tabSize;
9272              content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
9273              col += tabWidth;
9274            } else {
9275              var token = elt("span", "\u2022", "cm-invalidchar");
9276              token.title = "\\u" + m[0].charCodeAt(0).toString(16);
9277              content.appendChild(token);
9278              col += 1;
9279            }
9280          }
9281        }
9282        if (style) html.appendChild(elt("span", [content], style));
9283        else html.appendChild(content);
9284      }
9285      var span = span_;
9286      if (wrapAt != null) {
9287        var outPos = 0, anchor = pre.anchor = elt("span");
9288        span = function(html, text, style) {
9289          var l = text.length;
9290          if (wrapAt >= outPos && wrapAt < outPos + l) {
9291            var cut = wrapAt - outPos;
9292            if (cut) {
9293              span_(html, text.slice(0, cut), style);
9294              // See comment at the definition of spanAffectsWrapping
9295              if (compensateForWrapping) {
9296                var view = text.slice(cut - 1, cut + 1);
9297                if (spanAffectsWrapping.test(view)) html.appendChild(elt("wbr"));
9298                else if (!ie_lt8 && /\w\w/.test(view)) html.appendChild(document.createTextNode("\u200d"));
9299              }
9300            }
9301            html.appendChild(anchor);
9302            span_(anchor, opera ? text.slice(cut, cut + 1) : text.slice(cut), style);
9303            if (opera) span_(html, text.slice(cut + 1), style);
9304            wrapAt--;
9305            outPos += l;
9306          } else {
9307            outPos += l;
9308            span_(html, text, style);
9309            if (outPos == wrapAt && outPos == len) {
9310              setTextContent(anchor, eolSpanContent);
9311              html.appendChild(anchor);
9312            }
9313            // Stop outputting HTML when gone sufficiently far beyond measure
9314            else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){};
9315          }
9316        };
9317      }
9318
9319      var st = this.styles, allText = this.text, marked = this.markedSpans;
9320      var len = allText.length;
9321      function styleToClass(style) {
9322        if (!style) return null;
9323        return "cm-" + style.replace(/ +/g, " cm-");
9324      }
9325      if (!allText && wrapAt == null) {
9326        span(pre, " ");
9327      } else if (!marked || !marked.length) {
9328        for (var i = 0, ch = 0; ch < len; i+=2) {
9329          var str = st[i], style = st[i+1], l = str.length;
9330          if (ch + l > len) str = str.slice(0, len - ch);
9331          ch += l;
9332          span(pre, str, styleToClass(style));
9333        }
9334      } else {
9335        marked.sort(function(a, b) { return a.from - b.from; });
9336        var pos = 0, i = 0, text = "", style, sg = 0;
9337        var nextChange = marked[0].from || 0, marks = [], markpos = 0;
9338        var advanceMarks = function() {
9339          var m;
9340          while (markpos < marked.length &&
9341                 ((m = marked[markpos]).from == pos || m.from == null)) {
9342            if (m.marker.type == "range") marks.push(m);
9343            ++markpos;
9344          }
9345          nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
9346          for (var i = 0; i < marks.length; ++i) {
9347            var to = marks[i].to;
9348            if (to == null) to = Infinity;
9349            if (to == pos) marks.splice(i--, 1);
9350            else nextChange = Math.min(to, nextChange);
9351          }
9352        };
9353        var m = 0;
9354        while (pos < len) {
9355          if (nextChange == pos) advanceMarks();
9356          var upto = Math.min(len, nextChange);
9357          while (true) {
9358            if (text) {
9359              var end = pos + text.length;
9360              var appliedStyle = style;
9361              for (var j = 0; j < marks.length; ++j) {
9362                var mark = marks[j];
9363                appliedStyle = (appliedStyle ? appliedStyle + " " : "") + mark.marker.style;
9364                if (mark.marker.endStyle && mark.to === Math.min(end, upto)) appliedStyle += " " + mark.marker.endStyle;
9365                if (mark.marker.startStyle && mark.from === pos) appliedStyle += " " + mark.marker.startStyle;
9366              }
9367              span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
9368              if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
9369              pos = end;
9370            }
9371            text = st[i++]; style = styleToClass(st[i++]);
9372          }
9373        }
9374      }
9375      return pre;
9376    },
9377    cleanUp: function() {
9378      this.parent = null;
9379      detachMarkedSpans(this);
9380    }
9381  };
9382
9383  // Data structure that holds the sequence of lines.
9384  function LeafChunk(lines) {
9385    this.lines = lines;
9386    this.parent = null;
9387    for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
9388      lines[i].parent = this;
9389      height += lines[i].height;
9390    }
9391    this.height = height;
9392  }
9393  LeafChunk.prototype = {
9394    chunkSize: function() { return this.lines.length; },
9395    remove: function(at, n, callbacks) {
9396      for (var i = at, e = at + n; i < e; ++i) {
9397        var line = this.lines[i];
9398        this.height -= line.height;
9399        line.cleanUp();
9400        if (line.handlers)
9401          for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
9402      }
9403      this.lines.splice(at, n);
9404    },
9405    collapse: function(lines) {
9406      lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
9407    },
9408    insertHeight: function(at, lines, height) {
9409      this.height += height;
9410      this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
9411      for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
9412    },
9413    iterN: function(at, n, op) {
9414      for (var e = at + n; at < e; ++at)
9415        if (op(this.lines[at])) return true;
9416    }
9417  };
9418  function BranchChunk(children) {
9419    this.children = children;
9420    var size = 0, height = 0;
9421    for (var i = 0, e = children.length; i < e; ++i) {
9422      var ch = children[i];
9423      size += ch.chunkSize(); height += ch.height;
9424      ch.parent = this;
9425    }
9426    this.size = size;
9427    this.height = height;
9428    this.parent = null;
9429  }
9430  BranchChunk.prototype = {
9431    chunkSize: function() { return this.size; },
9432    remove: function(at, n, callbacks) {
9433      this.size -= n;
9434      for (var i = 0; i < this.children.length; ++i) {
9435        var child = this.children[i], sz = child.chunkSize();
9436        if (at < sz) {
9437          var rm = Math.min(n, sz - at), oldHeight = child.height;
9438          child.remove(at, rm, callbacks);
9439          this.height -= oldHeight - child.height;
9440          if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
9441          if ((n -= rm) == 0) break;
9442          at = 0;
9443        } else at -= sz;
9444      }
9445      if (this.size - n < 25) {
9446        var lines = [];
9447        this.collapse(lines);
9448        this.children = [new LeafChunk(lines)];
9449        this.children[0].parent = this;
9450      }
9451    },
9452    collapse: function(lines) {
9453      for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
9454    },
9455    insert: function(at, lines) {
9456      var height = 0;
9457      for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
9458      this.insertHeight(at, lines, height);
9459    },
9460    insertHeight: function(at, lines, height) {
9461      this.size += lines.length;
9462      this.height += height;
9463      for (var i = 0, e = this.children.length; i < e; ++i) {
9464        var child = this.children[i], sz = child.chunkSize();
9465        if (at <= sz) {
9466          child.insertHeight(at, lines, height);
9467          if (child.lines && child.lines.length > 50) {
9468            while (child.lines.length > 50) {
9469              var spilled = child.lines.splice(child.lines.length - 25, 25);
9470              var newleaf = new LeafChunk(spilled);
9471              child.height -= newleaf.height;
9472              this.children.splice(i + 1, 0, newleaf);
9473              newleaf.parent = this;
9474            }
9475            this.maybeSpill();
9476          }
9477          break;
9478        }
9479        at -= sz;
9480      }
9481    },
9482    maybeSpill: function() {
9483      if (this.children.length <= 10) return;
9484      var me = this;
9485      do {
9486        var spilled = me.children.splice(me.children.length - 5, 5);
9487        var sibling = new BranchChunk(spilled);
9488        if (!me.parent) { // Become the parent node
9489          var copy = new BranchChunk(me.children);
9490          copy.parent = me;
9491          me.children = [copy, sibling];
9492          me = copy;
9493        } else {
9494          me.size -= sibling.size;
9495          me.height -= sibling.height;
9496          var myIndex = indexOf(me.parent.children, me);
9497          me.parent.children.splice(myIndex + 1, 0, sibling);
9498        }
9499        sibling.parent = me.parent;
9500      } while (me.children.length > 10);
9501      me.parent.maybeSpill();
9502    },
9503    iter: function(from, to, op) { this.iterN(from, to - from, op); },
9504    iterN: function(at, n, op) {
9505      for (var i = 0, e = this.children.length; i < e; ++i) {
9506        var child = this.children[i], sz = child.chunkSize();
9507        if (at < sz) {
9508          var used = Math.min(n, sz - at);
9509          if (child.iterN(at, used, op)) return true;
9510          if ((n -= used) == 0) break;
9511          at = 0;
9512        } else at -= sz;
9513      }
9514    }
9515  };
9516
9517  function getLineAt(chunk, n) {
9518    while (!chunk.lines) {
9519      for (var i = 0;; ++i) {
9520        var child = chunk.children[i], sz = child.chunkSize();
9521        if (n < sz) { chunk = child; break; }
9522        n -= sz;
9523      }
9524    }
9525    return chunk.lines[n];
9526  }
9527  function lineNo(line) {
9528    if (line.parent == null) return null;
9529    var cur = line.parent, no = indexOf(cur.lines, line);
9530    for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
9531      for (var i = 0; ; ++i) {
9532        if (chunk.children[i] == cur) break;
9533        no += chunk.children[i].chunkSize();
9534      }
9535    }
9536    return no;
9537  }
9538  function lineAtHeight(chunk, h) {
9539    var n = 0;
9540    outer: do {
9541      for (var i = 0, e = chunk.children.length; i < e; ++i) {
9542        var child = chunk.children[i], ch = child.height;
9543        if (h < ch) { chunk = child; continue outer; }
9544        h -= ch;
9545        n += child.chunkSize();
9546      }
9547      return n;
9548    } while (!chunk.lines);
9549    for (var i = 0, e = chunk.lines.length; i < e; ++i) {
9550      var line = chunk.lines[i], lh = line.height;
9551      if (h < lh) break;
9552      h -= lh;
9553    }
9554    return n + i;
9555  }
9556  function heightAtLine(chunk, n) {
9557    var h = 0;
9558    outer: do {
9559      for (var i = 0, e = chunk.children.length; i < e; ++i) {
9560        var child = chunk.children[i], sz = child.chunkSize();
9561        if (n < sz) { chunk = child; continue outer; }
9562        n -= sz;
9563        h += child.height;
9564      }
9565      return h;
9566    } while (!chunk.lines);
9567    for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
9568    return h;
9569  }
9570
9571  // The history object 'chunks' changes that are made close together
9572  // and at almost the same time into bigger undoable units.
9573  function History() {
9574    this.time = 0;
9575    this.done = []; this.undone = [];
9576    this.compound = 0;
9577    this.closed = false;
9578  }
9579  History.prototype = {
9580    addChange: function(start, added, old) {
9581      this.undone.length = 0;
9582      var time = +new Date, cur = lst(this.done), last = cur && lst(cur);
9583      var dtime = time - this.time;
9584
9585      if (cur && !this.closed && this.compound) {
9586        cur.push({start: start, added: added, old: old});
9587      } else if (dtime > 400 || !last || this.closed ||
9588                 last.start > start + old.length || last.start + last.added < start) {
9589        this.done.push([{start: start, added: added, old: old}]);
9590        this.closed = false;
9591      } else {
9592        var startBefore = Math.max(0, last.start - start),
9593            endAfter = Math.max(0, (start + old.length) - (last.start + last.added));
9594        for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]);
9595        for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]);
9596        if (startBefore) last.start = start;
9597        last.added += added - (old.length - startBefore - endAfter);
9598      }
9599      this.time = time;
9600    },
9601    startCompound: function() {
9602      if (!this.compound++) this.closed = true;
9603    },
9604    endCompound: function() {
9605      if (!--this.compound) this.closed = true;
9606    }
9607  };
9608
9609  function stopMethod() {e_stop(this);}
9610  // Ensure an event has a stop method.
9611  function addStop(event) {
9612    if (!event.stop) event.stop = stopMethod;
9613    return event;
9614  }
9615
9616  function e_preventDefault(e) {
9617    if (e.preventDefault) e.preventDefault();
9618    else e.returnValue = false;
9619  }
9620  function e_stopPropagation(e) {
9621    if (e.stopPropagation) e.stopPropagation();
9622    else e.cancelBubble = true;
9623  }
9624  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
9625  CodeMirror.e_stop = e_stop;
9626  CodeMirror.e_preventDefault = e_preventDefault;
9627  CodeMirror.e_stopPropagation = e_stopPropagation;
9628
9629  function e_target(e) {return e.target || e.srcElement;}
9630  function e_button(e) {
9631    var b = e.which;
9632    if (b == null) {
9633      if (e.button & 1) b = 1;
9634      else if (e.button & 2) b = 3;
9635      else if (e.button & 4) b = 2;
9636    }
9637    if (mac && e.ctrlKey && b == 1) b = 3;
9638    return b;
9639  }
9640
9641  // Allow 3rd-party code to override event properties by adding an override
9642  // object to an event object.
9643  function e_prop(e, prop) {
9644    var overridden = e.override && e.override.hasOwnProperty(prop);
9645    return overridden ? e.override[prop] : e[prop];
9646  }
9647
9648  // Event handler registration. If disconnect is true, it'll return a
9649  // function that unregisters the handler.
9650  function connect(node, type, handler, disconnect) {
9651    if (typeof node.addEventListener == "function") {
9652      node.addEventListener(type, handler, false);
9653      if (disconnect) return function() {node.removeEventListener(type, handler, false);};
9654    } else {
9655      var wrapHandler = function(event) {handler(event || window.event);};
9656      node.attachEvent("on" + type, wrapHandler);
9657      if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
9658    }
9659  }
9660  CodeMirror.connect = connect;
9661
9662  function Delayed() {this.id = null;}
9663  Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id
9663); this.id = setTimeout(f, ms);}};
9664
9665  var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
9666
9667  // Detect drag-and-drop
9668  var dragAndDrop = function() {
9669    // There is *some* kind of drag-and-drop support in IE6-8, but I
9670    // couldn't get it to work yet.
9671    if (ie_lt9) return false;
9672    var div = elt('div');
9673    return "draggable" in div || "dragDrop" in div;
9674  }();
9675
9676  // Feature-detect whether newlines in textareas are converted to \r\n
9677  var lineSep = function () {
9678    var te = elt("textarea");
9679    te.value = "foo\nbar";
9680    if (te.value.indexOf("\r") > -1) return "\r\n";
9681    return "\n";
9682  }();
9683
9684  // For a reason I have yet to figure out, some browsers disallow
9685  // word wrapping between certain characters *only* if a new inline
9686  // element is started between them. This makes it hard to reliably
9687  // measure the position of things, since that requires inserting an
9688  // extra span. This terribly fragile set of regexps matches the
9689  // character combinations that suffer from this phenomenon on the
9690  // various browsers.
9691  var spanAffectsWrapping = /^$/; // Won't match any two-character string
9692  if (gecko) spanAffectsWrapping = /$'/;
9693  else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
9694  else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
9695
9696  // Counts the column offset in a string, taking tabs into account.
9697  // Used mostly to find indentation.
9698  function countColumn(string, end, tabSize) {
9699    if (end == null) {
9700      end = string.search(/[^\s\u00a0]/);
9701      if (end == -1) end = string.length;
9702    }
9703    for (var i = 0, n = 0; i < end; ++i) {
9704      if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
9705      else ++n;
9706    }
9707    return n;
9708  }
9709
9710  function eltOffset(node, screen) {
9711    // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
9712    // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
9713    try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
9714    catch(e) { box = {top: 0, left: 0}; }
9715    if (!screen) {
9716      // Get the toplevel scroll, working around browser differences.
9717      if (window.pageYOffset == null) {
9718        var t = document.documentElement || document.body.parentNode;
9719        if (t.scrollTop == null) t = document.body;
9720        box.top += t.scrollTop; box.left += t.scrollLeft;
9721      } else {
9722        box.top += window.pageYOffset; box.left += window.pageXOffset;
9723      }
9724    }
9725    return box;
9726  }
9727
9728  function eltText(node) {
9729    return node.textContent || node.innerText || node.nodeValue || "";
9730  }
9731
9732  var spaceStrs = [""];
9733  function spaceStr(n) {
9734    while (spaceStrs.length <= n)
9735      spaceStrs.push(lst(spaceStrs) + " ");
9736    return spaceStrs[n];
9737  }
9738
9739  function lst(arr) { return arr[arr.length-1]; }
9740
9741  function selectInput(node) {
9742    if (ios) { // Mobile Safari apparently has a bug where select() is broken.
9743      node.selectionStart = 0;
9744      node.selectionEnd = node.value.length;
9745    } else node.select();
9746  }
9747
9748  // Operations on {line, ch} objects.
9749  function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
9750  function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
9751  function copyPos(x) {return {line: x.line, ch: x.ch};}
9752
9753  function elt(tag, content, className, style) {
9754    var e = document.createElement(tag);
9755    if (className) e.className = className;
9756    if (style) e.style.cssText = style;
9757    if (typeof content == "string") setTextContent(e, content);
9758    else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
9759    return e;
9760  }
9761  function removeChildren(e) {
9762    e.innerHTML = "";
9763    return e;
9764  }
9765  function removeChildrenAndAdd(parent, e) {
9766    removeChildren(parent).appendChild(e);
9767  }
9768  function setTextContent(e, str) {
9769    if (ie_lt9) {
9770      e.innerHTML = "";
9771      e.appendChild(document.createTextNode(str));
9772    } else e.textContent = str;
9773  }
9774
9775  // Used to position the cursor after an undo/redo by finding the
9776  // last edited character.
9777  function editEnd(from, to) {
9778    if (!to) return 0;
9779    if (!from) return to.length;
9780    for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
9781      if (from.charAt(i) != to.charAt(j)) break;
9782    return j + 1;
9783  }
9784
9785  function indexOf(collection, elt) {
9786    if (collection.indexOf) return collection.indexOf(elt);
9787    for (var i = 0, e = collection.length; i < e; ++i)
9788      if (collection[i] == elt) return i;
9789    return -1;
9790  }
9791  var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/;
9792  function isWordChar(ch) {
9793    return /\w/.test(ch) || ch > "\x80" &&
9794      (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
9795  }
9796
9797  // See if "".split is the broken IE version, if so, provide an
9798  // alternative way to split lines.
9799  var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
9800    var pos = 0, result = [], l = string.length;
9801    while (pos <= l) {
9802      var nl = string.indexOf("\n", pos);
9803      if (nl == -1) nl = string.length;
9804      var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
9805      var rt = line.indexOf("\r");
9806      if (rt != -1) {
9807        result.push(line.slice(0, rt));
9808        pos += rt + 1;
9809      } else {
9810        result.push(line);
9811        pos = nl + 1;
9812      }
9813    }
9814    return result;
9815  } : function(string){return string.split(/\r\n?|\n/);};
9816  CodeMirror.splitLines = splitLines;
9817
9818  var hasSelection = window.getSelection ? function(te) {
9819    try { return te.selectionStart != te.selectionEnd; }
9820    catch(e) { return false; }
9821  } : function(te) {
9822    try {var range = te.ownerDocument.selection.createRange();}
9823    catch(e) {}
9824    if (!range || range.parentElement() != te) return false;
9825    return range.compareEndPoints("StartToEnd", range) != 0;
9826  };
9827
9828  CodeMirror.defineMode("null", function() {
9829    return {token: function(stream) {stream.skipToEnd();}};
9830  });
9831  CodeMirror.defineMIME("text/plain", "null");
9832
9833  var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
9834                  19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
9835                  36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
9836                  46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
9837                  186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
9838                  221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
9839                  63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
9840  CodeMirror.keyNames = keyNames;
9841  (function() {
9842    // Number keys
9843    for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
9844    // Alphabetic keys
9845    for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
9846    // Function keys
9847    for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
9848  })();
9849
9850  CodeMirror.version = "2.38";
9851
9852  return CodeMirror;
9853})();
9854;
9855
9856/*
9857 *	MySQL Mode for CodeMirror 2 by MySQL-Tools
9858 *	@author James Thorne (partydroid)
9859 *	@link 	http://github.com/partydroid/MySQL-Tools
9860 * 	@link 	http://mysqltools.org
9861 *	@version 02/Jan/2012
9862*/
9863CodeMirror.defineMode("mysql", function(config) {
9864  var indentUnit = config.indentUnit;
9865  var curPunc;
9866
9867  function wordRegexp(words) {
9868    return new RegExp("^(?:" + words.join("|") + ")$", "i");
9869  }
9870  var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri",
9871                        "isblank", "isliteral", "union", "a"]);
9872  var keywords = wordRegexp([
9873  ('ACCESSIBLE'),('ALTER'),('AS'),('BEFORE'),('BINARY'),('BY'),('CASE'),('CHARACTER'),('COLUMN'),('CONTINUE'),('CROSS'),('CURRENT_TIMESTAMP'),('DATABASE'),('DAY_MICROSECOND'),('DEC'),('DEFAULT'),
9874	('DESC'),('DISTINCT'),('DOUBLE'),('EACH'),('ENCLOSED'),('EXIT'),('FETCH'),('FLOAT8'),('FOREIGN'),('GRANT'),('HIGH_PRIORITY'),('HOUR_SECOND'),('IN'),('INNER'),('INSERT'),('INT2'),('INT8'),
9875	('INTO'),('JOIN'),('KILL'),('LEFT'),('LINEAR'),('LOCALTIME'),('LONG'),('LOOP'),('MATCH'),('MEDIUMTEXT'),('MINUTE_SECOND'),('NATURAL'),('NULL'),('OPTIMIZE'),('OR'),('OUTER'),('PRIMARY'),
9876	('RANGE'),('READ_WRITE'),('REGEXP'),('REPEAT'),('RESTRICT'),('RIGHT'),('SCHEMAS'),('SENSITIVE'),('SHOW'),('SPECIFIC'),('SQLSTATE'),('SQL_CALC_FOUND_ROWS'),('STARTING'),('TERMINATED'),
9877	('TINYINT'),('TRAILING'),('UNDO'),('UNLOCK'),('USAGE'),('UTC_DATE'),('VALUES'),('VARCHARACTER'),('WHERE'),('WRITE'),('ZEROFILL'),('ALL'),('AND'),('ASENSITIVE'),('BIGINT'),('BOTH'),('CASCADE'),
9878	('CHAR'),('COLLATE'),('CONSTRAINT'),('CREATE'),('CURRENT_TIME'),('CURSOR'),('DAY_HOUR'),('DAY_SECOND'),('DECLARE'),('DELETE'),('DETERMINISTIC'),('DIV'),('DUAL'),('ELSEIF'),('EXISTS'),('FALSE'),
9879	('FLOAT4'),('FORCE'),('FULLTEXT'),('HAVING'),('HOUR_MINUTE'),('IGNORE'),('INFILE'),('INSENSITIVE'),('INT1'),('INT4'),('INTERVAL'),('ITERATE'),('KEYS'),('LEAVE'),('LIMIT'),('LOAD'),('LOCK'),
9880	('LONGTEXT'),('MASTER_SSL_VERIFY_SERVER_CERT'),('MEDIUMINT'),('MINUTE_MICROSECOND'),('MODIFIES'),('NO_WRITE_TO_BINLOG'),('ON'),('OPTIONALLY'),('OUT'),('PRECISION'),('PURGE'),('READS'),
9881	('REFERENCES'),('RENAME'),('REQUIRE'),('REVOKE'),('SCHEMA'),('SELECT'),('SET'),('SPATIAL'),('SQLEXCEPTION'),('SQL_BIG_RESULT'),('SSL'),('TABLE'),('TINYBLOB'),('TO'),('TRUE'),('UNIQUE'),
9882	('UPDATE'),('USING'),('UTC_TIMESTAMP'),('VARCHAR'),('WHEN'),('WITH'),('YEAR_MONTH'),('ADD'),('ANALYZE'),('ASC'),('BETWEEN'),('BLOB'),('CALL'),('CHANGE'),('CHECK'),('CONDITION'),('CONVERT'),
9883	('CURRENT_DATE'),('CURRENT_USER'),('DATABASES'),('DAY_MINUTE'),('DECIMAL'),('DELAYED'),('DESCRIBE'),('DISTINCTROW'),('DROP'),('ELSE'),('ESCAPED'),('EXPLAIN'),('FLOAT'),('FOR'),('FROM'),
9884	('GROUP'),('HOUR_MICROSECOND'),('IF'),('INDEX'),('INOUT'),('INT'),('INT3'),('INTEGER'),('IS'),('KEY'),('LEADING'),('LIKE'),('LINES'),('LOCALTIMESTAMP'),('LONGBLOB'),('LOW_PRIORITY'),
9885	('MEDIUMBLOB'),('MIDDLEINT'),('MOD'),('NOT'),('NUMERIC'),('OPTION'),('ORDER'),('OUTFILE'),('PROCEDURE'),('READ'),('REAL'),('RELEASE'),('REPLACE'),('RETURN'),('RLIKE'),('SECOND_MICROSECOND'),
9886	('SEPARATOR'),('SMALLINT'),('SQL'),('SQLWARNING'),('SQL_SMALL_RESULT'),('STRAIGHT_JOIN'),('THEN'),('TINYTEXT'),('TRIGGER'),('UNION'),('UNSIGNED'),('USE'),('UTC_TIME'),('VARBINARY'),('VARYING'),
9887	('WHILE'),('XOR'),('FULL'),('COLUMNS'),('MIN'),('MAX'),('STDEV'),('COUNT')
9888  ]);
9889  var operatorChars = /[*+\-<>=&|]/;
9890
9891  function tokenBase(stream, state) {
9892    var ch = stream.next();
9893    curPunc = null;
9894    if (ch == "$" || ch == "?") {
9895      stream.match(/^[\w\d]*/);
9896      return "variable-2";
vendor: 4,513 bytes, lines 9897-10052
9897    }
9898    else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) {
9899      stream.match(/^[^\s\u00a0>]*>?/);
9900      return "atom";
9901    }
9902    else if (ch == "\"" || ch == "'") {
9903      state.tokenize = tokenLiteral(ch);
9904      return state.tokenize(stream, state);
9905    }
9906    else if (ch == "`") {
9907      state.tokenize = tokenOpLiteral(ch);
9908      return state.tokenize(stream, state);
9909    }
9910    else if (/[{}\(\),\.;\[\]]/.test(ch)) {
9911      curPunc = ch;
9912      return null;
9913    }
9914    else if (ch == "-" && stream.eat("-")) {
9915      stream.skipToEnd();
9916      return "comment";
9917    }
9918    else if (ch == "/" && stream.eat("*")) {
9919      state.tokenize = tokenComment;
9920      return state.tokenize(stream, state);
9921    }
9922    else if (operatorChars.test(ch)) {
9923      stream.eatWhile(operatorChars);
9924      return null;
9925    }
9926    else if (ch == ":") {
9927      stream.eatWhile(/[\w\d\._\-]/);
9928      return "atom";
9929    }
9930    else {
9931      stream.eatWhile(/[_\w\d]/);
9932      if (stream.eat(":")) {
9933        stream.eatWhile(/[\w\d_\-]/);
9934        return "atom";
9935      }
9936      var word = stream.current(), type;
9937      if (ops.test(word))
9938        return null;
9939      else if (keywords.test(word))
9940        return "keyword";
9941      else
9942        return "variable";
9943    }
9944  }
9945
9946  function tokenLiteral(quote) {
9947    return function(stream, state) {
9948      var escaped = false, ch;
9949      while ((ch = stream.next()) != null) {
9950        if (ch == quote && !escaped) {
9951          state.tokenize = tokenBase;
9952          break;
9953        }
9954        escaped = !escaped && ch == "\\";
9955      }
9956      return "string";
9957    };
9958  }
9959
9960  function tokenOpLiteral(quote) {
9961    return function(stream, state) {
9962      var escaped = false, ch;
9963      while ((ch = stream.next()) != null) {
9964        if (ch == quote && !escaped) {
9965          state.tokenize = tokenBase;
9966          break;
9967        }
9968        escaped = !escaped && ch == "\\";
9969      }
9970      return "variable-2";
9971    };
9972  }
9973
9974  function tokenComment(stream, state) {
9975    for (;;) {
9976      if (stream.skipTo("*")) {
9977        stream.next();
9978        if (stream.eat("/")) {
9979          state.tokenize = tokenBase;
9980          break;
9981        }
9982      } else {
9983        stream.skipToEnd();
9984        break;
9985      }
9986    }
9987    return "comment";
9988  }
9989
9990
9991  function pushContext(state, type, col) {
9992    state.context = {prev: state.context, indent: state.indent, col: col, type: type};
9993  }
9994  function popContext(state) {
9995    state.indent = state.context.indent;
9996    state.context = state.context.prev;
9997  }
9998
9999  return {
10000    startState: function(base) {
10001      return {tokenize: tokenBase,
10002              context: null,
10003              indent: 0,
10004              col: 0};
10005    },
10006
10007    token: function(stream, state) {
10008      if (stream.sol()) {
10009        if (state.context && state.context.align == null) state.context.align = false;
10010        state.indent = stream.indentation();
10011      }
10012      if (stream.eatSpace()) return null;
10013      var style = state.tokenize(stream, state);
10014
10015      if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") {
10016        state.context.align = true;
10017      }
10018
10019      if (curPunc == "(") pushContext(state, ")", stream.column());
10020      else if (curPunc == "[") pushContext(state, "]", stream.column());
10021      else if (curPunc == "{") pushContext(state, "}", stream.column());
10022      else if (/[\]\}\)]/.test(curPunc)) {
10023        while (state.context && state.context.type == "pattern") popContext(state);
10024        if (state.context && curPunc == state.context.type) popContext(state);
10025      }
10026      else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state);
10027      else if (/atom|string|variable/.test(style) && state.context) {
10028        if (/[\}\]]/.test(state.context.type))
10029          pushContext(state, "pattern", stream.column());
10030        else if (state.context.type == "pattern" && !state.context.align) {
10031          state.context.align = true;
10032          state.context.col = stream.column();
10033        }
10034      }
10035
10036      return style;
10037    },
10038
10039    indent: function(state, textAfter) {
10040      var firstChar = textAfter && textAfter.charAt(0);
10041      var context = state.context;
10042      if (/[\]\}]/.test(firstChar))
10043        while (context && context.type == "pattern") context = context.prev;
10044
10045      var closing = context && firstChar == context.type;
10046      if (!context)
10047        return 0;
10048      else if (context.type == "pattern")
10049        return context.col;
10050      else if (context.align)
10051        return context.col + (closing ? 0 : 1);
10052      else
10053        return context.indent + (closing ? 0 : indentUnit);
10054    }
10055  };
10056});
10057
10058CodeMirror.defineMIME("text/x-mysql", "mysql");
10059;
10060

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.