19
19
class DiagnosticsEngine
20
20
{
21
21
/**
22
- * @var Deferred<TextDocumentItem >
22
+ * @var Deferred<null >
23
23
*/
24
24
private Deferred $ deferred ;
25
25
26
- private bool $ running = false ;
27
-
28
- private ?TextDocumentItem $ next = null ;
26
+ private ?TextDocumentItem $ waiting = null ;
29
27
30
28
/**
31
29
* @var array<int|string,list<Diagnostic>>
@@ -42,6 +40,8 @@ class DiagnosticsEngine
42
40
*/
43
41
private array $ locks = [];
44
42
43
+ private float $ lastUpdatedAt ;
44
+
45
45
/**
46
46
* @param DiagnosticsProvider[] $providers
47
47
*/
@@ -51,6 +51,7 @@ public function __construct(private ClientApi $clientApi, private LoggerInterfac
51
51
$ this ->providers = $ providers ;
52
52
$ this ->clientApi = $ clientApi ;
53
53
$ this ->sleepTime = $ sleepTime ;
54
+ $ this ->lastUpdatedAt = 0.0 ;
54
55
}
55
56
56
57
public function clear (TextDocumentItem $ textDocument ): void
@@ -67,8 +68,6 @@ public function clear(TextDocumentItem $textDocument): void
67
68
*/
68
69
public function run (CancellationToken $ token ): Promise
69
70
{
70
-
71
-
72
71
return \Amp \call (function () use ($ token ) {
73
72
while (true ) {
74
73
try {
@@ -77,30 +76,29 @@ public function run(CancellationToken $token): Promise
77
76
return ;
78
77
}
79
78
80
- $ textDocument = yield $ this ->nextDocument ();
81
- $ lastKnownVersion = ($ this ->versions [$ textDocument ->uri ] ?? -1 );
79
+ yield $ this ->awaitNextDocument ();
82
80
83
- if ($ lastKnownVersion <= $ textDocument ->version && isset ($ this ->diagnostics [$ textDocument ->uri ])) {
84
- // reset diagnostics for this document
85
- $ this ->clientApi ->diagnostics ()->publishDiagnostics (
86
- $ textDocument ->uri ,
87
- $ textDocument ->version ,
88
- [],
89
- );
90
- }
81
+ $ gracePeriod = abs ($ this ->sleepTime - ((microtime (true ) - $ this ->lastUpdatedAt ) * 1000 ));
82
+ yield delay (intval ($ gracePeriod ));
91
83
92
- $ this ->diagnostics [$ textDocument ->uri ] = [];
84
+ $ textDocument = $ this ->waiting ;
85
+ $ this ->waiting = null ;
86
+ // allow the next document update to resolve
93
87
$ this ->deferred = new Deferred ();
94
- // after we have reset deferred, we can safely set linting to
95
- // `false` and let another resolve happen
96
- $ this ->running = false ;
97
88
98
- // if the last processed version of the document is more recent
99
- // than the last then continue.
100
- if ($ lastKnownVersion >= $ textDocument ->version ) {
89
+ // should never happen
90
+ if ($ textDocument === null ) {
101
91
continue ;
102
92
}
103
93
94
+ // reset diagnostics for this document
95
+ $ this ->clientApi ->diagnostics ()->publishDiagnostics (
96
+ $ textDocument ->uri ,
97
+ $ textDocument ->version ,
98
+ [],
99
+ );
100
+ $ this ->diagnostics [$ textDocument ->uri ] = [];
101
+
104
102
$ this ->versions [$ textDocument ->uri ] = $ textDocument ->version ;
105
103
106
104
$ crashedProviders = [];
@@ -113,7 +111,7 @@ public function run(CancellationToken $token): Promise
113
111
asyncCall (function () use ($ providerId , $ provider , $ token , $ textDocument , &$ crashedProviders ) {
114
112
$ start = microtime (true );
115
113
116
- yield $ this ->await ($ providerId );
114
+ yield $ this ->awaitProviderLock ($ providerId );
117
115
118
116
if (!$ this ->isDocumentCurrent ($ textDocument )) {
119
117
return ;
@@ -151,16 +149,11 @@ public function run(CancellationToken $token): Promise
151
149
$ diagnostics
152
150
);
153
151
154
- $ timeToSleep = $ this ->sleepTime - $ elapsed ;
155
-
156
- if ($ timeToSleep > 0 ) {
157
- yield delay ($ timeToSleep );
158
- }
159
-
160
152
if (!$ this ->isDocumentCurrent ($ textDocument )) {
161
153
return ;
162
154
}
163
155
156
+
164
157
$ this ->clientApi ->diagnostics ()->publishDiagnostics (
165
158
$ textDocument ->uri ,
166
159
$ textDocument ->version ,
@@ -174,27 +167,35 @@ public function run(CancellationToken $token): Promise
174
167
175
168
public function enqueue (TextDocumentItem $ textDocument ): void
176
169
{
177
- // if we are already linting then store whatever comes afterwards in
178
- // next, overwriting the redundant update
179
- if ($ this ->running === true ) {
180
- $ this ->next = $ textDocument ;
170
+ // set the last updated at timestamp - this will be used as the basis of
171
+ // the grace period before linting
172
+ $ this ->lastUpdatedAt = microtime (true );
173
+
174
+ $ waiting = $ this ->waiting ;
175
+
176
+ // set the next document
177
+ $ this ->waiting = $ textDocument ;
178
+
179
+ // if we already had a waiting document then do nothing
180
+ // it will get resolved next time
181
+ if ($ waiting !== null ) {
181
182
return ;
182
183
}
183
184
184
- // resolving the promise will start the diagnostc resolving
185
- $ this ->running = true ;
186
- $ this ->deferred ->resolve ($ textDocument );
185
+ // otherwise trigger the lint process
186
+ $ this ->deferred ->resolve ();
187
187
}
188
188
189
189
private function isDocumentCurrent (TextDocumentItem $ textDocument ): bool
190
190
{
191
191
return $ textDocument ->version === ($ this ->versions [$ textDocument ->uri ] ?? -1 );
192
192
}
193
+
193
194
/**
194
195
* @param string|int $providerId
195
196
* @return Promise<bool>
196
197
*/
197
- private function await ($ providerId ): Promise
198
+ private function awaitProviderLock ($ providerId ): Promise
198
199
{
199
200
if (!array_key_exists ($ providerId , $ this ->locks )) {
200
201
return new Success (true );
@@ -205,14 +206,12 @@ private function await($providerId): Promise
205
206
}
206
207
207
208
/**
208
- * @return Promise<TextDocumentItem >
209
+ * @return Promise<null >
209
210
*/
210
- private function nextDocument (): Promise
211
+ private function awaitNextDocument (): Promise
211
212
{
212
- if ($ this ->next ) {
213
- $ textDocument = $ this ->next ;
214
- $ this ->next = null ;
215
- return new Success ($ textDocument );
213
+ if ($ this ->waiting ) {
214
+ return new Success ();
216
215
}
217
216
218
217
return $ this ->deferred ->promise ();
0 commit comments