blob: bdb5832e380460b4c043c15e82290d2bbbabb7ff [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.php.editor.csl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.netbeans.modules.csl.api.KeystrokeHandler;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.php.editor.parser.PHPParseResult;
import org.netbeans.modules.php.editor.parser.astnodes.ASTNode;
import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor;
/**
* Provide bracket completion for Ruby. This class provides three broad
* services: - Identifying matching pairs (parentheses, begin/end pairs etc.),
* which is used both for highlighting in the IDE (when the caret is on for
* example an if statement, the corresponding end token is highlighted), and
* navigation where you can jump between matching pairs. - Automatically
* inserting corresponding pairs when you insert a character. For example, if
* you insert a single quote, a corresponding ending quote is inserted - unless
* you're typing "over" the existing quote (you should be able to type foo =
* "hello" without having to arrow over the second quote that was inserted after
* you typed the first one). - Automatically adjusting indentation in some
* scenarios, for example when you type the final "d" in "end" - and readjusting
* it back to the original indentation if you continue typing something other
* than "end", e.g. "endian".
*
* The logic around inserting matching ""'s is heavily based on the Java editor
* implementation, and probably should be rewritten to be Ruby oriented. One
* thing they did is process the characters BEFORE the character has been
* inserted into the document. This has some advantages - it's easy to detect
* whether you're typing in the middle of a string since the token hierarchy has
* not been updated yet. On the other hand, it makes it hard to identify whether
* some characters are what we expect - is a "/" truly a regexp starter or
* something else? The Ruby lexer has lots of logic and state to determine this.
* I think it would be better to switch to after-insert logic for this.
*
* @todo Match braces within literal strings, as in #{}
* @todo Match || in the argument list of blocks? do { |foo| etc. }
* @todo I'm currently highlighting the indentation tokens (else, elsif, ensure,
* etc.) by finding the corresponding begin. For "illegal" tokens, e.g. def foo;
* else; end; this means I'll show "def" as the matching token for else, which
* is wrong. I should make the "indentation tokens" list into a map and
* associate them with their corresponding tokens, such that an else is only
* lined up with an if, etc.
* @todo Pressing newline in a parameter list doesn't work well if it's on a
* blockdefining line - e.g. def foo(a,b => it will insert the end BEFORE the
* closing paren!
* @todo Pressing space in a comment beyond the textline limit should wrap text?
* http://ruby.netbeans.org/issues/show_bug.cgi?id=11553
* @todo Make ast-selection pick up =begin/=end documentation blocks
*
* @author Tor Norbye
*/
public class PHPBracketCompleter implements KeystrokeHandler {
public PHPBracketCompleter() {
}
@Override
public int beforeBreak(Document document, int offset, JTextComponent target)
throws BadLocationException {
return -1;
}
@Override
public boolean beforeCharInserted(Document document, int caretOffset, JTextComponent target, char ch)
throws BadLocationException {
return false;
}
@Override
public boolean afterCharInserted(Document document, int dotPos, JTextComponent target, char ch)
throws BadLocationException {
return false;
}
@Override
public OffsetRange findMatching(Document document, int offset /*, boolean simpleSearch*/) {
return OffsetRange.NONE;
}
@Override
public boolean charBackspaced(Document document, int dotPos, JTextComponent target, char ch)
throws BadLocationException {
return false;
}
@Override
public List<OffsetRange> findLogicalRanges(ParserResult info, final int caretOffset) {
final Set<OffsetRange> ranges = new LinkedHashSet<>();
final DefaultVisitor pathVisitor = new DefaultVisitor() {
@Override
public void scan(ASTNode node) {
if (node != null && node.getStartOffset() <= caretOffset && caretOffset <= node.getEndOffset()) {
ranges.add(new OffsetRange(node.getStartOffset(), node.getEndOffset()));
super.scan(node);
}
}
};
if (info instanceof PHPParseResult) {
pathVisitor.scan(((PHPParseResult) info).getProgram());
}
final ArrayList<OffsetRange> retval = new ArrayList<>(ranges);
Collections.reverse(retval);
return retval;
}
// UGH - this method has gotten really ugly after successive refinements based on unit tests - consider cleaning up
@Override
public int getNextWordOffset(Document document, int offset, boolean reverse) {
return -1;
}
}