Subversion Repositories wimsdev

Rev

Rev 4870 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
4870 bpr 1
/**
2
 * Autocompletion class
8476 bpr 3
 *
4870 bpr 4
 * An auto completion box appear while you're writing. It's possible to force it to appear with Ctrl+Space short cut
8476 bpr 5
 *
4870 bpr 6
 * Loaded as a plugin inside editArea (everything made here could have been made in the plugin directory)
7
 * But is definitly linked to syntax selection (no need to do 2 different files for color and auto complete for each syntax language)
8
 * and add a too important feature that many people would miss if included as a plugin
8476 bpr 9
 *
4870 bpr 10
 * - init param: autocompletion_start
11
 * - Button name: "autocompletion"
8476 bpr 12
 */
4870 bpr 13
 
14
var EditArea_autocompletion= {
8476 bpr 15
 
4870 bpr 16
        /**
17
         * Get called once this file is loaded (editArea still not initialized)
18
         *
8476 bpr 19
         * @return nothing
20
         */
21
        init: function(){
4870 bpr 22
                //      alert("test init: "+ this._someInternalFunction(2, 3));
8476 bpr 23
 
4870 bpr 24
                if(editArea.settings["autocompletion"])
25
                        this.enabled= true;
26
                else
27
                        this.enabled= false;
28
                this.current_word               = false;
29
                this.shown                              = false;
30
                this.selectIndex                = -1;
31
                this.forceDisplay               = false;
32
                this.isInMiddleWord             = false;
33
                this.autoSelectIfOneResult      = false;
34
                this.delayBeforeDisplay = 100;
35
                this.checkDelayTimer    = false;
36
                this.curr_syntax_str    = '';
8476 bpr 37
 
4870 bpr 38
                this.file_syntax_datas  = {};
39
        }
40
        /**
41
         * Returns the HTML code for a specific control string or false if this plugin doesn't have that control.
42
         * A control can be a button, select list or any other HTML item to present in the EditArea user interface.
43
         * Language variables such as {$lang_somekey} will also be replaced with contents from
44
         * the language packs.
8476 bpr 45
         *
46
         * @param {string} ctrl_name: the name of the control to add
4870 bpr 47
         * @return HTML code for a specific control or false.
48
         * @type string or boolean
8476 bpr 49
         */
4870 bpr 50
        /*,get_control_html: function(ctrl_name){
51
                switch( ctrl_name ){
52
                        case 'autocompletion':
53
                                // Control id, button img, command
54
                                return parent.editAreaLoader.get_button_html('autocompletion_but', 'autocompletion.gif', 'toggle_autocompletion', false, this.baseURL);
55
                                break;
56
                }
57
                return false;
58
        }*/
59
        /**
60
         * Get called once EditArea is fully loaded and initialised
8476 bpr 61
         *
4870 bpr 62
         * @return nothing
8476 bpr 63
         */
64
        ,onload: function(){
4870 bpr 65
                if(this.enabled)
66
                {
67
                        var icon= document.getElementById("autocompletion");
68
                        if(icon)
69
                                editArea.switchClassSticky(icon, 'editAreaButtonSelected', true);
70
                }
8476 bpr 71
 
4870 bpr 72
                this.container  = document.createElement('div');
73
                this.container.id       = "auto_completion_area";
74
                editArea.container.insertBefore( this.container, editArea.container.firstChild );
8476 bpr 75
 
4870 bpr 76
                // add event detection for hiding suggestion box
77
                parent.editAreaLoader.add_event( document, "click", function(){ editArea.plugins['autocompletion']._hide();} );
78
                parent.editAreaLoader.add_event( editArea.textarea, "blur", function(){ editArea.plugins['autocompletion']._hide();} );
8476 bpr 79
 
4870 bpr 80
        }
8476 bpr 81
 
4870 bpr 82
        /**
83
         * Is called each time the user touch a keyboard key.
8476 bpr 84
         *
4870 bpr 85
         * @param (event) e: the keydown event
86
         * @return true - pass to next handler in chain, false - stop chain execution
8476 bpr 87
         * @type boolean
4870 bpr 88
         */
89
        ,onkeydown: function(e){
90
                if(!this.enabled)
91
                        return true;
8476 bpr 92
 
4870 bpr 93
                if (EA_keys[e.keyCode])
94
                        letter=EA_keys[e.keyCode];
95
                else
8476 bpr 96
                        letter=String.fromCharCode(e.keyCode);
4870 bpr 97
                // shown
98
                if( this._isShown() )
8476 bpr 99
                {
4870 bpr 100
                        // if escape, hide the box
101
                        if(letter=="Esc")
102
                        {
103
                                this._hide();
104
                                return false;
105
                        }
106
                        // Enter
107
                        else if( letter=="Entrer")
108
                        {
109
                                var as  = this.container.getElementsByTagName('A');
110
                                // select a suggested entry
111
                                if( this.selectIndex >= 0 && this.selectIndex < as.length )
112
                                {
113
                                        as[ this.selectIndex ].onmousedown();
114
                                        return false
115
                                }
116
                                // simply add an enter in the code
117
                                else
118
                                {
119
                                        this._hide();
120
                                        return true;
121
                                }
122
                        }
123
                        else if( letter=="Tab" || letter=="Down")
124
                        {
125
                                this._selectNext();
126
                                return false;
127
                        }
128
                        else if( letter=="Up")
129
                        {
130
                                this._selectBefore();
131
                                return false;
132
                        }
133
                }
134
                // hidden
135
                else
136
                {
8476 bpr 137
 
4870 bpr 138
                }
8476 bpr 139
 
4870 bpr 140
                // show current suggestion list and do autoSelect if possible (no matter it's shown or hidden)
141
                if( letter=="Space" && CtrlPressed(e) )
142
                {
143
                        //parent.console.log('SHOW SUGGEST');
144
                        this.forceDisplay                       = true;
145
                        this.autoSelectIfOneResult      = true;
146
                        this._checkLetter();
147
                        return false;
148
                }
8476 bpr 149
 
4870 bpr 150
                // wait a short period for check that the cursor isn't moving
151
                setTimeout("editArea.plugins['autocompletion']._checkDelayAndCursorBeforeDisplay();", editArea.check_line_selection_timer +5 );
152
                this.checkDelayTimer = false;
153
                return true;
8476 bpr 154
        }
4870 bpr 155
        /**
156
         * Executes a specific command, this function handles plugin commands.
157
         *
158
         * @param {string} cmd: the name of the command being executed
8476 bpr 159
         * @param {unknown} param: the parameter of the command
4870 bpr 160
         * @return true - pass to next handler in chain, false - stop chain execution
8476 bpr 161
         * @type boolean
4870 bpr 162
         */
163
        ,execCommand: function(cmd, param){
164
                switch( cmd ){
165
                        case 'toggle_autocompletion':
166
                                var icon= document.getElementById("autocompletion");
167
                                if(!this.enabled)
168
                                {
169
                                        if(icon != null){
170
                                                editArea.restoreClass(icon);
171
                                                editArea.switchClassSticky(icon, 'editAreaButtonSelected', true);
172
                                        }
173
                                        this.enabled= true;
174
                                }
175
                                else
176
                                {
177
                                        this.enabled= false;
178
                                        if(icon != null)
179
                                                editArea.switchClassSticky(icon, 'editAreaButtonNormal', false);
180
                                }
181
                                return true;
182
                }
183
                return true;
184
        }
185
        ,_checkDelayAndCursorBeforeDisplay: function()
186
        {
187
                this.checkDelayTimer = setTimeout("if(editArea.textarea.selectionStart == "+ editArea.textarea.selectionStart +") EditArea_autocompletion._checkLetter();",  this.delayBeforeDisplay - editArea.check_line_selection_timer - 5 );
188
        }
189
        // hide the suggested box
190
        ,_hide: function(){
191
                this.container.style.display="none";
192
                this.selectIndex        = -1;
193
                this.shown      = false;
194
                this.forceDisplay       = false;
195
                this.autoSelectIfOneResult = false;
196
        }
197
        // display the suggested box
198
        ,_show: function(){
199
                if( !this._isShown() )
200
                {
201
                        this.container.style.display="block";
202
                        this.selectIndex        = -1;
203
                        this.shown      = true;
204
                }
205
        }
206
        // is the suggested box displayed?
207
        ,_isShown: function(){
208
                return this.shown;
209
        }
210
        // setter and getter
211
        ,_isInMiddleWord: function( new_value ){
212
                if( typeof( new_value ) == "undefined" )
213
                        return this.isInMiddleWord;
214
                else
215
                        this.isInMiddleWord     = new_value;
216
        }
217
        // select the next element in the suggested box
218
        ,_selectNext: function()
219
        {
220
                var as  = this.container.getElementsByTagName('A');
8476 bpr 221
 
4870 bpr 222
                // clean existing elements
223
                for( var i=0; i<as.length; i++ )
224
                {
225
                        if( as[i].className )
226
                                as[i].className = as[i].className.replace(/ focus/g, '');
227
                }
8476 bpr 228
 
229
                this.selectIndex++;
4870 bpr 230
                this.selectIndex        = ( this.selectIndex >= as.length || this.selectIndex < 0 ) ? 0 : this.selectIndex;
231
                as[ this.selectIndex ].className        += " focus";
232
        }
233
        // select the previous element in the suggested box
234
        ,_selectBefore: function()
235
        {
236
                var as  = this.container.getElementsByTagName('A');
8476 bpr 237
 
4870 bpr 238
                // clean existing elements
239
                for( var i=0; i<as.length; i++ )
240
                {
241
                        if( as[i].className )
242
                                as[i].className = as[ i ].className.replace(/ focus/g, '');
243
                }
8476 bpr 244
 
4870 bpr 245
                this.selectIndex--;
8476 bpr 246
 
4870 bpr 247
                this.selectIndex        = ( this.selectIndex >= as.length || this.selectIndex < 0 ) ? as.length-1 : this.selectIndex;
248
                as[ this.selectIndex ].className        += " focus";
249
        }
250
        ,_select: function( content )
251
        {
252
                cursor_forced_position  = content.indexOf( '{@}' );
253
                content = content.replace(/{@}/g, '' );
8476 bpr 254
                editArea.getIESelection();
255
 
4870 bpr 256
                // retrive the number of matching characters
257
                var start_index = Math.max( 0, editArea.textarea.selectionEnd - content.length );
8476 bpr 258
 
4870 bpr 259
                line_string     =       editArea.textarea.value.substring( start_index, editArea.textarea.selectionEnd + 1);
260
                limit   = line_string.length -1;
261
                nbMatch = 0;
262
                for( i =0; i<limit ; i++ )
263
                {
264
                        if( line_string.substring( limit - i - 1, limit ) == content.substring( 0, i + 1 ) )
265
                                nbMatch = i + 1;
266
                }
267
                // if characters match, we should include them in the selection that will be replaced
268
                if( nbMatch > 0 )
269
                        parent.editAreaLoader.setSelectionRange(editArea.id, editArea.textarea.selectionStart - nbMatch , editArea.textarea.selectionEnd);
8476 bpr 270
 
4870 bpr 271
                parent.editAreaLoader.setSelectedText(editArea.id, content );
272
                range= parent.editAreaLoader.getSelectionRange(editArea.id);
8476 bpr 273
 
4870 bpr 274
                if( cursor_forced_position != -1 )
275
                        new_pos = range["end"] - ( content.length-cursor_forced_position );
276
                else
8476 bpr 277
                        new_pos = range["end"];
4870 bpr 278
                parent.editAreaLoader.setSelectionRange(editArea.id, new_pos, new_pos);
279
                this._hide();
280
        }
8476 bpr 281
 
282
 
4870 bpr 283
        /**
284
         * Parse the AUTO_COMPLETION part of syntax definition files
285
         */
286
        ,_parseSyntaxAutoCompletionDatas: function(){
287
                //foreach syntax loaded
288
                for(var lang in parent.editAreaLoader.load_syntax)
289
                {
290
                        if(!parent.editAreaLoader.syntax[lang]['autocompletion'])       // init the regexp if not already initialized
291
                        {
292
                                parent.editAreaLoader.syntax[lang]['autocompletion']= {};
293
                                // the file has auto completion datas
294
                                if(parent.editAreaLoader.load_syntax[lang]['AUTO_COMPLETION'])
295
                                {
296
                                        // parse them
297
                                        for(var i in parent.editAreaLoader.load_syntax[lang]['AUTO_COMPLETION'])
298
                                        {
299
                                                datas   = parent.editAreaLoader.load_syntax[lang]['AUTO_COMPLETION'][i];
300
                                                tmp     = {};
301
                                                if(datas["CASE_SENSITIVE"]!="undefined" && datas["CASE_SENSITIVE"]==false)
302
                                                        tmp["modifiers"]="i";
303
                                                else
304
                                                        tmp["modifiers"]="";
305
                                                tmp["prefix_separator"]= datas["REGEXP"]["prefix_separator"];
306
                                                tmp["match_prefix_separator"]= new RegExp( datas["REGEXP"]["prefix_separator"] +"$", tmp["modifiers"]);
307
                                                tmp["match_word"]= new RegExp("(?:"+ datas["REGEXP"]["before_word"] +")("+ datas["REGEXP"]["possible_words_letters"] +")$", tmp["modifiers"]);
308
                                                tmp["match_next_letter"]= new RegExp("^("+ datas["REGEXP"]["letter_after_word_must_match"] +")$", tmp["modifiers"]);
309
                                                tmp["keywords"]= {};
310
                                                //console.log( datas["KEYWORDS"] );
311
                                                for( var prefix in datas["KEYWORDS"] )
312
                                                {
313
                                                        tmp["keywords"][prefix]= {
314
                                                                prefix: prefix,
315
                                                                prefix_name: prefix,
316
                                                                prefix_reg: new RegExp("(?:"+ parent.editAreaLoader.get_escaped_regexp( prefix ) +")(?:"+ tmp["prefix_separator"] +")$", tmp["modifiers"] ),
317
                                                                datas: []
318
                                                        };
319
                                                        for( var j=0; j<datas["KEYWORDS"][prefix].length; j++ )
320
                                                        {
321
                                                                tmp["keywords"][prefix]['datas'][j]= {
322
                                                                        is_typing: datas["KEYWORDS"][prefix][j][0],
323
                                                                        // if replace with is empty, replace with the is_typing value
324
                                                                        replace_with: datas["KEYWORDS"][prefix][j][1] ? datas["KEYWORDS"][prefix][j][1].replace('§', datas["KEYWORDS"][prefix][j][0] ) : '',
8476 bpr 325
                                                                        comment: datas["KEYWORDS"][prefix][j][2] ? datas["KEYWORDS"][prefix][j][2] : ''
4870 bpr 326
                                                                };
8476 bpr 327
 
4870 bpr 328
                                                                // the replace with shouldn't be empty
329
                                                                if( tmp["keywords"][prefix]['datas'][j]['replace_with'].length == 0 )
330
                                                                        tmp["keywords"][prefix]['datas'][j]['replace_with'] = tmp["keywords"][prefix]['datas'][j]['is_typing'];
8476 bpr 331
 
4870 bpr 332
                                                                // if the comment is empty, display the replace_with value
333
                                                                if( tmp["keywords"][prefix]['datas'][j]['comment'].length == 0 )
334
                                                                         tmp["keywords"][prefix]['datas'][j]['comment'] = tmp["keywords"][prefix]['datas'][j]['replace_with'].replace(/{@}/g, '' );
335
                                                        }
8476 bpr 336
 
4870 bpr 337
                                                }
338
                                                tmp["max_text_length"]= datas["MAX_TEXT_LENGTH"];
339
                                                parent.editAreaLoader.syntax[lang]['autocompletion'][i] = tmp;
340
                                        }
341
                                }
342
                        }
343
                }
344
        }
8476 bpr 345
 
4870 bpr 346
        ,_checkLetter: function(){
347
                // check that syntax hasn't changed
348
                if( this.curr_syntax_str != editArea.settings['syntax'] )
349
                {
350
                        if( !parent.editAreaLoader.syntax[editArea.settings['syntax']]['autocompletion'] )
351
                                this._parseSyntaxAutoCompletionDatas();
352
                        this.curr_syntax= parent.editAreaLoader.syntax[editArea.settings['syntax']]['autocompletion'];
353
                        this.curr_syntax_str = editArea.settings['syntax'];
354
                        //console.log( this.curr_syntax );
355
                }
8476 bpr 356
 
4870 bpr 357
                if( editArea.is_editable )
358
                {
359
                        time=new Date;
360
                        t1= time.getTime();
361
                        if(editArea.isIE)
362
                                editArea.getIESelection();
363
                        this.selectIndex        = -1;
364
                        start=editArea.textarea.selectionStart;
365
                        var str = editArea.textarea.value;
366
                        var results= [];
8476 bpr 367
 
368
 
4870 bpr 369
                        for(var i in this.curr_syntax)
370
                        {
371
                                var last_chars  = str.substring(Math.max(0, start-this.curr_syntax[i]["max_text_length"]), start);
372
                                var matchNextletter     = str.substring(start, start+1).match( this.curr_syntax[i]["match_next_letter"]);
373
                                // if not writting in the middle of a word or if forcing display
374
                                if( matchNextletter || this.forceDisplay )
375
                                {
376
                                        // check if the last chars match a separator
377
                                        var match_prefix_separator = last_chars.match(this.curr_syntax[i]["match_prefix_separator"]);
8476 bpr 378
 
4870 bpr 379
                                        // check if it match a possible word
380
                                        var match_word= last_chars.match(this.curr_syntax[i]["match_word"]);
8476 bpr 381
 
4870 bpr 382
                                        //console.log( match_word );
383
                                        if( match_word )
384
                                        {
385
                                                var begin_word= match_word[1];
386
                                                var match_curr_word= new RegExp("^"+ parent.editAreaLoader.get_escaped_regexp( begin_word ), this.curr_syntax[i]["modifiers"]);
387
                                                //console.log( match_curr_word );
388
                                                for(var prefix in this.curr_syntax[i]["keywords"])
389
                                                {
390
                                                //      parent.console.log( this.curr_syntax[i]["keywords"][prefix] );
391
                                                        for(var j=0; j<this.curr_syntax[i]["keywords"][prefix]['datas'].length; j++)
392
                                                        {
393
                                                //              parent.console.log( this.curr_syntax[i]["keywords"][prefix]['datas'][j]['is_typing'] );
8476 bpr 394
                                                                // the key word match or force display
4870 bpr 395
                                                                if( this.curr_syntax[i]["keywords"][prefix]['datas'][j]['is_typing'].match(match_curr_word) )
396
                                                                {
397
                                                        //              parent.console.log('match');
398
                                                                        hasMatch = false;
399
                                                                        var before = last_chars.substr( 0, last_chars.length - begin_word.length );
8476 bpr 400
 
4870 bpr 401
                                                                        // no prefix to match => it's valid
402
                                                                        if( !match_prefix_separator && this.curr_syntax[i]["keywords"][prefix]['prefix'].length == 0 )
403
                                                                        {
404
                                                                                if( ! before.match( this.curr_syntax[i]["keywords"][prefix]['prefix_reg'] ) )
405
                                                                                        hasMatch = true;
406
                                                                        }
407
                                                                        // we still need to check the prefix if there is one
408
                                                                        else if( this.curr_syntax[i]["keywords"][prefix]['prefix'].length > 0 )
409
                                                                        {
410
                                                                                if( before.match( this.curr_syntax[i]["keywords"][prefix]['prefix_reg'] ) )
411
                                                                                        hasMatch = true;
412
                                                                        }
8476 bpr 413
 
4870 bpr 414
                                                                        if( hasMatch )
415
                                                                                results[results.length]= [ this.curr_syntax[i]["keywords"][prefix], this.curr_syntax[i]["keywords"][prefix]['datas'][j] ];
8476 bpr 416
                                                                }
4870 bpr 417
                                                        }
418
                                                }
419
                                        }
420
                                        // it doesn't match any possible word but we want to display something
421
                                        // we'll display to list of all available words
422
                                        else if( this.forceDisplay || match_prefix_separator )
423
                                        {
424
                                                for(var prefix in this.curr_syntax[i]["keywords"])
425
                                                {
426
                                                        for(var j=0; j<this.curr_syntax[i]["keywords"][prefix]['datas'].length; j++)
427
                                                        {
428
                                                                hasMatch = false;
429
                                                                // no prefix to match => it's valid
430
                                                                if( !match_prefix_separator && this.curr_syntax[i]["keywords"][prefix]['prefix'].length == 0 )
431
                                                                {
432
                                                                        hasMatch        = true;
433
                                                                }
434
                                                                // we still need to check the prefix if there is one
435
                                                                else if( match_prefix_separator && this.curr_syntax[i]["keywords"][prefix]['prefix'].length > 0 )
436
                                                                {
437
                                                                        var before = last_chars; //.substr( 0, last_chars.length );
438
                                                                        if( before.match( this.curr_syntax[i]["keywords"][prefix]['prefix_reg'] ) )
439
                                                                                hasMatch = true;
8476 bpr 440
                                                                }
441
 
4870 bpr 442
                                                                if( hasMatch )
8476 bpr 443
                                                                        results[results.length]= [ this.curr_syntax[i]["keywords"][prefix], this.curr_syntax[i]["keywords"][prefix]['datas'][j] ];
4870 bpr 444
                                                        }
445
                                                }
446
                                        }
447
                                }
448
                        }
8476 bpr 449
 
4870 bpr 450
                        // there is only one result, and we can select it automatically
451
                        if( results.length == 1 && this.autoSelectIfOneResult )
452
                        {
453
                        //      console.log( results );
454
                                this._select( results[0][1]['replace_with'] );
455
                        }
456
                        else if( results.length == 0 )
457
                        {
458
                                this._hide();
459
                        }
460
                        else
461
                        {
462
                                // build the suggestion box content
463
                                var lines=[];
464
                                for(var i=0; i<results.length; i++)
465
                                {
466
                                        var line= "<li><a href=\"#\" class=\"entry\" onmousedown=\"EditArea_autocompletion._select('"+ results[i][1]['replace_with'].replace(new RegExp('"', "g"), "&quot;") +"');return false;\">"+ results[i][1]['comment'];
467
                                        if(results[i][0]['prefix_name'].length>0)
468
                                                line+='<span class="prefix">'+ results[i][0]['prefix_name'] +'</span>';
469
                                        line+='</a></li>';
470
                                        lines[lines.length]=line;
471
                                }
472
                                // sort results
473
                                this.container.innerHTML                = '<ul>'+ lines.sort().join('') +'</ul>';
8476 bpr 474
 
4870 bpr 475
                                var cursor      = _$("cursor_pos");
476
                                this.container.style.top                = ( cursor.cursor_top + editArea.lineHeight ) +"px";
477
                                this.container.style.left               = ( cursor.cursor_left + 8 ) +"px";
478
                                this._show();
479
                        }
8476 bpr 480
 
4870 bpr 481
                        this.autoSelectIfOneResult = false;
482
                        time=new Date;
483
                        t2= time.getTime();
8476 bpr 484
 
4870 bpr 485
                        //parent.console.log( begin_word +"\n"+ (t2-t1) +"\n"+ html );
486
                }
487
        }
488
};
489
 
490
// Load as a plugin
491
editArea.settings['plugins'][ editArea.settings['plugins'].length ] = 'autocompletion';
8476 bpr 492
editArea.add_plugin('autocompletion', EditArea_autocompletion);