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