blob: 3f5af8977feaaf150c5b285f488077977955ecff [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2018 xored software, Inc. and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* xored software, Inc. - initial API and Implementation (Sam Faktorovich)
*******************************************************************************/
package org.eclipse.dltk.ui.text;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.dltk.compiler.task.ITodoTaskPreferences;
import org.eclipse.dltk.ui.text.rules.CombinedWordRule;
import org.eclipse.dltk.ui.text.rules.CombinedWordRule.CharacterBuffer;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.IWordDetector;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.util.PropertyChangeEvent;
public class ScriptCommentScanner extends AbstractScriptScanner {
private final String fTodoToken;
private final String fDefaultTokenProperty;
private TaskTagMatcher fTaskTagMatcher;
private final ITodoTaskPreferences preferences;
private static class ScriptIdentifierDetector implements IWordDetector {
@Override
public boolean isWordStart(char c) {
return Character.isJavaIdentifierStart(c);
}
@Override
public boolean isWordPart(char c) {
return Character.isJavaIdentifierPart(c);
}
}
private static class TaskTagMatcher extends CombinedWordRule.WordMatcher {
private IToken fToken;
/**
* Uppercase words
*/
private Map<CharacterBuffer, IToken> fUppercaseWords = new HashMap<>();
/**
* <code>true</code> if task tag detection is case-sensitive.
*/
private boolean fCaseSensitive = true;
/**
* Buffer for uppercase word
*/
private CombinedWordRule.CharacterBuffer fBuffer = new CombinedWordRule.CharacterBuffer(
16);
public TaskTagMatcher(IToken token) {
fToken = token;
}
@Override
public synchronized void clearWords() {
super.clearWords();
fUppercaseWords.clear();
}
public synchronized void addTaskTags(String[] tasks) {
for (int i = 0; i < tasks.length; i++) {
if (tasks[i].length() > 0) {
addWord(tasks[i], fToken);
}
}
}
@Override
public synchronized void addWord(String word, IToken token) {
Assert.isNotNull(word);
Assert.isNotNull(token);
super.addWord(word, token);
fUppercaseWords.put(
new CombinedWordRule.CharacterBuffer(word.toUpperCase()),
token);
}
@Override
public synchronized IToken evaluate(ICharacterScanner scanner,
CombinedWordRule.CharacterBuffer word) {
if (fCaseSensitive)
return super.evaluate(scanner, word);
fBuffer.clear();
for (int i = 0, n = word.length(); i < n; i++)
fBuffer.append(Character.toUpperCase(word.charAt(i)));
IToken token = fUppercaseWords.get(fBuffer);
if (token != null)
return token;
return Token.UNDEFINED;
}
/**
* Is task tag detection case-sensitive?
*
* @return <code>true</code> iff task tag detection is case-sensitive
* @since 3.0
*/
@SuppressWarnings("unused")
public boolean isCaseSensitive() {
return fCaseSensitive;
}
/**
* Enables/disables the case-sensitivity of the task tag detection.
*
* @param caseSensitive
* <code>true</code> iff case-sensitivity
* should be enabled
* @since 3.0
*/
public void setCaseSensitive(boolean caseSensitive) {
fCaseSensitive = caseSensitive;
}
}
public ScriptCommentScanner(IColorManager manager, IPreferenceStore store,
String comment, String todoTag, ITodoTaskPreferences preferences) {
this(manager, store, comment, todoTag, preferences, true);
}
protected ScriptSourceViewerConfiguration fConfiguration;
public ScriptCommentScanner(ScriptSourceViewerConfiguration configuration,
String comment, String todoTag, ITodoTaskPreferences preferences) {
this(configuration.getColorManager(),
configuration.getPreferenceStore(), comment, todoTag,
preferences, false);
this.fConfiguration = configuration;
initialize();
}
/**
* @since 2.0
*/
public ScriptCommentScanner(IColorManager manager, IPreferenceStore store,
String comment, String todoTag, ITodoTaskPreferences preferences,
boolean initializeAutomatically) {
super(manager, store);
fConfiguration = null;
fTodoToken = todoTag;
fDefaultTokenProperty = comment;
this.preferences = preferences;
if (initializeAutomatically) {
initialize();
}
}
@Override
protected String[] getTokenProperties() {
return new String[] { fDefaultTokenProperty, fTodoToken };
}
/**
* @since 3.0
*/
protected Token getDefaultToken() {
return getToken(fDefaultTokenProperty);
}
/**
* @since 3.0
*/
protected Token getTodoToken() {
return getToken(fTodoToken);
}
@Override
protected List<IRule> createRules() {
setDefaultReturnToken(getDefaultToken());
List<IRule> list = new ArrayList<>();
list.add(createTodoRule());
return list;
}
protected IRule createTodoRule() {
CombinedWordRule combinedWordRule = new CombinedWordRule(
createIdentifierDetector(), getDefaultToken());
List<CombinedWordRule.WordMatcher> matchers = createMatchers();
if (matchers.size() > 0) {
for (int i = 0, n = matchers.size(); i < n; i++) {
combinedWordRule.addWordMatcher(matchers.get(i));
}
}
return combinedWordRule;
}
/**
* @since 3.0
*/
protected IWordDetector createIdentifierDetector() {
return new ScriptIdentifierDetector();
}
/**
* Creates a list of word matchers.
*
* @return the list of word matchers
*/
protected List<CombinedWordRule.WordMatcher> createMatchers() {
List<CombinedWordRule.WordMatcher> list = new ArrayList<>();
String[] tasks = preferences.getTagNames();
if (tasks != null && tasks.length != 0) {
fTaskTagMatcher = new TaskTagMatcher(getTodoToken());
fTaskTagMatcher.addTaskTags(tasks);
fTaskTagMatcher.setCaseSensitive(preferences.isCaseSensitive());
list.add(fTaskTagMatcher);
}
return list;
}
/**
* Returns the character used to identify a comment.
*
* <p>
* Default implementation returns <code>#</code>. Clients may override if
* their language uses a different identifier.
* </p>
*/
@Deprecated
protected char getCommentChar() {
return '#';
}
@Override
public void setRange(IDocument document, int offset, int length) {
super.setRange(document, offset, length);
state = STATE_START;
}
@Override
public void adaptToPreferenceChange(PropertyChangeEvent event) {
if (fTaskTagMatcher != null
&& event.getProperty().equals(ITodoTaskPreferences.TAGS)) {
Object value = event.getNewValue();
if (value instanceof String) {
synchronized (fTaskTagMatcher) {
fTaskTagMatcher.clearWords();
fTaskTagMatcher.addTaskTags(preferences.getTagNames());
}
}
} else if (fTaskTagMatcher != null && event.getProperty()
.equals(ITodoTaskPreferences.CASE_SENSITIVE)) {
Object value = event.getNewValue();
if (value instanceof String) {
boolean caseSensitive = Boolean.valueOf((String) value)
.booleanValue();
fTaskTagMatcher.setCaseSensitive(caseSensitive);
}
} else if (super.affectsBehavior(event)) {
super.adaptToPreferenceChange(event);
}
}
@Override
public boolean affectsBehavior(PropertyChangeEvent event) {
if (event.getProperty().equals(ITodoTaskPreferences.TAGS)) {
return true;
}
if (event.getProperty().equals(ITodoTaskPreferences.CASE_SENSITIVE)) {
return true;
}
return super.affectsBehavior(event);
}
private int state = STATE_START;
private static final int STATE_START = 0;
private static final int STATE_STARTED = 1;
private static final int STATE_BODY = 2;
/**
* Skip possible comment characters. Returns the number of characters
* skipped, zero if none.
*
* @return
* @since 2.0
*/
protected int skipCommentChars() {
if (read() == getCommentChar()) {
return 1;
}
unread();
return 0;
}
/*
* We overload nextToken() because of the way task parsing is implemented:
* the TO-DO tasks are recognized only at the beginning of the comment
*/
@Override
public IToken nextToken() {
fTokenOffset = fOffset;
fColumn = UNDEFINED;
if (state == STATE_START) {
state = STATE_STARTED;
int count = skipCommentChars();
int c = read();
while (c != EOF && (c == ' ' || c == '\t')) {
c = read();
++count;
}
unread();
if (count > 0) {
return fDefaultReturnToken;
} else if (c == EOF) {
return Token.EOF;
}
}
if (state == STATE_STARTED) {
state = STATE_BODY;
final IToken token = fRules[0].evaluate(this);
if (!token.isUndefined()) {
return token;
}
}
int count = 0;
for (;;) {
int c = read();
if (c == EOF) {
break;
}
++count;
if (c == '\r') {
if (read() == '\n') {
++count;
} else {
unread();
}
state = STATE_START;
break;
} else if (c == '\n') {
state = STATE_START;
break;
}
}
return count > 0 ? fDefaultReturnToken : Token.EOF;
}
}