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