~ubuntu-branches/ubuntu/vivid/kate/vivid-updates

« back to all changes in this revision

Viewing changes to part/script/data/indentation/cppstyle.js

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2014-12-04 16:49:41 UTC
  • mfrom: (1.6.6)
  • Revision ID: package-import@ubuntu.com-20141204164941-l3qbvsly83hhlw2v
Tags: 4:14.11.97-0ubuntu1
* New upstream release
* Update build-deps and use pkg-kde v3 for Qt 5 build
* kate-data now kate5-data for co-installability

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* kate-script
2
 
 * name: C++/boost Style
3
 
 * license: LGPL
4
 
 * author: Alex Turbov <i.zaufi@gmail.com>
5
 
 * revision: 30
6
 
 * kate-version: 3.4
7
 
 * priority: 10
8
 
 * indent-languages: C++, C++/Qt4, ISO C++
9
 
 * required-syntax-style: C++
10
 
 *
11
 
 * This file is part of the Kate Project.
12
 
 *
13
 
 * This library is free software; you can redistribute it and/or
14
 
 * modify it under the terms of the GNU Library General Public
15
 
 * License as published by the Free Software Foundation; either
16
 
 * version 2 of the License, or (at your option) any later version.
17
 
 *
18
 
 * This library is distributed in the hope that it will be useful,
19
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21
 
 * Library General Public License for more details.
22
 
 *
23
 
 * You should have received a copy of the GNU Library General Public License
24
 
 * along with this library; see the file COPYING.LIB.  If not, write to
25
 
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26
 
 * Boston, MA 02110-1301, USA.
27
 
 */
28
 
 
29
 
/**
30
 
 * \warning This indenter designed to be used with my C++ style! It consists
31
 
 * of mix of boost and STL styles + some my, unfortunately (still)
32
 
 * undocumented additions I've found useful after ~15 years of C++ coding.
33
 
 * So \b LOT of things here are \b HARDCODED and I \b DON'T care about other
34
 
 * styles!!!
35
 
 *
36
 
 * Ok, you've been warned :-)
37
 
 *
38
 
 * More info available here: http://zaufi.github.io/programming/2013/11/29/kate-cppstyle-indenter/
39
 
 *
40
 
 * Some settings it assumes being in effect:
41
 
 * - indent-width 4;
42
 
 * - space-indent true;
43
 
 * - auto-brackets true; <-- TODO REALLY?
44
 
 * - replace-tabs true;
45
 
 * - replace-tabs-save true;
46
 
 *
47
 
 * \todo Better to check (assert) some of that modelines...
48
 
 */
49
 
 
50
 
// required katepart js libraries
51
 
require ("range.js");
52
 
require ("string.js");
53
 
require ("utils.js")
54
 
 
55
 
// specifies the characters which should trigger indent, beside the default '\n'
56
 
// ':' is for `case'/`default' and class access specifiers: public, protected, private
57
 
// '/' is for single line comments
58
 
// ',' for parameter list
59
 
// '<' and '>' is for templates
60
 
// '#' is for preprocessor directives
61
 
// ')' is for align dangling close bracket
62
 
// ';' is for align `for' parts
63
 
// ' ' is to add a '()' after `if', `while', `for', ...
64
 
// TBD <others>
65
 
triggerCharacters = "{}()[]<>/:;,#\\?!|&/%.@ '\"=*^";
66
 
 
67
 
var debugMode = false;
68
 
 
69
 
//BEGIN global variables and functions
70
 
var gIndentWidth = 4;
71
 
var gSameLineCommentStartAt = 60;                           ///< Position for same-line-comments (inline comments)
72
 
var gBraceMap = {
73
 
    '(': ')', ')': '('
74
 
  , '<': '>', '>': '<'
75
 
  , '{': '}', '}': '{'
76
 
  , '[': ']', ']': '['
77
 
  };
78
 
//END global variables and functions
79
 
 
80
 
/**
81
 
 * Try to (re)align (to 60th position) inline comment if present
82
 
 * \return \c true if comment line was moved above
83
 
 */
84
 
function alignInlineComment(line)
85
 
{
86
 
    // Check is there any comment on the current line
87
 
    var sc = splitByComment(line);
88
 
    // Did we found smth and if so, make sure it is not a string or comment...
89
 
    if (sc.hasComment && !isStringOrComment(line, sc.before.length - 1))
90
 
    {
91
 
        var rbefore = sc.before.rtrim();
92
 
        var cursor = view.cursorPosition();
93
 
        /// \attention Kate has a BUG: even if everything is Ok and no realign
94
 
        /// required, document gets modified anyway! So condition below
95
 
        /// designed to prevent document modification w/o real actions won't
96
 
        /// help anyway :-( Need to fix Kate before!
97
 
        if (rbefore.length < gSameLineCommentStartAt && sc.before.length != gSameLineCommentStartAt)
98
 
        {
99
 
            // Ok, test on the line is shorter than needed.
100
 
            // But what about current padding?
101
 
            if (sc.before.length < gSameLineCommentStartAt)
102
 
                // Need to add some padding
103
 
                document.insertText(
104
 
                    line
105
 
                  , sc.before.length
106
 
                  , String().fill(' ', gSameLineCommentStartAt - sc.before.length)
107
 
                  );
108
 
            else
109
 
                // Need to remove a redundant padding
110
 
                document.removeText(line, gSameLineCommentStartAt, line, sc.before.length);
111
 
            // Keep cursor at the place we've found it before
112
 
            view.setCursorPosition(cursor);
113
 
        }
114
 
        else if (gSameLineCommentStartAt < rbefore.length)
115
 
        {
116
 
            // Move inline comment before the current line
117
 
            var startPos = document.firstColumn(line);
118
 
            var currentLineText = String().fill(' ', startPos) + "//" + sc.after.rtrim() + "\n";
119
 
            document.removeText(line, rbefore.length, line, document.lineLength(line));
120
 
            document.insertText(line, 0, currentLineText);
121
 
            // Keep cursor at the place we've found it before
122
 
            view.setCursorPosition(new Cursor(line + 1, cursor.column));
123
 
            return true;
124
 
        }
125
 
    }
126
 
    return false;
127
 
}
128
 
 
129
 
 
130
 
function tryIndentRelativePrevNonCommentLine(line)
131
 
{
132
 
    var current_line = line - 1;
133
 
    while (0 <= current_line && isStringOrComment(current_line, document.firstColumn(current_line)))
134
 
        --current_line;
135
 
    if (current_line == -1)
136
 
        return -2;
137
 
 
138
 
    var prevLineFirstChar = document.firstChar(current_line);
139
 
    var needHalfUnindent = !(
140
 
        prevLineFirstChar == ','
141
 
      || prevLineFirstChar == ':'
142
 
      || prevLineFirstChar == '?'
143
 
      || prevLineFirstChar == '<'
144
 
      || prevLineFirstChar == '>'
145
 
      || prevLineFirstChar == '&'
146
 
      );
147
 
    return document.firstColumn(current_line) - (needHalfUnindent ? 2 : 0);
148
 
}
149
 
 
150
 
/**
151
 
 * Try to keep same-line comment.
152
 
 * I.e. if \c ENTER was hit on a line w/ inline comment and before it,
153
 
 * try to keep it on a previous line...
154
 
 */
155
 
function tryToKeepInlineComment(line)
156
 
{
157
 
    // Make sure that there is some text still present on a prev line
158
 
    // i.e. it was just splitted and same-line-comment must be moved back to it
159
 
    if (document.line(line - 1).trim().length == 0)
160
 
        return;
161
 
 
162
 
    // Check is there any comment on the current (non empty) line
163
 
    var sc = splitByComment(line);
164
 
    dbg("sc.hasComment="+sc.hasComment);
165
 
    dbg("sc.before.rtrim().length="+sc.before.rtrim().length);
166
 
    if (sc.hasComment)
167
 
    {
168
 
        // Ok, here is few cases possible when ENTER pressed in different positions
169
 
        // |  |smth|was here; |        |// comment
170
 
        //
171
 
        // If sc.before has some text, it means that cursor was in the middle of some
172
 
        // non-commented text, and part of it left on a prev line, so we have to move
173
 
        // the comment back to that line...
174
 
        if (sc.before.trim().length > 0)                    // Is there some text before comment?
175
 
        {
176
 
            var lastPos = document.lastColumn(line - 1);    // Get last position of non space char @ prev line
177
 
            // Put the comment text to the prev line w/ padding
178
 
            document.insertText(
179
 
                line - 1
180
 
              , lastPos + 1
181
 
              , String().fill(' ', gSameLineCommentStartAt - lastPos - 1)
182
 
                  + "//"
183
 
                  + sc.after.rtrim()
184
 
              );
185
 
            // Remove it from current line starting from current position
186
 
            // 'till the line end
187
 
            document.removeText(line, sc.before.rtrim().length, line, document.lineLength(line));
188
 
        }
189
 
        else
190
 
        {
191
 
            // No text before comment. Need to remove possible spaces from prev line...
192
 
            var prevLine = line - 1;
193
 
            document.removeText(
194
 
                prevLine
195
 
              , document.lastColumn(prevLine) + 1
196
 
              , prevLine
197
 
              , document.lineLength(prevLine)
198
 
              );
199
 
        }
200
 
    }
201
 
}
202
 
 
203
 
/**
204
 
 * Return a current preprocessor indentation level
205
 
 * \note <em>preprocessor indentation</em> means how deep the current line
206
 
 * inside of \c #if directives.
207
 
 * \warning Negative result means that smth wrong w/ a source code
208
 
 */
209
 
function getPreprocessorLevelAt(line)
210
 
