aboutsummaryrefslogtreecommitdiffstats
path: root/language-web/src/main/js
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/src/main/js
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/src/main/js')
-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
3 files changed, 62 insertions, 26 deletions
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);