/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * Portions Copyrighted 2007 Sun Microsystems, Inc. */ package org.netbeans.modules.spellchecker.bindings.ruby; import javax.swing.event.ChangeListener; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenId; import org.netbeans.api.lexer.TokenSequence; import org.netbeans.editor.BaseDocument; import org.netbeans.modules.spellchecker.spi.language.TokenList; import org.openide.ErrorManager; /** * Tokenize Ruby text for spell checking. Based on corresponding * JavaTokenList by Jan Lahoda. * * @todo Check spelling in documentation sections * @todo Suppress spelling checks on :rdoc: modifiers * @todo Remove surrounding +, _, * on spelling words * @todo Spell check string literals? * @todo Spell check constant names and method names? * * * * @author Tor Norbye */ // TODO - rename AbstractTokenList public abstract class AbstractRubyTokenList implements TokenList { protected BaseDocument doc; /** Creates a new instance of RubyTokenList */ AbstractRubyTokenList(BaseDocument doc) { this.doc = doc; } public void setStartOffset(int offset) { currentBlockText = null; currentOffsetInComment = (-1); this.startOffset = this.nextBlockStart = offset; } public int getCurrentWordStartOffset() { return currentWordOffset; } public CharSequence getCurrentWordText() { return currentWord; } public boolean nextWord() { boolean hasNext = nextWordImpl(); while (hasNext && (currentWordOffset + currentWord.length()) < startOffset) { hasNext = nextWordImpl(); } return hasNext; } private int[] findNextSpellSpan() throws BadLocationException { TokenHierarchy<Document> h = TokenHierarchy.get((Document)doc); @SuppressWarnings("unchecked") //the cast below should be safe (and not necessary), //but JDK5 compiler fails to compile the class without it and complains about the cast. //likely a compiler bug TokenSequence<? extends TokenId> ts = (TokenSequence<? extends TokenId>) h.tokenSequence(); return findNextSpellSpan(ts, nextBlockStart); } /** Given a sequence of Ruby tokens, return the next span of eligible comments */ protected abstract int[] findNextSpellSpan(TokenSequence<? extends TokenId> ts, int offset) throws BadLocationException; private boolean nextWordImpl() { try { while (true) { if (currentBlockText == null) { int[] span = findNextSpellSpan(); if (span[0] == (-1)) { return false; } currentBlockStart = span[0]; currentBlockText = doc.getText(span[0], span[1] - span[0]); currentOffsetInComment = 0; nextBlockStart = span[1]; } String pairTag = null; Pair<CharSequence, Integer> data = wordBroker(currentBlockText, currentOffsetInComment, false); while (data != null) { currentOffsetInComment = data.b + data.a.length(); if (pairTag == null) { if (Character.isLetter(data.a.charAt(0)) && !isIdentifierLike(data.a)) { //TODO: check for identifiers: currentWordOffset = currentBlockStart + data.b; currentWord = data.a; return true; } switch (data.a.charAt(0)) { case '<': if (startsWith(data.a, "<a ")) { pairTag = "</a>"; } if (startsWith(data.a, "<code>")) { pairTag = "</code>"; } if (startsWith(data.a, "<pre>")) { pairTag = "</pre>"; } break; case '{': pairTag = "}"; break; } } else { if (pairTag.contentEquals(data.a)) { pairTag = null; } } data = wordBroker(currentBlockText, currentOffsetInComment, false); } currentBlockText = null; } } catch (BadLocationException e) { ErrorManager.getDefault().notify(e); return false; } } static boolean startsWith(CharSequence where, String withWhat) { if (where.length() >= withWhat.length()) { return withWhat.contentEquals(where.subSequence(0, withWhat.length())); } return false; } static boolean isIdentifierLike(CharSequence s) { boolean hasCapitalsInside = false; boolean hasUnderlinesInside = false; int offset = 1; while (offset < s.length() && !hasCapitalsInside) { char c = s.charAt(offset); if (c == '_') { hasUnderlinesInside = true; } else { hasCapitalsInside |= Character.isUpperCase(s.charAt(offset)); } offset++; } return hasCapitalsInside || hasUnderlinesInside; } private int currentBlockStart; private int nextBlockStart; private String currentBlockText; private int currentOffsetInComment; private int currentWordOffset; private CharSequence currentWord; private int startOffset; static boolean isLetter(char c) { return Character.isLetter(c) || c == '\'' || c == '_'; } static Pair<CharSequence, Integer> wordBroker(CharSequence start, int offset, boolean treatSpecialCharactersAsLetterInsideWords) { int state = 0; int offsetStart = offset; while (start.length() > offset) { char current = start.charAt(offset); switch (state) { case 0: if (current == ':') { state = 5; offsetStart = offset; break; } if (isLetter(current)) { state = 1; offsetStart = offset; break; } if (current == '@' || current == '#') { state = 2; offsetStart = offset; break; } if (current == '<') { state = 3; offsetStart = offset; break; } if (current == '\n' || current == '}') { return new Pair<CharSequence, Integer>(start.subSequence(offset, offset + 1), offset); } if (current == '{') { state = 4; offsetStart = offset; break; } break; case 1: // isLetter if (!isLetter(current) && ((current != '.' && current != '#') || !treatSpecialCharactersAsLetterInsideWords)) { return new Pair<CharSequence, Integer>(start.subSequence(offsetStart, offset), offsetStart); } break; case 2: // In @ or # if (!isLetter(current)) { return new Pair<CharSequence, Integer>(start.subSequence(offsetStart, offset), offsetStart); } break; case 3: // In < if (current == '>') { return new Pair<CharSequence, Integer>(start.subSequence(offsetStart, offset + 1), offsetStart); } break; case 4: // In { if (current == '@') { state = 2; break; } offset--; state = 0; break; case 5: // After : if (Character.isWhitespace(current)) { state = 0; } break; } offset++; } if (offset > offsetStart) { return new Pair<CharSequence, Integer>(start.subSequence(offsetStart, offset), offsetStart); } else { return null; } } public void addChangeListener(ChangeListener l) { //ignored... } public void removeChangeListener(ChangeListener l) { //ignored... } }