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-2007 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.cnd.editor.cplusplus;
042:
043: import javax.swing.text.JTextComponent;
044: import javax.swing.text.BadLocationException;
045:
046: import org.netbeans.editor.TokenItem;
047: import org.netbeans.editor.BaseDocument;
048: import org.netbeans.editor.Utilities;
049: import org.netbeans.editor.Syntax;
050:
051: import org.netbeans.editor.ext.AbstractFormatLayer;
052: import org.netbeans.editor.ext.FormatTokenPosition;
053: import org.netbeans.editor.ext.FormatSupport;
054: import org.netbeans.editor.ext.ExtFormatter;
055: import org.netbeans.editor.ext.FormatWriter;
056: import org.netbeans.modules.cnd.editor.api.CodeStyle;
057:
058: /**
059: * CC indentation services are located here.
060: *
061: * (Copied from from editor/libsrc/org/netbeans/editor/ext/java/JavaFormatter.java)
062: */
063: public class CCFormatter extends ExtFormatter {
064:
065: public CCFormatter(Class kitClass) {
066: super (kitClass);
067: }
068:
069: @Override
070: protected boolean acceptSyntax(Syntax syntax) {
071: return (syntax instanceof CCSyntax);
072: }
073:
074: @Override
075: public int[] getReformatBlock(JTextComponent target,
076: String typedText) {
077: int[] ret = null;
078: BaseDocument doc = Utilities.getDocument(target);
079: int dotPos = target.getCaret().getDot();
080: if (doc != null) {
081: ret = getKeywordBasedReformatBlock(doc, dotPos, typedText);
082: if (ret == null) {
083: ret = super .getReformatBlock(target, typedText);
084: }
085: }
086:
087: return ret;
088: }
089:
090: public static int[] getKeywordBasedReformatBlock(BaseDocument doc,
091: int dotPos, String typedText) {
092: /* Check whether the user has written the ending 'e'
093: * of the first 'else' on the line.
094: */
095: int[] ret = null;
096: if ("e".equals(typedText)) { // NOI18N
097: try {
098: int fnw = Utilities.getRowFirstNonWhite(doc, dotPos);
099: if (checkCase(doc, fnw, "else")) { // NOI18N
100: ret = new int[] { fnw, fnw + 4 };
101: }
102: } catch (BadLocationException e) {
103: }
104:
105: } else if (":".equals(typedText)) { // NOI18N
106: try {
107: int fnw = Utilities.getRowFirstNonWhite(doc, dotPos);
108: if (checkCase(doc, fnw, "case")) { // NOI18N
109: ret = new int[] { fnw, fnw + 4 };
110: } else if (checkCase(doc, fnw, "default")) { // NOI18N
111: ret = new int[] { fnw, fnw + 7 };
112: } else if (checkCase(doc, fnw, "public")) { // NOI18N
113: ret = new int[] { fnw, fnw + 6 };
114: } else if (checkCase(doc, fnw, "protected")) { // NOI18N
115: ret = new int[] { fnw, fnw + 9 };
116: } else if (checkCase(doc, fnw, "private")) { // NOI18N
117: ret = new int[] { fnw, fnw + 7 };
118: }
119: } catch (BadLocationException e) {
120: }
121: }
122: return ret;
123: }
124:
125: private static boolean checkCase(BaseDocument doc, int fnw,
126: String what) throws BadLocationException {
127: return fnw >= 0 && fnw + what.length() <= doc.getLength()
128: && what.equals(doc.getText(fnw, what.length()));
129: }
130:
131: @Override
132: protected void initFormatLayers() {
133: if (CKit.class.equals(getKitClass())) {
134: addFormatLayer(new StripEndWhitespaceLayer(
135: CodeStyle.Language.C));
136: addFormatLayer(new CCLayer(CodeStyle.Language.C));
137: } else {
138: addFormatLayer(new StripEndWhitespaceLayer(
139: CodeStyle.Language.CPP));
140: addFormatLayer(new CCLayer(CodeStyle.Language.CPP));
141: }
142: }
143:
144: public FormatSupport createFormatSupport(FormatWriter fw) {
145: if (CKit.class.equals(getKitClass())) {
146: return new CCFormatSupport(CodeStyle.Language.C, fw);
147: } else {
148: return new CCFormatSupport(CodeStyle.Language.CPP, fw);
149: }
150: }
151:
152: public class StripEndWhitespaceLayer extends AbstractFormatLayer {
153: private CodeStyle.Language language;
154:
155: public StripEndWhitespaceLayer(CodeStyle.Language language) {
156: super ("cc-strip-whitespace-at-line-end"); // NOI18N
157: this .language = language;
158: }
159:
160: @Override
161: protected FormatSupport createFormatSupport(FormatWriter fw) {
162: return new CCFormatSupport(language, fw);
163: }
164:
165: public void format(FormatWriter fw) {
166: CCFormatSupport ccfs = (CCFormatSupport) createFormatSupport(fw);
167:
168: FormatTokenPosition pos = ccfs.getFormatStartPosition();
169: if (ccfs.isIndentOnly()) {
170: // don't do anything
171: } else { // remove end-line whitespace
172: while (pos.getToken() != null) {
173: pos = ccfs.removeLineEndWhitespace(pos);
174: if (pos.getToken() != null) {
175: pos = ccfs.getNextPosition(pos);
176: }
177: }
178: }
179: }
180: }
181:
182: public class CCLayer extends AbstractFormatLayer {
183: private CodeStyle.Language language;
184:
185: public CCLayer(CodeStyle.Language language) {
186: super ("cc-layer"); // NOI18N
187: this .language = language;
188: }
189:
190: @Override
191: protected FormatSupport createFormatSupport(FormatWriter fw) {
192: return new CCFormatSupport(language, fw);
193: }
194:
195: public void format(FormatWriter fw) {
196: try {
197: CCFormatSupport ccfs = (CCFormatSupport) createFormatSupport(fw);
198:
199: FormatTokenPosition pos = ccfs.getFormatStartPosition();
200:
201: if (ccfs.isIndentOnly()) { // create indentation only
202: ccfs.indentLine(pos);
203:
204: } else { // regular formatting
205:
206: while (pos != null) {
207:
208: // Indent the current line
209: ccfs.indentLine(pos);
210:
211: // Format the line by additional rules
212: formatLine(ccfs, pos);
213:
214: // Goto next line
215: FormatTokenPosition pos2 = ccfs
216: .findLineEnd(pos);
217: if (pos2 == null || pos2.getToken() == null) {
218: break;
219: } // the last line was processed
220:
221: pos = ccfs.getNextPosition(pos2,
222: javax.swing.text.Position.Bias.Forward);
223: if (pos == pos2) {
224: break;
225: } // in case there is no next position
226: if (pos == null || pos.getToken() == null) {
227: break;
228: } // there is nothing after the end of line
229:
230: FormatTokenPosition fnw = ccfs
231: .findLineFirstNonWhitespace(pos);
232: if (fnw != null) {
233: pos = fnw;
234: } else { // no non-whitespace char on the line
235: pos = ccfs.findLineStart(pos);
236: }
237: }
238: }
239: } catch (IllegalStateException e) {
240: }
241: }
242:
243: private void removeLineBeforeToken(TokenItem token,
244: CCFormatSupport ccfs, boolean checkRBraceBefore) {
245: FormatTokenPosition tokenPos = ccfs.getPosition(token, 0);
246: // Check that nothing exists before token
247: if (ccfs.findNonWhitespace(tokenPos, null, true, true) != null) {
248: return;
249: }
250:
251: // Check that the backward nonWhite is }
252: if (checkRBraceBefore) {
253: FormatTokenPosition ftpos = ccfs.findNonWhitespace(
254: tokenPos, null, false, true);
255: if (ftpos == null
256: || ftpos.getToken().getTokenID().getNumericID() != CCTokenContext.RBRACE_ID) {
257: return;
258: }
259: }
260:
261: // Check that nothing exists after token, but ignore comments
262: if (ccfs.getNextPosition(tokenPos) != null) {
263: FormatTokenPosition ftp = ccfs.findImportant(ccfs
264: .getNextPosition(tokenPos), null, true, false);
265: if (ftp != null) {
266: insertNewLineBeforeToken(ftp.getToken(), ccfs);
267: }
268: }
269:
270: // check that on previous line is some stmt
271: FormatTokenPosition ftp = ccfs.findLineStart(tokenPos); // find start of current line
272: FormatTokenPosition endOfPreviousLine = ccfs
273: .getPreviousPosition(ftp); // go one position back - means previous line
274: if (endOfPreviousLine == null
275: || endOfPreviousLine.getToken().getTokenID() != CCTokenContext.WHITESPACE) {
276: return;
277: }
278: ftp = ccfs.findLineStart(endOfPreviousLine); // find start of the previous line - now we have limit position
279: ftp = ccfs.findImportant(tokenPos, ftp, false, true); // find something important till the limit
280: if (ftp == null) {
281: return;
282: }
283:
284: // check that previous line does not end with "{" or line comment
285: ftp = ccfs.findNonWhitespace(endOfPreviousLine, null, true,
286: true);
287: if (ftp.getToken().getTokenID() == CCTokenContext.LINE_COMMENT
288: || ftp.getToken().getTokenID() == CCTokenContext.LBRACE
289: || ccfs.isPreprocessorLine(ftp.getToken())) {
290: return;
291: }
292:
293: // now move the token to the end of previous line
294: boolean remove = true;
295: while (remove) {
296: if (token.getPrevious() == endOfPreviousLine.getToken()) {
297: remove = false;
298: }
299: if (ccfs.canRemoveToken(token.getPrevious())) {
300: ccfs.removeToken(token.getPrevious());
301: } else {
302: return; // should never get here!
303: }
304: }
305: // insert one space before token
306: if (ccfs.canInsertToken(token)) {
307: ccfs.insertSpaces(token, 1);
308: }
309:
310: }
311:
312: /** insertNewLineBeforeKeyword such as else, catch, finally
313: * if getFormatNewlineBeforeBrace is true
314: */
315: private void insertNewLineBeforeToken(TokenItem token,
316: CCFormatSupport ccfs) {
317: FormatTokenPosition elsePos = ccfs.getPosition(token, 0);
318: FormatTokenPosition imp = ccfs.findImportant(elsePos, null,
319: true, true); // stop on line start
320: if (imp != null
321: && imp.getToken().getTokenContextPath() == ccfs
322: .getTokenContextPath()) {
323: // Insert new-line
324: if (ccfs.canInsertToken(token)) {
325: ccfs
326: .insertToken(
327: token,
328: ccfs.getValidWhitespaceTokenID(),
329: ccfs
330: .getValidWhitespaceTokenContextPath(),
331: "\n"); // NOI18N
332: ccfs.removeLineEndWhitespace(imp);
333: // reindent newly created line
334: ccfs.indentLine(elsePos);
335: }
336: }
337: }
338:
339: protected void formatLine(CCFormatSupport ccfs,
340: FormatTokenPosition pos) {
341: if (pos.getToken().getTokenID() == CCTokenContext.WHITESPACE) {
342: if (pos.getToken().getImage().indexOf('\n') == 0) {
343: return;
344: }
345: }
346: TokenItem token = ccfs.findLineStart(pos).getToken();
347: if (ccfs.isPreprocessorLine(token)) {
348: return;
349: }
350: boolean first = true;
351: while (token != null) {
352: if (!first
353: && token.getTokenID() == CCTokenContext.WHITESPACE) {
354: if (token.getImage().indexOf('\n') >= 0) {
355: return;
356: }
357: }
358: first = false;
359: /* if (ccfs.findLineEnd(ccfs.getPosition(token, 0)).getToken() == token) {
360: break; // at line end
361: }
362: */
363: if (token.getTokenContextPath() == ccfs
364: .getTokenContextPath()) {
365: switch (token.getTokenID().getNumericID()) {
366: case CCTokenContext.LBRACE_ID: // '{'
367: token = processBrace(ccfs, token);
368: break;
369:
370: case CCTokenContext.LPAREN_ID:
371: if (ccfs
372: .getFormatSpaceBeforeMethodCallParenthesis()) {
373: TokenItem prevToken = token.getPrevious();
374: if (prevToken != null
375: && prevToken.getTokenID() == CCTokenContext.IDENTIFIER) {
376: if (ccfs.canInsertToken(token)) {
377: ccfs
378: .insertToken(
379: token,
380: ccfs
381: .getWhitespaceTokenID(),
382: ccfs
383: .getWhitespaceTokenContextPath(),
384: " "); // NOI18N
385: }
386: }
387: } else {
388: // bugfix 9813: remove space before left parenthesis
389: TokenItem prevToken = token.getPrevious();
390: if (prevToken != null
391: && prevToken.getTokenID() == CCTokenContext.WHITESPACE
392: && prevToken.getImage().length() == 1) {
393: TokenItem prevprevToken = prevToken
394: .getPrevious();
395: if (prevprevToken != null
396: && prevprevToken.getTokenID() == CCTokenContext.IDENTIFIER) {
397: if (ccfs.canRemoveToken(prevToken)) {
398: ccfs.removeToken(prevToken);
399: }
400: }
401: }
402: }
403: break;
404:
405: case CCTokenContext.COMMA_ID:
406: TokenItem nextToken = token.getNext();
407: if (nextToken != null) {
408: if (ccfs.getFormatSpaceAfterComma()) {
409: // insert a space if one isn't already there
410: if (nextToken.getTokenID() != CCTokenContext.WHITESPACE) {
411: ccfs
412: .insertToken(
413: nextToken,
414: ccfs
415: .getValidWhitespaceTokenID(),
416: ccfs
417: .getWhitespaceTokenContextPath(),
418: " "); //NOI18N
419: }
420: } else {
421: if (nextToken.getTokenID() == CCTokenContext.WHITESPACE) {
422: ccfs.removeToken(nextToken);
423: }
424: }
425: }
426: break;
427: } // end switch
428: }
429: token = token.getNext();
430: } //end while loop
431: } //end formatLine()
432:
433: private TokenItem processBrace(CCFormatSupport ccfs,
434: TokenItem token) {
435: if (ccfs.isIndentOnly()) {
436: return token;
437: }
438: if (ccfs.getFormatNewlineBeforeBrace()
439: || ccfs.getFormatNewlineBeforeBraceDeclaration()) {
440: FormatTokenPosition lbracePos = ccfs.getPosition(token,
441: 0);
442: // Look for first important token in backward direction
443: FormatTokenPosition imp = ccfs.findImportant(lbracePos,
444: null, true, true); // stop on line start
445: if (imp != null
446: && imp.getToken().getTokenContextPath() == ccfs
447: .getTokenContextPath()) {
448: switch (imp.getToken().getTokenID().getNumericID()) {
449: case CCTokenContext.BLOCK_COMMENT_ID:
450: case CCTokenContext.LINE_COMMENT_ID:
451: break; // comments are ignored
452:
453: case CCTokenContext.RBRACKET_ID:
454: break; // array initializtion "ttt [] {...}"
455:
456: case CCTokenContext.COMMA_ID:
457: case CCTokenContext.EQ_ID:
458: case CCTokenContext.LBRACE_ID:
459: // multi array initialization
460: // static int[][] CONVERT_TABLE= { {3,5},
461: // {1,2}, {2,3}, ...
462: break;
463:
464: default:
465: // Check whether it isn't a "{ }" case
466: FormatTokenPosition next = ccfs.findImportant(
467: lbracePos, null, true, false);
468: if (next == null
469: || next.getToken() == null
470: || next.getToken().getTokenID() != CCTokenContext.RBRACE) {
471: // Insert new-line
472: if (isAddNewLine(ccfs, token)) {
473: if (ccfs.canInsertToken(token)) {
474: ccfs
475: .insertToken(
476: token,
477: ccfs
478: .getValidWhitespaceTokenID(),
479: ccfs
480: .getValidWhitespaceTokenContextPath(),
481: "\n"); // NOI18N
482: ccfs.removeLineEndWhitespace(imp);
483: // bug fix: 10225 - reindent newly created line
484: ccfs.indentLine(lbracePos);
485: token = imp.getToken();
486: }
487: } else {
488: return removeNewLine(token, ccfs);
489: }
490: }
491: break;
492: }// end switch
493: } else {
494: if (!isAddNewLine(ccfs, token)) {
495: return removeNewLine(token, ccfs);
496: }
497: }
498: } else {
499: return removeNewLine(token, ccfs);
500: }
501: return token;
502: }
503:
504: private boolean isAddNewLine(CCFormatSupport ccfs,
505: TokenItem token) {
506: TokenItem prev = ccfs.findImportantToken(token, null, true,
507: true);
508: if (prev == null) {
509: return true;
510: }
511: switch (prev.getTokenID().getNumericID()) {
512: case CCTokenContext.TRY_ID: // 'thy {'
513: case CCTokenContext.ELSE_ID: // 'else {'
514: case CCTokenContext.DO_ID:
515: return ccfs.getFormatNewlineBeforeBrace();
516: case CCTokenContext.SEMICOLON_ID:
517: return true;
518: }
519: if (prev.getTokenID().getNumericID() == CCTokenContext.RPAREN_ID) {
520: TokenItem imp = ccfs.findStatementStart(prev, false);
521: if (imp != null) {
522: switch (imp.getTokenID().getNumericID()) {
523: case CCTokenContext.CATCH_ID: // 'catch (...) {'
524: case CCTokenContext.IF_ID:
525: case CCTokenContext.FOR_ID:
526: case CCTokenContext.WHILE_ID:
527: case CCTokenContext.SWITCH_ID:
528: return ccfs.getFormatNewlineBeforeBrace();
529: }
530: }
531: }
532: return ccfs.getFormatNewlineBeforeBraceDeclaration();
533: }
534:
535: private TokenItem removeNewLine(TokenItem token,
536: CCFormatSupport ccfs) {
537: FormatTokenPosition lbracePos = ccfs.getPosition(token, 0);
538:
539: // Check that nothing exists before "{"
540: if (ccfs.findNonWhitespace(lbracePos, null, true, true) != null) {
541: return token;
542: }
543: // Check that nothing exists after "{", but ignore comments
544: if (ccfs.getNextPosition(lbracePos) != null) {
545: if (ccfs.findImportant(ccfs.getNextPosition(lbracePos),
546: null, true, false) != null) {
547: return token;
548: }
549: }
550:
551: // check that on previous line is some stmt
552: FormatTokenPosition ftp = ccfs.findLineStart(lbracePos); // find start of current line
553: FormatTokenPosition endOfPreviousLine = ccfs
554: .getPreviousPosition(ftp); // go one position back - means previous line
555: if (endOfPreviousLine == null
556: || endOfPreviousLine.getToken().getTokenID() != CCTokenContext.WHITESPACE) {
557: return token;
558: }
559: ftp = ccfs.findLineStart(endOfPreviousLine); // find start of the previous line - now we have limit position
560: ftp = ccfs.findImportant(lbracePos, ftp, false, true); // find something important till the limit
561: if (ftp == null) {
562: return token;
563: }
564:
565: // check that previous line does not end with "{" or line comment
566: ftp = ccfs.findNonWhitespace(endOfPreviousLine, null, true,
567: true);
568: if (ftp.getToken().getTokenID() == CCTokenContext.LINE_COMMENT
569: || ftp.getToken().getTokenID() == CCTokenContext.LBRACE
570: || ccfs.isPreprocessorLine(ftp.getToken())) {
571: return token;
572: }
573:
574: // now move the "{" to the end of previous line
575: boolean remove = true;
576: while (remove) {
577: if (token.getPrevious() == endOfPreviousLine.getToken()) {
578: remove = false;
579: }
580: if (ccfs.canRemoveToken(token.getPrevious())) {
581: ccfs.removeToken(token.getPrevious());
582: } else {
583: break;
584: } // should never get here!
585: }
586: // insert one space before "{"
587: if (ccfs.canInsertToken(token)) {
588: ccfs.insertSpaces(token, 1);
589: }
590: return token;
591: }
592: } // end class CCLayer
593: }
|