File indexing completed on 2024-05-19 04:00:08

0001 var katescript = {
0002     "name": "ada",
0003     "author": "Trevor Blight <trevor-b@ovi.com>",
0004     "license": "LGPL",
0005     "revision": 2,
0006     "kate-version": "5.1"
0007 }; // kate-script-header, must be at the start of the file without comments, pure json
0008 
0009 /*
0010     This file is part of the Kate Project.
0011 
0012     SPDX-License-Identifier: LGPL-2.0-only
0013 */
0014 // loosely based on the vim ada indent file
0015 //-------- extract from original vim header follows --------------
0016 //
0017 //  Description: Vim Ada indent file
0018 //     Language: Ada (2005)
0019 //    Copyright: Copyright (C) 2006 Martin Krischik
0020 //   Maintainer: Martin Krischik <krischik@users.sourceforge.net>
0021 //              Neil Bird <neil@fnxweb.com>
0022 //              Ned Okie <nokie@radford.edu>
0023 //
0024 //--------------- end of extract -----------------
0025 
0026 // apart from porting and translating from vim script to kate javascript,
0027 // changes compared to orininal vim script:
0028 // - added handling for labels and 'generic' keyword
0029 // - recognise keyword 'private' as part of package specification
0030 // - handle multi line for/while ... loop
0031 // - cat open/close paren lines to handle scanning multi-line proc/func dec's
0032 // - recognise various multi line statements
0033 // - indent once only after case statement
0034 // - add checkParens() for aligning parameters in multiline arg list
0035 // - refactoring & optimisations
0036 
0037 // TODO: 'protected', 'task', 'select' &
0038 //       possibly other keywords not recognised
0039 
0040 // required katepart js libraries
0041 require ("range.js");
0042 require ("cursor.js");
0043 require ("string.js");
0044 
0045 //BEGIN USER CONFIGURATION
0046 
0047 // send debug output to terminal
0048 // the ada indenter recognises this as a document variable
0049 var debugMode = false;
0050 
0051 //END USER CONFIGURATION
0052 
0053 var AdaComment    = /\s*--.*$/;
0054 var unfStmt       = /([.=\(]|:=[^;]*)\s*$/;
0055 var unfLoop       = /^\s*(for|while)\b(?!.*\bloop\b)/i;
0056 var AdaBlockStart = /^\s*(if\b|while\b|else\b|elsif\b|loop\b|for\b.*\b(loop|use)\b|declare\b|begin\b|type\b.*\bis\b[^;]*$|(type\b.*)?\brecord\b|procedure\b|function\b|with\s+function\b|accept\b|do\b|task\b|generic\b|package\b|private\b|then\b|when\b|is\b)/i;
0057 var StatementStart = /^\s*(if|when|while|else|elsif|loop|for\b.*\b(loop|use)|begin)\b/i;
0058 
0059 function dbg() {
0060     if (debugMode) {
0061         debug.apply(this, arguments);
0062     }
0063 }
0064 
0065 
0066 //BEGIN global variables and functions
0067 
0068 // none implemented
0069 
0070 //END global variables and functions
0071 
0072 
0073 // regexp of keywords & patterns that cause reindenting of the current line.
0074 var AdaReIndent = /^\s*((then|end|elsif|when|exception|begin|is|record|private)\s+|<<\w+>>|end;|[#\)])(.*)$/;
0075 
0076 // characters which trigger indent, beside the default '\n'
0077 var triggerCharacters = " \t)#>;";
0078 
0079 // check if the trigger characters are in the right context,
0080 // otherwise running the indenter might be annoying to the user
0081 function reindentTrigger(line)
0082 {
0083     var res = AdaReIndent.exec(document.line(line));
0084     dbg("reindentTrigger: checking line, found", ((res && res[3] == "" )? "'"+ (res[2]?res[2]:res[1])+"'": "nothing") );
0085     return ( res && (res[3] == "" ));
0086 } // reindentTrigger
0087 
0088 
0089 /**
0090  * Find last non-empty line that is code
0091  */
0092 function lastCodeLine(line)
0093 {
0094     do {
0095         line = document.prevNonEmptyLine(--line);
0096         if ( line == -1 ) {
0097             return -1;
0098         }
0099 
0100         // keep looking until we reach code
0101         // skip labels & preprocessor lines
0102         var p = document.firstColumn(line);
0103     } while( document.firstChar(line) == '#'
0104              || !( document.isCode(line, document.firstColumn(line) )
0105                 || document.isString(line, document.firstColumn(line) )
0106                 || document.isChar(line, document.firstColumn(line) ) )
0107              || /^\s*<<\w+>>/.test(document.line(line)) );
0108 
0109     return line;
0110 } // lastCodeLine()
0111 
0112 
0113 /* test if line has an unclosed opening paren '('
0114  * line is document line
0115  * n is nr chars to test, starting at pos 0
0116  * return index of unmatched '(', or
0117  *        -1 if no unmatched '('
0118  */
0119 function checkOpenParen( line, n )
0120 {
0121     var nest = 0;
0122     var i = n;
0123     while( nest <= 0 && --i >= 0 ) {
0124         if( document.isCode(line, i) ) {
0125             var c = document.charAt( line, i);
0126             if( c == ')' )      nest--;
0127             else if( c == '(' ) nest++;
0128         }
0129     }
0130     //dbg("string " + document.line(line) + (i==-1? " has matched open parens": " has unclosed paren at pos " + i ));
0131     return i;
0132 }
0133 
0134 /* test if line has an unmatched close paren
0135  * line is document line
0136  * n is nr chars to test, starting at pos 0
0137  * return index of rightmost unclosed paren
0138  *        -1 if no unmatched paren
0139  */
0140 function checkCloseParen( line, n )
0141 {
0142     var nest = 0;
0143     var i = -1;
0144     var bpos = -1;
0145     while( ++i < n ) {
0146         if( document.isCode(line, i) ) {
0147             var c = document.charAt( line, i);
0148             if( c == ')' )      nest++;
0149             else if( c == '(' ) nest--;
0150         }
0151         if( nest > 0 ) {
0152             nest = 0;
0153             bpos = i;
0154         }
0155     }
0156     //dbg("string " + document.line(line) + (bpos==-1? " has matched close parens": " has unbalanced paren at pos " + bpos) );
0157     return bpos;
0158 }
0159 
0160 
0161 /*********************************************
0162  * line up args inside parens
0163  * if the opening line has no args, line up with first arg on next line
0164  * ignore trailing comments.
0165  * opening line ==> figure out indent based on pos of first arg
0166  * subsequent args ==> maintain indent
0167  *       ');' ==> align closing paren
0168  *
0169  *  paren indenting has these cases:
0170  *   pline: (    no args     -- new line indented
0171  *   pline: (    args        -- new line aligns with first arg
0172  *   pline  )                -- check if still inside parens
0173  *   cline  );   no args     -- open line, align with ( or args
0174  *   cline  );   args        -- align with line above, same as just args
0175  *               just args   -- align with line above
0176  */
0177 function checkParens(line, indentWidth, newLine )
0178 {
0179 
0180     var openParen = document.anchor(line, 0, ')');
0181     if( !openParen.isValid() ) {
0182         // nothing found, go back
0183         //dbg("checkParens: not in parens");
0184         return -1;
0185     }
0186 
0187     // note: pline is code, and
0188     // we get here only when we know it exists
0189     var pline = lastCodeLine(line);
0190     var plineStr = document.line(pline).replace(AdaComment, "");
0191 
0192     /**
0193      * look for the case where the new line starts with ')'
0194      */
0195     if( document.firstChar(line) == ')' ) {
0196         // we pressed enter in e.g. ()
0197         var argIndent = document.firstVirtualColumn(pline);
0198 
0199         var parenIndent = document.toVirtualColumn(openParen);
0200 
0201         // look for something like
0202         //        argn     <--- pline
0203         //        );       <--- line, trailing close paren
0204         // the trailing close paren lines up with the args or the open paren,
0205         // whichever is the leftmost
0206         if( pline > openParen.line && argIndent > 0 && argIndent < parenIndent) {
0207             parenIndent = argIndent;
0208         }
0209 
0210         // just align parens, not args
0211         if( !newLine || !/[,\(]$/.test(plineStr) ) {
0212             dbg("checkParens: align trailing ')'");
0213             return parenIndent;
0214         }
0215 
0216         /* now we have one of 2 scenarios:
0217          *    arg,  <--- pline, , ==> new arg to be inserted
0218          *    )     <--- line
0219          * or
0220          *    xxx(  <--- pline
0221          *    )     <--- line
0222          *
0223          * in both cases, we need to open a line for the new arg and
0224          * place closing paren in same column as the opening paren
0225          * we leave cursor on the blank line, ie
0226          *
0227          * ()
0228          * becomes:
0229          * (
0230          *   |
0231          * )
0232          */
0233 
0234         document.insertText(line, document.firstColumn(line), "\n");
0235         var anchorLine = line+1;
0236         // indent closing anchor
0237         view.setCursorPosition(line, parenIndent);
0238         document.indent(new Range(anchorLine, 0, anchorLine, 1), parenIndent / indentWidth);
0239         // make sure we add spaces to align perfectly on left anchor
0240         var padding =  parenIndent % indentWidth;
0241         if ( padding > 0 ) {
0242             document.insertText(anchorLine,
0243                                 document.fromVirtualColumn(anchorLine, parenIndent - padding),
0244                                 String().fill(' ', padding));
0245         }
0246     } // leading close paren
0247 
0248 
0249     var indent = document.firstVirtualColumn(line);
0250 
0251     // look for line ending with unclosed paren like "func("
0252     // and no args following open paren
0253     var bpos = plineStr.search(/\($/ );
0254     if( bpos != -1 && document.isCode(pline, bpos) ) {
0255         // we have something like
0256         //        func(     <--- pline
0257         //                  <--- line, first arg goes here
0258         // line contains the first arg in a multi line list
0259         // following args will be lined up against this
0260         // if line is not empty, assume user has set it - leave it as it is
0261         dbg( "checkParens: args indented from opening paren" );
0262         indent = document.toVirtualColumn(pline, bpos) + indentWidth;
0263         return indent;
0264     } // trailing open paren
0265 
0266     //  check the line above
0267     //  if it has unclosed paren ==> indent to first arg
0268     //  if it has unbalanced closing paren ==> indent to anchor line
0269     //  other lines, keep indent
0270 
0271     // now look for something like
0272     //        func( arg1,     <--- pline
0273     //                        <--- line
0274     // note: already handled case when there are no args
0275     bpos = checkOpenParen(pline, document.lineLength(pline));
0276     if( bpos != -1 ) {
0277         //dbg( "checkParens: found open paren in ", plineStr.trim() );
0278         bpos = document.nextNonSpaceColumn(pline, bpos+1);
0279         dbg("checkParens: aligning arg to", document.wordAt(pline, bpos), "in", plineStr);
0280         indent = document.toVirtualColumn(pline, bpos );
0281         return indent;
0282     } // trailing opening arg
0283 
0284 
0285     // check nested parens
0286     bpos = checkCloseParen(pline, plineStr.length);
0287     if( bpos != -1 ) {
0288         // we have something like this:
0289         //     )  <-- pline
0290         //        <-- line
0291 
0292         // assert( indent == -1 );
0293         //dbg( "arg list terminated with '" + plineStr[bpos] + "'", " at pos", bpos );
0294         openParen = document.anchor( pline, bpos, plineStr[bpos] );
0295         if (openParen.isValid() ) {
0296             // it's more complicated than it appears:
0297             // we might still be inside another arglist!
0298             // check back from openParen,
0299             // if another open paren is found then
0300             //      indent to next non white space after that
0301             var testLine = openParen.line;
0302             //dbg("checkParens: testing " + document.line(testLine).substr(0,openParen.column) );
0303             bpos = checkOpenParen(testLine, openParen.column);
0304             if( bpos != -1 ) {
0305                 // line above closes parens, still inside another paren level
0306                 //dbg("checkParens: found yet another open paren @ pos", bpos);
0307                 bpos = document.nextNonSpaceColumn(testLine, bpos+1);
0308                 dbg("checkParens: aligning with open paren in line", openParen.line);
0309                 indent = document.toVirtualColumn(testLine, bpos );
0310                 return indent;
0311             } else if( document.anchor(line,0,'(').isValid() ) {
0312                 dbg("checkParens: aligning to args at next outer paren level");
0313                 return document.firstVirtualColumn(testLine );
0314             }
0315         } else {
0316             // dbg("checkOpenParens: line above closes arg list, can't find open paren!");
0317         }
0318     } else {
0319         // line above doesn't close parens, we may or may not be inside parens
0320     }
0321 
0322     // we are inside parens, so align with previous line
0323     dbg("checkParens: inside '(...)', aligning with previous line" );
0324     return document.firstVirtualColumn(pline );
0325 
0326 } // checkParens()
0327 
0328 
0329 // Try to find indent of the parent of the block we're in
0330 // Hunt for a valid line with a lesser or equal indent than prev_lnum has.
0331 // Assume prev_lnum & all other lines before that are correctly indented
0332 // prev_indent = ignore every thing indented >= this, it's assumed to be an inner block level
0333 // prev_lnum   = line to start looking on
0334 // blockstart  = regexp that indicates a possible start of this block
0335 // stop_at     = if non-null, if a matching line is found, gives up!
0336 function MainBlockIndent (prev_indent, prev_lnum, blockstart, stop_at)
0337 {
0338    // can't outdent from col 0
0339    if( prev_indent == 0 ) return 0;
0340 
0341    var pline = prev_lnum;
0342    while( pline >= 0 ) {
0343        var plineStr = document.line(pline).replace(AdaComment, "");
0344        //dbg("MainBlockIndent: plineStr is", plineStr, "stop_at is", stop_at? stop_at.source: "null");
0345        var ind = document.firstVirtualColumn(pline);
0346        if( ind < prev_indent ) {
0347            // we are at an outer level
0348            if( new RegExp(/^\s*/.source + blockstart.source, "i").test(plineStr) ) {
0349                //dbg("MainBlockIndent: found start of block at", plineStr.trim() );
0350                return ind
0351            }
0352            else if( stop_at
0353                &&  new RegExp(/^\s*/.source + stop_at.source, "i").test(plineStr) ) {
0354                dbg("MainBlockIndent: stopped hunting at", plineStr.trim() );
0355                return prev_indent
0356            } // if
0357        } // if
0358 
0359        pline = lastCodeLine( pline );
0360    } // while
0361 
0362     // Fallback - just use prev_indent
0363     dbg("MainBlockIndent: no more code");
0364     return prev_indent;
0365 } //  MainBlockIndent
0366 
0367 
0368 // Try to find indent of the block we're in (and about to complete),
0369 // including handling of nested blocks. Works on the 'end' of a block.
0370 // prev_indent = the previous line's indent
0371 // prev_lnum   = previous line (to start looking on)
0372 // blockstart  = expr. that indicates a possible start of this block
0373 // blockend    = expr. that indicates a possible end of this block
0374 function EndBlockIndent( prev_indent, prev_lnum, blockstart, blockend )
0375 {
0376    // can't outdent from col 0
0377    if( prev_indent == 0 ) return 0;
0378 
0379     var pline = prev_lnum;
0380     var ends = 0;
0381     while( pline >= 0 ) {
0382         var plineStr = document.line(pline).replace(AdaComment, "");
0383         //dbg("EndBlockIndent: ind is", ind, ", plineStr is", plineStr);
0384         if( new RegExp(/^\s*/.source + blockstart.source, "i").test(plineStr) ) {
0385             ind = document.firstVirtualColumn(pline);
0386             if( ends <= 0 ) {
0387                 if( ind < prev_indent ) {
0388                     //dbg("EndBlockIndent: ind is", ind, ", found start of block at '" + plineStr.trim() + "'");
0389                     return ind
0390                 } // if
0391             }
0392             else {
0393                 //dbg("EndBlockIndent: out one level,", plineStr.trim());
0394                 ends = ends - 1;
0395             }
0396         }
0397         else if( new RegExp(/^\s*/.source + blockend.source, "i").test(plineStr) ) {
0398             //dbg("EndBlockIndent: in one level,", plineStr.trim());
0399             ends = ends + 1;
0400         } // if
0401 
0402         pline = lastCodeLine(pline);       // Get previous non-blank/non-comment-only line
0403     } // while
0404 
0405     // Fallback - just use prev_indent
0406     dbg("EndBlockIndent: no more code");
0407     return prev_indent;
0408 
0409 } // EndBlockIndent
0410 
0411 
0412 // Return indent of previous statement-start
0413 // Find start of a (possibly multiline) statement
0414 // (after we've indented due to multi-line statements).
0415 // eg,
0416 //   a := very_long_expression             < -- pline, this determines indent
0417 //            + another_long_expression;   < -- prev_lnum, skip this line
0418 //   s1;                                   < -- indent this current line
0419 // This time, we start searching on the line *before* the one given
0420 // (which is the end of a statement - we want the previous beginning).
0421 // find earlier sibling or parent to determine indent of current statement
0422 function StatementIndent( current_indent, prev_lnum )
0423 {
0424     // can't outdent from col 0
0425     if( current_indent == 0 ) return 0;
0426 
0427     var pline = prev_lnum;
0428     var ind;
0429 
0430    while( pline >= 0 ) {
0431       var ref_lnum = pline;
0432        pline = lastCodeLine( pline );
0433        if( pline < 0 ) {
0434            return current_indent
0435        } // if
0436 
0437        var plineStr = document.line(pline).replace(AdaComment, "");
0438        //if(pline == lastCodeLine( prev_lnum ))
0439        //   dbg("StatementIndent: current_indent is", current_indent, ", plineStr is", plineStr.trim());
0440        // Leave indent alone if our ';' line is part of a ';'-delineated
0441        // aggregate (e.g., procedure args.) or first line after a block start.
0442        if( AdaBlockStart.test(plineStr)
0443            ||  /^\s*end\b/i.test(plineStr)  // this was not in vim script
0444            || /\(\s*$/.test(plineStr) ) {
0445            ind = document.firstVirtualColumn(ref_lnum);
0446            if( ind < current_indent ) {  // this was not in vim script
0447                //dbg("StatementIndent: ind is", ind, ", taking indent of parent", document.line(ref_lnum).trim());
0448                return ind;
0449            } else {
0450                //dbg("StatementIndent: stopped looking at", plineStr.trim());
0451                return current_indent;
0452            } // if
0453        } // if
0454        if( ! unfStmt.test(plineStr) ) {
0455           ind = document.firstVirtualColumn(ref_lnum);
0456            //dbg("StatementIndent: ind is", ind, ", sibling found", plineStr.trim());
0457            if( ind < current_indent ) {
0458                //dbg("StatementIndent: ind is", ind, ", taking indent of sibling", document.line(ref_lnum).trim());
0459                return ind;
0460            } // if
0461        } // if
0462    } // while
0463 
0464    // Fallback - just use current one
0465    return current_indent;
0466 } // StatementIndent()
0467 
0468 
0469 //----------------------
0470 // check what's on the previous line
0471 // return expected indent for next line
0472 function checkPreviousLine( pline, indentWidth )
0473 {
0474     var kpos;      // temp var to hold char positions in search strings
0475     var plineStr = document.line(pline).replace(AdaComment, "");
0476 
0477     // test for multi-line statements by concatenating them
0478     // NOTE: original vim script did not join lines together
0479     if( /^\s*(renames|access|new)\b/i.test(plineStr) ) {
0480         var l = lastCodeLine(pline);
0481         if( l >= 0 ) {
0482             pline = l;
0483             plineStr = document.line(pline).replace(AdaComment, "") + plineStr;
0484             //dbg("checkPreviousLine: combined split statement, pline is", plineStr.trim() );
0485         }
0486     }
0487 
0488     // now do lines that include open & close parens, so
0489     //
0490     //   aaaaaa            <----  new pline, use this to derive indent
0491     //     ( ....
0492     //       ....
0493     //     ) zzzz          <----  original pline
0494     //
0495     // becomes one line:
0496     //
0497     //   aaaaaa     ( ....     ) zzzz
0498     //
0499     kpos = checkCloseParen(pline, plineStr.length);
0500     if( kpos != -1 ) {
0501         dbg("checkPreviousLine: unmatched close paren found");
0502         var r = document.anchor(pline, kpos, ')' );
0503         if(r.isValid()) {
0504             pline = r.line;
0505             plineStr = document.line(pline).replace(AdaComment, "") + plineStr;
0506             //dbg("checkPreviousLine: step 1, pline is", plineStr.trim() );
0507         }
0508     }
0509 
0510     if( /^\s*\(/.test(plineStr) ) {
0511         // open '(' on its own line - use previous indent
0512         pline = lastCodeLine( pline );
0513         plineStr = document.line(pline).replace(AdaComment, "") + plineStr;
0514         //dbg("checkPreviousLine: step 2, pline is", plineStr.trim() );
0515     }
0516 
0517     var ind = document.firstVirtualColumn(pline);
0518 
0519     //dbg("checkPreviousLine: ind is", ind + ", pline is", plineStr.trim() );
0520 
0521     if( AdaBlockStart.test( plineStr ) ||  (/\(\s*$/.test(plineStr)) ) {
0522         //dbg("checkPreviousLine: pline follows block start '", RegExp.$1, "'");
0523         // Check for false matches to AdaBlockStart
0524         var false_match = false;
0525         if( /^\s*(procedure|function|package)\b.*\bis\s+new\b/i.test(plineStr) ) {
0526             //dbg("checkPreviousLine: generic instantiation ignored for now");
0527             false_match = true;
0528         }
0529         else if( (/\)\s*;\s*$/.test(plineStr)) || (/^([^(]*\([^)]*\))*[^(]*;\s*$/.test(plineStr)) ) {
0530             // matches trailing ');' or 'xxx(...) yyy;'
0531             //dbg("checkPreviousLine: forward declaration ignored for now");
0532             false_match = true;
0533         }
0534         // Move indent in
0535         if ( false_match ) {
0536             //dbg("checkPreviousLine: block start ignored");
0537         } else {
0538             dbg("checkPreviousLine: indent after block start");
0539             ind += indentWidth;
0540         } // if
0541     }
0542     else if( /^\s*return\b/i.test(plineStr) ) {
0543         // is it a return statement, or part of a function declaration?
0544         // NOTE: original vim script did not recognise
0545         //       'return' on a new line for function declarations
0546 
0547         var l = lastCodeLine( pline );
0548         while( l >= 0 ) {
0549            lStr = document.line(l);
0550             if( StatementStart.test(lStr) ) {
0551                 dbg("checkPreviousLine: previous line is return statement");
0552                 break;
0553             }
0554             if(/^\s*(with\s*)?function\b/i.test(lStr)) {
0555                 // move indent back to parent 'function'
0556                 dbg("checkPreviousLine: 'return' is function return type");
0557                 ind  = document.firstVirtualColumn(l);
0558                 if( ! /\s*;\s*$/.test(plineStr) ) {
0559                     ind +=  indentWidth;
0560                     //dbg("checkPreviousLine: continue function body");
0561                 }
0562                 break;
0563             }
0564             l = lastCodeLine(l);
0565         } // while
0566     }
0567     else if( /^\s*(case|exception)\b/i.test(plineStr) ) {
0568         dbg("checkPreviousLine: pline follows", RegExp.$1, "indenting");
0569         // NOTE: original vim script indented 2x
0570         ind = ind + indentWidth;
0571     }
0572     else if( /^\s*end\s+record\b/i.test(plineStr) ) {
0573         dbg("checkPreviousLine: current line follows end of record");
0574         // Move indent back to tallying 'type' preceeding the 'record'.
0575         // Allow indent to be equal to 'end record's.
0576         ind = MainBlockIndent( ind+indentWidth, pline, /type\b/, '' );
0577     }
0578     else if( unfStmt.test(plineStr) ) {
0579         dbg("checkPreviousLine: previous line is an unfinished statement");
0580         kpos = plineStr.indexOf(":=");
0581         if( kpos != -1 ) {
0582             kpos = document.nextNonSpaceColumn(pline, kpos+2);
0583         }
0584         if( kpos != -1 ) {
0585             // a := xxx ... unfinished assignment like this
0586             //      ^-- line up here
0587             ind = document.toVirtualColumn(pline, kpos);
0588         } else {
0589             // A statement continuation - move in one
0590             ind += indentWidth
0591         }
0592     }
0593     else if( unfLoop.test(plineStr) ) {
0594         // Multi line for/while ... loop
0595         ind += indentWidth;
0596         dbg("checkPreviousLine: ind is", ind, ", previous line is an unfinished while/for loop");
0597     }
0598     else if( /^(?!\s*end\b).*;\s*$/i.test(plineStr) ) {
0599         // end of statement (but not 'end' )
0600         // start of statement might be on a previous line
0601         //  - try to find current statement-start indent
0602         ind = StatementIndent( ind, pline )
0603         dbg("checkPreviousLine: ind is", ind + ", pline follows end of statement");
0604     } else {
0605         //dbg("checkPreviousLine: previous line does not influence indent");
0606     } // if
0607 
0608     return ind;
0609 
0610 } // checkPreviousLine()
0611 
0612 
0613 // Check current line; search for simplistic matching start-of-block
0614 function indentLine( cline, indentWidth, newLine)
0615 {
0616 
0617     // Find a non-blank code line above the current line.
0618     var pline = lastCodeLine( cline );
0619     if( pline < 0 ) {
0620         if( document.startsWith( cline, "--", true ) ) {
0621             dbg("indentLine: align comment");
0622             pline = document.prevNonEmptyLine( cline - 1 );
0623             if( pline < 0 ) return -2; // first comment line
0624             return -1;                 // other comments follow
0625         }
0626         dbg("indent: first code line");
0627         return 0;
0628     }
0629 
0630     var plineStr = document.line(pline).replace(AdaComment, "");
0631     //dbg("indentLine: plineStr is", plineStr.trim());
0632 
0633     var clineStr = document.line(cline).replace(AdaComment, "");
0634     //dbg("indentLine: cline is", clineStr.trim() );
0635 
0636     var n = checkParens( cline, indentWidth, newLine );
0637     if( n != -1 ) {
0638         //dbg("indentLine: current line aligned inside parens");
0639         return n;
0640     }
0641     if( /^\s*#/.test(clineStr) ) {
0642         dbg( "indentLine: Start of line for ada-pp" );
0643         return 0;
0644     }
0645     if( ((kpos = plineStr.search(/[A-Za-z0-9_.]+(\s+is)?$/)) != -1 )
0646              && (/^\s*\(/.test(clineStr)) ) {
0647         dbg("indentLine: found potential argument list");
0648         return document.toVirtualColumn(pline,kpos) + indentWidth;
0649     }
0650 
0651     // Get default indent from prev. line
0652     var pind = checkPreviousLine(pline, indentWidth);
0653     //dbg( "indentLine: default indent is", pind );
0654 
0655     if( /^\s*(begin|is)\b/i.test(clineStr) ) {
0656         dbg("indentLine: pind is", pind, ", found ", RegExp.$1);
0657         return MainBlockIndent( pind, pline, /(procedure|function|declare|package|task)\b/i, /begin\b/i )
0658     }
0659     if( /^\s*record\b/i.test(clineStr) ) {
0660         dbg("indentLine: line is 'record'");
0661         return MainBlockIndent( pind, pline, /type\b|for\b.*\buse\b/i, '' ) + indentWidth
0662     }
0663     if( /^\s*(else|elsif)\b/i.test(clineStr) ) {
0664         dbg("indentLine: aligning", RegExp.$1, "with corresponding 'if'");
0665         return MainBlockIndent( pind, pline, /if\b/, /^\s*begin\b/ )
0666     }
0667     if( /^\s*when\b/i.test(clineStr) ) {
0668         // Align 'when' one /in/ from matching block start
0669         dbg("indentLine: 'when' clause indented from parent");
0670         return MainBlockIndent( pind, pline, /(case|exception)\b/, /\s*begin\b/ ) + indentWidth;
0671     }
0672     if( /^\s*end\b\s*\bif\b/i.test(clineStr) ) {
0673         // End of if statements
0674         dbg("indentLine: 'end if' aligned to corresponding 'if' statement");
0675         return EndBlockIndent( pind, pline, /if\b/, /end\b\s*\bif\b/ )
0676     }
0677     if( /^\s*end\b\s*\bloop\b/i.test(clineStr) ) {
0678         dbg("indentLine: line is end of loop");
0679         // End of loops
0680         return EndBlockIndent( pind, pline, /((while|for|loop)\b)/, /end\b\s*\bloop\b/ );
0681     }
0682     if( /^\s*end\b\s*\brecord\b/i.test(clineStr) ) {
0683         // End of records
0684         dbg("indentLine: 'end record' aligned to corresponding parent");
0685         return EndBlockIndent( pind, pline, /(type\b.*)?\brecord\b/, /end\b\s*\brecord\b/ );
0686     }
0687     if( /^\s*end\s+procedure\b/i.test(clineStr) ) {
0688         dbg("indentLine: 'end procedure' aligned to corresponding parent");
0689         // End of procedures
0690         // TODO: when does 'end procedure' occur?
0691         // TODO: multiline procedure heading
0692         return EndBlockIndent( pind, pline, /procedure\b.*\bis\b/, /end\b\s*\bprocedure\b/ );
0693     }
0694     if( /^\s*end\b\s*\bcase\b/i.test(clineStr) ) {
0695         // NOTE: original vim script required 'is' on same line
0696         dbg("indentLine: 'end case' aligned to 'case'");
0697         return EndBlockIndent( pind, pline, /case\b[^;]*(\bis\b|$)/i, /end\b\s*\bcase\b/i );
0698     }
0699     if( /^\s*end\b/i.test( clineStr) ) {
0700         // General case for end
0701         dbg("indentLine: 'end' aligned to its parent");
0702         return MainBlockIndent( pind, pline, /(if|while|for|loop|accept|begin|record|case|exception|package)\b/, '' );
0703     }
0704     if( /^\s*exception\b/i.test( clineStr) ) {
0705         dbg("indentLine: 'exception' aligned to corresponding 'begin'");
0706         return MainBlockIndent( pind, pline, /begin\b/, '' );
0707     }
0708     if( /^\s*private\b/i.test( clineStr) ) {
0709         dbg("indentLine: 'private' aligned to corresponding 'package'");
0710         return MainBlockIndent( pind, pline, /package\b/, '' );
0711     }
0712     if( /^\s*<<\w+>>/.test( clineStr) ) {
0713         // NOTE: original vim script did not consider labels
0714         dbg("indentLine: 'label' aligned to corresponding 'begin'");
0715         return MainBlockIndent( pind, pline, /begin\b/, '' );
0716     }
0717     if( /^\s*then\b/i.test( clineStr ) ) {
0718         dbg("indentLine: 'then' aligned to corresponding 'if'");
0719         return MainBlockIndent( pind, pline, /if\b/, /begin\b/ );
0720     }
0721     if( /^\s*loop\b/i.test( clineStr ) ) {
0722         // is it a multiline while/for, or loop on its own?
0723         // search back for while/for without a loop, stop at any statement
0724         dbg("indentLine: 'loop' aligned either to corresponding 'for/while', or on its own");
0725         return MainBlockIndent( pind, pline, unfLoop, StatementStart );
0726     }
0727     if( /^\s*(function|procedure|package)\b/i.test( clineStr ) ) {
0728         dbg("indentLine:", RegExp.$1, " - checking for corresponding 'generic'");
0729         return MainBlockIndent( pind, pline, /generic\b/, /^\s*(function|procedure|package)\b/ );
0730     }
0731     if( /^\s*:=/.test( clineStr ) ) {
0732         dbg("indentLine: continuing assignment");
0733         return pind + indentWidth;
0734     }
0735 
0736     dbg("indentLine: current line has default indent");
0737     return pind
0738 } // indentLine()
0739 
0740 
0741 // Find correct indent of a new line based upon what went before
0742 //
0743 // we assume all previous lines are correctly indented
0744 // indent of current line is to be determined
0745 // arguments:
0746 //              cline           -- current line
0747 //              indentWidth     -- in spaces
0748 //              ch              --  typed character indent
0749 //
0750 // returns the amount of characters (in spaces) to be indented.
0751 // Special return values:
0752 //   -2 = no indent
0753 //   -1 = keep previous indent
0754 
0755 function indent(cline, indentWidth, ch)
0756 {
0757     var t = document.variable("debugMode");
0758     if(t) debugMode = /^(true|on|enabled|1)$/i.test( t );
0759 
0760     dbg("\n------------------------------------ (" + cline + ")");
0761 
0762     // all functions assume valid line nr on entry
0763     if( cline < 0 ) return -2;
0764 
0765 
0766     var newLine = (ch == "\n");
0767 
0768     if( newLine ) {
0769         // cr entered - align the just completed line
0770         view.align(new Range(cline-1, 0, cline-1, 1));
0771     } else if( ch == "" ) {
0772         if( document.firstVirtualColumn(cline) == -1 ) {
0773             dbg("empty line,  zero indent");
0774             return 0;
0775         }
0776     } else if( !reindentTrigger(cline) ) {
0777         return -2;
0778     }
0779 
0780     return indentLine( cline, indentWidth, newLine);
0781 
0782 } // indent()
0783 
0784 ////////////////// end of ada.js //////////////