diff options
3 files changed, 62 insertions, 4 deletions
diff --git a/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java b/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java index 5327f7b4..3502c29f 100644 --- a/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java +++ b/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java | |||
@@ -3,16 +3,22 @@ | |||
3 | */ | 3 | */ |
4 | package tools.refinery.language.ide; | 4 | package tools.refinery.language.ide; |
5 | 5 | ||
6 | import org.eclipse.xtext.ide.editor.contentassist.IPrefixMatcher; | ||
6 | import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator; | 7 | import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator; |
7 | 8 | ||
9 | import tools.refinery.language.ide.contentassist.FuzzyMatcher; | ||
8 | import tools.refinery.language.ide.syntaxcoloring.ProblemSemanticHighlightingCalculator; | 10 | import tools.refinery.language.ide.syntaxcoloring.ProblemSemanticHighlightingCalculator; |
9 | 11 | ||
10 | /** | 12 | /** |
11 | * Use this class to register ide components. | 13 | * Use this class to register ide components. |
12 | */ | 14 | */ |
13 | public class ProblemIdeModule extends AbstractProblemIdeModule { | 15 | public class ProblemIdeModule extends AbstractProblemIdeModule { |
14 | |||
15 | public Class<? extends ISemanticHighlightingCalculator> bindISemanticHighlightingCalculator() { | 16 | public Class<? extends ISemanticHighlightingCalculator> bindISemanticHighlightingCalculator() { |
16 | return ProblemSemanticHighlightingCalculator.class; | 17 | return ProblemSemanticHighlightingCalculator.class; |
17 | } | 18 | } |
19 | |||
20 | @Override | ||
21 | public Class<? extends IPrefixMatcher> bindIPrefixMatcher() { | ||
22 | return FuzzyMatcher.class; | ||
23 | } | ||
18 | } | 24 | } |
diff --git a/language-ide/src/main/java/tools/refinery/language/ide/contentassist/FuzzyMatcher.java b/language-ide/src/main/java/tools/refinery/language/ide/contentassist/FuzzyMatcher.java new file mode 100644 index 00000000..fe722ca1 --- /dev/null +++ b/language-ide/src/main/java/tools/refinery/language/ide/contentassist/FuzzyMatcher.java | |||
@@ -0,0 +1,44 @@ | |||
1 | package tools.refinery.language.ide.contentassist; | ||
2 | |||
3 | import org.eclipse.xtext.ide.editor.contentassist.IPrefixMatcher; | ||
4 | |||
5 | import com.google.inject.Singleton; | ||
6 | |||
7 | /** | ||
8 | * Implements the candidate matching algoritm used by CodeMirror 6. | ||
9 | * | ||
10 | * Using this class ensures that the same candidates will be returned when | ||
11 | * filtering content assist proposals on the server as on the client. | ||
12 | * | ||
13 | * The matching is "fuzzy" (<code>fzf</code>-like), i.e., the prefix characters | ||
14 | * may occur anywhere in the name, but must be in the same order as in the | ||
15 | * prefix. | ||
16 | * | ||
17 | * @author Kristóf Marussy | ||
18 | */ | ||
19 | @Singleton | ||
20 | public class FuzzyMatcher implements IPrefixMatcher { | ||
21 | @Override | ||
22 | public boolean isCandidateMatchingPrefix(String name, String prefix) { | ||
23 | var nameIgnoreCase = name.toLowerCase(); | ||
24 | var prefixIgnoreCase = prefix.toLowerCase(); | ||
25 | int prefixLength = prefixIgnoreCase.length(); | ||
26 | if (prefixLength == 0) { | ||
27 | return true; | ||
28 | } | ||
29 | int nameLength = nameIgnoreCase.length(); | ||
30 | if (prefixLength > nameLength) { | ||
31 | return false; | ||
32 | } | ||
33 | int prefixIndex = 0; | ||
34 | for (int nameIndex = 0; nameIndex < nameLength; nameIndex++) { | ||
35 | if (nameIgnoreCase.charAt(nameIndex) == prefixIgnoreCase.charAt(prefixIndex)) { | ||
36 | prefixIndex++; | ||
37 | if (prefixIndex == prefixLength) { | ||
38 | return true; | ||
39 | } | ||
40 | } | ||
41 | } | ||
42 | return false; | ||
43 | } | ||
44 | } | ||
diff --git a/language-web/src/main/js/xtext/ContentAssistService.ts b/language-web/src/main/js/xtext/ContentAssistService.ts index ec6b80d2..8461ec7f 100644 --- a/language-web/src/main/js/xtext/ContentAssistService.ts +++ b/language-web/src/main/js/xtext/ContentAssistService.ts | |||
@@ -14,20 +14,28 @@ const PROPOSALS_LIMIT = 1000; | |||
14 | 14 | ||
15 | const IDENTIFIER_REGEXP_STR = '[a-zA-Z0-9_]*'; | 15 | const IDENTIFIER_REGEXP_STR = '[a-zA-Z0-9_]*'; |
16 | 16 | ||
17 | const HIGH_PRIORITY_KEYWORDS = ['<->']; | ||
18 | |||
19 | const QUALIFIED_NAME_SEPARATOR_REGEXP = /::/g; | ||
20 | |||
17 | const log = getLogger('xtext.ContentAssistService'); | 21 | const log = getLogger('xtext.ContentAssistService'); |
18 | 22 | ||
19 | function createCompletion(entry: IContentAssistEntry): Completion { | 23 | function createCompletion(entry: IContentAssistEntry): Completion { |
20 | let boost; | 24 | let boost; |
21 | switch (entry.kind) { | 25 | switch (entry.kind) { |
22 | case 'KEYWORD': | 26 | case 'KEYWORD': |
23 | boost = -99; | 27 | // Some hard-to-type operators should be on top. |
28 | boost = HIGH_PRIORITY_KEYWORDS.includes(entry.proposal) ? 10 : -99; | ||
24 | break; | 29 | break; |
25 | case 'TEXT': | 30 | case 'TEXT': |
26 | case 'SNIPPET': | 31 | case 'SNIPPET': |
27 | boost = -90; | 32 | boost = -90; |
28 | break; | 33 | break; |
29 | default: | 34 | default: { |
30 | boost = 0; | 35 | // Penalize qualified names (vs available unqualified names). |
36 | const extraSegments = entry.proposal.match(QUALIFIED_NAME_SEPARATOR_REGEXP)?.length || 0; | ||
37 | boost = Math.max(-5 * extraSegments, -50); | ||
38 | } | ||
31 | break; | 39 | break; |
32 | } | 40 | } |
33 | return { | 41 | return { |