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