blob: 5b358bb46566bff6863618743d4c25c2f64a8296 [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.javascript2.jade.editor;
import java.util.Arrays;
import java.util.List;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
import org.netbeans.modules.javascript2.jade.editor.lexer.JadeTokenId;
/**
*
* @author Petr Pisl
*/
public enum JadeCompletionContext {
NONE, // There shouldn't be any code completion
TAG, // offer only html tags
TAG_AND_KEYWORD, // tags and keywords
ATTRIBUTE, // html attributes
CSS_ID,
CSS_CLASS;
private static final List<Object[]> KEYWORD_POSITION = Arrays.asList(
new Object[]{JadeTokenId.EOL},
new Object[]{JadeTokenId.EOL, JadeTokenId.WHITESPACE},
new Object[]{JadeTokenId.EOL, JadeTokenId.TAG},
new Object[]{JadeTokenId.EOL, JadeTokenId.WHITESPACE, JadeTokenId.TAG}
);
private static final List<Object[]> TAG_POSITION = Arrays.asList(
new Object[]{JadeTokenId.TAG},
new Object[]{JadeTokenId.TAG, JadeTokenId.OPERATOR_COLON, JadeTokenId.WHITESPACE}
// new Object[]{JadeTokenId.BRACKET_LEFT_PAREN},
// new Object[]{JadeTokenId.EOL, JadeTokenId.EOL},
// new Object[]{JadeTokenId.EOL, JadeTokenId.TAG},
// new Object[]{JadeTokenId.EOL, JadeTokenId.WHITESPACE},
// new Object[]{JadeTokenId.EOL, JadeTokenId.WHITESPACE, JadeTokenId.TAG},
// new Object[]{JadeTokenId.TAG, JadeTokenId.OPERATOR_COLON},
// new Object[]{JadeTokenId.TAG, JadeTokenId.OPERATOR_COLON},
// new Object[]{JadeTokenId.TAG, JadeTokenId.WHITESPACE},
// new Object[]{JadeTokenId.TAG, JadeTokenId.EOL},
// new Object[]{JadeTokenId.TAG, JadeTokenId.OPERATOR_COLON, JadeTokenId.TAG},
//
// new Object[]{JadeTokenId.TAG, JadeTokenId.OPERATOR_COLON, JadeTokenId.WHITESPACE, JadeTokenId.TAG}
);
private static final List<JadeTokenId> WHITESPACES = Arrays.asList(JadeTokenId.WHITESPACE, JadeTokenId.EOL);
private static final List<Object[]> ATTRIBUTE_POSITION = Arrays.asList(
new Object[]{JadeTokenId.ATTRIBUTE},
new Object[]{JadeTokenId.BRACKET_LEFT_PAREN},
new Object[]{JadeTokenId.BRACKET_LEFT_PAREN, JadeTokenId.EOL},
new Object[]{JadeTokenId.BRACKET_LEFT_PAREN, JadeTokenId.WHITESPACE},
new Object[]{JadeTokenId.BRACKET_LEFT_PAREN, JadeTokenId.EOL, JadeTokenId.WHITESPACE},
new Object[]{JadeTokenId.JAVASCRIPT, JadeTokenId.OPERATOR_COMMA},
new Object[]{JadeTokenId.JAVASCRIPT, JadeTokenId.OPERATOR_COMMA, JadeTokenId.WHITESPACE},
new Object[]{JadeTokenId.ATTRIBUTE, JadeTokenId.OPERATOR_ASSIGNMENT, JadeTokenId.JAVASCRIPT, JadeTokenId.WHITESPACE},
new Object[]{JadeTokenId.ATTRIBUTE, JadeTokenId.OPERATOR_ASSIGNMENT, JadeTokenId.JAVASCRIPT}
);
private static final List<Object[]> CSS_ID_POSITION = Arrays.asList(
new Object[]{JadeTokenId.CSS_ID},
new Object[]{JadeTokenId.TAG, JadeTokenId.TEXT},
new Object[]{JadeTokenId.CSS_CLASS, JadeTokenId.TEXT}
);
private static final List<Object[]> CSS_CLASS_POSITION = Arrays.asList(
new Object[]{JadeTokenId.CSS_CLASS},
new Object[]{JadeTokenId.TAG, JadeTokenId.PLAIN_TEXT_DELIMITER},
new Object[]{JadeTokenId.CSS_ID, JadeTokenId.PLAIN_TEXT_DELIMITER},
new Object[]{JadeTokenId.CSS_CLASS, JadeTokenId.PLAIN_TEXT_DELIMITER}
);
@NonNull
public static JadeCompletionContext findCompletionContext(ParserResult info, int offset){
TokenHierarchy<?> th = info.getSnapshot().getTokenHierarchy();
boolean isEOF = false;
if (th == null) {
return NONE;
}
TokenSequence<JadeTokenId> ts = th.tokenSequence(JadeTokenId.jadeLanguage());
if (ts == null) {
return NONE;
}
ts.move(offset);
if (!ts.movePrevious()) {
return TAG_AND_KEYWORD;
} else if (ts.token().id() == JadeTokenId.TAG) {
// just check, whether we are on the first tag in the file. see issue #251160
if (!ts.movePrevious()) {
return TAG_AND_KEYWORD;
}
ts.moveNext();
}
if (!ts.moveNext()) {
if (ts.token() != null && ts.token().id() == JadeTokenId.EOL) {
while ((ts.token().id() == JadeTokenId.WHITESPACE || ts.token().id() == JadeTokenId.EOL) && ts.movePrevious()) {
}
if (acceptTokenChains(ts, ATTRIBUTE_POSITION, false)) {
return ATTRIBUTE;
}
return TAG_AND_KEYWORD;
}
isEOF = true;
}
Token<JadeTokenId> token = ts.token();
JadeTokenId id = token.id();
String text = null;
switch (id) {
case ATTRIBUTE: return ATTRIBUTE;
case TAG:
if (acceptTokenChains(ts, KEYWORD_POSITION, false)) {
return TAG_AND_KEYWORD;
}
return TAG;
case CSS_ID: return CSS_ID;
case CSS_CLASS: return CSS_CLASS;
case TEXT:
text = token.text().toString();
if (JadeCodeCompletion.CSS_ID_PREFIX.equals(text) && (acceptTokenChains(ts, TAG_POSITION, true) || isEOF)) {
return CSS_ID;
}
break;
case PLAIN_TEXT_DELIMITER:
if (acceptTokenChains(ts, TAG_POSITION, true)) {
return CSS_CLASS;
}
if (isEOF) {
return CSS_CLASS;
}
break;
case COMMENT:
String commentText = token.text().toString();
int index = offset - ts.offset() - 1;
int spaces = 0;
if (index > -1 && index < commentText.length()) {
while (index > -1 && (commentText.charAt(index) == ' ' || commentText.charAt(index) == '\t')) {
spaces++;
index--;
}
if (index > -1) {
char ch = commentText.charAt(index);
if (ch == '\n') {
if (spaces == 0) {
return TAG_AND_KEYWORD;
} else {
if (ts.movePrevious() && ts.token().id() == JadeTokenId.COMMENT_DELIMITER && ts.movePrevious()) {
token = ts.token();
id = token.id();
if (id == JadeTokenId.WHITESPACE && token.length() >= spaces) {
return TAG_AND_KEYWORD;
}
}
}
}
}
}
return NONE;
}
if (id.isKeyword()) {
return TAG_AND_KEYWORD;
}
int helpIndex = ts.index();
while ((id == JadeTokenId.WHITESPACE || id == JadeTokenId.EOL) && ts.movePrevious()) {
id = ts.token().id();
}
if (acceptTokenChains(ts, ATTRIBUTE_POSITION, helpIndex == ts.index())) {
return ATTRIBUTE;
}
if (helpIndex != ts.index()) {
ts.moveIndex(helpIndex);
ts.moveNext();
id = ts.token().id();
}
if (acceptTokenChains(ts, KEYWORD_POSITION, !isEOF)) {
return TAG_AND_KEYWORD;
}
// check tag: ^ position
if (acceptTokenChains(ts, TAG_POSITION, true)) {
return TAG;
}
if (acceptTokenChains(ts, CSS_CLASS_POSITION, true)) {
return CSS_CLASS;
}
if (acceptTokenChains(ts, CSS_ID_POSITION, true)) {
return CSS_ID;
}
boolean isBeginOfLine = false;
if (ts.movePrevious()) {
token = ts.token();
id = token.id();
if (id == JadeTokenId.WHITESPACE && ts.movePrevious()) {
token = ts.token();
id = token.id();
}
switch (id) {
case EOL:
isBeginOfLine = true;
if (text != null) {
if (JadeCodeCompletion.CSS_CLASS_PREFIX.equals(text)) {
return CSS_CLASS;
}
if (JadeCodeCompletion.CSS_ID_PREFIX.equals(text)) {
return CSS_ID;
}
}
break;
case COMMENT:
return TAG_AND_KEYWORD;
case TEXT:
text = token.text().toString();
if (JadeCodeCompletion.CSS_ID_PREFIX.equals(text) && acceptTokenChains(ts, KEYWORD_POSITION, true)) {
return CSS_ID;
}
break;
case PLAIN_TEXT_DELIMITER:
if (acceptTokenChains(ts, KEYWORD_POSITION, true)) {
return CSS_CLASS;
}
break;
}
while (ts.movePrevious()) {
token = ts.token();
id = token.id();
if (id == JadeTokenId.TAG) {
if (isBeginOfLine) {
return TAG_AND_KEYWORD;
} else {
return NONE;
}
}
if (id == JadeTokenId.ATTRIBUTE || id == JadeTokenId.BRACKET_LEFT_PAREN) {
return ATTRIBUTE;
}
if (id != JadeTokenId.EOL && id != JadeTokenId.WHITESPACE && id != JadeTokenId.JAVASCRIPT) {
if (isBeginOfLine) {
return TAG_AND_KEYWORD;
}
return NONE;
}
}
}
return NONE;
}
private static boolean acceptTokenChains(TokenSequence tokenSequence, List<Object[]> tokenIdChains, boolean movePrevious) {
for (Object[] tokenIDChain : tokenIdChains){
if (acceptTokenChain(tokenSequence, tokenIDChain, movePrevious)){
return true;
}
}
return false;
}
private static boolean acceptTokenChain(TokenSequence tokenSequence, Object[] tokenIdChain, boolean movePrevious) {
int orgTokenSequencePos = tokenSequence.offset();
boolean accept = true;
boolean moreTokens = movePrevious ? tokenSequence.movePrevious() : true;
for (int i = tokenIdChain.length - 1; i >= 0; i --){
Object tokenID = tokenIdChain[i];
if (!moreTokens){
accept = false;
break;
}
if (tokenID instanceof JadeTokenId) {
if (tokenSequence.token().id() == tokenID){
moreTokens = tokenSequence.movePrevious();
} else {
// NO MATCH
accept = false;
break;
}
} else {
assert false : "Unsupported token type: " + tokenID.getClass().getName();
}
}
tokenSequence.move(orgTokenSequencePos);
tokenSequence.moveNext();
return accept;
}
}