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

0001 var katescript = {
0002     "name": "Pascal",
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 
0015 /**************************  quick usage notes  **************************
0016 
0017 Automatically handle Pascal indenting while editing code.
0018 
0019 Whenever the return key is pressed,  the indenter correctly indents the 
0020 just finished line, and determines where to start the new line.
0021 
0022 Moderately sensible code is assumed:
0023    comments are expected to be on their own line, or at the end of the line
0024    keywords that start or end a statement should start a line
0025 
0026 The begin keyword can be on the same line as its parent or it can be at the 
0027 start the following line.
0028 If a begin keyword starts a line, by default this indenter aligns it with its 
0029 parent, so your code looks like this for example:
0030                    if x<0 then
0031                    begin           // begin is lined up with the if keyword
0032                        x := 0;
0033 You can indent the begin by setting the document variable cfgIndentBegin.
0034 
0035 Some effort is expended to recognise certain special cases, 
0036 eg there should be no indent after statements like this
0037      if x>0 then begin f(x); writeln end; 
0038 
0039 An if keyword that follows an else on the same line does not cause another 
0040 indent, so multiple if and else conditions line up like this:
0041            if x<0 then begin
0042               ...
0043            end
0044            else if x>0 then begin
0045               ...
0046            end
0047            else begin
0048               ...
0049            end;
0050 
0051 The indenter recognises arguments entered inside brackets over multiple lines. 
0052 The arguments are lined up, and the closing bracket is lined up with either
0053 the arguments or the opening bracket, whichever is appropriate.  
0054 Even nested brackets are handled like this.
0055 
0056 Labels are forced to the left margin and on a line of their own, apart from 
0057 an optional comment.
0058 
0059 These document variables are recognised (default value in parens):
0060   cfgIndentCase (true)     indents elements in a case statement
0061   cfgIndentBegin (0)       indents begin ... end pair the specified nr spaces
0062   cfgAutoInsertStar (true) auto insert '*' at start of new line in (* .. *)
0063   cfgSnapParen (true)      snap '* )' to '*)' at end of comment
0064   debugMode (false)        show debug output in terminal
0065 
0066 See the USER CONFIGURATION below, and code near the start of function indent().
0067 
0068 If, say, the nesting level of a code block changes, the affected code can be 
0069 selected and realigned. 
0070 
0071 You can manually adjust the line following a record, open bracket or 
0072 unfinished condition (ie if or while statement spread over 2 or more lines).
0073 When these lines are realigned your manual adjustment is repected.
0074 In much the same way, comments keep the same relative indent when the code 
0075 around them is realigned as part of the same block.
0076 
0077 The following bugs are known.
0078 Fixing them is possible, but would make the indenter too big, slow and clever.
0079  - 'else' keyword in 'case' statement not recognised.   Use 'otherwise' instead
0080  - comments keep alignment relative to preceding line, but naturally 
0081    should keep alignment relative to following line.   
0082  - procedure/function declarations in 'interface' should be indented
0083 */
0084 
0085 // required katepart js libraries
0086 require ("range.js");
0087 require ("string.js");
0088 
0089 //BEGIN USER CONFIGURATION
0090 var cfgIndentCase = true;         // indent elements in a case statement
0091 var cfgIndentBegin = 0;           // indent begin
0092 var cfgAutoInsertStar = true;     // auto insert '*' in comments
0093 var cfgSnapParen = true;          // snap ')' to '*)' in comments
0094 var debugMode = false;            // send debug output to terminal
0095 //END USER CONFIGURATION
0096 
0097 
0098 var patTrailingSemi = /;\s*(\/\/.*|\{.*\}|\(\*.*\*\))?\s*$/;
0099 var patMatchEnd     = /\b(begin|case|record)\b/i;
0100 var patDeclaration  = /^\s*(program|module|unit|uses|import|implementation|interface|label|const|type|var|function|procedure|operator)\b/i;
0101 var patTrailingBegin = /\bbegin\s*(\/\/.*|\{.*\}|\(\*.*\*\))?\s*$/i;
0102 var patTrailingEnd = /\bend;?\s*(\/\/.*|\{.*\}|\(\*.*\*\))?\s*$/i;
0103 var patCaseValue = /^(\s*[^,:;]+\s*(,\s*[^,:;]+\s*)*):(?!=)/;
0104 var patEndOfStatement = /(;|end)\s*(\/\/.*|\{.*\}|\(\*.*\*\))?\s*$/i;
0105 
0106 function dbg() {
0107     if (debugMode) {
0108         debug.apply(this, arguments);
0109     }
0110 }
0111 
0112 
0113 //BEGIN global variables and functions
0114 
0115 // checking if a line starts with a case value is costly and 
0116 // occurs multiple times per line
0117 // use this to make it faster
0118 var gCaseValue = -1;
0119 
0120 var gIndentWidth = 4;
0121 
0122 // when indenting selected code, 
0123 // we might need to respect relative indents of successsive lines
0124 // gShift remebers how far the previous line shifted
0125 var gShift = 0;
0126 
0127 // testing if current line is inside parens is expensive,
0128 // so need to remember if we are inside parens
0129 // -1 ==> don't know
0130 //  0 ==> not inside
0131 //  1 ==> inside parens
0132 var gInParens = -1;
0133 
0134 //END global variables and functions
0135 
0136 
0137 /**
0138  * recursively search for a corresponding 'repeat'
0139  * line contains keyword 'until', and its code
0140  * line is the start of the search.
0141  * return line number of corresponding 'repeat'
0142  *        -1 if not found 
0143  */
0144 function findMatchingRepeat( line )
0145 {
0146     var pLine = line;
0147 
0148     //dbg("\t starting from >>>>> " + document.line(pLine) + "<<<<<<<<<<");
0149     do {
0150         pLine = lastCodeLine(pLine);
0151         if( pLine < 0 ) return -1;
0152         var pLineStr = document.line( pLine );
0153         if( /^begin\b/i.test( pLineStr ) 
0154             && document.isCode(pLine, 0)) {
0155             dbg("\tfindMatchingRepeat: can't find 'repeat'");
0156             return -1;
0157         }
0158         //dbg("\tfindMatchingRepeat: backing up >>> " + pLineStr + " <<<");
0159 
0160         if( !/\brepeat\b.*\buntil\b/i.test(pLineStr) ) {
0161             var kpos = pLineStr.search( /\brepeat\b/i );
0162             if( kpos != -1 && document.isCode( pLine, kpos ) ) {
0163                 //dbg("\tfindMatchingRepeat: keyword 'repeat' found on line", pLine);
0164                 return pLine;
0165             }
0166             kpos = pLineStr.search( /\buntil\b/i );
0167             if( kpos != -1 && document.isCode( pLine, kpos ) ) {
0168                 //dbg("\tfindMatchingRepeat: keyword 'until' found on line", pLine);
0169                 pLine = findMatchingRepeat( pLine );
0170             }
0171         }
0172     } while( true );
0173 
0174 } // findMatchingRepeat()
0175 
0176 
0177 /**
0178  * recursively search for a corresponding 'if'
0179  * line contains keyword 'else', and its code
0180  * line is the start of the search.
0181  * return line number of corresponding 'if'
0182  *        -1 if not found 
0183  */
0184 function findMatchingIf( line, hasEnd )
0185 {
0186     var pLine = line;
0187 
0188     //dbg("\tstarting from >>>>> " + document.line(pLine) + "<<<<<<<<<<");
0189     do {
0190         // expect line to have one of
0191         //   - end else if
0192         //   - else if
0193         //   - if
0194         if( hasEnd ) {
0195             do {
0196                 pLine = findMatchingKeyword(pLine, /begin/i);
0197                 if( pLine < 0 ) return -1;
0198                 var pLineStr = document.line( pLine );
0199                 hasEnd = /^\s*end\b/i.test(pLineStr) 
0200                     && document.isCode(line, document.firstColumn(pLine) );
0201                 //dbg("\tfinding 'begin' >>>>> " + document.line(pLine));
0202                 //dbg("\thasEnd is", hasEnd ? "true" : "false");
0203             } while( hasEnd )
0204 
0205             // expect begin on its own, or
0206             // begin to trail else line
0207             if( !/^\s*(else|if)\b/i.test(pLineStr) ) {
0208                 pLine = findMatchingKeyword(pLine, /\b(else|if)\b/i );
0209             }
0210         } else {
0211             pLine = findMatchingKeyword(pLine, /\b(else|if)\b/i );
0212         }
0213         if( pLine < 0 ) return -1;
0214         pLineStr = document.line( pLine );
0215 
0216         //dbg("\tfindMatchingIf: finding 'else' >>> " + pLineStr + " <<<");
0217 
0218         hasEnd = /^\s*end\b/i.test( pLineStr );
0219         if( ! /\belse\s+if\b/i.test(pLineStr) ) {
0220             var kpos = pLineStr.search( /\bif\b/i );
0221             if( kpos != -1 && document.isCode( pLine, kpos ) ) {
0222                 //dbg("\tfindMatchingIf: 'if' found in " + pLineStr);
0223                 return pLine;
0224             }
0225             kpos = pLineStr.search( /\belse\b/i );
0226             if( kpos != -1 && document.isCode( pLine, kpos ) ) {
0227                 //dbg("\tfindMatchingIf: 'else' found in " + pLineStr);
0228                 pLine = findMatchingIf( pLine, hasEnd );
0229             }
0230         }
0231     } while( true );
0232 
0233 } // findMatchingIf()
0234 
0235 
0236 /**
0237  * current line contains some end pattern, eg 'end'
0238  * recursively search for a corresponding starting pattern, 
0239  * eg 'begin'/'case'/'record', and return its line number.
0240  * matching keyword is assumed to be on some previous line, not current line
0241  * If not found return -1; 
0242  * line is the start of the search, and is not included
0243  */
0244 function findMatchingKeyword(line, pattern )
0245 {
0246     var testLine = lastCodeLine(line);
0247     if( testLine < 0 ) return -1;
0248     var testStr = document.line( testLine );
0249 
0250     //dbg("\t starting from >>>>> " + document.line(testLine) + "<<<<<<<<<<");
0251      while( true ) {
0252 
0253         //dbg("\tfindMatchingKeyword: backing up >>>> " + testStr + " <<<");
0254 
0255         // if /pattern/ found return
0256         // if end of searchable code return error
0257         // if end found, recursively skip block
0258         // need to consider:
0259         // if c then begin s1; s2 end; // strip these lines
0260         // begin   // normal case
0261         // end     // normal case
0262         // end else if begin // should be OK since /end/ tested last
0263 
0264         var kpos = testStr.search(/\bbegin\b.*\bend\b/i);
0265         if( kpos != -1 && document.isCode( testLine, kpos ) ) {
0266             // need to consider ...
0267             //                  if c1 then begin s1; s2 end
0268             //                  else s3; <-- align this line
0269             testStr = testStr.substr(0,kpos);
0270             //dbg("findMatchingKeyword: begin...end stripped in line " + testStr );
0271         } 
0272 
0273         kpos = testStr.search( pattern );
0274         if( kpos != -1 && document.isCode( testLine, kpos ) ) {
0275             //dbg("\tfindMatchingKeyword: keyword '" + document.wordAt( testLine, kpos == 0? document.firstColumn(testLine): kpos) + "' found on line " + testLine);
0276             return testLine;
0277         }
0278 
0279         if( /^(begin|var|type|function|procedure|operator)\b/i.test(testStr)  
0280             && document.isCode(testLine, 0) ) {
0281             dbg("\tfindMatchingKeyword: stopped searching at", RegExp.$1, "line", testLine );
0282             return -1;
0283         }
0284 
0285         kpos = testStr.search( /\bend\b/i );
0286         if( kpos != -1 && document.isCode( testLine, kpos ) ) {
0287             //dbg("\tfindMatchingKeyword: keyword 'end' found on line " + testLine);
0288             testLine = matchEnd( testLine );
0289             if( testLine == -1 ) return -1;
0290 
0291             var testStr = document.line( testLine );
0292 
0293             // line contains 'begin' (or case or record)
0294             // /pattern/ might be in the first part of the line, eg
0295             // if c then begin  <-- we might be looking for the 'if'
0296             kpos = testStr.search(patMatchEnd);
0297             if(kpos != -1) testStr = testStr.substr(0,kpos);
0298             //dbg("findMatchingKeyword: teststring is:" + testStr );
0299         } else {
0300             testLine = lastCodeLine(testLine);
0301             if( testLine < 0 ) return -1;
0302             testStr = document.line( testLine );
0303         }
0304     } // while
0305 } // findMatchingKeyword()
0306 
0307 
0308 /**
0309 * Search for a start of block, ie corresponding 'begin'/'case'/record'
0310 * line contains 'end'
0311 * return line nr of start of block
0312 * return -1 if not found
0313 * account for variant records
0314 */
0315 function matchEnd( line )
0316 {
0317     var pLine = findMatchingKeyword( line, patMatchEnd );
0318     if( pLine < 0 ) return -1;
0319 
0320     var pLineStr = document.line(pLine);
0321 
0322     if( /\b(record\s+)?case\b/i.test(pLineStr)
0323         && RegExp.$1.length == 0 ) {  // findMatchingKeyword checks this is code
0324         // variant case doesn't count, ignore it
0325         // if 'case' found, go to next statement, look for ':\s*('
0326         // (possibly over multi lines)
0327 
0328         // dbg("matchEnd: found keyword 'case' " + pLineStr );
0329         var testLine = pLine;
0330         do {
0331             testLine = document.nextNonEmptyLine(testLine+1);
0332             if( testLine < 0 ) return -1;
0333         } while( !document.isCode(testLine, document.firstColumn(testLine) ) );
0334         var testStr = document.line(testLine);
0335         //dbg("matchEnd: found case, checking " + testStr);
0336         // variant records have caseValue: (...)
0337         var kpos = testStr.search(/:\s*\((?!\*)/g);
0338         if( kpos != -1 && document.isCode(testLine, kpos) ) {
0339             //dbg("matchEnd: variant case found in line " + testStr );
0340             pLine = findMatchingKeyword( pLine, /\brecord\b/i );
0341             // current line had better contain 'record'
0342             //dbg("matchEnd: parent record is", pLine );
0343         } else {
0344             // case tagType of
0345             //    something:
0346             //        ( field list ....
0347             do {
0348                 testLine = document.nextNonEmptyLine(testLine+1);
0349                 if( testLine < 0 ) return -1;
0350             } while( !document.isCode(testLine, document.firstColumn(testLine) ) );
0351             if( document.firstChar(testLine) == '(' ) {
0352                 pLine = findMatchingKeyword( pLine, /\brecord\b/i );
0353                 // current line had better contain 'record'
0354                 //dbg("matchEnd: parent record is " + pLine );
0355             }
0356         }
0357     }
0358     return pLine;
0359 } // matchEnd()
0360 
0361 
0362 /**
0363  * Find last non-empty line that is code
0364  */
0365 function lastCodeLine(line)
0366 {
0367     do {
0368         line = document.prevNonEmptyLine(--line);
0369         if ( line == -1 ) {
0370             return -1;
0371         }
0372   
0373         // keep looking until we reach code
0374         // skip labels & preprocessor lines
0375     } while( document.firstChar(line) == '#'
0376              || !( document.isCode(line, document.firstColumn(line) )
0377                 || document.isString(line, document.firstColumn(line) )
0378                 || document.isChar(line, document.firstColumn(line) ) )
0379              || /^\w+?\s*:(?!=)/.test(document.line(line)) );
0380 
0381     return line;
0382 } // lastCodeLine()
0383 
0384 
0385 /**
0386  * 'then/do' and/or 'begin' found on the line, 
0387  * (this must be guaranteed by the caller)
0388  * scan back to find the corresponding if/while, etc, 
0389  * allowing it to be on a previous line
0390  * try to find the right line for indent for constructs like:
0391  * if a = b
0392  *     and c = d then begin <- check for 'then', and find 'if',
0393  * then return its line number
0394  * Returns -1, if no success
0395  */
0396 function findStartOfStatement(line)
0397 {
0398     if( line < 0 ) return -1;
0399 
0400     var pLine = line;
0401     var pLineStr = document.line(pLine);
0402     //dbg("\tfindStartOfStatement: starting at " + pLineStr );
0403 
0404     if( /^\s*begin\b/i.test( pLineStr )) {
0405         //dbg( "\tfindStartOfStatement: leading begin found" );
0406     } else {
0407 
0408         // looking for something like 'caseValue: begin'
0409         if( hasCaseValue( pLine) ) return pLine;
0410 
0411         // if there's a 'then' or 'do' 'begin',
0412         // the corresponding 'if', etc could be on a previous line.
0413         var kpos = -1;
0414         while( ((kpos = pLineStr.search(
0415                 /\b(if|while|for|with|else)\b/i )) == -1)
0416                 ||  !document.isCode(pLine, kpos) ) {
0417             pLine = document.prevNonEmptyLine(pLine-1);
0418             if( pLine < 0 ) return -1;
0419             pLineStr = document.line(pLine);
0420             //dbg("\tfindStartOfStatement: going back ... ", pLineStr);
0421             if( /^begin\b/i.test( pLineStr ) 
0422                  && document.isCode(pLine, 0) ) {
0423                 dbg("\tfindStartOfStatement: can't find start of statement");
0424                 return -1;
0425             }
0426         }
0427         //dbg("\tfindStartOfStatement: Found statement: " + pLineStr.trim() );
0428     }
0429     return pLine;
0430 } // findStartOfStatement()
0431 
0432 
0433 /* 
0434  * line is valid and contains a label
0435  * split label part & statement part (if any) into separate lines
0436  * align label on left margin
0437  * no return value 
0438  */
0439 function doLabel( labelLine )
0440 {
0441     if( /^(\s*\w+\s*:)\s*(.*?)?\s*((\/\/|\{|\(\*).*)?$/.test( document.line(labelLine) ) ) {
0442         dbg("doLabel: label is '" + RegExp.$1 + "', statement is '" + RegExp.$2 + "', comment is '" + RegExp.$3 + "'" );
0443 
0444         if( RegExp.$2.length != 0 ) {
0445             document.wrapLine( labelLine, RegExp.$1.length );
0446         }
0447     }
0448     //else dbg("doLabel: couldn't decode line", document.line(labelLine));
0449 
0450 } // doLabel()
0451 
0452 
0453 function hasCaseValue( line )
0454 {
0455     if(line < 0) return false;
0456     if( gCaseValue == line ) {
0457         //dbg("hasCaseValue: line already decoded");
0458         return true;
0459     }
0460 
0461     var lineStr = document.line(line);
0462 
0463     if( /^\s*otherwise\b/i.test(lineStr)
0464         && document.isCode(line, document.firstColumn(line)) ) {
0465         //dbg( "hasCaseValue: 'otherwise' found ", lineStr );
0466         return true;
0467     }
0468     return (decodeColon(line) > 0);
0469 } // hasCaseValue()
0470 
0471 
0472 /*
0473 * check if the line starts with a case value
0474 * return cursor position of 'case' statement if it exists
0475 *        0 when a label is found
0476 *       -1 otherwise
0477 * called when there's a ':' on the current line, and it's code
0478 *
0479 * need to test that the : is a case value, not
0480 *  - inside writeln parens, or 
0481 *  - a var declaration, or 
0482 *  - a function procedure heading
0483 */
0484 function decodeColon( line )
0485 {
0486     if( !document.isCode(line, 0 ) )
0487         return -1;
0488 
0489     var kpos;
0490     var pLine = line;
0491     var pLineStr = document.line(pLine);
0492 
0493     if( !patCaseValue.test(pLineStr)
0494         || !document.isCode(pLine, RegExp.$1.length) ) {
0495         //dbg( "decodeColon: uninteresting line ", pLineStr );
0496         return -1;
0497     }
0498 
0499     //dbg("decodeColon: value is " + RegExp.$1 );
0500     pLineStr = RegExp.$1;
0501     
0502     /* check ':' in statements.   are they case values?
0503      * special cases are ':' inside '(...)'
0504      * eg
0505      *  writeln( a:3 );
0506      *  function( x: real );
0507      * parens over multiple lines already filtered out by tryParens
0508      */
0509 
0510     var Paren = document.anchor(pLine, pLineStr.length, '(' );
0511     if( Paren.isValid() ) {
0512         //dbg("decodeColon: ':' found inside '(...)', not case value");
0513         return -1;
0514     }
0515     
0516     do {
0517         //dbg("decodeColon: scanning back >" + pLineStr);
0518         // we know that pLine is code
0519         if( /(^\s*var|\brecord)\b/i.test(pLineStr) ) {
0520             //dbg("decodeColon: ':' is part of", RegExp.$1, "declaration");
0521             return -1;
0522         } 
0523         if( /^\s*type\b/i.test(pLineStr)  ) {
0524             dbg("decodeColon: ':' can't find parent, giving up after finding keyword 'type' at line", pLine);
0525             return -1;
0526         } 
0527 
0528         // jump over 'end' to ensure we don't find an inner 'case' statement
0529         if( /^\s*end\b/i.test(pLineStr) ) {
0530             //dbg("decodeColon: skipping inner block");
0531             pLine = matchEnd( pLine );
0532             if(pLine < 0) return -1;
0533             //dbg("decodeColon: back to previous block");
0534             // line contains 'begin' (or case)
0535             // /case/ might be in the first part of the line, eg
0536             //  c then begin  <-- we might be looking for the 'if'
0537             pLineStr = document.line( pLine );
0538             kpos = pLineStr.search(patMatchEnd);
0539             if(kpos != -1)
0540                 pLineStr = pLineStr.substr(0,kpos);
0541             //dbg("decodeColon: retesting line:" + pLineStr );
0542         } else if( (kpos = pLineStr.search( /\)\s*;/ )) != -1 
0543                    && document.isCode( pLine, kpos )  ) {
0544             //dbg("\tdecodeColon: close paren found on line " + pLineStr);
0545             var openParen = document.anchor( pLine, kpos, ')' );
0546             if( !openParen.isValid() ) {
0547                 dbg("decodeColon: mismatched parens");
0548                 return -1;
0549             }
0550             // go to open paren, get substr
0551             pLine = openParen.line;
0552             pLineStr = document.line( pLine ).substr(0,openParen.column);
0553             //dbg("\tdecodeColon: skipping over parens to line " + pLine + pLineStr);
0554         } else {
0555             if( /^\s*until\b/i.test(pLineStr) ) {
0556                 pLine = findMatchingRepeat( pLine );
0557             }
0558             pLine = lastCodeLine(pLine);
0559             if( pLine < 0 ) {
0560                 dbg("decodeColon: no more lines");
0561                 return -1;
0562             }
0563             pLineStr = document.line(pLine);
0564         }
0565 
0566         // no more code to search
0567         // we are looking at a label if we reach start of a block
0568         if( /^\s*(begin\b(?!.*\bend)|repeat\b(?!.*\buntil)|otherwise)\b/i.test(pLineStr) 
0569             || patTrailingBegin.test(pLineStr) ) {
0570             //dbg("decodeColon: found a label");
0571             return 0;
0572         }
0573 
0574         kpos = pLineStr.search(/\bcase\b/i); 
0575         if( kpos != -1 && document.isCode(pLine,kpos) ) {
0576             //dbg("decodeColon: found a case value");
0577             gCaseValue = line;
0578 
0579             var indent = document.toVirtualColumn(pLine, kpos);
0580             if (indent != -1) {
0581                 if (cfgIndentCase)  indent += gIndentWidth;
0582             }
0583             return indent;
0584         } 
0585     } while( pLine > 0 );
0586 
0587     dbg("decodeColon: unexpected return");
0588     return -1;
0589 } // decodeColon()
0590 
0591 
0592 /**
0593  * Comment checking.
0594  * If the previous line begins with a "(*" or a "* ", then
0595  * return its leading white spaces + ' *' + the white spaces after the *
0596  * return: filler string or null, if not in a comment
0597  */
0598 function tryComment(line, newLine)
0599 {
0600     var p = document.firstColumn(line);
0601     if( !document.isComment(line, p<0? 0: p) ) {
0602         //dbg( "tryComment: line " + line + " is not a comment" );
0603         return -1;
0604     }
0605 
0606     var startComment = Cursor.invalid();
0607     var adjust = 0;
0608     if( document.startsWith(line, "*)", true ) ) {
0609         //dbg("find the opening (* and line up with it");
0610         startComment = document.rfind(line, 0, "(*");
0611         adjust = 1;
0612     }
0613     else if( document.startsWith(line,"}", true ) ) {
0614         //dbg("find the opening { and line up with it");
0615         startComment = document.rfind(line, 0, "{");
0616     }
0617 
0618     if( startComment.isValid() ) {
0619         var indent = document.toVirtualColumn(startComment);
0620         if (indent != -1) { 
0621             dbg("tryComment: aligned to start of comment at line", startComment.line);
0622             return indent+adjust;
0623         }
0624     }
0625 
0626     var pline = document.prevNonEmptyLine(line - 1);
0627     if( pline < 0 )  {
0628         dbg("tryComment: keep existing indent");
0629         return -2;
0630     }
0631 
0632     var indent = -1;
0633 
0634     if( pline == line - 1 ) {   // no empty line in between
0635 
0636         var plineStr = document.line(pline);
0637         var startPos = plineStr.indexOf("(*");
0638         if( startPos != -1 
0639             && !plineStr.contains("*)")
0640             && document.isComment(pline, startPos) ) {
0641 
0642             indent = document.toVirtualColumn(pline,startPos); // line up with '(*'
0643             indent += 1; // line up with '*'
0644             dbg("tryComment: " + line + " follows start of comment");
0645         } else if( document.firstChar(pline) == '*' 
0646                    && ! plineStr.contains("*)") ) {
0647             indent = document.firstVirtualColumn(pline);
0648         }
0649     }
0650 
0651     // now indent != -1 if there is a '*' to follow
0652 
0653     if( indent != -1 && newLine && cfgAutoInsertStar ) {
0654         dbg("tryComment: leading '*' inserted in comment line");
0655         document.insertText(line, view.cursorPosition().column, '*');
0656         if (!document.isSpace(line, document.firstColumn(line) + 1))
0657             document.insertText(line, document.firstColumn(line) + 1, ' ');
0658     } else if( indent == -1 || document.firstChar(line) != '*' ) {
0659         indent = document.firstVirtualColumn(line);
0660         if( indent >= 0 ) {
0661             dbg("tryComment: keeping relative indent of existing comment");
0662             if( indent + gShift >= 0) indent += gShift;
0663         } else {
0664             dbg("tryComment: new comment line follows previous one");
0665             indent = document.firstVirtualColumn(pline);
0666         }
0667     } else {
0668         dbg("tryComment: aligning leading '*' in comments");
0669     }
0670 
0671     return indent;
0672 
0673 } // tryComment()
0674 
0675 
0676 /**
0677  * Check for a keyword that determines the indent of the current line
0678  * some keywords directly determine the indent of the current line
0679  *  (eg 'program' => zero indent)
0680  * some keywords need to align with another keyword further up the code
0681  *  (eg until needs to line up with its matching repeat)
0682  * for some lines,  the indent depends on a keyword on the previous line
0683  *  (eg a line that follws if, else, while, const, var, case etc...) 
0684  */
0685 function tryKeywords(line)
0686 {
0687     var kpos;
0688 
0689     if(document.firstChar(line) == '#') {
0690         dbg("tryKeywords: preprocessor line found: zero indent");
0691         return 0;
0692     }
0693 
0694     var lineStr = document.line(line);
0695     var isBegin = false;
0696 
0697     if( document.isCode( line, 0 ) ) {
0698 
0699         // these declarations always go on the left margin
0700         if( patDeclaration.test(lineStr) ) {
0701             dbg("tryKeywords: zero indent for '" + RegExp.$1 + "'" );
0702             return 0;
0703         }
0704 
0705         // when begin starts the line, determine indent later
0706         isBegin = /^\s*begin\b/i.test(lineStr);
0707 
0708         var indent = -1;
0709         var refLine = -1;
0710 
0711         /**
0712          * Check if the current line is end of a code block
0713          * (ie it contains 'else', 'end' or 'until')
0714          * return indent of corresponding start of block
0715          * If not found return -1; 
0716          * start of the search at line.
0717          */
0718         var found = true;
0719         if( /^\s*end\b/i.test(lineStr) ) {
0720             //dbg("tryKeywords: 'end' found, hunting for start of block");
0721             refLine = matchEnd( line );
0722             var refLineStr = document.line(refLine);
0723             // dbg("refLine is ", refLineStr );
0724             // if end matched 'begin', return indent of corresponding statement
0725             kpos = refLineStr.search(/\bbegin\b/i);
0726             if( kpos != -1 && document.isCode(refLine, kpos) ) {
0727                 refLine = findStartOfStatement(refLine);
0728                 //dbg("tryKeywords: keyword 'begin' found, start of statement is", refLineStr);
0729             } else if( /\brecord\b/i.test(refLineStr) ) {
0730                 //align to record, not start of line
0731                 refLine = document.nextNonEmptyLine(refLine+1);
0732                 dbg("tryKeywords: outdenting 'end' relative to", document.line(refLine));
0733                 return document.firstVirtualColumn(refLine) - gIndentWidth;
0734             }
0735         } else if( /^\s*until\b/i.test(lineStr) ) {
0736             //dbg( "tryKeywords: found keyword 'until' in " + lineStr );
0737             refLine = findMatchingRepeat( line );
0738         } else if( /^\s*(end)?\s*else\b/i.test(lineStr) ) {
0739             //dbg("tryKeywords: found 'else', hunting back for matching 'if'");
0740             refLine = findMatchingIf( line, RegExp.$1 );
0741         } else {
0742             found = false;
0743         }
0744 
0745         if( found ) {
0746             if( refLine < 0 ) return -1;
0747             // we must line up with statement at refLine
0748             dbg("tryKeywords: align to start of block at", document.line(refLine));
0749             indent = document.firstVirtualColumn(refLine);
0750             if( hasCaseValue( refLine ) ) indent += gIndentWidth;
0751             return indent;
0752         }
0753 
0754         if( /^\s*otherwise/i.test(lineStr) ) {
0755             refLine = findMatchingKeyword( line, /\bcase\b/i );
0756             if( refLine != -1 ) {
0757                 indent = document.firstVirtualColumn(refLine);
0758                 if (indent != -1) {
0759                     if (cfgIndentCase)  indent += gIndentWidth;
0760                     dbg("tryKeywords: 'otherwise' aligned to 'case' in line " + refLine);
0761                 }
0762                 return indent;
0763             }
0764         } else if( /^\s*(then|do)\b/i.test(lineStr) ) {
0765             dbg("tryKeywords: '" + RegExp.$1 + "' aligned with start of statement" );
0766             refLine = findStartOfStatement( line );
0767             if( refLine >= 0 )
0768                 return document.firstVirtualColumn(refLine);
0769         }
0770         else if( /^\s*of/i.test(lineStr) ) {
0771             // check leading 'of' in array declaration
0772             //dbg("tryKeywords: found leading 'of'");
0773             refline = lastCodeLine(line);
0774             if(refline >= 0) {
0775                 reflineStr = document.line(refline);
0776                 kpos = reflineStr.search(/\]\s*(\/\/.*|\{.*\}|\(\*.*\*\))?\s*$/);
0777                 if( kpos != -1 ) {
0778                     //dbg("tryKeywords: found trailing ']'");
0779                     var c = document.anchor(refline, kpos, ']');
0780                     if( c.isValid() ) {
0781                         reflineStr = document.line(c.line).substring(0,c.column);
0782                         //dbg("tryKeywords: start of array is", reflineStr.trim());
0783                         kpos = reflineStr.search(/\barray\s*$/i);
0784                         if( kpos != -1 ) {
0785                             dbg("tryKeywords: indenting leading 'of' in array declaration");
0786                             return document.toVirtualColumn(c.line, kpos) + gIndentWidth;
0787                         }
0788                     }
0789                 }
0790             }
0791         }
0792         else {
0793             //dbg("tryKeywords: checking for a case value: " + lineStr);
0794             indent = decodeColon( line );
0795             if( indent != -1 ) {
0796                 if(indent == 0) 
0797                     doLabel(line);
0798                 else 
0799                     dbg("tryKeywords: case value at line", line, "aligned to earlier 'case'");
0800                 return indent;  // maybe it's a label
0801             }
0802         }
0803     } // is code
0804 
0805     //** now see if previous line determines how to indent line
0806 
0807     var pline = lastCodeLine(line);
0808     if (pline < 0)  return -1;
0809     var plineStr = document.line(pline);
0810     //dbg("tryKeywords: test prev line", plineStr);
0811 
0812     //dbg("tryKeywords: checking main begin ...");
0813     if( isBegin && patTrailingSemi.test( plineStr ) ) {
0814         // the previous line has trailing ';' this is the main begin
0815         // it goes on the left margin
0816         dbg("tryKeywords: main begin at line " + line);
0817         return 0;
0818     }
0819 
0820     // check array declarations
0821     //dbg("tryKeywords: checking arrays ....");
0822     kpos = plineStr.search(/\]\s*of\s*(\/\/.*|\{.*\}|\(\*.*\*\))?\s*$/i) ;
0823     if( kpos != -1 && document.isCode(pline, kpos) ) {
0824         var c = document.anchor(pline, kpos, ']');
0825         if( c.isValid() ) {
0826             reflineStr = document.line(c.line);
0827             kpos = reflineStr.search(/\barray\s*\[/i);
0828             if( kpos != -1 ) {
0829                 dbg("tryKeywords: indenting from unfinished array declaration");
0830                 return document.toVirtualColumn(c.line, kpos) + gIndentWidth;
0831             }
0832         }
0833     }
0834 
0835     //dbg("tryKeywords: checking record ...");
0836     kpos = plineStr.search(/\brecord\b/i);
0837     if( kpos != -1 && document.isCode(pline, kpos) ) {
0838         // need to skip single line records
0839         if( !patTrailingEnd.test(plineStr) ) {
0840             indent = document.firstVirtualColumn(line);
0841             if( indent + gShift > document.firstVirtualColumn(pline) ) {
0842                 dbg("tryKeywords: keeping indent following 'record'");
0843                 return indent + gShift;
0844             } else {
0845                 dbg("tryKeywords: indenting after 'record'");
0846                 return document.toVirtualColumn(pline,kpos) + gIndentWidth;
0847             }
0848         }
0849     }
0850 
0851     //dbg("tryKeywords: checking unfinished type decs ...");
0852     if( /^(type)?\s*\b\w+\s*=\s*(\/\/.*|\{.*|\(\*.*)?$/.test(plineStr) ) {
0853         indent = document.toVirtualColumn(pline,
0854                        document.nextNonSpaceColumn(pline, RegExp.$1.length) );
0855         dbg("tryKeywords: indenting after 'type'" );
0856         return indent + gIndentWidth;        
0857     }
0858 
0859     if( /^(uses|import|label|type|const|var|case)\b/i.test(plineStr) ) {
0860         // indent after these
0861         dbg("tryKeywords: indent after", RegExp.$1);
0862         return gIndentWidth;
0863     }
0864 
0865     // ignore repeat s1 until c;
0866     if( /^\s*repeat\b/i.test(plineStr) 
0867         && document.isCode(pline, document.firstColumn(line)) ) {
0868         kpos = plineStr.search(/\buntil\b/i);
0869         if( kpos == -1 || !document.isCode(pline, kpos) ) {
0870             dbg( "tryKeywords: indenting after 'repeat'" );
0871             return document.firstVirtualColumn(pline) + gIndentWidth;
0872         }
0873     }
0874         
0875     if( /^\s*otherwise\b/i.test(plineStr) 
0876         && document.isCode(pline, document.firstColumn(line)) ) {
0877         dbg( "tryKeywords: indenting after 'otherwise'" );
0878         return document.firstVirtualColumn(pline) + gIndentWidth;
0879     }
0880         
0881     // after case keyword indent sb ready for a case value
0882     kpos = plineStr.search(/^\s*case\b/i);
0883     if( kpos != -1 && document.isCode(pline, kpos) ) {
0884         indent = document.firstVirtualColumn(pline);
0885         dbg("tryKeywords: new case value");
0886         return (cfgIndentCase)?  indent + gIndentWidth: indent;
0887     }
0888 
0889     //dbg("tryKeywords: checking multiline condition ...");
0890     if( /^\s*(while|for|with|(else\s*)?if)\b/i.test(plineStr)
0891         && document.isCode(pline, document.firstColumn(pline)) ) {
0892         // found if, etc.
0893         // if there's no then/do it's a multiline condition
0894         // if the line is empty indent by one,
0895         // otherwise keep existing indent
0896         var keyword = RegExp.$1; // only used for debug
0897         kpos = plineStr.search(/\b(then|do)\b/i);
0898         if( kpos == -1 || ! document.isCode(pline, kpos) ) {
0899             indent = document.firstVirtualColumn(line);
0900             if( indent + gShift > document.firstVirtualColumn(pline) ) {
0901                 dbg("tryKeywords: keeping relative indent following '" +keyword+ "'");
0902                 return indent + gShift;
0903             } else {
0904                 dbg("tryKeywords: indenting after '" + keyword + "'");
0905                 return document.firstVirtualColumn(pline)+2*gIndentWidth;
0906             }
0907         }
0908     } else {
0909         // there's no 'if', 'while', etc => test for 'then' or 'do'
0910         // then go back to the corresponding 'if' while'/'for'/'with'
0911         // assume case/with do not spread over multiple lines
0912         //dbg("tryKeywords: checking then/do ...");
0913         kpos = plineStr.search( /\b(then|do)(\s*begin)?\s*(\/\/.*|\{.*|\(\*.*)?$/i );
0914         if ( kpos != -1 && document.isCode( pline, kpos)) {
0915             //dbg("tryKeywords: found '" + RegExp.$1 + "', look for 'if/while/for/with'");
0916             pline = findStartOfStatement( pline );
0917             plineStr = document.line(pline);
0918         }
0919     }
0920     
0921     var xind = 0;   // extra indent
0922     //dbg("tryKeywords: checking case value ...");
0923     if( hasCaseValue( pline ) ) {
0924         plineStr = plineStr.substr( plineStr.indexOf(":")+1 );
0925         //dbg("tryKeywords: plineStr is '" + plineStr +"'" );
0926         kpos = plineStr.search(patEndOfStatement);
0927         if( kpos == -1 || !document.isCode(pline, kpos) ) {
0928             dbg("tryKeywords: extra indent after case value");
0929             xind = gIndentWidth;
0930         }
0931     }
0932 
0933     // link the line to 'begin' on the line above
0934     if( /^\s*begin\b/i.test(plineStr)  && document.isCode(pline, 0) ) {
0935         if( document.firstColumn(pline) == 0 ) {
0936             dbg( "tryKeywords: indent after main 'begin'" );
0937             return document.firstVirtualColumn(pline) + gIndentWidth;
0938         }
0939         kpos = plineStr.search(/\bend\b/i);
0940         if( kpos == -1 || !document.isCode(pline, kpos) ) {
0941             dbg( "tryKeywords: indent after leading 'begin'" );
0942             return document.firstVirtualColumn(pline) + xind + gIndentWidth - cfgIndentBegin;
0943         }
0944     }
0945 
0946     //dbg("tryKeywords: checking control statements ...");
0947     if( /^\s*(if|for|while|with|(end\s*)?\else)\b/i.test(plineStr) 
0948         || ((xind > 0) && (/^\s*begin\b/i.test(plineStr)) ) ) { // xind>0 ==> case value 
0949         var keyword = RegExp.$1; // only used for debug
0950         // indent after any of these, 
0951         // unless it's a single line statement
0952         if( !patEndOfStatement.test(plineStr) ) {
0953             kpos = lineStr.search(/end\s*;?\s*(\/\/.*|\{.*\}|\(\*.*\*\))?\s*$/i) ;
0954             if( isBegin && (kpos == -1 || !document.isCode(line, kpos)) ) {
0955                 //dbg("begin found - not cancelled by trailing end");
0956                 dbg("tryKeywords: indent", cfgIndentBegin, "extra spaces for begin after " + keyword );
0957                 xind += cfgIndentBegin;
0958             } else {
0959                 dbg("tryKeywords: indent after", keyword);
0960                 xind += gIndentWidth;
0961             }
0962             return  xind + document.firstVirtualColumn(pline);
0963        } 
0964     }
0965 
0966     //casevalue or  if/while/etc
0967     if( xind > 0 ) {
0968         return  xind + document.firstVirtualColumn(pline);
0969     }
0970     
0971     //dbg("tryKeywords: check unfinished := ...");
0972     kpos = plineStr.search(/:=[^;]*(\/\/.*|\{.*\}|\(\*.*\*\))?\s*$/i);
0973     if( kpos != -1 && document.isCode(pline, kpos) ) {
0974         indent = document.firstVirtualColumn(line);
0975         if( indent + gShift > document.firstVirtualColumn(pline) ) {
0976             dbg("tryKeywords: keeping indent following ':='");
0977             return indent + gShift;
0978         } else {
0979             var pos = document.nextNonSpaceColumn(pline, kpos+2);
0980             dbg( "tryKeywords: indent after unfinished assignment" );
0981             if( pos != -1 )
0982                 return document.toVirtualColumn(pline, pos);
0983             else
0984                 return document.toVirtualColumn(pline, kpos) + gIndentWidth;
0985         }
0986     }
0987 
0988     return -1;
0989 } // tryKeywords()
0990 
0991 
0992 /* test if line has an unclosed opening paren '('
0993  * line is document line
0994  * n is nr chars to test, starting at pos 0
0995  * return index of unmatched '(', or
0996  *        -1 if no unmatched '(' 
0997  */
0998 function checkOpenParen( line, n )
0999 {
1000     var nest = 0;
1001     var i = n;
1002     while( nest <= 0 && --i >= 0 ) {
1003         if( document.isCode(line, i) ) {
1004             var c = document.charAt( line, i);
1005             if( c == ')' )      nest--;
1006             else if( c == '(' ) nest++;
1007             else if( c == ']' ) nest--;
1008             else if( c == '[' ) nest++;
1009         }
1010     }
1011     //dbg("string " + document.line(line) + (i==-1? " has matched open parens": " has unclosed paren at pos " + i ));
1012     return i;
1013 }
1014 
1015 /* test if line has an unmatched close paren
1016  * line is document line
1017  * n is nr chars to test, starting at pos 0
1018  * return index of rightmost unclosed paren
1019  *        -1 if no unmatched paren 
1020  */
1021 function checkCloseParen( line, n )
1022 {
1023     var nest = 0;
1024     var i = -1;
1025     var bpos = -1;
1026     while( ++i < n ) {
1027         if( document.isCode(line, i) ) {
1028             var c = document.charAt( line, i);
1029             if( c == ')' )      nest++;
1030             else if( c == '(' ) nest--;
1031             else if( c == ']' ) nest++;
1032             else if( c == '[' ) nest--;
1033         }
1034         if( nest > 0 ) {
1035             nest = 0;
1036             bpos = i;
1037         }
1038     }
1039     //dbg("string " + document.line(line) + (bpos==-1? " has matched close parens": " has unbalanced paren at pos " + bpos) );
1040     return bpos;
1041 }
1042 
1043 
1044 /**
1045  * line up args inside parens
1046  * if the opening line has no args, line up with first arg on next line
1047  * ignore trailing comments.
1048  * opening line ==> figure out indent based on pos of first arg
1049  * subsequent args ==> maintain indent
1050  *       ');' ==> align closing paren
1051  */
1052     //  check the line above
1053     //  if it has unclosed paren ==> indent to first arg
1054     //  other lines, keep indent
1055 
1056 function tryParens(line, newLine)
1057 {
1058     var pline = lastCodeLine(line);    // note: pline is code
1059     if (pline < 0) return -1;
1060     var plineStr = document.line(pline);
1061     //dbg("tryParens: gInParens is", gInParens);
1062  
1063     /**
1064      * first, handle the case where the new line starts with ')' or ']'
1065      * find out whether we pressed return in something like () or [] and 
1066      * indent properly, so
1067      * []
1068      * becomes:
1069      * [
1070      *   |
1071      * ]
1072      *
1073      */
1074     var char = document.firstChar(line);
1075     if ( (char == ')' || char == ']')
1076             &&  document.isCode( line, document.firstColumn(line) ) ) {
1077         //dbg("tryParens - leading close " + char + " on current line");
1078         gInParens = -1;
1079         var openParen = document.anchor(line, 0, char);
1080         if( !openParen.isValid() ) {
1081             // nothing found, continue with other cases
1082             dbg("tryParens: open paren not found");
1083             return -1;
1084         }
1085         var indent = document.toVirtualColumn(openParen);
1086 
1087         //dbg("tryParens: found '" + document.charAt(openParen) + "' at " + openParen.toString() );
1088 
1089         // look for something like
1090         //        argn     <--- pline
1091         //        );       <--- line, trailing close paren
1092         // the trailing close paren lines up with the args or the open paren,
1093         // whichever is the leftmost
1094         if( pline > openParen.line ) {
1095             var argIndent = document.firstVirtualColumn(pline);
1096             if( argIndent > 0 && argIndent < indent) { 
1097                 indent = argIndent;
1098                 dbg("tryParens: align trailing '" + char + "' with arg list");
1099             }
1100             else {
1101                 dbg("tryParens: align trailing '" + char + "' with open paren");
1102             }
1103         }
1104 
1105         // look for previous line ending with unclosed paren like "func(" {comment}
1106         // and no args following open paren
1107         var bpos = plineStr.search(/[,(\[]\s*(\/\/.*|\{.*\}|\(\*.*\*\))?\s*$/ );
1108         // open line for a new arg if following a trailing ','
1109         // or if this is the first arg, ie parens match
1110         var openLine = false;
1111         if( bpos != -1 && document.isCode(pline, bpos)) {
1112             var lastChar = plineStr[bpos];
1113             openLine =   ( lastChar == ',' ) || 
1114                 ( lastChar == '(' && char == ')' ) ||
1115                 ( lastChar == '[' && char == ']' );
1116             //dbg("lastChar is '" + lastChar + "', openLine is " + (openLine? "true": "false") );
1117         }
1118 
1119         // just align brackets, not args
1120         if( !newLine || !openLine ) {
1121             return indent;
1122         }
1123 
1124         dbg("tryParens: opening line for new argument");
1125         //dbg(lastChar == ','? "following trailing ',' ": "parens match");
1126         /* on entry, we have one of 2 scenarios:
1127          *    arg,  <--- pline, , ==> new arg to be inserted
1128          *    )     <--- line
1129          * or
1130          *    xxx(  <--- pline
1131          *    )     <--- line
1132          *
1133          * in both cases, we need to open a line for the new arg and 
1134          * place right paren in same column as the opening paren
1135          * we leave cursor on the blank line
1136          */
1137         document.insertText(line, document.firstColumn(line), "\n");
1138         var anchorLine = line+1;
1139         // indent closing paren 
1140         view.setCursorPosition(line, indent);
1141         document.indent(new Range(anchorLine, 0, anchorLine, 1), indent / gIndentWidth);
1142         // make sure we add spaces to align perfectly on left paren
1143         var padding =  indent % gIndentWidth;
1144         if ( padding > 0 ) {
1145             document.insertText(anchorLine, 
1146                                 document.fromVirtualColumn(anchorLine, indent - padding),
1147                                 String().fill(' ', padding));
1148         }
1149     } // leading close paren
1150 
1151 
1152     // now look for line ending with unclosed paren like "func(" {comment}
1153     // and no args following open paren
1154     var bpos = plineStr.search(/[(\[]\s*(\/\/.*|\{.*\}|\(\*.*\*\))?\s*$/ );
1155     if( bpos != -1 && document.isCode(pline, bpos) ) {
1156         // we have something like
1157         //        func(     <--- pline
1158         //                  <--- line, first arg goes here
1159         // line contains the first arg in a multi line list
1160         // other args should be lined up against this
1161         // if line is not empty, assume user has set it - leave it as it is
1162         gInParens = 1;
1163         var bChar = plineStr[bpos];
1164         //dbg("tryParens: - trailing open " + char + " on previous line");
1165         indent = document.firstVirtualColumn(line);
1166             if( indent + gShift > document.firstVirtualColumn(pline) ) {
1167             dbg("tryParens: keep relative indent following '" + bChar + "'");
1168             indent += gShift;
1169         } else {
1170             dbg( "tryParens: args indented from opening paren" );
1171             indent = document.toVirtualColumn(pline, bpos) + gIndentWidth;
1172         }
1173         return indent;
1174     } // trailing open paren
1175 
1176     // next, look for something like
1177     //        func( arg1,     <--- pline
1178     //                        <--- line
1179     // note: if open paren is last char we would not be here
1180     bpos = checkOpenParen(pline, document.lineLength(pline));
1181     if( bpos != -1 ) {
1182         //dbg( "tryParens: found open paren '" + plineStr[bpos] + "' and trailing arg" );
1183         bpos = document.nextNonSpaceColumn(pline, bpos+1);
1184         gInParens = 1;
1185         dbg("tryParens: aligning arg to", document.wordAt(pline, bpos), "in", plineStr);
1186         return document.toVirtualColumn(pline, bpos );
1187     } // trailing opening arg
1188 
1189     // next step, check for close paren,  but nested inside outer paren
1190     bpos = checkCloseParen(pline, plineStr.length);
1191     if( bpos != -1 ) {
1192         // we have something like this:
1193         //     )  <-- pline
1194         //        <-- line
1195 
1196         // assert( indent == -1 );
1197         //dbg( "arg list terminated with '" + plineStr[bpos] + "'", " at pos", bpos );
1198         var openParen = document.anchor( pline, bpos, plineStr[bpos] );
1199         if (openParen.isValid() ) {
1200             // it's more complicated than it appears:
1201             // we might still be inside another arglist!
1202             // check back from openParen, 
1203             // if another open paren is found then
1204             //      indent to next non white space after that
1205             var testLine = openParen.line;
1206             //dbg("tryParens: testing " + document.line(testLine).substr(0,openParen.column) );
1207             bpos = checkOpenParen(testLine, openParen.column);
1208             if( bpos != -1 ) {
1209                 // line above closes parens, still inside another paren level
1210                 //dbg("tryParens: found yet another open paren @ pos", bpos);
1211                 bpos = document.nextNonSpaceColumn(testLine, bpos+1);
1212                 //dbg("tryParens: aligning with open paren in line", openParen.line);
1213                 indent = document.toVirtualColumn(testLine, bpos );
1214                 return indent;
1215             } else if( document.anchor(line,0,'(').isValid() 
1216                        || document.anchor(line,0,'[').isValid() ) {
1217                 dbg("tryParens: aligning to args at next outer paren level");
1218                 return document.firstVirtualColumn(testLine );
1219             } else {
1220                 //dbg("tryParens: no longer inside parens");
1221                 gInParens = 0;
1222             }
1223         } else {
1224             gInParens = -1;
1225             // line above closes list, can't find open paren!
1226         }
1227     } else {
1228         // line above doesn't close parens, we may or may not be inside parens
1229     }
1230 
1231     // finally, check if inside parens, 
1232     // if so, align with previous line
1233     // eliminates a few checks later on
1234     if( gInParens == -1 ) {
1235         gInParens = 0;
1236         openParen = document.anchor(line,0,'(');
1237         if( openParen.isValid() && document.isCode(openParen) ) {
1238             dbg("tryParens: inside '(...' at", openParen.line, ", aligning with previous line" );
1239             gInParens = 1;
1240         }
1241 
1242         openParen = document.anchor(line,0,'[');
1243         if( openParen.isValid() && document.isCode(openParen) ) {
1244             dbg("tryParens: inside '[...]', aligning with previous line" );
1245             gInParens = 1;
1246         }
1247     }
1248 
1249     if( gInParens == 1 )
1250         return document.firstVirtualColumn(pline );
1251 
1252     return -1;
1253 
1254 } // tryParens()
1255 
1256 
1257 /**
1258  * hunt back to find statement at the same level, or parent
1259  * align to same as sibling, or add one indent from parent
1260  * line is start of the search
1261  * return indent of line
1262  *        never return -1, follow previous line if indent not determined
1263  * note:
1264  * the first child of a keyword has alreday been indented by tryKeywords()
1265  */
1266 function tryStatement(line)
1267 {
1268     var plineStr;
1269     var indent = -1;
1270     var pline = lastCodeLine(line);
1271     if(pline<0) {
1272         //dbg("tryStatement: no code to align to");
1273         return 0;
1274     }
1275 
1276     var defaultIndent = document.firstVirtualColumn(pline);
1277     plineStr = document.line(pline);
1278 
1279     var kpos = plineStr.search(patTrailingSemi);
1280 
1281     if( kpos == -1 || !document.isCode(pline, kpos) ) {
1282         dbg("tryStatement: following previous line");
1283         return defaultIndent;
1284     }
1285 
1286     var prevInd = defaultIndent;
1287     //dbg("tryStatement: plineStr is " + plineStr );
1288 
1289     do {
1290         if( /^\s*end\b/i.test(plineStr) ) {
1291             //dbg("tryStatement: end found, skipping block");
1292             pline = matchEnd( pline );
1293             if( pline < 0 ) return defaultIndent;
1294             plineStr = document.line(pline);
1295             //dbg("tryStatement: start of block is " + plineStr);
1296 
1297             kpos = plineStr.search(patMatchEnd);
1298             if(kpos != -1)
1299                 plineStr = plineStr.substr(0,kpos);
1300             //dbg("tryStatement: start of block is " + plineStr );
1301             // prevInd for 'begin' must be same as for 'end'
1302         } else if( /^\s*until\b/i.test(plineStr) ) {
1303             //dbg("tryStatement: aligning after 'until'" );
1304             pline = findMatchingRepeat(pline);
1305             if( pline < 0 ) return defaultIndent;
1306             plineStr = document.line(pline);
1307 
1308             kpos = plineStr.search(/\brepeat\b/i);
1309             if( kpos != -1 )
1310                 plineStr = plineStr.substr(0,kpos);
1311             //dbg("tryStatement(r): plineStr is " + plineStr );
1312             // prevInd for 'repeat' must be same as for 'until'
1313         } else {
1314             // keep indent of old line in case we need to line up with it
1315             prevInd = document.firstVirtualColumn(pline);
1316             pline = lastCodeLine( pline );
1317             if(pline<0) return defaultIndent;
1318             plineStr = document.line(pline);
1319             kpos = plineStr.search(patTrailingSemi);
1320             if( kpos != -1 ) {
1321                 var c = document.anchor(pline, kpos, ')');
1322                 if( !c.isValid() ) {
1323                     dbg("tryStatement: aligning with statement after ';'");
1324                     return prevInd;
1325                 }
1326             }
1327         }
1328 
1329         //dbg("tryStatement: plineStr is " + plineStr );
1330 
1331         // need record to have priority over type & var, so test it first
1332         kpos = plineStr.search(/\brecord\b/i);
1333         if( kpos != -1 
1334             && document.isCode(pline, kpos) ) {
1335             if( patTrailingEnd.test(plineStr) ) {
1336                 //dbg( "tryStatement: skipping single line record " );
1337             } else {
1338                 dbg( "tryStatement: indenting after record " );
1339                 return prevInd;  // line up with first member
1340             }
1341         }
1342 
1343         // patDeclaration has leading whitespace
1344         if( /^(uses|import|label|const|type|var)\b/i.test(plineStr) 
1345             && document.isCode(pline, 0) ) {
1346             // align after the keyword
1347             dbg("tryStatement: indenting after " + RegExp.$1 );
1348             return gIndentWidth;
1349         } 
1350 
1351         // ignore repeat s1 until c;
1352          if( /^\s*(repeat\b(?!.*\buntil\b)|otherwise)\b/i.test(plineStr) ) {
1353             dbg("tryStatement: indent after '"+ RegExp.$1 + "' in line", pline);
1354             return prevInd;
1355         }
1356 
1357         // statements like 'if c then begin s1 end' should just pass thru
1358         kpos = plineStr.search(patTrailingBegin);
1359         if( kpos != -1 && document.isCode(pline, kpos) ) {
1360             dbg( "tryStatement: indenting from begin" );
1361             return prevInd;
1362         }
1363 
1364         // after case keyword indent sb ready for a case value
1365         kpos = plineStr.search(/^\s*case\b/i);
1366         if( kpos != -1 && document.isCode(pline, kpos) ) {
1367             dbg("tryStatement: new case value");
1368             return prevInd;
1369         }
1370 
1371     } while( true );
1372 
1373 } // tryStatement()
1374 
1375 
1376 // specifies the characters which should trigger indent, beside the default '\n'
1377 triggerCharacters = " \t)]}#;";
1378 
1379 // possible outdent for lines that match this regexp
1380 var PascalReIndent = /^\s*((end|const|type|var|begin|until|function|procedure|operator|else|otherwise|\w+\s*:)\s+|[#\)\]\}]|end;)(.*)$/;
1381 
1382 // check if the trigger characters are in the right context,
1383 // otherwise running the indenter might be annoying to the user
1384 function reindentTrigger(line)
1385 {
1386     var res = PascalReIndent.exec(document.line(line));
1387     dbg("reindentTrigger: checking line, found", ((res && res[3] == "" )? "'"+ (res[2]?res[2]:res[1])+"'": "nothing") );
1388     return ( res && (res[3] == "" ));
1389 } // reindentTrigger
1390 
1391 
1392 function processChar(line, c)
1393 {
1394    var cursor = view.cursorPosition();
1395     if (!cursor)
1396         return -2;
1397 
1398     var column = cursor.column;
1399     var lastPos = document.lastColumn(line);
1400 
1401     //dbg("processChar: lastPos:", lastPos, ", column..:", column);
1402 
1403     if (cfgSnapParen 
1404                && c == ')'
1405                && lastPos == column - 1) {
1406         // try to snap the string "* )" to "*)"
1407         var plineStr = document.line(line);
1408         if( /^(\s*\*)\s+\)\s*$/.test(plineStr) ) {
1409             plineStr = RegExp.$1 + c;
1410             document.editBegin();
1411             document.removeLine(line);
1412             document.insertLine(line, plineStr);
1413             view.setCursorPosition(line, plineStr.length);
1414             document.editEnd();
1415         }
1416     }
1417     return -2;
1418 } // processChar()
1419 
1420 
1421 // indent() returns the amount of characters (in spaces) to be indented.
1422 // arguments: line, indentwidth in spaces, typed character indent
1423 // Special indent() return values:
1424 //   -2 = no indent
1425 //   -1 = keep previous indent
1426 
1427 function indent(line, indentWidth, ch)
1428 {
1429     // all functions assume valid line nr on entry 
1430     if( line < 0 ) return -1;
1431 
1432     var t = document.variable("debugMode");
1433     if(t) debugMode = /^(true|on|enabled|1)$/i.test( t );
1434 
1435     dbg("\n------------------------------------ (" + line + ")");
1436 
1437     t = document.variable("cfgIndentCase");
1438     if(t) cfgIndentCase = /^(true|on|enabled|1)$/i.test( t );
1439     t = document.variable("cfgIndentBegin");
1440     if(/^[0-9]+$/.test(t)) cfgIndentBegin = parseInt(t);
1441 
1442     t = document.variable("cfgAutoInsertStar");
1443     if(t) cfgAutoInsertStar = /^(true|on|enabled|1)$/i.test( t );
1444     t = document.variable("cfgSnapParen");
1445     if(t) cfgSnapParen = /^(true|on|enabled|1)$/i.test( t );
1446 
1447     gIndentWidth = indentWidth;
1448     gCaseValue = -99;
1449 
1450     var newLine = (ch == "\n");
1451 
1452     if( ch == "" ) {
1453         // align Only
1454         if( document.firstVirtualColumn(line) == -1 ) {
1455             dbg("empty line,  zero indent");
1456             return 0;
1457         }
1458     } else if (ch == '\n') {
1459         // cr entered - align the just completed line
1460         view.align(new Range(line-1, 0, line-1, 1));
1461     } else if( !reindentTrigger(line) ) {
1462         return processChar(line, ch);
1463     }
1464 
1465     var oldIndent = document.firstVirtualColumn(line);
1466     var sel = view.selection();
1467     if( !sel.isValid() || line == 0 || !sel.containsLine(line-1) ) {
1468         gShift = 0;
1469         gInParens=-1;
1470     }
1471 
1472     var filler = tryComment(line, newLine);
1473 
1474     if (filler == -1)
1475         filler = tryParens(line, newLine);
1476 
1477     if (filler == -1)
1478         filler = tryKeywords(line);
1479 
1480     if (filler == -1)
1481         filler = tryStatement(line);
1482 
1483     if( sel.isValid() ) {
1484         //dbg("shifting this line", gShift, "places." );
1485         gShift = filler - oldIndent;
1486    }
1487 
1488     return filler;
1489 }
1490 
1491 // kate: space-indent on; indent-width 4; replace-tabs on;