Mercurial > projects > dwt-addons
annotate dwtx/jface/text/reconciler/AbstractReconciler.d @ 133:7d818bd32d63
Fix ctors to this with gvim regexp
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sun, 24 Aug 2008 01:29:22 +0200 |
parents | c4fb132a086c |
children | 25f1f92fa3df |
rev | line source |
---|---|
129 | 1 /******************************************************************************* |
2 * Copyright (c) 2000, 2008 IBM Corporation and others. | |
3 * All rights reserved. This program and the accompanying materials | |
4 * are made available under the terms of the Eclipse Public License v1.0 | |
5 * which accompanies this distribution, and is available at | |
6 * http://www.eclipse.org/legal/epl-v10.html | |
7 * | |
8 * Contributors: | |
9 * IBM Corporation - initial API and implementation | |
10 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module dwtx.jface.text.reconciler.AbstractReconciler; | |
14 | |
131 | 15 import dwtx.jface.text.reconciler.IReconciler; // packageimport |
16 import dwtx.jface.text.reconciler.DirtyRegionQueue; // packageimport | |
17 import dwtx.jface.text.reconciler.IReconcilingStrategy; // packageimport | |
18 import dwtx.jface.text.reconciler.AbstractReconcileStep; // packageimport | |
19 import dwtx.jface.text.reconciler.IReconcilingStrategyExtension; // packageimport | |
20 import dwtx.jface.text.reconciler.MonoReconciler; // packageimport | |
21 import dwtx.jface.text.reconciler.IReconcileStep; // packageimport | |
22 import dwtx.jface.text.reconciler.Reconciler; // packageimport | |
23 import dwtx.jface.text.reconciler.IReconcilableModel; // packageimport | |
24 import dwtx.jface.text.reconciler.DirtyRegion; // packageimport | |
25 import dwtx.jface.text.reconciler.IReconcileResult; // packageimport | |
26 import dwtx.jface.text.reconciler.IReconcilerExtension; // packageimport | |
27 | |
28 | |
129 | 29 import dwt.dwthelper.utils; |
30 | |
31 | |
32 import dwtx.core.runtime.Assert; | |
33 import dwtx.core.runtime.IProgressMonitor; | |
34 import dwtx.core.runtime.NullProgressMonitor; | |
35 import dwtx.jface.text.DocumentEvent; | |
36 import dwtx.jface.text.IDocument; | |
37 import dwtx.jface.text.IDocumentListener; | |
38 import dwtx.jface.text.ITextInputListener; | |
39 import dwtx.jface.text.ITextViewer; | |
40 | |
41 | |
42 /** | |
43 * Abstract implementation of {@link IReconciler}. The reconciler | |
44 * listens to input document changes as well as changes of | |
45 * the input document of the text viewer it is installed on. Depending on | |
46 * its configuration it manages the received change notifications in a | |
47 * queue folding neighboring or overlapping changes together. The reconciler | |
48 * processes the dirty regions as a background activity after having waited for further | |
49 * changes for the configured duration of time. A reconciler is started using the | |
50 * {@link #install(ITextViewer)} method. As a first step {@link #initialProcess()} is | |
51 * executed in the background. Then, the reconciling thread waits for changes that | |
52 * need to be reconciled. A reconciler can be resumed by calling {@link #forceReconciling()} | |
53 * independent from the existence of actual changes. This mechanism is for subclasses only. | |
54 * It is the clients responsibility to stop a reconciler using its {@link #uninstall()} | |
55 * method. Unstopped reconcilers do not free their resources. | |
56 * <p> | |
57 * It is subclass responsibility to specify how dirty regions are processed. | |
58 * </p> | |
59 * | |
60 * @see dwtx.jface.text.IDocumentListener | |
61 * @see dwtx.jface.text.ITextInputListener | |
62 * @see dwtx.jface.text.reconciler.DirtyRegion | |
63 * @since 2.0 | |
64 */ | |
65 abstract public class AbstractReconciler : IReconciler { | |
66 | |
67 | |
68 /** | |
69 * Background thread for the reconciling activity. | |
70 */ | |
71 class BackgroundThread : Thread { | |
72 | |
73 /** Has the reconciler been canceled. */ | |
74 private bool fCanceled= false; | |
75 /** Has the reconciler been reset. */ | |
76 private bool fReset= false; | |
77 /** Some changes need to be processed. */ | |
78 private bool fIsDirty= false; | |
79 /** Is a reconciling strategy active. */ | |
80 private bool fIsActive= false; | |
81 | |
82 /** | |
83 * Creates a new background thread. The thread | |
84 * runs with minimal priority. | |
85 * | |
86 * @param name the thread's name | |
87 */ | |
133
7d818bd32d63
Fix ctors to this with gvim regexp
Frank Benoit <benoit@tionex.de>
parents:
131
diff
changeset
|
88 public this(String name) { |
129 | 89 super(name); |
90 setPriority(Thread.MIN_PRIORITY); | |
91 setDaemon(true); | |
92 } | |
93 | |
94 /** | |
95 * Returns whether a reconciling strategy is active right now. | |
96 * | |
97 * @return <code>true</code> if a activity is active | |
98 */ | |
99 public bool isActive() { | |
100 return fIsActive; | |
101 } | |
102 | |
103 /** | |
104 * Returns whether some changes need to be processed. | |
105 * | |
106 * @return <code>true</code> if changes wait to be processed | |
107 * @since 3.0 | |
108 */ | |
109 public synchronized bool isDirty() { | |
110 return fIsDirty; | |
111 } | |
112 | |
113 /** | |
114 * Cancels the background thread. | |
115 */ | |
116 public void cancel() { | |
117 fCanceled= true; | |
118 IProgressMonitor pm= fProgressMonitor; | |
119 if (pm !is null) | |
120 pm.setCanceled(true); | |
121 synchronized (fDirtyRegionQueue) { | |
122 fDirtyRegionQueue.notifyAll(); | |
123 } | |
124 } | |
125 | |
126 /** | |
127 * Suspends the caller of this method until this background thread has | |
128 * emptied the dirty region queue. | |
129 */ | |
130 public void suspendCallerWhileDirty() { | |
131 bool isDirty; | |
132 do { | |
133 synchronized (fDirtyRegionQueue) { | |
134 isDirty= fDirtyRegionQueue.getSize() > 0; | |
135 if (isDirty) { | |
136 try { | |
137 fDirtyRegionQueue.wait(); | |
138 } catch (InterruptedException x) { | |
139 } | |
140 } | |
141 } | |
142 } while (isDirty); | |
143 } | |
144 | |
145 /** | |
146 * Reset the background thread as the text viewer has been changed, | |
147 */ | |
148 public void reset() { | |
149 | |
150 if (fDelay > 0) { | |
151 | |
152 synchronized (this) { | |
153 fIsDirty= true; | |
154 fReset= true; | |
155 } | |
156 | |
157 } else { | |
158 | |
159 synchronized (this) { | |
160 fIsDirty= true; | |
161 } | |
162 | |
163 synchronized (fDirtyRegionQueue) { | |
164 fDirtyRegionQueue.notifyAll(); | |
165 } | |
166 } | |
167 | |
168 reconcilerReset(); | |
169 } | |
170 | |
171 /** | |
172 * The background activity. Waits until there is something in the | |
173 * queue managing the changes that have been applied to the text viewer. | |
174 * Removes the first change from the queue and process it. | |
175 * <p> | |
176 * Calls {@link AbstractReconciler#initialProcess()} on entrance. | |
177 * </p> | |
178 */ | |
179 public void run() { | |
180 | |
181 synchronized (fDirtyRegionQueue) { | |
182 try { | |
183 fDirtyRegionQueue.wait(fDelay); | |
184 } catch (InterruptedException x) { | |
185 } | |
186 } | |
187 | |
188 if (fCanceled) | |
189 return; | |
190 | |
191 initialProcess(); | |
192 | |
193 while (!fCanceled) { | |
194 | |
195 synchronized (fDirtyRegionQueue) { | |
196 try { | |
197 fDirtyRegionQueue.wait(fDelay); | |
198 } catch (InterruptedException x) { | |
199 } | |
200 } | |
201 | |
202 if (fCanceled) | |
203 break; | |
204 | |
205 if (!isDirty()) | |
206 continue; | |
207 | |
208 synchronized (this) { | |
209 if (fReset) { | |
210 fReset= false; | |
211 continue; | |
212 } | |
213 } | |
214 | |
215 DirtyRegion r= null; | |
216 synchronized (fDirtyRegionQueue) { | |
217 r= fDirtyRegionQueue.removeNextDirtyRegion(); | |
218 } | |
219 | |
220 fIsActive= true; | |
221 | |
222 fProgressMonitor.setCanceled(false); | |
223 | |
224 process(r); | |
225 | |
226 synchronized (fDirtyRegionQueue) { | |
227 if (0 is fDirtyRegionQueue.getSize()) { | |
228 synchronized (this) { | |
229 fIsDirty= fProgressMonitor.isCanceled(); | |
230 } | |
231 fDirtyRegionQueue.notifyAll(); | |
232 } | |
233 } | |
234 | |
235 fIsActive= false; | |
236 } | |
237 } | |
238 } | |
239 | |
240 /** | |
241 * Internal document listener and text input listener. | |
242 */ | |
243 class Listener : IDocumentListener, ITextInputListener { | |
244 | |
245 /* | |
246 * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent) | |
247 */ | |
248 public void documentAboutToBeChanged(DocumentEvent e) { | |
249 } | |
250 | |
251 /* | |
252 * @see IDocumentListener#documentChanged(DocumentEvent) | |
253 */ | |
254 public void documentChanged(DocumentEvent e) { | |
255 | |
256 if (!fThread.isDirty() && fThread.isAlive()) { | |
257 if (!fIsAllowedToModifyDocument && Thread.currentThread() is fThread) | |
258 throw new UnsupportedOperationException("The reconciler thread is not allowed to modify the document"); //$NON-NLS-1$ | |
259 aboutToBeReconciled(); | |
260 } | |
261 | |
262 /* | |
263 * The second OR condition handles the case when the document | |
264 * gets changed while still inside initialProcess(). | |
265 */ | |
266 if (fThread.isActive() || fThread.isDirty() && fThread.isAlive()) | |
267 fProgressMonitor.setCanceled(true); | |
268 | |
269 if (fIsIncrementalReconciler) | |
270 createDirtyRegion(e); | |
271 | |
272 fThread.reset(); | |
273 | |
274 } | |
275 | |
276 /* | |
277 * @see ITextInputListener#inputDocumentAboutToBeChanged(IDocument, IDocument) | |
278 */ | |
279 public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { | |
280 | |
281 if (oldInput is fDocument) { | |
282 | |
283 if (fDocument !is null) | |
284 fDocument.removeDocumentListener(this); | |
285 | |
286 if (fIsIncrementalReconciler) { | |
287 synchronized (fDirtyRegionQueue) { | |
288 fDirtyRegionQueue.purgeQueue(); | |
289 } | |
290 if (fDocument !is null && fDocument.getLength() > 0 && fThread.isDirty() && fThread.isAlive()) { | |
291 DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), ""); //$NON-NLS-1$ | |
292 createDirtyRegion(e); | |
293 fThread.reset(); | |
294 fThread.suspendCallerWhileDirty(); | |
295 } | |
296 } | |
297 | |
298 fDocument= null; | |
299 } | |
300 } | |
301 | |
302 /* | |
303 * @see ITextInputListener#inputDocumentChanged(IDocument, IDocument) | |
304 */ | |
305 public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { | |
306 | |
307 fDocument= newInput; | |
308 if (fDocument is null) | |
309 return; | |
310 | |
311 | |
312 reconcilerDocumentChanged(fDocument); | |
313 | |
314 fDocument.addDocumentListener(this); | |
315 | |
316 if (!fThread.isDirty()) | |
317 aboutToBeReconciled(); | |
318 | |
319 startReconciling(); | |
320 } | |
321 } | |
322 | |
323 /** Queue to manage the changes applied to the text viewer. */ | |
324 private DirtyRegionQueue fDirtyRegionQueue; | |
325 /** The background thread. */ | |
326 private BackgroundThread fThread; | |
327 /** Internal document and text input listener. */ | |
328 private Listener fListener; | |
329 /** The background thread delay. */ | |
330 private int fDelay= 500; | |
331 /** Are there incremental reconciling strategies? */ | |
332 private bool fIsIncrementalReconciler= true; | |
333 /** The progress monitor used by this reconciler. */ | |
334 private IProgressMonitor fProgressMonitor; | |
335 /** | |
336 * Tells whether this reconciler is allowed to modify the document. | |
337 * @since 3.2 | |
338 */ | |
339 private bool fIsAllowedToModifyDocument= true; | |
340 | |
341 | |
342 /** The text viewer's document. */ | |
343 private IDocument fDocument; | |
344 /** The text viewer */ | |
345 private ITextViewer fViewer; | |
346 | |
347 | |
348 /** | |
349 * Processes a dirty region. If the dirty region is <code>null</code> the whole | |
350 * document is consider being dirty. The dirty region is partitioned by the | |
351 * document and each partition is handed over to a reconciling strategy registered | |
352 * for the partition's content type. | |
353 * | |
354 * @param dirtyRegion the dirty region to be processed | |
355 */ | |
356 abstract protected void process(DirtyRegion dirtyRegion); | |
357 | |
358 /** | |
359 * Hook called when the document whose contents should be reconciled | |
360 * has been changed, i.e., the input document of the text viewer this | |
361 * reconciler is installed on. Usually, subclasses use this hook to | |
362 * inform all their reconciling strategies about the change. | |
363 * | |
364 * @param newDocument the new reconciler document | |
365 */ | |
366 abstract protected void reconcilerDocumentChanged(IDocument newDocument); | |
367 | |
368 | |
369 /** | |
370 * Creates a new reconciler without configuring it. | |
371 */ | |
133
7d818bd32d63
Fix ctors to this with gvim regexp
Frank Benoit <benoit@tionex.de>
parents:
131
diff
changeset
|
372 protected this() { |
129 | 373 fProgressMonitor= new NullProgressMonitor(); |
374 } | |
375 | |
376 /** | |
377 * Tells the reconciler how long it should wait for further text changes before | |
378 * activating the appropriate reconciling strategies. | |
379 * | |
380 * @param delay the duration in milliseconds of a change collection period. | |
381 */ | |
382 public void setDelay(int delay) { | |
383 fDelay= delay; | |
384 } | |
385 | |
386 /** | |
387 * Tells the reconciler whether any of the available reconciling strategies | |
388 * is interested in getting detailed dirty region information or just in the | |
389 * fact that the document has been changed. In the second case, the reconciling | |
390 * can not incrementally be pursued. | |
391 * | |
392 * @param isIncremental indicates whether this reconciler will be configured with | |
393 * incremental reconciling strategies | |
394 * | |
395 * @see DirtyRegion | |
396 * @see IReconcilingStrategy | |
397 */ | |
398 public void setIsIncrementalReconciler(bool isIncremental) { | |
399 fIsIncrementalReconciler= isIncremental; | |
400 } | |
401 | |
402 /** | |
403 * Tells the reconciler whether it is allowed to change the document | |
404 * inside its reconciler thread. | |
405 * <p> | |
406 * If this is set to <code>false</code> an {@link UnsupportedOperationException} | |
407 * will be thrown when this restriction will be violated. | |
408 * </p> | |
409 * | |
410 * @param isAllowedToModify indicates whether this reconciler is allowed to modify the document | |
411 * @since 3.2 | |
412 */ | |
413 public void setIsAllowedToModifyDocument(bool isAllowedToModify) { | |
414 fIsAllowedToModifyDocument= isAllowedToModify; | |
415 } | |
416 | |
417 /** | |
418 * Sets the progress monitor of this reconciler. | |
419 * | |
420 * @param monitor the monitor to be used | |
421 */ | |
422 public void setProgressMonitor(IProgressMonitor monitor) { | |
423 Assert.isLegal(monitor !is null); | |
424 fProgressMonitor= monitor; | |
425 } | |
426 | |
427 /** | |
428 * Returns whether any of the reconciling strategies is interested in | |
429 * detailed dirty region information. | |
430 * | |
431 * @return whether this reconciler is incremental | |
432 * | |
433 * @see IReconcilingStrategy | |
434 */ | |
435 protected bool isIncrementalReconciler() { | |
436 return fIsIncrementalReconciler; | |
437 } | |
438 | |
439 /** | |
440 * Returns the input document of the text viewer this reconciler is installed on. | |
441 * | |
442 * @return the reconciler document | |
443 */ | |
444 protected IDocument getDocument() { | |
445 return fDocument; | |
446 } | |
447 | |
448 /** | |
449 * Returns the text viewer this reconciler is installed on. | |
450 * | |
451 * @return the text viewer this reconciler is installed on | |
452 */ | |
453 protected ITextViewer getTextViewer() { | |
454 return fViewer; | |
455 } | |
456 | |
457 /** | |
458 * Returns the progress monitor of this reconciler. | |
459 * | |
460 * @return the progress monitor of this reconciler | |
461 */ | |
462 protected IProgressMonitor getProgressMonitor() { | |
463 return fProgressMonitor; | |
464 } | |
465 | |
466 /* | |
467 * @see IReconciler#install(ITextViewer) | |
468 */ | |
469 public void install(ITextViewer textViewer) { | |
470 | |
471 Assert.isNotNull(textViewer); | |
472 fViewer= textViewer; | |
473 | |
474 synchronized (this) { | |
475 if (fThread !is null) | |
476 return; | |
477 fThread= new BackgroundThread(getClass().getName()); | |
478 } | |
479 | |
480 fDirtyRegionQueue= new DirtyRegionQueue(); | |
481 | |
482 fListener= new Listener(); | |
483 fViewer.addTextInputListener(fListener); | |
484 | |
485 // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=67046 | |
486 // if the reconciler gets installed on a viewer that already has a document | |
487 // (e.g. when reusing editors), we force the listener to register | |
488 // itself as document listener, because there will be no input change | |
489 // on the viewer. | |
490 // In order to do that, we simulate an input change. | |
491 IDocument document= textViewer.getDocument(); | |
492 if (document !is null) { | |
493 fListener.inputDocumentAboutToBeChanged(fDocument, document); | |
494 fListener.inputDocumentChanged(fDocument, document); | |
495 } | |
496 } | |
497 | |
498 /* | |
499 * @see IReconciler#uninstall() | |
500 */ | |
501 public void uninstall() { | |
502 if (fListener !is null) { | |
503 | |
504 fViewer.removeTextInputListener(fListener); | |
505 if (fDocument !is null) { | |
506 fListener.inputDocumentAboutToBeChanged(fDocument, null); | |
507 fListener.inputDocumentChanged(fDocument, null); | |
508 } | |
509 fListener= null; | |
510 | |
511 synchronized (this) { | |
512 // http://dev.eclipse.org/bugs/show_bug.cgi?id=19135 | |
513 BackgroundThread bt= fThread; | |
514 fThread= null; | |
515 bt.cancel(); | |
516 } | |
517 } | |
518 } | |
519 | |
520 /** | |
521 * Creates a dirty region for a document event and adds it to the queue. | |
522 * | |
523 * @param e the document event for which to create a dirty region | |
524 */ | |
525 private void createDirtyRegion(DocumentEvent e) { | |
526 synchronized (fDirtyRegionQueue) { | |
527 if (e.getLength() is 0 && e.getText() !is null) { | |
528 // Insert | |
529 fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText())); | |
530 | |
531 } else if (e.getText() is null || e.getText().length() is 0) { | |
532 // Remove | |
533 fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null)); | |
534 | |
535 } else { | |
536 // Replace (Remove + Insert) | |
537 fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null)); | |
538 fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText())); | |
539 } | |
540 } | |
541 } | |
542 | |
543 /** | |
544 * Hook for subclasses which want to perform some | |
545 * action as soon as reconciliation is needed. | |
546 * <p> | |
547 * Default implementation is to do nothing. | |
548 * </p> | |
549 * | |
550 * @since 3.0 | |
551 */ | |
552 protected void aboutToBeReconciled() { | |
553 } | |
554 | |
555 /** | |
556 * This method is called on startup of the background activity. It is called only | |
557 * once during the life time of the reconciler. Clients may reimplement this method. | |
558 */ | |
559 protected void initialProcess() { | |
560 } | |
561 | |
562 /** | |
563 * Forces the reconciler to reconcile the structure of the whole document. | |
564 * Clients may extend this method. | |
565 */ | |
566 protected void forceReconciling() { | |
567 | |
568 if (fDocument !is null) { | |
569 | |
570 if (!fThread.isDirty()&& fThread.isAlive()) | |
571 aboutToBeReconciled(); | |
572 | |
573 if (fThread.isActive()) | |
574 fProgressMonitor.setCanceled(true); | |
575 | |
576 if (fIsIncrementalReconciler) { | |
577 DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), fDocument.get()); | |
578 createDirtyRegion(e); | |
579 } | |
580 | |
581 startReconciling(); | |
582 } | |
583 } | |
584 | |
585 /** | |
586 * Starts the reconciler to reconcile the queued dirty-regions. | |
587 * Clients may extend this method. | |
588 */ | |
589 protected synchronized void startReconciling() { | |
590 if (fThread is null) | |
591 return; | |
592 | |
593 if (!fThread.isAlive()) { | |
594 try { | |
595 fThread.start(); | |
596 } catch (IllegalThreadStateException e) { | |
597 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=40549 | |
598 // This is the only instance where the thread is started; since | |
599 // we checked that it is not alive, it must be dead already due | |
600 // to a run-time exception or error. Exit. | |
601 } | |
602 } else { | |
603 fThread.reset(); | |
604 } | |
605 } | |
606 | |
607 /** | |
608 * Hook that is called after the reconciler thread has been reset. | |
609 */ | |
610 protected void reconcilerReset() { | |
611 } | |
612 | |
613 /** | |
614 * Tells whether the code is running in this reconciler's | |
615 * background thread. | |
616 * | |
617 * @return <code>true</code> if running in this reconciler's background thread | |
618 * @since 3.4 | |
619 */ | |
620 protected bool isRunningInReconcilerThread() { | |
621 return Thread.currentThread() is fThread; | |
622 } | |
623 } |