aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/xtext/OccurrencesService.ts
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src/xtext/OccurrencesService.ts')
-rw-r--r--subprojects/frontend/src/xtext/OccurrencesService.ts119
1 files changed, 46 insertions, 73 deletions
diff --git a/subprojects/frontend/src/xtext/OccurrencesService.ts b/subprojects/frontend/src/xtext/OccurrencesService.ts
index 35913f43..c8d6fd7b 100644
--- a/subprojects/frontend/src/xtext/OccurrencesService.ts
+++ b/subprojects/frontend/src/xtext/OccurrencesService.ts
@@ -1,20 +1,15 @@
1import { Transaction } from '@codemirror/state'; 1import { Transaction } from '@codemirror/state';
2import { debounce } from 'lodash-es';
2 3
3import type EditorStore from '../editor/EditorStore'; 4import type EditorStore from '../editor/EditorStore';
4import { 5import {
5 type IOccurrence, 6 type IOccurrence,
6 isCursorWithinOccurence, 7 isCursorWithinOccurence,
7} from '../editor/findOccurrences'; 8} from '../editor/findOccurrences';
8import Timer from '../utils/Timer';
9import getLogger from '../utils/getLogger'; 9import getLogger from '../utils/getLogger';
10 10
11import type UpdateService from './UpdateService'; 11import type UpdateService from './UpdateService';
12import type XtextWebSocketClient from './XtextWebSocketClient'; 12import type { TextRegion } from './xtextServiceResults';
13import {
14 isConflictResult,
15 OccurrencesResult,
16 type TextRegion,
17} from './xtextServiceResults';
18 13
19const FIND_OCCURRENCES_TIMEOUT_MS = 1000; 14const FIND_OCCURRENCES_TIMEOUT_MS = 1000;
20 15
@@ -34,38 +29,23 @@ function transformOccurrences(regions: TextRegion[]): IOccurrence[] {
34} 29}
35 30
36export default class OccurrencesService { 31export default class OccurrencesService {
37 private readonly store: EditorStore;
38
39 private readonly webSocketClient: XtextWebSocketClient;
40
41 private readonly updateService: UpdateService;
42
43 private hasOccurrences = false; 32 private hasOccurrences = false;
44 33
45 private readonly findOccurrencesTimer = new Timer(() => { 34 private readonly findOccurrencesLater = debounce(
46 this.handleFindOccurrences(); 35 () => this.findOccurrences(),
47 }, FIND_OCCURRENCES_TIMEOUT_MS); 36 FIND_OCCURRENCES_TIMEOUT_MS,
48 37 );
49 private readonly clearOccurrencesTimer = new Timer(() => {
50 this.clearOccurrences();
51 });
52 38
53 constructor( 39 constructor(
54 store: EditorStore, 40 private readonly store: EditorStore,
55 webSocketClient: XtextWebSocketClient, 41 private readonly updateService: UpdateService,
56 updateService: UpdateService, 42 ) {}
57 ) {
58 this.store = store;
59 this.webSocketClient = webSocketClient;
60 this.updateService = updateService;
61 }
62 43
63 onTransaction(transaction: Transaction): void { 44 onTransaction(transaction: Transaction): void {
64 if (transaction.docChanged) { 45 if (transaction.docChanged) {
65 // Must clear occurrences asynchronously from `onTransaction`, 46 // Must clear occurrences asynchronously from `onTransaction`,
66 // because we must not emit a conflicting transaction when handling the pending transaction. 47 // because we must not emit a conflicting transaction when handling the pending transaction.
67 this.clearOccurrencesTimer.schedule(); 48 this.clearAndFindOccurrencesLater();
68 this.findOccurrencesTimer.reschedule();
69 return; 49 return;
70 } 50 }
71 if (!transaction.isUserEvent('select')) { 51 if (!transaction.isUserEvent('select')) {
@@ -73,11 +53,10 @@ export default class OccurrencesService {
73 } 53 }
74 if (this.needsOccurrences) { 54 if (this.needsOccurrences) {
75 if (!isCursorWithinOccurence(this.store.state)) { 55 if (!isCursorWithinOccurence(this.store.state)) {
76 this.clearOccurrencesTimer.schedule(); 56 this.clearAndFindOccurrencesLater();
77 this.findOccurrencesTimer.reschedule();
78 } 57 }
79 } else { 58 } else {
80 this.clearOccurrencesTimer.schedule(); 59 this.clearOccurrencesLater();
81 } 60 }
82 } 61 }
83 62
@@ -85,8 +64,26 @@ export default class OccurrencesService {
85 return this.store.state.selection.main.empty; 64 return this.store.state.selection.main.empty;
86 } 65 }
87 66
88 private handleFindOccurrences() { 67 private clearAndFindOccurrencesLater(): void {
89 this.clearOccurrencesTimer.cancel(); 68 this.clearOccurrencesLater();
69 this.findOccurrencesLater();
70 }
71
72 /**
73 * Clears the occurences from a new immediate task to let the current editor transaction finish.
74 */
75 private clearOccurrencesLater() {
76 setTimeout(() => this.clearOccurrences(), 0);
77 }
78
79 private clearOccurrences() {
80 if (this.hasOccurrences) {
81 this.store.updateOccurrences([], []);
82 this.hasOccurrences = false;
83 }
84 }
85
86 private findOccurrences() {
90 this.updateOccurrences().catch((error) => { 87 this.updateOccurrences().catch((error) => {
91 log.error('Unexpected error while updating occurrences', error); 88 log.error('Unexpected error while updating occurrences', error);
92 this.clearOccurrences(); 89 this.clearOccurrences();
@@ -98,43 +95,26 @@ export default class OccurrencesService {
98 this.clearOccurrences(); 95 this.clearOccurrences();
99 return; 96 return;
100 } 97 }
101 await this.updateService.update(); 98 const fetchResult = await this.updateService.fetchOccurrences(() => {
102 const result = await this.webSocketClient.send({ 99 return this.needsOccurrences
103 resource: this.updateService.resourceName, 100 ? {
104 serviceType: 'occurrences', 101 cancelled: false,
105 expectedStateId: this.updateService.xtextStateId, 102 data: this.store.state.selection.main.head,
106 caretOffset: this.store.state.selection.main.head, 103 }
104 : { cancelled: true };
107 }); 105 });
108 const allChanges = this.updateService.computeChangesSinceLastUpdate(); 106 if (fetchResult.cancelled) {
109 if (!allChanges.empty || isConflictResult(result, 'canceled')) {
110 // Stale occurrences result, the user already made some changes. 107 // Stale occurrences result, the user already made some changes.
111 // We can safely ignore the occurrences and schedule a new find occurrences call. 108 // We can safely ignore the occurrences and schedule a new find occurrences call.
112 this.clearOccurrences(); 109 this.clearOccurrences();
113 this.findOccurrencesTimer.schedule(); 110 if (this.needsOccurrences) {
114 return; 111 this.findOccurrencesLater();
115 } 112 }
116 const parsedOccurrencesResult = OccurrencesResult.safeParse(result);
117 if (!parsedOccurrencesResult.success) {
118 log.error(
119 'Unexpected occurences result',
120 result,
121 'not an OccurrencesResult: ',
122 parsedOccurrencesResult.error,
123 );
124 this.clearOccurrences();
125 return;
126 }
127 const { stateId, writeRegions, readRegions } = parsedOccurrencesResult.data;
128 if (stateId !== this.updateService.xtextStateId) {
129 log.error(
130 'Unexpected state id, expected:',
131 this.updateService.xtextStateId,
132 'got:',
133 stateId,
134 );
135 this.clearOccurrences();
136 return; 113 return;
137 } 114 }
115 const {
116 data: { writeRegions, readRegions },
117 } = fetchResult;
138 const write = transformOccurrences(writeRegions); 118 const write = transformOccurrences(writeRegions);
139 const read = transformOccurrences(readRegions); 119 const read = transformOccurrences(readRegions);
140 this.hasOccurrences = write.length > 0 || read.length > 0; 120 this.hasOccurrences = write.length > 0 || read.length > 0;
@@ -147,11 +127,4 @@ export default class OccurrencesService {
147 ); 127 );
148 this.store.updateOccurrences(write, read); 128 this.store.updateOccurrences(write, read);
149 } 129 }
150
151 private clearOccurrences() {
152 if (this.hasOccurrences) {
153 this.store.updateOccurrences([], []);
154 this.hasOccurrences = false;
155 }
156 }
157} 130}