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.html;
043:
044: import javax.swing.text.BadLocationException;
045: import javax.swing.text.Caret;
046:
047: import org.netbeans.api.html.lexer.HTMLTokenId;
048:
049: import org.netbeans.api.lexer.Token;
050: import org.netbeans.api.lexer.TokenHierarchy;
051: import org.netbeans.api.lexer.TokenSequence;
052:
053: import org.netbeans.editor.BaseDocument;
054: import org.netbeans.editor.ext.ExtSyntaxSupport;
055:
056: /**
057: * This static class groups the whole aspect of bracket
058: * completion. It is defined to clearly separate the functionality
059: * and keep actions clean.
060: * The methods of the class are called from different actions as
061: * KeyTyped, DeletePreviousChar.
062: */
063: class HTMLAutoCompletion {
064:
065: //an index of lastly completed equals sign
066: private static int equalsSignInsertedOffset = -1;
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
079: */
080: static void charInserted(BaseDocument doc, int dotPos, Caret caret,
081: char ch) throws BadLocationException {
082: String mimeType = (String) doc.getProperty("mimeType"); //NOI18N
083: if ("text/html".equals(mimeType)) { //NOI18N
084: if (ch == '=') {
085: completeQuotes(doc, dotPos, caret);
086: } else if (ch == '"') {
087: //user has pressed quotation mark
088: handleQuotationMark(doc, dotPos, caret);
089: } else {
090: //user has pressed a key so I need to cancel the "quotation consuming mode"
091: equalsSignInsertedOffset = -1;
092: }
093: }
094: }
095:
096: //called when user deleted something in the document
097: static void charDeleted(BaseDocument doc, int dotPos, Caret caret,
098: char ch) {
099: equalsSignInsertedOffset = -1;
100: }
101:
102: private static void handleQuotationMark(BaseDocument doc,
103: int dotPos, Caret caret) throws BadLocationException {
104: if (equalsSignInsertedOffset != -1) {
105: //test whether the cursor is between completed quotations: attrname="|"
106: //this situation can happen when user autocompletes ="|",
107: //moves cursor somewhere else and type "
108: if (dotPos == (equalsSignInsertedOffset + ("=\"".length()))) {
109: //remove the quotation mark
110: doc.remove(dotPos, 1);
111: caret.setDot(dotPos);
112: }
113:
114: } else {
115: //test whether the user typed an ending quotation in the attribute value
116: doc.readLock();
117: try {
118: TokenHierarchy hi = TokenHierarchy.get(doc);
119: TokenSequence ts = hi.tokenSequence();
120:
121: ts.move(dotPos);
122: if (!ts.moveNext()) {
123: return; //no token
124: }
125:
126: Token token = ts.token();
127: if (token.id() == HTMLTokenId.VALUE) {
128: //test if the user inserted the qutation in an attribute value and before
129: //an already existing end quotation
130: //the text looks following in such a situation:
131: //
132: // atrname="abcd|"", where offset of the | == dotPos
133: if ("\"\"".equals(doc.getText(dotPos, 2))) {
134: doc.remove(dotPos, 1);
135: caret.setDot(dotPos + 1);
136: }
137: }
138: } finally {
139: doc.readUnlock();
140: }
141: }
142: //reset the semaphore
143: equalsSignInsertedOffset = -1;
144: }
145:
146: private static void completeQuotes(BaseDocument doc, int dotPos,
147: Caret caret) throws BadLocationException {
148: doc.readLock();
149: try {
150: TokenHierarchy hi = TokenHierarchy.get(doc);
151: TokenSequence ts = hi.tokenSequence();
152:
153: ts.move(dotPos);
154: if (!ts.moveNext()) {
155: return; //no token
156: }
157:
158: Token token = ts.token();
159:
160: int dotPosAfterTypedChar = dotPos + 1;
161: if (token != null && token.id() == HTMLTokenId.OPERATOR) {
162: doc.insertString(dotPosAfterTypedChar, "\"\"", null);
163: caret.setDot(dotPosAfterTypedChar + 1);
164: //mark the last autocomplete position
165: equalsSignInsertedOffset = dotPos;
166: }
167:
168: } finally {
169: doc.readUnlock();
170: }
171:
172: }
173:
174: }
|