aboutsummaryrefslogtreecommitdiffstats
path: root/language-web
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-30 14:29:51 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-31 19:26:13 +0100
commit692cb2218b6684d1289660a0d97e644806c31cc5 (patch)
tree7a8f9565ff62dbdb7ebc328abb71ebcf9bb40877 /language-web
parentchore(web): refactor xtext client (diff)
downloadrefinery-692cb2218b6684d1289660a0d97e644806c31cc5.tar.gz
refinery-692cb2218b6684d1289660a0d97e644806c31cc5.tar.zst
refinery-692cb2218b6684d1289660a0d97e644806c31cc5.zip
feat(web): server-side content assist filtering
Diffstat (limited to 'language-web')
-rw-r--r--language-web/package.json1
-rw-r--r--language-web/src/main/js/xtext/ContentAssistService.ts66
-rw-r--r--language-web/src/main/js/xtext/UpdateService.ts5
-rw-r--r--language-web/src/main/js/xtext/XtextClient.ts17
-rw-r--r--language-web/yarn.lock5
5 files changed, 68 insertions, 26 deletions
diff --git a/language-web/package.json b/language-web/package.json
index a602ec8d..9185f8fe 100644
--- a/language-web/package.json
+++ b/language-web/package.json
@@ -81,6 +81,7 @@
81 "@codemirror/view": "^0.19.9", 81 "@codemirror/view": "^0.19.9",
82 "@emotion/react": "^11.4.1", 82 "@emotion/react": "^11.4.1",
83 "@emotion/styled": "^11.3.0", 83 "@emotion/styled": "^11.3.0",
84 "escape-string-regexp": "^5.0.0",
84 "@fontsource/jetbrains-mono": "^4.5.0", 85 "@fontsource/jetbrains-mono": "^4.5.0",
85 "@fontsource/roboto": "^4.5.0", 86 "@fontsource/roboto": "^4.5.0",
86 "@lezer/lr": "^0.15.4", 87 "@lezer/lr": "^0.15.4",
diff --git a/language-web/src/main/js/xtext/ContentAssistService.ts b/language-web/src/main/js/xtext/ContentAssistService.ts
index 91789864..e9fdd12e 100644
--- a/language-web/src/main/js/xtext/ContentAssistService.ts
+++ b/language-web/src/main/js/xtext/ContentAssistService.ts
@@ -4,12 +4,54 @@ import type {
4 CompletionResult, 4 CompletionResult,
5} from '@codemirror/autocomplete'; 5} from '@codemirror/autocomplete';
6import type { ChangeSet, Transaction } from '@codemirror/state'; 6import type { ChangeSet, Transaction } from '@codemirror/state';
7import escapeStringRegexp from 'escape-string-regexp';
7 8
8import { getLogger } from '../logging'; 9import { getLogger } from '../logging';
9import type { UpdateService } from './UpdateService'; 10import type { UpdateService } from './UpdateService';
11import type { IContentAssistEntry } from './xtextServiceResults';
12
13const PROPOSALS_LIMIT = 1000;
14
15const IDENTIFIER_REGEXP_STR = '[a-zA-Z0-9_]*';
10 16
11const log = getLogger('xtext.ContentAssistService'); 17const log = getLogger('xtext.ContentAssistService');
12 18
19function createCompletion(entry: IContentAssistEntry): Completion {
20 let boost;
21 switch (entry.kind) {
22 case 'KEYWORD':
23 boost = -99;
24 break;
25 case 'TEXT':
26 case 'SNIPPET':
27 boost = -90;
28 break;
29 default:
30 boost = 0;
31 break;
32 }
33 return {
34 label: entry.proposal,
35 detail: entry.description,
36 info: entry.documentation,
37 type: entry.kind?.toLowerCase(),
38 boost,
39 };
40}
41
42function computeSpan(prefix: string, entryCount: number) {
43 const escapedPrefix = escapeStringRegexp(prefix);
44 if (entryCount < PROPOSALS_LIMIT) {
45 // Proposals with the current prefix fit the proposals limit.
46 // We can filter client side as long as the current prefix is preserved.
47 return new RegExp(`^${escapedPrefix}${IDENTIFIER_REGEXP_STR}$`);
48 }
49 // The current prefix overflows the proposals limits,
50 // so we have to fetch the completions again on the next keypress.
51 // Hopefully, it'll return a shorter list and we'll be able to filter client side.
52 return new RegExp(`^${escapedPrefix}$`);
53}
54
13export class ContentAssistService { 55export class ContentAssistService {
14 updateService: UpdateService; 56 updateService: UpdateService;
15 57
@@ -28,7 +70,7 @@ export class ContentAssistService {
28 async contentAssist(context: CompletionContext): Promise<CompletionResult> { 70 async contentAssist(context: CompletionContext): Promise<CompletionResult> {
29 const tokenBefore = context.tokenBefore(['QualifiedName']); 71 const tokenBefore = context.tokenBefore(['QualifiedName']);
30 let range: { from: number, to: number }; 72 let range: { from: number, to: number };
31 let selection: { selectionStart?: number, selectionEnd?: number }; 73 let prefix = '';
32 if (tokenBefore === null) { 74 if (tokenBefore === null) {
33 if (!context.explicit) { 75 if (!context.explicit) {
34 return { 76 return {
@@ -40,16 +82,16 @@ export class ContentAssistService {
40 from: context.pos, 82 from: context.pos,
41 to: context.pos, 83 to: context.pos,
42 }; 84 };
43 selection = {}; 85 prefix = '';
44 } else { 86 } else {
45 range = { 87 range = {
46 from: tokenBefore.from, 88 from: tokenBefore.from,
47 to: tokenBefore.to, 89 to: tokenBefore.to,
48 }; 90 };
49 selection = { 91 const prefixLength = context.pos - tokenBefore.from;
50 selectionStart: tokenBefore.from, 92 if (prefixLength > 0) {
51 selectionEnd: tokenBefore.to, 93 prefix = tokenBefore.text.substring(0, context.pos - tokenBefore.from);
52 }; 94 }
53 } 95 }
54 if (!context.explicit && this.shouldReturnCachedCompletion(tokenBefore)) { 96 if (!context.explicit && this.shouldReturnCachedCompletion(tokenBefore)) {
55 log.trace('Returning cached completion result'); 97 log.trace('Returning cached completion result');
@@ -64,7 +106,7 @@ export class ContentAssistService {
64 resource: this.updateService.resourceName, 106 resource: this.updateService.resourceName,
65 serviceType: 'assist', 107 serviceType: 'assist',
66 caretOffset: context.pos, 108 caretOffset: context.pos,
67 ...selection, 109 proposalsLimit: PROPOSALS_LIMIT,
68 }, context); 110 }, context);
69 if (context.aborted) { 111 if (context.aborted) {
70 return { 112 return {
@@ -74,19 +116,13 @@ export class ContentAssistService {
74 } 116 }
75 const options: Completion[] = []; 117 const options: Completion[] = [];
76 entries.forEach((entry) => { 118 entries.forEach((entry) => {
77 options.push({ 119 options.push(createCompletion(entry));
78 label: entry.proposal,
79 detail: entry.description,
80 info: entry.documentation,
81 type: entry.kind?.toLowerCase(),
82 boost: entry.kind === 'KEYWORD' ? -90 : 0,
83 });
84 }); 120 });
85 log.debug('Fetched', options.length, 'completions from server'); 121 log.debug('Fetched', options.length, 'completions from server');
86 this.lastCompletion = { 122 this.lastCompletion = {
87 ...range, 123 ...range,
88 options, 124 options,
89 span: /^[a-zA-Z0-9_:]*$/, 125 span: computeSpan(prefix, entries.length),
90 }; 126 };
91 return this.lastCompletion; 127 return this.lastCompletion;
92 } 128 }
diff --git a/language-web/src/main/js/xtext/UpdateService.ts b/language-web/src/main/js/xtext/UpdateService.ts
index f8ab7438..bbe7bb62 100644
--- a/language-web/src/main/js/xtext/UpdateService.ts
+++ b/language-web/src/main/js/xtext/UpdateService.ts
@@ -53,6 +53,11 @@ export class UpdateService {
53 this.webSocketClient = webSocketClient; 53 this.webSocketClient = webSocketClient;
54 } 54 }
55 55
56 onConnect(): Promise<void> {
57 this.xtextStateId = null;
58 return this.updateFullText();
59 }
60
56 onTransaction(transaction: Transaction): void { 61 onTransaction(transaction: Transaction): void {
57 const { changes } = transaction; 62 const { changes } = transaction;
58 if (!changes.empty) { 63 if (!changes.empty) {
diff --git a/language-web/src/main/js/xtext/XtextClient.ts b/language-web/src/main/js/xtext/XtextClient.ts
index f8b06258..92bad0d3 100644
--- a/language-web/src/main/js/xtext/XtextClient.ts
+++ b/language-web/src/main/js/xtext/XtextClient.ts
@@ -14,23 +14,18 @@ import { XtextWebSocketClient } from './XtextWebSocketClient';
14const log = getLogger('xtext.XtextClient'); 14const log = getLogger('xtext.XtextClient');
15 15
16export class XtextClient { 16export class XtextClient {
17 webSocketClient: XtextWebSocketClient; 17 private webSocketClient: XtextWebSocketClient;
18 18
19 updateService: UpdateService; 19 private updateService: UpdateService;
20 20
21 contentAssistService: ContentAssistService; 21 private contentAssistService: ContentAssistService;
22 22
23 validationService: ValidationService; 23 private validationService: ValidationService;
24 24
25 constructor(store: EditorStore) { 25 constructor(store: EditorStore) {
26 this.webSocketClient = new XtextWebSocketClient( 26 this.webSocketClient = new XtextWebSocketClient(
27 async () => { 27 () => this.updateService.onConnect(),
28 this.updateService.xtextStateId = null; 28 (resource, stateId, service, push) => this.onPush(resource, stateId, service, push),
29 await this.updateService.updateFullText();
30 },
31 async (resource, stateId, service, push) => {
32 await this.onPush(resource, stateId, service, push);
33 },
34 ); 29 );
35 this.updateService = new UpdateService(store, this.webSocketClient); 30 this.updateService = new UpdateService(store, this.webSocketClient);
36 this.contentAssistService = new ContentAssistService(this.updateService); 31 this.contentAssistService = new ContentAssistService(this.updateService);
diff --git a/language-web/yarn.lock b/language-web/yarn.lock
index 9d899a52..a29eb715 100644
--- a/language-web/yarn.lock
+++ b/language-web/yarn.lock
@@ -3236,6 +3236,11 @@ escape-string-regexp@^4.0.0:
3236 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 3236 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
3237 integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 3237 integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
3238 3238
3239escape-string-regexp@^5.0.0:
3240 version "5.0.0"
3241 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
3242 integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
3243
3239eslint-config-airbnb-base@^14.2.1: 3244eslint-config-airbnb-base@^14.2.1:
3240 version "14.2.1" 3245 version "14.2.1"
3241 resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e" 3246 resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e"