diff options
Diffstat (limited to 'subprojects/frontend/src/xtext/OccurrencesService.ts')
-rw-r--r-- | subprojects/frontend/src/xtext/OccurrencesService.ts | 119 |
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 @@ | |||
1 | import { Transaction } from '@codemirror/state'; | 1 | import { Transaction } from '@codemirror/state'; |
2 | import { debounce } from 'lodash-es'; | ||
2 | 3 | ||
3 | import type EditorStore from '../editor/EditorStore'; | 4 | import type EditorStore from '../editor/EditorStore'; |
4 | import { | 5 | import { |
5 | type IOccurrence, | 6 | type IOccurrence, |
6 | isCursorWithinOccurence, | 7 | isCursorWithinOccurence, |
7 | } from '../editor/findOccurrences'; | 8 | } from '../editor/findOccurrences'; |
8 | import Timer from '../utils/Timer'; | ||
9 | import getLogger from '../utils/getLogger'; | 9 | import getLogger from '../utils/getLogger'; |
10 | 10 | ||
11 | import type UpdateService from './UpdateService'; | 11 | import type UpdateService from './UpdateService'; |
12 | import type XtextWebSocketClient from './XtextWebSocketClient'; | 12 | import type { TextRegion } from './xtextServiceResults'; |
13 | import { | ||
14 | isConflictResult, | ||
15 | OccurrencesResult, | ||
16 | type TextRegion, | ||
17 | } from './xtextServiceResults'; | ||
18 | 13 | ||
19 | const FIND_OCCURRENCES_TIMEOUT_MS = 1000; | 14 | const FIND_OCCURRENCES_TIMEOUT_MS = 1000; |
20 | 15 | ||
@@ -34,38 +29,23 @@ function transformOccurrences(regions: TextRegion[]): IOccurrence[] { | |||
34 | } | 29 | } |
35 | 30 | ||
36 | export default class OccurrencesService { | 31 | export 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 | } |