/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed 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 com.intellij.util.textCompletion;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.CharFilter;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.project.DumbAware;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Completion provider for a fixed collection of elements. See {@link ValuesCompletionProviderDumbAware} for dumb aware version.
* <p>
* Completion elements and their presentation (represented with {@link TextCompletionValueDescriptor}) are provided in constructor.
* Use {@link TextFieldWithCompletion} to create a text field component with completion.
* <p>
* Completion is done via {@link com.intellij.util.TextFieldCompletionProvider}.
*
* @param <T> completion element type.
*/
public class ValuesCompletionProvider<T> implements TextCompletionProvider {
@NotNull protected final TextCompletionValueDescriptor<T> myDescriptor;
@NotNull private final List<Character> mySeparators;
@NotNull protected final Collection<? extends T> myValues;
private final boolean myCaseSensitive;
@NotNull private final InsertHandler<LookupElement> myInsertHandler = new CompletionCharInsertHandler();
/**
* Create a completion provider.
*
* @param descriptor descriptor for completion values (text, icons, etc).
* @param separators characters that separate values in the editor (like new line or space). If user is supposed to choose only one value this list should be empty.
* @param values values to show in completion.
* @param caseSensitive is completion case-sensitive.
*/
public ValuesCompletionProvider(@NotNull TextCompletionValueDescriptor<T> descriptor,
@NotNull List<Character> separators,
@NotNull Collection<? extends T> values, boolean caseSensitive) {
myDescriptor = descriptor;
mySeparators = separators;
myValues = values;
myCaseSensitive = caseSensitive;
}
/**
* Creates a completion provider for selecting single value from a list of values. Completion is case-insensitive.
* @param presentation descriptor for completion values.
* @param values list of values.
*/
public ValuesCompletionProvider(@NotNull TextCompletionValueDescriptor<T> presentation,
@NotNull Collection<? extends T> values) {
this(presentation, Collections.emptyList(), values, false);
}
@Nullable
@Override
public String getAdvertisement() {
return "";
}
@Nullable
@Override
public String getPrefix(@NotNull String text, int offset) {
return getPrefix(text, offset, mySeparators);
}
@NotNull
protected static String getPrefix(@NotNull String text, int offset, @NotNull Collection<Character> separators) {
int index = -1;
for (char c : separators) {
index = Math.max(text.lastIndexOf(c, offset - 1), index);
}
return text.substring(index + 1, offset);
}
@NotNull
@Override
public CompletionResultSet applyPrefixMatcher(@NotNull CompletionResultSet result, @NotNull String prefix) {
CompletionResultSet resultWithMatcher = result.withPrefixMatcher(new PlainPrefixMatcher(prefix));
if (!myCaseSensitive) resultWithMatcher = resultWithMatcher.caseInsensitive();
return resultWithMatcher;
}
@Override
@Nullable
public CharFilter.Result acceptChar(char c) {
if (!mySeparators.contains(c)) return CharFilter.Result.ADD_TO_PREFIX;
return CharFilter.Result.HIDE_LOOKUP;
}
@Override
public void fillCompletionVariants(@NotNull CompletionParameters parameters,
@NotNull String prefix,
@NotNull CompletionResultSet result) {
Collection<? extends T> values = getValues(prefix, result);
values = ContainerUtil.sorted(values, myDescriptor);
for (T completionVariant : values) {
result.addElement(installInsertHandler(myDescriptor.createLookupBuilder(completionVariant)));
}
result.stopHere();
}
@NotNull
protected LookupElement installInsertHandler(@NotNull LookupElementBuilder builder) {
InsertHandler<LookupElement> handler = builder.getInsertHandler();
if (handler == null) return builder.withInsertHandler(myInsertHandler);
return builder.withInsertHandler(new InsertHandler<LookupElement>() {
@Override
public void handleInsert(InsertionContext context, LookupElement item) {
myInsertHandler.handleInsert(context, item);
handler.handleInsert(context, item);
}
});
}
@NotNull
protected Collection<? extends T> getValues(@NotNull String prefix, @NotNull CompletionResultSet result) {
return myValues;
}
public class CompletionCharInsertHandler implements InsertHandler<LookupElement> {
@Override
public void handleInsert(InsertionContext context, LookupElement item) {
context.setAddCompletionChar(mySeparators.contains(context.getCompletionChar()));
}
}
public static class ValuesCompletionProviderDumbAware<T> extends ValuesCompletionProvider<T> implements DumbAware {
public ValuesCompletionProviderDumbAware(@NotNull TextCompletionValueDescriptor<T> descriptor,
@NotNull List<Character> separators,
@NotNull Collection<? extends T> values,
boolean caseSensitive) {
super(descriptor, separators, values, caseSensitive);
}
public ValuesCompletionProviderDumbAware(@NotNull TextCompletionValueDescriptor<T> presentation,
@NotNull Collection<? extends T> values) {
super(presentation, values);
}
}
}