Mercurial > projects > dwt2
comparison org.eclipse.jface.text/src/org/eclipse/jface/text/DocumentCommand.d @ 12:bc29606a740c
Added dwt-addons in original directory structure of eclipse.org
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 14 Mar 2009 18:23:29 +0100 |
parents | |
children | 52184e4b815c |
comparison
equal
deleted
inserted
replaced
11:43904fec5dca | 12:bc29606a740c |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2000, 2007 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 | |
14 | |
15 module org.eclipse.jface.text.DocumentCommand; | |
16 | |
17 import org.eclipse.jface.text.IDocumentPartitioningListener; // packageimport | |
18 import org.eclipse.jface.text.IRegion; // packageimport | |
19 import org.eclipse.jface.text.IDocumentListener; // packageimport | |
20 import org.eclipse.jface.text.IDocument; // packageimport | |
21 import org.eclipse.jface.text.BadLocationException; // packageimport | |
22 import org.eclipse.jface.text.DefaultPositionUpdater; // packageimport | |
23 import org.eclipse.jface.text.Position; // packageimport | |
24 import org.eclipse.jface.text.BadPositionCategoryException; // packageimport | |
25 | |
26 import java.lang.all; | |
27 import java.util.ListIterator; | |
28 import java.util.Collections; | |
29 import java.util.List; | |
30 import java.util.ArrayList; | |
31 import java.util.Iterator; | |
32 import java.util.Set; | |
33 import tango.core.Exception; | |
34 | |
35 import org.eclipse.swt.events.VerifyEvent; | |
36 import org.eclipse.core.runtime.Assert; | |
37 | |
38 | |
39 /** | |
40 * Represents a text modification as a document replace command. The text | |
41 * modification is given as a {@link org.eclipse.swt.events.VerifyEvent} and | |
42 * translated into a document replace command relative to a given offset. A | |
43 * document command can also be used to initialize a given | |
44 * <code>VerifyEvent</code>. | |
45 * <p> | |
46 * A document command can also represent a list of related changes.</p> | |
47 */ | |
48 public class DocumentCommand { | |
49 | |
50 /** | |
51 * A command which is added to document commands. | |
52 * @since 2.1 | |
53 */ | |
54 private static class Command : Comparable { | |
55 /** The offset of the range to be replaced */ | |
56 private const int fOffset; | |
57 /** The length of the range to be replaced. */ | |
58 private const int fLength; | |
59 /** The replacement text */ | |
60 private const String fText; | |
61 /** The listener who owns this command */ | |
62 private const IDocumentListener fOwner; | |
63 | |
64 /** | |
65 * Creates a new command with the given specification. | |
66 * | |
67 * @param offset the offset of the replace command | |
68 * @param length the length of the replace command | |
69 * @param text the text to replace with, may be <code>null</code> | |
70 * @param owner the document command owner, may be <code>null</code> | |
71 * @since 3.0 | |
72 */ | |
73 public this(int offset, int length, String text, IDocumentListener owner) { | |
74 if (offset < 0 || length < 0) | |
75 throw new IllegalArgumentException(null); | |
76 fOffset= offset; | |
77 fLength= length; | |
78 fText= text; | |
79 fOwner= owner; | |
80 } | |
81 | |
82 /** | |
83 * Returns the length delta for this command. | |
84 * | |
85 * @return the length delta for this command | |
86 */ | |
87 public int getDeltaLength() { | |
88 return (fText is null ? 0 : fText.length) - fLength; | |
89 } | |
90 | |
91 /** | |
92 * Executes the document command on the specified document. | |
93 * | |
94 * @param document the document on which to execute the command. | |
95 * @throws BadLocationException in case this commands cannot be executed | |
96 */ | |
97 public void execute(IDocument document) { | |
98 | |
99 if (fLength is 0 && fText is null) | |
100 return; | |
101 | |
102 if (fOwner !is null) | |
103 document.removeDocumentListener(fOwner); | |
104 | |
105 document.replace(fOffset, fLength, fText); | |
106 | |
107 if (fOwner !is null) | |
108 document.addDocumentListener(fOwner); | |
109 } | |
110 | |
111 /* | |
112 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) | |
113 */ | |
114 public int compareTo(Object object) { | |
115 if (isEqual(object)) | |
116 return 0; | |
117 | |
118 final Command command= cast(Command) object; | |
119 | |
120 // diff middle points if not intersecting | |
121 if (fOffset + fLength <= command.fOffset || command.fOffset + command.fLength <= fOffset) { | |
122 int value= (2 * fOffset + fLength) - (2 * command.fOffset + command.fLength); | |
123 if (value !is 0) | |
124 return value; | |
125 } | |
126 // the answer | |
127 return 42; | |
128 } | |
129 | |
130 private bool isEqual(Object object) { | |
131 if (object is this) | |
132 return true; | |
133 if (!( cast(Command)object )) | |
134 return false; | |
135 final Command command= cast(Command) object; | |
136 return command.fOffset is fOffset && command.fLength is fLength; | |
137 } | |
138 } | |
139 | |
140 /** | |
141 * An iterator, which iterates in reverse over a list. | |
142 */ | |
143 private static class ReverseListIterator : Iterator { | |
144 | |
145 /** The list iterator. */ | |
146 private const ListIterator fListIterator; | |
147 | |
148 /** | |
149 * Creates a reverse list iterator. | |
150 * @param listIterator the iterator that this reverse iterator is based upon | |
151 */ | |
152 public this(ListIterator listIterator) { | |
153 if (listIterator is null) | |
154 throw new IllegalArgumentException(null); | |
155 fListIterator= listIterator; | |
156 } | |
157 | |
158 /* | |
159 * @see java.util.Iterator#hasNext() | |
160 */ | |
161 public bool hasNext() { | |
162 return fListIterator.hasPrevious(); | |
163 } | |
164 | |
165 /* | |
166 * @see java.util.Iterator#next() | |
167 */ | |
168 public Object next() { | |
169 return fListIterator.previous(); | |
170 } | |
171 | |
172 /* | |
173 * @see java.util.Iterator#remove() | |
174 */ | |
175 public void remove() { | |
176 throw new UnsupportedOperationException(); | |
177 } | |
178 } | |
179 | |
180 /** | |
181 * A command iterator. | |
182 */ | |
183 private static class CommandIterator : Iterator { | |
184 | |
185 /** The command iterator. */ | |
186 private const Iterator fIterator; | |
187 | |
188 /** The original command. */ | |
189 private Command fCommand; | |
190 | |
191 /** A flag indicating the direction of iteration. */ | |
192 private bool fForward; | |
193 | |
194 /** | |
195 * Creates a command iterator. | |
196 * | |
197 * @param commands an ascending ordered list of commands | |
198 * @param command the original command | |
199 * @param forward the direction | |
200 */ | |
201 public this(List commands, Command command, bool forward) { | |
202 if (commands is null || command is null) | |
203 throw new IllegalArgumentException(null); | |
204 fIterator= forward ? commands.iterator() : new ReverseListIterator(commands.listIterator(commands.size())); | |
205 fCommand= command; | |
206 fForward= forward; | |
207 } | |
208 | |
209 /* | |
210 * @see java.util.Iterator#hasNext() | |
211 */ | |
212 public bool hasNext() { | |
213 return fCommand !is null || fIterator.hasNext(); | |
214 } | |
215 | |
216 /* | |
217 * @see java.util.Iterator#next() | |
218 */ | |
219 public Object next() { | |
220 | |
221 if (!hasNext()) | |
222 throw new NoSuchElementException(null); | |
223 | |
224 if (fCommand is null) | |
225 return fIterator.next(); | |
226 | |
227 if (!fIterator.hasNext()) { | |
228 final Command tempCommand= fCommand; | |
229 fCommand= null; | |
230 return tempCommand; | |
231 } | |
232 | |
233 final Command command= cast(Command) fIterator.next(); | |
234 final int compareValue= command.compareTo(fCommand); | |
235 | |
236 if ((compareValue < 0) ^ !fForward) { | |
237 return command; | |
238 | |
239 } else if ((compareValue > 0) ^ !fForward) { | |
240 final Command tempCommand= fCommand; | |
241 fCommand= command; | |
242 return tempCommand; | |
243 | |
244 } else { | |
245 throw new IllegalArgumentException(null); | |
246 } | |
247 } | |
248 | |
249 /* | |
250 * @see java.util.Iterator#remove() | |
251 */ | |
252 public void remove() { | |
253 throw new UnsupportedOperationException(); | |
254 } | |
255 } | |
256 | |
257 /** Must the command be updated */ | |
258 public bool doit= false; | |
259 /** The offset of the command. */ | |
260 public int offset; | |
261 /** The length of the command */ | |
262 public int length; | |
263 /** The text to be inserted */ | |
264 public String text; | |
265 /** | |
266 * The owner of the document command which will not be notified. | |
267 * @since 2.1 | |
268 */ | |
269 public IDocumentListener owner; | |
270 /** | |
271 * The caret offset with respect to the document before the document command is executed. | |
272 * @since 2.1 | |
273 */ | |
274 public int caretOffset; | |
275 /** | |
276 * Additional document commands. | |
277 * @since 2.1 | |
278 */ | |
279 private const List fCommands; | |
280 /** | |
281 * Indicates whether the caret should be shifted by this command. | |
282 * @since 3.0 | |
283 */ | |
284 public bool shiftsCaret; | |
285 | |
286 | |
287 /** | |
288 * Creates a new document command. | |
289 */ | |
290 /+protected+/ this() { | |
291 fCommands= new ArrayList(); | |
292 } | |
293 | |
294 /** | |
295 * Translates a verify event into a document replace command using the given offset. | |
296 * | |
297 * @param event the event to be translated | |
298 * @param modelRange the event range as model range | |
299 */ | |
300 void setEvent(VerifyEvent event, IRegion modelRange) { | |
301 | |
302 doit= true; | |
303 text= event.text; | |
304 | |
305 offset= modelRange.getOffset(); | |
306 length= modelRange.getLength(); | |
307 | |
308 owner= null; | |
309 caretOffset= -1; | |
310 shiftsCaret= true; | |
311 fCommands.clear(); | |
312 } | |
313 | |
314 /** | |
315 * Fills the given verify event with the replace text and the <code>doit</code> | |
316 * flag of this document command. Returns whether the document command | |
317 * covers the same range as the verify event considering the given offset. | |
318 * | |
319 * @param event the event to be changed | |
320 * @param modelRange to be considered for range comparison | |
321 * @return <code>true</code> if this command and the event cover the same range | |
322 */ | |
323 bool fillEvent(VerifyEvent event, IRegion modelRange) { | |
324 event.text= text; | |
325 event.doit= (offset is modelRange.getOffset() && length is modelRange.getLength() && doit && caretOffset is -1); | |
326 return event.doit; | |
327 } | |
328 | |
329 /** | |
330 * Adds an additional replace command. The added replace command must not overlap | |
331 * with existing ones. If the document command owner is not <code>null</code>, it will not | |
332 * get document change notifications for the particular command. | |
333 * | |
334 * @param commandOffset the offset of the region to replace | |
335 * @param commandLength the length of the region to replace | |
336 * @param commandText the text to replace with, may be <code>null</code> | |
337 * @param commandOwner the command owner, may be <code>null</code> | |
338 * @throws BadLocationException if the added command intersects with an existing one | |
339 * @since 2.1 | |
340 */ | |
341 public void addCommand(int commandOffset, int commandLength, String commandText, IDocumentListener commandOwner) { | |
342 final Command command= new Command(commandOffset, commandLength, commandText, commandOwner); | |
343 | |
344 if (intersects(command)) | |
345 throw new BadLocationException(); | |
346 | |
347 final int index= Collections.binarySearch(fCommands, command); | |
348 | |
349 // a command with exactly the same ranges exists already | |
350 if (index >= 0) | |
351 throw new BadLocationException(); | |
352 | |
353 // binary search result is defined as (-(insertionIndex) - 1) | |
354 final int insertionIndex= -(index + 1); | |
355 | |
356 // overlaps to the right? | |
357 if (insertionIndex !is fCommands.size() && intersects(cast(Command) fCommands.get(insertionIndex), command)) | |
358 throw new BadLocationException(); | |
359 | |
360 // overlaps to the left? | |
361 if (insertionIndex !is 0 && intersects(cast(Command) fCommands.get(insertionIndex - 1), command)) | |
362 throw new BadLocationException(); | |
363 | |
364 fCommands.add(insertionIndex, command); | |
365 } | |
366 | |
367 /** | |
368 * Returns an iterator over the commands in ascending position order. | |
369 * The iterator includes the original document command. | |
370 * Commands cannot be removed. | |
371 * | |
372 * @return returns the command iterator | |
373 */ | |
374 public Iterator getCommandIterator() { | |
375 Command command= new Command(offset, length, text, owner); | |
376 return new CommandIterator(fCommands, command, true); | |
377 } | |
378 | |
379 /** | |
380 * Returns the number of commands including the original document command. | |
381 * | |
382 * @return returns the number of commands | |
383 * @since 2.1 | |
384 */ | |
385 public int getCommandCount() { | |
386 return 1 + fCommands.size(); | |
387 } | |
388 | |
389 /** | |
390 * Returns whether the two given commands intersect. | |
391 * | |
392 * @param command0 the first command | |
393 * @param command1 the second command | |
394 * @return <code>true</code> if the commands intersect | |
395 * @since 2.1 | |
396 */ | |
397 private bool intersects(Command command0, Command command1) { | |
398 // diff middle points if not intersecting | |
399 if (command0.fOffset + command0.fLength <= command1.fOffset || command1.fOffset + command1.fLength <= command0.fOffset) | |
400 return (2 * command0.fOffset + command0.fLength) - (2 * command1.fOffset + command1.fLength) is 0; | |
401 return true; | |
402 } | |
403 | |
404 /** | |
405 * Returns whether the given command intersects with this command. | |
406 * | |
407 * @param command the command | |
408 * @return <code>true</code> if the command intersects with this command | |
409 * @since 2.1 | |
410 */ | |
411 private bool intersects(Command command) { | |
412 // diff middle points if not intersecting | |
413 if (offset + length <= command.fOffset || command.fOffset + command.fLength <= offset) | |
414 return (2 * offset + length) - (2 * command.fOffset + command.fLength) is 0; | |
415 return true; | |
416 } | |
417 | |
418 /** | |
419 * Executes the document commands on a document. | |
420 * | |
421 * @param document the document on which to execute the commands | |
422 * @throws BadLocationException in case access to the given document fails | |
423 * @since 2.1 | |
424 */ | |
425 void execute(IDocument document) { | |
426 | |
427 if (length is 0 && text is null && fCommands.size() is 0) | |
428 return; | |
429 | |
430 DefaultPositionUpdater updater= new DefaultPositionUpdater(getCategory()); | |
431 Position caretPosition= null; | |
432 try { | |
433 if (updateCaret()) { | |
434 document.addPositionCategory(getCategory()); | |
435 document.addPositionUpdater(updater); | |
436 caretPosition= new Position(caretOffset); | |
437 document.addPosition(getCategory(), caretPosition); | |
438 } | |
439 | |
440 final Command originalCommand= new Command(offset, length, text, owner); | |
441 for (final Iterator iterator= new CommandIterator(fCommands, originalCommand, false); iterator.hasNext(); ) | |
442 (cast(Command) iterator.next()).execute(document); | |
443 | |
444 } catch (BadLocationException e) { | |
445 // ignore | |
446 } catch (BadPositionCategoryException e) { | |
447 // ignore | |
448 } finally { | |
449 delegate(){ | |
450 if (updateCaret()) { | |
451 document.removePositionUpdater(updater); | |
452 try { | |
453 document.removePositionCategory(getCategory()); | |
454 } catch (BadPositionCategoryException e) { | |
455 Assert.isTrue(false); | |
456 } | |
457 caretOffset= caretPosition.getOffset(); | |
458 } | |
459 }(); | |
460 } | |
461 } | |
462 | |
463 /** | |
464 * Returns <code>true</code> if the caret offset should be updated, <code>false</code> otherwise. | |
465 * | |
466 * @return <code>true</code> if the caret offset should be updated, <code>false</code> otherwise | |
467 * @since 3.0 | |
468 */ | |
469 private bool updateCaret() { | |
470 return shiftsCaret && caretOffset !is -1; | |
471 } | |
472 | |
473 /** | |
474 * Returns the position category for the caret offset position. | |
475 * | |
476 * @return the position category for the caret offset position | |
477 * @since 3.0 | |
478 */ | |
479 private String getCategory() { | |
480 return toString(); | |
481 } | |
482 | |
483 } |