/* Terminology: I call soft context any context that does not need to be closed. Soft contexts are implicitely closed by any '}' or ';' character encountered excepted for the 'if' soft context if the next context after a ';' or '}' is a 'else' For example : the indentation generated by a '{' context is voided only by the associated character '}'. So '{' is a hard context. The indentation generated by a 'for' context is not voided by any special context. In the java grammar, these statements are closed by the end of the following instruction (;) or group of instruction (}) */ // All the possible contexts while parsing the file int CONTEXT_BRACE = 0; int CONTEXT_PARENTHESIS = 1; int CONTEXT_LINECOMMENT = 2; // Not Used int CONTEXT_MLCOMMENT = 3; int CONTEXT_CHAR = 4; int CONTEXT_STRING = 5; int CONTEXT_BRACKETS = 6; int CONTEXT_IF = 7; // Beginning of the soft contexts int CONTEXT_ELSE = 8; int CONTEXT_FOR = 9; int CONTEXT_CATCH = 10; int CONTEXT_RETURN = 12; int CONTEXT_WHILE = 13; // End of the soft context int CONTEXT_DO = 14; // Checked only to distinguish between "while () {}" and "do {} while ()" int CONTEXT_SWITCH = 15; int CONTEXT_TRY = 16; char INDENT_CHAR = ' '; // Special tab to be used before any "case" and "default" statement in a switch int CASE_TAB = 2; /* Sets the number of spaces to be inserted when a specified context For example : ----------- { toto } ----------- will be indented : ----------- { toto } ----------- and ----------- ( toto ) ----------- will be indented : ----------- ( toto ) ----------- */ int contextValue(int c) { switch (c) { case CONTEXT_BRACE : return 2; case CONTEXT_PARENTHESIS : return 4; case CONTEXT_BRACKETS : return 4; case CONTEXT_IF : return 2; case CONTEXT_ELSE : return 2; case CONTEXT_FOR : return 2; case CONTEXT_RETURN : return 7; case CONTEXT_WHILE : return 2; case CONTEXT_SWITCH : return 2; case CONTEXT_CATCH : return 2; } return 0; } // Provides a String representation for each context String getStr(int c) { switch (c) { case CONTEXT_BRACE : return "{}"; case CONTEXT_PARENTHESIS : return "()"; case CONTEXT_LINECOMMENT : return "//"; case CONTEXT_MLCOMMENT : return "/* */"; case CONTEXT_CHAR : return "''"; case CONTEXT_STRING : return "\"\""; case CONTEXT_BRACKETS : return "[]"; case CONTEXT_IF : return "if"; case CONTEXT_ELSE : return "else"; case CONTEXT_FOR : return "for"; case CONTEXT_CATCH : return "catch"; case CONTEXT_RETURN : return "return"; case CONTEXT_WHILE : return "while"; case CONTEXT_DO : return "do"; case CONTEXT_SWITCH : return "switch"; case CONTEXT_TRY : return "try"; default : return "?"; } } // ---------------------------------------------------------------------------- // Stack Management // ---------------------------------------------------------------------------- // When a ';' or a '}' is encountered, all the "soft" contexts are removed from the context. // Starting with the first "if" context encountered by the remove procedure, thay are stored // in a temp stack (contextStackTemp) // Then is the next keyword is a 'else', they are pushed back and the last 'if' is replaced // with a 'else' context int pushBackTempStackWithElse() { int res=0; while (contextStackTemp[0]>0) { res+=contextValue(contextStack[contextStack[0]=contextStack[0]+1] = contextStackTemp[contextStackTemp[0]--]); } contextStack[contextStack[0]] = CONTEXT_ELSE; return res; } // Removes the context at the top of the stack. Checks that the context provided matches. // Special treatment: // If the next context in the stack is a switch, removes it also, and pop the shiftForSwitch stack. int popContext(int c) { if (contextStack[0]==0) throw new Exception ("Invalid syntax in buffer, cannot continue parsing, line " + (lineNumber+1) + " char " + carNumber); if (contextStack[contextStack[0]]!=c) { throw new Exception ("Invalid syntax in buffer, cannot continue parsing, line " + (lineNumber+1) + " char " + carNumber + "\nExpected: End Of Context " + getStr(contextStack[contextStack[0]]) + ", provided " + getStr(c)); } contextStack[0]=contextStack[0]-1; if (c==CONTEXT_BRACE) if (isContext(CONTEXT_SWITCH)) { shiftForSwitch[0]--; popContext(CONTEXT_SWITCH); return -(contextValue(c) + contextValue(CONTEXT_SWITCH)); } return -(contextValue(c)); } // Pushes the context specified in the current context stack // Special treatment: // If the context is a else, retrieve the asociated "soft" contexts in the stack // If the context is a while, checks that the top context is not a do. If so, removes it // and prevent the while from being pushed // If the context is a switch, pushes the current tab for the switch keyword in the shiftForSwitch stack // and add to this the CASE_TAB value. all the case and default context to be found are then // absolutely indented to this new value int pushContext(int c) { if (contextStack[0] >= contextStack.length-1) throw new Exception ("Stack Overflow"); if (c==CONTEXT_ELSE) return pushBackTempStackWithElse(); else { if (c==CONTEXT_WHILE) { if (isContext(CONTEXT_DO)) { popContext(CONTEXT_DO); return 0; } } contextStack[contextStack[0]=contextStack[0]+1] = c; if (c==CONTEXT_SWITCH) { shiftForSwitch[++shiftForSwitch[0]] = oldTabs+CASE_TAB; } } return contextValue(c); } // Returns true us the context provided is the current top-level context. boolean isContext(int c) { if (contextStack[0]<=0) return false; return contextStack[contextStack[0]] == c; } // Returns true if the current top-level context is a soft context boolean isSoftContext() { if (contextStack[0]<=0) return false; return (contextStack[contextStack[0]] >= CONTEXT_IF) && (contextStack[contextStack[0]] <= CONTEXT_WHILE); } // Returns true if the current context is a multi-line comment, a String or a char // ie: We don't have to interpret what's inside boolean isComment() { return isContext(CONTEXT_LINECOMMENT) || isContext(CONTEXT_MLCOMMENT) || isContext(CONTEXT_CHAR) || isContext(CONTEXT_STRING); } // return the current top-level context int getContext() { if (contextStack[0]<=0) return -1; return contextStack[contextStack[0]]; } // Gives a String representing the current context stack String getContextString(int[] stack) { String res="["; for (i=1 ; i<=stack[0] ; i++) res+=getStr(stack[i]) + ","; return res+"]"; } // Relieve the stack from all the soft contexts // Stores them in the contextStackTemp // (If the next context is 'else' we may want to retrieve the last 'if' and all its previous contexts) int popSoftExpression() { int res=0; boolean pushBack=false; contextStackTemp[0]=0; while (isSoftContext()) { if (isContext(CONTEXT_IF)) pushBack=true; if (pushBack) contextStackTemp[++contextStackTemp[0]]=getContext(); res+=popContext(getContext()); } return res; } // ---------------------------------------------------------------------------- // Char utilities // ---------------------------------------------------------------------------- // Checks if the char provided is alphanumeric (ie: valid in any java identifier) boolean isValidID(char c) { return (c>='a' && c<='z') ||(c>='A' && c<='Z') ||(c>='0' && c<='9') || c=='$' || c=='_'; } // Verify that 'keyword' is present at the 'offset' position in 'line' // Checks also that it is not surrounded by any alphanumeric char boolean lookForKeyword(String line, int offset, String keyword) { if (offset<0) return false; if (offset+keyword.length() > line.length()) return false; for (i=0 ; i0) if (isValidID(line.charAt(offset-1))) return false; if (offset+keyword.length()=0) { if (line.charAt(index--)=='\\') nbbs++; else break; } return (nbbs%2) == 0; } // Line processor void processLine(String line, int[]res) { // Used to detect comments (// and /*) char lastOne; // res[0] represents the offset to be applied to THIS line res[0]=0; // res[1] represents the offset to be applied for the next lines res[1]=0; // res[2] represents the absolute offset to be applied to THIS line // Used only in the "case" special processing res[2]=-1; // Is true until any non-blank character is encountered boolean first=true; // Process all the characters in the line for (carNumber=0 ; carNumber 0 && line.charAt(carNumber-1)=='*') // '*/' encountered if (isContext(CONTEXT_MLCOMMENT)) popContext(CONTEXT_MLCOMMENT); break; case '\'': if (charApplicable(line,carNumber)) if (isContext(CONTEXT_CHAR)) popContext(CONTEXT_CHAR); // Closes the '' section break; case '"': if (charApplicable(line,carNumber)) if (isContext(CONTEXT_STRING)) popContext(CONTEXT_STRING); // Closes the "" section break; } } else { switch (cc) { case '{': if (first) { // If this brace is the first character in line boolean sc = isSoftContext(); if (sc) { // If last context is soft, cancels the brace indentation res[0]-=contextValue(getContext()); res[1]+=contextValue(getContext()); pushContext(CONTEXT_BRACE); } else res[1]+=pushContext(CONTEXT_BRACE); // Indent the next lines } else { // This brace is not the first character in line if (isSoftContext()) // If last context is soft, ignores the brace indentation pushContext(CONTEXT_BRACE); else res[1]+=pushContext(CONTEXT_BRACE); // Otherwise increment the indent of the next lines } break; case '}': popSoftExpression(); // Removes any soft expressions res[(first)?0:1]+=popContext(CONTEXT_BRACE); // unindent the next/current line if (isSoftContext()) { // if last one is soft, cancels the indent removal // (The soft context is coing to handle that) res[1]+=contextValue(getContext()); res[(first)?0:1]+=contextValue(CONTEXT_BRACE); res[(first)?0:1]-=contextValue(getContext()); } res[1]+=popSoftExpression(); break; case '(': res[1]+=pushContext(CONTEXT_PARENTHESIS); break; case ')': res[(first)?0:1]+=popContext(CONTEXT_PARENTHESIS); break; case '[': res[1]+=pushContext(CONTEXT_BRACKETS); break; case ']': res[(first)?0:1]+=popContext(CONTEXT_BRACKETS); break; case '/': if (lastOne == '/') { return; // Stop interpreting the line if a // is encountered } break; case '*': if (lastOne == '/') pushContext(CONTEXT_MLCOMMENT); break; case '\'': pushContext(CONTEXT_CHAR); break; case '"': pushContext(CONTEXT_STRING); break; case ';' : res[1]+=popSoftExpression(); break; case ' ': case '\t': // A blank character has been encountered blank=true; break; case 'e': if (lookForKeyword(line,carNumber-4,"while")) res[1]+=pushContext(CONTEXT_WHILE); else if (lookForKeyword(line,carNumber,"else")) { int i = pushContext(CONTEXT_ELSE); //Macros.message(view,"else, cost: " +i + " // " + res[0] + " " + res[1] + " " + first); res[(first)?0:1]+=i-contextValue(CONTEXT_ELSE); res[1]+=contextValue(CONTEXT_ELSE); //Macros.message(view,"else2 // " + res[0] + " " + res[1]); } else if (lookForKeyword(line,carNumber-3,"case")) { res[2]=shiftForSwitch[shiftForSwitch[0]]; } break; case 'r': if (lookForKeyword(line,carNumber-2,"for")) res[1]+=pushContext(CONTEXT_FOR); break; case 'f': if (lookForKeyword(line,carNumber-1,"if")) res[1]+=pushContext(CONTEXT_IF); break; case 'o': if (lookForKeyword(line,carNumber-1,"do")) res[1]+=pushContext(CONTEXT_DO); break; case 'n': if (lookForKeyword(line,carNumber-5,"return")) res[1]+=pushContext(CONTEXT_RETURN); break; case 'y': if (lookForKeyword(line,carNumber-2,"try")) res[1]+=pushContext(CONTEXT_TRY); break; case 'h': if (lookForKeyword(line,carNumber-5,"switch")) res[1]+=pushContext(CONTEXT_SWITCH); else if (lookForKeyword(line,carNumber-4,"catch")) { res[1]-=popContext(CONTEXT_TRY); res[1]+=pushContext(CONTEXT_CATCH); } break; case 't': if (lookForKeyword(line,carNumber-6,"default")) { res[2]=shiftForSwitch[shiftForSwitch[0]]; } break; case 'y': // Macros.message(view,"line " + lineNumber + " context " + getContextString(contextStack) + " contextTemp " + getContextString(contextStackTemp)); default : } } lastOne=cc; // If the character processed is not a blank character then the next one will not be the first one... if (!blank) first=false; } // Terminate any unterminated context that is line-bounded if (isContext(CONTEXT_STRING)) popContext(CONTEXT_STRING); if (isContext(CONTEXT_CHAR)) popContext(CONTEXT_CHAR); if (isContext(CONTEXT_LINECOMMENT)) popContext(CONTEXT_LINECOMMENT); if (first) res[2]=-2; //Macros.message(view,"line " + (lineNumber+1) + " context " + getContextString(contextStack) + " // " + res[0] + " " + res[1]); } // Potition in the buffer int lineNumber=0,carNumber=0; // stacks int[]contextStack = new int[1024]; int[]contextStackTemp = new int[1024]; int[]shiftForSwitch = new int[1024]; contextStack[0]=0; contextStackTemp[0]=0; shiftForSwitch[0]=0; // Total Number of lines int lines = textArea.getBuffer().getLineCount(); // indent for current line and indent for next line int oldTabs; int tabs = 0; // parameter for the processLine method which needs to return 3 types of int int []offsets = new int[3]; // Line where the caret was when the macro was launched int lastLine = textArea.getCaretLine(); int[] linesToProcess = textArea.getSelectedLines(); if (linesToProcess!=null) if (linesToProcess.length > 1) { String firstLine = textArea.getBuffer().getLineText(linesToProcess[0]); for (int temp=0 ; temp=0) oldTabs=offsets[2]; textArea.setCaretPosition(textArea.getLineStartOffset(lineNumber)); textArea.goToStartOfLine(false); if (offsets[2] == -2) textArea.goToEndOfLine(true); else textArea.goToStartOfWhiteSpace(true); if (oldTabs==0){ textArea.userInput(' '); textArea.backspace(); } else for (int i=0 ; i