Source Code Cross Referenced for JsFormatter.java in  » IDE-Netbeans » javascript » org » netbeans » modules » javascript » editing » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » IDE Netbeans » javascript » org.netbeans.modules.javascript.editing 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003:         *
0004:         * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005:         *
0006:         * The contents of this file are subject to the terms of either the GNU
0007:         * General Public License Version 2 only ("GPL") or the Common
0008:         * Development and Distribution License("CDDL") (collectively, the
0009:         * "License"). You may not use this file except in compliance with the
0010:         * License. You can obtain a copy of the License at
0011:         * http://www.netbeans.org/cddl-gplv2.html
0012:         * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013:         * specific language governing permissions and limitations under the
0014:         * License.  When distributing the software, include this License Header
0015:         * Notice in each file and include the License file at
0016:         * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
0017:         * particular file as subject to the "Classpath" exception as provided
0018:         * by Sun in the GPL Version 2 section of the License file that
0019:         * accompanied this code. If applicable, add the following below the
0020:         * License Header, with the fields enclosed by brackets [] replaced by
0021:         * your own identifying information:
0022:         * "Portions Copyrighted [year] [name of copyright owner]"
0023:         *
0024:         * Contributor(s):
0025:         *
0026:         * The Original Software is NetBeans. The Initial Developer of the Original
0027:         * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028:         * Microsystems, Inc. All Rights Reserved.
0029:         *
0030:         * If you wish your version of this file to be governed by only the CDDL
0031:         * or only the GPL Version 2, indicate your decision by adding
0032:         * "[Contributor] elects to include this software in this distribution
0033:         * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034:         * single choice of license, a recipient has the option to distribute
0035:         * your version of this file under either the CDDL, the GPL Version 2 or
0036:         * to extend the choice of license to its licensees as provided above.
0037:         * However, if you add GPL Version 2 code and therefore, elected the GPL
0038:         * Version 2 license, then the option applies only if the new code is
0039:         * made subject to such option by the copyright holder.
0040:         */
0041:        package org.netbeans.modules.javascript.editing;
0042:
0043:        import java.util.ArrayList;
0044:        import java.util.HashSet;
0045:        import java.util.List;
0046:        import java.util.Set;
0047:        import java.util.Stack;
0048:        import javax.swing.text.BadLocationException;
0049:        import javax.swing.text.Document;
0050:        import org.netbeans.api.lexer.Token;
0051:        import org.netbeans.api.lexer.TokenHierarchy;
0052:        import org.netbeans.api.lexer.TokenId;
0053:        import org.netbeans.api.lexer.TokenSequence;
0054:        import org.netbeans.editor.BaseDocument;
0055:        import org.netbeans.editor.Utilities;
0056:        import org.netbeans.modules.gsf.api.CompilationInfo;
0057:        import org.netbeans.modules.gsf.api.OffsetRange;
0058:        import org.netbeans.modules.javascript.editing.lexer.LexUtilities;
0059:        import org.netbeans.modules.javascript.editing.lexer.JsTokenId;
0060:        import org.openide.util.Exceptions;
0061:
0062:        /**
0063:         * Formatting and indentation for JavaScript
0064:         * 
0065:         * @todo Handle JSP
0066:         * @todo Handle if-blocks that don't have an associated block - just indents the next statement
0067:         * @todo Handle block comments - similar to multiline literals but should be indented by a relative amount
0068:         * @todo Handle XML/E4X content
0069:         * @todo Use the Context.modifyIndent() method to change line indents instead of
0070:         *   the current document/formatter method
0071:         * @todo Indent block comments properly: See if the first char is "*", and if so, indent it one extra
0072:         *   char somehow such that it lines up with the * in /*
0073:         *
0074:         * @author Tor Norbye
0075:         */
0076:        public class JsFormatter implements 
0077:                org.netbeans.modules.gsf.api.Formatter {
0078:            private boolean embeddedJavaScript;
0079:            private CodeStyle codeStyle;
0080:            private int rightMarginOverride = -1;
0081:
0082:            /**
0083:             * <p>
0084:             * Stack describing indentation of blocks defined by '{', '[' and blocks
0085:             * with missing optional curly braces '{'. See also getBracketBalanceDelta()
0086:             * </p>
0087:             * For example:
0088:             * <pre>
0089:             * if (true)        // [ StackItem[block=true] ]
0090:             *   if (true) {    // [ StackItem[block=true], StackItem[block=false] ]
0091:             *     if (true)    // [ StackItem[block=true], StackItem[block=false], StackItem[block=true] ]
0092:             *       foo();     // [ StackItem[block=true], StackItem[block=false] ]
0093:             *     bar();       // [ StackItem[block=true], StackItem[block=false] ]
0094:             *   }              // [ StackItem[block=true] ]
0095:             * fooBar();        // [ ]
0096:             * </pre>
0097:             */
0098:            private Stack<StackItem> stack = new Stack<StackItem>();
0099:
0100:            public JsFormatter() {
0101:                this .codeStyle = CodeStyle.getDefault(null);
0102:            }
0103:
0104:            public JsFormatter(CodeStyle codeStyle, int rightMarginOverride) {
0105:                assert codeStyle != null;
0106:                this .codeStyle = codeStyle;
0107:                this .rightMarginOverride = rightMarginOverride;
0108:            }
0109:
0110:            public boolean needsParserResult() {
0111:                return true;
0112:            }
0113:
0114:            public void reformat(Document document, int startOffset,
0115:                    int endOffset, CompilationInfo info) {
0116:
0117:                reindent(document, startOffset, endOffset, info, false);
0118:            }
0119:
0120:            public void reindent(Document document, int startOffset,
0121:                    int endOffset) {
0122:                reindent(document, startOffset, endOffset, null, true);
0123:            }
0124:
0125:            public int indentSize() {
0126:                return codeStyle.getIndentSize();
0127:            }
0128:
0129:            public int hangingIndentSize() {
0130:                return codeStyle.getContinuationIndentSize();
0131:            }
0132:
0133:            /** Compute the initial balance of brackets at the given offset. */
0134:            private int getFormatStableStart(BaseDocument doc, int offset) {
0135:                TokenSequence<? extends JsTokenId> ts = LexUtilities
0136:                        .getJsTokenSequence(doc, offset);
0137:                if (ts == null) {
0138:                    return 0;
0139:                }
0140:
0141:                ts.move(offset);
0142:
0143:                if (!ts.movePrevious()) {
0144:                    return 0;
0145:                }
0146:
0147:                // Look backwards to find a suitable context - a class, module or method definition
0148:                // which we will assume is properly indented and balanced
0149:                do {
0150:                    Token<? extends JsTokenId> token = ts.token();
0151:                    TokenId id = token.id();
0152:
0153:                    if (id == JsTokenId.FUNCTION) {
0154:                        return ts.offset();
0155:                    }
0156:                } while (ts.movePrevious());
0157:
0158:                if (embeddedJavaScript && !ts.movePrevious()) {
0159:                    // I may have moved to the front of an embedded JavaScript area, e.g. in
0160:                    // an attribute or in a <script> tag. If this is the end of the line,
0161:                    // go to the next line instead since the reindent code will go to the beginning
0162:                    // of the stable formatting start.
0163:                    int sequenceBegin = ts.offset();
0164:                    try {
0165:                        int lineTextEnd = Utilities.getRowLastNonWhite(doc,
0166:                                sequenceBegin);
0167:                        if (lineTextEnd == -1 || sequenceBegin > lineTextEnd) {
0168:                            return Math.min(doc.getLength(), Utilities
0169:                                    .getRowEnd(doc, sequenceBegin) + 1);
0170:                        }
0171:
0172:                    } catch (BadLocationException ble) {
0173:                        Exceptions.printStackTrace(ble);
0174:                    }
0175:                }
0176:
0177:                return ts.offset();
0178:            }
0179:
0180:            private static int getPreviousLineFirstNonWhiteOffset(
0181:                    BaseDocument doc, int offset) {
0182:                int offsetPrevLine = -1;
0183:                try {
0184:                    if (offset > -1) {
0185:                        int o = Utilities.getRowStart(doc, offset);
0186:                        if (o > 0) {
0187:                            offsetPrevLine = Utilities.getRowStart(doc, o - 1);
0188:                            if (offsetPrevLine > -1) {
0189:                                offsetPrevLine = Utilities.getRowFirstNonWhite(
0190:                                        doc, offsetPrevLine);
0191:                            }
0192:                        }
0193:                    }
0194:                } catch (BadLocationException ex) {
0195:                    Exceptions.printStackTrace(ex);
0196:                }
0197:                return offsetPrevLine;
0198:            }
0199:
0200:            private int getBracketBalanceDelta(TokenId id) {
0201:                if (id == JsTokenId.LPAREN || id == JsTokenId.LBRACKET) {
0202:                    return 1;
0203:                } else if (id == JsTokenId.RPAREN || id == JsTokenId.RBRACKET) {
0204:                    return -1;
0205:                }
0206:                return 0;
0207:            }
0208:
0209:            private int getTokenBalanceDelta(TokenId id, BaseDocument doc,
0210:                    TokenSequence<? extends JsTokenId> ts) {
0211:                try {
0212:                    if (id == JsTokenId.LBRACKET || id == JsTokenId.LBRACE) {
0213:                        // block with braces, just record it to stack and return 1
0214:                        stack.push(new StackItem(false, new OffsetRange(ts
0215:                                .offset(), ts.offset())));
0216:                        return 1;
0217:                    } else if (id == JsTokenId.RBRACKET
0218:                            || id == JsTokenId.RBRACE) {
0219:                        /*
0220:                         * End of braces block.
0221:                         * If we are not on same line where block started, try to push 
0222:                         * all braceless blocks from stack and decrease indent for them,
0223:                         * otherwise just decrese indent by 1.
0224:                         * For example:
0225:                         * if (true)
0226:                         *   if (true)
0227:                         *     if (true)
0228:                         *       foo();     // we should decrease indent by 3 levels
0229:                         * 
0230:                         * but:
0231:                         * if (true)
0232:                         *   if (true)
0233:                         *     if (map[0]) // at ']' we should decrease only by 1
0234:                         *       foo();
0235:                         */
0236:                        int delta = -1;
0237:                        StackItem lastPop = stack.empty() ? null : stack.pop();
0238:                        if (lastPop != null
0239:                                && Utilities.getLineOffset(doc, lastPop.range
0240:                                        .getStart()) != Utilities
0241:                                        .getLineOffset(doc, ts.offset())) {
0242:                            int blocks = 0;
0243:                            while (!stack.empty() && stack.pop().braceless) {
0244:                                blocks++;
0245:                            }
0246:                            delta -= blocks;
0247:                        }
0248:                        return delta;
0249:                    } else if (LexUtilities.getMultilineRange(doc, ts.offset()) != OffsetRange.NONE) {
0250:                        // we found braceless block, let's record it in the stack
0251:                        stack.push(new StackItem(true, LexUtilities
0252:                                .getMultilineRange(doc, ts.offset())));
0253:                    } else if (id == JsTokenId.EOL) {
0254:                        if (!stack.empty()) {
0255:                            if (stack.peek().braceless) {
0256:                                // end of line after braceless block start
0257:                                OffsetRange stackOffset = stack.peek().range;
0258:                                if (stackOffset.containsInclusive(ts.offset())) {
0259:                                    // we are in the braceless block statement
0260:                                    int stackEndLine = Utilities.getLineOffset(
0261:                                            doc, stackOffset.getEnd());
0262:                                    int offsetLine = Utilities.getLineOffset(
0263:                                            doc, ts.offset());
0264:                                    if (stackEndLine == offsetLine) {
0265:                                        // if we are at the last line of braceless block statement
0266:                                        // increse indent by 1
0267:                                        return 1;
0268:                                    }
0269:                                } else {
0270:                                    // we are not in braceless block statement,
0271:                                    // let's decrease indent for all braceless blocks in top of stack (if any)
0272:                                    int blocks = 0;
0273:                                    while (!stack.empty()
0274:                                            && stack.peek().braceless) {
0275:                                        blocks++;
0276:                                        stack.pop();
0277:                                    }
0278:                                    return -blocks;
0279:                                }
0280:                            }
0281:                        }
0282:                    }
0283:                } catch (BadLocationException ble) {
0284:                    Exceptions.printStackTrace(ble);
0285:                }
0286:                return 0;
0287:            }
0288:
0289:            // TODO RHTML - there can be many discontiguous sections, I've gotta process all of them on the given line
0290:            private int getTokenBalance(BaseDocument doc, int begin, int end,
0291:                    boolean includeKeywords, Set<OffsetRange> ranges) {
0292:                int balance = 0;
0293:
0294:                if (embeddedJavaScript) {
0295:                    TokenHierarchy<Document> th = TokenHierarchy
0296:                            .get((Document) doc);
0297:                    // Probably an RHTML file - gotta process it in sections since I can have lines
0298:                    // made up of both whitespace, ruby, html and delimiters and all ruby sections
0299:                    // can affect the token balance
0300:                    TokenSequence<?> t = th.tokenSequence();
0301:                    if (t == null) {
0302:                        return 0;
0303:                    }
0304:                    t.move(begin);
0305:                    if (!t.moveNext()) {
0306:                        return 0;
0307:                    }
0308:
0309:                    do {
0310:                        Token<?> token = t.token();
0311:                        TokenId id = token.id();
0312:                        if (id.primaryCategory().equals("html")) { // NOI18N
0313:                            // Some kind of "top level" language like RHTML which is two
0314:                            // levels away from JavaScript...
0315:                            TokenSequence<?> hts = t.embedded();
0316:                            hts.move(begin);
0317:                            hts.moveNext();
0318:                            do {
0319:                                Token<?> htmlToken = hts.token();
0320:                                if (htmlToken == null) {
0321:                                    break;
0322:                                }
0323:                                TokenId htmlId = htmlToken.id();
0324:                                if (htmlId.primaryCategory().equals("script")) {
0325:                                    TokenSequence<? extends JsTokenId> ts = hts
0326:                                            .embedded(JsTokenId.language());
0327:                                    ts.move(begin);
0328:                                    ts.moveNext();
0329:
0330:                                    do {
0331:                                        Token<? extends JsTokenId> jsToken = ts
0332:                                                .token();
0333:                                        if (jsToken == null) {
0334:                                            break;
0335:                                        }
0336:                                        TokenId jsId = jsToken.id();
0337:
0338:                                        if (includeKeywords) {
0339:                                            balance += getTokenBalanceDelta(
0340:                                                    jsId, doc, ts);
0341:                                        } else {
0342:                                            balance += getBracketBalanceDelta(jsId);
0343:                                        }
0344:                                    } while (ts.moveNext()
0345:                                            && (ts.offset() < end));
0346:                                }
0347:                            } while (hts.moveNext() && (hts.offset() < end));
0348:                        } else if (id.primaryCategory().equals("script")) { // NOI18N
0349:                            TokenSequence<? extends JsTokenId> ts = t
0350:                                    .embedded(JsTokenId.language());
0351:                            ts.move(begin);
0352:                            ts.moveNext();
0353:
0354:                            do {
0355:                                Token<? extends JsTokenId> jsToken = ts.token();
0356:                                if (jsToken == null) {
0357:                                    break;
0358:                                }
0359:                                TokenId jsId = jsToken.id();
0360:
0361:                                if (includeKeywords) {
0362:                                    balance += getTokenBalanceDelta(jsId, doc,
0363:                                            ts);
0364:                                } else {
0365:                                    balance += getBracketBalanceDelta(jsId);
0366:                                }
0367:                            } while (ts.moveNext() && (ts.offset() < end));
0368:                        }
0369:
0370:                    } while (t.moveNext() && (t.offset() < end));
0371:                } else {
0372:                    TokenSequence<? extends JsTokenId> ts = LexUtilities
0373:                            .getJsTokenSequence(doc, begin);
0374:                    if (ts == null) {
0375:                        return 0;
0376:                    }
0377:
0378:                    ts.move(begin);
0379:
0380:                    if (!ts.moveNext()) {
0381:                        return 0;
0382:                    }
0383:
0384:                    do {
0385:                        Token<? extends JsTokenId> token = ts.token();
0386:                        TokenId id = token.id();
0387:
0388:                        if (includeKeywords) {
0389:                            balance += getTokenBalanceDelta(id, doc, ts);
0390:                        } else {
0391:                            balance += getBracketBalanceDelta(id);
0392:                        }
0393:                    } while (ts.moveNext() && (ts.offset() < end));
0394:                }
0395:
0396:                return balance;
0397:            }
0398:
0399:            /** 
0400:             * Get the first token on the given line. Similar to LexUtilities.getToken(doc, lineBegin)
0401:             * except (a) it computes the line begin from the offset itself, and more importantly,
0402:             * (b) it handles RHTML tokens specially; e.g. if a line begins with
0403:             * {@code
0404:             *    <% if %>
0405:             * }
0406:             * then the "if" embedded token will be returned rather than the RHTML delimiter, or even
0407:             * the whitespace token (which is the first Ruby token in the embedded sequence).
0408:             *    
0409:             * </pre>   
0410:             */
0411:            private Token<? extends JsTokenId> getFirstToken(BaseDocument doc,
0412:                    int offset) throws BadLocationException {
0413:                int lineBegin = Utilities.getRowFirstNonWhite(doc, offset);
0414:
0415:                if (lineBegin != -1) {
0416:                    if (embeddedJavaScript) {
0417:                        TokenSequence<? extends JsTokenId> ts = LexUtilities
0418:                                .getJsTokenSequence(doc, lineBegin);
0419:                        if (ts != null) {
0420:                            ts.moveNext();
0421:                            Token<? extends JsTokenId> token = ts.token();
0422:                            while (token != null
0423:                                    && token.id() == JsTokenId.WHITESPACE) {
0424:                                if (!ts.moveNext()) {
0425:                                    return null;
0426:                                }
0427:                                token = ts.token();
0428:                            }
0429:                            return token;
0430:                        }
0431:                    } else {
0432:                        return LexUtilities.getToken(doc, lineBegin);
0433:                    }
0434:                }
0435:
0436:                return null;
0437:            }
0438:
0439:            private boolean hasBlockOnLine(BaseDocument doc, int begin, int end) {
0440:                int balance = 0;
0441:                if (embeddedJavaScript) {
0442:                    TokenHierarchy<Document> th = TokenHierarchy
0443:                            .get((Document) doc);
0444:                    // Probably an RHTML file - gotta process it in sections since I can have lines
0445:                    // made up of both whitespace, ruby, html and delimiters and all ruby sections
0446:                    // can affect the token balance
0447:                    TokenSequence<?> t = th.tokenSequence();
0448:                    if (t == null) {
0449:                        return false;
0450:                    }
0451:                    t.move(begin);
0452:                    if (!t.moveNext()) {
0453:                        return false;
0454:                    }
0455:
0456:                    do {
0457:                        Token<?> token = t.token();
0458:                        TokenId id = token.id();
0459:                        if (id.primaryCategory().equals("html")) { // NOI18N
0460:                            // Some kind of "top level" language like RHTML which is two
0461:                            // levels away from JavaScript...
0462:                            TokenSequence<?> hts = t.embedded();
0463:                            hts.move(begin);
0464:                            hts.moveNext();
0465:                            do {
0466:                                Token<?> htmlToken = hts.token();
0467:                                if (htmlToken == null) {
0468:                                    break;
0469:                                }
0470:                                TokenId htmlId = htmlToken.id();
0471:                                if (htmlId.primaryCategory().equals("script")) {
0472:                                    TokenSequence<? extends JsTokenId> ts = hts
0473:                                            .embedded(JsTokenId.language());
0474:                                    ts.move(begin);
0475:                                    ts.moveNext();
0476:                                    do {
0477:                                        Token<? extends JsTokenId> jsToken = ts
0478:                                                .token();
0479:                                        if (jsToken == null) {
0480:                                            break;
0481:                                        }
0482:                                        TokenId jsId = jsToken.id();
0483:
0484:                                        if (jsId == JsTokenId.LBRACE) {
0485:                                            return true;
0486:                                        }
0487:                                        if (balance == 0
0488:                                                && jsId == JsTokenId.SEMI) {
0489:                                            return true;
0490:                                        }
0491:                                        if (jsId == JsTokenId.LPAREN) {
0492:                                            balance++;
0493:                                        } else if (jsId == JsTokenId.RPAREN) {
0494:                                            balance--;
0495:                                        }
0496:                                    } while (ts.moveNext()
0497:                                            && (ts.offset() < end));
0498:                                }
0499:                            } while (hts.moveNext() && (hts.offset() < end));
0500:                        } else if (id.primaryCategory().equals("script")) { // NOI18N
0501:                            TokenSequence<? extends JsTokenId> ts = t
0502:                                    .embedded(JsTokenId.language());
0503:                            ts.move(begin);
0504:                            ts.moveNext();
0505:                            do {
0506:                                Token<? extends JsTokenId> jsToken = ts.token();
0507:                                if (jsToken == null) {
0508:                                    break;
0509:                                }
0510:                                TokenId jsId = jsToken.id();
0511:
0512:                                if (jsId == JsTokenId.LBRACE) {
0513:                                    return true;
0514:                                }
0515:                                if (balance == 0 && jsId == JsTokenId.SEMI) {
0516:                                    return true;
0517:                                }
0518:                                if (jsId == JsTokenId.LPAREN) {
0519:                                    balance++;
0520:                                } else if (jsId == JsTokenId.RPAREN) {
0521:                                    balance--;
0522:                                }
0523:                            } while (ts.moveNext() && (ts.offset() < end));
0524:                        }
0525:
0526:                    } while (t.moveNext() && (t.offset() < end));
0527:                } else {
0528:                    TokenSequence<? extends JsTokenId> ts = LexUtilities
0529:                            .getJsTokenSequence(doc, begin);
0530:                    if (ts == null) {
0531:                        return false;
0532:                    }
0533:
0534:                    ts.move(begin);
0535:
0536:                    if (!ts.moveNext()) {
0537:                        return false;
0538:                    }
0539:
0540:                    do {
0541:                        Token<? extends JsTokenId> token = ts.token();
0542:                        TokenId jsId = token.id();
0543:
0544:                        if (jsId == JsTokenId.LBRACE) {
0545:                            return true;
0546:                        }
0547:                        if (balance == 0 && jsId == JsTokenId.SEMI) {
0548:                            return true;
0549:                        }
0550:                        if (jsId == JsTokenId.LPAREN) {
0551:                            balance++;
0552:                        } else if (jsId == JsTokenId.RPAREN) {
0553:                            balance--;
0554:                        }
0555:                    } while (ts.moveNext() && (ts.offset() < end));
0556:                }
0557:
0558:                return false;
0559:            }
0560:
0561:            private int isEndIndent(BaseDocument doc, int offset)
0562:                    throws BadLocationException {
0563:                int lineBegin = Utilities.getRowFirstNonWhite(doc, offset);
0564:
0565:                if (lineBegin != -1) {
0566:                    Token<? extends JsTokenId> token = getFirstToken(doc,
0567:                            offset);
0568:
0569:                    if (token == null) {
0570:                        return 0;
0571:                    }
0572:
0573:                    TokenId id = token.id();
0574:
0575:                    // If the line starts with an end-marker, such as "end", "}", "]", etc.,
0576:                    // find the corresponding opening marker, and indent the line to the same
0577:                    // offset as the beginning of that line.
0578:                    if (/*(LexUtilities.isIndentToken(id) && !LexUtilities.isBeginToken(id, doc, offset)) || LexUtilities.isEndToken(id, doc, offset) ||*/
0579:                    id == JsTokenId.RBRACE || id == JsTokenId.RBRACKET
0580:                            || id == JsTokenId.RPAREN) {
0581:                        int indents = 1;
0582:
0583:                        // Check if there are multiple end markers here... if so increase indent level.
0584:                        // This should really do an iteration... for now just handling the most common
0585:                        // scenario in JavaScript where we have }) in object literals
0586:                        int lineEnd = Utilities.getRowEnd(doc, offset);
0587:                        int newOffset = offset;
0588:                        while (newOffset < lineEnd) {
0589:                            newOffset = newOffset + token.length();
0590:                            if (newOffset < doc.getLength()) {
0591:                                token = LexUtilities.getToken(doc, newOffset);
0592:                                if (token != null) {
0593:                                    id = token.id();
0594:                                    if (id == JsTokenId.WHITESPACE) {
0595:                                        continue;
0596:                                        /*} else if ((LexUtilities.isIndentToken(id) && !LexUtilities.isBeginToken(id, doc, offset)) || LexUtilities.isEndToken(id, doc, offset) ||
0597:                                            id == JsTokenId.RBRACE || id == JsTokenId.RBRACKET || id == JsTokenId.RPAREN) {
0598:                                            indents++;*/
0599:                                    } else {
0600:                                        break;
0601:                                    }
0602:                                }
0603:                            }
0604:                        }
0605:
0606:                        return indents;
0607:                    }
0608:                }
0609:
0610:                return 0;
0611:            }
0612:
0613:            private static boolean isLineContinued(BaseDocument doc,
0614:                    int offset, int bracketBalance) throws BadLocationException {
0615:                // TODO RHTML - this isn't going to work for rhtml embedded strings...
0616:                offset = Utilities.getRowLastNonWhite(doc, offset);
0617:                if (offset == -1) {
0618:                    return false;
0619:                }
0620:
0621:                TokenSequence<? extends JsTokenId> ts = LexUtilities
0622:                        .getPositionedSequence(doc, offset);
0623:                Token<? extends JsTokenId> token = (ts != null ? ts.token()
0624:                        : null);
0625:
0626:                if (token != null) {
0627:                    TokenId id = token.id();
0628:
0629:                    // http://www.netbeans.org/issues/show_bug.cgi?id=115279
0630:                    boolean isContinuationOperator = (id == JsTokenId.NONUNARY_OP || id == JsTokenId.DOT);
0631:
0632:                    if (ts.offset() == offset && token.length() > 1
0633:                            && token.text().toString().startsWith("\\")) {
0634:                        // Continued lines have different token types
0635:                        isContinuationOperator = true;
0636:                    }
0637:
0638:                    /* No line continuations with comma in JavaScrip - this misformats nested object literals
0639:                     * like those used in prototype and isn't necesary for real JavaScript code (since we
0640:                     * always have parentheses in parameter lists etc. to help with indentation
0641:                    if (token.length() == 1 && id == JsTokenId.IDENTIFIER && token.text().toString().equals(",")) {
0642:                        // If there's a comma it's a continuation operator, but inside arrays, hashes or parentheses
0643:                        // parameter lists we should not treat it as such since we'd "double indent" the items, and
0644:                        // NOT the first item (where there's no comma, e.g. you'd have
0645:                        //  foo(
0646:                        //    firstarg,
0647:                        //      secondarg,  # indented both by ( and hanging indent ,
0648:                        //      thirdarg)
0649:                        if (bracketBalance == 0) {
0650:                            isContinuationOperator = true;
0651:                        }
0652:                    }
0653:                     */
0654:                    if (id == JsTokenId.NONUNARY_OP
0655:                            && ",".equals(token.text().toString())) {
0656:                        // If there's a comma it's a continuation operator, but inside arrays, hashes or parentheses
0657:                        // parameter lists we should not treat it as such since we'd "double indent" the items, and
0658:                        // NOT the first item (where there's no comma, e.g. you'd have
0659:                        //  foo(
0660:                        //    firstarg,
0661:                        //      secondarg,  # indented both by ( and hanging indent ,
0662:                        //      thirdarg)
0663:                        isContinuationOperator = (bracketBalance == 0);
0664:                    }
0665:
0666:                    //            if (isContinuationOperator) {
0667:                    //                // Make sure it's not a case like this:
0668:                    //                //    alias eql? ==
0669:                    //                // or
0670:                    //                //    def ==
0671:                    //                token = LexUtilities.getToken(doc, Utilities.getRowFirstNonWhite(doc, offset));
0672:                    //                if (token != null) {
0673:                    //                    id = token.id();
0674:                    //                    if (id == JsTokenId.DEF || id == JsTokenId.ANY_KEYWORD && token.text().toString().equals("alias")) { // NOI18N
0675:                    //                        return false;
0676:                    //                    }
0677:                    //                }
0678:                    //
0679:                    //                return true;
0680:                    //            } else if (id == JsTokenId.ANY_KEYWORD) {
0681:                    //                String text = token.text().toString();
0682:                    //                if ("or".equals(text) || "and".equals(text)) { // NOI18N
0683:                    //                    return true;
0684:                    //                }
0685:                    //            }
0686:
0687:                    return isContinuationOperator;
0688:                }
0689:
0690:                return false;
0691:            }
0692:
0693:            private void reindent(Document document, int startOffset,
0694:                    int endOffset, CompilationInfo info, boolean indentOnly) {
0695:                embeddedJavaScript = !JsUtils.isJsDocument(document);
0696:
0697:                try {
0698:                    BaseDocument doc = (BaseDocument) document; // document.getText(0, document.getLength())
0699:
0700:                    if (indentOnly && embeddedJavaScript) {
0701:                        // Make sure we're not messing with indentation in HTML
0702:                        Token<? extends JsTokenId> token = LexUtilities
0703:                                .getToken(doc, startOffset);
0704:                        if (token == null) {
0705:                            return;
0706:                        }
0707:                    }
0708:
0709:                    syncOptions(doc, codeStyle);
0710:
0711:                    if (endOffset > doc.getLength()) {
0712:                        endOffset = doc.getLength();
0713:                    }
0714:
0715:                    startOffset = Utilities.getRowStart(doc, startOffset);
0716:                    int lineStart = startOffset;//Utilities.getRowStart(doc, startOffset);
0717:                    int initialOffset = 0;
0718:                    int initialIndent = 0;
0719:                    if (startOffset > 0) {
0720:                        int prevOffset = Utilities.getRowStart(doc,
0721:                                startOffset - 1);
0722:                        initialOffset = getFormatStableStart(doc, prevOffset);
0723:                        initialIndent = LexUtilities.getLineIndent(doc,
0724:                                initialOffset);
0725:                    }
0726:
0727:                    // Build up a set of offsets and indents for lines where I know I need
0728:                    // to adjust the offset. I will then go back over the document and adjust
0729:                    // lines that are different from the intended indent. By doing piecemeal
0730:                    // replacements in the document rather than replacing the whole thing,
0731:                    // a lot of things will work better: breakpoints and other line annotations
0732:                    // will be left in place, semantic coloring info will not be temporarily
0733:                    // damaged, and the caret will stay roughly where it belongs.
0734:                    List<Integer> offsets = new ArrayList<Integer>();
0735:                    List<Integer> indents = new ArrayList<Integer>();
0736:
0737:                    // When we're formatting sections, include whitespace on empty lines; this
0738:                    // is used during live code template insertions for example. However, when
0739:                    // wholesale formatting a whole document, leave these lines alone.
0740:                    boolean indentEmptyLines = (startOffset != 0 || endOffset != doc
0741:                            .getLength());
0742:
0743:                    boolean includeEnd = endOffset == doc.getLength()
0744:                            || indentOnly;
0745:
0746:                    // TODO - remove initialbalance etc.
0747:                    computeIndents(doc, initialIndent, initialOffset,
0748:                            endOffset, info, offsets, indents,
0749:                            indentEmptyLines, includeEnd, indentOnly);
0750:
0751:                    try {
0752:                        doc.atomicLock();
0753:
0754:                        // Iterate in reverse order such that offsets are not affected by our edits
0755:                        assert indents.size() == offsets.size();
0756:                        org.netbeans.editor.Formatter editorFormatter = doc
0757:                                .getFormatter();
0758:                        for (int i = indents.size() - 1; i >= 0; i--) {
0759:                            int indent = indents.get(i);
0760:                            int lineBegin = offsets.get(i);
0761:
0762:                            if (lineBegin < lineStart) {
0763:                                // We're now outside the region that the user wanted reformatting;
0764:                                // these offsets were computed to get the correct continuation context etc.
0765:                                // for the formatter
0766:                                break;
0767:                            }
0768:
0769:                            if (lineBegin == lineStart && i > 0) {
0770:                                // Look at the previous line, and see how it's indented
0771:                                // in the buffer.  If it differs from the computed position,
0772:                                // offset my computed position (thus, I'm only going to adjust
0773:                                // the new line position relative to the existing editing.
0774:                                // This avoids the situation where you're inserting a newline
0775:                                // in the middle of "incorrectly" indented code (e.g. different
0776:                                // size than the IDE is using) and the newline position ending
0777:                                // up "out of sync"
0778:                                int prevOffset = offsets.get(i - 1);
0779:                                int prevIndent = indents.get(i - 1);
0780:                                int actualPrevIndent = LexUtilities
0781:                                        .getLineIndent(doc, prevOffset);
0782:                                if (actualPrevIndent != prevIndent) {
0783:                                    // For blank lines, indentation may be 0, so don't adjust in that case
0784:                                    if (!(Utilities.isRowEmpty(doc, prevOffset) || Utilities
0785:                                            .isRowWhite(doc, prevOffset))) {
0786:                                        indent = actualPrevIndent
0787:                                                + (indent - prevIndent);
0788:                                    }
0789:                                }
0790:                            }
0791:
0792:                            // Adjust the indent at the given line (specified by offset) to the given indent
0793:                            int currentIndent = LexUtilities.getLineIndent(doc,
0794:                                    lineBegin);
0795:
0796:                            if (currentIndent != indent) {
0797:                                editorFormatter.changeRowIndent(doc, lineBegin,
0798:                                        indent);
0799:                            }
0800:                        }
0801:
0802:                        if (!indentOnly && codeStyle.reformatComments()) {
0803:                            reformatComments(doc, startOffset, endOffset);
0804:                        }
0805:                    } finally {
0806:                        doc.atomicUnlock();
0807:                    }
0808:                } catch (BadLocationException ble) {
0809:                    Exceptions.printStackTrace(ble);
0810:                }
0811:            }
0812:
0813:            public void computeIndents(BaseDocument doc, int initialIndent,
0814:                    int startOffset, int endOffset, CompilationInfo info,
0815:                    List<Integer> offsets, List<Integer> indents,
0816:                    boolean indentEmptyLines, boolean includeEnd,
0817:                    boolean indentOnly) {
0818:                // PENDING:
0819:                // The reformatting APIs in NetBeans should be lexer based. They are still
0820:                // based on the old TokenID apis. Once we get a lexer version, convert this over.
0821:                // I just need -something- in place until that is provided.
0822:
0823:                try {
0824:                    // Algorithm:
0825:                    // Iterate over the range.
0826:                    // Accumulate a token balance ( {,(,[, and keywords like class, case, etc. increases the balance, 
0827:                    //      },),] and "end" decreases it
0828:                    // If the line starts with an end marker, indent the line to the level AFTER the token
0829:                    // else indent the line to the level BEFORE the token (the level being the balance * indentationSize)
0830:                    // Compute the initial balance and indentation level and use that as a "base".
0831:                    // If the previous line is not "done" (ends with a comma or a binary operator like "+" etc.
0832:                    // add a "hanging indent" modifier.
0833:                    // At the end of the day, we're recording a set of line offsets and indents.
0834:                    // This can be used either to reformat the buffer, or indent a new line.
0835:
0836:                    // State:
0837:                    int offset = Utilities.getRowStart(doc, startOffset); // The line's offset
0838:                    int end = endOffset;
0839:
0840:                    int indentSize = codeStyle.getIndentSize();
0841:                    int hangingIndentSize = codeStyle
0842:                            .getContinuationIndentSize();
0843:
0844:                    // Pending - apply comment formatting too?
0845:
0846:                    // XXX Look up RHTML too
0847:                    //int indentSize = EditorOptions.get(RubyInstallation.RUBY_MIME_TYPE).getSpacesPerTab();
0848:                    //int hangingIndentSize = indentSize;
0849:
0850:                    // Build up a set of offsets and indents for lines where I know I need
0851:                    // to adjust the offset. I will then go back over the document and adjust
0852:                    // lines that are different from the intended indent. By doing piecemeal
0853:                    // replacements in the document rather than replacing the whole thing,
0854:                    // a lot of things will work better: breakpoints and other line annotations
0855:                    // will be left in place, semantic coloring info will not be temporarily
0856:                    // damaged, and the caret will stay roughly where it belongs.
0857:
0858:                    // The token balance at the offset
0859:                    int balance = 0;
0860:                    // The bracket balance at the offset ( parens, bracket, brace )
0861:                    int bracketBalance = 0;
0862:                    boolean continued = false;
0863:                    //            boolean indentHtml = false;
0864:                    //            if (embeddedJavaScript) {
0865:                    //                indentHtml = codeStyle.indentHtml();
0866:                    //            }
0867:
0868:                    int originallockCommentIndention = 0;
0869:                    int adjustedBlockCommentIndention = 0;
0870:
0871:                    Set<OffsetRange> ranges = new HashSet<OffsetRange>();
0872:
0873:                    int endIndents;
0874:                    while ((!includeEnd && offset < end)
0875:                            || (includeEnd && offset <= end)) {
0876:                        int indent; // The indentation to be used for the current line
0877:
0878:                        // No compound indentation for JavaScript                
0879:                        //                if (embeddedJavaScript && !indentOnly) {
0880:                        //                    // Pick up the indentation level assigned by the HTML indenter; gets HTML structure
0881:                        //                    initialIndent = LexUtilities.getLineIndent(doc, offset);
0882:                        //                }
0883:
0884:                        final int IN_CODE = 0;
0885:                        final int IN_LITERAL = 1;
0886:                        final int IN_BLOCK_COMMENT_START = 2;
0887:                        final int IN_BLOCK_COMMENT_MIDDLE = 3;
0888:                        int lineType = IN_CODE;
0889:                        int pos = Utilities.getRowFirstNonWhite(doc, offset);
0890:                        TokenSequence<? extends JsTokenId> ts = null;
0891:
0892:                        if (pos != -1) {
0893:                            // I can't look at the first position on the line, since
0894:                            // for a string array that is indented, the indentation portion
0895:                            // is recorded as a blank identifier
0896:                            ts = LexUtilities.getPositionedSequence(doc, pos);
0897:
0898:                            if (ts != null) {
0899:                                TokenId id = ts.token().id();
0900:                                // We don't have multiline string literals in JavaScript!
0901:                                if (id == JsTokenId.BLOCK_COMMENT) {
0902:                                    if (ts.offset() == pos) {
0903:                                        lineType = IN_BLOCK_COMMENT_START;
0904:                                        originallockCommentIndention = LexUtilities
0905:                                                .getLineIndent(doc, offset);
0906:                                    } else {
0907:                                        lineType = IN_BLOCK_COMMENT_MIDDLE;
0908:                                    }
0909:                                } else if (id == JsTokenId.NONUNARY_OP) {
0910:                                    // If a line starts with a non unary operator we can
0911:                                    // assume it's a continuation from a previous line
0912:                                    continued = true;
0913:                                } else if (id == JsTokenId.STRING_LITERAL
0914:                                        || id == JsTokenId.STRING_END
0915:                                        || id == JsTokenId.REGEXP_LITERAL
0916:                                        || id == JsTokenId.REGEXP_END) {
0917:                                    // You can get multiline literals in JavaScript by inserting a \ at the end
0918:                                    // of the line
0919:                                    lineType = IN_LITERAL;
0920:                                }
0921:                            } else {
0922:                                // No ruby token -- leave the formatting alone!
0923:                                // (Probably in an RHTML file on a line with no JavaScript)
0924:                                lineType = IN_LITERAL;
0925:                            }
0926:                        }
0927:
0928:                        int hangingIndent = continued ? (hangingIndentSize) : 0;
0929:
0930:                        if (lineType == IN_LITERAL) {
0931:                            // Skip this line - leave formatting as it is prior to reformatting 
0932:                            indent = LexUtilities.getLineIndent(doc, offset);
0933:
0934:                            // No compound indent for JavaScript                    
0935:                            //                    if (embeddedJavaScript && indentHtml && balance > 0) {
0936:                            //                        indent += balance * indentSize;
0937:                            //                    }
0938:                        } else if (lineType == IN_BLOCK_COMMENT_MIDDLE) {
0939:                            if (doc.getText(pos, 1).charAt(0) == '*') {
0940:                                // *-lines get indented to be flushed with the * in /*, other lines
0941:                                // get indented to be aligned with the presumably indented text content!
0942:                                //indent = LexUtilities.getLineIndent(doc, ts.offset())+1;
0943:                                indent = adjustedBlockCommentIndention + 1;
0944:                            } else {
0945:                                // Leave indentation of comment blocks alone since they probably correspond
0946:                                // to commented out code - we don't want to lose the indentation.
0947:                                // Possibly, I could shift the code all relative to the first line
0948:                                // in the commented out block... A possible later enhancement.
0949:                                // This shifts by the starting line which is wrong - should use the first comment line
0950:                                //indent = LexUtilities.getLineIndent(doc, offset)-originallockCommentIndention+adjustedBlockCommentIndention;
0951:                                indent = LexUtilities
0952:                                        .getLineIndent(doc, offset);
0953:                            }
0954:                        } else if ((endIndents = isEndIndent(doc, offset)) > 0) {
0955:                            indent = (balance - endIndents) * indentSize
0956:                                    + hangingIndent + initialIndent;
0957:                        } else {
0958:                            assert lineType == IN_CODE
0959:                                    || lineType == IN_BLOCK_COMMENT_START;
0960:                            indent = balance * indentSize + hangingIndent
0961:                                    + initialIndent;
0962:                            if (lineType == IN_BLOCK_COMMENT_START) {
0963:                                adjustedBlockCommentIndention = indent;
0964:                            }
0965:                        }
0966:
0967:                        if (indent < 0) {
0968:                            indent = 0;
0969:                        }
0970:
0971:                        int lineBegin = Utilities.getRowFirstNonWhite(doc,
0972:                                offset);
0973:
0974:                        // Insert whitespace on empty lines too -- needed for abbreviations expansion
0975:                        if (lineBegin != -1 || indentEmptyLines) {
0976:                            // Don't do a hanging indent if we're already indenting beyond the parent level?
0977:
0978:                            indents.add(Integer.valueOf(indent));
0979:                            offsets.add(Integer.valueOf(offset));
0980:                        }
0981:
0982:                        int endOfLine = Utilities.getRowEnd(doc, offset) + 1;
0983:
0984:                        if (lineBegin != -1) {
0985:                            balance += getTokenBalance(doc, lineBegin,
0986:                                    endOfLine, true, ranges);
0987:                            int bracketDelta = getTokenBalance(doc, lineBegin,
0988:                                    endOfLine, false, ranges);
0989:                            bracketBalance += bracketDelta;
0990:                            continued = isLineContinued(doc, offset,
0991:                                    bracketBalance);
0992:                        }
0993:
0994:                        offset = endOfLine;
0995:                    }
0996:                } catch (BadLocationException ble) {
0997:                    Exceptions.printStackTrace(ble);
0998:                }
0999:            }
1000:
1001:            void reformatComments(BaseDocument doc, int start, int end) {
1002:                int rightMargin = rightMarginOverride;
1003:                if (rightMargin == -1) {
1004:                    CodeStyle style = codeStyle;
1005:                    if (style == null) {
1006:                        style = CodeStyle.getDefault(null);
1007:                    }
1008:
1009:                    rightMargin = style.getRightMargin();
1010:                }
1011:
1012:                //        ReflowParagraphAction action = new ReflowParagraphAction();
1013:                //        action.reflowComments(doc, start, end, rightMargin);
1014:                throw new RuntimeException("Not yet implemented!");
1015:            }
1016:
1017:            /**
1018:             * Ensure that the editor-settings for tabs match our code style, since the
1019:             * primitive "doc.getFormatter().changeRowIndent" calls will be using
1020:             * those settings
1021:             */
1022:            private static void syncOptions(BaseDocument doc, CodeStyle style) {
1023:                org.netbeans.editor.Formatter formatter = doc.getFormatter();
1024:                if (formatter.getSpacesPerTab() != style.getIndentSize()) {
1025:                    formatter.setSpacesPerTab(style.getIndentSize());
1026:                }
1027:            }
1028:
1029:            /**
1030:             * One item in indent stack, see description of stack variable
1031:             */
1032:            private static final class StackItem {
1033:
1034:                private StackItem(boolean braceless, OffsetRange range) {
1035:                    this .braceless = braceless;
1036:                    this .range = range;
1037:                }
1038:
1039:                /**
1040:                 * true for block without optional curly braces, false otherwise
1041:                 */
1042:                private final boolean braceless;
1043:
1044:                /**
1045:                 * For braceless blocks it is range from statement beginning (e.g. |if...)
1046:                 * to end of line where curly brace would be (e.g. if(...) |\n )<br>
1047:                 * For braces and brackets blocks it is offset of beginning of token for 
1048:                 * both - beginning and end of range (e.g. OffsetRange[ts.token(), ts.token()])
1049:                 */
1050:                private final OffsetRange range;
1051:
1052:                public String toString() {
1053:                    return "StackItem[" + braceless + "," + range + "]";
1054:                }
1055:            }
1056:
1057:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.