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;