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:
042: package org.netbeans.modules.cnd.editor.cplusplus;
043:
044: import java.util.Stack;
045: import javax.swing.text.BadLocationException;
046: import javax.swing.text.Caret;
047: import org.netbeans.editor.BaseDocument;
048: import org.netbeans.editor.TokenID;
049: import org.netbeans.editor.TokenProcessor;
050: import org.netbeans.editor.TokenContextPath;
051: import org.netbeans.editor.ext.ExtSyntaxSupport;
052: import org.netbeans.editor.SyntaxSupport;
053: import org.netbeans.editor.TokenItem;
054: import org.netbeans.editor.Settings;
055: import org.netbeans.editor.SettingsNames;
056: import org.netbeans.editor.Utilities;
057:
058: /**
059: * This static class groups the whole aspect of bracket
060: * completion. It is defined to clearly separate the functionality
061: * and keep actions clean.
062: * The methods of the class are called from different actions as
063: * KeyTyped, DeletePreviousChar.
064: */
065: public class BracketCompletion {
066: /**
067: * A hook method called after a character was inserted into the
068: * document. The function checks for special characters for
069: * completion ()[]'"{} and other conditions and optionally performs
070: * changes to the doc and or caret (complets braces, moves caret,
071: * etc.)
072: * @param doc the document where the change occurred
073: * @param dotPos position of the character insertion
074: * @param caret caret
075: * @param ch the character that was inserted
076: * @throws BadLocationException if dotPos is not correct
077: */
078: static void charInserted(BaseDocument doc, int dotPos, Caret caret,
079: char ch) throws BadLocationException {
080: SyntaxSupport syntaxSupport = doc.getSyntaxSupport();
081: if (!(syntaxSupport instanceof ExtSyntaxSupport)
082: || !completionSettingEnabled()) {
083: return;
084: }
085:
086: ExtSyntaxSupport support = (ExtSyntaxSupport) syntaxSupport;
087: if (ch == ')' || ch == ']' || ch == '(' || ch == '[') {
088: TokenID tokenAtDot = support.getTokenID(dotPos);
089: if (tokenAtDot == CCTokenContext.RBRACKET
090: || tokenAtDot == CCTokenContext.RPAREN) {
091: skipClosingBracket(doc, caret, ch);
092: } else if (tokenAtDot == CCTokenContext.LBRACKET
093: || tokenAtDot == CCTokenContext.LPAREN) {
094: completeOpeningBracket(doc, dotPos, caret, ch);
095: }
096: } else if (ch == '\"' || ch == '\'') {
097: completeQuote(doc, dotPos, caret, ch);
098: } else if (ch == ';') {
099: moveSemicolon(doc, dotPos, caret);
100: } else if (ch == '<') {
101: TokenID tokenAtDot = support.getTokenID(dotPos);
102: if (tokenAtDot == CCTokenContext.INCOMPLETE_SYS_INCLUDE) {
103: completeOpeningBracket(doc, dotPos, caret, ch);
104: }
105: } else if (ch == '>') {
106: TokenID tokenAtDot = support.getTokenID(dotPos);
107: if (tokenAtDot == CCTokenContext.SYS_INCLUDE
108: || tokenAtDot == CCTokenContext.INCOMPLETE_SYS_INCLUDE) {
109: char match[] = doc.getChars(dotPos + 1, 1);
110: if (match != null && match[0] == '>') {
111: doc.remove(dotPos + 1, 1);
112: }
113: }
114: }
115: }
116:
117: private static void moveSemicolon(BaseDocument doc, int dotPos,
118: Caret caret) throws BadLocationException {
119: int eolPos = Utilities.getRowEnd(doc, dotPos);
120: ExtSyntaxSupport ssup = (ExtSyntaxSupport) doc
121: .getSyntaxSupport();
122: int lastParenPos = dotPos;
123: TokenItem token = ssup.getTokenChain(dotPos, eolPos);
124: for (TokenItem item = token.getNext(); item != null
125: && item.getOffset() <= eolPos; item = item.getNext()) {
126: TokenID tokenID = item.getTokenID();
127: if (tokenID == CCTokenContext.RPAREN) {
128: lastParenPos = item.getOffset();
129: } else if (tokenID != CCTokenContext.WHITESPACE) {
130: return;
131: }
132: }
133: if (isForLoopSemicolon(token) || posWithinAnyQuote(doc, dotPos)) {
134: return;
135: }
136: // may be check offsets?
137: // if (lastParenPos != dotPos) {
138: doc.remove(dotPos, 1);
139: doc.insertString(lastParenPos, ";", null); // NOI18N
140: caret.setDot(lastParenPos + 1);
141: // }
142: }
143:
144: private static boolean isForLoopSemicolon(TokenItem token) {
145: if (token == null
146: || token.getTokenID() != CCTokenContext.SEMICOLON) {
147: return false;
148: }
149: int parDepth = 0; // parenthesis depth
150: int braceDepth = 0; // brace depth
151: boolean semicolonFound = false; // next semicolon
152: token = token.getPrevious(); // ignore this semicolon
153: while (token != null) {
154: if (token.getTokenID() == CCTokenContext.LPAREN) {
155: if (parDepth == 0) { // could be a 'for ('
156: token = token.getPrevious();
157: while (token != null
158: && (token.getTokenID() == CCTokenContext.WHITESPACE
159: || token.getTokenID() == CCTokenContext.BLOCK_COMMENT || token
160: .getTokenID() == CCTokenContext.LINE_COMMENT)) {
161: token = token.getPrevious();
162: }
163: if (token.getTokenID() == CCTokenContext.FOR) {
164: return true;
165: }
166: return false;
167: } else { // non-zero depth
168: parDepth--;
169: }
170: } else if (token.getTokenID() == CCTokenContext.RPAREN) {
171: parDepth++;
172: } else if (token.getTokenID() == CCTokenContext.LBRACE) {
173: if (braceDepth == 0) { // unclosed left brace
174: return false;
175: }
176: braceDepth--;
177: } else if (token.getTokenID() == CCTokenContext.RBRACE) {
178: braceDepth++;
179: } else if (token.getTokenID() == CCTokenContext.SEMICOLON) {
180: if (semicolonFound) { // one semicolon already found
181: return false;
182: }
183: semicolonFound = true;
184: }
185: token = token.getPrevious();
186: }
187: return false;
188: }
189:
190: /**
191: * Hook called after a character *ch* was backspace-deleted from
192: * *doc*. The function possibly removes bracket or quote pair if
193: * appropriate.
194: * @param doc the document
195: * @param dotPos position of the change
196: * @param caret caret
197: * @param ch the character that was deleted
198: */
199: static void charBackspaced(BaseDocument doc, int dotPos,
200: Caret caret, char ch) throws BadLocationException {
201: if (completionSettingEnabled()) {
202: if (ch == '(' || ch == '[') {
203: TokenID tokenAtDot = ((ExtSyntaxSupport) doc
204: .getSyntaxSupport()).getTokenID(dotPos);
205: if ((tokenAtDot == CCTokenContext.RBRACKET && tokenBalance(
206: doc, CCTokenContext.LBRACKET,
207: CCTokenContext.RBRACKET) != 0)
208: || (tokenAtDot == CCTokenContext.RPAREN && tokenBalance(
209: doc, CCTokenContext.LPAREN,
210: CCTokenContext.RPAREN) != 0)) {
211: doc.remove(dotPos, 1);
212: }
213: } else if (ch == '\"') {
214: char match[] = doc.getChars(dotPos, 1);
215: if (match != null && match[0] == '\"') {
216: doc.remove(dotPos, 1);
217: }
218: } else if (ch == '\'') {
219: char match[] = doc.getChars(dotPos, 1);
220: if (match != null && match[0] == '\'') {
221: doc.remove(dotPos, 1);
222: }
223: } else if (ch == '<') {
224: TokenID tokenAtDot = ((ExtSyntaxSupport) doc
225: .getSyntaxSupport()).getTokenID(dotPos);
226: if (tokenAtDot == CCTokenContext.GT) {
227: TokenItem item = ((ExtSyntaxSupport) doc
228: .getSyntaxSupport()).getTokenChain(
229: dotPos - 1, dotPos);
230: TokenItem itemPrev = item != null ? item
231: .getPrevious() : null;
232: TokenID[] matchIDs = new TokenID[] {
233: CCTokenContext.CPPINCLUDE,
234: CCTokenContext.CPPINCLUDE_NEXT };
235: if ((item != null && matchIDs(item.getTokenID(),
236: matchIDs))
237: || (itemPrev != null && matchIDs(itemPrev
238: .getTokenID(), matchIDs))) {
239: char match[] = doc.getChars(dotPos, 1);
240: if (match != null && match[0] == '>') {
241: doc.remove(dotPos, 1);
242: }
243: }
244: }
245: }
246: }
247: }
248:
249: /**
250: * Resolve whether pairing right curly should be added automatically
251: * at the caret position or not.
252: * <br>
253: * There must be only whitespace or line comment or block comment
254: * between the caret position
255: * and the left brace and the left brace must be on the same line
256: * where the caret is located.
257: * <br>
258: * The caret must not be "contained" in the opened block comment token.
259: *
260: * @param doc document in which to operate.
261: * @param caretOffset offset of the caret.
262: * @return true if a right brace '}' should be added
263: * or false if not.
264: */
265: static boolean isAddRightBrace(BaseDocument doc, int caretOffset)
266: throws BadLocationException {
267: boolean addRightBrace = false;
268: if (completionSettingEnabled()) {
269: if (caretOffset > 0) {
270: // Check whether line ends with '{' ignoring any whitespace
271: // or comments
272: int tokenOffset = caretOffset;
273: TokenItem token = ((ExtSyntaxSupport) doc
274: .getSyntaxSupport()).getTokenChain(
275: tokenOffset - 1, tokenOffset);
276: if (token == null) {
277: return false;
278: }
279: addRightBrace = true; // suppose that right brace should be added
280:
281: // Disable right brace adding if caret not positioned within whitespace
282: // or line comment
283: int off = (caretOffset - token.getOffset());
284: if (off > 0 && off < token.getImage().length()) { // caret contained in token
285: switch (token.getTokenID().getNumericID()) {
286: case CCTokenContext.WHITESPACE_ID:
287: case CCTokenContext.LINE_COMMENT_ID:
288: break; // the above tokens are OK
289:
290: default:
291: // Disable brace adding for the remaining ones
292: addRightBrace = false;
293: }
294: }
295:
296: if (addRightBrace) { // still candidate for adding
297: int caretRowStartOffset = Utilities.getRowStart(
298: doc, caretOffset);
299:
300: // Check whether there are only whitespace or comment tokens
301: // between caret and left brace and check only on the line
302: // with the caret
303: while (token != null
304: && token.getOffset() >= caretRowStartOffset) {
305: boolean ignore = false;
306: // Assuming java token context here
307: switch (token.getTokenID().getNumericID()) {
308: case CCTokenContext.WHITESPACE_ID:
309: case CCTokenContext.BLOCK_COMMENT_ID:
310: case CCTokenContext.LINE_COMMENT_ID:
311: // skip
312: ignore = true;
313: break;
314: }
315:
316: if (ignore) {
317: token = token.getPrevious();
318: } else { // break on the current token
319: break;
320: }
321: }
322:
323: if (token == null
324: || token.getTokenID() != CCTokenContext.LBRACE // must be left brace
325: || token.getOffset() < caretRowStartOffset // on the same line as caret
326: ) {
327: addRightBrace = false;
328: }
329:
330: }
331:
332: if (addRightBrace) { // Finally check the brace balance whether there are any missing right braces
333: addRightBrace = (braceBalance(doc) > 0);
334: }
335: }
336: }
337: return addRightBrace;
338: }
339:
340: /**
341: * Returns position of the first unpaired closing paren/brace/bracket from the caretOffset
342: * till the end of caret row. If there is no such element, position after the last non-white
343: * character on the caret row is returned.
344: */
345: static int getRowOrBlockEnd(BaseDocument doc, int caretOffset)
346: throws BadLocationException {
347: int rowEnd = Utilities.getRowLastNonWhite(doc, caretOffset);
348: if (rowEnd == -1 || caretOffset >= rowEnd) {
349: return caretOffset;
350: }
351: rowEnd += 1;
352: int parenBalance = 0;
353: int braceBalance = 0;
354: int bracketBalance = 0;
355: ExtSyntaxSupport ssup = (ExtSyntaxSupport) doc
356: .getSyntaxSupport();
357: TokenItem token = ssup.getTokenChain(caretOffset, rowEnd);
358: while (token != null && token.getOffset() < rowEnd) {
359: switch (token.getTokenID().getNumericID()) {
360: case CCTokenContext.LPAREN_ID:
361: parenBalance++;
362: break;
363: case CCTokenContext.RPAREN_ID:
364: if (parenBalance-- == 0)
365: return token.getOffset();
366: break;
367: case CCTokenContext.LBRACE_ID:
368: braceBalance++;
369: break;
370: case CCTokenContext.RBRACE_ID:
371: if (braceBalance-- == 0)
372: return token.getOffset();
373: break;
374: case CCTokenContext.LBRACKET_ID:
375: bracketBalance++;
376: break;
377: case CCTokenContext.RBRACKET_ID:
378: if (bracketBalance-- == 0)
379: return token.getOffset();
380: break;
381: }
382: token = token.getNext();
383: }
384: return rowEnd;
385: }
386:
387: /**
388: * Counts the number of braces starting at dotPos to the end of the
389: * document. Every occurence of { increses the count by 1, every
390: * occurrence of } decreses the count by 1. The result is returned.
391: * @return The number of { - number of } (>0 more { than } ,<0 more } than {)
392: */
393: private static int braceBalance(BaseDocument doc)
394: throws BadLocationException {
395: return tokenBalance(doc, CCTokenContext.LBRACE,
396: CCTokenContext.RBRACE);
397: }
398:
399: /**
400: * The same as braceBalance but generalized to any pair of matching
401: * tokens.
402: * @param open the token that increses the count
403: * @param close the token that decreses the count
404: */
405: private static int tokenBalance(BaseDocument doc, TokenID open,
406: TokenID close) throws BadLocationException {
407:
408: ExtSyntaxSupport sup = (ExtSyntaxSupport) doc
409: .getSyntaxSupport();
410: BalanceTokenProcessor balanceTP = new BalanceTokenProcessor(
411: open, close);
412: sup.tokenizeText(balanceTP, 0, doc.getLength(), true);
413: return balanceTP.getBalance();
414: }
415:
416: /**
417: * A hook to be called after closing bracket ) or ] was inserted into
418: * the document. The method checks if the bracket should stay there
419: * or be removed and some exisitng bracket just skipped.
420: *
421: * @param doc the document
422: * @param dotPos position of the inserted bracket
423: * @param caret caret
424: * @param theBracket the bracket character ']' or ')'
425: */
426: private static void skipClosingBracket(BaseDocument doc,
427: Caret caret, char theBracket) throws BadLocationException {
428: TokenID bracketId = (theBracket == ')') ? CCTokenContext.RPAREN
429: : CCTokenContext.RBRACKET;
430: int caretOffset = caret.getDot();
431: if (isSkipClosingBracket(doc, caretOffset, bracketId)) {
432: doc.remove(caretOffset - 1, 1);
433: caret.setDot(caretOffset); // skip closing bracket
434: }
435: }
436:
437: /**
438: * Check whether the typed bracket should stay in the document
439: * or be removed.
440: * <br>
441: * This method is called by <code>skipClosingBracket()</code>.
442: *
443: * @param doc document into which typing was done.
444: * @param caretOffset
445: */
446: static boolean isSkipClosingBracket(BaseDocument doc,
447: int caretOffset, TokenID bracketId)
448: throws BadLocationException {
449: // First check whether the caret is not after the last char in the document
450: // because no bracket would follow then so it could not be skipped.
451: if (caretOffset == doc.getLength()) {
452: return false; // no skip in this case
453: }
454:
455: boolean skipClosingBracket = false; // by default do not remove
456: // Examine token at the caret offset
457: TokenItem token = ((ExtSyntaxSupport) doc.getSyntaxSupport())
458: .getTokenChain(caretOffset, caretOffset + 1);
459: // Check whether character follows the bracket is the same bracket
460: if (token != null && token.getTokenID() == bracketId) {
461: int bracketIntId = bracketId.getNumericID();
462: int leftBracketIntId = (bracketIntId == CCTokenContext.RPAREN_ID) ? CCTokenContext.LPAREN_ID
463: : CCTokenContext.LBRACKET_ID;
464:
465: // Skip all the brackets of the same type that follow the last one
466: TokenItem nextToken = token.getNext();
467: while (nextToken != null
468: && nextToken.getTokenID() == bracketId) {
469: token = nextToken;
470: nextToken = nextToken.getNext();
471: }
472: // token var points to the last bracket in a group of two or more right brackets
473: // Attempt to find the left matching bracket for it
474: // Search would stop on an extra opening left brace if found
475: int braceBalance = 0; // balance of '{' and '}'
476: int bracketBalance = -1; // balance of the brackets or parenthesis
477: TokenItem lastRBracket = token;
478: token = token.getPrevious();
479: boolean finished = false;
480: while (!finished && token != null) {
481: int tokenIntId = token.getTokenID().getNumericID();
482: switch (tokenIntId) {
483: case CCTokenContext.LPAREN_ID:
484: case CCTokenContext.LBRACKET_ID:
485: if (tokenIntId == bracketIntId) {
486: bracketBalance++;
487: if (bracketBalance == 0) {
488: if (braceBalance != 0) {
489: // Here the bracket is matched but it is located
490: // inside an unclosed brace block
491: // e.g. ... ->( } a()|)
492: // which is in fact illegal but it's a question
493: // of what's best to do in this case.
494: // We chose to leave the typed bracket
495: // by setting bracketBalance to 1.
496: // It can be revised in the future.
497: bracketBalance = 1;
498: }
499: finished = true;
500: }
501: }
502: break;
503:
504: case CCTokenContext.RPAREN_ID:
505: case CCTokenContext.RBRACKET_ID:
506: if (tokenIntId == bracketIntId) {
507: bracketBalance--;
508: }
509: break;
510: case CCTokenContext.LBRACE_ID:
511: braceBalance++;
512: if (braceBalance > 0) { // stop on extra left brace
513: finished = true;
514: }
515: break;
516:
517: case CCTokenContext.RBRACE_ID:
518: braceBalance--;
519: break;
520:
521: }
522:
523: token = token.getPrevious(); // done regardless of finished flag state
524: }
525:
526: if (bracketBalance != 0) { // not found matching bracket
527: // Remove the typed bracket as it's unmatched
528: skipClosingBracket = true;
529:
530: } else { // the bracket is matched
531: // Now check whether the bracket would be matched
532: // when the closing bracket would be removed
533: // i.e. starting from the original lastRBracket token
534: // and search for the same bracket to the right in the text
535: // The search would stop on an extra right brace if found
536: braceBalance = 0;
537: bracketBalance = 1; // simulate one extra left bracket
538: token = lastRBracket.getNext();
539: finished = false;
540: while (!finished && token != null) {
541: int tokenIntId = token.getTokenID().getNumericID();
542: switch (tokenIntId) {
543: case CCTokenContext.LPAREN_ID:
544: case CCTokenContext.LBRACKET_ID:
545: if (tokenIntId == leftBracketIntId) {
546: bracketBalance++;
547: }
548: break;
549:
550: case CCTokenContext.RPAREN_ID:
551: case CCTokenContext.RBRACKET_ID:
552: if (tokenIntId == bracketIntId) {
553: bracketBalance--;
554: if (bracketBalance == 0) {
555: if (braceBalance != 0) {
556: // Here the bracket is matched but it is located
557: // inside an unclosed brace block
558: // which is in fact illegal but it's a question
559: // of what's best to do in this case.
560: // We chose to leave the typed bracket
561: // by setting bracketBalance to -1.
562: // It can be revised in the future.
563: bracketBalance = -1;
564: }
565: finished = true;
566: }
567: }
568: break;
569:
570: case CCTokenContext.LBRACE_ID:
571: braceBalance++;
572: break;
573:
574: case CCTokenContext.RBRACE_ID:
575: braceBalance--;
576: if (braceBalance < 0) { // stop on extra right brace
577: finished = true;
578: }
579: break;
580:
581: }
582:
583: token = token.getPrevious(); // done regardless of finished flag state
584: }
585:
586: // If bracketBalance == 0 the bracket would be matched
587: // by the bracket that follows the last right bracket.
588: skipClosingBracket = (bracketBalance == 0);
589: }
590: }
591: return skipClosingBracket;
592: }
593:
594: /**
595: * Check for various conditions and possibly add a pairing bracket
596: * to the already inserted.
597: * @param doc the document
598: * @param dotPos position of the opening bracket (already in the doc)
599: * @param caret caret
600: * @param theBracket the bracket that was inserted
601: */
602: private static void completeOpeningBracket(BaseDocument doc,
603: int dotPos, Caret caret, char theBracket)
604: throws BadLocationException {
605: if (isCompletablePosition(doc, dotPos + 1)) {
606: String matchinBracket = "" + matching(theBracket);
607: doc.insertString(dotPos + 1, matchinBracket, null);
608: caret.setDot(dotPos + 1);
609: }
610: }
611:
612: private static boolean isEscapeSequence(BaseDocument doc, int dotPos)
613: throws BadLocationException {
614: if (dotPos <= 0)
615: return false;
616: char previousChar = doc.getChars(dotPos - 1, 1)[0];
617: return previousChar == '\\';
618: }
619:
620: /**
621: * Check for conditions and possibly complete an already inserted
622: * quote .
623: * @param doc the document
624: * @param dotPos position of the opening bracket (already in the doc)
625: * @param caret caret
626: * @param theBracket the character that was inserted
627: */
628: private static void completeQuote(BaseDocument doc, int dotPos,
629: Caret caret, char theBracket) throws BadLocationException {
630: if (isEscapeSequence(doc, dotPos)) {
631: return;
632: }
633: TokenID[] tokenIDs = theBracket == '\"' ? new TokenID[] {
634: CCTokenContext.STRING_LITERAL,
635: CCTokenContext.INCOMPLETE_USR_INCLUDE }
636: : new TokenID[] { CCTokenContext.CHAR_LITERAL };
637: if ((posWithinQuotes(doc, dotPos + 1, theBracket, tokenIDs) && isCompletablePosition(
638: doc, dotPos + 1))
639: && (isUnclosedStringAtLineEnd(doc, dotPos, tokenIDs) && ((doc
640: .getLength() == dotPos + 1) || (doc.getLength() != dotPos + 1 && doc
641: .getChars(dotPos + 1, 1)[0] != theBracket)))) {
642: doc.insertString(dotPos + 1, "" + theBracket, null);
643: caret.setDot(dotPos + 1);
644: } else {
645: char[] charss = doc.getChars(dotPos + 1, 1);
646: // System.out.println("NOT Within string, " + new String(charss));
647: if (charss != null && charss[0] == theBracket) {
648: doc.remove(dotPos + 1, 1);
649: }
650: }
651: }
652:
653: /**
654: * Checks whether dotPos is a position at which bracket and quote
655: * completion is performed. Brackets and quotes are not completed
656: * everywhere but just at suitable places .
657: * @param doc the document
658: * @param dotPos position to be tested
659: */
660: private static boolean isCompletablePosition(BaseDocument doc,
661: int dotPos) throws BadLocationException {
662: if (dotPos == doc.getLength()) // there's no other character to test
663: return true;
664: else {
665: // test that we are in front of ) , " or '
666: char chr = doc.getChars(dotPos, 1)[0];
667: return (chr == ')' || chr == ',' || chr == '\"'
668: || chr == '\'' || chr == ' ' || chr == '-'
669: || chr == '+' || chr == '|' || chr == '&'
670: || chr == ']' || chr == '}' || chr == '\n'
671: || chr == '\t' || chr == ';');
672: }
673: }
674:
675: /**
676: * Returns true if bracket completion is enabled in options.
677: */
678: private static boolean completionSettingEnabled() {
679: Boolean value = ((Boolean) Settings.getValue(CCKit.class,
680: SettingsNames.PAIR_CHARACTERS_COMPLETION));
681: if (value != null) {
682: return value.booleanValue();
683: }
684: return true;
685: }
686:
687: /**
688: * Returns for an opening bracket or quote the appropriate closing
689: * character.
690: */
691: private static char matching(char theBracket) {
692: switch (theBracket) {
693: case '(':
694: return ')';
695: case '[':
696: return ']';
697: case '\"':
698: return '\"'; // NOI18N
699: case '\'':
700: return '\'';
701: case '<':
702: return '>';
703:
704: default:
705: return ' ';
706: }
707: }
708:
709: /**
710: * posWithinString(doc, pos) iff position *pos* is within a string
711: * literal in document doc.
712: * @param doc the document
713: * @param dotPos position to be tested
714: */
715: static boolean posWithinString(BaseDocument doc, int dotPos) {
716: return posWithinQuotes(doc, dotPos, '\"', new TokenID[] {
717: CCTokenContext.STRING_LITERAL,
718: CCTokenContext.USR_INCLUDE });
719: }
720:
721: /**
722: * Generalized posWithingString to any token and delimiting
723: * character. It works for tokens are delimited by *quote* and
724: * extend up to the other *quote* or whitespace in case of an
725: * incomplete token.
726: * @param doc the document
727: * @param dotPos position to be tested
728: */
729: static boolean posWithinQuotes(BaseDocument doc, int dotPos,
730: char quote, TokenID[] tokenIDs) {
731: try {
732: MyTokenProcessor proc = new MyTokenProcessor();
733: doc.getSyntaxSupport().tokenizeText(proc, dotPos - 1,
734: doc.getLength(), true);
735: if (matchIDs(proc.tokenID, tokenIDs)) {
736: return (dotPos - proc.tokenStart == 1 || doc.getChars(
737: dotPos - 1, 1)[0] != quote);
738: }
739: return false;
740: } catch (BadLocationException ex) {
741: return false;
742: }
743: }
744:
745: static boolean posWithinAnyQuote(BaseDocument doc, int dotPos) {
746: try {
747: MyTokenProcessor proc = new MyTokenProcessor();
748: doc.getSyntaxSupport().tokenizeText(proc, dotPos - 1,
749: doc.getLength(), true);
750: if (proc.tokenID == CCTokenContext.STRING_LITERAL
751: || proc.tokenID == CCTokenContext.CHAR_LITERAL
752: || proc.tokenID == CCTokenContext.SYS_INCLUDE
753: || proc.tokenID == CCTokenContext.USR_INCLUDE) {
754: char[] ch = doc.getChars(dotPos - 1, 1);
755: return dotPos - proc.tokenStart == 1
756: || (ch[0] != '\"' && ch[0] != '\'');
757: }
758: return false;
759: } catch (BadLocationException ex) {
760: return false;
761: }
762: }
763:
764: static boolean isUnclosedStringAtLineEnd(BaseDocument doc,
765: int dotPos, TokenID[] tokenIDs) {
766: try {
767: MyTokenProcessor proc = new MyTokenProcessor();
768: doc.getSyntaxSupport().tokenizeText(proc,
769: Utilities.getRowLastNonWhite(doc, dotPos),
770: doc.getLength(), true);
771: return matchIDs(proc.tokenID, tokenIDs);
772: } catch (BadLocationException ex) {
773: return false;
774: }
775: }
776:
777: static boolean matchIDs(TokenID toCheck, TokenID[] checkWith) {
778: for (int i = checkWith.length - 1; i >= 0; i--) {
779: if (toCheck == checkWith[i]) {
780: return true;
781: }
782: }
783: return false;
784: }
785:
786: /**
787: * A token processor used to find out the length of a token.
788: */
789: static class MyTokenProcessor implements TokenProcessor {
790: public TokenID tokenID = null;
791: public int tokenStart = -1;
792:
793: public boolean token(TokenID tokenID, TokenContextPath tcp,
794: int tokBuffOffset, int tokLength) {
795: this .tokenStart = tokenBuffer2DocumentOffset(tokBuffOffset);
796: this .tokenID = tokenID;
797: // System.out.println("token " + tokenID.getName() + " at " + tokenStart + " (" +
798: // tokBuffOffset + ") len:" + tokLength);
799: return false;
800: }
801:
802: public int eot(int offset) { // System.out.println("EOT");
803: return 0;
804: }
805:
806: public void nextBuffer(char[] buffer, int offset, int len,
807: int startPos, int preScan, boolean lastBuffer) {
808: // System.out.println("nextBuffer "+ new String(buffer) + "," + offset + "len: " + len + " startPos:"+startPos + " preScan:" + preScan + " lastBuffer:" + lastBuffer);
809: this .bufferStartPos = startPos - offset;
810: }
811:
812: private int bufferStartPos = 0;
813:
814: private int tokenBuffer2DocumentOffset(int offs) {
815: return offs + bufferStartPos;
816: }
817: }
818:
819: /**
820: * Token processor for finding of balance of brackets and braces.
821: */
822: private static class BalanceTokenProcessor implements
823: TokenProcessor {
824: private TokenID leftTokenID;
825: private TokenID rightTokenID;
826: private Stack<Integer> stack = new Stack<Integer>();
827:
828: private int balance;
829: private boolean isDefine;
830: private char[] buffer;
831: private int bufferStartPos;
832:
833: BalanceTokenProcessor(TokenID leftTokenID, TokenID rightTokenID) {
834: this .leftTokenID = leftTokenID;
835: this .rightTokenID = rightTokenID;
836: }
837:
838: public boolean token(TokenID tokenID, TokenContextPath tcp,
839: int tokBuffOffset, int tokLength) {
840:
841: if (tokenID.getCategory() == CCTokenContext.CPP) {
842: switch (tokenID.getNumericID()) {
843: case CCTokenContext.CPPIF_ID:
844: case CCTokenContext.CPPIFDEF_ID:
845: case CCTokenContext.CPPIFNDEF_ID:
846: stack.push(balance);
847: break;
848: case CCTokenContext.CPPELIF_ID:
849: case CCTokenContext.CPPELSE_ID:
850: if (!stack.empty()) {
851: balance = stack.peek();
852: }
853: break;
854: case CCTokenContext.CPPENDIF_ID:
855: if (!stack.empty()) {
856: stack.pop();
857: }
858: break;
859: case CCTokenContext.CPPDEFINE_ID:
860: isDefine = true;
861: break;
862: }
863: } else {
864: if (tokenID == leftTokenID) {
865: if (!isDefine) {
866: balance++;
867: }
868: } else if (tokenID == rightTokenID) {
869: if (!isDefine) {
870: balance--;
871: }
872: } else if (tokenID.getNumericID() == CCTokenContext.WHITESPACE_ID) {
873: for (int i = tokBuffOffset; i < tokBuffOffset
874: + tokLength; i++) {
875: if (buffer[i] == '\n') {
876: isDefine = false;
877: }
878: }
879: }
880: }
881: return true;
882: }
883:
884: public int eot(int offset) {
885: return 0;
886: }
887:
888: public void nextBuffer(char[] buffer, int offset, int len,
889: int startPos, int preScan, boolean lastBuffer) {
890: this .buffer = buffer;
891: bufferStartPos = startPos - offset;
892: }
893:
894: public int getBalance() {
895: return balance;
896: }
897:
898: }
899:
900: }
|