2   var slice = Array.prototype.slice;
 
   4   function update(array, args) {
 
   5     var arrayLength = array.length, length = args.length;
 
   6     while (length--) array[arrayLength + length] = args[length];
 
  10   function merge(array, args) {
 
  11     array = slice.call(array, 0);
 
  12     return update(array, args);
 
  15   Function.prototype.bind = function(context) {
 
  16     if (arguments.length < 2 && typeof arguments[0] === 'undefined') {
 
  20     var args = slice.call(arguments, 1);
 
  22       var a = merge(args, arguments);
 
  23       return __method.apply(context, a);
 
  29 function nblck_each(array, body, after) {
 
  30         $.each(array, function(i) {
 
  37 function nblck_map(array, func, after) {
 
  40         nblck_each(array, function(elem, index) {
 
  41                 acc.push(func(elem, index));
 
  47 function ScriptletCenter()
 
  51     this.scriptlets['insert_text'] = function(context, params, text, move_forward, move_up, done)
 
  53         done(params.text, move_forward, move_up);
 
  56     this.scriptlets['insert_tag'] = function(context, params, text, move_forward, move_up, done)
 
  59         for (var i=params.padding_top; i; i--)
 
  62         var start_tag = '<'+params.tag;
 
  63         var cursor_inside = false;
 
  65         for (var attr in params.attrs) {
 
  66             start_tag += ' '+attr+'="' + params.attrs[attr] + '"';
 
  70         var end_tag = '</'+params.tag+'>';
 
  72         var padding_bottom = '';
 
  73         for (var i=params.padding_bottom; i; i--)
 
  74             padding_bottom += '\n';
 
  80             for(var index=0; index < text.length; index++)
 
  82                 if (text[index].match(/\s/)) { // whitespace
 
  87                     if(output == token) output += padding_top + start_tag;
 
  89                     output += text[index];
 
  93             if( output[output.length-1] == '\\' ) {
 
  94                 output = output.substr(0, output.length-1) + end_tag + padding_bottom + '\\';
 
  96                 output += end_tag + padding_bottom;
 
 100             // keep cursor inside tag if some previous scriptlet has already moved it
 
 101             cursor_inside = move_forward != 0 || move_up != 0;
 
 104             if(params.nocontent) {
 
 105                 output = padding_top + "<"+params.tag +" />" + padding_bottom;
 
 108                 output = padding_top + start_tag + end_tag + padding_bottom;
 
 109                 cursor_inside = true;
 
 114             move_forward -= params.tag.length + 3;
 
 115             move_up += params.padding_bottom || 0;
 
 118         done(output, move_forward, move_up);
 
 121     this.scriptlets['lineregexp'] = function(context, params, text, move_forward, move_up, done) {
 
 124         var exprs = $.map(params.exprs, function(expr) {
 
 126             if(expr.length > 2) {
 
 129                 rx: new RegExp(expr[0], opts),
 
 134         if(!text) done(text, move_forward, move_up);
 
 137         var lines = text.split('\n');
 
 139                 nblck_map(lines, function(line, index) {
 
 141             $(exprs).each(function() {
 
 143                 line = line.replace(expr.rx, expr.repl);
 
 146                         $progress.html(index);
 
 148             if(old_line != line) changed += 1;
 
 150         }, function(newlines) {
 
 152                 text = newlines.join('\n');
 
 155             done(text, move_forward, move_up);
 
 159     this.scriptlets['fulltextregexp'] = function(context, params, text, move_forward, move_up, done) {
 
 162         var exprs = $.map(params.exprs, function(expr) {
 
 164             if(expr.length > 2) {
 
 168                 rx: new RegExp(expr[0], opts),
 
 173         if(!text) done(text, move_forward, move_up);
 
 175                 nblck_each(exprs, function(expr, index) {
 
 176                         $progress.html(600 + index);
 
 177             text = text.replace(expr.rx, expr.repl);
 
 179                         done(text, move_forward, move_up);
 
 183     this.scriptlets['macro'] = function(context, params, text, move_forward, move_up, done) {
 
 187                 function next(text, move_forward, move_up) {
 
 188                 if (i < params.length) {
 
 191                                 self.scriptlets[e[0]](context, e[1], text, move_forward, move_up, next);
 
 194                                 done(text, move_forward, move_up);
 
 198                 next(text, move_forward, move_up);
 
 201     this.scriptlets['lowercase'] = function(context, params, text, move_forward, move_up, done)
 
 203         if(!text) done(text, move_forward, move_up);
 
 204         done(text.toLowerCase(), move_forward, move_up);
 
 208     this.scriptlets["insert_stanza"] = function(context, params, text, move_forward, move_up, done) {
 
 209         var padding_top = '';
 
 210         for (var i=params.padding_top; i; i--)
 
 213         var padding_bottom = '';
 
 214         for (var i=params.padding_bottom; i; i--)
 
 215             padding_bottom += '\n';
 
 218             var verses = text.split('\n');
 
 219             text = ''; var buf = ''; var ebuf = '';
 
 222             for(var i=0;  i < verses.length; i++) {
 
 223                 var verse = verses[i].replace(/^\s+/, "").replace(/\s+$/, "");
 
 225                     text += (buf ? buf + '/\n' : '') + ebuf;
 
 226                     buf = (first ? '<strofa>\n' : '') + verses[i];
 
 230                     ebuf += '\n' + verses[i];
 
 233             text = padding_top + text + buf + '\n</strofa>' + padding_bottom + ebuf;
 
 236             text = padding_top + "<strofa></strofa>" + padding_bottom;
 
 237             move_forward -= "</strofa>".length;
 
 238             move_up += params.padding_bottom || 0;
 
 241         done(text, move_forward, move_up);
 
 245     this.scriptlets['autotag'] = function(context, params, text, move_forward, move_up, done)
 
 247         if(!text.match(/^\n+$/)) done(text, move_forward, move_up);
 
 251         function insert_done(text, mf, mu) {
 
 255         if (!params.split) params.split = 2;
 
 256         if (!params.padding) params.padding = 3;
 
 258         if (params.tag == 'strofa')
 
 259             tagger = this.scriptlets['insert_stanza'];
 
 261             tagger = this.scriptlets['insert_tag'];
 
 263         var padding_top = text.match(/^\n+/)
 
 264         output = padding_top ? padding_top[0] : '';
 
 267         for(var i=params.padding; i; --i) {
 
 271         text = text.substr(output.length);
 
 272         var chunk_reg = new RegExp("^([\\s\\S]+?)(\\n{"+params.split+",}|$)");
 
 273         while (match = text.match(chunk_reg)) {
 
 274             if (params.tag == 'akap' && match[1].match(/^---/))
 
 276             else tag = params.tag;
 
 277             tagger(context, {tag: tag}, match[1], 0, 0, insert_done);
 
 278             if (match[2].length > params.padding)
 
 282             text = text.substr(match[0].length)
 
 287         done(output, move_forward, move_up);
 
 291     this.scriptlets['slugify'] = function(context, params, text, move_forward, move_up, done)
 
 293         done(slugify(text.replace(/_/g, '-')), move_forward, move_up);
 
 298 ScriptletCenter.prototype.callInteractive = function(opts) {
 
 299         $progress = $('<span>Executing script</span>');
 
 302         /* This won't work, 'cause the JS below might be synchronous :( */
 
 303         /* var timer = setTimeout(function() {
 
 304             $.blockUI({message: $progress});
 
 308         $.blockUI({message: $progress, showOverlay: false});
 
 310     var input = self.XMLEditorSelectedText(opts.context);
 
 311         self.scriptlets[opts.action](opts.context, opts.extra, input, 0, 0, function(output, move_forward, move_up){
 
 315         if (input != output) {
 
 316             self.XMLEditorReplaceSelectedText(opts.context, output)
 
 318         if (move_forward || move_up) {
 
 320                 self.XMLEditorMoveCursorForward(opts.context, move_forward, move_up)
 
 324             $.unblockUI(); // done
 
 328 ScriptletCenter.prototype.XMLEditorSelectedText = function(editor) {
 
 330     return editor.selection();
 
 333 ScriptletCenter.prototype.XMLEditorReplaceSelectedText = function(editor, replacement)
 
 335         $progress.html("Replacing text");
 
 336         editor.replaceSelection(replacement);
 
 339 ScriptletCenter.prototype.XMLEditorMoveCursorForward = function(panel, right, up) {
 
 340     var pos = panel.cursorPosition();
 
 344             line = panel.nextLine(line);
 
 348             line = panel.prevLine(line);
 
 351         len = panel.lineContent(line).length;
 
 352         panel.selectLines(line, len + right);
 
 355         panel.selectLines(pos.line, pos.character + right);
 
 362     scriptletCenter = new ScriptletCenter();