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: * If you wish your version of this file to be governed by only the CDDL
025: * or only the GPL Version 2, indicate your decision by adding
026: * "[Contributor] elects to include this software in this distribution
027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
028: * single choice of license, a recipient has the option to distribute
029: * your version of this file under either the CDDL, the GPL Version 2 or
030: * to extend the choice of license to its licensees as provided above.
031: * However, if you add GPL Version 2 code and therefore, elected the GPL
032: * Version 2 license, then the option applies only if the new code is
033: * made subject to such option by the copyright holder.
034: *
035: * Contributor(s):
036: *
037: * Portions Copyrighted 2008 Sun Microsystems, Inc.
038: */
039: package org.netbeans.modules.css.gsf;
040:
041: import java.util.Collections;
042: import java.util.List;
043: import javax.swing.text.BadLocationException;
044: import javax.swing.text.Caret;
045: import javax.swing.text.Document;
046: import javax.swing.text.JTextComponent;
047: import org.netbeans.api.lexer.Token;
048: import org.netbeans.api.lexer.TokenHierarchy;
049: import org.netbeans.api.lexer.TokenSequence;
050: import org.netbeans.editor.BaseDocument;
051: import org.netbeans.editor.Utilities;
052: import org.netbeans.modules.gsf.api.BracketCompletion;
053: import org.netbeans.modules.gsf.api.CompilationInfo;
054: import org.netbeans.modules.gsf.api.OffsetRange;
055: import org.netbeans.modules.css.editor.LexerUtils;
056: import org.netbeans.modules.css.lexer.api.CSSTokenId;
057: import org.netbeans.modules.editor.indent.api.Reformat;
058: import org.openide.util.Exceptions;
059:
060: /**
061: *
062: * @author marek
063: */
064: public class CssBracketCompleter implements BracketCompletion {
065:
066: private static final char[][] PAIRS = new char[][] { { '{', '}' },
067: { '"', '"' }, { '\'', '\'' } };
068: private char justAddedPair;
069:
070: private int pairIndex(char ch) {
071: for (int i = 0; i < PAIRS.length; i++) {
072: char pair = PAIRS[i][0];
073: if (pair == ch) {
074: return i;
075: }
076: }
077: return -1;
078: }
079:
080: public boolean beforeCharInserted(Document doc, int dot,
081: JTextComponent target, char ch) throws BadLocationException {
082: Caret caret = target.getCaret();
083:
084: if (justAddedPair == ch) {
085: //skip
086: justAddedPair = 0;
087: caret.setDot(dot + 1);
088: return true;
089: }
090:
091: justAddedPair = 0;
092:
093: //test if we care about the typed character
094: int pairIdx = pairIndex(ch);
095: if (pairIdx == -1) {
096: return false;
097: }
098:
099: if (target.getSelectionStart() != dot) {
100: /** @todo implement the adding quotes around selected text
101: */
102: return false;
103: }
104:
105: if (ch == '\'' || ch == '"') {
106: //handle quotations
107:
108: TokenSequence<CSSTokenId> ts = LexerUtils
109: .getCssTokenSequence(doc, dot);
110: if (ts != null) {
111: int diff = ts.move(dot);
112: if (ts.moveNext()) {
113: Token t = ts.token();
114: if (t.id() == CSSTokenId.STRING) {
115: //we are in or at a string
116: char front = t.text().charAt(diff);
117: if (front == ch) {
118: //do not insert, just move caret
119: caret.setDot(dot + 1);
120: return true;
121: } else {
122: //do nothing
123: return false;
124: }
125: }
126: }
127:
128: //cover "text| and user types "
129: //in such case just the quotation should be added
130:
131: //go back until we find " or ; { or } and test of the
132: //found quotation is a part of a string or not
133: ts.move(dot);
134: while (ts.movePrevious()) {
135: Token t = ts.token();
136: if (t.text().charAt(0) == ch) {
137: if (t.id() == CSSTokenId.STRING
138: || t.id() == CSSTokenId.STRING1
139: || t.id() == CSSTokenId.STRING2) {
140: //no unmatched quotation mark
141: break;
142: } else {
143: //found unmatched quotation mark - do nothing
144: return false;
145: }
146: }
147: if (t.id() == CSSTokenId.LBRACE
148: || t.id() == CSSTokenId.RBRACE
149: || t.id() == CSSTokenId.SEMICOLON) {
150: //break the loop, not quotation found - we can complete
151: break;
152: }
153: }
154: }
155: }
156:
157: justAddedPair = PAIRS[pairIdx][1];
158:
159: doc.insertString(dot, String.valueOf(PAIRS[pairIdx][0]), null);
160: doc.insertString(dot + 1, String.valueOf(justAddedPair), null);
161: caret.setDot(dot + 1);
162: return true;
163:
164: }
165:
166: public boolean afterCharInserted(Document doc, int caretOffset,
167: JTextComponent target, char ch) throws BadLocationException {
168: return false;
169: }
170:
171: public boolean charBackspaced(Document doc, int dot,
172: JTextComponent target, char ch) throws BadLocationException {
173: return false;
174:
175: }
176:
177: public int beforeBreak(Document doc, int dot, JTextComponent jtc)
178: throws BadLocationException {
179: if (dot == 0 || dot == doc.getLength()) { //check corners
180: return -1;
181: }
182: String context = doc.getText(dot - 1, 2); //get char before and after
183:
184: if ("{}".equals(context)) {
185: Reformat reformatter = Reformat.get(doc);
186: BaseDocument bdoc = (BaseDocument) doc;
187:
188: reformatter.lock();
189: try {
190: bdoc.atomicLock();
191: //smart indent
192: doc.insertString(dot, "\n", null);
193: //move caret
194: jtc.getCaret().setDot(dot);
195: //and indent the line
196: try {
197: reformatter.reformat(dot - 1, dot + 2);
198: } finally {
199: bdoc.atomicUnlock();
200: }
201: } finally {
202: reformatter.unlock();
203: }
204:
205: }
206:
207: return -1;
208:
209: }
210:
211: public OffsetRange findMatching(Document doc, int caretOffset) {
212: //XXX returning null or the default should cause GSF to use the IDE default matcher
213: return OffsetRange.NONE;
214: }
215:
216: public List<OffsetRange> findLogicalRanges(CompilationInfo info,
217: int caretOffset) {
218: return Collections.emptyList();
219: }
220:
221: public int getNextWordOffset(Document doc, int caretOffset,
222: boolean reverse) {
223: return -1;
224: }
225:
226: }
|