{
211
 
    // Just follow towards start and count #if/#endif directives
212
 
    var currentLine = line;
213
 
    var result = 0;
214
 
    while (currentLine >= 0)
215
 
    {
216
 
        currentLine--;
217
 
        var currentLineText = document.line(currentLine);
218
 
        if (currentLineText.search(/^\s*#\s*(if|ifdef|ifndef)\s+.*$/) != -1)
219
 
            result++;
220
 
        else if (currentLineText.search(/^\s*#\s*endif.*$/) != -1)
221
 
            result--;
222
 
    }
223
 
    return result;
224
 
}
225
 
 
226
 
/**
227
 
 * Check if \c ENTER was hit between ()/{}/[]/<>
228
 
 * \todo Match closing brace forward, put content between
229
 
 * braces on a separate line and align a closing brace.
230
 
 */
231
 
function tryBraceSplit_ch(line)
232
 
{
233
 
    var result = -1;
234
 
    // Get last char from previous line (opener) and a first from the current (closer)
235
 
    var firstCharPos = document.lastColumn(line - 1);
236
 
    var firstChar = document.charAt(line - 1, firstCharPos);
237
 
    var lastCharPos = document.firstColumn(line);
238
 
    var lastChar = document.charAt(line, lastCharPos);
239
 
 
240
 
    var isCurveBracketsMatched = (firstChar == '{' && lastChar == '}');
241
 
    var isBracketsMatched = isCurveBracketsMatched
242
 
        || (firstChar == '[' && lastChar == ']')
243
 
        || (firstChar == '(' && lastChar == ')')
244
 
        || (firstChar == '<' && lastChar == '>')
245
 
        ;
246
 
    if (isBracketsMatched)
247
 
    {
248
 
        var currentIndentation = document.firstVirtualColumn(line - 1);
249
 
        result = currentIndentation + gIndentWidth;
250
 
        document.insertText(line, document.firstColumn(line), "\n");
251
 
        document.indent(new Range(line + 1, 0, line + 1, 1), currentIndentation / gIndentWidth);
252
 
        // Add half-tab (2 spaces) if matched not a curve bracket or
253
 
        // open character isn't the only one on the line
254
 
        var isOpenCharTheOnlyOnLine = (document.firstColumn(line - 1) == firstCharPos);
255
 
        if (!(isCurveBracketsMatched || isOpenCharTheOnlyOnLine))
256
 
            document.insertText(line + 1, document.firstColumn(line + 1), "  ");
257
 
        view.setCursorPosition(line, result);
258
 
    }
259
 
    if (result != -1)
260
 
    {
261
 
        dbg("tryBraceSplit_ch result="+result);
262
 
        tryToKeepInlineComment(line);
263
 
    }
264
 
    return result;
265
 
}
266
 
 
267
 
/**
268
 
 * Even if counterpart brace not found (\sa \c tryBraceSplit_ch), align the current line
269
 
 * to one level deeper if last char on a previous line is one of open braces.
270
 
 * \code
271
 
 *     foo(|blah);
272
 
 *     // or
273
 
 *     {|
274
 
 *     // or
275
 
 *     smth<|blah, blah>
276
 
 *     // or
277
 
 *     array[|idx] = blah;
278
 
 * \endcode
279
 
 */
280
 
function tryToAlignAfterOpenBrace_ch(line)
281
 
{
282
 
    var result = -1;
283
 
    var pos = document.lastColumn(line - 1);
284
 
    var ch = document.charAt(line - 1, pos);
285
 
 
286
 
    if (ch == '(' || ch == '[')
287
 
    {
288
 
        result = document.firstColumn(line - 1) + gIndentWidth;
289
 
    }
290
 
    else if (ch == '{')
291
 
    {
292
 
        if (document.startsWith(line - 1, "namespace", true))
293
 
            result = 0;
294
 
        else
295
 
            result = document.firstColumn(line - 1) + gIndentWidth;
296
 
    }
297
 
    else if (ch == '<')
298
 
    {
299
 
        // Does it looks like 'operator<<'?
300
 
        if (document.charAt(line - 1, pos - 1) != '<')
301
 
            result = document.firstColumn(line - 1) + gIndentWidth;
302
 
        else
303
 
            result = document.firstColumn(line - 1) + (gIndentWidth / 2);
304
 
    }
305
 
 
306
 
    if (result != -1)
307
 
    {
308
 
        tryToKeepInlineComment(line);
309
 
        dbg("tryToAlignOpenBrace_ch result="+result);
310
 
    }
311
 
    return result;
312
 
}
313
 
 
314
 
function tryToAlignBeforeCloseBrace_ch(line)
315
 
{
316
 
    var result = -1;
317
 
    var pos = document.firstColumn(line);
318
 
    var ch = document.charAt(line, pos);
319
 
 
320
 
    if (ch == '}' || ch == ')' || ch == ']')
321
 
    {
322
 
        var openBracePos = document.anchor(line, pos, ch);
323
 
        dbg("Found open brace @", openBracePos);
324
 
        if (openBracePos.isValid())
325
 
            result = document.firstColumn(openBracePos.line) + (ch == '}' ? 0 : (gIndentWidth / 2));
326
 
    }
327
 
    else if (ch == '>')
328
 
    {
329
 
        // TBD
330
 
    }
331
 
 
332
 
    if (result != -1)
333
 
    {
334
 
        tryToKeepInlineComment(line);
335
 
        dbg("tryToAlignBeforeCloseBrace_ch result="+result);
336
 
    }
337
 
    return result;
338
 
}
339
 
 
340
 
function tryToAlignBeforeComma_ch(line)
341
 
{
342
 
    var result = -1;
343
 
    var pos = document.firstColumn(line);
344
 
    var ch = document.charAt(line, pos);
345
 
 
346
 
    if (line > 0 && (ch == ',' || ch == ';'))
347
 
    {
348
 
        var openBracePos = document.anchor(line, pos, '(');
349
 
        if (!openBracePos.isValid())
350
 
            openBracePos = document.anchor(line, pos, '[');
351
 
 
352
 
        if (openBracePos.isValid())
353
 
            result = document.firstColumn(openBracePos.line) + 2;
354
 
    }
355
 
 
356
 
    if (result != -1)
357
 
    {
358
 
        tryToKeepInlineComment(line);
359
 
        dbg("tryToAlignBeforeComma_ch result="+result);
360
 
    }
361
 
    return result;
362
 
}
363
 
 
364
 
/// Check if a multiline comment introduced on a previous line
365
 
function tryMultilineCommentStart_ch(line)
366
 
{
367
 
    var result = -1;
368
 
 
369
 
    // Check if multiline comment was started on the line
370
 
    // and ENTER wan't pressed right after a /*C-style comment*/
371
 
    if (document.startsWith(line - 1, "/*", true) && !document.endsWith(line - 1, "*/", true))
372
 
    {
373
 
        var filler = String().fill(' ', document.firstVirtualColumn(line - 1) + 1);
374
 
        var padding = filler + "* ";
375
 
        // If next line (if present) doesn't looks like a continue of the current comment,
376
 
        // then append a comment closer also...
377
 
        if ((line + 1) < document.lines())
378
 
        {
379
 
            // Maybe user wants to extend a multiline C-style/Doxygen comment
380
 
            // by pressing ENTER at start of it?
381
 
            if (!document.startsWith(line + 1, "*", true))
382
 
            {
383
 
                // ... doesn't looks like a multiline comment
384
 
                padding += "\n" + filler;
385
 
                // Maybe user just splits a C-style comment?
386
 
                if (!document.startsWith(line, "*/", true))
387
 
                    padding += document.endsWith(line, "*/", true) ? "* " : "*/";
388
 
                else
389
 
                    document.removeText(line, 0, line, document.firstColumn(line))
390
 
            }                                               // else, no need to append a closing */
391
 
        }
392
 
        else                                                // There is no a next line...
393
 
        {
394
 
            padding += "\n" + filler;
395
 
            if (!document.startsWith(line, "*/", true))
396
 
                padding += document.endsWith(line, "*/", true) ? "* " : "*/";
397
 
            else
398
 
                document.removeText(line, 0, line, document.firstColumn(line))
399
 
        }
400
 
        document.insertText(line, 0, padding);
401
 
        view.setCursorPosition(line, filler.length + 2);
402
 
        result = -2;
403
 
    }
404
 
    if (result != -1)
405
 
    {
406
 
        dbg("tryMultilineCommentStart_ch result="+result);
407
 
    }
408
 
    return result;
409
 
}
410
 
 
411
 
/// Check if \c ENTER was hit inside or at last line of a multiline comment
412
 
function tryMultilineCommentCont_ch(line)
413
 
{
414
 
    var result = -1;
415
 
    // Check if multiline comment continued on the line:
416
 
    // 0) it starts w/ a start
417
 
    // 1) and followed by a space (i.e. it doesn't looks like a dereference) or nothing
418
 
    var firstCharPos = document.firstColumn(line - 1);
419
 
    var prevLineFirstChar = document.charAt(line - 1, firstCharPos);
420
 
    var prevLineSecondChar = document.charAt(line - 1, firstCharPos + 1);
421
 
    if (prevLineFirstChar == '*' && (prevLineSecondChar == ' ' || prevLineSecondChar == -1))
422
 
    {
423
 
        if (document.charAt(line - 1, firstCharPos + 1) == '/')
424
 
            // ENTER pressed after multiline comment: unindent 1 space!
425
 
            result = firstCharPos - 1;
426
 
        else
427
 
        {
428
 
            // Ok, ENTER pressed inside of the multiline comment:
429
 
            // just append one more line...
430
 
            var filler = String().fill(' ', document.firstColumn(line - 1));
431
 
            // Try to continue a C-style comment
432
 
            document.insertText(line, 0, filler + "* ");
433
 
            result = filler.length;
434
 
        }
435
 
    }
436
 
    if (result != -1)
437
 
    {
438
 
        dbg("tryMultilineCommentCont_ch result="+result);
439
 
    }
440
 
    return result;
441
 
}
442
 
 
443
 
function tryAfterCloseMultilineComment_ch(line)
444
 
{
445
 
    var result = -1;
446
 
    if (document.startsWith(line - 1, "*/", true))
447
 
    {
448
 
        result = document.firstColumn(line - 1) - 1;
449
 
    }
450
 
    if (result != -1)
451
 
    {
452
 
        dbg("tryAfterCloseMultilineComment_ch result="+result);
453
 
    }
454
 
    return result;
455
 
}
456
 
 
457
 
/**
458
 
 * Check if a current line has a text after cursor position
459
 
 * and a previous one has a comment, then append a <em>"// "</em>
460
 
 * before cursor and realign if latter was inline comment...
461
 
 */
462
 
function trySplitComment_ch(line)
463
 
{
464
 
    var result = -1;
465
 
    if (document.lastColumn(line) != -1)
466
 
    {
467
 
        // Ok, current line has some text after...
468
 
        // NOTE There is should be at least one space between
469
 
        // the text and the comment
470
 
        var match = /^(.*\s)(\/\/)(.*)$/.exec(document.line(line - 1));
471
 
        if (match != null && 0 < match[3].trim().length)    // If matched and there is some text in a comment
472
 
        {
473
 
            if (0 < match[1].trim().length)                 // Is there some text before the comment?
474
 
            {
475
 
                // Align comment to gSameLineCommentStartAt
476
 
                result = gSameLineCommentStartAt;
477
 
            }
478
 
            else
479
 
            {
480
 
                result = match[1].length;
481
 
            }
482
 
            var leadMatch = /^([^\s]*\s+).*$/.exec(match[3]);
483
 
            var lead = "";
484
 
            if (leadMatch != null)
485
 
                lead = leadMatch[1];
486
 
            else
487
 
                lead = " ";
488
 
            document.insertText(line, 0, "//" + lead);
489
 
        }
490
 
    }
491
 
    if (result != -1)
492
 
    {
493
 
        dbg("trySplitComment_ch result="+result);
494
 
    }
495
 
    return result;
496
 
}
497
 
 
498
 
/**
499
 
 * \brief Indent a next line after some keywords.
500
 
 *
501
 
 * Incrase indent after the following keywords:
502
 
 * - \c if
503
 
 * - \c else
504
 
 * - \c for
505
 
 * - \c while
506
 
 * - \c do
507
 
 * - \c case
508
 
 * - \c default
509
 
 * - \c return
510
 
 * - and access modifiers \c public, \c protected and \c private
511
 
 */
512
 
function tryIndentAfterSomeKeywords_ch(line)
513
 
{
514
 
    var result = -1;
515
 
    // Check if ENTER was pressed after some keywords...
516
 
    var sr = splitByComment(line - 1);
517
 
    var prevString = sr.before;
518
 
    dbg("tryIndentAfterSomeKeywords_ch prevString='"+prevString+"'");
519
 
    var r = /^(\s*)((if|for|while)\s*\(|\bdo\b|\breturn\b|(((public|protected|private)(\s+(slots|Q_SLOTS))?)|default|case\s+.*)\s*:).*$/
520
 
      .exec(prevString);
521
 
    if (r != null)
522
 
    {
523
 
        dbg("r=",r);
524
 
        if (!r[2].startsWith("return") || !prevString.rtrim().endsWith(';'))
525
 
            result = r[1].length + gIndentWidth;
526
 
    }
527
 
    else
528
 
    {
529
 
        r = /^\s*\belse\b.*$/.exec(prevString)
530
 
        if (r != null)
531
 
        {
532
 
            var prevPrevString = stripComment(line - 2);
533
 
            dbg("tryIndentAfterSomeKeywords_ch prevPrevString='"+prevPrevString+"'");
534
 
            if (prevPrevString.endsWith('}'))
535
 
                result = document.firstColumn(line - 2);
536
 
            else if (prevPrevString.match(/^\s*[\])>]/))
537
 
                result = document.firstColumn(line - 2) - gIndentWidth - (gIndentWidth / 2);
538
 
            else
539
 
                result = document.firstColumn(line - 2) - gIndentWidth;
540
 
            // Realign 'else' statement if needed
541
 
            var pp = document.firstColumn(line - 1);
542
 
            if (pp < result)
543
 
                document.insertText(line - 1, 0, String().fill(' ', result - pp));
544
 
            else if (result < pp)
545
 
                document.removeText(line - 1, 0, line - 1, pp - result);
546
 
            result += gIndentWidth;
547
 
        }
548
 
    }
549
 
    if (result != -1)
550
 
    {
551
 
        tryToKeepInlineComment(line);
552
 
        dbg("tryIndentAfterSomeKeywords_ch result="+result);
553
 
    }
554
 
    return result;
555
 
}
556
 
 
557
 
/**
558
 
 * Try to indent a line right after a dangling semicolon
559
 
 * (possible w/ leading close braces and comment after)
560
 
 * \code
561
 
 *     foo(
562
 
 *         blah
563
 
 *     );|
564
 
 * \endcode
565
 
 */
566
 
function tryAfterDanglingSemicolon_ch(line)
567
 
{
568
 
    var result = -1;
569
 
    var prevString = document.line(line - 1);
570
 
    var r = /^(\s*)(([\)\]}]?\s*)*([\)\]]\s*))?;/.exec(prevString);
571
 
    if (r != null)
572
 
    {
573
 
        result = Math.floor(r[1].length / 4) * 4;
574
 
    }
575
 
    else
576
 
    {
577
 
        // Does it looks like a template tail?
578
 
        // i.e. smth like this:
579
 
        // typedef boost::mpl::blah<
580
 
        //    params
581
 
        //  > type;|
582
 
        r = /^(\s*)([>]+).*;/.exec(prevString);
583
 
        if (r != null)
584
 
            result = Math.floor(r[1].length / 4) * 4;
585
 
    }
586
 
    if (result != -1)
587
 
    {
588
 
        tryToKeepInlineComment(line);
589
 
        dbg("tryDanglingSemicolon_ch result="+result);
590
 
    }
591
 
    return result;
592
 
}
593
 
 
594
 
/**
595
 
 * Check if \c ENTER pressed after equal sign
596
 
 * \code
597
 
 *     blah =
598
 
 *         |blah
599
 
 * \endcode
600
 
 */
601
 
function tryAfterEqualChar_ch(line)
602
 
{
603
 
    var result = -1;
604
 
    var pos = document.lastColumn(line - 1);
605
 
    if (document.charAt(line - 1, pos) == '=')
606
 
        result = document.firstColumn(line - 1) + gIndentWidth;
607
 
    if (result != -1)
608
 
    {
609
 
        tryToKeepInlineComment(line);
610
 
        dbg("tryAfterEqualChar_ch result="+result);
611
 
    }
612
 
    return result;
613
 
}
614
 
 
615
 
/// Check if \c ENTER hits after \c #define w/ a backslash
616
 
function tryMacroDefinition_ch(line)
617
 
