aboutsummaryrefslogtreecommitdiffstats
path: root/language-web/src/main/js/xtext/ContentAssistService.ts
diff options
context:
space:
mode:
Diffstat (limited to 'language-web/src/main/js/xtext/ContentAssistService.ts')
-rw-r--r--language-web/src/main/js/xtext/ContentAssistService.ts66
1 files changed, 51 insertions, 15 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 }