aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-31 01:53:11 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-31 19:26:15 +0100
commita7ba55f2b781544cd74927137025f2c00a398f69 (patch)
tree3fdb3a0bea8f2b53e238180b33388004368f6bd6
parentfeat(web): find occurrences when idle (diff)
downloadrefinery-a7ba55f2b781544cd74927137025f2c00a398f69.tar.gz
refinery-a7ba55f2b781544cd74927137025f2c00a398f69.tar.zst
refinery-a7ba55f2b781544cd74927137025f2c00a398f69.zip
fix(web): fix server-side content assist filtering
-rw-r--r--language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java8
-rw-r--r--language-ide/src/main/java/tools/refinery/language/ide/contentassist/FuzzyMatcher.java44
-rw-r--r--language-web/src/main/js/xtext/ContentAssistService.ts14
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 */
4package tools.refinery.language.ide; 4package tools.refinery.language.ide;
5 5
6import org.eclipse.xtext.ide.editor.contentassist.IPrefixMatcher;
6import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator; 7import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator;
7 8
9import tools.refinery.language.ide.contentassist.FuzzyMatcher;
8import tools.refinery.language.ide.syntaxcoloring.ProblemSemanticHighlightingCalculator; 10import 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 */
13public class ProblemIdeModule extends AbstractProblemIdeModule { 15public 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 @@
1package tools.refinery.language.ide.contentassist;
2
3import org.eclipse.xtext.ide.editor.contentassist.IPrefixMatcher;
4
5import 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
20public 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
15const IDENTIFIER_REGEXP_STR = '[a-zA-Z0-9_]*'; 15const IDENTIFIER_REGEXP_STR = '[a-zA-Z0-9_]*';
16 16
17const HIGH_PRIORITY_KEYWORDS = ['<->'];
18
19const QUALIFIED_NAME_SEPARATOR_REGEXP = /::/g;
20
17const log = getLogger('xtext.ContentAssistService'); 21const log = getLogger('xtext.ContentAssistService');
18 22
19function createCompletion(entry: IContentAssistEntry): Completion { 23function 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 {