View Javadoc
1   package org.vaadin.aceeditor.client;
2   
3   import com.google.gwt.core.client.JsArray;
4   import com.google.gwt.core.client.JsArrayInteger;
5   import com.google.gwt.user.client.DOM;
6   import com.google.gwt.user.client.ui.FocusWidget;
7   import com.vaadin.client.VConsole;
8   
9   import org.vaadin.aceeditor.client.AceAnnotation.MarkerAnnotation;
10  import org.vaadin.aceeditor.client.AceAnnotation.RowAnnotation;
11  import org.vaadin.aceeditor.client.AceMarker.OnTextChange;
12  import org.vaadin.aceeditor.client.ClientSideDocDiff.Adjuster;
13  import org.vaadin.aceeditor.client.gwt.*;
14  import org.vaadin.aceeditor.client.gwt.GwtAceChangeEvent.Data.Action;
15  
16  import java.util.*;
17  import java.util.Map.Entry;
18  
19  /**
20   * A {@link com.google.gwt.user.client.ui.Widget} containing
21   * {@link org.vaadin.aceeditor.client.gwt.GwtAceEditor}
22   */
23  public class AceEditorWidget extends FocusWidget implements
24  		GwtAceChangeHandler, GwtAceFocusBlurHandler,
25  		GwtAceChangeSelectionHandler, GwtAceChangeCursorHandler {
26  
27  	public interface TextChangeListener {
28  		public void changed();
29  	}
30  	public interface SelectionChangeListener {
31  		public void selectionChanged();
32  	}
33  
34  	public interface FocusChangeListener {
35  		public void focusChanged(boolean focused);
36  	}
37  
38      protected LinkedList<TextChangeListener> changeListeners = new LinkedList<TextChangeListener>();
39  	public void addTextChangeListener(TextChangeListener li) {
40  		changeListeners.add(li);
41  	}
42  	public void removeTextChangeListener(TextChangeListener li) {
43  		changeListeners.remove(li);
44  	}
45  
46      protected LinkedList<SelectionChangeListener> selChangeListeners = new LinkedList<SelectionChangeListener>();
47  	public void addSelectionChangeListener(SelectionChangeListener li) {
48  		selChangeListeners.add(li);
49  	}
50  	public void removeSelectionChangeListener(SelectionChangeListener li) {
51  		selChangeListeners.remove(li);
52  	}
53  
54      protected FocusChangeListener focusChangeListener;
55  	public void setFocusChangeListener(FocusChangeListener li) {
56  		focusChangeListener = li;
57  	}
58  
59      protected class MarkerInEditor {
60          protected AceMarker marker;
61          protected String clientId;
62          protected MarkerInEditor(AceMarker marker, String clientId) {
63  			this.marker = marker;
64  			this.clientId = clientId;
65  		}
66  	}
67  
68      protected class AnnotationInEditor {
69          protected int row;
70          protected AceAnnotation ann;
71          protected String markerId;
72          protected AnnotationInEditor(AceAnnotation ann, String markerId) {
73  			this.ann = ann;
74  			this.markerId = markerId;
75  		}
76  	}
77  
78      protected GwtAceEditor editor;
79  
80      protected String editorId;
81  
82      protected static int idCounter = 0;
83  
84      protected String text = "";
85      protected boolean readOnly = false;
86      protected boolean propertyReadOnly = false;
87      protected boolean focused;
88      protected AceRange selection = new AceRange(0,0,0,0);
89  
90  	// key: marker markerId
91      protected Map<String,MarkerInEditor> markersInEditor = Collections.emptyMap();
92  
93      protected Set<RowAnnotation> rowAnnsInEditor = Collections.emptySet();
94      protected Set<AnnotationInEditor> markerAnnsInEditor = Collections.emptySet();
95  
96      protected Map<Integer, AceRange> invisibleMarkers = new HashMap<Integer, AceRange>();
97      protected int latestInvisibleMarkerId = 0;
98  
99      protected boolean ignoreEditorEvents = false;
100 
101     protected Set<MarkerAnnotation> markerAnnotations = Collections.emptySet();
102     protected Set<RowAnnotation> rowAnnotations = Collections.emptySet();
103 
104     protected GwtAceKeyboardHandler keyboardHandler;
105 
106     protected AceDoc doc;
107 
108     protected static String nextId() {
109 		return "_AceEditorWidget_" + (++idCounter);
110 	}
111 
112 	public AceEditorWidget() {
113 		super(DOM.createDiv());
114 		this.editorId = nextId();
115 		this.setStylePrimaryName("AceEditorWidget");
116 		
117 	}
118 
119     public boolean isInitialized() {
120         return editor != null;
121     }
122 	
123 	public void initialize() {
124 		editor = GwtAceEditor.create(this.getElement(), editorId);
125 		editor.addChangeHandler(this);
126 		editor.addFocusListener(this);
127 		editor.addChangeSelectionHandler(this);
128 		editor.addChangeCursorHandler(this);
129 		if (keyboardHandler!=null) {
130 			editor.setKeyboardHandler(keyboardHandler);
131 		}
132 	}
133 	
134 	public void setKeyboardHandler(GwtAceKeyboardHandler handler) {
135 		this.keyboardHandler = handler;
136 		if (isInitialized()) {
137 			editor.setKeyboardHandler(handler);
138 		}
139 	}
140 	
141 	@Override
142 	public void setWidth(String w) {
143 		super.setWidth(w);
144 		if (editor!=null) {
145 			editor.resize();
146 		}
147 	}
148 	
149 	@Override
150 	public void setHeight(String h) {
151 		super.setHeight(h);
152 		if (editor!=null) {
153 			editor.resize();
154 		}
155 	}
156 	
157 	public void setWordwrap(boolean wrap) {
158 		if (isInitialized()) { 
159 			editor.setUseWrapMode(wrap);
160 		}
161 	}
162 
163     public void setShowGutter(boolean showGutter) {
164         if (isInitialized()) {
165             editor.setShowGutter(showGutter);
166         }
167     }
168 
169     public void setShowPrintMargin(boolean showPrintMargin) {
170         if (isInitialized()) {
171             editor.setShowPrintMargin(showPrintMargin);
172         }
173     }
174 
175     public void setHighlightActiveLineEnabled(boolean highlightActiveLine) {
176         if (isInitialized()) {
177             editor.setHighlightActiveLineEnabled(highlightActiveLine);
178         }
179     }
180 
181     protected void setText(String text) {
182 		if (!isInitialized()) {
183 			return;
184 		}
185 		AceRange oldSelection = selection;
186 		Adjuster adjuster = new Adjuster(this.text, text);
187 		adjustInvisibleMarkersOnTextChange(adjuster);
188 		this.text = text;
189 		this.doc = null;
190 		ignoreEditorEvents = true;
191 		double wasAtRow = editor.getScrollTopRow();
192 		editor.setText(text);
193 		AceRange adjSel = adjuster.adjust(oldSelection);
194 		setSelection(adjSel, true);
195 		editor.scrollToRow(wasAtRow);
196 		ignoreEditorEvents = false;
197 	}
198 
199 
200     protected void adjustInvisibleMarkersOnTextChange(Adjuster adjuster) {
201 		HashMap<Integer, AceRange> ims = new HashMap<Integer, AceRange>(invisibleMarkers.size());
202 		for (Entry<Integer, AceRange> e : invisibleMarkers.entrySet()) {
203 			ims.put(e.getKey(), adjuster.adjust(e.getValue()));
204 		}
205 		invisibleMarkers = ims;
206 	}
207 	
208 	public void setSelection(AceRange s) {
209 		setSelection(s, false);
210 	}
211 
212     protected void setSelection(AceRange s, boolean force) {
213 		if (!isInitialized()) {
214 			return;
215 		}
216 		if (s.equals(selection) && !force) {
217 			return;
218 		}
219 		
220 		selection = s;
221 		
222 		int r1 = s.getStartRow();
223 		int c1 = s.getStartCol();
224 		int r2 = s.getEndRow();
225 		int c2 = s.getEndCol();
226 		boolean backwards = r1 > r2 || (r1 == r2 && c1 > c2);
227 		GwtAceRange range;
228 		if (backwards) {
229 			range = GwtAceRange.create(r2, c2, r1, c1);
230 		} else {
231 			range = GwtAceRange.create(r1, c1, r2, c2);
232 		}
233 		editor.setSelection(range, backwards);
234 	}
235 
236 	public void setMode(String mode) {
237 		if (!isInitialized()) {
238 			return;
239 		}
240 		editor.setMode(mode);
241 	}
242 
243 	public void setTheme(String theme) {
244 		if (!isInitialized()) {
245 			return;
246 		}
247 		editor.setTheme(theme);
248 	}
249 
250 	protected void setMarkers(Map<String, AceMarker> markers) {
251 		if (!isInitialized()) {
252 			return;
253 		}
254 		
255 		HashMap<String,MarkerInEditor> newMarkers = new HashMap<String,MarkerInEditor>();
256 		for (Entry<String, AceMarker> e : markers.entrySet()) {
257 			String mId = e.getKey();
258 			AceMarker m = e.getValue();
259 			MarkerInEditor existing = markersInEditor.get(mId);
260 			if (existing!=null) {
261 				editor.removeMarker(existing.clientId);
262 			}
263 			String clientId = editor.addMarker(convertRange(m.getRange()), m.getCssClass(), m.getType().toString(), m.isInFront());
264 			existing = new MarkerInEditor(m, clientId);
265 			newMarkers.put(mId, existing);
266 		}
267 		
268 		
269 		for (MarkerInEditor hehe : markersInEditor.values()) {
270 			if (!newMarkers.containsKey(hehe.marker.getMarkerId())) {
271 				editor.removeMarker(hehe.clientId);
272 			}
273 		}
274 		
275 		markersInEditor = newMarkers;
276 		adjustMarkerAnnotations();
277 	}
278 	
279 	protected void adjustMarkerAnnotations() {
280 		boolean changed = false;
281 		for (AnnotationInEditor aie : markerAnnsInEditor) {
282 			int row = rowOfMarker(aie.markerId);
283 			if (row!=-1 && row != aie.row) {
284 				aie.row = row;
285 				changed = true;
286 			}
287 		}
288 		if (changed) {
289 			setAnnotationsToEditor();
290 		}
291 	}
292 
293 	protected void setAnnotations(Set<MarkerAnnotation> manns, Set<RowAnnotation> ranns) {
294 		if (!isInitialized()) {
295 			return;
296 		}
297 		if (manns!=null) {
298 			markerAnnotations = manns;
299 			markerAnnsInEditor = createAIEfromMA(manns);
300 		}
301 		if (ranns!=null) {
302 			rowAnnotations = ranns;
303 			rowAnnsInEditor = ranns;
304 		}
305 		setAnnotationsToEditor();
306 	}
307 
308 	protected void setAnnotationsToEditor() {
309 		JsArray<GwtAceAnnotation> arr = GwtAceAnnotation.createEmptyArray();
310 		
311 		JsArray<GwtAceAnnotation> existing = editor.getAnnotations();
312 		
313 		for (int i=0; i<existing.length(); ++i) {
314 			GwtAceAnnotation ann = existing.get(i);
315 			if (!ann.isVaadinAceEditorAnnotation()) {
316 				arr.push(ann);
317 			}
318 		}
319 		
320 		for (AnnotationInEditor maie : markerAnnsInEditor) {
321 			GwtAceAnnotation jsAnn = GwtAceAnnotation.create(maie.ann.getType().toString(), maie.ann.getMessage(), maie.row);
322 			arr.push(jsAnn);
323 		}
324 		for (RowAnnotation ra : rowAnnsInEditor) {
325 			AceAnnotation a = ra.getAnnotation();
326 			GwtAceAnnotation jsAnn = GwtAceAnnotation.create(a.getType().toString(), a.getMessage(), ra.getRow());
327 			arr.push(jsAnn);
328 		}
329 		editor.setAnnotations(arr);
330 	}
331 
332 	protected Set<AnnotationInEditor> createAIEfromMA(
333 			Set<MarkerAnnotation> anns) {
334 		Set<AnnotationInEditor> adjusted = new HashSet<AnnotationInEditor>();
335 		for (MarkerAnnotation a : anns) {
336 			int row = rowOfMarker(a.getMarkerId());
337 			if (row!=-1) {
338 				AnnotationInEditor maie = new AnnotationInEditor(a.getAnnotation(), a.getMarkerId());
339 				maie.row = row;
340 				adjusted.add(maie);
341 			}
342 		}
343 		return adjusted;
344 	}
345 	
346 	protected int rowOfMarker(String markerId) {
347 		MarkerInEditor cm = markersInEditor.get(markerId);
348 		if (cm==null) {
349 			return -1;
350 		}
351 		return cm.marker.getRange().getStartRow();
352 	}
353 
354 	@Override
355 	public void onChange(GwtAceChangeEvent e) {
356 		if (ignoreEditorEvents) {
357 			return;
358 		}
359 		String newText = editor.getText();
360 		if (newText.equals(text)) {
361 			return;
362 		}
363 		
364 		// TODO: do we do too much work here?
365 		// most of the time the editor doesn't have any markers nor annotations...
366 		
367 		adjustMarkers(e);
368 		adjustInvisibleMarkers(e);
369 		adjustMarkerAnnotations();
370 		text = newText;
371 		doc = null;
372 		fireTextChanged();
373 	}
374 
375 	public void fireTextChanged() {
376 		for (TextChangeListener li : changeListeners) {
377 			li.changed();
378 		}
379 	}
380 	
381 	protected void adjustMarkers(GwtAceChangeEvent e) {
382 		Action act = e.getData().getAction();
383 		GwtAceRange range = e.getData().getRange();
384 		Set<MarkerInEditor> moved = new HashSet<MarkerInEditor>();
385 		Set<MarkerInEditor> removed = new HashSet<MarkerInEditor>();
386 		
387 		if (act==Action.insertLines || act==Action.insertText) {
388 			for (MarkerInEditor cm : markersInEditor.values()) {
389 				if (cm.marker.getOnChange()==OnTextChange.ADJUST) {
390 					AceRange newRange = moveMarkerOnInsert(cm.marker.getRange(), range);
391 					if (newRange!=null) {
392 						cm.marker = cm.marker.withNewPosition(newRange);
393 						if (markerIsValid(cm.marker)) {
394 							moved.add(cm);
395 						}
396 						else {
397 							removed.add(cm);
398 						}
399 					}
400 				}
401 				else if (cm.marker.getOnChange()==OnTextChange.REMOVE) {
402 					removed.add(cm);
403 				}
404 			}
405 		}
406 		else if (act==Action.removeLines || act==Action.removeText) {
407 			for (MarkerInEditor cm : markersInEditor.values()) {
408 				if (cm.marker.getOnChange()==OnTextChange.ADJUST) {
409 					AceRange newRange = moveMarkerOnRemove(cm.marker.getRange(), range);
410 					if (newRange!=null) {
411 						cm.marker = cm.marker.withNewPosition(newRange);
412 						if (markerIsValid(cm.marker)) {
413 							moved.add(cm);
414 						}
415 						else {
416 							removed.add(cm);
417 						}
418 					}
419 				}
420 				else if (cm.marker.getOnChange()==OnTextChange.REMOVE) {
421 					removed.add(cm);
422 				}
423 			}
424 		}
425 		
426 		removeMarkers(removed);
427 		updateMarkers(moved);
428 	}
429 	
430 	protected void adjustInvisibleMarkers(GwtAceChangeEvent event) {
431 		Action act = event.getData().getAction();
432 		GwtAceRange range = event.getData().getRange();
433 		HashMap<Integer, AceRange> newMap = new HashMap<Integer, AceRange>();
434 		if (act==Action.insertLines || act==Action.insertText) {
435 			for (Entry<Integer, AceRange> e : invisibleMarkers.entrySet()) {
436 				AceRange newRange = moveMarkerOnInsert(e.getValue(), range);
437 				newMap.put(e.getKey(), newRange==null?e.getValue():newRange);
438 			}
439 		}
440 		else if (act==Action.removeLines || act==Action.removeText) {
441 			for (Entry<Integer, AceRange> e : invisibleMarkers.entrySet()) {
442 				AceRange newRange = moveMarkerOnRemove(e.getValue(), range);
443 				newMap.put(e.getKey(), newRange==null?e.getValue():newRange);
444 			}
445 		}
446 		invisibleMarkers = newMap;
447 	}
448 	
449 	protected static boolean markerIsValid(AceMarker marker) {
450 		AceRange r = marker.getRange();
451 		return !r.isZeroLength() && !r.isBackwards() && r.getStartRow() >= 0 && r.getStartCol() >= 0 && r.getEndCol() >= 0; // no need to check endrow
452 	}
453 
454 
455 	
456 	protected static AceRange moveMarkerOnInsert(AceRange mr, GwtAceRange range) {
457 		int startRow = range.getStart().getRow();
458 		int startCol = range.getStart().getColumn();
459 		int dRow = range.getEnd().getRow() - startRow;
460 		int dCol = range.getEnd().getColumn() - startCol;
461 		
462 		if (dRow==0 && dCol==0) {
463 			return null;
464 		}
465 		
466 		if (range.getStart().getRow() > mr.getEndRow()) {
467 			return null;
468 		}
469 		
470 		boolean aboveMarkerStart = startRow < mr.getStartRow();
471 		boolean beforeMarkerStartOnRow = startRow == mr.getStartRow() && startCol < mr.getStartCol(); // < or <=
472 		boolean aboveMarkerEnd = startRow < mr.getEndRow();
473 		boolean beforeMarkerEndOnRow = startRow == mr.getEndRow() && startCol <= mr.getEndCol();	 // < or <=
474 		
475 		int row1 = mr.getStartRow();
476 		int col1 = mr.getStartCol();
477 		if (aboveMarkerStart) {
478 			row1 += dRow;
479 		}
480 		else if (beforeMarkerStartOnRow) {
481 			row1 += dRow;
482 			col1 += dCol;
483 		}
484 		
485 		int row2 = mr.getEndRow();
486 		int col2 = mr.getEndCol();
487 		if (aboveMarkerEnd) {
488 			row2 += dRow;
489 		}
490 		else if (beforeMarkerEndOnRow) {
491 			row2 += dRow;
492 			col2 += dCol;
493 		}
494 		
495 		return new AceRange(row1, col1, row2, col2);
496 	}
497 	
498 	protected static AceRange moveMarkerOnRemove(AceRange mr, GwtAceRange range) {
499 		int[] p1 = overlapping(range, mr.getStartRow(), mr.getStartCol());
500 		boolean changed = false;
501 		if (p1 == null) {
502 			p1 = new int[]{mr.getStartRow(), mr.getStartCol()};
503 		}
504 		else {
505 			changed = true;
506 		}
507 		
508 		int[] p2 = overlapping(range, mr.getEndRow(), mr.getEndCol());
509 		if (p2 == null) {
510 			p2 = new int[]{mr.getEndRow(), mr.getEndCol()};
511 		}
512 		else {
513 			changed = true;
514 		}
515 		
516 		return changed ? new AceRange(p1[0], p1[1], p2[0], p2[1]) : null;
517 	}
518 	
519 	protected static int[] overlapping(GwtAceRange range, int row, int col) {
520 		GwtAcePosition start = range.getStart();
521 		
522 		if (start.getRow() > row || (start.getRow() == row && start.getColumn() >= col)) {
523 			return null;
524 		}
525 		
526 		GwtAcePosition end = range.getEnd();
527 		
528 		if (end.getRow() < row) {
529 			int dRow = end.getRow() - start.getRow();
530 			return new int[] {row-dRow, col};
531 		}
532 		if (end.getRow() == row && end.getColumn() < col) {
533 			int dRow = end.getRow() - start.getRow();
534 			int dCol = end.getColumn() - start.getColumn();
535 			return new int[] {row-dRow, col-dCol};
536 		}
537 		return new int[] {start.getRow(), start.getColumn()};
538 	}
539 	
540 	protected void removeMarkers(Set<MarkerInEditor> removed) {
541 		for (MarkerInEditor cm : removed) {
542 			editor.removeMarker(cm.clientId);
543 			markersInEditor.remove(cm.marker.getMarkerId());
544 		}
545 	}
546 	
547 	protected void updateMarkers(Set<MarkerInEditor> moved) {
548 		for (MarkerInEditor cm : moved) {
549 			editor.removeMarker(cm.clientId);
550 			AceMarker m = cm.marker;
551 			cm.clientId = editor.addMarker(convertRange(m.getRange()), m.getCssClass(), m.getType().toString(), m.isInFront());
552 		}
553 		
554 	}
555 
556 	public String getText() {
557 		return text;
558 	}
559 
560 	public void setPropertyReadOnly(boolean propertyReadOnly) {
561 		if (!isInitialized()) {
562 			return;
563 		}
564 		this.propertyReadOnly = propertyReadOnly;
565 		editor.setReadOnly(this.readOnly || this.propertyReadOnly);
566 	}
567 
568 	public void setReadOnly(boolean readOnly) {
569 		if (!isInitialized()) {
570 			return;
571 		}
572 		this.readOnly = readOnly;
573 		editor.setReadOnly(this.readOnly || this.propertyReadOnly);
574 	}
575 
576 	protected static AceRange convertSelection(GwtAceSelection selection) {
577 		GwtAcePosition start = selection.getRange().getStart();
578 		GwtAcePosition end = selection.getRange().getEnd();
579 		if (selection.isBackwards()) {
580 			return new AceRange(end.getRow(), end.getColumn(), start.getRow(),
581 					start.getColumn());
582 		} else {
583 			return new AceRange(start.getRow(), start.getColumn(),
584 					end.getRow(), end.getColumn());
585 		}
586 
587 	}
588 
589 	public AceRange getSelection() {
590 		return selection;
591 	}
592 
593 	@Override
594 	public void onFocus(GwtAceEvent e) {
595 		if (focused) {
596 			return;
597 		}
598 		focused = true;
599 		if (focusChangeListener != null) {
600 			focusChangeListener.focusChanged(true);
601 		}
602 	}
603 
604 	@Override
605 	public void onBlur(GwtAceEvent e) {
606 		if (!focused) {
607 			return;
608 		}
609 		focused = false;
610 		if (focusChangeListener != null) {
611 			focusChangeListener.focusChanged(false);
612 		}
613 	}
614 
615 	@Override
616 	public void onChangeSelection(GwtAceEvent e) {
617 		selectionChanged();
618 	}
619 
620 	@Override
621 	public void onChangeCursor(GwtAceEvent e) {
622 		selectionChanged();
623 	}
624 
625 	protected void selectionChanged() {
626 		if (ignoreEditorEvents) {
627 			return;
628 		}
629 		AceRange sel = convertSelection(editor.getSelection());
630 		if (!sel.equals(selection)) {
631 			selection = sel;
632 			for (SelectionChangeListener li : selChangeListeners) {
633 				li.selectionChanged();
634 			}
635 		}
636 	}
637 	
638 	public void setUseWorker(boolean use) {
639 		if (!isInitialized()) {
640 			return;
641 		}
642 		editor.setUseWorker(use);
643 	}
644 	
645 	@Override
646 	public void setFocus(boolean focused) {
647 		super.setFocus(focused);
648 		if (focused) {
649 			editor.focus();
650 		}
651 		else {
652 			editor.blur();
653 		}
654 		// Waiting for the event from editor to update 'focused'.
655 	}
656 
657 	public boolean isFocused() {
658 		return focused;
659 	}
660 	
661 	protected GwtAceRange convertRange(AceRange r) {
662 		int r1 = r.getStartRow();
663 		int c1 = r.getStartCol();
664 		int r2 = r.getEndRow();
665 		int c2 = r.getEndCol();
666 		boolean backwards = r1 > r2 || (r1 == r2 && c1 > c2);
667 		if (backwards) {
668 			return GwtAceRange.create(r2, c2, r1, c1);
669 		} else {
670 			return GwtAceRange.create(r1, c1, r2, c2);
671 		}
672 	}
673 
674 	protected Map<String, AceMarker> getMarkers() {
675 		HashMap<String, AceMarker> markers = new HashMap<String, AceMarker>();
676 		for (MarkerInEditor cm : markersInEditor.values()) {
677 			markers.put(cm.marker.getMarkerId(), cm.marker);
678 		}
679 		return markers;
680 	}
681 
682 	public void resize() {
683 		if (editor!=null) {
684 			editor.resize();
685 		}
686 	}
687 	
688 	public AceDoc getDoc() {
689 		if (doc==null) {
690 			doc = new AceDoc(getText(), getMarkers(), getRowAnnotations(), getMarkerAnnotations());
691 		}
692 		return doc;
693 	}
694 
695 	public void scrollToRow(int row) {
696 		editor.scrollToRow(row);
697 	}
698 	
699 	protected Set<MarkerAnnotation> getMarkerAnnotations() {
700 		return markerAnnotations;
701 	}
702 
703 	protected Set<RowAnnotation> getRowAnnotations() {
704 		return rowAnnotations;
705 	}
706 
707 	public void setDoc(AceDoc doc) {
708 		if (doc.equals(this.doc)) {
709 			return;
710 		}
711 		
712 		setText(doc.getText());
713 		
714 		// Too much work is done in the case there
715 		// are no markers or annotations, which is probably most of the time...
716 		// TODO: optimize
717 		
718 		setMarkers(doc.getMarkers());
719 		setAnnotations(doc.getMarkerAnnotations(), doc.getRowAnnotations());
720 		this.doc = doc;
721 	}
722 
723 	public int[] getCursorCoords() {
724 		JsArrayInteger cc = editor.getCursorCoords();
725 		return new int[] {cc.get(0), cc.get(1)};
726 	}
727 	
728 	public int addInvisibleMarker(AceRange range) {
729 		int id = ++latestInvisibleMarkerId;
730 		invisibleMarkers.put(id, range);
731 		return id;
732 	}
733 	
734 	public void removeInvisibleMarker(int id) {
735 		invisibleMarkers.remove(id);
736 	}
737 	
738 	public AceRange getInvisibleMarker(int id) {
739 		return invisibleMarkers.get(id);
740 	}
741 	
742 	public void setTextAndAdjust(String text) {
743 		if (this.text.equals(text)) {
744 			return;
745 		}
746 		
747 		HashMap<String, AceMarker> newMarkers = adjustMarkersOnTextChange(this.text, text);
748 		setText(text);
749 		if (newMarkers!=null) {
750 			setMarkers(newMarkers);
751 		}
752 	}
753 
754 	protected HashMap<String, AceMarker> adjustMarkersOnTextChange(String text1, String text2) {
755 		Map<String, AceMarker> ms = getMarkers();
756 		if (ms.isEmpty()) {
757 			return null;
758 		}
759 		HashMap<String, AceMarker> newMarkers = new HashMap<String, AceMarker>();
760 		Adjuster adjuster = new Adjuster(text1, text2);
761 		boolean adjusted = false;
762 		for (Entry<String, AceMarker> e : ms.entrySet()) {
763 			if (e.getValue().getOnChange()==OnTextChange.ADJUST) {
764 				AceMarker m1 = e.getValue();
765 				AceMarker m2 = m1.withNewPosition(adjuster.adjust(m1.getRange()));
766 				newMarkers.put(e.getKey(), m2);
767 				adjusted = true;
768 			}
769 			else {
770 				newMarkers.put(e.getKey(), e.getValue());
771 			}
772 		}
773 		if (!adjusted) {
774 			return null;
775 		}
776 		return newMarkers;
777 	}
778 
779 	public void removeContentsOfInvisibleMarker(int imId) {
780 		AceRange r = getInvisibleMarker(imId);
781 		if (r==null || r.isZeroLength()) {
782 			return;
783 		}
784 		String newText = Util.replaceContents(r, text, "");
785 		setTextAndAdjust(newText);
786 	}
787 }