/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package ca.weblite.netbeans.mirah.typinghooks;
import ca.weblite.netbeans.mirah.lexer.DocumentQuery;
import ca.weblite.netbeans.mirah.lexer.MirahLanguageHierarchy;
import ca.weblite.netbeans.mirah.lexer.MirahTokenId;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import mirah.impl.Tokens;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.lexer.PartType;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.editor.BaseDocument;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.netbeans.modules.editor.indent.api.Indent;
import org.netbeans.modules.editor.indent.api.IndentUtils;
import org.netbeans.spi.editor.typinghooks.DeletedTextInterceptor;
import org.netbeans.spi.editor.typinghooks.TypedBreakInterceptor;
import org.netbeans.spi.editor.typinghooks.TypedTextInterceptor;
/**
*
* @author shannah
*/
public class MirahTypingCompletion {
private static final Logger LOG = Logger.getLogger(MirahTypingCompletion.class.getCanonicalName());
/**
* Returns true if bracket completion is enabled in options.
*/
static boolean isCompletionSettingEnabled() {
//Preferences prefs = MimeLookup.getLookup(JavaKit.JAVA_MIME_TYPE).lookup(Preferences.class);
//return prefs.getBoolean(SimpleValueNames.COMPLETION_PAIR_CHARACTERS, false);
return true;
}
private static int tokenBalance(Document doc, MirahTokenId leftTokenId) {
TokenBalance tb = TokenBalance.get(doc);
MirahTokenId tEnd = MirahTokenId.get(Tokens.tEnd.ordinal());
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tLParen.ordinal()), MirahTokenId.get(Tokens.tRParen.ordinal()));
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tLBrace.ordinal()), MirahTokenId.get(Tokens.tRBrace.ordinal()));
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tLBrack.ordinal()), MirahTokenId.get(Tokens.tRBrack.ordinal()));
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tDo.ordinal()), tEnd);
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tDef.ordinal()), tEnd);
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tClass.ordinal()), tEnd);
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tInterface.ordinal()), tEnd);
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tIf.ordinal()), tEnd);
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tWhile.ordinal()), tEnd);
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tCase.ordinal()), tEnd);
tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tBegin.ordinal()), tEnd);
//tb.addTokenPair(MirahTokenId.getLanguage(), MirahTokenId.get(Tokens.tDo.ordinal()), MirahTokenId.get(Tokens.tEnd.ordinal()));
int balance = tb.balance(MirahTokenId.getLanguage(), leftTokenId);
assert (balance != Integer.MAX_VALUE);
return balance;
}
/**
* Returns position of the first unpaired closing paren/brace/bracket from the caretOffset
* till the end of caret row. If there is no such element, position after the last non-white
* character on the caret row is returned.
*/
static int getRowOrBlockEnd(BaseDocument doc, int caretOffset, boolean[] insert) throws BadLocationException {
int rowEnd = org.netbeans.editor.Utilities.getRowLastNonWhite(doc, caretOffset);
if (rowEnd == -1 || caretOffset >= rowEnd+1) {
return caretOffset;
}
rowEnd += 1;
int parenBalance = 0;
int braceBalance = 0;
int bracketBalance = 0;
TokenSequence<MirahTokenId> ts = mirahTokenSequence(doc, caretOffset, false);
if (ts == null) {
return caretOffset;
}
while (ts.offset() < rowEnd) {
final int id = ts.token().id().ordinal();
if ( id == Tokens.tLParen.ordinal()) {
parenBalance++;
} else if ( id == Tokens.tRParen.ordinal()){
parenBalance--;
} else if ( id == Tokens.tLBrace.ordinal()){
braceBalance++;
} else if ( id == Tokens.tRBrace.ordinal()){
braceBalance--;
} else if ( id == Tokens.tLBrack.ordinal()){
bracketBalance++;
} else if ( id == Tokens.tRBrack.ordinal()){
bracketBalance--;
}
if (!ts.moveNext()) {
break;
}
}
if ( parenBalance > 0 || bracketBalance > 0 || braceBalance > 0 ){
doc.insertString(rowEnd+1, "\n", null);
return rowEnd+1;
}
return rowEnd;
}
private static TokenSequence<MirahTokenId> mirahTokenSequence(TypedTextInterceptor.MutableContext context, boolean backwardBias) {
return mirahTokenSequence(context.getDocument(), context.getOffset(), backwardBias);
}
private static TokenSequence<MirahTokenId> mirahTokenSequence(DeletedTextInterceptor.Context context, boolean backwardBias) {
return mirahTokenSequence(context.getDocument(), context.getOffset(), backwardBias);
}
private static TokenSequence<MirahTokenId> mirahTokenSequence(TypedBreakInterceptor.Context context, boolean backwardBias) {
return mirahTokenSequence(context.getDocument(), context.getCaretOffset(), backwardBias);
}
/**
* Get token sequence positioned over a token.
*
* @param doc
* @param caretOffset
* @param backwardBias
* @return token sequence positioned over a token that "contains" the offset
* or null if the document does not contain any java token sequence or the
* offset is at doc-or-section-start-and-bwd-bias or
* doc-or-section-end-and-fwd-bias.
*/
private static TokenSequence<MirahTokenId> mirahTokenSequence(Document doc, int caretOffset, boolean backwardBias) {
((BaseDocument)doc).readLock();
try {
TokenHierarchy<?> hi = TokenHierarchy.get(doc);
List<TokenSequence<?>> tsList = hi.embeddedTokenSequences(caretOffset, backwardBias);
// Go from inner to outer TSes
for (int i = tsList.size() - 1; i >= 0; i--) {
TokenSequence<?> ts = tsList.get(i);
if (ts.languagePath().innerLanguage() == MirahTokenId.getLanguage()) {
TokenSequence<MirahTokenId> javaInnerTS = (TokenSequence<MirahTokenId>) ts;
return javaInnerTS;
}
}
return null;
} finally {
((BaseDocument)doc).readUnlock();
}
}
static boolean isAddEnd(BaseDocument doc, int caretOffset) throws BadLocationException {
Set<MirahTokenId> assignmentTokens = MirahTokenId.set(
Tokens.tOpAssign,
Tokens.tOrEq,
Tokens.tAndEq,
Tokens.tPipes,
Tokens.tPlus,
Tokens.tMinus,
Tokens.tEEEQ,
Tokens.tEEQ,
Tokens.tGE,
Tokens.tLT,
Tokens.tIn,
Tokens.tGT,
Tokens.tLE,
Tokens.tAmpers,
Tokens.tNE,
Tokens.tQuestion,
Tokens.tEQ
);
int indentSize = IndentUtils.indentLevelSize(doc);
DocumentQuery dq = new DocumentQuery(doc);
if ( caretOffset > 0 ){
TokenSequence<MirahTokenId> seq = dq.getTokens(caretOffset-1, false);
if ( DocumentQuery.findPrevious(
seq,
null,
null,
MirahTokenId.get(Tokens.tNL)
)){
int bol = seq.offset();
seq.moveNext();
// See if the if statement is the first statement on
// the line... only then do we add an auto end
if ( DocumentQuery.findNext(
seq,
null,//MirahTokenId.WHITESPACE_AND_COMMENTS,
MirahTokenId.get(Tokens.tNL),
MirahTokenId.get(Tokens.tIf)
)){
int ifOffset = seq.token().offset(TokenHierarchy.get(doc));
MirahTokenId ifPrefixToken = null;
int ifPrefixOffset = -1;
while ( seq.movePrevious() && seq.offset() > bol ){
if ( !MirahTokenId.WHITESPACE_AND_COMMENTS.
contains(seq.token().id())){
ifPrefixToken = seq.token().id();
ifPrefixOffset = seq.offset();
break;
}
}
if ( ifPrefixToken != null &&
!assignmentTokens.contains(ifPrefixToken)){
return false;
}
int indent = dq.getIndent(ifOffset);
//System.out.println("Indent is "+indent);
// Look for an end
if ( DocumentQuery.findNext(
seq,
null,
MirahTokenId.get(Tokens.tNL),
MirahTokenId.get(Tokens.tEnd))){
//System.out.println("Found end on this line");
return false;
}
seq.move(ifOffset);
seq.moveNext();
DocumentQuery.consumeLine(seq);
seq.moveNext();
if ( seq.token() != null ){
int nextIndent = dq.getIndent(seq.token().offset(TokenHierarchy.get(doc)));
//System.out.println("Next indent is"+nextIndent);
if ( nextIndent != indent+indentSize ){
return true;
} else {
return false;
}
}
// We now have an if statement
// Let's see if the next line is already indented
return true;
}
}
}
//System.out.println("isAddEnd: "+doc.getText(bol, eol-bol));
if ( caretOffset <= 1){
return false;
}
int prevBOL = dq.getBOL(caretOffset-1);
int nextBOL = dq.getBOL(caretOffset);
if ( dq.getIndent(prevBOL) < dq.getIndent(nextBOL)){
return false;
}
MirahTokenId[] starts = new MirahTokenId[]{
MirahTokenId.get(Tokens.tDo.ordinal()),
MirahTokenId.get(Tokens.tClass.ordinal()),
MirahTokenId.get(Tokens.tInterface.ordinal()),
MirahTokenId.get(Tokens.tDef.ordinal()),
//MirahTokenId.get(Tokens.tIf.ordinal()),
MirahTokenId.get(Tokens.tCase.ordinal()),
MirahTokenId.get(Tokens.tWhile.ordinal()),
MirahTokenId.get(Tokens.tBegin.ordinal()),
MirahTokenId.get(Tokens.tUnless.ordinal())
};
boolean foundStart = false;
for ( int i=0; i<starts.length; i++){
if (tokenBalance(doc, starts[i]) > 0) {
foundStart = true;
}
}
if ( !foundStart ){
return false;
}
//if (tokenBalance(doc, MirahTokenId.get(Tokens.tDo.ordinal())) <= 0) {
// return false;
// }
int caretRowStartOffset = org.netbeans.editor.Utilities.getRowStart(doc, caretOffset);
TokenSequence<MirahTokenId> ts = mirahTokenSequence(doc, caretOffset, true);
if (ts == null) {
return false;
}
boolean first = true;
MirahTokenId WHITESPACE = MirahTokenId.get(Tokens.tWhitespace.ordinal());
//MirahTokenId LINE_COMMENT = MirahTokenId.get(Tokens.t)
MirahTokenId LBRACE = MirahTokenId.get(Tokens.tLBrace.ordinal());
do {
if (ts.offset() < caretRowStartOffset) {
return false;
}
for (int i=0; i<starts.length; i++){
if ( ts.token().id().equals(starts[i])){
return true;
}
}
//if ( ts.token().id().equals(LBRACE)){
// return true;
//}
first = false;
} while (ts.movePrevious());
return false;
}
/**
* Resolve whether pairing right curly should be added automatically
* at the caret position or not.
* <br>
* There must be only whitespace or line comment or block comment
* between the caret position
* and the left brace and the left brace must be on the same line
* where the caret is located.
* <br>
* The caret must not be "contained" in the opened block comment token.
*
* @param doc document in which to operate.
* @param caretOffset offset of the caret.
* @return true if a right brace '}' should be added
* or false if not.
*/
static boolean isAddRightBrace(BaseDocument doc, int caretOffset) throws BadLocationException {
if (tokenBalance(doc, MirahTokenId.get(Tokens.tLBrace.ordinal())) <= 0) {
return false;
}
int caretRowStartOffset = org.netbeans.editor.Utilities.getRowStart(doc, caretOffset);
TokenSequence<MirahTokenId> ts = mirahTokenSequence(doc, caretOffset, true);
if (ts == null) {
return false;
}
boolean first = true;
MirahTokenId WHITESPACE = MirahTokenId.get(Tokens.tWhitespace.ordinal());
//MirahTokenId LINE_COMMENT = MirahTokenId.get(Tokens.t)
MirahTokenId LBRACE = MirahTokenId.get(Tokens.tLBrace.ordinal());
do {
if (ts.offset() < caretRowStartOffset) {
return false;
}
if ( ts.token().id().equals(LBRACE)){
return true;
}
first = false;
} while (ts.movePrevious());
return false;
}
/**
* Check for various conditions and possibly add a pairing bracket.
*
* @param context
* @throws BadLocationException
*/
static void completeOpeningBracket(TypedTextInterceptor.MutableContext context) throws BadLocationException {
if (isStringOrComment(mirahTokenSequence(context, false).token().id())) {
return;
}
char chr = context.getDocument().getText(context.getOffset(), 1).charAt(0);
if (chr == ')' || chr == ',' || chr == '\"' || chr == '\'' || chr == ' ' || chr == ']' || chr == '}' || chr == '\n' || chr == '\t' || chr == ';') {
char insChr = context.getText().charAt(0);
context.setText("" + insChr + matching(insChr) , 1); // NOI18N
}
}
/**
* Returns for an opening bracket or quote the appropriate closing
* character.
*/
private static char matching(char bracket) {
switch (bracket) {
case '(':
return ')';
case '[':
return ']';
case '\"':
return '\"'; // NOI18N
case '\'':
return '\'';
default:
return ' ';
}
}
private static MirahTokenId.Enum matching(MirahTokenId.Enum id) {
switch (id) {
case LPAREN:
return MirahTokenId.Enum.RPAREN;
case LBRACK:
return MirahTokenId.Enum.RBRACK;
case RPAREN:
return MirahTokenId.Enum.LPAREN;
case RBRACK:
return MirahTokenId.Enum.LBRACK;
default:
return null;
}
}
private static boolean isStringOrComment(MirahTokenId id) {
return id.ordinal() == Tokens.tStringContent.ordinal();
}
/**
* Check for various conditions and possibly skip a closing bracket.
*
* @param context
* @return relative caretOffset change
* @throws BadLocationException
*/
static int skipClosingBracket(TypedTextInterceptor.MutableContext context) throws BadLocationException {
TokenSequence<MirahTokenId> javaTS = mirahTokenSequence(context, false);
if (javaTS == null || (javaTS.token().id().ordinal() != Tokens.tRParen.ordinal()) && javaTS.token().id().ordinal() != Tokens.tRBrack.ordinal() || isStringOrComment(javaTS.token().id())) {
return -1;
}
MirahTokenId.Enum bracketId = bracketCharToId(context.getText().charAt(0));
if (isSkipClosingBracket(context, javaTS, bracketId)) {
context.setText("", 0); // NOI18N
return context.getOffset() + 1;
}
return -1;
}
private static MirahTokenId.Enum bracketCharToId(char bracket) {
switch (bracket) {
case '(':
return MirahTokenId.Enum.LPAREN;
case ')':
return MirahTokenId.Enum.RPAREN;
case '[':
return MirahTokenId.Enum.LBRACK;
case ']':
return MirahTokenId.Enum.RBRACK;
case '{':
return MirahTokenId.Enum.LBRACE;
case '}':
return MirahTokenId.Enum.RBRACE;
default:
throw new IllegalArgumentException("Not a bracket char '" + bracket + '\''); // NOI18N
}
}
private static Set<MirahTokenId> STOP_TOKENS_FOR_SKIP_CLOSING_BRACKET = new HashSet<MirahTokenId>();
static {
STOP_TOKENS_FOR_SKIP_CLOSING_BRACKET.add(MirahTokenId.LBRACE);
STOP_TOKENS_FOR_SKIP_CLOSING_BRACKET.add(MirahTokenId.RBRACE);
}
private static boolean isSkipClosingBracket(TypedTextInterceptor.MutableContext context, TokenSequence<MirahTokenId> javaTS, MirahTokenId.Enum rightBracketId) {
if (context.getOffset() == context.getDocument().getLength()) {
return false;
}
boolean skipClosingBracket = false;
if (javaTS != null && javaTS.token().id().asEnum() == rightBracketId) {
MirahTokenId.Enum leftBracketId = matching(rightBracketId);
// Skip all the brackets of the same type that follow the last one
do {
if (STOP_TOKENS_FOR_SKIP_CLOSING_BRACKET.contains(javaTS.token().id())
|| (javaTS.token().id() == MirahTokenId.WHITESPACE && javaTS.token().text().toString().contains("\n"))) { // NOI18N
while (javaTS.token().id().asEnum() != rightBracketId) {
boolean isPrevious = javaTS.movePrevious();
if (!isPrevious) {
break;
}
}
break;
}
} while (javaTS.moveNext());
// token var points to the last bracket in a group of two or more right brackets
// Attempt to find the left matching bracket for it
// Search would stop on an extra opening left brace if found
int braceBalance = 0; // balance of '{' and '}'
int bracketBalance = -1; // balance of the brackets or parenthesis
int numOfSemi = 0;
boolean finished = false;
while (!finished && javaTS.movePrevious()) {
MirahTokenId tokId = javaTS.token().id();
if ( tokId == null ){
continue;
}
MirahTokenId.Enum id = tokId.asEnum();
if ( id == null ){
continue;
}
switch (id) {
case LPAREN:
case LBRACK:
if (id == leftBracketId) {
bracketBalance++;
if (bracketBalance == 1) {
if (braceBalance != 0) {
// Here the bracket is matched but it is located
// inside an unclosed brace block
// e.g. ... ->( } a()|)
// which is in fact illegal but it's a question
// of what's best to do in this case.
// We chose to leave the typed bracket
// by setting bracketBalance to 1.
// It can be revised in the future.
bracketBalance = 2;
}
finished = javaTS.offset() < context.getOffset();
}
}
break;
case RPAREN:
case RBRACK:
if (id == rightBracketId) {
bracketBalance--;
}
break;
case LBRACE:
braceBalance++;
if (braceBalance > 0) { // stop on extra left brace
finished = true;
}
break;
case RBRACE:
braceBalance--;
break;
//case SEMICOLON:
// numOfSemi++;
// break;
}
}
if (bracketBalance == 1 && numOfSemi < 2) {
finished = false;
while (!finished && javaTS.movePrevious()) {
switch (javaTS.token().id().asEnum()) {
case WHITESPACE:
//case LINE_COMMENT:
//case BLOCK_COMMENT:
//case JAVADOC_COMMENT:
break;
//case FOR:
// bracketBalance--;
default:
finished = true;
break;
}
}
}
skipClosingBracket = bracketBalance != 1;
}
return skipClosingBracket;
}
/**
* Called to insert either single bracket or bracket pair.
*
* @param context
* @return relative caretOffset change
* @throws BadLocationException
*/
static int completeQuote(TypedTextInterceptor.MutableContext context) throws BadLocationException {
if (isEscapeSequence(context)) {
return -1;
}
// Examine token id at the caret offset
TokenSequence<MirahTokenId> javaTS = mirahTokenSequence(context, true);
MirahTokenId.Enum id = (javaTS != null) ? javaTS.token().id().asEnum() : null;
// If caret within comment return false
if ( id != null ){
}
boolean caretInsideToken = (id != null)
&& (javaTS.offset() + javaTS.token().length() >= context.getOffset()
|| javaTS.token().partType() == PartType.START);
boolean completablePosition = isQuoteCompletablePosition(context);
boolean insideString = caretInsideToken
&& (id == MirahTokenId.Enum.STRING_LITERAL || id == MirahTokenId.Enum.CHAR_LITERAL || id == MirahTokenId.Enum.SQUOTE || id == MirahTokenId.Enum.DQUOTE);
int lastNonWhite = org.netbeans.editor.Utilities.getRowLastNonWhite((BaseDocument) context.getDocument(), context.getOffset());
// eol - true if the caret is at the end of line (ignoring whitespaces)
boolean eol = lastNonWhite < context.getOffset();
if (insideString) {
if (eol) {
return -1;
} else {
//#69524
char chr = context.getDocument().getText(context.getOffset(), 1).charAt(0);
if (chr == context.getText().charAt(0)) {
//#83044
if (context.getOffset() > 0) {
javaTS.move(context.getOffset() - 1);
if (javaTS.moveNext()) {
id = javaTS.token().id().asEnum();
if (id == MirahTokenId.Enum.STRING_LITERAL || id == MirahTokenId.Enum.CHAR_LITERAL || (chr == '\'' && id == MirahTokenId.Enum.SQUOTE) || (chr == '"' && id == MirahTokenId.Enum.DQUOTE) ) {
context.setText("", 0); // NOI18N
return context.getOffset() + 1;
}
}
}
}
}
}
if ((completablePosition && !insideString) || eol) {
context.setText(context.getText() + context.getText(), 1);
}
return -1;
}
private static boolean isEscapeSequence(TypedTextInterceptor.MutableContext context) throws BadLocationException {
if (context.getOffset() <= 0) {
return false;
}
char[] previousChars;
for (int i = 2; context.getOffset() - i >= 0; i += 2) {
previousChars = context.getDocument().getText(context.getOffset() - i, 2).toCharArray();
if (previousChars[1] != '\\') {
return false;
}
if (previousChars[0] != '\\') {
return true;
}
}
return context.getDocument().getText(context.getOffset() - 1, 1).charAt(0) == '\\';
}
private static boolean isQuoteCompletablePosition(TypedTextInterceptor.MutableContext context) throws BadLocationException {
if (context.getOffset() == context.getDocument().getLength()) {
return true;
} else {
for (int i = context.getOffset(); i < context.getDocument().getLength(); i++) {
char chr = context.getDocument().getText(i, 1).charAt(0);
if (chr == '\n') {
break;
}
if (!Character.isWhitespace(chr)) {
return (chr == ')' || chr == ',' || chr == '+' || chr == '}' || chr == ';');
}
}
return false;
}
}
/**
* Check for various conditions and possibly remove two brackets.
*
* @param context
* @throws BadLocationException
*/
static void removeBrackets(DeletedTextInterceptor.Context context) throws BadLocationException {
int caretOffset = context.isBackwardDelete() ? context.getOffset() - 1 : context.getOffset();
TokenSequence<MirahTokenId> ts = mirahTokenSequence(context.getDocument(), caretOffset, false);
if (ts == null) {
return;
}
switch (ts.token().id().asEnum()) {
case RPAREN:
if (tokenBalance(context.getDocument(), MirahTokenId.LPAREN) != 0) {
context.getDocument().remove(caretOffset, 1);
}
break;
case RBRACK:
if (tokenBalance(context.getDocument(), MirahTokenId.LBRACK) != 0) {
context.getDocument().remove(caretOffset, 1);
}
break;
}
}
/**
* Check for various conditions and possibly remove two quotes.
*
* @param context
* @throws BadLocationException
*/
static void removeCompletedQuote(DeletedTextInterceptor.Context context) throws BadLocationException {
TokenSequence<MirahTokenId> ts = mirahTokenSequence(context, false);
if (ts == null) {
return;
}
char removedChar = context.getText().charAt(0);
int caretOffset = context.isBackwardDelete() ? context.getOffset() - 1 : context.getOffset();
if (removedChar == '\"' || removedChar == '\'') {
if (ts.token().id().asEnum() == MirahTokenId.Enum.STRING_LITERAL && ts.offset() == caretOffset) {
context.getDocument().remove(caretOffset, 1);
}
} else if (removedChar == '\'') {
if (ts.token().id().asEnum() == MirahTokenId.Enum.CHAR_LITERAL && ts.offset() == caretOffset) {
context.getDocument().remove(caretOffset, 1);
}
}
}
static boolean blockCommentCompletion(TypedBreakInterceptor.Context context) {
return blockCommentCompletionImpl(context, false);
}
static boolean javadocBlockCompletion(TypedBreakInterceptor.Context context) {
return blockCommentCompletionImpl(context, true);
}
private static boolean blockCommentCompletionImpl(TypedBreakInterceptor.Context context, boolean javadoc) {
TokenSequence<MirahTokenId> ts = mirahTokenSequence(context, false);
if (ts == null) {
return false;
}
int dotPosition = context.getCaretOffset();
ts.move(dotPosition);
if (!((ts.moveNext() || ts.movePrevious()) && (ts.token().id() == MirahTokenId.WHITESPACE) || ts.token().id() == MirahTokenId.NL)) {
return false;
}
int jdoffset = dotPosition - (javadoc ? 3 : 2);
if (jdoffset >= 0) {
//CharSequence content = org.netbeans.lib.editor.util.swing.DocumentUtilities.getText(context.getDocument());
CharSequence content = DocumentUtilities.getText(context.getDocument());
if (isOpenBlockComment(content, dotPosition - 1, javadoc) && !isClosedBlockComment(content, dotPosition) && isAtRowEnd(content, dotPosition)) {
return true;
}
}
return false;
}
private static boolean isOpenBlockComment(CharSequence content, int pos, boolean javadoc) {
for (int i = pos; i >= 0; i--) {
char c = content.charAt(i);
if (c == '*' && (javadoc ? i - 2 >= 0 && content.charAt(i - 1) == '*' && content.charAt(i - 2) == '/' : i - 1 >= 0 && content.charAt(i - 1) == '/')) {
// matched /*
return true;
} else if (c == '\n') {
// no javadoc, matched start of line
return false;
} else if (c == '/' && i - 1 >= 0 && content.charAt(i - 1) == '*') {
// matched javadoc enclosing tag
return false;
}
}
return false;
}
private static boolean isClosedBlockComment(CharSequence txt, int pos) {
int length = txt.length();
int quotation = 0;
for (int i = pos; i < length; i++) {
char c = txt.charAt(i);
if (c == '*' && i < length - 1 && txt.charAt(i + 1) == '/') {
if (quotation == 0 || i < length - 2) {
return true;
}
// guess it is not just part of some text constant
boolean isClosed = true;
for (int j = i + 2; j < length; j++) {
char cc = txt.charAt(j);
if (cc == '\n') {
break;
} else if (cc == '"' && j < length - 1 && txt.charAt(j + 1) != '\'') {
isClosed = false;
break;
}
}
if (isClosed) {
return true;
}
} else if (c == '/' && i < length - 1 && txt.charAt(i + 1) == '*') {
// start of another comment block
return false;
} else if (c == '\n') {
quotation = 0;
} else if (c == '"' && i < length - 1 && txt.charAt(i + 1) != '\'') {
quotation = ++quotation % 2;
}
}
return false;
}
private static boolean isAtRowEnd(CharSequence txt, int pos) {
int length = txt.length();
for (int i = pos; i < length; i++) {
char c = txt.charAt(i);
if (c == '\n') {
return true;
}
if (!Character.isWhitespace(c)) {
return false;
}
}
return true;
}
}