{
618
 
    var result = -1;
619
 
    var prevString = document.line(line - 1);
620
 
    if (prevString.search(/^\s*#\s*define\s+.*\\$/) != -1)
621
 
        result = gIndentWidth;
622
 
    if (result != -1)
623
 
    {
624
 
        dbg("tryMacroDefinition_ch result="+result);
625
 
    }
626
 
    return result;
627
 
}
628
 
 
629
 
/**
630
 
 * Do not incrase indent if ENTER pressed before access
631
 
 * specifier (i.e. public/private/protected)
632
 
 */
633
 
function tryBeforeAccessSpecifier_ch(line)
634
 
{
635
 
    var result = -1;
636
 
    if (document.line(line).match(/(public|protected|private):/))
637
 
    {
638
 
        var openPos = document.anchor(line, 0, '{');
639
 
        if (openPos.isValid())
640
 
            result = document.firstColumn(openPos.line);
641
 
    }
642
 
    if (result != -1)
643
 
    {
644
 
        tryToKeepInlineComment(line);
645
 
        dbg("tryBeforeAccessSpecifier_ch result="+result);
646
 
    }
647
 
    return result;
648
 
}
649
 
 
650
 
/**
651
 
 * Try to align a line w/ a leading (word) delimiter symbol
652
 
 * (i.e. not an identifier and a brace)
653
 
 */
654
 
function tryBeforeDanglingDelimiter_ch(line)
655
 
{
656
 
    var result = -1;
657
 
    var halfTabNeeded =
658
 
        // current line do not starts w/ a comment
659
 
        !document.line(line).ltrim().startsWith("//")
660
 
        // if a previous line starts w/ an identifier
661
 
      && (document.line(line - 1).search(/^\s*[A-Za-z_][A-Za-z0-9_]*/) != -1)
662
 
        // but the current one starts w/ a delimiter (which is looks like operator)
663
 
      && (document.line(line).search(/^\s*[,%&<=:\|\-\?\/\+\*\.]/) != -1)
664
 
      ;
665
 
    // check if we r at function call or array index
666
 
    var insideBraces = document.anchor(line, document.firstColumn(line), '(').isValid()
667
 
      || document.anchor(line, document.firstColumn(line), '[').isValid()
668
 
      ;
669
 
    if (halfTabNeeded)
670
 
        result = document.firstVirtualColumn(line - 1) + (insideBraces ? -2 : 2);
671
 
    if (result != -1)
672
 
    {
673
 
        tryToKeepInlineComment(line);
674
 
        dbg("tryBeforeDanglingDelimiter_ch result="+result);
675
 
    }
676
 
    return result;
677
 
}
678
 
 
679
 
function tryPreprocessor_ch(line)
680
 
{
681
 
    var result = -1;
682
 
    if (document.firstChar(line) == '#')
683
 
    {
684
 
        result = 0;
685
 
        var text = document.line(line);
686
 
        // Get current depth level
687
 
        var currentLevel = getPreprocessorLevelAt(line);
688
 
        if (currentLevel > 0)
689
 
        {
690
 
            // How much spaces we have after hash?
691
 
            var spacesCnt = 0;
692
 
            var column = document.firstColumn(line) + 1;
693
 
            var i = column;
694
 
            for (; i < text.length; i++)
695
 
            {
696
 
                if (text[i] != ' ')
697
 
                    break;
698
 
                spacesCnt++;
699
 
            }
700
 
            var wordAfterHash = document.wordAt(line, i);
701
 
            dbg("wordAfterHash='"+wordAfterHash+"'");
702
 
            if (wordAfterHash[0] == '#')
703
 
                wordAfterHash = wordAfterHash.substring(1, wordAfterHash.length);
704
 
            if (wordAfterHash == "else" || wordAfterHash == "elif" || wordAfterHash == "endif")
705
 
                currentLevel--;
706
 
            var paddingLen = (currentLevel == 0) ? 0 : (currentLevel - 1) * 2 + 1;
707
 
            if (spacesCnt < paddingLen)
708
 
            {
709
 
                var padding = String().fill(' ', paddingLen - spacesCnt);
710
 
                document.insertText(line, column, padding);
711
 
            }
712
 
            else if (paddingLen < spacesCnt)
713
 
            {
714
 
                document.removeText(line, column, line, column + spacesCnt - paddingLen);
715
 
            }
716
 
        }
717
 
    }
718
 
    if (result != -1)
719
 
    {
720
 
        dbg("tryPreprocessor_ch result="+result);
721
 
    }
722
 
    return result;
723
 
}
724
 
 
725
 
/**
726
 
 * Check if \c ENTER was pressed on a start of line and
727
 
 * after a block comment.
728
 
 */
729
 
function tryAfterBlockComment_ch(line)
730
 
{
731
 
    var result = -1;
732
 
    if (0 < line)
733
 
    {
734
 
        var prev_non_empty_line = document.prevNonEmptyLine(line - 1);
735
 
        if (prev_non_empty_line != -1 && document.line(prev_non_empty_line).trim().startsWith("*/"))
736
 
        {
737
 
            var p = document.firstColumn(prev_non_empty_line);
738
 
            if ((p % gIndentWidth) != 0)
739
 
                result = Math.floor(p / gIndentWidth) * gIndentWidth;
740
 
        }
741
 
    }
742
 
    if (result != -1)
743
 
    {
744
 
        dbg("tryAfterBlockComment_ch result="+result);
745
 
    }
746
 
    return result;
747
 
}
748
 
 
749
 
/**
750
 
 * Check if \c ENTER was pressed after \c break or \c continue statements
751
 
 * and if so, unindent the current line.
752
 
 */
753
 
function tryAfterBreakContinue_ch(line)
754
 
{
755
 
    var result = -1;
756
 
    var currentLineText = document.line(line - 1).ltrim();
757
 
    var should_proceed = currentLineText.startsWith("break;") || currentLineText.startsWith("continue;")
758
 
    if (should_proceed)
759
 
    {
760
 
        result = document.firstColumn(line - 1) - gIndentWidth;
761
 
    }
762
 
    if (result != -1)
763
 
    {
764
 
        dbg("tryAfterBreakContinue_ch result="+result);
765
 
    }
766
 
    return result;
767
 
}
768
 
 
769
 
/// \internal
770
 
function getStringAligmentAfterSplit(line)
771
 
{
772
 
    var prevLineFirstChar = document.firstChar(line - 1);
773
 
    var halfIndent = prevLineFirstChar == ','
774
 
      || prevLineFirstChar == ':'
775
 
      || prevLineFirstChar == '?'
776
 
      || prevLineFirstChar == '<'
777
 
      || prevLineFirstChar == '>'
778
 
      || prevLineFirstChar == '&'
779
 
      ;
780
 
    return document.firstColumn(line - 1) + (
781
 
        prevLineFirstChar != '"'
782
 
      ? (halfIndent ? (gIndentWidth / 2) : gIndentWidth)
783
 
      : 0
784
 
      );
785
 
}
786
 
 
787
 
/**
788
 
 * Handle the case when \c ENTER has pressed in the middle of a string.
789
 
 * Find a string begin (a quote char) and analyze if it is a C++11 raw
790
 
 * string literal. If it is not, add a "closing" quote to a previous line
791
 
 * and to the current one. Align a 2nd part (the moved down one) of a string
792
 
 * according a previous line. If latter is a pure string, then give the same
793
 
 * indentation level, otherwise incrase it to one \c TAB.
794
 
 *
795
 
 * Here is few cases possible:
796
 
 * - \c ENTER has pressed in a line <code>auto some = ""|</code>, so a new
797
 
 *   line just have an empty string or some text which is doesn't matter now;
798
 
 * - \c ENTER has pressed in a line <code>auto some = "possible some text here| and here"</code>,
799
 
 *   then a new line will have <code> and here"</code> text
800
 
 *
801
 
 * In both cases attribute at (line-1, lastColumn-1) will be \c String
802
 
 */
803
 
function trySplitString_ch(line)
804
 
{
805
 
    var result = -1;
806
 
    var column = document.lastColumn(line - 1);
807
 
 
808
 
    if (isComment(line - 1, column))
809
 
        return result;                                      // Do nothing for comments
810
 
 
811
 
    // Check if last char on a prev line has string attribute
812
 
    var lastColumnIsString = isString(line - 1, column);
813
 
    var firstColumnIsString = isString(line, 0);
814
 
    var firstChar = (document.charAt(line, 0) == '"');
815
 
    if (!lastColumnIsString)                                // If it is not,
816
 
    {
817
 
        // TODO TBD
818
 
        if (firstColumnIsString && firstChar == '"')
819
 
            result = getStringAligmentAfterSplit(line);
820
 
        return result;                                      // then nothing to do...
821
 
    }
822
 
 
823
 
    var lastChar = (document.charAt(line - 1, column) == '"');
824
 
    var prevLastColumnIsString = isString(line - 1, column - 1);
825
 
    var prevLastChar = (document.charAt(line - 1, column - 1) == '"');
826
 
    dbg("trySplitString_ch: lastColumnIsString="+lastColumnIsString);
827
 
    dbg("trySplitString_ch: lastChar="+lastChar);
828
 
    dbg("trySplitString_ch: prevLastColumnIsString="+prevLastColumnIsString);
829
 
    dbg("trySplitString_ch: prevLastChar="+prevLastChar);
830
 
    dbg("trySplitString_ch: isString(line,0)="+firstColumnIsString);
831
 
    dbg("trySplitString_ch: firstChar="+firstChar);
832
 
    var startOfString = firstColumnIsString && firstChar;
833
 
    var endOfString = !(firstColumnIsString || firstChar);
834
 
    var should_proceed = !lastChar && prevLastColumnIsString && (endOfString || !prevLastChar && startOfString)
835
 
      || lastChar && !prevLastColumnIsString && !prevLastChar && (endOfString || startOfString)
836
 
      ;
837
 
    dbg("trySplitString_ch: ------ should_proceed="+should_proceed);
838
 
    if (should_proceed)
839
 
    {
840
 
        // Add closing quote to the previous line
841
 
        document.insertText(line - 1, document.lineLength(line - 1), '"');
842
 
        // and open quote to the current one
843
 
        document.insertText(line, 0, '"');
844
 
        // NOTE If AutoBrace plugin is used, it won't add a quote
845
 
        // char, if cursor positioned right before another quote char
846
 
        // (which was moved from a line above in this case)...
847
 
        // So, lets force it!
848
 
        if (startOfString && document.charAt(line, 1) != '"')
849
 
        {
850
 
            document.insertText(line, 1, '"');              // Add one more!
851
 
            view.setCursorPosition(line, 1);                // Step back inside of string
852
 
        }
853
 
        result = getStringAligmentAfterSplit(line);
854
 
    }
855
 
    if (result != -1)
856
 
    {
857
 
        dbg("trySplitString_ch result="+result);
858
 
        tryToKeepInlineComment(line);
859
 
    }
860
 
    return result;
861
 
}
862
 
 
863
 
/**
864
 
 * Here is few cases possible:
865
 
 * \code
866
 
 *  // set some var to lambda function
867
 
 *  auto some = [foo](bar)|
868
 
 *
869
 
 *  // lambda as a parameter (possible the first one,
870
 
 *  // i.e. w/o a leading comma)
871
 
 *  std::foreach(
872
 
 *      begin(container)
873
 
 *    , end(container)
874
 
 *    , [](const value_type& v)|
875
 
 *    );
876
 
 * \endcode
877
 
 */
878
 
function tryAfterLambda_ch(line)
879
 
{
880
 
    var result = -1;
881
 
    var column = document.lastColumn(line - 1);
882
 
 
883
 
    if (isComment(line - 1, column))
884
 
        return result;                                      // Do nothing for comments
885
 
 
886
 
    var sr = splitByComment(line - 1);
887
 
    if (sr.before.match(/\[[^\]]*\]\([^{]*\)[^{}]*$/))
888
 
    {
889
 
        var align = document.firstColumn(line - 1);
890
 
        var before = sr.before.ltrim();
891
 
        if (before.startsWith(','))
892
 
            align += 2;
893
 
        var padding = String().fill(' ', align);
894
 
        var tail = before.startsWith('auto ') ? "};" : "}";
895
 
        document.insertText(
896
 
            line
897
 
          , 0
898
 
          , padding + "{\n" + padding + String().fill(' ', gIndentWidth) + "\n" + padding + tail
899
 
          );
900
 
        view.setCursorPosition(line + 1, align + gIndentWidth);
901
 
        result = -2;
902
 
    }
903
 
 
904
 
    if (result != -1)
905
 
    {
906
 
        dbg("tryAfterLambda_ch result="+result);
907
 
    }
908
 
    return result;
909
 
}
910
 
 
911
 
/// Wrap \c tryToKeepInlineComment as \e caret-handler
912
 
function tryToKeepInlineComment_ch(line)
913
 
{
914
 
    tryToKeepInlineComment(line);
915
 
    return -1;
916
 
}
917
 
 
918
 
/**
919
 
 * \brief Handle \c ENTER key
920
 
 */
921
 
function caretPressed(cursor)
922
 
{
923
 
    var result = -1;
924
 
    var line = cursor.line;
925
 
 
926
 
    // Dunno what to do if previous line isn't available
927
 
    if (line - 1 < 0)
928
 
        return result;                                      // Nothing (dunno) to do if no previous line...
929
 
 
930
 
    // Register all indent functions
931
 
    var handlers = [
932
 
        tryBraceSplit_ch                                    // Handle ENTER between braces
933
 
      , tryMultilineCommentStart_ch
934
 
      , tryMultilineCommentCont_ch
935
 
      , tryAfterCloseMultilineComment_ch
936
 
      , trySplitComment_ch
937
 
      , tryToAlignAfterOpenBrace_ch                         // Handle {,[,(,< on a previous line
938
 
      , tryToAlignBeforeCloseBrace_ch                       // Handle },],),> on a current line before cursor
939
 
      , tryToAlignBeforeComma_ch                            // Handle ENTER pressed before comma or semicolon
940
 
      , tryIndentAfterSomeKeywords_ch                       // NOTE It must follow after trySplitComment_ch!
941
 
      , tryAfterDanglingSemicolon_ch
942
 
      , tryMacroDefinition_ch
943
 
      , tryBeforeDanglingDelimiter_ch
944
 
      , tryBeforeAccessSpecifier_ch
945
 
      , tryAfterEqualChar_ch
946
 
      , tryPreprocessor_ch
947
 
      , tryAfterBlockComment_ch
948
 
      , tryAfterBreakContinue_ch
949
 
      , trySplitString_ch                                   // Handle ENTER pressed in the middle of a string
950
 
      , tryAfterLambda_ch                                   // Handle ENTER after lambda prototype and before body
951
 
      , tryToKeepInlineComment_ch                           // NOTE This must be a last checker!
952
 
    ];
953
 
 
954
 
    // Apply all all functions until result gets changed
955
 
    for (
956
 
        var i = 0
957
 
      ; i < handlers.length && result == -1
958
 
      ; result = handlers[i++](line)
959
 
      );
960
 
 
961
 
    return result;
962
 
}
963
 
 
964
 
/**
965
 
 * \brief Handle \c '/' key pressed
966
 
 *
967
 
 * Check if is it start of a comment. Here is few cases possible:
968
 
 * \li very first \c '/' -- do nothing
969
 
 * \li just entered \c '/' is a second in a sequence. If no text before or some present after,
970
 
 *     do nothing, otherwise align a \e same-line-comment to \c gSameLineCommentStartAt
971
 
 *     position.
972
 
 * \li just entered \c '/' is a 3rd in a sequence. If there is some text before and no after,
973
 
 *     it looks like inlined doxygen comment, so append \c '<' char after. Do nothing otherwise.
974
 
 * \li if there is a <tt>'// '</tt> string right before just entered \c '/', form a
975
 
 *     doxygen comment <tt>'///'</tt> or <tt>'///<'</tt> depending on presence of some text
976
 
 *     on a line before the comment.
977
 
 *
978
 
 * \todo Due the BUG #316809 in a current version of Kate, this code doesn't work as expected!
979
 
 * It always returns a <em>"NormalText"</em>!
980
 
 * \code
981
 
 * var cm = document.attributeName(cursor);
982
 
 * if (cm.indexOf("String") != -1)
983
 
 *    return;
984
 
 * \endcode
985
 
 *
986
 
 * \bug This code doesn't work properly in the following case:
987
 
 * \code
988
 
 *  std::string bug = "some text//
989
 
 * \endcode
990
 
 *
991
 
 */
992
 
function trySameLineComment(cursor)
993
 
{
994
 
    var line = cursor.line;
995
 
    var column = cursor.column;
996
 
 
997
 
    // First of all check that we are not withing a string
998
 
    if (document.isString(line, column))
999
 
        return;
1000
 
 
1001
 
    var sc = splitByComment(line);
1002
 
    if (sc.hasComment)                                      // Is there any comment on a line?
1003
 
    {
1004
 
        // Make sure we r not in a comment already -- it can be a multiline one...
1005
 
        var fc = document.firstColumn(line);
1006
 
        var text = document.line(line).ltrim();
1007
 
        var nothing_to_do = (fc < (column - 1)) && document.isComment(line, fc);
1008
 
        // Also check that line has smth that needs to be "fixed"...
1009
 
        if (nothing_to_do && text != "//" && text != "// /" && text != "///" && text != "/// /")
1010
 
            return;
1011
 
        // If no text after the comment and it still not aligned
1012
 
        var text_len = sc.before.rtrim().length;
1013
 
        if (text_len != 0 && sc.after.length == 0 && text_len < gSameLineCommentStartAt)
1014
 
        {
1015
 
            // Align it!
1016
 
            document.insertText(
1017
 
                line
1018
 
              , column - 2
1019
 
              , String().fill(' ', gSameLineCommentStartAt - sc.before.length)
1020
 
              );
1021
 
            document.insertText(line, gSameLineCommentStartAt + 2, ' ');
1022
 
        }
1023
 
        // If text in a comment equals to '/' or ' /' -- it looks like a 3rd '/' pressed
1024
 
        else if (sc.after == " /" || sc.after == "/")
1025
 
        {
1026
 
            // Form a Doxygen comment!
1027
 
            document.removeText(line, column - sc.after.length, line, column);
1028
 
            document.insertText(line, column - sc.after.length, text_len != 0 ? "/< " : "/ ");
1029
 
        }
1030
 
        // If right trimmed text in a comment equals to '/' -- it seems user moves cursor
1031
 
        // one char left (through space) to add one more '/'
1032
 
        else if (sc.after.rtrim() == "/")
1033
 
        {
1034
 
            // Form a Doxygen comment!
1035
 
            document.removeText(line, column, line, column + sc.after.length);
1036
 
            document.insertText(line, column, text_len != 0 ? "< " : " ");
1037
 
        }
1038
 
        else if (sc.after == "/ /")
1039
 
        {
1040
 
            // Looks like user wants to "draw a fence" w/ '/'s
1041
 
            document.removeText(line, column - 2, line, column - 1);
1042
 
        }
1043
 
        else if (text_len == 0 && sc.after.length == 0)
1044
 
        {
1045
 
            document.insertText(line, column, ' ');
1046
 
        }
1047
 
    }
1048
 
}
1049
 
 
1050
 
/**
1051
 
 * \brief Maybe '>' needs to be added?
1052
 
 *
1053
 
 * Here is a few cases possible:
1054
 
 * \li user entered <em>"template &gt;</em>
1055
 
 * \li user entered smth like <em>std::map&gt;</em>
1056
 
 * \li user wants to output smth to C++ I/O stream by typing <em>&gt;&gt;</em>
1057
 
 *     possible at the line start, so it must be half indented
1058
 
 * \li shortcut: <tt>some(<|)</tt> transformed into <tt>some()<|</tt>
1059
 
 *
1060
 
 * But, do not add '>' if there some text after cursor.
1061
 
 */
1062
 
function tryTemplate(cursor)
1063
 
{
1064
 
    var line = cursor.line;
1065
 
    var column = cursor.column;
1066
 
    var result = -2;
1067
 
 
1068
 
    if (isStringOrComment(line, column))
1069
 
        return result;                                      // Do nothing for comments and strings
1070
 
 
1071
 
    // Check for 'template' keyword at line start
1072
 
    var currentString = document.line(line);
1073
 
    var prevWord = document.wordAt(line, column - 1);
1074
 
    dbg("tryTemplate: prevWord='"+prevWord+"'");
1075
 
    dbg("tryTemplate: prevWord.match="+prevWord.match(/\b[A-Za-z_][A-Za-z0-9_]*/));
1076
 
    // Add a closing angle bracket if a prev word is not a 'operator'
1077
 
    // and it looks like an identifier or current line starts w/ 'template' keyword
1078
 
    var isCloseAngleBracketNeeded = (prevWord != "operator")
1079
 
      && (currentString.match(/^\s*template\s*<$/) || prevWord.match(/\b[A-Za-z_][A-Za-z0-9_]*/))
1080
 
      && (column == document.lineLength(line) || document.charAt(cursor).match(/\W/))
1081
 
      ;
1082
 
    if (isCloseAngleBracketNeeded)
1083
 
    {
1084
 
        document.insertText(cursor, ">");
1085
 
        view.setCursorPosition(cursor);
1086
 
    }
1087
 
    else if (justEnteredCharIsFirstOnLine(line, column, '<'))
1088
 
    {
1089
 
        result = tryIndentRelativePrevNonCommentLine(line);
1090
 
    }
1091
 
    // Add a space after 2nd '<' if a word before is not a 'operator'
1092
 
    else if (document.charAt(line, column - 2) == '<')
1093
 
    {
1094
 
        if (document.wordAt(line, column - 3) != "operator")
1095
 
        {
1096
 
            // Looks like case 3...
1097
 
            // 0) try to remove '>' if user typed 'some<' before
1098
 
            // (and closing '>' was added by tryTemplate)
1099
 
            if (column < document.lineLength(line) && document.charAt(line, column) == '>')
1100
 
            {
1101
 
                document.removeText(line, column, line, column + 1);
1102
 
                addCharOrJumpOverIt(line, column - 2, ' ');
1103
 
                view.setCursorPosition(line, column + 1);
1104
 
                column = column + 1;
1105
 
            }
1106
 
            // add a space after operator<<
1107
 
            document.insertText(line, column, " ");
1108
 
        }
1109
 
        else
1110
 
        {
1111
 
            document.insertText(line, column, "()");
1112
 
            view.setCursorPosition(line, column + 1);
1113
 
        }
1114
 
    }
1115
 
    else
1116
 
    {
1117
 
        cursor = tryJumpOverParenthesis(cursor);            // Try to jump out of parenthesis
1118
 
        tryAddSpaceAfterClosedBracketOrQuote(cursor);
1119
 
    }
1120
 
    return result;
1121
 
}
1122
 
 
1123
 
/**
1124
 
 * This function called for some characters and trying to do the following:
1125
 
 * if the cursor (right after a trigger character is entered) is positioned withing
1126
 
 * a parenthesis, move the entered character out of parenthesis.
1127
 
 *
1128
 
 * For example:
1129
 
 * \code
1130
 
 *  auto a = two_params_func(get_first(,|))
1131
 
 *  // ... transformed into
1132
 
 *  auto a = two_params_func(get_first(),|)
1133
 
 * \endcode
1134
 
 *
1135
 
 * because entering comma right after <tt>(</tt> definitely incorrect, but
1136
 
 * we can help the user (programmer) to avoid 3 key presses ;-)
1137
 
 * (RightArrow, ',', space)
1138
 
 *
1139
 
 * except comma here are other "impossible" characters:
1140
 
 * \c ., \c ?, \c :, \c %, \c |, \c /, \c =, \c <, \c >, \c ], \c }
1141
 
 *
1142
 
 * But \c ; will be handled separately to be able to jump over all closing \c ).
1143
 
 *
1144
 
 * \sa \c trySemicolon()
1145
 
 *
1146
 
 * \note This valid if we r not inside a comment or a string literal,
1147
 
 * and the char out of the parenthesis is not the same as just entered ;-)
1148
 
 *
1149
 
 * \param cursor initial cursor position
1150
 
 * \param es edit session instance
1151
 
 * \return new (possible modified) cursor position
1152
 
 */
1153
 
function tryJumpOverParenthesis(cursor)
1154
 
{
1155
 
    var line = cursor.line;
1156
 
    var column = cursor.column;
1157
 
 
1158
 
    if (2 < column && isStringOrComment(line, column))
1159
 
        return cursor;                                      // Do nothing for comments of string literals
1160
 
 
1161
 
    // Check that we r inside of parenthesis and some symbol between
1162
 
    var pc = document.charAt(line, column - 2);
1163
 
    var cc = document.charAt(cursor);
1164
 
    if ((pc == '(' && cc == ')') || (pc == '{' && cc == '}'))
1165
 
    {
1166
 
        var c = document.charAt(line, column - 1);
1167
 
        switch (c)
1168
 
        {
1169
 
            case '.':
1170
 
                if (pc == '(' && cc == ')')
1171
 
                {
1172
 
                    // Make sure this is not a `catch (...)`
1173
 
                    if (document.startsWith(line, "catch (.)", true))
1174
 
                    {
1175
 
                        document.insertText(line, column, "..");
1176
 
                        view.setCursorPosition(line, column + 3);
1177
 
                        break;
1178
 
                    }
1179
 
                }
1180
 
            case ',':
1181
 
            case '?':
1182
 
            case ':':
1183
 
            case '%':
1184
 
            case '^':
1185
 
            case '|':
1186
 
            case '/':                                       // TODO ORLY?
1187
 
            case '=':
1188
 
            case '<':
1189
 
            case '>':
1190
 
            case '}':
1191
 
            case ')':
1192
 
            case ']':                                       // NOTE '[' could be a part of lambda
1193
 
            {
1194
 
                // Ok, move character out of parenthesis
1195
 
                document.removeText(line, column - 1, line, column);
1196
 
                // Check is a character after the closing brace the same as just entered one
1197
 
                addCharOrJumpOverIt(line, column, c);
1198
 
                return view.cursorPosition();
1199
 
            }
1200
 
            default:
1201
 
                break;
1202
 
        }
1203
 
    }
1204
 
    return cursor;
1205
 
}
1206
 
 
1207
 
/**
1208
 
 * Handle the case when some character was entered after a some closing bracket.
1209
 
 * Here is few close brackets possible:
1210
 
 * \li \c ) -- ordinal function call
1211
 
 * \li \c } -- C++11 constructor call
1212
 
 * \li \c ] -- array access
1213
 
 * \li \c " -- end of a string literal
1214
 
 * \li \c ' -- and of a char literal
1215
 
 *
1216
 
 * This function try to add a space between a closing quote/bracket and operator char.
1217
 
 *
1218
 
 * \note This valid if we r not inside a comment or a string literal.
1219
 
 */
1220
 
function tryAddSpaceAfterClosedBracketOrQuote(cursor)
1221
 
{
1222
 
    var line = cursor.line;
1223
 
    var column = cursor.column;
1224
 
 
1225
 
    if (isStringOrComment(line, column - 1))
1226
 
        return cursor;                                      // Do nothing for comments of string literals
1227
 
 
1228
 
    // Check if we have a closing bracket before a last entered char
1229
 
    var b = document.charAt(line, column - 2);
1230
 
    if (!(b == ']' || b == '}' || b == ')' || b == '"' || b == "'"))
1231
 
        return cursor;
1232
 
 
1233
 
    // Ok, lets check what we've got as a last char
1234
 
    var c = document.charAt(line, column - 1);
1235
 
    dbg("tryAddSpaceAfterClosedBracketOrQuote: c='"+c+"', @"+new Cursor(line, column-1));
1236
 
    switch (c)
1237
 
    {
1238
 
        case '*':
1239
 
        case '/':
1240
 
        case '%':
1241
 
        case '&':
1242
 
        case '|':
1243
 
        case '=':
1244
 
        case '^':
1245
 
        case '?':
1246
 
        case ':':
1247
 
        case '<':
1248
 
            document.insertText(line, column - 1, " ");
1249
 
            view.setCursorPosition(line, column + 1);
1250
 
            return view.cursorPosition();
1251
 
        case '>':
1252
 
            // Close angle bracket may be a part of template instantiation
1253
 
            // w/ some function type parameter... Otherwise, add a space after.
1254
 
            if (b != ')')
1255
 
            {
1256
 
                document.insertText(line, column - 1, " ");
1257
 
                view.setCursorPosition(line, column + 1);
1258
 
                return view.cursorPosition();
1259
 
            }
1260
 
            break;
1261
 
        default:
1262
 
            break;
1263
 
    }
1264
 
    return cursor;
1265
 
}
1266
 
 
1267
 
/**
1268
 
 * \brief Try to align parameters list
1269
 
 *
1270
 
 * If (just entered) comma is a first symbol on a line,
1271
 
 * just move it on a half-tab left relative to a previous line
1272
 
 * (if latter doesn't starts w/ comma or ':').
1273
 
 * Do nothing otherwise. A space would be added after it anyway.
1274
 
 */
1275
 
function tryComma(cursor)
1276
 
{
1277
 
    var result = -2;
1278
 
    var line = cursor.line;
1279
 
    var column = cursor.column;
1280
 
    // Check is comma a very first character on a line...
1281
 
    if (justEnteredCharIsFirstOnLine(line, column, ','))
1282
 
    {
1283
 
        result = tryIndentRelativePrevNonCommentLine(line);
1284
 
    }
1285
 
    else
1286
 
    {
1287
 
        // Try to stick a comma to a previous non-space char
1288
 
        var lastWordPos = document.text(line, 0, line, column - 1).rtrim().length;
1289
 
        if (lastWordPos < column)
1290
 
        {
1291
 
            document.removeText(line, column - 1, line, column);
1292
 
            document.insertText(line, lastWordPos, ",");
1293
 
            cursor = new Cursor(line, lastWordPos + 1);
1294
 
            view.setCursorPosition(cursor);
1295
 
        }
1296
 
    }
1297
 
 
1298
 
    cursor = tryJumpOverParenthesis(cursor);                // Try to jump out of parenthesis
1299
 
    addCharOrJumpOverIt(cursor.line, cursor.column, ' ');
1300
 
    return result;
1301
 
}
1302
 
 
1303
 
/**
1304
 
 * \brief Move towards a document start and look for control flow keywords
1305
 
 *
1306
 
 * \note Keyword must be at the line start
1307
 
 *
1308
 
 * \return found line's indent, otherwise \c -2 if nothing found
1309
 
 */
1310
 
function tryBreakContinue(line, is_break)
1311
 
{
1312
 
    var result = -2;
1313
 
    // Ok, look backward and find a loop/switch statement
1314
 
    for (; 0 <= line; --line)
1315
 
    {
1316
 
        var text = document.line(line).ltrim();
1317
 
        var is_loop_or_switch = text.startsWith("for ")
1318
 
          || text.startsWith("do ")
1319
 
          || text.startsWith("while ")
1320
 
          || text.startsWith("if ")
1321
 
          || text.startsWith("else if ")
1322
 
          ;
1323
 
        if (is_break)
1324
 
            is_loop_or_switch =  is_loop_or_switch
1325
 
              || text.startsWith("case ")
1326
 
              || text.startsWith("default:")
1327
 
              ;
1328
 
        if (is_loop_or_switch)
1329
 
            break;
1330
 
    }
1331
 
    if (line != -1)                                     // Found smth?
1332
 
        result = document.firstColumn(line) + gIndentWidth;
1333
 
 
1334
 
    return result;
1335
 
}
1336
 
 
1337
 
/**
1338
 
 * \brief Handle \c ; character.
1339
 
 *
1340
 
 * Here is few cases possible (handled):
1341
 
 * \li semicolon is a first char on a line -- then, it looks like \c for statement
1342
 
 *      splitted accross the lines
1343
 
 * \li semicolon entered after some keywords: \c break or \c continue, then we
1344
 
 *      need to align this line taking in account a previous one
1345
 
 * \li and finally here is a trick: when auto brackets extension enabled, and user types
1346
 
 *      a function call like this:
1347
 
 * \code
1348
 
 *  auto var = some_call(arg1, arg2|)
1349
 
 * \endcode
1350
 
 * (\c '|' shows a cursor position). Note there is no final semicolon in this expression,
1351
 
 * cuz pressing <tt>'('</tt> leads to the following snippet: <tt>some_call(|)</tt>, so to
1352
 
 * add a semicolon you have to move cursor out of parenthesis. The trick is to allow to press
1353
 
 * <tt>';'</tt> at position shown in the code snippet, so indenter will transform it into this:
1354
 
 * \code
1355
 
 *  auto var = some_call(arg1, arg2);|
1356
 
 * \endcode
1357
 
 * same works even there is no arguments...
1358
 
 *
1359
 
 * All the time, when simicolon is not a first non-space symbol (and not a part of a comment
1360
 
 * or string) it will be stiked to the last non-space character on the line.
1361
 
 */
1362
 
function trySemicolon(cursor)
1363
 
{
1364
 
    var result = -2;
1365
 
    var line = cursor.line;
1366
 
    var column = cursor.column;
1367
 
 
1368
 
    if (isStringOrComment(line, column))
1369
 
        return result;                                      // Do nothing for comments and strings
1370
 
 
1371
 
    // If ';' is a first char on a line?
1372
 
    if (justEnteredCharIsFirstOnLine(line, column, ';'))
1373
 
    {
1374
 
        // Check if we are inside a `for' statement
1375
 
        var openBracePos = document.anchor(line, column, '(');
1376
 
        if (openBracePos.isValid())
1377
 
        {
1378
 
            // Add a half-tab relative '('
1379
 
            result = document.firstColumn(openBracePos.line) + 2;
1380
 
            document.insertText(cursor, " ");
1381
 
        }
1382
 
    }
1383
 
    else
1384
 
    {
1385
 
        // Stick ';' to the last "word"
1386
 
        var lcsc = document.prevNonSpaceColumn(line, column - 2);
1387
 
        if (2 < column && lcsc < (column - 2))
1388
 
        {
1389
 
            document.removeText(line, column - 1, line, column);
1390
 
            if (document.charAt(line, lcsc) != ';')
1391
 
            {
1392
 
                document.insertText(line, lcsc + 1, ";");
1393
 
                view.setCursorPosition(line, lcsc + 2);
1394
 
            }
1395
 
            else view.setCursorPosition(line, lcsc + 1);
1396
 
            cursor = view.cursorPosition();
1397
 
            column = cursor.column;
1398
 
        }
1399
 
        var text = document.line(line).ltrim();
1400
 
        var is_break = text.startsWith("break;");
1401
 
        var should_proceed = is_break || text.startsWith("continue;")
1402
 
        if (should_proceed)
1403
 
        {
1404
 
            result = tryBreakContinue(line - 1, is_break);
1405
 
            if (result == -2)
1406
 
                result = -1;
1407
 
        }
1408
 
        // Make sure we r not inside of `for' statement
1409
 
        /// \todo Make sure cursor is really inside of \c for and
1410
 
        /// not smth like this: <tt>for (blah; blah; blah) some(arg,;|)</tt>
1411
 
        else if (!text.startsWith("for "))
1412
 
        {
1413
 
            // Check if next character(s) is ')' and nothing after
1414
 
            should_proceed = true;
1415
 
            var lineLength = document.lineLength(line);
1416
 
            for (var i = column; i < lineLength; ++i)
1417
 
            {
1418
 
                var c = document.charAt(line, i);
1419
 
                if (!(c == ')' || c == ']'))
1420
 
                {
1421
 
                    should_proceed = false;
1422
 
                    break;
1423
 
                }
1424
 
            }
1425
 
            // Ok, lets move ';' out of "a(b(c(;)))" of any level...
1426
 
            if (should_proceed)
1427
 
            {
1428
 
                // Remove ';' from column - 1
1429
 
                document.removeText(line, column - 1, line, column);
1430
 
                // Append ';' to the end of line
1431
 
                document.insertText(line, lineLength - 1, ";");
1432
 
                view.setCursorPosition(line, lineLength);
1433
 
                cursor = view.cursorPosition();
1434
 
                column = cursor.column;
1435
 
            }
1436
 
        }
1437
 
        // In C++ there is no need to have more than one semicolon.
1438
 
        // So remove a redundant one!
1439
 
        if (document.charAt(line, column - 2) == ';')
1440
 
        {
1441
 
            // Remove just entered ';'
1442
 
            document.removeText(line, column - 1, line, column);
1443
 
        }
1444
 
    }
1445
 
    return result;
1446
 
}
1447
 
 
1448
 
/**
1449
 
 * Handle possible dangling operators (moved from a previous line)
1450
 
 *
1451
 
 * \c ?, \c |, \c ^, \c %, \c .
1452
 
 *
1453
 
 * Add spaces around ternary operator.
1454
 
 */
1455
 
function tryOperator(cursor, ch)
1456
 
{
1457
 
    var result = -2;
1458
 
    var line = cursor.line;
1459
 
    var column = cursor.column;
1460
 
 
1461
 
    if (isStringOrComment(line, column))
1462
 
        return result;                                      // Do nothing for comments and strings
1463
 
 
1464
 
    var halfTabNeeded = justEnteredCharIsFirstOnLine(line, column, ch)
1465
 
      && document.line(line - 1).search(/^\s*[A-Za-z_][A-Za-z0-9_]*/) != -1
1466
 
      ;
1467
 
    dbg("tryOperator: halfTabNeeded =", halfTabNeeded);
1468
 
    if (halfTabNeeded)
1469
 
    {
1470
 
        // check if we r at function call or array index
1471
 
        var insideBraces = document.anchor(line, document.firstColumn(line), '(').isValid()
1472
 
          || document.anchor(line, document.firstColumn(line), '[').isValid()
1473
 
          || document.anchor(line, document.firstColumn(line), '{').isValid()
1474
 
          ;
1475
 
        dbg("tryOperator: insideBraces =",insideBraces);
1476
 
        result = document.firstColumn(line - 1) + (insideBraces && ch != '.' ? -2 : 2);
1477
 
    }
1478
 
    var prev_pos = cursor;
1479
 
    cursor = tryJumpOverParenthesis(cursor);                // Try to jump out of parenthesis
1480
 
    cursor = tryAddSpaceAfterClosedBracketOrQuote(cursor);
1481
 
 
1482
 
    // Check if a space before '?' still needed
1483
 
    if (prev_pos == cursor && ch == '?' && document.charAt(line, cursor.column - 1) != ' ')
1484
 
        document.insertText(line, cursor.column - 1, " ");  // Add it!
1485
 
 
1486
 
    cursor = view.cursorPosition();                         // Update cursor position
1487
 
    line = cursor.line;
1488
 
    column = cursor.column;
1489
 
 
1490
 
    if (ch == '?')
1491
 
    {
1492
 
        addCharOrJumpOverIt(line, column, ' ');
1493
 
    }
1494
 
    // Handle operator| and/or operator||
1495
 
    else if (ch == '|')
1496
 
    {
1497
 
        /**
1498
 
         * Here is 6+3 cases possible (the last bar is just entered):
1499
 
         * 0) <tt>???</tt> -- add a space before bar and after if needed
1500
 
         * 1) <tt>?? </tt> -- add a space after if needed
1501
 
         * 2) <tt>??|</tt> -- add a space before 1st bar and after the 2nd if needed
1502
 
         * 3) <tt>? |</tt> -- add a space after the 2nd bar if needed
1503
 
         * 4) <tt>?| </tt> -- add a space before 1st bar, remove the mid one, add a space after 2nd bar
1504
 
         * 5) <tt> | </tt> -- remove the mid space, add one after 2nd bar
1505
 
         * and finally,
1506
 
         * 6a) <tt>|| </tt> -- add a space before 1st bar if needed, remove the last bar
1507
 
         * 6b) <tt> ||</tt> -- remove the last bar and add a space after 2nd bar if needed
1508
 
         * 6c) <tt>||</tt> -- add a space after if needed
1509
 
         */
1510
 
        var prev = document.text(line, column - 4, line, column - 1);
1511
 
        dbg("tryOperator: checking @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'");
1512
 
        var space_offset = 0;
1513
 
        if (prev.endsWith(" | "))
1514
 
        {
1515
 
            // case 5: remove the mid space
1516
 
            document.removeText(line, column - 2, line, column - 1);
1517
 
            space_offset = -1;
1518
 
        }
1519
 
        else if (prev.endsWith("|| "))
1520
 
        {
1521
 
            // case 6a: add a space before 1st bar if needed, remove the last bar
1522
 
            document.removeText(line, column - 1, line, column);
1523
 
            var space_has_added = addCharOrJumpOverIt(line, column - 4, ' ');
1524
 
            space_offset = (space_has_added ? 1 : 0) - 2;
1525
 
        }
1526
 
        else if (prev.endsWith(" ||"))
1527
 
        {
1528
 
            // case 6b: remove the last bar
1529
 
            document.removeText(line, column - 1, line, column);
1530
 
            space_offset = -1;
1531
 
        }
1532
 
        else if (prev.endsWith("||"))
1533
 
        {
1534
 
            // case 6a: add a space before and remove the last bar
1535
 
            document.removeText(line, column - 1, line, column);
1536
 
            document.insertText(line, column - 3, " ");
1537
 
        }
1538
 
        else if (prev.endsWith("| "))
1539
 
        {
1540
 
            // case 4: add a space before 1st bar, remove the mid one
1541
 
            document.removeText(line, column - 2, line, column - 1);
1542
 
            document.insertText(line, column - 3, " ");
1543
 
        }
1544
 
        else if (prev.endsWith(" |") || prev.endsWith(" "))
1545
 
        {
1546
 
            // case 3 and 1
1547
 
        }
1548
 
        else
1549
 
        {
1550
 
            // case 2: add a space before 1st bar
1551
 
            if (prev.endsWith('|'))
1552
 
                space_offset = 1;
1553
 
            // case 0: add a space before bar
1554
 
            document.insertText(line, column - 1 - space_offset, " ");
1555
 
            space_offset = 1;
1556
 
        }
1557
 
        addCharOrJumpOverIt(line, column + space_offset, ' ');
1558
 
    }
1559
 
    // Handle operator% and/or operator^
1560
 
    else if (ch == '%' || ch == '^')
1561
 
    {
1562
 
        var prev = document.text(line, column - 4, line, column - 1);
1563
 
        dbg("tryOperator: checking2 @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'");
1564
 
        var patterns = [" % ", "% ", " %", "%", " "];
1565
 
        for (
1566
 
            var i = 0
1567
 
          ; i < patterns.length
1568
 
          ; i++
1569
 
          ) patterns[i] = patterns[i].replace('%', ch);
1570
 
 
1571
 
        var space_offset = 0;
1572
 
        if (prev.endsWith(patterns[0]))
1573
 
        {
1574
 
            // case 0: remove just entered char
1575
 
            document.removeText(line, column - 1, line, column);
1576
 
            space_offset = -2;
1577
 
        }
1578
 
        else if (prev.endsWith(patterns[1]))
1579
 
        {
1580
 
            // case 1: remove just entered char, add a space before
1581
 
            document.removeText(line, column - 1, line, column);
1582
 
            document.insertText(line, column - 3, " ");
1583
 
            space_offset = -1;
1584
 
        }
1585
 
        else if (prev.endsWith(patterns[2]))
1586
 
        {
1587
 
            // case 2: remove just entered char
1588
 
            document.removeText(line, column - 1, line, column);
1589
 
            space_offset = -1;
1590
 
        }
1591
 
        else if (prev.endsWith(patterns[3]))
1592
 
        {
1593
 
            // case 3: add a space before
1594
 
            document.removeText(line, column - 1, line, column);
1595
 
            document.insertText(line, column - 2, " ");
1596
 
            space_offset = 0;
1597
 
        }
1598
 
        else if (prev.endsWith(patterns[4]))
1599
 
        {
1600
 
            // case 4: no space needed before
1601
 
            space_offset = 0;
1602
 
        }
1603
 
        else
1604
 
        {
1605
 
            // case everything else: surround operator w/ spaces
1606
 
            document.insertText(line, column - 1, " ");
1607
 
            space_offset = 1;
1608
 
        }
1609
 
        addCharOrJumpOverIt(line, column + space_offset, ' ');
1610
 
    }
1611
 
    else if (ch == '.')                                     // Replace '..' w/ '...'
1612
 
    {
1613
 
        var prev = document.text(line, column - 3, line, column);
1614
 
        dbg("tryOperator: checking3 @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'");
1615
 
        if (prev == "...")                                  // If there is already 3 dots
1616
 
        {
1617
 
            // Remove just entered (redundant) one
1618
 
            document.removeText(line, column - 1, line, column);
1619
 
        }
1620
 
        // Append one more if only two here and we are in 'Normal Mode'
1621
 
        else if (prev[1] == '.' && prev[2] == '.' && document.defStyleNum(line, column) == 0)
1622
 
        {
1623
 
            addCharOrJumpOverIt(line, column, '.');
1624
 
        }                                                   // Otherwise, do nothing...
1625
 
    }
1626
 
    if (result != -2)
1627
 
    {
1628
 
        dbg("tryOperator result="+result);
1629
 
    }
1630
 
    return result;
1631
 
}
1632
 
 
1633
 
/**
1634
 
 * \brief Try to align a given close bracket
1635
 
 */
1636
 
function tryCloseBracket(cursor, ch)
1637
 
{
1638
 
    var result = -2;
1639
 
    var line = cursor.line;
1640
 
    var column = cursor.column;
1641
 
 
1642
 
    var braceCursor = Cursor.invalid();
1643
 
    if (ch != '>')
1644
 
    {
1645
 
        // TODO Make sure a given `ch` in the gBraceMap
1646
 
        braceCursor = document.anchor(line, column - 1, gBraceMap[ch]);
1647
 
        // TODO Otherwise, it seems we have a template parameters list...
1648
 
    }
1649
 
 
1650
 
    // Check if a given closing brace is a first char on a line
1651
 
    // (i.e. it is 'dangling' brace)...
1652
 
    if (justEnteredCharIsFirstOnLine(line, column, ch) && braceCursor.isValid())
1653
 
    {
1654
 
        // Move to one half-TAB right, if anything but not closing '}', else
1655
 
        // align to the corresponding open char
1656
 
        result = document.firstColumn(braceCursor.line) + (ch != '}' ? 2 : 0);
1657
 
        dbg("tryCloseBracket: setting result="+result);
1658
 
    }
1659
 
 
1660
 
    // Check if ';' required after closing '}'
1661
 
    if (ch == '}' && braceCursor.isValid())
1662
 
    {
1663
 
        var is_check_needed = false;
1664
 
        // Check if corresponding anchor is a class/struct/union/enum,
1665
 
        // (possible keyword located on same or prev line)
1666
 
        // and check for trailing ';'...
1667
 
        var anchoredString = document.line(braceCursor.line);
1668
 
        dbg("tryCloseBracket: anchoredString='"+anchoredString+"'");
1669
 
        var regex = /^(\s*)(class|struct|union|enum).*$/;
1670
 
        var r = regex.exec(anchoredString);
1671
 
        if (r != null)
1672
 
        {
1673
 
            dbg("tryCloseBracket: same line");
1674
 
            is_check_needed = true;
1675
 
        }
1676
 
        else (!is_check_needed && 0 < braceCursor.line)     // Is there any line before?
1677
 
        {
1678
 
            dbg("tryCloseBracket: cheking prev line");
1679
 
 
1680
 
            // Ok, lets check it!
1681
 
            anchoredString = document.line(braceCursor.line - 1);
1682
 
            dbg("tryCloseBracket: anchoredString-1='"+anchoredString+"'");
1683
 
            r = regex.exec(anchoredString);
1684
 
            if (r != null)
1685
 
            {
1686
 
                is_check_needed = true;
1687
 
                dbg("tryCloseBracket: prev line");
1688
 
            }
1689
 
        }
1690
 
        dbg("tryCloseBracket: is_check_needed="+is_check_needed);
1691
 
        if (is_check_needed)
1692
 
        {
1693
 
            var is_ok = document.line(line)
1694
 
              .substring(column, document.lineLength(line))
1695
 
              .ltrim()
1696
 
              .startsWith(';')
1697
 
              ;
1698
 
            if (!is_ok)
1699
 
            {
1700
 
                document.insertText(line, column, ';');
1701
 
                view.setCursorPosition(line, column + 1);
1702
 
            }
1703
 
        }
1704
 
    }
1705
 
    else if (ch == '>')
1706
 
    {
1707
 
        // If user typed 'some' + '<' + '>', jump over the '>'
1708
 
        // (which was added by the tryTemplate)
1709
 
        if (document.charAt(line, column) == '>')
1710
 
        {
1711
 
            document.removeText(line, column, line, column + 1);
1712
 
        }
1713
 
    }
1714
 
 
1715
 
    tryJumpOverParenthesis(view.cursorPosition());
1716
 
 
1717
 
    return result;
1718
 
}
1719
 
 
1720
 
/**
1721
 
 * \brief Indent a new scope block
1722
 
 *
1723
 
 * ... try to unindent to be precise... First of all check that open
1724
 
 * \c '{' is a first symbol on a line, and if it doesn't,
1725
 
 * add space (if absent at previous position) after <tt>')'</tt> or \c '='
1726
 
 * or if line stats w/ some keywords: \c enum, \c class, \c struct or \c union.
1727
 
 * Otherwise, look at the previous line for dangling <tt>')'</tt> or
1728
 
 * a line started w/ one of flow control keywords.
1729
 
 *
1730
 
 */
1731
 
function tryBlock(cursor)
1732
 
{
1733
 
    var result = -2;
1734
 
    var line = cursor.line;
1735
 
    var column = cursor.column;
1736
 
 
1737
 
    // Make sure we r not in a comment or string
1738
 
    dbg("tryBlock: isStringOrComment(line, column - 2)="+isStringOrComment(line, column - 2))
1739
 
    if (isStringOrComment(line, column - 2))
1740
 
        return result;
1741
 
 
1742
 
    if (justEnteredCharIsFirstOnLine(line, column, '{'))
1743
 
    {
1744
 
        // Check for a dangling close brace on a previous line
1745
 
        // (this may mean that `for' or `if' or `while' w/ looong parameters list on it)
1746
 
        if (document.firstChar(line - 1) == ')')
1747
 
            result = Math.floor(document.firstColumn(line - 1) / gIndentWidth) * gIndentWidth;
1748
 
        else
1749
 
        {
1750
 
            // Otherwise, check for a keyword on the previous line and
1751
 
            // indent the started block to it...
1752
 
            var prevString = document.line(line - 1);
1753
 
            var r = /^(\s*)((catch|if|for|while)\s*\(|do|else|try|(default|case\s+.*)\s*:).*$/.exec(prevString);
1754
 
            if (r != null)
1755
 
                result = r[1].length;
1756
 
        }
1757
 
    }
1758
 
    else
1759
 
    {
1760
 
        // '{' is not a first char. Check for a previous one...
1761
 
        if (1 < column)
1762
 
        {
1763
 
            var prevChar = document.charAt(line, column - 2);
1764
 
            dbg("tryBlock: prevChar='"+prevChar+"'");
1765
 
            if (prevChar == ')' || prevChar == '=')
1766
 
                document.insertText(line, column - 1, ' ');
1767
 
            else if (prevChar != ' ')
1768
 
            {
1769
 
                var currentLine = document.line(line).ltrim();
1770
 
                var starts_with_keyword = currentLine.startsWith('struct ')
1771
 
                  || currentLine.startsWith('class ')
1772
 
                  || currentLine.startsWith('union ')
1773
 
                  || currentLine.startsWith('enum ')
1774
 
                  ;
1775
 
                if (starts_with_keyword)
1776
 
                    document.insertText(line, column - 1, ' ');
1777
 
            }
1778
 
        }
1779
 
    }
1780
 
    return result;
1781
 
}
1782
 
 
1783
 
/**
1784
 
 * \brief Align preprocessor directives
1785
 
 */
1786
 
function tryPreprocessor(cursor)
1787
 
{
1788
 
    var result = -2;
1789
 
    var line = cursor.line;
1790
 
    var column = cursor.column;
1791
 
 
1792
 
    // Check if just entered '#' is a first on a line
1793
 
    if (justEnteredCharIsFirstOnLine(line, column, '#'))
1794
 
    {
1795
 
        // Get current indentation level
1796
 
        var currentLevel = getPreprocessorLevelAt(line);
1797
 
        if (currentLevel > 0)
1798
 
        {
1799
 
            var padding = String().fill(' ', (currentLevel - 1) * 2 + 1);
1800
 
            document.insertText(cursor, padding);
1801
 
        }
1802
 
        result = 0;
1803
 
    }
1804
 
    return result;
1805
 
}
1806
 
 
1807
 
/**
1808
 
 * \brief Try to align access modifiers or class initialization list
1809
 
 *
1810
 
 * Here is few cases possible:
1811
 
 * \li \c ':' pressed after a keyword \c public, \c protected or \c private.
1812
 
 *     Then align a current line to corresponding class/struct definition.
1813
 
 *     Check a previous line and if it is not starts w/ \c '{' add a new line before.
1814
 
 * \li \c ':' is a first char on the line, then it looks like a class initialization
1815
 
 *     list or 2nd line of ternary operator.
1816
 
 * \li \c ':' is pressed on a line started w/ \c for statement and after a space
1817
 
 * \li \c ':' after '&gt;' looks like an access to template's member
1818
 
 * \li shortcut: transform <tt>some(:|)</tt> into <tt>some() :|</tt>
1819
 
 *
1820
 
 * \todo Should it be done only for non strings and comments?
1821
 
 */
1822
 
function tryColon(cursor)
1823
 
{
1824
 
    var result = -2;
1825
 
    var line = cursor.line;
1826
 
    var column = cursor.column;
1827
 
 
1828
 
    if (isStringOrComment(line, column))
1829
 
        return result;                                      // Do nothing for comments and strings
1830
 
 
1831
 
    // Check if just entered ':' is a first on a line
1832
 
    if (justEnteredCharIsFirstOnLine(line, column, ':'))
1833
 
    {
1834
 
        // Check if there a dangling ')' or '?' (ternary operator) on a previous line
1835
 
        var ch = document.firstChar(line - 1);
1836
 
        if (ch == ')' || ch == '?')
1837
 
            result = document.firstVirtualColumn(line - 1);
1838
 
        else
1839
 
            result = document.firstVirtualColumn(line - 1) + 2;
1840
 
        document.insertText(cursor, " ");
1841
 
    }
1842
 
    else
1843
 
    {
1844
 
        var currentLine = document.line(line);
1845
 
        if (currentLine.search(/^\s*((public|protected|private)\s*(slots|Q_SLOTS)?|(signals|Q_SIGNALS)\s*):\s*$/) != -1)
1846
 
        {
1847
 
            var definitionCursor = document.anchor(line, 0, '{');
1848
 
            if (definitionCursor.isValid())
1849
 
            {
1850
 
                result = document.firstVirtualColumn(definitionCursor.line);
1851
 
                dbg("tryColon: result="+result);
1852
 
                if (0 < line)                               // Is there any line before?
1853
 
                {
1854
 
                    // Check if previous line is not empty and not starts w/ '{'
1855
 
                    var prevLine = document.line(line - 1).trim()
1856
 
                    if (prevLine.length && !prevLine.startsWith("{"))
1857
 
                    {
1858
 
                        // Cuz a new line will be added in place of current, returning
1859
 
                        // result will not affect indentation. So do it manually.
1860
 
                        var firstColumn = document.firstColumn(line);
1861
 
                        var padding = "";
1862
 
                        if (firstColumn < result)
1863
 
                            padding = String().fill(' ', result - firstColumn);
1864
 
                        else if (result < firstColumn)
1865
 
                            document.removeText(line, 0, line, firstColumn - result);
1866
 
                        // Add an empty line before the current
1867
 
                        document.insertText(line, 0, "\n" + padding);
1868
 
                        result = 0;
1869
 
                    }
1870
 
                }
1871
 
            }
1872
 
        }
1873
 
        else if (document.charAt(line, column - 2) == ' ')
1874
 
        {
1875
 
            // Is it looks like a range based `for' or class/struct/enum?
1876
 
            var add_space = currentLine.ltrim().startsWith("for (")
1877
 
              || currentLine.ltrim().startsWith("class ")
1878
 
              || currentLine.ltrim().startsWith("struct ")
1879
 
              || currentLine.ltrim().startsWith("enum ")
1880
 
              ;
1881
 
            if (add_space)
1882
 
            {
1883
 
                // Add a space after ':'
1884
 
                document.insertText(line, column, " ");
1885
 
            }
1886
 
            else if (document.charAt(line, column - 3) == ':')
1887
 
            {
1888
 
                // Transform ': :' -> '::'
1889
 
                document.removeText(line, column - 2, line, column - 1);
1890
 
            }
1891
 
        }
1892
 
        else if (document.charAt(line, column - 2) == ':')
1893
 
        {
1894
 
            // A char before is (already) a one more colon.
1895
 
            // Make sure there is no more than two colons...
1896
 
            // NOTE In C++ it is not possible to have more than two of them adjacent!
1897
 
            if (document.charAt(line, column - 3) == ':')
1898
 
            {
1899
 
                // Remove the current (just entered) one...
1900
 
                document.removeText(line, column - 1, line, column);
1901
 
            }
1902
 
        }
1903
 
        else
1904
 
        {
1905
 
            // Check that it is not a 'case' and not a magic sequence.
1906
 
            // NOTE "Magic sequence" means support for dynamic expand functions.
1907
 
            // http://zaufi.github.io/programming/2014/02/13/kate-c++-stuff/
1908
 
            var is_magic_sequence = document.charAt(
1909
 
                line
1910
 
              , document.wordRangeAt(line, column - 1).start.column - 1
1911
 
              ) == ';';
1912
 
            if (!currentLine.ltrim().startsWith("case ") && !is_magic_sequence)
1913
 
            {
1914
 
                // Add one more ':'
1915
 
                // Example some<T>: --> some<T>:: or std: --> std::
1916
 
                document.insertText(line, column, ":");
1917
 
            }
1918
 
            else
1919
 
            {
1920
 
                // Try to jump out of parenthesis
1921
 
                cursor = tryJumpOverParenthesis(cursor);
1922
 
                // Try add a space after close bracket
1923
 
                tryAddSpaceAfterClosedBracketOrQuote(cursor);
1924
 
            }
1925
 
        }
1926
 
    }
1927
 
    return result;
1928
 
}
1929
 
 
1930
 
/**
1931
 
 * \brief Try to add one space after keywords and before an open brace
1932
 
 */
1933
 
function tryOpenBrace(cursor)
1934
 
{
1935
 
    var line = cursor.line;
1936
 
    var column = cursor.column;
1937
 
    var wordBefore = document.wordAt(line, column - 1);
1938
 
    dbg("word before: '"+wordBefore+"'");
1939
 
    if (wordBefore.search(/\b(catch|for|if|switch|while|return)\b/) != -1)
1940
 
        document.insertText(line, column - 1, " ");
1941
 
}
1942
 
 
1943
 
function getMacroRange(line)
1944
 
{
1945
 
    function stripLastCharAndRTrim(str)
1946
 
    {
1947
 
        return str.substring(0, str.length - 1).rtrim();
1948
 
    }
1949
 
    var maxLength = 0;
1950
 
    var macroStartLine = -1;
1951
 
    // Look up towards begining of a document
1952
 
    for (var i = line; i >= 0; --i)
1953
 
    {
1954
 
        var currentLineText = document.line(i);
1955
 
        dbg("up: '"+currentLineText+"'");
1956
 
        if (currentLineText.search(/^\s*#\s*define\s+.*\\$/) != -1)
1957
 
        {
1958
 
            macroStartLine = i;
1959
 
            maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length);
1960
 
            break;                                          // Ok, we've found the macro start!
1961
 
        }
1962
 
        else if (currentLineText.search(/\\$/) == -1)
1963
 
            break;                                          // Oops! No backslash found and #define still not reached!
1964
 
        maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length);
1965
 
    }
1966
 
 
1967
 
    if (macroStartLine == -1)
1968
 
        return null;
1969
 
 
1970
 
    // Look down towards end of the document
1971
 
    var macroEndLine = -1;
1972
 
    for (var i = line; i < document.lines(); ++i)
1973
 
    {
1974
 
        var currentLineText = document.line(i);
1975
 
        dbg("dw: '"+currentLineText+"'");
1976
 
        if (currentLineText.search(/\\$/) != -1)            // Make sure the current line have a '\' at the end
1977
 
        {
1978
 
            macroEndLine = i;
1979
 
            maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length);
1980
 
        }
1981
 
        else break;                                         // No backslash at the end --> end of macro!
1982
 
    }
1983
 
 
1984
 
    if (macroEndLine == -1)
1985
 
        return null;
1986
 
 
1987
 
    macroEndLine++;
1988
 
    return {
1989
 
        range: new Range(macroStartLine, 0, macroEndLine, 0)
1990
 
      , max: maxLength
1991
 
      };
1992
 
}
1993
 
 
1994
 
/**
1995
 
 * \brief Try to align a backslashes in macro definition
1996
 
 *
1997
 
 * \note It is \b illegal to have smth after a backslash in source code!
1998
 
 */
1999
 
function tryBackslash(cursor)
2000
 
{
2001
 
    var line = cursor.line;
2002
 
    var result = getMacroRange(line);                       // Look up and down for macro definition range
2003
 
    if (result != null)
2004
 
    {
2005
 
        dbg("macroRange:",result.range);
2006
 
        dbg("maxLength:",result.max);
2007
 
        // Iterate over macro definition, strip backslash
2008
 
        // and add a padding string up to result.max length + backslash
2009
 
        for (var i = result.range.start.line; i < result.range.end.line; ++i)
2010
 
        {
2011
 
            var currentLineText = document.line(i);
2012
 
            var originalTextLength = currentLineText.length;
2013
 
            currentLineText = currentLineText.substring(0, currentLineText.length - 1).rtrim();
2014
 
            var textLength = currentLineText.length;
2015
 
            document.removeText(i, textLength, i, originalTextLength);
2016
 
            document.insertText(i, textLength, String().fill(' ', result.max - textLength + 1) + "\\");
2017
 
        }
2018
 
    }
2019
 
}
2020
 
 
2021
 
/**
2022
 
 * \brief Handle a <tt>@</tt> symbol
2023
 
 *
2024
 
 * Possible user wants to add a Doxygen group
2025
 
 */
2026
 
function tryDoxygenGrouping(cursor)
2027
 
{
2028
 
    var line = cursor.line;
2029
 
    var column = cursor.column;
2030
 
    var firstColumn = document.firstColumn(line);
2031
 
    // Check the symbol before the just entered
2032
 
    var looks_like_doxgorup = isStringOrComment(line, column - 2)
2033
 
      && firstColumn == (column - 4)
2034
 
      && document.line(line).ltrim().startsWith("// ")
2035
 
      ;
2036
 
    if (looks_like_doxgorup)
2037
 
    {
2038
 
        document.removeText(line, column - 2, line, column - 1);
2039
 
        var padding = String().fill(' ', firstColumn);
2040
 
        document.insertText(line, column - 1, "{\n" + padding + "\n" + padding + "//@}");
2041
 
        view.setCursorPosition(line + 1, document.lineLength(line + 1));
2042
 
    }
2043
 
}
2044
 
 
2045
 
/**
2046
 
 * \brief Handle quote character
2047
 
 *
2048
 
 * Look back for \c 'R' char right before \c '"' and if
2049
 
 * the next one (after \c '"') is not an alphanumeric,
2050
 
 * then add a delimiters.
2051
 
 *
2052
 
 * \attention Effect of AutoBrace extension has already neutralized at this point :)
2053
 
 */
2054
 
function tryStringLiteral(cursor, ch)
2055
 
{
2056
 
    var line = cursor.line;
2057
 
    var column = cursor.column;
2058
 
 
2059
 
    if (isComment(line, column - 2))                        // Do nothing for comments
2060
 
        return;
2061
 
 
2062
 
    // First of all we have to determinate where we are:
2063
 
    // 0) new string literal just started, or ...
2064
 
    // 1) string literal just ends
2065
 
 
2066
 
    // Check if the '"' is a very first char on a line
2067
 
    var new_string_just_started;
2068
 
    if (column < 2)
2069
 
        // Yes, then we have to look to the last char of the previous line
2070
 
        new_string_just_started = !(line != 0 && isString(line - 1, document.lastColumn(line - 1)));
2071
 
    else
2072
 
        // Ok, just check attribute of the char right before '"'
2073
 
        new_string_just_started = !isString(line, column - 2);
2074
 
 
2075
 
    // TODO Add a space after possible operator right before just
2076
 
    // started string literal...
2077
 
    if (new_string_just_started)
2078
 
    {
2079
 
        // Is there anything after just entered '"'?
2080
 
        var nc = document.charAt(line, column);
2081
 
        var need_closing_quote = column == document.lineLength(line)
2082
 
          || document.isSpace(nc)
2083
 
          || nc == ','                                      // user tries to add new string param,
2084
 
          || nc == ')'                                      // ... or one more param to the end of some call
2085
 
          || nc == ']'                                      // ... or string literal as subscript index
2086
 
          || nc == ';'                                      // ... or one more string before end of expression
2087
 
          || nc == '<'                                      // ... or `some << "|<<`
2088
 
          ;
2089
 
        if (need_closing_quote)
2090
 
        {
2091
 
            // Check for 'R' right before '"'
2092
 
            if (ch == '"' && document.charAt(line, column - 2) == 'R')
2093
 
            {
2094
 
                // Yeah, looks like a raw string literal
2095
 
                /// \todo Make delimiter configurable... HOW?
2096
 
                /// It would be nice if indenters can have a configuration page somehow...
2097
 
                document.insertText(cursor, "~()~\"");
2098
 
                view.setCursorPosition(line, column + 2);
2099
 
            }
2100
 
            else
2101
 
            {
2102
 
                document.insertText(cursor, ch);
2103
 
                view.setCursorPosition(line, column);
2104
 
            }
2105
 
        }
2106
 
    }
2107
 
}
2108
 
 
2109
 
/**
2110
 
 * \brief Handle \c '!' char
2111
 
 *
2112
 
 * Exclamation symbol can be a part of \c operator!= or unary operator.
2113
 
 * in both cases, a space required before it! Except few cases:
2114
 
 * - when it is at the line start
2115
 
 * - when a char before it \c '(' -- i.e. argument of a control flow keyword (\c if, \c while)
2116
 
 *   or a function call parameter
2117
 
 * - when a char before it \c '[' (array subscript)
2118
 
 * - when a char before it \c '<' -- here is two cases possible:
2119
 
 *      - it is a first non-type template parameter (w/ type \c bool obviously)
2120
 
 *      - it is a part of less or shift operators.
2121
 
 * To distinct last case, it is enough to check that a word before \c '<' (w/o space)
2122
 
 * is an identifier.
2123
 
 * \note Yep, operators supposed to be separated from around text.
2124
 
 */
2125
 
function tryExclamation(cursor)
2126
 
{
2127
 
    var line = cursor.line;
2128
 
    var column = cursor.column;
2129
 
 
2130
 
    if (column == 0)                                        // Do nothing for very first char
2131
 
        return;
2132
 
 
2133
 
    if (isStringOrComment(line, column - 1))                // Do nothing for comments and stings
2134
 
        return;
2135
 
 
2136
 
    if (document.firstColumn(line) == column - 1)           // Make sure '!' is not a first char on a line
2137
 
        return;
2138
 
 
2139
 
    if (column < 2)                                         // Do nothing if there is less than 2 chars before
2140
 
        return;
2141
 
 
2142
 
    var pc = document.charAt(line, column - 2);             // Do nothing if one of 'stop' chars:
2143
 
    if (pc == ' ' || pc == '(' || pc == '[' || pc == '{')
2144
 
        return;
2145
 
 
2146
 
    // And finally make sure it is not a part of 'relation operator'
2147
 
    if (pc == '<' && column >= 3)
2148
 
    {
2149
 
        // Make sure a char before is not a space or another '<'
2150
 
        var ppc = document.charAt(line, column - 3);
2151
 
        if (ppc != ' ' && ppc != '<')
2152
 
            return;
2153
 
    }
2154
 
 
2155
 
    // Ok, if we r here, just insert a space ;)
2156
 
    document.insertText(line, column - 1, " ");
2157
 
}
2158
 
 
2159
 
/**
2160
 
 * \brief Handle a space
2161
 
 *
2162
 
 * - add <tt>'()'</tt> pair after some keywords like: \c if, \c while, \c for, \c switch
2163
 
 * - add <tt>';'</tt> if space pressed right after \c return, and no text after it
2164
 
 * - if space pressed inside of angle brackets 'some<|>' transform into 'some < |'
2165
 
 */
2166
 
function tryKeywordsWithBrackets(cursor)
2167
 
{
2168
 
    var line = cursor.line;
2169
 
    var column = cursor.column;
2170
 
    var text = document.line(line).ltrim();
2171
 
    var need_brackets = text == "if "
2172
 
      || text == "else if "
2173
 
      || text == "while "
2174
 
      || text == "for "
2175
 
      || text == "switch "
2176
 
      || text == "catch "
2177
 
      ;
2178
 
    if (need_brackets)
2179
 
    {
2180
 
        document.insertText(cursor, "()");
2181
 
        view.setCursorPosition(line, column + 1);
2182
 
    }
2183
 
    else if (text == "return ")
2184
 
    {
2185
 
        document.insertText(cursor, ";");
2186
 
        view.setCursorPosition(line, column);
2187
 
    }
2188
 
    else if (document.charAt(line, column - 2) == '<' && document.charAt(cursor) == '>')
2189
 
    {
2190
 
        document.removeText(line, column, line, column + 1);
2191
 
        document.insertText(line, column - 2, " ");
2192
 
    }
2193
 
}
2194
 
 
2195
 
/**
2196
 
 * Try to add space before and after some equal operators.
2197
 
 */
2198
 
function tryEqualOperator(cursor)
2199
 
{
2200
 
    var line = cursor.line;
2201
 
    var column = cursor.column;
2202
 
 
2203
 
    // Do nothing for comments or string literals or lines shorter than 2
2204
 
    if (2 < column && isStringOrComment(line, column))
2205
 
        return cursor;
2206
 
 
2207
 
    var c = document.charAt(line, column - 2);
2208
 
    dbg("tryEqualOperator: checking @Cursor("+line+","+(column - 2)+"), c='"+c+"'");
2209
 
 
2210
 
    switch (c)
2211
 
    {
2212
 
        // Two chars operators: !=, ==, ...
2213
 
        case '*':
2214
 
        case '%':
2215
 
        case '/':
2216
 
        case '^':
2217
 
        case '|':
2218
 
        case '&':
2219
 
        case '!':
2220
 
        case '=':
2221
 
            addCharOrJumpOverIt(line, column, ' ');         // Make sure there is a space after it!
2222
 
            // Make sure there is a space before it!
2223
 
            if (column >= 3 && document.charAt(line, column - 3) != ' ')
2224
 
                document.insertText(line, column - 2, " ");
2225
 
            break;
2226
 
        case '(':                                           // some(=|) --> some() =|
2227
 
            cursor = tryJumpOverParenthesis(cursor);
2228
 
            tryEqualOperator(cursor);                       // Call self again to handle "some()=|"
2229
 
            break;
2230
 
        case ')':                                           // "some()=" or "(expr)=" --> ") =|"
2231
 
        case '}':                                           // It can be a ctor of some proxy object
2232
 
            // Add a space between closing bracket and just entered '='
2233
 
            document.insertText(line, column - 1, " ");
2234
 
            addCharOrJumpOverIt(line, column + 1, ' ');     // Make sure there is a space after it!
2235
 
            break;
2236
 
        case '<':
2237
 
            // Shortcut: transfrom "some<=|>" -> "some <= |"
2238
 
            if (document.charAt(cursor) == '>')
2239
 
                document.removeText(line, column, line, column + 1);
2240
 
        case '>':
2241
 
            // This could be '<<=', '>>=', '<=', '>='
2242
 
            // Make sure there is a space after it!
2243
 
            addCharOrJumpOverIt(line, column, ' ');         // Make sure there is a space after it!
2244
 
            // Check if this is one of >>= or <<=
2245
 
            if (column >= 3)
2246
 
            {
2247
 
                if (document.charAt(line, column - 3) == c)
2248
 
                {
2249
 
                    if (column >= 4 && document.charAt(line, column - 4) != ' ')
2250
 
                        document.insertText(line, column - 3, " ");
2251
 
                }
2252
 
                else if (document.charAt(line, column - 3) != ' ')
2253
 
                {
2254
 
                    // <= or >=
2255
 
                    document.insertText(line, column - 2, " ");
2256
 
                }
2257
 
            }
2258
 
            break;
2259
 
        case '[':                                           // This could be a part of lambda capture [=]
2260
 
            break;
2261
 
        case ' ':
2262
 
            // Lookup one more character towards left
2263
 
            if (column >= 3)
2264
 
            {
2265
 
                var pc = document.charAt(line, column - 3);
2266
 
                dbg("tryEqualOperator: checking @Cursor("+line+","+(column - 3)+"), pc='"+pc+"'");
2267
 
                switch (pc)
2268
 
                {
2269
 
                    case '=':                               // Stick the current '=' to the previous char
2270
 
                    case '|':
2271
 
                    case '&':
2272
 
                    case '^':
2273
 
                    case '<':
2274
 
                    case '>':
2275
 
                    case '*':
2276
 
                    case '/':
2277
 
                    case '%':
2278
 
                        document.removeText(line, column - 1, line, column);
2279
 
                        document.insertText(line, column - 2, '=');
2280
 
                        break;
2281
 
                    default:
2282
 
                        break;
2283
 
                }
2284
 
            }
2285
 
            break;
2286
 
        case '+':
2287
 
        case '-':
2288
 
            addCharOrJumpOverIt(line, column, ' ');         // Make sure there is a space after it!
2289
 
            // Here is few things possible:
2290
 
            // some+=| --> some += |
2291
 
            // some++=| --> some++ = |
2292
 
            // some+++=| --> some++ += |
2293
 
            var space_offset = -1;
2294
 
            if (column >= 3)
2295
 
            {
2296
 
                if (document.charAt(line, column - 3) == c)
2297
 
                {
2298
 
                    if (column >= 4)
2299
 
                    {
2300
 
                        if (document.charAt(line, column - 4) == c)
2301
 
                            space_offset = 2;
2302
 
                        else if (document.charAt(line, column - 4) != ' ')
2303
 
                            space_offset = 1;
2304
 
                    }
2305
 
                }
2306
 
                else if (document.charAt(line, column - 3) != ' ')
2307
 
                    space_offset = 2;
2308
 
            }
2309
 
            if (space_offset != -1)
2310
 
                document.insertText(line, column - space_offset, " ");
2311
 
            break;
2312
 
        default:
2313
 
            dbg("tryEqualOperator: default");
2314
 
            // '=' always surrounded by spaces!
2315
 
            addCharOrJumpOverIt(line, column, ' ');         // Make sure there is a space after it!
2316
 
            document.insertText(line, column - 1, " ");     // Make sure there is a space before it!
2317
 
            break;
2318
 
    }
2319
 
}
2320
 
 
2321
 
/**
2322
 
 * \brief Process one character
2323
 
 *
2324
 
 * NOTE Cursor positioned right after just entered character and has +1 in column.
2325
 
 *
2326
 
 * \attention This function will roll back the effect of \b AutoBrace extension
2327
 
 * for quote chars. So this indenter can handle that chars withing predictable
2328
 
 * surround...
2329
 
 *
2330
 
 */
2331
 
function processChar(line, ch)
2332
 
{
2333
 
    var result = -2;                                        // By default, do nothing...
2334
 
    var cursor = view.cursorPosition();
2335
 
    if (!cursor)
2336
 
        return result;
2337
 
 
2338
 
    // TODO Is there any `assert' in JS?
2339
 
    if (line != cursor.line)
2340
 
    {
2341
 
        dbg("ASSERTION FAILURE: line != cursor.line");
2342
 
        return result;
2343
 
    }
2344
 
 
2345
 
    document.editBegin();
2346
 
    // Check if char under cursor is the same as just entered,
2347
 
    // and if so, remove it... to make it behave like "overwrite" mode
2348
 
    if (ch != ' ' && document.charAt(cursor) == ch)
2349
 
        document.removeText(line, cursor.column, line, cursor.column + 1);
2350
 
 
2351
 
    switch (ch)
2352
 
    {
2353
 
        case '\n':
2354
 
            result = caretPressed(cursor);
2355
 
            break;
2356
 
        case '/':
2357
 
            trySameLineComment(cursor);                     // Possible user wants to start a comment
2358
 
            break;
2359
 
        case '<':
2360
 
            result = tryTemplate(cursor);                   // Possible need to add closing '>' after template
2361
 
            break;
2362
 
        case ',':
2363
 
            result = tryComma(cursor);                      // Possible need to align parameters list
2364
 
            break;
2365
 
        case ';':
2366
 
            result = trySemicolon(cursor);                  // Possible `for ()` loop spread over lines
2367
 
            break;
2368
 
        case '?':
2369
 
        case '|':
2370
 
        case '^':
2371
 
        case '%':
2372
 
        case '.':
2373
 
            result = tryOperator(cursor, ch);               // Possible need to align some operator
2374
 
            break;
2375
 
        case '}':
2376
 
        case ')':
2377
 
        case ']':
2378
 
        case '>':
2379
 
            result = tryCloseBracket(cursor, ch);           // Try to align a given close bracket
2380
 
            break;
2381
 
        case '{':
2382
 
            result = tryBlock(cursor);
2383
 
            break;
2384
 
        case '#':
2385
 
            result = tryPreprocessor(cursor);
2386
 
            break;
2387
 
        case ':':
2388
 
            result = tryColon(cursor);
2389
 
            break;
2390
 
        case '(':
2391
 
            tryOpenBrace(cursor);                           // Try to add a space after some keywords
2392
 
            break;
2393
 
        case '\\':
2394
 
            tryBackslash(cursor);
2395
 
            break;
2396
 
        case '@':
2397
 
            tryDoxygenGrouping(cursor);
2398
 
            break;
2399
 
        case '"':
2400
 
        case '\'':
2401
 
            tryStringLiteral(cursor, ch);
2402
 
            break;
2403
 
        case '!':                                           // Almost all the time there should be a space before!
2404
 
            tryExclamation(cursor);
2405
 
            break;
2406
 
        case ' ':
2407
 
            tryKeywordsWithBrackets(cursor);
2408
 
            break;
2409
 
        case '=':
2410
 
            tryEqualOperator(cursor);
2411
 
            break;
2412
 
        case '*':
2413
 
        case '&':
2414
 
            tryAddSpaceAfterClosedBracketOrQuote(cursor);
2415
 
            break;
2416
 
        default:
2417
 
            break;                                          // Nothing to do...
2418
 
    }
2419
 
 
2420
 
    // Make sure it is not a pure comment line
2421
 
    var currentLineText = document.line(cursor.line).ltrim();
2422
 
    if (ch != '\n' && !currentLineText.startsWith("//"))
2423
 
    {
2424
 
        // Ok, try to keep an inline comment aligned (if any)...
2425
 
        // BUG If '=' was inserted (and a space added) in a code line w/ inline comment,
2426
 
        // it seems kate do not update highlighting, so position, where comment was before,
2427
 
        // still counted as a 'Comment' attribute, but actually it should be 'Normal Text'...
2428
 
        // It is why adding '=' will not realign an inline comment...
2429
 
        if (alignInlineComment(cursor.line) && ch == ' ')
2430
 
            document.insertText(view.cursorPosition(), ' ');
2431
 
    }
2432
 
 
2433
 
    document.editEnd();
2434
 
    return result;
2435
 
}
2436
 
 
2437
 
function alignPreprocessor(line)
2438
 
{
2439
 
    if (tryPreprocessor_ch(line) == -1)                     // Is smth happened?
2440
 
        return -2;                                          // No! Signal to upper level to try next aligner...
2441
 
    return 0;                                               // NOTE preprocessor directives always aligned to 0!
2442
 
}
2443
 
 
2444
 
/**
2445
 
 * Try to find a next non comment line assuming that a given
2446
 
 * one is a start or middle of a multi-line comment.
2447
 
 *
2448
 
 * \attention This function would ignore anything else than
2449
 
 * a simple comments like this one... I.e. if \b right after
2450
 
 * star+slash starts anything (non comment, or even maybe after
2451
 
 * that another one comment begins), it will be \b IGNORED.
2452
 
 * (Just because this is a damn ugly style!)
2453
 
 *
2454
 
 * \return line number or \c 0 if not found
2455
 
 * \note \c 0 is impossible value, so suitable to indicate an error!
2456
 
 *
2457
 
 * \sa \c alignInsideBraces()
2458
 
 */
2459
 
function findMultiLineCommentBlockEnd(line)
2460
 
{
2461
 
    for (; line < document.lines(); line++)
2462
 
    {
2463
 
        var text = document.line(line).rtrim();
2464
 
        if (text.endsWith("*/"))
2465
 
            break;
2466
 
    }
2467
 
    line++;                                                 // Move to *next* line
2468
 
    if (line < document.lines())
2469
 
    {
2470
 
        // Make sure it is not another one comment, and if so,
2471
 
        // going to find it's end as well...
2472
 
        var currentLineText = document.line(line).ltrim();
2473
 
        if (currentLineText.startsWith("//"))
2474
 
            line = findSingleLineCommentBlockEnd(line);
2475
 
        else if (currentLineText.startsWith("/*"))
2476
 
            line = findMultiLineCommentBlockEnd(line);
2477
 
    }
2478
 
    else line = 0;                                          // EOF found
2479
 
    return line;
2480
 
}
2481
 
 
2482
 
/**
2483
 
 * Try to find a next non comment line assuming that a given
2484
 
 * one is a single-line one
2485
 
 *
2486
 
 * \return line number or \c 0 if not found
2487
 
 * \note \c 0 is impossible value, so suitable to indicate an error!
2488
 
 *
2489
 
 * \sa \c alignInsideBraces()
2490
 
 */
2491
 
function findSingleLineCommentBlockEnd(line)
2492
 
{
2493
 
    while (++line < document.lines())
2494
 
    {
2495
 
        var text = document.line(line).ltrim();
2496
 
        if (text.length == 0) continue;                     // Skip empty lines...
2497
 
        if (!text.startsWith("//")) break;                  // Yeah! Smth was found finally.
2498
 
    }
2499
 
    if (line < document.lines())
2500
 
    {
2501
 
        var currentLineText = document.line(line).ltrim();  // Get text of the found line
2502
 
        while (currentLineText.length == 0)                 // Skip empty lines if any
2503
 
            currentLineText = document.line(++line).ltrim();
2504
 
        // Make sure it is not another one multiline comment, and if so,
2505
 
        // going to find it's end as well...
2506
 
        if (currentLineText.startsWith("/*"))
2507
 
            line = findMultiLineCommentBlockEnd(line);
2508
 
    }
2509
 
    else line = 0;                                          // EOF found
2510
 
    return line;
2511
 
}
2512
 
 
2513
 
/**
2514
 
 * Almost anything in a code is placed withing some brackets.
2515
 
 * So the ideas is simple:
2516
 
 * \li find nearest open bracket of any kind
2517
 
 * \li depending on its type and presence of leading delimiters (non identifier characters)
2518
 
 *     add one or half TAB relative a first non-space char of a line w/ found bracket.
2519
 
 *
2520
 
 * But here is some details:
2521
 
 * \li do nothing on empty lines
2522
 
 * \li do nothing if first position is a \e string
2523
 
 * \li align comments according next non-comment and non-preprocessor line
2524
 
 *     (i.e. it's desired indent cuz it maybe still unaligned)
2525
 
 *
2526
 
 * \attention Current Kate version has a BUG: \c anchor() unable to find smth
2527
 
 * in a multiline macro definition (i.e. where every line ends w/ a backslash)!
2528
 
 */
2529
 
function alignInsideBraces(line)
2530
 
{
2531
 
    // Make sure there is a text on a line, otherwise nothing to align here...
2532
 
    var thisLineIndent = document.firstColumn(line);
2533
 
    if (thisLineIndent == -1 || document.isString(line, 0))
2534
 
        return 0;
2535
 
 
2536
 
    // Check for comment on the current line
2537
 
    var currentLineText = document.line(line).ltrim();
2538
 
    var nextNonCommentLine = -1;
2539
 
    var middleOfMultilineBlock = false;
2540
 
    var isSingleLineComment = false;
2541
 
    if (currentLineText.startsWith('//'))                   // Is single line comment on this line?
2542
 
    {
2543
 
        dbg("found a single-line comment");
2544
 
        // Yep, go to find a next non-comment line...
2545
 
        nextNonCommentLine = findSingleLineCommentBlockEnd(line);
2546
 
        isSingleLineComment = true;
2547
 
    }
2548
 
    else if (currentLineText.startsWith('/*'))              // Is multiline comment starts on this line?
2549
 
    {
2550
 
        // Yep, go to find a next non-comment line...
2551
 
        dbg("found start of a multiline comment");
2552
 
        nextNonCommentLine = findMultiLineCommentBlockEnd(line);
2553
 
    }
2554
 
    // Are we already inside of a multiline comment?
2555
 
    // NOTE To be sure that we are not inside of #if0/#endif block,
2556
 
    // lets check that current line starts w/ '*' also!
2557
 
    // NOTE Yep, it is expected (hardcoded) that multiline comment has
2558
 
    // all lines started w/ a star symbol!
2559
 
    // TODO BUG Kate has a bug: when multiline code snippet gets inserted into
2560
 
    // a multiline comment block (like Doxygen's @code/@endcode)
2561
 
    // document.isComment() returns true *only& for the first line of it!
2562
 
    // So some other way needs to be found to indent comments properly...
2563
 
    // TODO DAMN... it doesn't work that way also... for snippets longer than 2 lines.
2564
 
    // I suppose kate first insert text, then indent it, and after that highlight it
2565
 
    // So indenters based on a highlighting info will not work! BUT THEY DEFINITELY SHOULD!
2566
 
    else if (currentLineText.startsWith("*") && document.isComment(line, 0))
2567
 
    {
2568
 
        dbg("found middle of a multiline comment");
2569
 
        // Yep, go to find a next non-comment line...
2570
 
        nextNonCommentLine = findMultiLineCommentBlockEnd(line);
2571
 
        middleOfMultilineBlock = true;
2572
 
    }
2573
 
    dbg("line="+line);
2574
 
    dbg("document.isComment(line, 0)="+document.isComment(line, 0));
2575
 
    //dbg("document.defStyleNum(line, 0)="+document.defStyleNum(line-1, 0));
2576
 
    dbg("currentLineText='"+currentLineText+"'");
2577
 
    dbg("middleOfMultilineBlock="+middleOfMultilineBlock);
2578
 
 
2579
 
    if (nextNonCommentLine == 0)                            // End of comment not found?
2580
 
        // ... possible due temporary invalid code...
2581
 
        // anyway, dunno how to align it!
2582
 
        return -2;
2583
 
    // So, are we inside a comment? (and we know where it ends)
2584
 
    if (nextNonCommentLine != -1)
2585
 
    {
2586
 
        // Yep, lets try to get desired indent for next non-comment line
2587
 
        var desiredIndent = indentLine(nextNonCommentLine);
2588
 
        if (desiredIndent < 0)
2589
 
        {
2590
 
            // Have no idea how to indent this comment! So try to align it
2591
 
            // as found line:
2592
 
            desiredIndent = document.firstColumn(nextNonCommentLine);
2593
 
        }
2594
 
        // TODO Make sure that next non-comment line do not starts
2595
 
        // w/ 'special' chars...
2596
 
        return desiredIndent + (middleOfMultilineBlock|0);
2597
 
    }
2598
 
 
2599
 
    var brackets = [
2600
 
        document.anchor(line, document.firstColumn(line), '(')
2601
 
      , document.anchor(line, document.firstColumn(line), '{')
2602
 
      , document.anchor(line, document.firstColumn(line), '[')
2603
 
      ].sort();
2604
 
    dbg("Found open brackets @ "+brackets);
2605
 
 
2606
 
    // Check if we are at some brackets, otherwise do nothing
2607
 
    var nearestBracket = brackets[brackets.length - 1];
2608
 
    if (!nearestBracket.isValid())
2609
 
        return 0;
2610
 
 
2611
 
    // Make sure it is not a `namespace' level
2612
 
    // NOTE '{' brace should be at the same line w/ a 'namespace' keyword
2613
 
    // (yep, according my style... :-)
2614
 
    var bracketChar = document.charAt(nearestBracket);
2615
 
    var parentLineText = document.line(nearestBracket.line).ltrim();
2616
 
    if (bracketChar == '{' && parentLineText.startsWith("namespace"))
2617
 
        return 0;
2618
 
 
2619
 
    // Ok, (re)align it!
2620
 
    var result = -2;
2621
 
    switch (bracketChar)
2622
 
    {
2623
 
        case '{':
2624
 
        case '(':
2625
 
        case '[':
2626
 
            // If current line has some leading delimiter, i.e. non alphanumeric character
2627
 
            // add a half-TAB, otherwise add a one TAB... if needed!
2628
 
            var parentIndent = document.firstColumn(nearestBracket.line);
2629
 
            var openBraceIsFirst = parentIndent == nearestBracket.column;
2630
 
            var firstChar = document.charAt(line, thisLineIndent);
2631
 
            var isCloseBraceFirst = firstChar == ')' || firstChar == ']' || firstChar == '}';
2632
 
            var doNotAddAnything = openBraceIsFirst && isCloseBraceFirst;
2633
 
            var mustAddHalfTab = (!openBraceIsFirst && isCloseBraceFirst)
2634
 
              || firstChar == ','
2635
 
              || firstChar == '?'
2636
 
              || firstChar == ':'
2637
 
              || firstChar == ';'
2638
 
              ;
2639
 
            var desiredIndent = parentIndent + (
2640
 
                mustAddHalfTab
2641
 
              ? (gIndentWidth / 2)
2642
 
              : (doNotAddAnything ? 0 : gIndentWidth)
2643
 
              );
2644
 
            result = desiredIndent;                         // Reassign a result w/ desired value!
2645
 
            //BEGIN SPAM
2646
 
            dbg("parentIndent="+parentIndent);
2647
 
            dbg("openBraceIsFirst="+openBraceIsFirst);
2648
 
            dbg("firstChar="+firstChar);
2649
 
            dbg("isCloseBraceFirst="+isCloseBraceFirst);
2650
 
            dbg("doNotAddAnything="+doNotAddAnything);
2651
 
            dbg("mustAddHalfTab="+mustAddHalfTab);
2652
 
            dbg("desiredIndent="+desiredIndent);
2653
 
            //END SPAM
2654
 
            break;
2655
 
        default:
2656
 
            dbg("Dunno how to align this line...");
2657
 
            break;
2658
 
    }
2659
 
    return result;
2660
 
}
2661
 
 
2662
 
function alignAccessSpecifier(line)
2663
 
{
2664
 
    var result = -2;
2665
 
    var currentLineText = document.line(line).ltrim();
2666
 
    var match = currentLineText.search(
2667
 
        /^\s*((public|protected|private)\s*(slots|Q_SLOTS)?|(signals|Q_SIGNALS)\s*):\s*$/
2668
 
      );
2669
 
    if (match != -1)
2670
 
    {
2671
 
        // Ok, lets find an open brace of the `class'/`struct'
2672
 
        var openBracePos = document.anchor(line, document.firstColumn(line), '{');
2673
 
        if (openBracePos.isValid())
2674
 
            result = document.firstColumn(openBracePos.line);
2675
 
    }
2676
 
    return result;
2677
 
}
2678
 
 
2679
 
/**
2680
 
 * Try to align \c case statements in a \c switch
2681
 
 */
2682
 
function alignCase(line)
2683
 
{
2684
 
    var result = -2;
2685
 
    var currentLineText = document.line(line).ltrim();
2686
 
    if (currentLineText.startsWith("case ") || currentLineText.startsWith("default:"))
2687
 
    {
2688
 
        // Ok, lets find an open brace of the `switch'
2689
 
        var openBracePos = document.anchor(line, document.firstColumn(line), '{');
2690
 
        if (openBracePos.isValid())
2691
 
            result = document.firstColumn(openBracePos.line) + gIndentWidth;
2692
 
    }
2693
 
    return result;
2694
 
}
2695
 
 
2696
 
/**
2697
 
 * Try to align \c break or \c continue statements in a loop or \c switch.
2698
 
 *
2699
 
 * Also it take care about the following case:
2700
 
 * \code
2701
 
 *  for (blah-blah)
2702
 
 *  {
2703
 
 *      if (smth)
2704
 
 *          break;
2705
 
 *  }
2706
 
 * \endcode
2707
 
 */
2708
 
function alignBreakContinue(line)
2709
 
{
2710
 
    var result = -2;
2711
 
    var currentLineText = document.line(line).ltrim();
2712
 
    var is_break = currentLineText.startsWith("break;");
2713
 
    var should_proceed = is_break || currentLineText.startsWith("continue;")
2714
 
    if (should_proceed)
2715
 
        result = tryBreakContinue(line - 1, is_break);
2716
 
    return result;
2717
 
}
2718
 
 
2719
 
/**
2720
 
 * Try to align a given line
2721
 
 * \todo More actions
2722
 
 */
2723
 
function indentLine(line)
2724
 
{
2725
 
    dbg(">> Going to indent line "+line);
2726
 
    var result = alignPreprocessor(line);                   // Try to align a preprocessor directive
2727
 
    if (result == -2)                                       // Nothing has changed?
2728
 
        result = alignAccessSpecifier(line);                // Try to align access specifiers in a class
2729
 
    if (result == -2)                                       // Nothing has changed?
2730
 
        result = alignCase(line);                           // Try to align `case' statements in a `switch'
2731
 
    if (result == -2)                                       // Nothing has changed?
2732
 
        result = alignBreakContinue(line);                  // Try to align `break' or `continue' statements
2733
 
    if (result == -2)                                       // Nothing has changed?
2734
 
        result = alignInsideBraces(line);                   // Try to align a generic line
2735
 
    alignInlineComment(line);                               // Always try to align inline comments
2736
 
 
2737
 
    dbg("indentLine result="+result);
2738
 
 
2739
 
    if (result == -2)                                       // Still dunno what to do?
2740
 
        result = -1;                                        // ... just align according a previous non empty line
2741
 
    return result;
2742
 
}
2743
 
 
2744
 
/**
2745
 
 * \brief Process a newline or one of \c triggerCharacters character.
2746
 
 *
2747
 
 * This function is called whenever the user hits \c ENTER key.
2748
 
 *
2749
 
 * It gets three arguments: \c line, \c indentwidth in spaces and typed character
2750
 
 *
2751
 
 * Called for each newline (<tt>ch == \n</tt>) and all characters specified in
2752
 
 * the global variable \c triggerCharacters. When calling \e Tools->Align
2753
 
 * the variable \c ch is empty, i.e. <tt>ch == ''</tt>.
2754
 
 */
2755
 
function indent(line, indentWidth, ch)
2756
 
{
2757
 
    // NOTE Update some global variables
2758
 
    gIndentWidth = indentWidth;
2759
 
    var crsr = view.cursorPosition();
2760
 
 
2761
 
    dbg("indentWidth: " + indentWidth);
2762
 
    dbg("       Mode: " + document.highlightingModeAt(crsr));
2763
 
    dbg("  Attribute: " + document.attributeName(crsr));
2764
 
    dbg("       line: " + line);
2765
 
    dbg("       char: " + crsr + " -> '" + ch + "'");
2766
 
 
2767
 
    if (ch != "")
2768
 
        return processChar(line, ch);
2769
 
 
2770
 
    return indentLine(line);
2771
 
}
2772
 
 
2773
 
/**
2774
 
 * \todo Better to use \c defStyleNum() instead of \c attributeName() and string comparison
2775
 
 *
2776
 
 * \todo Prevent second '//' on a line... ? Fix the current way anyway...
2777
 
 */
2778
 
 
2779
 
// kate: space-indent on; indent-width 4; replace-tabs on;