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 }