1 package org.vaadin.aceeditor;
2
3 import java.lang.reflect.Method;
4 import java.util.Collections;
5 import java.util.Set;
6 import java.util.logging.Level;
7 import java.util.logging.Logger;
8
9 import org.vaadin.aceeditor.client.AceAnnotation;
10 import org.vaadin.aceeditor.client.AceAnnotation.MarkerAnnotation;
11 import org.vaadin.aceeditor.client.AceAnnotation.RowAnnotation;
12 import org.vaadin.aceeditor.client.AceDoc;
13 import org.vaadin.aceeditor.client.AceEditorClientRpc;
14 import org.vaadin.aceeditor.client.AceEditorServerRpc;
15 import org.vaadin.aceeditor.client.AceEditorState;
16 import org.vaadin.aceeditor.client.AceMarker;
17 import org.vaadin.aceeditor.client.AceMarker.OnTextChange;
18 import org.vaadin.aceeditor.client.AceMarker.Type;
19 import org.vaadin.aceeditor.client.AceRange;
20 import org.vaadin.aceeditor.client.TransportDiff;
21 import org.vaadin.aceeditor.client.TransportDoc.TransportRange;
22 import org.vaadin.aceeditor.client.Util;
23
24 import com.vaadin.annotations.JavaScript;
25 import com.vaadin.annotations.StyleSheet;
26 import com.vaadin.event.FieldEvents.BlurEvent;
27 import com.vaadin.event.FieldEvents.BlurListener;
28 import com.vaadin.v7.event.FieldEvents.BlurNotifier;
29 import com.vaadin.event.FieldEvents.FocusEvent;
30 import com.vaadin.event.FieldEvents.FocusListener;
31 import com.vaadin.v7.event.FieldEvents.FocusNotifier;
32 import com.vaadin.v7.event.FieldEvents.TextChangeEvent;
33 import com.vaadin.v7.event.FieldEvents.TextChangeListener;
34 import com.vaadin.v7.event.FieldEvents.TextChangeNotifier;
35 import com.vaadin.v7.ui.AbstractField;
36 import com.vaadin.v7.ui.AbstractTextField;
37 import com.vaadin.v7.ui.AbstractTextField.TextChangeEventMode;
38 import com.vaadin.util.ReflectTools;
39
40
41
42
43
44
45 @SuppressWarnings("serial")
46 @JavaScript({
47 "client/js/ace/ace.js",
48 "client/js/ace/ext-searchbox.js",
49 "client/js/diff_match_patch.js" })
50 @StyleSheet("client/css/ace-gwt.css")
51 public class AceEditor extends AbstractField<String> implements BlurNotifier,
52 FocusNotifier, TextChangeNotifier {
53
54 public static class DiffEvent extends Event {
55 public static String EVENT_ID = "aceeditor-diff";
56 private final ServerSideDocDiff diff;
57
58 public DiffEvent(AceEditor ed, ServerSideDocDiff diff) {
59 super(ed);
60 this.diff = diff;
61 }
62
63 public ServerSideDocDiff getDiff() {
64 return diff;
65 }
66 }
67
68 public interface DiffListener {
69 public static final Method diffMethod = ReflectTools.findMethod(
70 DiffListener.class, "diff", DiffEvent.class);
71
72 public void diff(DiffEvent e);
73 }
74
75 public static class SelectionChangeEvent extends Event {
76 public static String EVENT_ID = "aceeditor-selection";
77 private final TextRange selection;
78
79 public SelectionChangeEvent(AceEditor ed) {
80 super(ed);
81 this.selection = ed.getSelection();
82 }
83
84 public TextRange getSelection() {
85 return selection;
86 }
87 }
88
89 public interface SelectionChangeListener {
90 public static final Method selectionChangedMethod = ReflectTools
91 .findMethod(SelectionChangeListener.class, "selectionChanged",
92 SelectionChangeEvent.class);
93
94 public void selectionChanged(SelectionChangeEvent e);
95 }
96
97 public static class TextChangeEventImpl extends TextChangeEvent {
98 private final TextRange selection;
99 private final String text;
100
101 private TextChangeEventImpl(final AceEditor ace, String text,
102 AceRange selection) {
103 super(ace);
104 this.text = text;
105 this.selection = ace.getSelection();
106 }
107
108 @Override
109 public AbstractTextField getComponent() {
110 return (AbstractTextField) super.getComponent();
111 }
112
113 @Override
114 public int getCursorPosition() {
115 return selection.getEnd();
116 }
117
118 @Override
119 public String getText() {
120 return text;
121 }
122 }
123
124 private static final String DEFAULT_ACE_PATH = "http://d1n0x3qji82z53.cloudfront.net/src-min-noconflict";
125
126 private AceDoc doc = new AceDoc();
127
128 private boolean isFiringTextChangeEvent;
129
130 private boolean latestFocus = false;
131 private long latestMarkerId = 0L;
132
133 private static final Logger logger = Logger.getLogger(AceEditor.class.getName());
134
135 private boolean onRoundtrip = false;
136
137 private AceEditorServerRpc rpc = new AceEditorServerRpc() {
138 @Override
139 public void changed(TransportDiff diff, TransportRange selection,
140 boolean focused) {
141 clientChanged(diff, selection, focused);
142 }
143
144 @Override
145 public void changedDelayed(TransportDiff diff,
146 TransportRange selection, boolean focused) {
147 clientChanged(diff, selection, focused);
148 }
149 };
150
151 private TextRange selection = new TextRange("", 0, 0, 0, 0);
152
153 private Integer[] selectionToClient = null;
154 private AceDoc shadow = new AceDoc();
155
156 {
157 logger.setLevel(Level.WARNING);
158 }
159
160 public AceEditor() {
161 super();
162 setWidth("300px");
163 setHeight("200px");
164
165 setModePath(DEFAULT_ACE_PATH);
166 setThemePath(DEFAULT_ACE_PATH);
167 setWorkerPath(DEFAULT_ACE_PATH);
168
169 registerRpc(rpc);
170 }
171
172 public void addDiffListener(DiffListener listener) {
173 addListener(DiffEvent.EVENT_ID, DiffEvent.class, listener,
174 DiffListener.diffMethod);
175 }
176
177 @Override
178 public void addFocusListener(FocusListener listener) {
179 addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
180 FocusListener.focusMethod);
181 getState().listenToFocusChanges = true;
182 }
183
184 @Override
185 public void addBlurListener(BlurListener listener) {
186 addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
187 BlurListener.blurMethod);
188 getState().listenToFocusChanges = true;
189 }
190
191 @Deprecated
192 public void addListener(BlurListener listener) {
193 addBlurListener(listener);
194 }
195
196 @Deprecated
197 public void addListener(FocusListener listener) {
198 addFocusListener(listener);
199 }
200
201 @Deprecated
202 public void addListener(TextChangeListener listener) {
203 addTextChangeListener(listener);
204 }
205
206
207
208
209
210
211
212
213 public String addMarker(AceMarker marker) {
214 doc = doc.withAdditionalMarker(marker);
215 markAsDirty();
216 return marker.getMarkerId();
217 }
218
219
220
221
222
223
224
225
226
227
228
229
230 public String addMarker(AceRange range, String cssClass, Type type,
231 boolean inFront, OnTextChange onChange) {
232 return addMarker(new AceMarker(newMarkerId(), range, cssClass, type,
233 inFront, onChange));
234 }
235
236 public void addMarkerAnnotation(AceAnnotation ann, AceMarker marker) {
237 addMarkerAnnotation(ann, marker.getMarkerId());
238 }
239
240 public void addMarkerAnnotation(AceAnnotation ann, String markerId) {
241 doc = doc.withAdditionalMarkerAnnotation(new MarkerAnnotation(markerId,
242 ann));
243 markAsDirty();
244 }
245
246 public void addRowAnnotation(AceAnnotation ann, int row) {
247 doc = doc.withAdditionalRowAnnotation(new RowAnnotation(row, ann));
248 markAsDirty();
249 }
250
251 public void addSelectionChangeListener(SelectionChangeListener listener) {
252 addListener(SelectionChangeEvent.EVENT_ID, SelectionChangeEvent.class,
253 listener, SelectionChangeListener.selectionChangedMethod);
254 getState().listenToSelectionChanges = true;
255 }
256
257 @Override
258 public void addTextChangeListener(TextChangeListener listener) {
259 addListener(TextChangeListener.EVENT_ID, TextChangeEvent.class,
260 listener, TextChangeListener.EVENT_METHOD);
261 }
262
263 @Override
264 public void beforeClientResponse(boolean initial) {
265 super.beforeClientResponse(initial);
266 if (initial) {
267 getState().initialValue = doc.asTransport();
268 shadow = doc;
269 } else if (onRoundtrip) {
270 ServerSideDocDiff diff = ServerSideDocDiff.diff(shadow, doc);
271 shadow = doc;
272 TransportDiff td = diff.asTransport();
273 getRpcProxy(AceEditorClientRpc.class).diff(td);
274
275 onRoundtrip = false;
276 } else if (true ) {
277 getRpcProxy(AceEditorClientRpc.class).changedOnServer();
278 }
279
280 if (selectionToClient != null) {
281
282 if (selectionToClient.length == 2) {
283 AceRange r = AceRange.fromPositions(selectionToClient[0],
284 selectionToClient[1], doc.getText());
285 getState().selection = r.asTransport();
286 }
287
288 else if (selectionToClient.length == 4) {
289 TransportRange tr = new TransportRange(selectionToClient[0],
290 selectionToClient[1], selectionToClient[2],
291 selectionToClient[3]);
292 getState().selection = tr;
293 }
294 selectionToClient = null;
295 }
296 }
297
298 public void clearMarkerAnnotations() {
299 Set<MarkerAnnotation> manns = Collections.emptySet();
300 doc = doc.withMarkerAnnotations(manns);
301 markAsDirty();
302 }
303
304 public void clearMarkers() {
305 doc = doc.withoutMarkers();
306 markAsDirty();
307 }
308
309 public void clearRowAnnotations() {
310 Set<RowAnnotation> ranns = Collections.emptySet();
311 doc = doc.withRowAnnotations(ranns);
312 markAsDirty();
313 }
314
315 public int getCursorPosition() {
316 return selection.getEnd();
317 }
318
319 public AceDoc getDoc() {
320 return doc;
321 }
322
323 public TextRange getSelection() {
324 return selection;
325 }
326
327 @Override
328 public Class<? extends String> getType() {
329 return String.class;
330 }
331
332 public void removeDiffListener(DiffListener listener) {
333 removeListener(DiffEvent.EVENT_ID, DiffEvent.class, listener);
334 }
335
336 @Override
337 public void removeFocusListener(FocusListener listener) {
338 removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
339 getState().listenToFocusChanges =
340 !getListeners(FocusEvent.class).isEmpty() ||
341 !getListeners(BlurEvent.class).isEmpty();
342 }
343
344 @Override
345 public void removeBlurListener(BlurListener listener) {
346 removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
347 getState().listenToFocusChanges =
348 !getListeners(FocusEvent.class).isEmpty() ||
349 !getListeners(BlurEvent.class).isEmpty();
350 }
351
352 @Deprecated
353 public void removeListener(BlurListener listener) {
354 removeBlurListener(listener);
355 }
356
357 @Deprecated
358 public void removeListener(FocusListener listener) {
359 removeFocusListener(listener);
360 }
361
362 @Deprecated
363 public void removeListener(TextChangeListener listener) {
364 removeTextChangeListener(listener);
365 }
366
367 public void removeMarker(AceMarker marker) {
368 removeMarker(marker.getMarkerId());
369 }
370
371 public void removeMarker(String markerId) {
372 doc = doc.withoutMarker(markerId);
373 markAsDirty();
374 }
375
376 public void removeSelectionChangeListener(SelectionChangeListener listener) {
377 removeListener(SelectionChangeEvent.EVENT_ID,
378 SelectionChangeEvent.class, listener);
379 getState().listenToSelectionChanges = !getListeners(
380 SelectionChangeEvent.class).isEmpty();
381 }
382
383 @Override
384 public void removeTextChangeListener(TextChangeListener listener) {
385 removeListener(TextChangeListener.EVENT_ID, TextChangeEvent.class,
386 listener);
387 }
388
389 public void setBasePath(String path) {
390 setAceConfig("basePath", path);
391 }
392
393
394
395
396
397
398
399 public void setCursorPosition(int pos) {
400 setSelection(pos, pos);
401 }
402
403
404
405
406
407
408
409
410
411 public void setCursorRowCol(int row, int col) {
412 setSelectionRowCol(row, col, row, col);
413 }
414
415 public void setDoc(AceDoc doc) {
416 if (this.doc.equals(doc)) {
417 return;
418 }
419 this.doc = doc;
420 boolean wasReadOnly = isReadOnly();
421 setReadOnly(false);
422 setValue(doc.getText());
423 setReadOnly(wasReadOnly);
424 markAsDirty();
425 }
426
427 public void setMode(AceMode mode) {
428 getState().mode = mode.toString();
429 }
430
431 public void setMode(String mode) {
432 getState().mode = mode;
433 }
434
435 public void setModePath(String path) {
436 setAceConfig("modePath", path);
437 }
438
439
440
441
442
443
444
445
446
447
448 public void setSelection(int start, int end) {
449 setSelectionToClient(new Integer[] { start, end });
450 setInternalSelection(new TextRange(getInternalValue(), start, end));
451 }
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468 public void setSelectionRowCol(int startRow, int startCol, int endRow,
469 int endCol) {
470 setSelectionToClient(new Integer[] { startRow, startCol, endRow, endCol });
471 setInternalSelection(new TextRange(doc.getText(), startRow, startCol,
472 endRow, endCol));
473 }
474
475
476
477
478
479
480
481
482
483 public void setTextChangeEventMode(TextChangeEventMode inputEventMode) {
484 getState().changeMode = inputEventMode.toString();
485 }
486
487
488
489
490
491
492
493
494
495
496 public void setTextChangeTimeout(int timeoutMs) {
497 getState().changeTimeout = timeoutMs;
498
499 }
500
501
502
503
504
505 public void scrollToRow(int row) {
506 getState().scrollToRow = row;
507 }
508
509
510
511
512
513 public void scrollToPosition(int pos) {
514 int[] rowcol = Util.lineColFromCursorPos(getInternalValue(), pos, 0);
515 scrollToRow(rowcol[0]);
516 }
517
518 public void setTheme(AceTheme theme) {
519 getState().theme = theme.toString();
520 }
521
522 public void setTheme(String theme) {
523 getState().theme = theme;
524 }
525
526 public void setThemePath(String path) {
527 setAceConfig("themePath", path);
528 }
529
530 public void setUseWorker(boolean useWorker) {
531 getState().useWorker = useWorker;
532 }
533
534 public void setWordWrap(boolean ww) {
535 getState().wordwrap = ww;
536 }
537
538 public void setShowGutter(boolean showGutter) {
539 getState().showGutter = showGutter;
540 }
541
542 public boolean isShowGutter() {
543 return getState(false).showGutter;
544 }
545
546 public void setShowPrintMargin(boolean showPrintMargin) {
547 getState().showPrintMargin = showPrintMargin;
548 }
549
550 public boolean isShowPrintMargin() {
551 return getState(false).showPrintMargin;
552 }
553
554 public void setHighlightActiveLine(boolean highlightActiveLine) {
555 getState().highlightActiveLine = highlightActiveLine;
556 }
557
558 public boolean isHighlightActiveLine() {
559 return getState(false).highlightActiveLine;
560 }
561
562 public void setWorkerPath(String path) {
563 setAceConfig("workerPath", path);
564 }
565
566 protected void clientChanged(TransportDiff diff, TransportRange selection,
567 boolean focused) {
568 diffFromClient(diff);
569 selectionFromClient(selection);
570 if (latestFocus != focused) {
571 latestFocus = focused;
572 if (focused) {
573 fireFocus();
574 } else {
575 fireBlur();
576 }
577 }
578
579 clearStateFromServerToClient();
580 }
581
582
583
584
585
586
587 private void clearStateFromServerToClient() {
588 getState().selection = null;
589 getState().scrollToRow = -1;
590 }
591
592 @Override
593 protected AceEditorState getState() {
594 return (AceEditorState) super.getState();
595 }
596
597 @Override
598 protected AceEditorState getState(boolean markAsDirty) {
599 return (AceEditorState) super.getState(markAsDirty);
600 }
601
602 @Override
603 protected void setInternalValue(String newValue) {
604 super.setInternalValue(newValue);
605 doc = doc.withText(newValue);
606 }
607
608 private void diffFromClient(TransportDiff d) {
609 String previousText = doc.getText();
610 ServerSideDocDiff diff = ServerSideDocDiff.fromTransportDiff(d);
611 shadow = diff.applyTo(shadow);
612 doc = diff.applyTo(doc);
613 if (!TextUtils.equals(doc.getText(), previousText)) {
614 setValue(doc.getText(), true);
615 fireTextChangeEvent();
616 }
617 if (!diff.isIdentity()) {
618 fireDiff(diff);
619 }
620 onRoundtrip = true;
621 markAsDirty();
622 }
623
624 private void fireBlur() {
625 fireEvent(new BlurEvent(this));
626 }
627
628 private void fireDiff(ServerSideDocDiff diff) {
629 fireEvent(new DiffEvent(this, diff));
630 }
631
632 private void fireFocus() {
633 fireEvent(new FocusEvent(this));
634 }
635
636 private void fireSelectionChanged() {
637 fireEvent(new SelectionChangeEvent(this));
638 }
639
640 private void fireTextChangeEvent() {
641 if (!isFiringTextChangeEvent) {
642 isFiringTextChangeEvent = true;
643 try {
644 fireEvent(new TextChangeEventImpl(this, getInternalValue(),
645 selection));
646 } finally {
647 isFiringTextChangeEvent = false;
648 }
649 }
650 }
651
652 private String newMarkerId() {
653 return "m" + (++latestMarkerId);
654 }
655
656 private void selectionFromClient(TransportRange sel) {
657 setInternalSelection(new TextRange(doc.getText(),
658 AceRange.fromTransport(sel)));
659 fireSelectionChanged();
660 }
661
662 private void setAceConfig(String key, String value) {
663 getState().config.put(key, value);
664 }
665
666 private void setInternalSelection(TextRange selection) {
667 this.selection = selection;
668 getState().selection = selection.asTransport();
669 }
670
671 private void setSelectionToClient(Integer[] stc) {
672 selectionToClient = stc;
673 markAsDirty();
674 }
675
676 }