Mercurial > projects > dwt-addons
comparison dwtx/jface/text/source/projection/ProjectionViewer.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
comparison
equal
deleted
inserted
replaced
128:8df1d4193877 | 129:eb30df5ca28b |
---|---|
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.source.projection.ProjectionViewer; | |
14 | |
15 import dwt.dwthelper.utils; | |
16 | |
17 import java.util.ArrayList; | |
18 import java.util.Iterator; | |
19 import java.util.List; | |
20 | |
21 import dwt.DWTError; | |
22 import dwt.custom.ST; | |
23 import dwt.custom.StyledText; | |
24 import dwt.dnd.Clipboard; | |
25 import dwt.dnd.DND; | |
26 import dwt.dnd.TextTransfer; | |
27 import dwt.dnd.Transfer; | |
28 import dwt.events.VerifyEvent; | |
29 import dwt.graphics.Point; | |
30 import dwt.widgets.Composite; | |
31 import dwt.widgets.Display; | |
32 import dwtx.core.runtime.Assert; | |
33 import dwtx.jface.text.BadLocationException; | |
34 import dwtx.jface.text.DocumentEvent; | |
35 import dwtx.jface.text.FindReplaceDocumentAdapter; | |
36 import dwtx.jface.text.IDocument; | |
37 import dwtx.jface.text.IDocumentInformationMappingExtension; | |
38 import dwtx.jface.text.IDocumentListener; | |
39 import dwtx.jface.text.IRegion; | |
40 import dwtx.jface.text.ISlaveDocumentManager; | |
41 import dwtx.jface.text.ITextViewerExtension5; | |
42 import dwtx.jface.text.Position; | |
43 import dwtx.jface.text.Region; | |
44 import dwtx.jface.text.TextUtilities; | |
45 import dwtx.jface.text.projection.ProjectionDocument; | |
46 import dwtx.jface.text.projection.ProjectionDocumentEvent; | |
47 import dwtx.jface.text.projection.ProjectionDocumentManager; | |
48 import dwtx.jface.text.source.Annotation; | |
49 import dwtx.jface.text.source.AnnotationModelEvent; | |
50 import dwtx.jface.text.source.CompositeRuler; | |
51 import dwtx.jface.text.source.IAnnotationModel; | |
52 import dwtx.jface.text.source.IAnnotationModelExtension; | |
53 import dwtx.jface.text.source.IAnnotationModelListener; | |
54 import dwtx.jface.text.source.IAnnotationModelListenerExtension; | |
55 import dwtx.jface.text.source.IOverviewRuler; | |
56 import dwtx.jface.text.source.IVerticalRuler; | |
57 import dwtx.jface.text.source.IVerticalRulerColumn; | |
58 import dwtx.jface.text.source.SourceViewer; | |
59 | |
60 | |
61 /** | |
62 * A projection source viewer is a source viewer which supports multiple visible | |
63 * regions which can dynamically be changed. | |
64 * <p> | |
65 * A projection source viewer uses a <code>ProjectionDocumentManager</code> | |
66 * for the management of the visible document.</p> | |
67 * <p> | |
68 * NOTE: The <code>ProjectionViewer</code> only supports projections that cover full lines. | |
69 * </p> | |
70 * <p> | |
71 * This class should not be subclassed.</p> | |
72 * | |
73 * @since 3.0 | |
74 * @noextend This class is not intended to be subclassed by clients. | |
75 */ | |
76 public class ProjectionViewer : SourceViewer , ITextViewerExtension5 { | |
77 | |
78 private static final int BASE= INFORMATION; // see ISourceViewer.INFORMATION | |
79 | |
80 /** Operation constant for the expand operation. */ | |
81 public static final int EXPAND= BASE + 1; | |
82 /** Operation constant for the collapse operation. */ | |
83 public static final int COLLAPSE= BASE + 2; | |
84 /** Operation constant for the toggle projection operation. */ | |
85 public static final int TOGGLE= BASE + 3; | |
86 /** Operation constant for the expand all operation. */ | |
87 public static final int EXPAND_ALL= BASE + 4; | |
88 /** | |
89 * Operation constant for the collapse all operation. | |
90 * | |
91 * @since 3.2 | |
92 */ | |
93 public static final int COLLAPSE_ALL= BASE + 5; | |
94 | |
95 /** | |
96 * Internal listener to changes of the annotation model. | |
97 */ | |
98 private class AnnotationModelListener : IAnnotationModelListener, IAnnotationModelListenerExtension { | |
99 | |
100 /* | |
101 * @see dwtx.jface.text.source.IAnnotationModelListener#modelChanged(dwtx.jface.text.source.IAnnotationModel) | |
102 */ | |
103 public void modelChanged(IAnnotationModel model) { | |
104 processModelChanged(model, null); | |
105 } | |
106 | |
107 /* | |
108 * @see dwtx.jface.text.source.IAnnotationModelListenerExtension#modelChanged(dwtx.jface.text.source.AnnotationModelEvent) | |
109 */ | |
110 public void modelChanged(AnnotationModelEvent event) { | |
111 processModelChanged(event.getAnnotationModel(), event); | |
112 } | |
113 | |
114 private void processModelChanged(IAnnotationModel model, AnnotationModelEvent event) { | |
115 if (model is fProjectionAnnotationModel) { | |
116 | |
117 if (fProjectionSummary !is null) | |
118 fProjectionSummary.updateSummaries(); | |
119 processCatchupRequest(event); | |
120 | |
121 } else if (model is getAnnotationModel() && fProjectionSummary !is null) | |
122 fProjectionSummary.updateSummaries(); | |
123 } | |
124 } | |
125 | |
126 /** | |
127 * Executes the 'replaceVisibleDocument' operation when called the first time. Self-destructs afterwards. | |
128 */ | |
129 private class ReplaceVisibleDocumentExecutor : IDocumentListener { | |
130 | |
131 private IDocument fSlaveDocument; | |
132 private IDocument fExecutionTrigger; | |
133 | |
134 /** | |
135 * Creates a new executor in order to free the given slave document. | |
136 * | |
137 * @param slaveDocument the slave document to free | |
138 */ | |
139 public ReplaceVisibleDocumentExecutor(IDocument slaveDocument) { | |
140 fSlaveDocument= slaveDocument; | |
141 } | |
142 | |
143 /** | |
144 * Installs this executor on the given trigger document. | |
145 * | |
146 * @param executionTrigger the trigger document | |
147 */ | |
148 public void install(IDocument executionTrigger) { | |
149 if (executionTrigger !is null && fSlaveDocument !is null) { | |
150 fExecutionTrigger= executionTrigger; | |
151 fExecutionTrigger.addDocumentListener(this); | |
152 } | |
153 } | |
154 | |
155 /* | |
156 * @see dwtx.jface.text.IDocumentListener#documentAboutToBeChanged(dwtx.jface.text.DocumentEvent) | |
157 */ | |
158 public void documentAboutToBeChanged(DocumentEvent event) { | |
159 } | |
160 | |
161 /* | |
162 * @see dwtx.jface.text.IDocumentListener#documentChanged(dwtx.jface.text.DocumentEvent) | |
163 */ | |
164 public void documentChanged(DocumentEvent event) { | |
165 fExecutionTrigger.removeDocumentListener(this); | |
166 executeReplaceVisibleDocument(fSlaveDocument); | |
167 } | |
168 } | |
169 | |
170 /** | |
171 * A command representing a change of the projection document. This can be either | |
172 * adding a master document range, removing a master document change, or invalidating | |
173 * the viewer text presentation. | |
174 */ | |
175 private static class ProjectionCommand { | |
176 | |
177 final static int ADD= 0; | |
178 final static int REMOVE= 1; | |
179 final static int INVALIDATE_PRESENTATION= 2; | |
180 | |
181 ProjectionDocument fProjection; | |
182 int fType; | |
183 int fOffset; | |
184 int fLength; | |
185 | |
186 ProjectionCommand(ProjectionDocument projection, int type, int offset, int length) { | |
187 fProjection= projection; | |
188 fType= type; | |
189 fOffset= offset; | |
190 fLength= length; | |
191 } | |
192 | |
193 ProjectionCommand(int offset, int length) { | |
194 fType= INVALIDATE_PRESENTATION; | |
195 fOffset= offset; | |
196 fLength= length; | |
197 } | |
198 | |
199 int computeExpectedCosts() { | |
200 | |
201 switch(fType) { | |
202 case ADD: { | |
203 try { | |
204 IRegion[] gaps= fProjection.computeUnprojectedMasterRegions(fOffset, fLength); | |
205 return gaps is null ? 0 : gaps.length; | |
206 } catch (BadLocationException x) { | |
207 } | |
208 break; | |
209 } | |
210 case REMOVE: { | |
211 try { | |
212 IRegion[] fragments= fProjection.computeProjectedMasterRegions(fOffset, fLength); | |
213 return fragments is null ? 0 : fragments.length; | |
214 } catch (BadLocationException x) { | |
215 } | |
216 break; | |
217 } | |
218 } | |
219 return 0; | |
220 } | |
221 } | |
222 | |
223 /** | |
224 * The queue of projection command objects. | |
225 */ | |
226 private static class ProjectionCommandQueue { | |
227 | |
228 final static int REDRAW_COSTS= 15; | |
229 final static int INVALIDATION_COSTS= 10; | |
230 | |
231 List fList= new ArrayList(15); | |
232 int fExpectedExecutionCosts= -1; | |
233 | |
234 | |
235 void add(ProjectionCommand command) { | |
236 fList.add(command); | |
237 } | |
238 | |
239 Iterator iterator() { | |
240 return fList.iterator(); | |
241 } | |
242 | |
243 void clear() { | |
244 fList.clear(); | |
245 fExpectedExecutionCosts= -1; | |
246 } | |
247 | |
248 bool passedRedrawCostsThreshold() { | |
249 if (fExpectedExecutionCosts is -1) | |
250 computeExpectedExecutionCosts(); | |
251 return fExpectedExecutionCosts > REDRAW_COSTS; | |
252 } | |
253 | |
254 bool passedInvalidationCostsThreshold() { | |
255 if (fExpectedExecutionCosts is -1) | |
256 computeExpectedExecutionCosts(); | |
257 return fExpectedExecutionCosts > INVALIDATION_COSTS; | |
258 } | |
259 | |
260 private void computeExpectedExecutionCosts() { | |
261 int max_costs= Math.max(REDRAW_COSTS, INVALIDATION_COSTS); | |
262 fExpectedExecutionCosts= fList.size(); | |
263 if (fExpectedExecutionCosts <= max_costs) { | |
264 ProjectionCommand command; | |
265 Iterator e= fList.iterator(); | |
266 while (e.hasNext()) { | |
267 command= (ProjectionCommand) e.next(); | |
268 fExpectedExecutionCosts += command.computeExpectedCosts(); | |
269 if (fExpectedExecutionCosts > max_costs) | |
270 break; | |
271 } | |
272 } | |
273 } | |
274 } | |
275 | |
276 /** The projection annotation model used by this viewer. */ | |
277 private ProjectionAnnotationModel fProjectionAnnotationModel; | |
278 /** The annotation model listener */ | |
279 private IAnnotationModelListener fAnnotationModelListener= new AnnotationModelListener(); | |
280 /** The projection summary. */ | |
281 private ProjectionSummary fProjectionSummary; | |
282 /** Indication that an annotation world change has not yet been processed. */ | |
283 private bool fPendingAnnotationWorldChange= false; | |
284 /** Indication whether projection changes in the visible document should be considered. */ | |
285 private bool fHandleProjectionChanges= true; | |
286 /** The list of projection listeners. */ | |
287 private List fProjectionListeners; | |
288 /** Internal lock for protecting the list of pending requests */ | |
289 private Object fLock= new Object(); | |
290 /** The list of pending requests */ | |
291 private List fPendingRequests= new ArrayList(); | |
292 /** The replace-visible-document execution trigger */ | |
293 private IDocument fReplaceVisibleDocumentExecutionTrigger; | |
294 /** <code>true</code> if projection was on the last time we switched to segmented mode. */ | |
295 private bool fWasProjectionEnabled; | |
296 /** The queue of projection commands used to assess the costs of projection changes. */ | |
297 private ProjectionCommandQueue fCommandQueue; | |
298 /** | |
299 * The amount of lines deleted by the last document event issued by the | |
300 * visible document event. | |
301 * @since 3.1 | |
302 */ | |
303 private int fDeletedLines; | |
304 | |
305 | |
306 /** | |
307 * Creates a new projection source viewer. | |
308 * | |
309 * @param parent the DWT parent control | |
310 * @param ruler the vertical ruler | |
311 * @param overviewRuler the overview ruler | |
312 * @param showsAnnotationOverview <code>true</code> if the overview ruler should be shown | |
313 * @param styles the DWT style bits | |
314 */ | |
315 public ProjectionViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, bool showsAnnotationOverview, int styles) { | |
316 super(parent, ruler, overviewRuler, showsAnnotationOverview, styles); | |
317 } | |
318 | |
319 /** | |
320 * Sets the projection summary for this viewer. | |
321 * | |
322 * @param projectionSummary the projection summary. | |
323 */ | |
324 public void setProjectionSummary(ProjectionSummary projectionSummary) { | |
325 fProjectionSummary= projectionSummary; | |
326 } | |
327 | |
328 /** | |
329 * Adds the projection annotation model to the given annotation model. | |
330 * | |
331 * @param model the model to which the projection annotation model is added | |
332 */ | |
333 private void addProjectionAnnotationModel(IAnnotationModel model) { | |
334 if (model instanceof IAnnotationModelExtension) { | |
335 IAnnotationModelExtension extension= (IAnnotationModelExtension) model; | |
336 extension.addAnnotationModel(ProjectionSupport.PROJECTION, fProjectionAnnotationModel); | |
337 model.addAnnotationModelListener(fAnnotationModelListener); | |
338 } | |
339 } | |
340 | |
341 /** | |
342 * Removes the projection annotation model from the given annotation model. | |
343 * | |
344 * @param model the mode from which the projection annotation model is removed | |
345 * @return the removed projection annotation model or <code>null</code> if there was none | |
346 */ | |
347 private IAnnotationModel removeProjectionAnnotationModel(IAnnotationModel model) { | |
348 if (model instanceof IAnnotationModelExtension) { | |
349 model.removeAnnotationModelListener(fAnnotationModelListener); | |
350 IAnnotationModelExtension extension= (IAnnotationModelExtension) model; | |
351 return extension.removeAnnotationModel(ProjectionSupport.PROJECTION); | |
352 } | |
353 return null; | |
354 } | |
355 | |
356 /* | |
357 * @see dwtx.jface.text.source.SourceViewer#setDocument(dwtx.jface.text.IDocument, dwtx.jface.text.source.IAnnotationModel, int, int) | |
358 */ | |
359 public void setDocument(IDocument document, IAnnotationModel annotationModel, int modelRangeOffset, int modelRangeLength) { | |
360 bool wasProjectionEnabled= false; | |
361 | |
362 synchronized (fLock) { | |
363 fPendingRequests.clear(); | |
364 } | |
365 | |
366 if (fProjectionAnnotationModel !is null) { | |
367 wasProjectionEnabled= removeProjectionAnnotationModel(getVisualAnnotationModel()) !is null; | |
368 fProjectionAnnotationModel= null; | |
369 } | |
370 | |
371 super.setDocument(document, annotationModel, modelRangeOffset, modelRangeLength); | |
372 | |
373 if (wasProjectionEnabled && document !is null) | |
374 enableProjection(); | |
375 } | |
376 | |
377 /* | |
378 * @see dwtx.jface.text.source.SourceViewer#createVisualAnnotationModel(dwtx.jface.text.source.IAnnotationModel) | |
379 */ | |
380 protected IAnnotationModel createVisualAnnotationModel(IAnnotationModel annotationModel) { | |
381 IAnnotationModel model= super.createVisualAnnotationModel(annotationModel); | |
382 fProjectionAnnotationModel= new ProjectionAnnotationModel(); | |
383 return model; | |
384 } | |
385 | |
386 /** | |
387 * Returns the projection annotation model. | |
388 * | |
389 * @return the projection annotation model | |
390 */ | |
391 public ProjectionAnnotationModel getProjectionAnnotationModel() { | |
392 IAnnotationModel model= getVisualAnnotationModel(); | |
393 if (model instanceof IAnnotationModelExtension) { | |
394 IAnnotationModelExtension extension= (IAnnotationModelExtension) model; | |
395 return (ProjectionAnnotationModel) extension.getAnnotationModel(ProjectionSupport.PROJECTION); | |
396 } | |
397 return null; | |
398 } | |
399 | |
400 /* | |
401 * @see dwtx.jface.text.TextViewer#createSlaveDocumentManager() | |
402 */ | |
403 protected ISlaveDocumentManager createSlaveDocumentManager() { | |
404 return new ProjectionDocumentManager(); | |
405 } | |
406 | |
407 /* | |
408 * @see dwtx.jface.text.TextViewer#updateSlaveDocument(dwtx.jface.text.IDocument, int, int) | |
409 */ | |
410 protected bool updateSlaveDocument(IDocument slaveDocument, int modelRangeOffset, int modelRangeLength) throws BadLocationException { | |
411 if (slaveDocument instanceof ProjectionDocument) { | |
412 ProjectionDocument projection= (ProjectionDocument) slaveDocument; | |
413 | |
414 int offset= modelRangeOffset; | |
415 int length= modelRangeLength; | |
416 | |
417 if (!isProjectionMode()) { | |
418 // mimic original TextViewer behavior | |
419 IDocument master= projection.getMasterDocument(); | |
420 int line= master.getLineOfOffset(modelRangeOffset); | |
421 offset= master.getLineOffset(line); | |
422 length= (modelRangeOffset - offset) + modelRangeLength; | |
423 | |
424 } | |
425 | |
426 try { | |
427 fHandleProjectionChanges= false; | |
428 projection.replaceMasterDocumentRanges(offset, length); | |
429 } finally { | |
430 fHandleProjectionChanges= true; | |
431 } | |
432 return true; | |
433 } | |
434 return false; | |
435 } | |
436 | |
437 /** | |
438 * Adds a projection annotation listener to this viewer. The listener may | |
439 * not be <code>null</code>. If the listener is already registered, this method | |
440 * does not have any effect. | |
441 * | |
442 * @param listener the listener to add | |
443 */ | |
444 public void addProjectionListener(IProjectionListener listener) { | |
445 | |
446 Assert.isNotNull(listener); | |
447 | |
448 if (fProjectionListeners is null) | |
449 fProjectionListeners= new ArrayList(); | |
450 | |
451 if (!fProjectionListeners.contains(listener)) | |
452 fProjectionListeners.add(listener); | |
453 } | |
454 | |
455 /** | |
456 * Removes the given listener from this viewer. The listener may not be | |
457 * <code>null</code>. If the listener is not registered with this viewer, | |
458 * this method is without effect. | |
459 * | |
460 * @param listener the listener to remove | |
461 */ | |
462 public void removeProjectionListener(IProjectionListener listener) { | |
463 | |
464 Assert.isNotNull(listener); | |
465 | |
466 if (fProjectionListeners !is null) { | |
467 fProjectionListeners.remove(listener); | |
468 if (fProjectionListeners.size() is 0) | |
469 fProjectionListeners= null; | |
470 } | |
471 } | |
472 | |
473 /** | |
474 * Notifies all registered projection listeners | |
475 * that projection mode has been enabled. | |
476 */ | |
477 protected void fireProjectionEnabled() { | |
478 if (fProjectionListeners !is null) { | |
479 Iterator e= new ArrayList(fProjectionListeners).iterator(); | |
480 while (e.hasNext()) { | |
481 IProjectionListener l= (IProjectionListener) e.next(); | |
482 l.projectionEnabled(); | |
483 } | |
484 } | |
485 } | |
486 | |
487 /** | |
488 * Notifies all registered projection listeners | |
489 * that projection mode has been disabled. | |
490 */ | |
491 protected void fireProjectionDisabled() { | |
492 if (fProjectionListeners !is null) { | |
493 Iterator e= new ArrayList(fProjectionListeners).iterator(); | |
494 while (e.hasNext()) { | |
495 IProjectionListener l= (IProjectionListener) e.next(); | |
496 l.projectionDisabled(); | |
497 } | |
498 } | |
499 } | |
500 | |
501 /** | |
502 * Returns whether this viewer is in projection mode. | |
503 * | |
504 * @return <code>true</code> if this viewer is in projection mode, | |
505 * <code>false</code> otherwise | |
506 */ | |
507 public final bool isProjectionMode() { | |
508 return getProjectionAnnotationModel() !is null; | |
509 } | |
510 | |
511 /** | |
512 * Disables the projection mode. | |
513 */ | |
514 public final void disableProjection() { | |
515 if (isProjectionMode()) { | |
516 removeProjectionAnnotationModel(getVisualAnnotationModel()); | |
517 fProjectionAnnotationModel.removeAllAnnotations(); | |
518 fFindReplaceDocumentAdapter= null; | |
519 fireProjectionDisabled(); | |
520 } | |
521 } | |
522 | |
523 /** | |
524 * Enables the projection mode. | |
525 */ | |
526 public final void enableProjection() { | |
527 if (!isProjectionMode()) { | |
528 addProjectionAnnotationModel(getVisualAnnotationModel()); | |
529 fFindReplaceDocumentAdapter= null; | |
530 fireProjectionEnabled(); | |
531 } | |
532 } | |
533 | |
534 private void expandAll() { | |
535 int offset= 0; | |
536 IDocument doc= getDocument(); | |
537 int length= doc is null ? 0 : doc.getLength(); | |
538 if (isProjectionMode()) { | |
539 fProjectionAnnotationModel.expandAll(offset, length); | |
540 } | |
541 } | |
542 | |
543 private void expand() { | |
544 if (isProjectionMode()) { | |
545 Position found= null; | |
546 Annotation bestMatch= null; | |
547 Point selection= getSelectedRange(); | |
548 for (Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) { | |
549 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next(); | |
550 if (annotation.isCollapsed()) { | |
551 Position position= fProjectionAnnotationModel.getPosition(annotation); | |
552 // take the first most fine grained match | |
553 if (position !is null && touches(selection, position)) | |
554 if (found is null || position.includes(found.offset) && position.includes(found.offset + found.length)) { | |
555 found= position; | |
556 bestMatch= annotation; | |
557 } | |
558 } | |
559 } | |
560 | |
561 if (bestMatch !is null) { | |
562 fProjectionAnnotationModel.expand(bestMatch); | |
563 revealRange(selection.x, selection.y); | |
564 } | |
565 } | |
566 } | |
567 | |
568 private bool touches(Point selection, Position position) { | |
569 return position.overlapsWith(selection.x, selection.y) || selection.y is 0 && position.offset + position.length is selection.x + selection.y; | |
570 } | |
571 | |
572 private void collapse() { | |
573 if (isProjectionMode()) { | |
574 Position found= null; | |
575 Annotation bestMatch= null; | |
576 Point selection= getSelectedRange(); | |
577 for (Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) { | |
578 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next(); | |
579 if (!annotation.isCollapsed()) { | |
580 Position position= fProjectionAnnotationModel.getPosition(annotation); | |
581 // take the first most fine grained match | |
582 if (position !is null && touches(selection, position)) | |
583 if (found is null || found.includes(position.offset) && found.includes(position.offset + position.length)) { | |
584 found= position; | |
585 bestMatch= annotation; | |
586 } | |
587 } | |
588 } | |
589 | |
590 if (bestMatch !is null) { | |
591 fProjectionAnnotationModel.collapse(bestMatch); | |
592 revealRange(selection.x, selection.y); | |
593 } | |
594 } | |
595 } | |
596 | |
597 /* | |
598 * @since 3.2 | |
599 */ | |
600 private void collapseAll() { | |
601 int offset= 0; | |
602 IDocument doc= getDocument(); | |
603 int length= doc is null ? 0 : doc.getLength(); | |
604 if (isProjectionMode()) { | |
605 fProjectionAnnotationModel.collapseAll(offset, length); | |
606 } | |
607 } | |
608 | |
609 /** | |
610 * Adds the given master range to the given projection document. While the | |
611 * modification is processed, the viewer no longer handles projection | |
612 * changes, as it is causing them. | |
613 * | |
614 * @param projection the projection document | |
615 * @param offset the offset in the master document | |
616 * @param length the length in the master document | |
617 * @throws BadLocationException in case the specified range is invalid | |
618 * | |
619 * @see ProjectionDocument#addMasterDocumentRange(int, int) | |
620 */ | |
621 private void addMasterDocumentRange(ProjectionDocument projection, int offset, int length) throws BadLocationException { | |
622 | |
623 if (fCommandQueue !is null) { | |
624 fCommandQueue.add(new ProjectionCommand(projection, ProjectionCommand.ADD, offset, length)); | |
625 } else { | |
626 try { | |
627 fHandleProjectionChanges= false; | |
628 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=108258 | |
629 // make sure the document range is strictly line based | |
630 int end= offset + length; | |
631 offset= toLineStart(projection.getMasterDocument(), offset, false); | |
632 length= toLineStart(projection.getMasterDocument(), end, true) - offset; | |
633 projection.addMasterDocumentRange(offset, length); | |
634 } finally { | |
635 fHandleProjectionChanges= true; | |
636 } | |
637 } | |
638 } | |
639 | |
640 /** | |
641 * Removes the given master range from the given projection document. While the | |
642 * modification is processed, the viewer no longer handles projection | |
643 * changes, as it is causing them. | |
644 * | |
645 * @param projection the projection document | |
646 * @param offset the offset in the master document | |
647 * @param length the length in the master document | |
648 * @throws BadLocationException in case the specified range is invalid | |
649 * | |
650 * @see ProjectionDocument#removeMasterDocumentRange(int, int) | |
651 */ | |
652 private void removeMasterDocumentRange(ProjectionDocument projection, int offset, int length) throws BadLocationException { | |
653 if (fCommandQueue !is null) { | |
654 fCommandQueue.add(new ProjectionCommand(projection, ProjectionCommand.REMOVE, offset, length)); | |
655 } else { | |
656 try { | |
657 fHandleProjectionChanges= false; | |
658 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=108258 | |
659 // make sure the document range is strictly line based | |
660 int end= offset + length; | |
661 offset= toLineStart(projection.getMasterDocument(), offset, false); | |
662 length= toLineStart(projection.getMasterDocument(), end, true) - offset; | |
663 projection.removeMasterDocumentRange(offset, length); | |
664 } finally { | |
665 fHandleProjectionChanges= true; | |
666 } | |
667 } | |
668 } | |
669 | |
670 /** | |
671 * Returns the first line offset <= <code>offset</code>. If <code>testLastLine</code> | |
672 * is <code>true</code> and the offset is on last line then <code>offset</code> is returned. | |
673 * | |
674 * @param document the document | |
675 * @param offset the master document offset | |
676 * @param testLastLine <code>true</code> if the test for the last line should be performed | |
677 * @return the closest line offset >= <code>offset</code> | |
678 * @throws BadLocationException if the offset is invalid | |
679 * @since 3.2 | |
680 */ | |
681 private int toLineStart(IDocument document, int offset, bool testLastLine) throws BadLocationException { | |
682 if (document is null) | |
683 return offset; | |
684 | |
685 if (testLastLine && offset >= document.getLineInformationOfOffset(document.getLength() - 1).getOffset()) | |
686 return offset; | |
687 | |
688 return document.getLineInformationOfOffset(offset).getOffset(); | |
689 } | |
690 | |
691 /* | |
692 * @see dwtx.jface.text.TextViewer#setVisibleRegion(int, int) | |
693 */ | |
694 public void setVisibleRegion(int start, int length) { | |
695 fWasProjectionEnabled= isProjectionMode(); | |
696 disableProjection(); | |
697 super.setVisibleRegion(start, length); | |
698 } | |
699 | |
700 /* | |
701 * @see dwtx.jface.text.TextViewer#setVisibleDocument(dwtx.jface.text.IDocument) | |
702 */ | |
703 protected void setVisibleDocument(IDocument document) { | |
704 if (!isProjectionMode()) { | |
705 super.setVisibleDocument(document); | |
706 return; | |
707 } | |
708 | |
709 // In projection mode we don't want to throw away the find/replace document adapter | |
710 FindReplaceDocumentAdapter adapter= fFindReplaceDocumentAdapter; | |
711 super.setVisibleDocument(document); | |
712 fFindReplaceDocumentAdapter= adapter; | |
713 } | |
714 | |
715 /* | |
716 * @see dwtx.jface.text.TextViewer#resetVisibleRegion() | |
717 */ | |
718 public void resetVisibleRegion() { | |
719 super.resetVisibleRegion(); | |
720 if (fWasProjectionEnabled) | |
721 enableProjection(); | |
722 } | |
723 | |
724 /* | |
725 * @see dwtx.jface.text.ITextViewer#getVisibleRegion() | |
726 */ | |
727 public IRegion getVisibleRegion() { | |
728 disableProjection(); | |
729 IRegion visibleRegion= getModelCoverage(); | |
730 if (visibleRegion is null) | |
731 visibleRegion= new Region(0, 0); | |
732 | |
733 return visibleRegion; | |
734 } | |
735 | |
736 /* | |
737 * @see dwtx.jface.text.ITextViewer#overlapsWithVisibleRegion(int,int) | |
738 */ | |
739 public bool overlapsWithVisibleRegion(int offset, int length) { | |
740 disableProjection(); | |
741 IRegion coverage= getModelCoverage(); | |
742 if (coverage is null) | |
743 return false; | |
744 | |
745 bool appending= (offset is coverage.getOffset() + coverage.getLength()) && length is 0; | |
746 return appending || TextUtilities.overlaps(coverage, new Region(offset, length)); | |
747 } | |
748 | |
749 /** | |
750 * Replace the visible document with the given document. Maintains the | |
751 * scroll offset and the selection. | |
752 * | |
753 * @param slave the visible document | |
754 */ | |
755 private void replaceVisibleDocument(IDocument slave) { | |
756 if (fReplaceVisibleDocumentExecutionTrigger !is null) { | |
757 ReplaceVisibleDocumentExecutor executor= new ReplaceVisibleDocumentExecutor(slave); | |
758 executor.install(fReplaceVisibleDocumentExecutionTrigger); | |
759 } else | |
760 executeReplaceVisibleDocument(slave); | |
761 } | |
762 | |
763 | |
764 private void executeReplaceVisibleDocument(IDocument visibleDocument) { | |
765 StyledText textWidget= getTextWidget(); | |
766 try { | |
767 if (textWidget !is null && !textWidget.isDisposed()) | |
768 textWidget.setRedraw(false); | |
769 | |
770 int topIndex= getTopIndex(); | |
771 Point selection= getSelectedRange(); | |
772 setVisibleDocument(visibleDocument); | |
773 Point currentSelection= getSelectedRange(); | |
774 if (currentSelection.x !is selection.x || currentSelection.y !is selection.y) | |
775 setSelectedRange(selection.x, selection.y); | |
776 setTopIndex(topIndex); | |
777 | |
778 } finally { | |
779 if (textWidget !is null && !textWidget.isDisposed()) | |
780 textWidget.setRedraw(true); | |
781 } | |
782 } | |
783 | |
784 /** | |
785 * Hides the given range by collapsing it. If requested, a redraw request is issued. | |
786 * | |
787 * @param offset the offset of the range to hide | |
788 * @param length the length of the range to hide | |
789 * @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code> otherwise | |
790 * @throws BadLocationException in case the range is invalid | |
791 */ | |
792 private void collapse(int offset, int length, bool fireRedraw) throws BadLocationException { | |
793 ProjectionDocument projection= null; | |
794 | |
795 IDocument visibleDocument= getVisibleDocument(); | |
796 if (visibleDocument instanceof ProjectionDocument) | |
797 projection= (ProjectionDocument) visibleDocument; | |
798 else { | |
799 IDocument master= getDocument(); | |
800 IDocument slave= createSlaveDocument(getDocument()); | |
801 if (slave instanceof ProjectionDocument) { | |
802 projection= (ProjectionDocument) slave; | |
803 addMasterDocumentRange(projection, 0, master.getLength()); | |
804 replaceVisibleDocument(projection); | |
805 } | |
806 } | |
807 | |
808 if (projection !is null) | |
809 removeMasterDocumentRange(projection, offset, length); | |
810 | |
811 if (projection !is null && fireRedraw) { | |
812 // repaint line above to get the folding box | |
813 IDocument document= getDocument(); | |
814 int line= document.getLineOfOffset(offset); | |
815 if (line > 0) { | |
816 IRegion info= document.getLineInformation(line - 1); | |
817 internalInvalidateTextPresentation(info.getOffset(), info.getLength()); | |
818 } | |
819 } | |
820 } | |
821 | |
822 /** | |
823 * Makes the given range visible again while not changing the folding state of any contained | |
824 * ranges. If requested, a redraw request is issued. | |
825 * | |
826 * @param offset the offset of the range to be expanded | |
827 * @param length the length of the range to be expanded | |
828 * @param fireRedraw <code>true</code> if a redraw request should be issued, | |
829 * <code>false</code> otherwise | |
830 * @throws BadLocationException in case the range is invalid | |
831 */ | |
832 private void expand(int offset, int length, bool fireRedraw) throws BadLocationException { | |
833 IDocument slave= getVisibleDocument(); | |
834 if (slave instanceof ProjectionDocument) { | |
835 ProjectionDocument projection= (ProjectionDocument) slave; | |
836 | |
837 // expand | |
838 addMasterDocumentRange(projection, offset, length); | |
839 | |
840 // collapse contained regions | |
841 ProjectionAnnotation[] collapsed= computeCollapsedNestedAnnotations(offset, length); | |
842 if (collapsed !is null) { | |
843 for (int i= 0; i < collapsed.length; i++) { | |
844 IRegion[] regions= computeCollapsedRegions(fProjectionAnnotationModel.getPosition(collapsed[i])); | |
845 if (regions !is null) | |
846 for (int j= 0; j < regions.length; j++) | |
847 removeMasterDocumentRange(projection, regions[j].getOffset(), regions[j].getLength()); | |
848 } | |
849 } | |
850 | |
851 // redraw if requested | |
852 if (fireRedraw) | |
853 internalInvalidateTextPresentation(offset, length); | |
854 } | |
855 } | |
856 | |
857 /** | |
858 * Processes the request for catch up with the annotation model in the UI thread. If the current | |
859 * thread is not the UI thread or there are pending catch up requests, a new request is posted. | |
860 * | |
861 * @param event the annotation model event | |
862 */ | |
863 protected final void processCatchupRequest(AnnotationModelEvent event) { | |
864 if (Display.getCurrent() !is null) { | |
865 bool run= false; | |
866 synchronized (fLock) { | |
867 run= fPendingRequests.isEmpty(); | |
868 } | |
869 if (run) { | |
870 | |
871 try { | |
872 catchupWithProjectionAnnotationModel(event); | |
873 } catch (BadLocationException x) { | |
874 throw new IllegalArgumentException(); | |
875 } | |
876 | |
877 } else | |
878 postCatchupRequest(event); | |
879 } else { | |
880 postCatchupRequest(event); | |
881 } | |
882 } | |
883 | |
884 /** | |
885 * Posts the request for catch up with the annotation model into the UI thread. | |
886 * | |
887 * @param event the annotation model event | |
888 */ | |
889 protected final void postCatchupRequest(final AnnotationModelEvent event) { | |
890 synchronized (fLock) { | |
891 fPendingRequests.add(event); | |
892 if (fPendingRequests.size() is 1) { | |
893 StyledText widget= getTextWidget(); | |
894 if (widget !is null) { | |
895 Display display= widget.getDisplay(); | |
896 if (display !is null) { | |
897 display.asyncExec(new Runnable() { | |
898 public void run() { | |
899 try { | |
900 while (true) { | |
901 AnnotationModelEvent ame= null; | |
902 synchronized (fLock) { | |
903 if (fPendingRequests.size() is 0) | |
904 return; | |
905 ame= (AnnotationModelEvent) fPendingRequests.remove(0); | |
906 } | |
907 catchupWithProjectionAnnotationModel(ame); | |
908 } | |
909 } catch (BadLocationException x) { | |
910 try { | |
911 catchupWithProjectionAnnotationModel(null); | |
912 } catch (BadLocationException x1) { | |
913 throw new IllegalArgumentException(); | |
914 } finally { | |
915 synchronized (fLock) { | |
916 fPendingRequests.clear(); | |
917 } | |
918 } | |
919 } | |
920 } | |
921 }); | |
922 } | |
923 } | |
924 } | |
925 } | |
926 } | |
927 | |
928 /** | |
929 * Tests whether the visible document's master document | |
930 * is identical to this viewer's document. | |
931 * | |
932 * @return <code>true</code> if the visible document's master is | |
933 * identical to this viewer's document | |
934 * @since 3.1 | |
935 */ | |
936 private bool isVisibleMasterDocumentSameAsDocument() { | |
937 IDocument visibleDocument= getVisibleDocument(); | |
938 return (visibleDocument instanceof ProjectionDocument) && ((ProjectionDocument)visibleDocument).getMasterDocument() is getDocument(); | |
939 } | |
940 | |
941 /** | |
942 * Adapts the slave visual document of this viewer to the changes described | |
943 * in the annotation model event. When the event is <code>null</code>, | |
944 * this is identical to a world change event. | |
945 * | |
946 * @param event the annotation model event or <code>null</code> | |
947 * @exception BadLocationException in case the annotation model event is no longer in synchronization with the document | |
948 */ | |
949 private void catchupWithProjectionAnnotationModel(AnnotationModelEvent event) throws BadLocationException { | |
950 | |
951 if (event is null || !isVisibleMasterDocumentSameAsDocument()) { | |
952 | |
953 fPendingAnnotationWorldChange= false; | |
954 reinitializeProjection(); | |
955 | |
956 } else if (event.isWorldChange()) { | |
957 | |
958 if (event.isValid()) { | |
959 fPendingAnnotationWorldChange= false; | |
960 reinitializeProjection(); | |
961 } else | |
962 fPendingAnnotationWorldChange= true; | |
963 | |
964 } else if (fPendingAnnotationWorldChange) { | |
965 if (event.isValid()) { | |
966 fPendingAnnotationWorldChange= false; | |
967 reinitializeProjection(); | |
968 } | |
969 } else { | |
970 | |
971 Annotation[] addedAnnotations= event.getAddedAnnotations(); | |
972 Annotation[] changedAnnotation= event.getChangedAnnotations(); | |
973 Annotation[] removedAnnotations= event.getRemovedAnnotations(); | |
974 | |
975 fCommandQueue= new ProjectionCommandQueue(); | |
976 | |
977 bool isRedrawing= redraws(); | |
978 int topIndex= isRedrawing ? getTopIndex() : -1; | |
979 | |
980 processDeletions(event, removedAnnotations, true); | |
981 List coverage= new ArrayList(); | |
982 processChanges(addedAnnotations, true, coverage); | |
983 processChanges(changedAnnotation, true, coverage); | |
984 | |
985 ProjectionCommandQueue commandQueue= fCommandQueue; | |
986 fCommandQueue= null; | |
987 | |
988 if (commandQueue.passedRedrawCostsThreshold()) { | |
989 setRedraw(false); | |
990 try { | |
991 executeProjectionCommands(commandQueue, false); | |
992 } catch (IllegalArgumentException x) { | |
993 reinitializeProjection(); | |
994 } finally { | |
995 setRedraw(true, topIndex); | |
996 } | |
997 } else { | |
998 try { | |
999 bool fireRedraw= !commandQueue.passedInvalidationCostsThreshold(); | |
1000 executeProjectionCommands(commandQueue, fireRedraw); | |
1001 if (!fireRedraw) | |
1002 invalidateTextPresentation(); | |
1003 } catch (IllegalArgumentException x) { | |
1004 reinitializeProjection(); | |
1005 } | |
1006 } | |
1007 } | |
1008 } | |
1009 | |
1010 private void executeProjectionCommands(ProjectionCommandQueue commandQueue, bool fireRedraw) throws BadLocationException { | |
1011 | |
1012 ProjectionCommand command; | |
1013 Iterator e= commandQueue.iterator(); | |
1014 while (e.hasNext()) { | |
1015 command= (ProjectionCommand) e.next(); | |
1016 switch (command.fType) { | |
1017 case ProjectionCommand.ADD: | |
1018 addMasterDocumentRange(command.fProjection, command.fOffset, command.fLength); | |
1019 break; | |
1020 case ProjectionCommand.REMOVE: | |
1021 removeMasterDocumentRange(command.fProjection, command.fOffset, command.fLength); | |
1022 break; | |
1023 case ProjectionCommand.INVALIDATE_PRESENTATION: | |
1024 if (fireRedraw) | |
1025 invalidateTextPresentation(command.fOffset, command.fLength); | |
1026 break; | |
1027 } | |
1028 } | |
1029 | |
1030 commandQueue.clear(); | |
1031 } | |
1032 | |
1033 private bool covers(int offset, int length, Position position) { | |
1034 if (!(position.offset is offset && position.length is length) && !position.isDeleted()) | |
1035 return offset <= position.getOffset() && position.getOffset() + position.getLength() <= offset + length; | |
1036 return false; | |
1037 } | |
1038 | |
1039 private ProjectionAnnotation[] computeCollapsedNestedAnnotations(int offset, int length) { | |
1040 List annotations= new ArrayList(5); | |
1041 Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); | |
1042 while (e.hasNext()) { | |
1043 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next(); | |
1044 if (annotation.isCollapsed()) { | |
1045 Position position= fProjectionAnnotationModel.getPosition(annotation); | |
1046 if (position is null) { | |
1047 // annotation might already be deleted, we will be informed later on about this deletion | |
1048 continue; | |
1049 } | |
1050 if (covers(offset, length, position)) | |
1051 annotations.add(annotation); | |
1052 } | |
1053 } | |
1054 | |
1055 if (annotations.size() > 0) { | |
1056 ProjectionAnnotation[] result= new ProjectionAnnotation[annotations.size()]; | |
1057 annotations.toArray(result); | |
1058 return result; | |
1059 } | |
1060 | |
1061 return null; | |
1062 } | |
1063 | |
1064 private void internalInvalidateTextPresentation(int offset, int length) { | |
1065 if (fCommandQueue !is null) { | |
1066 fCommandQueue.add(new ProjectionCommand(offset, length)); | |
1067 } else { | |
1068 invalidateTextPresentation(offset, length); | |
1069 } | |
1070 } | |
1071 | |
1072 /* | |
1073 * We pass the removed annotation into this method for performance reasons only. Otherwise, they could be fetch from the event. | |
1074 */ | |
1075 private void processDeletions(AnnotationModelEvent event, Annotation[] removedAnnotations, bool fireRedraw) throws BadLocationException { | |
1076 for (int i= 0; i < removedAnnotations.length; i++) { | |
1077 ProjectionAnnotation annotation= (ProjectionAnnotation) removedAnnotations[i]; | |
1078 if (annotation.isCollapsed()) { | |
1079 Position expanded= event.getPositionOfRemovedAnnotation(annotation); | |
1080 expand(expanded.getOffset(), expanded.getLength(), fireRedraw); | |
1081 } | |
1082 } | |
1083 } | |
1084 | |
1085 /** | |
1086 * Computes the region that must be collapsed when the given position is the | |
1087 * position of an expanded projection annotation. | |
1088 * | |
1089 * @param position the position | |
1090 * @return the range that must be collapsed | |
1091 */ | |
1092 public IRegion computeCollapsedRegion(Position position) { | |
1093 try { | |
1094 IDocument document= getDocument(); | |
1095 if (document is null) | |
1096 return null; | |
1097 | |
1098 int line= document.getLineOfOffset(position.getOffset()); | |
1099 int offset= document.getLineOffset(line + 1); | |
1100 | |
1101 int length= position.getLength() - (offset - position.getOffset()); | |
1102 if (length > 0) | |
1103 return new Region(offset, length); | |
1104 } catch (BadLocationException x) { | |
1105 } | |
1106 | |
1107 return null; | |
1108 } | |
1109 | |
1110 /** | |
1111 * Computes the regions that must be collapsed when the given position is | |
1112 * the position of an expanded projection annotation. | |
1113 * | |
1114 * @param position the position | |
1115 * @return the ranges that must be collapsed, or <code>null</code> if | |
1116 * there are none | |
1117 * @since 3.1 | |
1118 */ | |
1119 IRegion[] computeCollapsedRegions(Position position) { | |
1120 try { | |
1121 IDocument document= getDocument(); | |
1122 if (document is null) | |
1123 return null; | |
1124 | |
1125 if (position instanceof IProjectionPosition) { | |
1126 IProjectionPosition projPosition= (IProjectionPosition) position; | |
1127 return projPosition.computeProjectionRegions(document); | |
1128 } | |
1129 | |
1130 int line= document.getLineOfOffset(position.getOffset()); | |
1131 int offset= document.getLineOffset(line + 1); | |
1132 | |
1133 int length= position.getLength() - (offset - position.getOffset()); | |
1134 if (length > 0) | |
1135 return new IRegion[] {new Region(offset, length)}; | |
1136 | |
1137 return null; | |
1138 } catch (BadLocationException x) { | |
1139 return null; | |
1140 } | |
1141 } | |
1142 | |
1143 /** | |
1144 * Computes the collapsed region anchor for the given position. Assuming | |
1145 * that the position is the position of an expanded projection annotation, | |
1146 * the anchor is the region that is still visible after the projection | |
1147 * annotation has been collapsed. | |
1148 * | |
1149 * @param position the position | |
1150 * @return the collapsed region anchor | |
1151 */ | |
1152 public Position computeCollapsedRegionAnchor(Position position) { | |
1153 try { | |
1154 IDocument document= getDocument(); | |
1155 if (document is null) | |
1156 return null; | |
1157 | |
1158 int captionOffset= position.getOffset(); | |
1159 if (position instanceof IProjectionPosition) | |
1160 captionOffset+= ((IProjectionPosition) position).computeCaptionOffset(document); | |
1161 | |
1162 IRegion lineInfo= document.getLineInformationOfOffset(captionOffset); | |
1163 return new Position(lineInfo.getOffset() + lineInfo.getLength(), 0); | |
1164 } catch (BadLocationException x) { | |
1165 } | |
1166 return null; | |
1167 } | |
1168 | |
1169 private void processChanges(Annotation[] annotations, bool fireRedraw, List coverage) throws BadLocationException { | |
1170 for (int i= 0; i < annotations.length; i++) { | |
1171 ProjectionAnnotation annotation= (ProjectionAnnotation) annotations[i]; | |
1172 Position position= fProjectionAnnotationModel.getPosition(annotation); | |
1173 | |
1174 if (position is null) | |
1175 continue; | |
1176 | |
1177 if (!covers(coverage, position)) { | |
1178 if (annotation.isCollapsed()) { | |
1179 coverage.add(position); | |
1180 IRegion[] regions= computeCollapsedRegions(position); | |
1181 if (regions !is null) | |
1182 for (int j= 0; j < regions.length; j++) | |
1183 collapse(regions[j].getOffset(), regions[j].getLength(), fireRedraw); | |
1184 } else { | |
1185 expand(position.getOffset(), position.getLength(), fireRedraw); | |
1186 } | |
1187 } | |
1188 } | |
1189 } | |
1190 | |
1191 private bool covers(List coverage, Position position) { | |
1192 Iterator e= coverage.iterator(); | |
1193 while (e.hasNext()) { | |
1194 Position p= (Position) e.next(); | |
1195 if (p.getOffset() <= position.getOffset() && position.getOffset() + position.getLength() <= p.getOffset() + p.getLength()) | |
1196 return true; | |
1197 } | |
1198 return false; | |
1199 } | |
1200 | |
1201 /** | |
1202 * Forces this viewer to throw away any old state and to initialize its content | |
1203 * from its projection annotation model. | |
1204 * | |
1205 * @throws BadLocationException in case something goes wrong during initialization | |
1206 */ | |
1207 public final void reinitializeProjection() throws BadLocationException { | |
1208 | |
1209 ProjectionDocument projection= null; | |
1210 | |
1211 ISlaveDocumentManager manager= getSlaveDocumentManager(); | |
1212 if (manager !is null) { | |
1213 IDocument master= getDocument(); | |
1214 if (master !is null) { | |
1215 IDocument slave= manager.createSlaveDocument(master); | |
1216 if (slave instanceof ProjectionDocument) { | |
1217 projection= (ProjectionDocument) slave; | |
1218 addMasterDocumentRange(projection, 0, master.getLength()); | |
1219 } | |
1220 } | |
1221 } | |
1222 | |
1223 if (projection !is null) { | |
1224 Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); | |
1225 while (e.hasNext()) { | |
1226 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next(); | |
1227 if (annotation.isCollapsed()) { | |
1228 Position position= fProjectionAnnotationModel.getPosition(annotation); | |
1229 if (position !is null) { | |
1230 IRegion[] regions= computeCollapsedRegions(position); | |
1231 if (regions !is null) | |
1232 for (int i= 0; i < regions.length; i++) | |
1233 removeMasterDocumentRange(projection, regions[i].getOffset(), regions[i].getLength()); | |
1234 } | |
1235 } | |
1236 } | |
1237 | |
1238 } | |
1239 | |
1240 replaceVisibleDocument(projection); | |
1241 } | |
1242 | |
1243 /* | |
1244 * @see dwtx.jface.text.TextViewer#handleVerifyEvent(dwt.events.VerifyEvent) | |
1245 */ | |
1246 protected void handleVerifyEvent(VerifyEvent e) { | |
1247 IRegion modelRange= event2ModelRange(e); | |
1248 if (exposeModelRange(modelRange)) | |
1249 e.doit= false; | |
1250 else | |
1251 super.handleVerifyEvent(e); | |
1252 } | |
1253 | |
1254 /** | |
1255 * Adds the give column as last column to this viewer's vertical ruler. | |
1256 * | |
1257 * @param column the column to be added | |
1258 */ | |
1259 public void addVerticalRulerColumn(IVerticalRulerColumn column) { | |
1260 IVerticalRuler ruler= getVerticalRuler(); | |
1261 if (ruler instanceof CompositeRuler) { | |
1262 CompositeRuler compositeRuler= (CompositeRuler) ruler; | |
1263 compositeRuler.addDecorator(99, column); | |
1264 } | |
1265 } | |
1266 | |
1267 /** | |
1268 * Removes the give column from this viewer's vertical ruler. | |
1269 * | |
1270 * @param column the column to be removed | |
1271 */ | |
1272 public void removeVerticalRulerColumn(IVerticalRulerColumn column) { | |
1273 IVerticalRuler ruler= getVerticalRuler(); | |
1274 if (ruler instanceof CompositeRuler) { | |
1275 CompositeRuler compositeRuler= (CompositeRuler) ruler; | |
1276 compositeRuler.removeDecorator(column); | |
1277 } | |
1278 } | |
1279 | |
1280 /* | |
1281 * @see dwtx.jface.text.ITextViewerExtension5#exposeModelRange(dwtx.jface.text.IRegion) | |
1282 */ | |
1283 public bool exposeModelRange(IRegion modelRange) { | |
1284 if (isProjectionMode()) | |
1285 return fProjectionAnnotationModel.expandAll(modelRange.getOffset(), modelRange.getLength()); | |
1286 | |
1287 if (!overlapsWithVisibleRegion(modelRange.getOffset(), modelRange.getLength())) { | |
1288 resetVisibleRegion(); | |
1289 return true; | |
1290 } | |
1291 | |
1292 return false; | |
1293 } | |
1294 | |
1295 /* | |
1296 * @see dwtx.jface.text.source.SourceViewer#setRangeIndication(int, int, bool) | |
1297 */ | |
1298 public void setRangeIndication(int offset, int length, bool moveCursor) { | |
1299 IRegion rangeIndication= getRangeIndication(); | |
1300 if (moveCursor && fProjectionAnnotationModel !is null && (rangeIndication is null || offset !is rangeIndication.getOffset() || length !is rangeIndication.getLength())) { | |
1301 List expand= new ArrayList(2); | |
1302 // expand the immediate effected collapsed regions | |
1303 Iterator iterator= fProjectionAnnotationModel.getAnnotationIterator(); | |
1304 while (iterator.hasNext()) { | |
1305 ProjectionAnnotation annotation= (ProjectionAnnotation)iterator.next(); | |
1306 if (annotation.isCollapsed() && willAutoExpand(fProjectionAnnotationModel.getPosition(annotation), offset, length)) | |
1307 expand.add(annotation); | |
1308 } | |
1309 | |
1310 if (!expand.isEmpty()) { | |
1311 Iterator e= expand.iterator(); | |
1312 while (e.hasNext()) | |
1313 fProjectionAnnotationModel.expand((Annotation)e.next()); | |
1314 } | |
1315 } | |
1316 super.setRangeIndication(offset, length, moveCursor); | |
1317 } | |
1318 | |
1319 private bool willAutoExpand(Position position, int offset, int length) { | |
1320 if (position is null || position.isDeleted()) | |
1321 return false; | |
1322 // right or left boundary | |
1323 if (position.getOffset() is offset || position.getOffset() + position.getLength() is offset + length) | |
1324 return true; | |
1325 // completely embedded in given position | |
1326 if (position.getOffset() < offset && offset + length < position.getOffset() + position.getLength()) | |
1327 return true; | |
1328 return false; | |
1329 } | |
1330 | |
1331 /* | |
1332 * @see dwtx.jface.text.source.SourceViewer#handleDispose() | |
1333 * @since 3.0 | |
1334 */ | |
1335 protected void handleDispose() { | |
1336 fWasProjectionEnabled= false; | |
1337 super.handleDispose(); | |
1338 } | |
1339 | |
1340 /* | |
1341 * @see dwtx.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(dwtx.jface.text.DocumentEvent) | |
1342 */ | |
1343 protected void handleVisibleDocumentChanged(DocumentEvent event) { | |
1344 if (fHandleProjectionChanges && event instanceof ProjectionDocumentEvent && isProjectionMode()) { | |
1345 ProjectionDocumentEvent e= (ProjectionDocumentEvent) event; | |
1346 | |
1347 DocumentEvent master= e.getMasterEvent(); | |
1348 if (master !is null) | |
1349 fReplaceVisibleDocumentExecutionTrigger= master.getDocument(); | |
1350 | |
1351 try { | |
1352 | |
1353 int replaceLength= e.getText() is null ? 0 : e.getText().length(); | |
1354 if (ProjectionDocumentEvent.PROJECTION_CHANGE is e.getChangeType()) { | |
1355 if (e.getLength() is 0 && replaceLength !is 0) | |
1356 fProjectionAnnotationModel.expandAll(e.getMasterOffset(), e.getMasterLength()); | |
1357 } else if (master !is null && (replaceLength > 0 || fDeletedLines > 1)) { | |
1358 try { | |
1359 int numberOfLines= e.getDocument().getNumberOfLines(e.getOffset(), replaceLength); | |
1360 if (numberOfLines > 1 || fDeletedLines > 1) | |
1361 fProjectionAnnotationModel.expandAll(master.getOffset(), master.getLength()); | |
1362 } catch (BadLocationException x) { | |
1363 } | |
1364 } | |
1365 | |
1366 } finally { | |
1367 fReplaceVisibleDocumentExecutionTrigger= null; | |
1368 } | |
1369 | |
1370 } | |
1371 } | |
1372 | |
1373 /* | |
1374 * @see dwtx.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(dwtx.jface.text.DocumentEvent) | |
1375 * @since 3.1 | |
1376 */ | |
1377 protected void handleVisibleDocumentAboutToBeChanged(DocumentEvent event) { | |
1378 if (fHandleProjectionChanges && event instanceof ProjectionDocumentEvent && isProjectionMode()) { | |
1379 int deletedLines; | |
1380 try { | |
1381 deletedLines= event.getDocument().getNumberOfLines(event.getOffset(), event.getLength()); | |
1382 } catch (BadLocationException e1) { | |
1383 deletedLines= 0; | |
1384 } | |
1385 fDeletedLines= deletedLines; | |
1386 } | |
1387 } | |
1388 | |
1389 /* | |
1390 * @see dwtx.jface.text.ITextViewerExtension5#getCoveredModelRanges(dwtx.jface.text.IRegion) | |
1391 */ | |
1392 public IRegion[] getCoveredModelRanges(IRegion modelRange) { | |
1393 if (fInformationMapping is null) | |
1394 return new IRegion[] { new Region(modelRange.getOffset(), modelRange.getLength()) }; | |
1395 | |
1396 if (fInformationMapping instanceof IDocumentInformationMappingExtension) { | |
1397 IDocumentInformationMappingExtension extension= (IDocumentInformationMappingExtension) fInformationMapping; | |
1398 try { | |
1399 return extension.getExactCoverage(modelRange); | |
1400 } catch (BadLocationException x) { | |
1401 } | |
1402 } | |
1403 | |
1404 return null; | |
1405 } | |
1406 | |
1407 /* | |
1408 * @see dwtx.jface.text.ITextOperationTarget#doOperation(int) | |
1409 */ | |
1410 public void doOperation(int operation) { | |
1411 switch (operation) { | |
1412 case TOGGLE: | |
1413 if (canDoOperation(TOGGLE)) { | |
1414 if (!isProjectionMode()) { | |
1415 enableProjection(); | |
1416 } else { | |
1417 expandAll(); | |
1418 disableProjection(); | |
1419 } | |
1420 return; | |
1421 } | |
1422 } | |
1423 | |
1424 if (!isProjectionMode()) { | |
1425 super.doOperation(operation); | |
1426 return; | |
1427 } | |
1428 | |
1429 StyledText textWidget= getTextWidget(); | |
1430 if (textWidget is null) | |
1431 return; | |
1432 | |
1433 Point selection= null; | |
1434 switch (operation) { | |
1435 | |
1436 case CUT: | |
1437 | |
1438 if (redraws()) { | |
1439 selection= getSelectedRange(); | |
1440 if (exposeModelRange(new Region(selection.x, selection.y))) | |
1441 return; | |
1442 | |
1443 if (selection.y is 0) | |
1444 copyMarkedRegion(true); | |
1445 else | |
1446 copyToClipboard(selection.x, selection.y, true, textWidget); | |
1447 | |
1448 selection= textWidget.getSelectionRange(); | |
1449 fireSelectionChanged(selection.x, selection.y); | |
1450 } | |
1451 break; | |
1452 | |
1453 case COPY: | |
1454 | |
1455 if (redraws()) { | |
1456 selection= getSelectedRange(); | |
1457 if (selection.y is 0) | |
1458 copyMarkedRegion(false); | |
1459 else | |
1460 copyToClipboard(selection.x, selection.y, false, textWidget); | |
1461 } | |
1462 break; | |
1463 | |
1464 case DELETE: | |
1465 | |
1466 if (redraws()) { | |
1467 try { | |
1468 selection= getSelectedRange(); | |
1469 Point widgetSelection= textWidget.getSelectionRange(); | |
1470 if (selection.y is 0 || selection.y is widgetSelection.y) | |
1471 getTextWidget().invokeAction(ST.DELETE_NEXT); | |
1472 else | |
1473 deleteTextRange(selection.x, selection.y, textWidget); | |
1474 | |
1475 selection= textWidget.getSelectionRange(); | |
1476 fireSelectionChanged(selection.x, selection.y); | |
1477 | |
1478 } catch (BadLocationException x) { | |
1479 // ignore | |
1480 } | |
1481 } | |
1482 break; | |
1483 | |
1484 | |
1485 case EXPAND_ALL: | |
1486 if (redraws()) | |
1487 expandAll(); | |
1488 break; | |
1489 | |
1490 case EXPAND: | |
1491 if (redraws()) { | |
1492 expand(); | |
1493 } | |
1494 break; | |
1495 | |
1496 case COLLAPSE_ALL: | |
1497 if (redraws()) | |
1498 collapseAll(); | |
1499 break; | |
1500 | |
1501 case COLLAPSE: | |
1502 if (redraws()) { | |
1503 collapse(); | |
1504 } | |
1505 break; | |
1506 | |
1507 default: | |
1508 super.doOperation(operation); | |
1509 } | |
1510 } | |
1511 | |
1512 /* | |
1513 * @see dwtx.jface.text.source.SourceViewer#canDoOperation(int) | |
1514 */ | |
1515 public bool canDoOperation(int operation) { | |
1516 | |
1517 switch (operation) { | |
1518 case COLLAPSE: | |
1519 case COLLAPSE_ALL: | |
1520 case EXPAND: | |
1521 case EXPAND_ALL: | |
1522 return isProjectionMode(); | |
1523 case TOGGLE: | |
1524 return isProjectionMode() || !isSegmented(); | |
1525 } | |
1526 | |
1527 return super.canDoOperation(operation); | |
1528 } | |
1529 | |
1530 private bool isSegmented() { | |
1531 IDocument document= getDocument(); | |
1532 int length= document is null ? 0 : document.getLength(); | |
1533 IRegion visible= getModelCoverage(); | |
1534 bool isSegmented= visible !is null && !visible.equals(new Region(0, length)); | |
1535 return isSegmented; | |
1536 } | |
1537 | |
1538 private IRegion getMarkedRegion() { | |
1539 if (getTextWidget() is null) | |
1540 return null; | |
1541 | |
1542 if (fMarkPosition is null || fMarkPosition.isDeleted()) | |
1543 return null; | |
1544 | |
1545 int start= fMarkPosition.getOffset(); | |
1546 int end= getSelectedRange().x; | |
1547 | |
1548 return start > end ? new Region (end, start - end) : new Region(start, end - start); | |
1549 } | |
1550 | |
1551 /* | |
1552 * @see dwtx.jface.text.TextViewer#copyMarkedRegion(bool) | |
1553 */ | |
1554 protected void copyMarkedRegion(bool delete) { | |
1555 IRegion markedRegion= getMarkedRegion(); | |
1556 if (markedRegion !is null) | |
1557 copyToClipboard(markedRegion.getOffset(), markedRegion.getLength(), delete, getTextWidget()); | |
1558 } | |
1559 | |
1560 private void copyToClipboard(int offset, int length, bool delete, StyledText textWidget) { | |
1561 | |
1562 String copyText= null; | |
1563 | |
1564 try { | |
1565 IDocument document= getDocument(); | |
1566 copyText= document.get(offset, length); | |
1567 } catch (BadLocationException ex) { | |
1568 // XXX: should log here, but JFace Text has no Log | |
1569 // As a fallback solution let the widget handle this | |
1570 textWidget.copy(); | |
1571 } | |
1572 | |
1573 if (copyText !is null && copyText.equals(textWidget.getSelectionText())) { | |
1574 /* | |
1575 * XXX: Reduce pain of https://bugs.eclipse.org/bugs/show_bug.cgi?id=64498 | |
1576 * by letting the widget handle the copy operation in this special case. | |
1577 */ | |
1578 textWidget.copy(); | |
1579 } else if (copyText !is null) { | |
1580 | |
1581 Clipboard clipboard= new Clipboard(textWidget.getDisplay()); | |
1582 | |
1583 try { | |
1584 Transfer[] dataTypes= new Transfer[] { TextTransfer.getInstance() }; | |
1585 Object[] data= new Object[] { copyText }; | |
1586 try { | |
1587 clipboard.setContents(data, dataTypes); | |
1588 } catch (DWTError e) { | |
1589 if (e.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) | |
1590 throw e; | |
1591 /* | |
1592 * TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59459 | |
1593 * we should either log and/or inform the user | |
1594 * silently fail for now. | |
1595 */ | |
1596 return; | |
1597 } | |
1598 | |
1599 } finally { | |
1600 clipboard.dispose(); | |
1601 } | |
1602 } | |
1603 | |
1604 if (delete) { | |
1605 try { | |
1606 deleteTextRange(offset, length, textWidget); | |
1607 } catch (BadLocationException x) { | |
1608 // XXX: should log here, but JFace Text has no Log | |
1609 } | |
1610 } | |
1611 } | |
1612 | |
1613 private void deleteTextRange(int offset, int length, StyledText textWidget) throws BadLocationException { | |
1614 getDocument().replace(offset, length, ""); //$NON-NLS-1$ | |
1615 int widgetCaret= modelOffset2WidgetOffset(offset); | |
1616 if (widgetCaret > -1) | |
1617 textWidget.setSelection(widgetCaret); | |
1618 } | |
1619 | |
1620 /** | |
1621 * Adapts the behavior of the super class to respect line based folding. | |
1622 * | |
1623 * @param widgetSelection the widget selection | |
1624 * @return the model selection while respecting line based folding | |
1625 */ | |
1626 protected Point widgetSelection2ModelSelection(Point widgetSelection) { | |
1627 | |
1628 if (!isProjectionMode()) | |
1629 return super.widgetSelection2ModelSelection(widgetSelection); | |
1630 | |
1631 /* | |
1632 * There is one requirement that governs preservation of logical | |
1633 * positions: | |
1634 * | |
1635 * 1) a selection with widget_length is 0 should never expand to have | |
1636 * model_length > 0. | |
1637 * | |
1638 * There are a number of ambiguities to resolve with projection regions. | |
1639 * A projected region P has a widget-length of zero. Its widget offset | |
1640 * may interact with the selection S in various ways: | |
1641 * | |
1642 * A) P.widget_offset lies at the caret, S.widget_length is zero. | |
1643 * Requirement 1 applies. S is *behind* P (done so by widgetRange2ModelRange). | |
1644 * | |
1645 * B) P.widget_offset lies inside the widget selection. This case is | |
1646 * easy: P is included in S, which is automatically done so by | |
1647 * widgetRange2ModelRange. | |
1648 * | |
1649 * C) P.widget_offset lies at S.widget_end: This is | |
1650 * arguable - our policy is to include P if it belongs to a projection | |
1651 * annotation that overlaps with the widget selection. | |
1652 * | |
1653 * D) P.widget_offset lies at S.widget_offset: Arguable - our policy | |
1654 * is to include P if it belongs to a projection annotation that | |
1655 * overlaps with the widget selection | |
1656 */ | |
1657 IRegion modelSelection= widgetRange2ModelRange(new Region(widgetSelection.x, widgetSelection.y)); | |
1658 if (modelSelection is null) | |
1659 return null; | |
1660 | |
1661 int modelOffset= modelSelection.getOffset(); | |
1662 int modelEndOffset= modelOffset + modelSelection.getLength(); | |
1663 | |
1664 /* Case A: never expand a zero-length selection. S is *behind* P. */ | |
1665 if (widgetSelection.y is 0) | |
1666 return new Point(modelEndOffset, 0); | |
1667 | |
1668 int widgetSelectionExclusiveEnd= widgetSelection.x + widgetSelection.y; | |
1669 Position[] annotationPositions= computeOverlappingAnnotationPositions(modelSelection); | |
1670 for (int i= 0; i < annotationPositions.length; i++) { | |
1671 IRegion[] regions= computeCollapsedRegions(annotationPositions[i]); | |
1672 if (regions is null) | |
1673 continue; | |
1674 for (int j= 0; j < regions.length; j++) { | |
1675 IRegion modelRange= regions[j]; | |
1676 IRegion widgetRange= modelRange2ClosestWidgetRange(modelRange); | |
1677 // only take collapsed ranges, i.e. widget length is 0 | |
1678 if (widgetRange !is null && widgetRange.getLength() is 0) { | |
1679 int widgetOffset= widgetRange.getOffset(); | |
1680 // D) region is collapsed at S.widget_offset | |
1681 if (widgetOffset is widgetSelection.x) | |
1682 modelOffset= Math.min(modelOffset, modelRange.getOffset()); | |
1683 // C) region is collapsed at S.widget_end | |
1684 else if (widgetOffset is widgetSelectionExclusiveEnd) | |
1685 modelEndOffset= Math.max(modelEndOffset, modelRange.getOffset() + modelRange.getLength()); | |
1686 } | |
1687 } | |
1688 } | |
1689 return new Point(modelOffset, modelEndOffset - modelOffset); | |
1690 } | |
1691 | |
1692 /** | |
1693 * Returns the positions of all annotations that intersect with | |
1694 * <code>modelSelection</code> and that are at least partly visible. | |
1695 * @param modelSelection a model range | |
1696 * @return the positions of all annotations that intersect with | |
1697 * <code>modelSelection</code> | |
1698 * @since 3.1 | |
1699 */ | |
1700 private Position[] computeOverlappingAnnotationPositions(IRegion modelSelection) { | |
1701 List positions= new ArrayList(); | |
1702 for (Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) { | |
1703 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next(); | |
1704 Position position= fProjectionAnnotationModel.getPosition(annotation); | |
1705 if (position !is null && position.overlapsWith(modelSelection.getOffset(), modelSelection.getLength()) && modelRange2WidgetRange(position) !is null) | |
1706 positions.add(position); | |
1707 } | |
1708 return (Position[]) positions.toArray(new Position[positions.size()]); | |
1709 } | |
1710 | |
1711 /* | |
1712 * @see dwtx.jface.text.TextViewer#getFindReplaceDocumentAdapter() | |
1713 */ | |
1714 protected FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() { | |
1715 if (fFindReplaceDocumentAdapter is null) { | |
1716 IDocument document= isProjectionMode() ? getDocument() : getVisibleDocument(); | |
1717 fFindReplaceDocumentAdapter= new FindReplaceDocumentAdapter(document); | |
1718 } | |
1719 return fFindReplaceDocumentAdapter; | |
1720 } | |
1721 | |
1722 /* | |
1723 * @see dwtx.jface.text.TextViewer#findAndSelect(int, java.lang.String, bool, bool, bool, bool) | |
1724 */ | |
1725 protected int findAndSelect(int startPosition, String findString, bool forwardSearch, bool caseSensitive, bool wholeWord, bool regExSearch) { | |
1726 | |
1727 if (!isProjectionMode()) | |
1728 return super.findAndSelect(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); | |
1729 | |
1730 StyledText textWidget= getTextWidget(); | |
1731 if (textWidget is null) | |
1732 return -1; | |
1733 | |
1734 try { | |
1735 | |
1736 IRegion matchRegion= getFindReplaceDocumentAdapter().find(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); | |
1737 if (matchRegion !is null) { | |
1738 exposeModelRange(matchRegion); | |
1739 revealRange(matchRegion.getOffset(), matchRegion.getLength()); | |
1740 setSelectedRange(matchRegion.getOffset(), matchRegion.getLength()); | |
1741 return matchRegion.getOffset(); | |
1742 } | |
1743 | |
1744 } catch (BadLocationException x) { | |
1745 } | |
1746 | |
1747 return -1; | |
1748 } | |
1749 | |
1750 /* | |
1751 * @see dwtx.jface.text.TextViewer#findAndSelectInRange(int, java.lang.String, bool, bool, bool, int, int, bool) | |
1752 */ | |
1753 protected int findAndSelectInRange(int startPosition, String findString, bool forwardSearch, bool caseSensitive, bool wholeWord, int rangeOffset, int rangeLength, bool regExSearch) { | |
1754 | |
1755 if (!isProjectionMode()) | |
1756 return super.findAndSelectInRange(startPosition, findString, forwardSearch, caseSensitive, wholeWord, rangeOffset, rangeLength, regExSearch); | |
1757 | |
1758 StyledText textWidget= getTextWidget(); | |
1759 if (textWidget is null) | |
1760 return -1; | |
1761 | |
1762 try { | |
1763 | |
1764 int modelOffset= startPosition; | |
1765 if (forwardSearch && (startPosition is -1 || startPosition < rangeOffset)) { | |
1766 modelOffset= rangeOffset; | |
1767 } else if (!forwardSearch && (startPosition is -1 || startPosition > rangeOffset + rangeLength)) { | |
1768 modelOffset= rangeOffset + rangeLength; | |
1769 } | |
1770 | |
1771 IRegion matchRegion= getFindReplaceDocumentAdapter().find(modelOffset, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); | |
1772 if (matchRegion !is null) { | |
1773 int offset= matchRegion.getOffset(); | |
1774 int length= matchRegion.getLength(); | |
1775 if (rangeOffset <= offset && offset + length <= rangeOffset + rangeLength) { | |
1776 exposeModelRange(matchRegion); | |
1777 revealRange(offset, length); | |
1778 setSelectedRange(offset, length); | |
1779 return offset; | |
1780 } | |
1781 } | |
1782 | |
1783 } catch (BadLocationException x) { | |
1784 } | |
1785 | |
1786 return -1; | |
1787 } | |
1788 } |