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