001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.ruby;
042:
043: import java.util.ArrayList;
044: import java.util.List;
045:
046: import javax.swing.text.BadLocationException;
047: import javax.swing.text.Document;
048:
049: import org.netbeans.modules.gsf.api.OffsetRange;
050: import org.netbeans.api.lexer.Token;
051: import org.netbeans.api.lexer.TokenHierarchy;
052: import org.netbeans.api.lexer.TokenId;
053: import org.netbeans.api.lexer.TokenSequence;
054: import org.netbeans.editor.BaseDocument;
055: import org.netbeans.editor.Utilities;
056: import org.netbeans.modules.gsf.api.CompilationInfo;
057: import org.netbeans.modules.ruby.lexer.LexUtilities;
058: import org.netbeans.modules.ruby.lexer.RubyTokenId;
059: import org.netbeans.modules.ruby.options.CodeStyle;
060: import org.openide.util.Exceptions;
061:
062: /**
063: * Formatting and indentation for Ruby.
064: *
065: * @todo Handle RHTML!
066: * - 4 space indents
067: * - conflicts with HTML formatter (HtmlIndentTask)
068: * - The Ruby indentation should be indented into the HTML level as well!
069: * (so I should look at the previous HTML level for my initial context
070: * whenever the balance is 0.)
071: * @todo Use configuration object to pass in Ruby conventions
072: * @todo Use the provided parse tree, if any, to for example check heredoc nodes
073: * and see if they are indentable.
074: * @todo If you select a complete line, the endOffset is on a new line; adjust it back
075: * @todo If line ends with \ I definitely have a line continuation!
076: * @todo Use the Context.modifyIndent() method to change line indents instead of
077: * the current document/formatter method
078: * @todo This line screws up formatting:
079: * alias __class__ class #:nodoc:
080: * @todo Why doesn't this format correctly?
081: * <pre>
082: class Module
083: alias_method :class?, :===
084: end
085: * </pre>
086: *
087: * @author Tor Norbye
088: */
089: public class Formatter implements
090: org.netbeans.modules.gsf.api.Formatter {
091: private boolean isRhtmlDocument;
092: private final CodeStyle codeStyle;
093: private int rightMarginOverride = -1;
094:
095: public Formatter() {
096: this .codeStyle = CodeStyle.getDefault(null);
097: }
098:
099: public Formatter(CodeStyle codeStyle, int rightMarginOverride) {
100: assert codeStyle != null;
101: this .codeStyle = codeStyle;
102: this .rightMarginOverride = rightMarginOverride;
103: }
104:
105: public boolean needsParserResult() {
106: return false;
107: }
108:
109: public void reindent(Document document, int startOffset,
110: int endOffset) {
111: // Make sure we're not reindenting HTML content
112: reindent(document, startOffset, endOffset, null, true);
113: }
114:
115: public void reformat(Document document, int startOffset,
116: int endOffset, CompilationInfo info) {
117: reindent(document, startOffset, endOffset, info, false);
118: }
119:
120: public int indentSize() {
121: return codeStyle.getIndentSize();
122: }
123:
124: public int hangingIndentSize() {
125: return codeStyle.getContinuationIndentSize();
126: }
127:
128: /** Compute the initial balance of brackets at the given offset. */
129: private int getFormatStableStart(BaseDocument doc, int offset) {
130: TokenSequence<? extends RubyTokenId> ts = LexUtilities
131: .getRubyTokenSequence(doc, offset);
132: if (ts == null) {
133: return 0;
134: }
135:
136: ts.move(offset);
137:
138: if (!ts.movePrevious()) {
139: return 0;
140: }
141:
142: // Look backwards to find a suitable context - a class, module or method definition
143: // which we will assume is properly indented and balanced
144: do {
145: Token<? extends RubyTokenId> token = ts.token();
146: TokenId id = token.id();
147:
148: if (id == RubyTokenId.CLASS || id == RubyTokenId.MODULE
149: || id == RubyTokenId.DEF) {
150: return ts.offset();
151: }
152: } while (ts.movePrevious());
153:
154: return ts.offset();
155: }
156:
157: public static int getTokenBalanceDelta(TokenId id,
158: Token<? extends RubyTokenId> token, BaseDocument doc,
159: TokenSequence<? extends RubyTokenId> ts,
160: boolean includeKeywords) {
161: if (id == RubyTokenId.IDENTIFIER) {
162: // In some cases, the [ shows up as an identifier, for example in this expression:
163: // for k, v in sort{|a1, a2| a1[0].id2name <=> a2[0].id2name}
164: if (token.length() == 1) {
165: char c = token.text().charAt(0);
166: if (c == '[') {
167: return 1;
168: } else if (c == ']') {
169: // I've seen "]" come instead of a RBRACKET too - for example in RHTML:
170: // <%if session[:user]%>
171: return -1;
172: }
173: }
174: } else if (id == RubyTokenId.LPAREN
175: || id == RubyTokenId.LBRACKET
176: || id == RubyTokenId.LBRACE) {
177: return 1;
178: } else if (id == RubyTokenId.RPAREN
179: || id == RubyTokenId.RBRACKET
180: || id == RubyTokenId.RBRACE) {
181: return -1;
182: } else if (includeKeywords) {
183: if (LexUtilities.isBeginToken(id, doc, ts)) {
184: return 1;
185: } else if (id == RubyTokenId.END) {
186: return -1;
187: }
188: }
189:
190: return 0;
191: }
192:
193: // TODO RHTML - there can be many discontiguous sections, I've gotta process all of them on the given line
194: public static int getTokenBalance(BaseDocument doc, int begin,
195: int end, boolean includeKeywords, boolean rhtml) {
196: int balance = 0;
197:
198: if (rhtml) {
199: TokenHierarchy<Document> th = TokenHierarchy
200: .get((Document) doc);
201: // Probably an RHTML file - gotta process it in sections since I can have lines
202: // made up of both whitespace, ruby, html and delimiters and all ruby sections
203: // can affect the token balance
204: TokenSequence<?> t = th.tokenSequence();
205: if (t == null) {
206: return 0;
207: }
208: t.move(begin);
209: if (!t.moveNext()) {
210: return 0;
211: }
212:
213: do {
214: Token<?> token = t.token();
215: TokenId id = token.id();
216:
217: if (id.primaryCategory().equals("ruby")) { // NOI18N
218: TokenSequence<? extends RubyTokenId> ts = t
219: .embedded(RubyTokenId.language());
220: ts.move(begin);
221: ts.moveNext();
222: do {
223: Token<? extends RubyTokenId> rubyToken = ts
224: .token();
225: if (rubyToken == null) {
226: break;
227: }
228: TokenId rubyId = rubyToken.id();
229:
230: balance += getTokenBalanceDelta(rubyId,
231: rubyToken, doc, ts, includeKeywords);
232: } while (ts.moveNext() && (ts.offset() < end));
233: }
234:
235: } while (t.moveNext() && (t.offset() < end));
236: } else {
237: TokenSequence<? extends RubyTokenId> ts = LexUtilities
238: .getRubyTokenSequence(doc, begin);
239: if (ts == null) {
240: return 0;
241: }
242:
243: ts.move(begin);
244:
245: if (!ts.moveNext()) {
246: return 0;
247: }
248:
249: do {
250: Token<? extends RubyTokenId> token = ts.token();
251: TokenId id = token.id();
252:
253: balance += getTokenBalanceDelta(id, token, doc, ts,
254: includeKeywords);
255: } while (ts.moveNext() && (ts.offset() < end));
256: }
257:
258: return balance;
259: }
260:
261: private boolean isInLiteral(BaseDocument doc, int offset)
262: throws BadLocationException {
263: // TODO: Handle arrays better
264: // %w(January February March April May June July
265: // August September October November December)
266: // I should indent to the same level
267:
268: // Can't reformat these at the moment because reindenting a line
269: // that is a continued string array causes incremental lexing errors
270: // (which further screw up formatting)
271: int pos = Utilities.getRowFirstNonWhite(doc, offset);
272: //int pos = offset;
273:
274: if (pos != -1) {
275: // I can't look at the first position on the line, since
276: // for a string array that is indented, the indentation portion
277: // is recorded as a blank identifier
278: Token<? extends RubyTokenId> token = LexUtilities.getToken(
279: doc, pos);
280:
281: if (token != null) {
282: TokenId id = token.id();
283: // If we're in a string literal (or regexp or documentation) leave
284: // indentation alone!
285: if ((id == RubyTokenId.STRING_LITERAL)
286: || id == RubyTokenId.DOCUMENTATION
287: || (id == RubyTokenId.QUOTED_STRING_LITERAL)
288: || (id == RubyTokenId.REGEXP_LITERAL)) {
289: // No indentation for literal strings in Ruby, since they can
290: // contain newlines. Leave it as is.
291: return true;
292: }
293:
294: if (id == RubyTokenId.STRING_END
295: || id == RubyTokenId.QUOTED_STRING_END) {
296: // Possibly a heredoc
297: TokenSequence<? extends RubyTokenId> ts = LexUtilities
298: .getRubyTokenSequence(doc, pos);
299: ts.move(pos);
300: OffsetRange range = LexUtilities.findHeredocBegin(
301: ts, token);
302: if (range != OffsetRange.NONE) {
303: String text = doc.getText(range.getStart(),
304: range.getLength());
305: if (text.startsWith("<<-")) { // NOI18N
306: return false;
307: } else {
308: return true;
309: }
310: }
311: }
312: } else {
313: // No ruby token -- leave the formatting alone!
314: // (Probably in an RHTML file on a line with no Ruby)
315: return true;
316: }
317: } else {
318: // Empty line inside a string, documentation etc. literal?
319: Token<? extends RubyTokenId> token = LexUtilities.getToken(
320: doc, offset);
321:
322: if (token != null) {
323: TokenId id = token.id();
324: // If we're in a string literal (or regexp or documentation) leave
325: // indentation alone!
326: if ((id == RubyTokenId.STRING_LITERAL)
327: || id == RubyTokenId.DOCUMENTATION
328: || (id == RubyTokenId.QUOTED_STRING_LITERAL)
329: || (id == RubyTokenId.REGEXP_LITERAL)) {
330: // No indentation for literal strings in Ruby, since they can
331: // contain newlines. Leave it as is.
332: return true;
333: }
334: }
335: }
336:
337: return false;
338: }
339:
340: /**
341: * Get the first token on the given line. Similar to LexUtilities.getToken(doc, lineBegin)
342: * except (a) it computes the line begin from the offset itself, and more importantly,
343: * (b) it handles RHTML tokens specially; e.g. if a line begins with
344: * {@code
345: * <% if %>
346: * }
347: * then the "if" embedded token will be returned rather than the RHTML delimiter, or even
348: * the whitespace token (which is the first Ruby token in the embedded sequence).
349: *
350: * </pre>
351: */
352: private Token<? extends RubyTokenId> getFirstToken(
353: BaseDocument doc, int offset) throws BadLocationException {
354: int lineBegin = Utilities.getRowFirstNonWhite(doc, offset);
355:
356: if (lineBegin != -1) {
357: if (isRhtmlDocument) {
358: TokenSequence<? extends RubyTokenId> ts = LexUtilities
359: .getRubyTokenSequence(doc, lineBegin);
360: if (ts != null) {
361: ts.moveNext();
362: Token<? extends RubyTokenId> token = ts.token();
363: while (token != null
364: && token.id() == RubyTokenId.WHITESPACE) {
365: if (!ts.moveNext()) {
366: return null;
367: }
368: token = ts.token();
369: }
370: return token;
371: }
372: } else {
373: return LexUtilities.getToken(doc, lineBegin);
374: }
375: }
376:
377: return null;
378: }
379:
380: private boolean isEndIndent(BaseDocument doc, int offset)
381: throws BadLocationException {
382: int lineBegin = Utilities.getRowFirstNonWhite(doc, offset);
383:
384: if (lineBegin != -1) {
385: Token<? extends RubyTokenId> token = getFirstToken(doc,
386: offset);
387:
388: if (token == null) {
389: if (isRhtmlDocument) {
390: // Could be the END of a Ruby section - line begins with "%>"
391: if (lineBegin < doc.getLength() - 2) {
392: String lineBeginStr = doc.getText(lineBegin, 2);
393: if (lineBeginStr.equals("-%")
394: && lineBegin < doc.getLength() - 3) { // NOI18N
395: lineBeginStr = doc.getText(lineBegin, 3);
396: if (lineBeginStr.equals("-%>")) { // NOI18N
397: return true;
398: }
399: } else if (lineBeginStr.equals("%>")) { // NOI18N
400: return true;
401: }
402: }
403:
404: }
405: return false;
406: }
407:
408: TokenId id = token.id();
409:
410: // If the line starts with an end-marker, such as "end", "}", "]", etc.,
411: // find the corresponding opening marker, and indent the line to the same
412: // offset as the beginning of that line.
413: return (LexUtilities.isIndentToken(id) && !LexUtilities
414: .isBeginToken(id, doc, offset))
415: || id == RubyTokenId.END
416: || id == RubyTokenId.RBRACE
417: || id == RubyTokenId.RBRACKET
418: || id == RubyTokenId.RPAREN;
419: }
420:
421: return false;
422: }
423:
424: private boolean isLineContinued(BaseDocument doc, int offset,
425: int bracketBalance) throws BadLocationException {
426: // TODO RHTML - this isn't going to work for rhtml embedded strings...
427: offset = Utilities.getRowLastNonWhite(doc, offset);
428: if (offset == -1) {
429: return false;
430: }
431:
432: TokenSequence<? extends RubyTokenId> ts = LexUtilities
433: .getRubyTokenSequence(doc, offset);
434:
435: if (ts == null) {
436: return false;
437: }
438: ts.move(offset);
439:
440: if (!ts.moveNext() && !ts.movePrevious()) {
441: return false;
442: }
443:
444: Token<? extends RubyTokenId> token = ts.token();
445:
446: if (token != null) {
447: TokenId id = token.id();
448:
449: // http://www.netbeans.org/issues/show_bug.cgi?id=115279
450: boolean isContinuationOperator = (id == RubyTokenId.NONUNARY_OP || id == RubyTokenId.DOT);
451:
452: if (ts.offset() == offset && token.length() > 1
453: && token.text().toString().startsWith("\\")) {
454: // Continued lines have different token types
455: isContinuationOperator = true;
456: }
457:
458: if (token.length() == 1 && id == RubyTokenId.IDENTIFIER
459: && token.text().toString().equals(",")) {
460: // If there's a comma it's a continuation operator, but inside arrays, hashes or parentheses
461: // parameter lists we should not treat it as such since we'd "double indent" the items, and
462: // NOT the first item (where there's no comma, e.g. you'd have
463: // foo(
464: // firstarg,
465: // secondarg, # indented both by ( and hanging indent ,
466: // thirdarg)
467: if (bracketBalance == 0) {
468: isContinuationOperator = true;
469: }
470: }
471:
472: if (isContinuationOperator) {
473: // Make sure it's not a case like this:
474: // alias eql? ==
475: // or
476: // def ==
477: token = LexUtilities.getToken(doc, Utilities
478: .getRowFirstNonWhite(doc, offset));
479: if (token != null) {
480: id = token.id();
481: if (id == RubyTokenId.DEF
482: || id == RubyTokenId.ANY_KEYWORD
483: && token.text().toString().equals("alias")) { // NOI18N
484: return false;
485: }
486: }
487:
488: return true;
489: } else if (id == RubyTokenId.ANY_KEYWORD) {
490: String text = token.text().toString();
491: if ("or".equals(text) || "and".equals(text)) { // NOI18N
492: return true;
493: }
494: }
495: }
496:
497: return false;
498: }
499:
500: private void reindent(Document document, int startOffset,
501: int endOffset, CompilationInfo info, boolean indentOnly) {
502: isRhtmlDocument = RubyUtils.isRhtmlDocument(document);
503:
504: try {
505: BaseDocument doc = (BaseDocument) document; // document.getText(0, document.getLength())
506:
507: if (indentOnly && isRhtmlDocument) {
508: // Make sure we're not messing with indentation in HTML
509: Token<? extends RubyTokenId> token = LexUtilities
510: .getToken(doc, startOffset);
511: if (token == null) {
512: return;
513: }
514: }
515:
516: syncOptions(doc, codeStyle);
517:
518: if (endOffset > doc.getLength()) {
519: endOffset = doc.getLength();
520: }
521:
522: startOffset = Utilities.getRowStart(doc, startOffset);
523: int lineStart = startOffset;//Utilities.getRowStart(doc, startOffset);
524: int initialOffset = 0;
525: int initialIndent = 0;
526: if (startOffset > 0) {
527: int prevOffset = Utilities.getRowStart(doc,
528: startOffset - 1);
529: initialOffset = getFormatStableStart(doc, prevOffset);
530: initialIndent = LexUtilities.getLineIndent(doc,
531: initialOffset);
532: }
533:
534: // Build up a set of offsets and indents for lines where I know I need
535: // to adjust the offset. I will then go back over the document and adjust
536: // lines that are different from the intended indent. By doing piecemeal
537: // replacements in the document rather than replacing the whole thing,
538: // a lot of things will work better: breakpoints and other line annotations
539: // will be left in place, semantic coloring info will not be temporarily
540: // damaged, and the caret will stay roughly where it belongs.
541: List<Integer> offsets = new ArrayList<Integer>();
542: List<Integer> indents = new ArrayList<Integer>();
543:
544: // When we're formatting sections, include whitespace on empty lines; this
545: // is used during live code template insertions for example. However, when
546: // wholesale formatting a whole document, leave these lines alone.
547: boolean indentEmptyLines = (startOffset != 0 || endOffset != doc
548: .getLength());
549:
550: boolean includeEnd = endOffset == doc.getLength()
551: || indentOnly;
552:
553: // TODO - remove initialbalance etc.
554: computeIndents(doc, initialIndent, initialOffset,
555: endOffset, info, offsets, indents,
556: indentEmptyLines, includeEnd, indentOnly);
557:
558: try {
559: doc.atomicLock();
560:
561: // Iterate in reverse order such that offsets are not affected by our edits
562: assert indents.size() == offsets.size();
563: org.netbeans.editor.Formatter editorFormatter = doc
564: .getFormatter();
565: for (int i = indents.size() - 1; i >= 0; i--) {
566: int indent = indents.get(i);
567: int lineBegin = offsets.get(i);
568:
569: if (lineBegin < lineStart) {
570: // We're now outside the region that the user wanted reformatting;
571: // these offsets were computed to get the correct continuation context etc.
572: // for the formatter
573: break;
574: }
575:
576: if (lineBegin == lineStart && i > 0) {
577: // Look at the previous line, and see how it's indented
578: // in the buffer. If it differs from the computed position,
579: // offset my computed position (thus, I'm only going to adjust
580: // the new line position relative to the existing editing.
581: // This avoids the situation where you're inserting a newline
582: // in the middle of "incorrectly" indented code (e.g. different
583: // size than the IDE is using) and the newline position ending
584: // up "out of sync"
585: int prevOffset = offsets.get(i - 1);
586: int prevIndent = indents.get(i - 1);
587: int actualPrevIndent = LexUtilities
588: .getLineIndent(doc, prevOffset);
589: if (actualPrevIndent != prevIndent) {
590: // For blank lines, indentation may be 0, so don't adjust in that case
591: if (!(Utilities.isRowEmpty(doc, prevOffset) || Utilities
592: .isRowWhite(doc, prevOffset))) {
593: indent = actualPrevIndent
594: + (indent - prevIndent);
595: }
596: }
597: }
598:
599: // Adjust the indent at the given line (specified by offset) to the given indent
600: int currentIndent = LexUtilities.getLineIndent(doc,
601: lineBegin);
602:
603: if (currentIndent != indent) {
604: editorFormatter.changeRowIndent(doc, lineBegin,
605: indent);
606: }
607: }
608:
609: if (!indentOnly && codeStyle.reformatComments()) {
610: reformatComments(doc, startOffset, endOffset);
611: }
612: } finally {
613: doc.atomicUnlock();
614: }
615: } catch (BadLocationException ble) {
616: Exceptions.printStackTrace(ble);
617: }
618: }
619:
620: public void computeIndents(BaseDocument doc, int initialIndent,
621: int startOffset, int endOffset, CompilationInfo info,
622: List<Integer> offsets, List<Integer> indents,
623: boolean indentEmptyLines, boolean includeEnd,
624: boolean indentOnly) {
625: // PENDING:
626: // The reformatting APIs in NetBeans should be lexer based. They are still
627: // based on the old TokenID apis. Once we get a lexer version, convert this over.
628: // I just need -something- in place until that is provided.
629:
630: try {
631: // Algorithm:
632: // Iterate over the range.
633: // Accumulate a token balance ( {,(,[, and keywords like class, case, etc. increases the balance,
634: // },),] and "end" decreases it
635: // If the line starts with an end marker, indent the line to the level AFTER the token
636: // else indent the line to the level BEFORE the token (the level being the balance * indentationSize)
637: // Compute the initial balance and indentation level and use that as a "base".
638: // If the previous line is not "done" (ends with a comma or a binary operator like "+" etc.
639: // add a "hanging indent" modifier.
640: // At the end of the day, we're recording a set of line offsets and indents.
641: // This can be used either to reformat the buffer, or indent a new line.
642:
643: // State:
644: int offset = Utilities.getRowStart(doc, startOffset); // The line's offset
645: int end = endOffset;
646:
647: int indentSize = codeStyle.getIndentSize();
648: int hangingIndentSize = codeStyle
649: .getContinuationIndentSize();
650:
651: // Pending - apply comment formatting too?
652:
653: // XXX Look up RHTML too
654: //int indentSize = EditorOptions.get(RubyInstallation.RUBY_MIME_TYPE).getSpacesPerTab();
655: //int hangingIndentSize = indentSize;
656:
657: // Build up a set of offsets and indents for lines where I know I need
658: // to adjust the offset. I will then go back over the document and adjust
659: // lines that are different from the intended indent. By doing piecemeal
660: // replacements in the document rather than replacing the whole thing,
661: // a lot of things will work better: breakpoints and other line annotations
662: // will be left in place, semantic coloring info will not be temporarily
663: // damaged, and the caret will stay roughly where it belongs.
664:
665: // The token balance at the offset
666: int balance = 0;
667: // The bracket balance at the offset ( parens, bracket, brace )
668: int bracketBalance = 0;
669: boolean continued = false;
670: boolean indentHtml = false;
671: if (isRhtmlDocument) {
672: indentHtml = codeStyle.indentHtml();
673: }
674:
675: while ((!includeEnd && offset < end)
676: || (includeEnd && offset <= end)) {
677: int indent; // The indentation to be used for the current line
678:
679: int hangingIndent = continued ? (hangingIndentSize) : 0;
680:
681: if (isRhtmlDocument && !indentOnly) {
682: // Pick up the indentation level assigned by the HTML indenter; gets HTML structure
683: initialIndent = LexUtilities.getLineIndent(doc,
684: offset);
685: }
686:
687: if (isInLiteral(doc, offset)) {
688: // Skip this line - leave formatting as it is prior to reformatting
689: indent = LexUtilities.getLineIndent(doc, offset);
690:
691: if (isRhtmlDocument && indentHtml && balance > 0) {
692: indent += balance * indentSize;
693: }
694: } else if (isEndIndent(doc, offset)) {
695: indent = (balance - 1) * indentSize + hangingIndent
696: + initialIndent;
697: } else {
698: indent = balance * indentSize + hangingIndent
699: + initialIndent;
700: }
701:
702: if (indent < 0) {
703: indent = 0;
704: }
705:
706: int lineBegin = Utilities.getRowFirstNonWhite(doc,
707: offset);
708:
709: // Insert whitespace on empty lines too -- needed for abbreviations expansion
710: if (lineBegin != -1 || indentEmptyLines) {
711: // Don't do a hanging indent if we're already indenting beyond the parent level?
712:
713: indents.add(Integer.valueOf(indent));
714: offsets.add(Integer.valueOf(offset));
715: }
716:
717: int endOfLine = Utilities.getRowEnd(doc, offset) + 1;
718:
719: if (lineBegin != -1) {
720: balance += getTokenBalance(doc, lineBegin,
721: endOfLine, true, isRhtmlDocument);
722: bracketBalance += getTokenBalance(doc, lineBegin,
723: endOfLine, false, isRhtmlDocument);
724: continued = isLineContinued(doc, offset,
725: bracketBalance);
726: }
727:
728: offset = endOfLine;
729: }
730: } catch (BadLocationException ble) {
731: Exceptions.printStackTrace(ble);
732: }
733: }
734:
735: void reformatComments(BaseDocument doc, int start, int end) {
736: int rightMargin = rightMarginOverride;
737: if (rightMargin == -1) {
738: CodeStyle style = codeStyle;
739: if (style == null) {
740: style = CodeStyle.getDefault(null);
741: }
742:
743: rightMargin = style.getRightMargin();
744: }
745:
746: ReflowParagraphAction action = new ReflowParagraphAction();
747: action.reflowComments(doc, start, end, rightMargin);
748: }
749:
750: /**
751: * Ensure that the editor-settings for tabs match our code style, since the
752: * primitive "doc.getFormatter().changeRowIndent" calls will be using
753: * those settings
754: */
755: private static void syncOptions(BaseDocument doc, CodeStyle style) {
756: org.netbeans.editor.Formatter formatter = doc.getFormatter();
757: if (formatter.getSpacesPerTab() != style.getIndentSize()) {
758: formatter.setSpacesPerTab(style.getIndentSize());
759: }
760: }
761: }
|