├── code-completion.iml
├── resources
└── META-INF
│ └── plugin.xml
├── src
└── dev
│ └── ja
│ └── samples
│ └── completion
│ ├── Dictionary.java
│ ├── DictionaryCompletionProvider.java
│ ├── DictionaryLoader.java
│ ├── StringLiteralDictionaryContributor.java
│ └── StringLiteralPattern.java
└── test
└── dev
└── ja
└── samples
└── completion
├── StringLiteralDictionaryAutoPopupContributorTest.java
└── StringLiteralDictionaryContributorTest.java
/code-completion.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | dev.j-a.sample.code-completion
3 | Code Completion
4 | 0.1.0
5 | Joachim Ansorg
6 |
7 |
11 |
12 |
13 |
14 | com.intellij.modules.lang
15 |
16 |
17 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/dev/ja/samples/completion/Dictionary.java:
--------------------------------------------------------------------------------
1 | package dev.ja.samples.completion;
2 |
3 | import com.intellij.spellchecker.BundledDictionaryProvider;
4 | import com.intellij.spellchecker.SpellCheckerManager;
5 | import com.intellij.spellchecker.StreamLoader;
6 | import com.intellij.spellchecker.compress.CompressedDictionary;
7 | import com.intellij.spellchecker.engine.Transformation;
8 | import com.intellij.util.containers.ContainerUtil;
9 |
10 | import java.io.InputStream;
11 | import java.util.List;
12 |
13 | /**
14 | * @author jansorg
15 | */
16 | class Dictionary {
17 | private static final List bundledDictionaries = ContainerUtil.createLockFreeCopyOnWriteList();
18 |
19 | // load() loads all build-in dictionaries in a thread-safe manner
20 | static void load() {
21 | for (BundledDictionaryProvider provider : BundledDictionaryProvider.EP_NAME.getExtensions()) {
22 | for (String name : provider.getBundledDictionaries()) {
23 | InputStream stream = SpellCheckerManager.class.getResourceAsStream(name);
24 | if (stream != null) {
25 | StreamLoader loader = new StreamLoader(stream, name);
26 | CompressedDictionary dict = CompressedDictionary.create(loader, new Transformation());
27 | bundledDictionaries.add(dict);
28 | }
29 | }
30 | }
31 | }
32 |
33 | static List get() {
34 | return bundledDictionaries;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/dev/ja/samples/completion/DictionaryCompletionProvider.java:
--------------------------------------------------------------------------------
1 | package dev.ja.samples.completion;
2 |
3 | import com.intellij.codeInsight.completion.CompletionParameters;
4 | import com.intellij.codeInsight.completion.CompletionProvider;
5 | import com.intellij.codeInsight.completion.CompletionResultSet;
6 | import com.intellij.codeInsight.lookup.LookupElementBuilder;
7 | import com.intellij.openapi.progress.ProgressManager;
8 | import com.intellij.spellchecker.compress.CompressedDictionary;
9 | import com.intellij.util.ProcessingContext;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import java.util.List;
13 |
14 | /**
15 | * @author jansorg
16 | */
17 | class DictionaryCompletionProvider extends CompletionProvider {
18 | private final boolean onlyManual;
19 |
20 | /**
21 | * @param onlyManual if true, then completions are only returned when the user manually requested it
22 | */
23 | DictionaryCompletionProvider(boolean onlyManual) {
24 | this.onlyManual = onlyManual;
25 | }
26 |
27 | @Override
28 | protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
29 | if (parameters.isAutoPopup() && onlyManual) {
30 | return;
31 | }
32 |
33 | String prefix = result.getPrefixMatcher().getPrefix();
34 | if (prefix.isEmpty()) {
35 | return;
36 | }
37 |
38 | // make sure that our prefix is the last word
39 | // for plain text file, all the content up to the caret is the prefix
40 | // we don't want that, because we're only completing a single word
41 | CompletionResultSet dictResult;
42 | int lastSpace = prefix.lastIndexOf(' ');
43 | if (lastSpace >= 0 && lastSpace < prefix.length() - 1) {
44 | prefix = prefix.substring(lastSpace + 1);
45 | dictResult = result.withPrefixMatcher(prefix);
46 | } else {
47 | dictResult = result;
48 | }
49 |
50 | int length = prefix.length();
51 | char firstChar = prefix.charAt(0);
52 | boolean isUppercase = Character.isUpperCase(firstChar);
53 |
54 | List dicts = Dictionary.get();
55 | if (dicts == null || dicts.isEmpty()) {
56 | return;
57 | }
58 |
59 | for (CompressedDictionary dict : dicts) {
60 | dict.getWords(Character.toLowerCase(firstChar), length, length + 20, word -> {
61 | ProgressManager.checkCanceled();
62 |
63 | LookupElementBuilder element;
64 | if (isUppercase) {
65 | element = LookupElementBuilder.create(word.substring(0, 1).toUpperCase() + word.substring(1));
66 | } else {
67 | element = LookupElementBuilder.create(word);
68 | }
69 | dictResult.addElement(element);
70 | });
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/dev/ja/samples/completion/DictionaryLoader.java:
--------------------------------------------------------------------------------
1 | package dev.ja.samples.completion;
2 |
3 | import com.intellij.openapi.application.PreloadingActivity;
4 | import com.intellij.openapi.progress.ProgressIndicator;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 |
8 | /**
9 | * Preloading activity to load the dictionary at startup.
10 | *
11 | * @author jansorg
12 | */
13 | public class DictionaryLoader extends PreloadingActivity {
14 | @Override
15 | public void preload(@NotNull ProgressIndicator indicator) {
16 | Dictionary.load();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/dev/ja/samples/completion/StringLiteralDictionaryContributor.java:
--------------------------------------------------------------------------------
1 | package dev.ja.samples.completion;
2 |
3 | import com.intellij.codeInsight.completion.CompletionContributor;
4 | import com.intellij.codeInsight.completion.CompletionType;
5 | import com.intellij.patterns.PlatformPatterns;
6 | import com.intellij.psi.PlainTextTokenTypes;
7 |
8 | /**
9 | * @author jansorg
10 | */
11 | public class StringLiteralDictionaryContributor extends CompletionContributor {
12 | public StringLiteralDictionaryContributor() {
13 | // completions for plain text files
14 | extend(CompletionType.BASIC,
15 | PlatformPatterns.psiElement(PlainTextTokenTypes.PLAIN_TEXT),
16 | new DictionaryCompletionProvider(false));
17 |
18 | // completions for content of string literals
19 | extend(CompletionType.BASIC,
20 | PlatformPatterns.psiElement().with(new StringLiteralPattern()),
21 | new DictionaryCompletionProvider(false));
22 |
23 | // always suggest when invoked manually
24 | extend(CompletionType.BASIC,
25 | PlatformPatterns.not(PlatformPatterns.alwaysFalse()),
26 | new DictionaryCompletionProvider(true));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/dev/ja/samples/completion/StringLiteralPattern.java:
--------------------------------------------------------------------------------
1 | package dev.ja.samples.completion;
2 |
3 | import com.intellij.lang.ASTNode;
4 | import com.intellij.lang.Language;
5 | import com.intellij.lang.LanguageParserDefinitions;
6 | import com.intellij.lang.ParserDefinition;
7 | import com.intellij.patterns.PatternCondition;
8 | import com.intellij.psi.PsiElement;
9 | import com.intellij.psi.tree.TokenSet;
10 | import com.intellij.psi.util.PsiUtilCore;
11 | import com.intellij.util.ProcessingContext;
12 | import org.jetbrains.annotations.NotNull;
13 |
14 | /**
15 | * Pattern which only accepts PsiElements, which either are a string literal or a comment of the current language.
16 | * It also accepts if the element's parent matches these conditions.
17 | *
18 | * @author jansorg
19 | */
20 | class StringLiteralPattern extends PatternCondition {
21 | StringLiteralPattern() {
22 | super("stringLiteralPattern()");
23 | }
24 |
25 | @Override
26 | public boolean accepts(@NotNull PsiElement psi, ProcessingContext context) {
27 | Language language = PsiUtilCore.findLanguageFromElement(psi);
28 | ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(language);
29 | if (definition == null) {
30 | return false;
31 | }
32 |
33 | // suggest completions in string and comment literals
34 | TokenSet tokens = TokenSet.orSet(
35 | definition.getStringLiteralElements(),
36 | definition.getCommentTokens());
37 |
38 | ASTNode node = psi.getNode();
39 | if (node == null) {
40 | return false;
41 | }
42 |
43 | if (tokens.contains(node.getElementType())) {
44 | return true;
45 | }
46 |
47 | return false;
48 | // node = node.getTreeParent();
49 | // return node != null && tokens.contains(node.getElementType());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/test/dev/ja/samples/completion/StringLiteralDictionaryAutoPopupContributorTest.java:
--------------------------------------------------------------------------------
1 | package dev.ja.samples.completion;
2 |
3 | import com.intellij.testFramework.fixtures.BasePlatformTestCase;
4 | import com.intellij.testFramework.fixtures.CompletionAutoPopupTester;
5 | import org.junit.Assert;
6 |
7 | /**
8 | * We're using the Junit 3 base class, because we the Junit4 variant doesn't support runInDispatchThread() properly.
9 | *
10 | * @author jansorg
11 | */
12 | public class StringLiteralDictionaryAutoPopupContributorTest extends BasePlatformTestCase {
13 | @Override
14 | protected void setUp() throws Exception {
15 | super.setUp();
16 | // preloadingActivity don't seem to be run in tests
17 | Dictionary.load();
18 | }
19 |
20 | @Override
21 | protected boolean runInDispatchThread() {
22 | return false;
23 | }
24 |
25 | public void testAutoPopupCompletions() {
26 | CompletionAutoPopupTester tester = new CompletionAutoPopupTester(myFixture);
27 | tester.runWithAutoPopupEnabled(() -> {
28 | myFixture.configureByText("test.txt", "");
29 |
30 | tester.typeWithPauses("ob");
31 |
32 | tester.joinCompletion();
33 | Assert.assertNotNull(tester.getLookup());
34 | Assert.assertEquals(314, tester.getLookup().getItems().size());
35 |
36 | // ob -> obfusc
37 | tester.typeWithPauses("fusc");
38 | tester.joinCompletion();
39 | Assert.assertNotNull(tester.getLookup());
40 | Assert.assertEquals(8, tester.getLookup().getItems().size());
41 | });
42 | }
43 | }
--------------------------------------------------------------------------------
/test/dev/ja/samples/completion/StringLiteralDictionaryContributorTest.java:
--------------------------------------------------------------------------------
1 | package dev.ja.samples.completion;
2 |
3 | import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixture4TestCase;
4 | import org.junit.Assert;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | /**
9 | * @author jansorg
10 | */
11 | public class StringLiteralDictionaryContributorTest extends LightPlatformCodeInsightFixture4TestCase {
12 | @Before
13 | public void loadDicts() {
14 | // preloadingActivity don't seem to be run in tests
15 | Dictionary.load();
16 | }
17 |
18 | @Test
19 | public void noEmptyPrefix() {
20 | myFixture.configureByText("test.txt", "");
21 | Assert.assertEquals("expected no completions for an empty file", 0, myFixture.completeBasic().length);
22 |
23 | // whitespace suffix
24 | myFixture.configureByText("test.txt", "foo");
25 | myFixture.type(" ");
26 | Assert.assertEquals("expected no completions after whitespace", 0, myFixture.completeBasic().length);
27 | }
28 |
29 | @Test
30 | public void completions() {
31 | myFixture.configureByText("test.txt", "");
32 |
33 | myFixture.type("ob");
34 | Assert.assertEquals(314, myFixture.completeBasic().length);
35 |
36 | // ob -> obfus
37 | myFixture.type("fus");
38 | Assert.assertEquals(8, myFixture.completeBasic().length);
39 | }
40 | }
--------------------------------------------------------------------------------