1 /*
2  * AceEditor.java
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 package org.rstudio.studio.client.workbench.views.source.editors.text;
16 
17 import com.google.gwt.animation.client.AnimationScheduler;
18 import com.google.gwt.core.client.JavaScriptObject;
19 import com.google.gwt.core.client.JsArray;
20 import com.google.gwt.core.client.JsArrayString;
21 import com.google.gwt.core.client.Scheduler;
22 import com.google.gwt.dom.client.Document;
23 import com.google.gwt.dom.client.Element;
24 import com.google.gwt.dom.client.NativeEvent;
25 import com.google.gwt.dom.client.PreElement;
26 import com.google.gwt.dom.client.Style.Unit;
27 import com.google.gwt.event.dom.client.BlurHandler;
28 import com.google.gwt.event.dom.client.ClickHandler;
29 import com.google.gwt.event.dom.client.ContextMenuHandler;
30 import com.google.gwt.event.dom.client.FocusEvent;
31 import com.google.gwt.event.dom.client.FocusHandler;
32 import com.google.gwt.event.dom.client.KeyCodes;
33 import com.google.gwt.event.dom.client.KeyDownHandler;
34 import com.google.gwt.event.dom.client.KeyPressHandler;
35 import com.google.gwt.event.dom.client.KeyUpHandler;
36 import com.google.gwt.event.dom.client.MouseDownHandler;
37 import com.google.gwt.event.dom.client.MouseMoveHandler;
38 import com.google.gwt.event.dom.client.MouseUpHandler;
39 import com.google.gwt.event.logical.shared.AttachEvent;
40 import com.google.gwt.event.logical.shared.ValueChangeEvent;
41 import com.google.gwt.event.logical.shared.ValueChangeHandler;
42 import com.google.gwt.event.shared.GwtEvent;
43 import com.google.gwt.event.shared.HandlerManager;
44 import com.google.gwt.event.shared.HandlerRegistration;
45 
46 import java.util.function.BiPredicate;
47 import java.util.function.Consumer;
48 
49 import com.google.gwt.user.client.Command;
50 import com.google.gwt.user.client.Timer;
51 import com.google.gwt.user.client.ui.RootPanel;
52 import com.google.gwt.user.client.ui.Widget;
53 import com.google.inject.Inject;
54 import org.rstudio.core.client.AceSupport;
55 import org.rstudio.core.client.CommandWithArg;
56 import org.rstudio.core.client.ElementIds;
57 import org.rstudio.core.client.ExternalJavaScriptLoader;
58 import org.rstudio.core.client.KeyboardTracker;
59 import org.rstudio.core.client.Rectangle;
60 import org.rstudio.core.client.StringUtil;
61 import org.rstudio.core.client.command.KeySequence;
62 import org.rstudio.core.client.dom.DomUtils;
63 import org.rstudio.core.client.dom.WindowEx;
64 import org.rstudio.core.client.js.JsMap;
65 import org.rstudio.core.client.js.JsObject;
66 import org.rstudio.core.client.js.JsUtil;
67 import org.rstudio.core.client.patch.TextChange;
68 import org.rstudio.core.client.regex.Match;
69 import org.rstudio.core.client.regex.Pattern;
70 import org.rstudio.core.client.resources.StaticDataResource;
71 import org.rstudio.core.client.widget.DynamicIFrame;
72 import org.rstudio.studio.client.RStudioGinjector;
73 import org.rstudio.studio.client.application.Desktop;
74 import org.rstudio.studio.client.application.events.EventBus;
75 import org.rstudio.studio.client.common.SuperDevMode;
76 import org.rstudio.studio.client.common.codetools.CodeToolsServerOperations;
77 import org.rstudio.studio.client.common.debugging.model.Breakpoint;
78 import org.rstudio.studio.client.common.filetypes.DocumentMode;
79 import org.rstudio.studio.client.common.filetypes.DocumentMode.Mode;
80 import org.rstudio.studio.client.events.EditEvent;
81 import org.rstudio.studio.client.common.filetypes.TextFileType;
82 import org.rstudio.studio.client.server.Void;
83 import org.rstudio.studio.client.workbench.MainWindowObject;
84 import org.rstudio.studio.client.workbench.commands.Commands;
85 import org.rstudio.studio.client.workbench.model.ChangeTracker;
86 import org.rstudio.studio.client.workbench.model.EventBasedChangeTracker;
87 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs;
88 import org.rstudio.studio.client.workbench.snippets.SnippetHelper;
89 import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager;
90 import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager.InitCompletionFilter;
91 import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionPopupPanel;
92 import org.rstudio.studio.client.workbench.views.console.shell.assist.DelegatingCompletionManager;
93 import org.rstudio.studio.client.workbench.views.console.shell.assist.MarkdownCompletionManager;
94 import org.rstudio.studio.client.workbench.views.console.shell.assist.NullCompletionManager;
95 import org.rstudio.studio.client.workbench.views.console.shell.assist.PythonCompletionManager;
96 import org.rstudio.studio.client.workbench.views.console.shell.assist.RCompletionManager;
97 import org.rstudio.studio.client.workbench.views.console.shell.assist.SqlCompletionManager;
98 import org.rstudio.studio.client.workbench.views.console.shell.assist.StanCompletionManager;
99 import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
100 import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorPosition;
101 import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorSelection;
102 import org.rstudio.studio.client.workbench.views.output.lint.DiagnosticsBackgroundPopup;
103 import org.rstudio.studio.client.workbench.views.output.lint.model.AceAnnotation;
104 import org.rstudio.studio.client.workbench.views.output.lint.model.LintItem;
105 import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditorWidget.TabKeyMode;
106 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceAfterCommandExecutedEvent;
107 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceBackgroundHighlighter;
108 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceClickEvent.Handler;
109 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceCommand;
110 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceCommandManager;
111 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceEditorBackgroundLinkHighlighter;
112 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceEditorCommandEvent;
113 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceEditorNative;
114 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceFold;
115 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceInputEditorPosition;
116 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceKeyboardActivityEvent;
117 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceResources;
118 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Anchor;
119 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AnchoredRange;
120 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.CodeModel;
121 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.EditSession;
122 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.FoldingRules;
123 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.KeyboardHandler;
124 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.LineWidget;
125 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Marker;
126 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Mode.InsertChunkInfo;
127 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
128 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
129 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Renderer;
130 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Renderer.ScreenCoordinates;
131 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Search;
132 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Selection;
133 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Token;
134 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.TokenCursor;
135 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.TokenIterator;
136 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.CharClassifier;
137 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.TokenPredicate;
138 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.WordIterable;
139 import org.rstudio.studio.client.workbench.views.source.editors.text.cpp.CppCompletionContext;
140 import org.rstudio.studio.client.workbench.views.source.editors.text.cpp.CppCompletionManager;
141 import org.rstudio.studio.client.workbench.views.source.editors.text.events.AceSelectionChangedEvent;
142 import org.rstudio.studio.client.workbench.views.source.editors.text.events.ActiveScopeChangedEvent;
143 import org.rstudio.studio.client.workbench.views.source.editors.text.events.BreakpointMoveEvent;
144 import org.rstudio.studio.client.workbench.views.source.editors.text.events.BreakpointSetEvent;
145 import org.rstudio.studio.client.workbench.views.source.editors.text.events.CommandClickEvent;
146 import org.rstudio.studio.client.workbench.views.source.editors.text.events.CursorChangedEvent;
147 import org.rstudio.studio.client.workbench.views.source.editors.text.events.DocumentChangedEvent;
148 import org.rstudio.studio.client.workbench.views.source.editors.text.events.EditorModeChangedEvent;
149 import org.rstudio.studio.client.workbench.views.source.editors.text.events.FindRequestedEvent;
150 import org.rstudio.studio.client.workbench.views.source.editors.text.events.FoldChangeEvent;
151 import org.rstudio.studio.client.workbench.views.source.editors.text.events.LineWidgetsChangedEvent;
152 import org.rstudio.studio.client.workbench.views.source.editors.text.events.PasteEvent;
153 import org.rstudio.studio.client.workbench.views.source.editors.text.events.RenderFinishedEvent;
154 import org.rstudio.studio.client.workbench.views.source.editors.text.events.ScopeTreeReadyEvent;
155 import org.rstudio.studio.client.workbench.views.source.editors.text.events.UndoRedoEvent;
156 import org.rstudio.studio.client.workbench.views.source.editors.text.rmd.ChunkDefinition;
157 import org.rstudio.studio.client.workbench.views.source.editors.text.rmd.TextEditingTargetNotebook;
158 import org.rstudio.studio.client.workbench.views.source.editors.text.spelling.SpellingDoc;
159 import org.rstudio.studio.client.workbench.views.source.events.CollabEditStartParams;
160 import org.rstudio.studio.client.workbench.views.source.events.RecordNavigationPositionEvent;
161 import org.rstudio.studio.client.workbench.views.source.events.SaveFileEvent;
162 import org.rstudio.studio.client.workbench.views.source.events.ScrollYEvent;
163 import org.rstudio.studio.client.workbench.views.source.model.DirtyState;
164 import org.rstudio.studio.client.workbench.views.source.model.RnwCompletionContext;
165 import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
166 
167 import java.util.ArrayList;
168 import java.util.Iterator;
169 import java.util.List;
170 import java.util.Map;
171 
172 public class AceEditor implements DocDisplay,
173                                   InputEditorDisplay,
174                                   NavigableSourceEditor
175 {
176    public enum NewLineMode
177    {
178       Windows("windows"),
179       Unix("unix"),
180       Auto("auto");
181 
NewLineMode(String type)182       NewLineMode(String type)
183       {
184          this.type = type;
185       }
186 
getType()187       public String getType()
188       {
189          return type;
190       }
191 
192       private String type;
193    }
194 
195    public enum EditorBehavior
196    {
197       // Behave like a typical (top-level) editor
198       AceBehaviorDefault,
199 
200       // Behave like an embedded (code chunk) editor; used in embedded Ace
201       // instances in visual mode
202       AceBehaviorEmbedded,
203 
204       // Behave like the R console
205       AceBehaviorConsole
206    }
207 
208    private class Filter implements InitCompletionFilter
209    {
shouldComplete(NativeEvent event)210       public boolean shouldComplete(NativeEvent event)
211       {
212          // Never complete if there's an active selection.
213          Range range = getSession().getSelection().getRange();
214          if (!range.isEmpty())
215             return false;
216 
217          // If the user explicitly requested an auto-complete by pressing 'ctrl-space' instead of 'tab', always attempt auto-complete.
218          if (event != null && event.getKeyCode() != KeyCodes.KEY_TAB)
219             return true;
220 
221          // Tab was pressed. Don't attempt auto-complete if the user opted out of tab completions.
222          if (!userPrefs_.tabCompletion().getValue() || userPrefs_.tabKeyMoveFocus().getValue())
223             return false;
224 
225          // If the user opted in to multi-line tab completion, there is no case where we don't attempt auto-complete.
226          if (userPrefs_.tabMultilineCompletion().getValue())
227             return true;
228 
229          // Otherwise, don't complete if we're at the start of the line...
230          int col = range.getStart().getColumn();
231          if (col == 0)
232             return false;
233 
234          // ... or if there is nothing but whitespace between the start of the line and the cursor.
235          String line = getSession().getLine(range.getStart().getRow());
236          return line.substring(0, col).trim().length() != 0;
237 
238       }
239    }
240 
241    private class AnchoredSelectionImpl implements AnchoredSelection
242    {
AnchoredSelectionImpl(Anchor start, Anchor end)243       private AnchoredSelectionImpl(Anchor start, Anchor end)
244       {
245          start_ = start;
246          end_ = end;
247       }
248 
getValue()249       public String getValue()
250       {
251          return getSession().getTextRange(getRange());
252       }
253 
apply()254       public void apply()
255       {
256          getSession().getSelection().setSelectionRange(
257             getRange());
258       }
259 
getRange()260       public Range getRange()
261       {
262          return Range.fromPoints(start_.getPosition(), end_.getPosition());
263       }
264 
detach()265       public void detach()
266       {
267          start_.detach();
268          end_.detach();
269       }
270 
271       private final Anchor start_;
272       private final Anchor end_;
273    }
274 
275    private class AceEditorChangeTracker extends EventBasedChangeTracker<Void>
276    {
AceEditorChangeTracker()277       private AceEditorChangeTracker()
278       {
279          super(AceEditor.this);
280          AceEditor.this.addFoldChangeHandler(event -> changed_ = true);
281          AceEditor.this.addLineWidgetsChangedHandler(event -> changed_ = true);
282       }
283 
284       @Override
fork()285       public ChangeTracker fork()
286       {
287          AceEditorChangeTracker forked = new AceEditorChangeTracker();
288          forked.changed_ = changed_;
289          return forked;
290       }
291    }
292 
preload()293    public static void preload()
294    {
295       load(null);
296    }
297 
load(final Command command)298    public static void load(final Command command)
299    {
300       aceLoader_.addCallback(() ->
301             aceSupportLoader_.addCallback(() ->
302                   extLanguageToolsLoader_.addCallback(() ->
303                         vimLoader_.addCallback(() ->
304                               emacsLoader_.addCallback(() ->
305                               {
306                                  AceSupport.initialize();
307 
308                                  if (command != null)
309                                     command.execute();
310                               })
311                         )
312                   )
313             )
314       );
315    }
316 
getEditor(Element el)317    public static final native AceEditor getEditor(Element el)
318    /*-{
319       for (; el != null; el = el.parentElement)
320          if (el.$RStudioAceEditor != null)
321             return el.$RStudioAceEditor;
322    }-*/;
323 
attachToWidget(Element el, AceEditor editor)324    private static final native void attachToWidget(Element el, AceEditor editor)
325    /*-{
326       el.$RStudioAceEditor = editor;
327    }-*/;
328 
detachFromWidget(Element el)329    private static final native void detachFromWidget(Element el)
330    /*-{
331       el.$RStudioAceEditor = null;
332    }-*/;
333 
334    @Inject
AceEditor()335    public AceEditor()
336    {
337       widget_ = new AceEditorWidget();
338       snippets_ = new SnippetHelper(this);
339       monitor_ = new AceEditorMonitor(this);
340       editorEventListeners_ = new ArrayList<>();
341       mixins_ = new AceEditorMixins(this);
342       editLines_ = new AceEditorEditLinesHelper(this);
343       behavior_ = EditorBehavior.AceBehaviorDefault;
344 
345       completionManager_ = new NullCompletionManager();
346       diagnosticsBgPopup_ = new DiagnosticsBackgroundPopup(this);
347 
348       RStudioGinjector.INSTANCE.injectMembers(this);
349 
350       backgroundTokenizer_ = new BackgroundTokenizer(this);
351       vim_ = new Vim(this);
352       bgLinkHighlighter_ = new AceEditorBackgroundLinkHighlighter(this);
353       bgChunkHighlighter_ = new AceBackgroundHighlighter(this);
354 
355       widget_.addValueChangeHandler(evt ->
356       {
357          if (!valueChangeSuppressed_)
358          {
359             ValueChangeEvent.fire(AceEditor.this, null);
360          }
361       });
362 
363       widget_.addFoldChangeHandler(event -> AceEditor.this.fireEvent(new FoldChangeEvent()));
364 
365       events_.addHandler(EditEvent.TYPE, new EditEvent.Handler()
366       {
367          @Override
368          public void onEdit(EditEvent event)
369          {
370             if (event.isBeforeEdit())
371             {
372                activeEditEventType_ = event.getType();
373             }
374             else
375             {
376                Scheduler.get().scheduleDeferred(() ->
377                {
378                   activeEditEventType_ = EditEvent.TYPE_NONE;
379                });
380             }
381          }
382       });
383 
384       addPasteHandler(event ->
385       {
386          if (completionManager_ != null)
387             completionManager_.onPaste(event);
388 
389          final Position start = getSelectionStart();
390 
391          Scheduler.get().scheduleDeferred(() ->
392          {
393             Range range = Range.fromPoints(start, getSelectionEnd());
394             if (shouldIndentOnPaste())
395                indentPastedRange(range);
396          });
397       });
398 
399       // handle click events
400       addAceClickHandler(event ->
401       {
402          fixVerticalOffsetBug();
403          if (DomUtils.isCommandClick(event.getNativeEvent()))
404          {
405             // eat the event so ace doesn't do anything with it
406             event.preventDefault();
407             event.stopPropagation();
408 
409             // go to function definition
410             fireEvent(new CommandClickEvent(event));
411          }
412          else
413          {
414             // if the focus in the Help pane or another iframe
415             // we need to make sure to get it back
416             WindowEx.get().focus();
417          }
418       });
419 
420       lastCursorChangedTime_ = 0;
421       addCursorChangedHandler(event ->
422       {
423          fixVerticalOffsetBug();
424          clearLineHighlight();
425          lastCursorChangedTime_ = System.currentTimeMillis();
426       });
427 
428       lastModifiedTime_ = 0;
429       addValueChangeHandler(event ->
430       {
431          lastModifiedTime_ = System.currentTimeMillis();
432          clearDebugLineHighlight();
433       });
434 
435       widget_.addAttachHandler(event ->
436       {
437          if (event.isAttached())
438          {
439             attachToWidget(widget_.getElement(), AceEditor.this);
440 
441             // If the ID was set earlier, as is done for the Console's edit field, don't stomp over it
442             if (StringUtil.isNullOrEmpty(widget_.getElement().getId()))
443                ElementIds.assignElementId(widget_, ElementIds.SOURCE_TEXT_EDITOR);
444          }
445          else
446             detachFromWidget(widget_.getElement());
447 
448          if (!event.isAttached())
449          {
450             for (HandlerRegistration handler : editorEventListeners_)
451                handler.removeHandler();
452             editorEventListeners_.clear();
453 
454             if (completionManager_ != null)
455             {
456                completionManager_.detach();
457                completionManager_ = null;
458             }
459 
460             if (s_lastFocusedEditor == AceEditor.this)
461             {
462                s_lastFocusedEditor = null;
463             }
464          }
465       });
466 
467       widget_.addFocusHandler((FocusEvent event) ->
468       {
469          String id = AceEditor.this.getWidget().getElement().getId();
470          MainWindowObject.lastFocusedEditorId().set(id);
471       });
472 
473       addFocusHandler((FocusEvent event) -> s_lastFocusedEditor = this);
474 
475       events_.addHandler(
476             AceEditorCommandEvent.TYPE,
477             event ->
478             {
479                // skip this if this is only for the actively focused Ace instance
480                // (note: in RStudio Server, the Ace Editor instance may become
481                // unfocused when e.g. executing commands from the menu, so we
482                // need to ensure this routes to the most recently focused editor)
483                boolean ignore =
484                      event.getExecutionPolicy() == AceEditorCommandEvent.EXECUTION_POLICY_FOCUSED &&
485                      AceEditor.this != s_lastFocusedEditor;
486 
487                if (ignore)
488                   return;
489 
490                switch (event.getCommand())
491                {
492                case AceEditorCommandEvent.YANK_REGION:                yankRegion();               break;
493                case AceEditorCommandEvent.YANK_BEFORE_CURSOR:         yankBeforeCursor();         break;
494                case AceEditorCommandEvent.YANK_AFTER_CURSOR:          yankAfterCursor();          break;
495                case AceEditorCommandEvent.PASTE_LAST_YANK:            pasteLastYank();            break;
496                case AceEditorCommandEvent.INSERT_ASSIGNMENT_OPERATOR: insertAssignmentOperator(); break;
497                case AceEditorCommandEvent.INSERT_PIPE_OPERATOR:       insertPipeOperator();       break;
498                case AceEditorCommandEvent.JUMP_TO_MATCHING:           jumpToMatching();           break;
499                case AceEditorCommandEvent.SELECT_TO_MATCHING:         selectToMatching();         break;
500                case AceEditorCommandEvent.EXPAND_TO_MATCHING:         expandToMatching();         break;
501                case AceEditorCommandEvent.ADD_CURSOR_ABOVE:           addCursorAbove();           break;
502                case AceEditorCommandEvent.ADD_CURSOR_BELOW:           addCursorBelow();           break;
503                case AceEditorCommandEvent.EDIT_LINES_FROM_START:      editLinesFromStart();       break;
504                case AceEditorCommandEvent.INSERT_SNIPPET:             onInsertSnippet();          break;
505                case AceEditorCommandEvent.MOVE_LINES_UP:              moveLinesUp();              break;
506                case AceEditorCommandEvent.MOVE_LINES_DOWN:            moveLinesDown();            break;
507                case AceEditorCommandEvent.EXPAND_TO_LINE:             expandToLine();             break;
508                case AceEditorCommandEvent.COPY_LINES_DOWN:            copyLinesDown();            break;
509                case AceEditorCommandEvent.JOIN_LINES:                 joinLines();                break;
510                case AceEditorCommandEvent.REMOVE_LINE:                removeLine();               break;
511                case AceEditorCommandEvent.SPLIT_INTO_LINES:           splitIntoLines();           break;
512                case AceEditorCommandEvent.BLOCK_INDENT:               blockIndent();              break;
513                case AceEditorCommandEvent.BLOCK_OUTDENT:              blockOutdent();             break;
514                case AceEditorCommandEvent.REINDENT:                   reindent();                 break;
515                }
516             });
517    }
518 
yankRegion()519    public void yankRegion()
520    {
521       if (isVimModeOn() && !isVimInInsertMode())
522          return;
523 
524       // no-op if there is no selection
525       String selectionValue = getSelectionValue();
526       if (StringUtil.isNullOrEmpty(selectionValue))
527          return;
528 
529       if (Desktop.hasDesktopFrame() && isEmacsModeOn())
530       {
531          Desktop.getFrame().setClipboardText(selectionValue);
532          replaceSelection("");
533          clearEmacsMark();
534       }
535       else
536       {
537          yankedText_ = getSelectionValue();
538          replaceSelection("");
539       }
540    }
541 
yankBeforeCursor()542    public void yankBeforeCursor()
543    {
544       if (isVimModeOn() && !isVimInInsertMode())
545          return;
546 
547       Position cursorPos = getCursorPosition();
548       Range yankRange = Range.fromPoints(
549             Position.create(cursorPos.getRow(), 0),
550             cursorPos);
551 
552       if (Desktop.hasDesktopFrame() && isEmacsModeOn())
553       {
554          String text = getTextForRange(yankRange);
555          Desktop.getFrame().setClipboardText(StringUtil.notNull(text));
556          replaceRange(yankRange, "");
557          clearEmacsMark();
558       }
559       else
560       {
561          setSelectionRange(yankRange);
562          yankedText_ = getSelectionValue();
563          replaceSelection("");
564       }
565    }
566 
yankAfterCursor()567    public void yankAfterCursor()
568    {
569       if (isVimModeOn() && !isVimInInsertMode())
570          return;
571 
572       Position cursorPos = getCursorPosition();
573       Range yankRange = null;
574       String line = getLine(cursorPos.getRow());
575       int lineLength = line.length();
576 
577       // if the cursor is already at the end of the line
578       // (allowing for trailing whitespace), then eat the
579       // newline as well; otherwise, just eat to end of line
580       String rest = line.substring(cursorPos.getColumn());
581       if (rest.trim().isEmpty())
582       {
583          yankRange = Range.fromPoints(
584                cursorPos,
585                Position.create(cursorPos.getRow() + 1, 0));
586       }
587       else
588       {
589          yankRange = Range.fromPoints(
590                cursorPos,
591                Position.create(cursorPos.getRow(), lineLength));
592       }
593 
594       if ((Desktop.hasDesktopFrame()) && isEmacsModeOn())
595       {
596          String text = getTextForRange(yankRange);
597          Desktop.getFrame().setClipboardText(StringUtil.notNull(text));
598          replaceRange(yankRange, "");
599          clearEmacsMark();
600       }
601       else
602       {
603          setSelectionRange(yankRange);
604          yankedText_ = getSelectionValue();
605          replaceSelection("");
606       }
607    }
608 
pasteLastYank()609    public void pasteLastYank()
610    {
611       if (isVimModeOn() && !isVimInInsertMode())
612          return;
613 
614       if (Desktop.hasDesktopFrame() && isEmacsModeOn())
615       {
616          Desktop.getFrame().getClipboardText((String text) ->
617          {
618             replaceSelection(text);
619             setCursorPosition(getSelectionEnd());
620          });
621       }
622       else
623       {
624          if (yankedText_ == null)
625             return;
626 
627          replaceSelection(yankedText_);
628          setCursorPosition(getSelectionEnd());
629       }
630    }
631 
insertAssignmentOperator()632    public void insertAssignmentOperator()
633    {
634       if (DocumentMode.isCursorInRMode(this))
635          insertAssignmentOperatorImpl("<-");
636       else
637          insertAssignmentOperatorImpl("=");
638    }
639 
640    @SuppressWarnings("deprecation")
insertAssignmentOperatorImpl(String op)641    private void insertAssignmentOperatorImpl(String op)
642    {
643       boolean hasWhitespaceBefore =
644             Character.isSpace(getCharacterBeforeCursor()) ||
645             (!hasSelection() && getCursorPosition().getColumn() == 0);
646 
647       String insertion = hasWhitespaceBefore
648             ? op + " "
649             : " " + op + " ";
650 
651       insertCode(insertion, false);
652    }
653 
654    @SuppressWarnings("deprecation")
insertPipeOperator()655    public void insertPipeOperator()
656    {
657       boolean hasWhitespaceBefore =
658             Character.isSpace(getCharacterBeforeCursor()) ||
659             (!hasSelection() && getCursorPosition().getColumn() == 0);
660 
661       // Use magrittr style pipes unless user has opted into new native pipe syntax in R 4.1+
662       String pipe = userPrefs_.insertNativePipeOperator().getValue() ? "|>" : "%>%";
663 
664       if (hasWhitespaceBefore)
665          insertCode(pipe + " ", false);
666       else
667          insertCode(" " + pipe + " ", false);
668    }
669 
shouldIndentOnPaste()670    private boolean shouldIndentOnPaste()
671    {
672       if (fileType_ == null || !fileType_.canAutoIndent())
673          return false;
674 
675       // if the user has requested reindent on paste, then we reindent
676       boolean indentPref = RStudioGinjector.INSTANCE.getUserPrefs().reindentOnPaste().getValue();
677       if (indentPref)
678          return true;
679 
680       // if the user has explicitly executed a paste with indent command, we reindent
681       if (activeEditEventType_ == EditEvent.TYPE_PASTE_WITH_INDENT)
682          return true;
683 
684       // finally, infer based on whether shift key is down
685       return keyboard_.isShiftKeyDown();
686    }
687 
indentPastedRange(Range range)688    private void indentPastedRange(Range range)
689    {
690       String firstLinePrefix = getSession().getTextRange(
691             Range.fromPoints(Position.create(range.getStart().getRow(), 0),
692                              range.getStart()));
693 
694       if (firstLinePrefix.trim().length() != 0)
695       {
696          Position newStart = Position.create(range.getStart().getRow() + 1, 0);
697          if (newStart.compareTo(range.getEnd()) >= 0)
698             return;
699 
700          range = Range.fromPoints(newStart, range.getEnd());
701       }
702 
703       getSession().reindent(range);
704    }
705 
getCommandManager()706    public AceCommandManager getCommandManager()
707    {
708       return getWidget().getEditor().getCommandManager();
709    }
710 
setEditorCommandBinding(String id, List<KeySequence> keys)711    public void setEditorCommandBinding(String id, List<KeySequence> keys)
712    {
713       getWidget().getEditor().getCommandManager().rebindCommand(id, keys);
714    }
715 
resetCommands()716    public void resetCommands()
717    {
718       AceCommandManager manager = AceCommandManager.create();
719       JsObject commands = manager.getCommands();
720       for (String key : JsUtil.asIterable(commands.keys()))
721       {
722          AceCommand command = commands.getObject(key);
723          getWidget().getEditor().getCommandManager().addCommand(command);
724       }
725    }
726 
727    @Inject
initialize(CodeToolsServerOperations server, UserPrefs uiPrefs, CollabEditor collab, KeyboardTracker keyboard, Commands commands, EventBus events)728    void initialize(CodeToolsServerOperations server,
729                    UserPrefs uiPrefs,
730                    CollabEditor collab,
731                    KeyboardTracker keyboard,
732                    Commands commands,
733                    EventBus events)
734    {
735       server_ = server;
736       userPrefs_ = uiPrefs;
737       collab_ = collab;
738       keyboard_ = keyboard;
739       commands_ = commands;
740       events_ = events;
741    }
742 
getFileType()743    public TextFileType getFileType()
744    {
745       return fileType_;
746    }
747 
setFileType(TextFileType fileType)748    public void setFileType(TextFileType fileType)
749    {
750       setFileType(fileType, false);
751    }
752 
setFileType(TextFileType fileType, boolean suppressCompletion)753    public void setFileType(TextFileType fileType, boolean suppressCompletion)
754    {
755       fileType_ = fileType;
756       updateLanguage(suppressCompletion);
757    }
758 
setFileType(TextFileType fileType, CompletionManager completionManager)759    public void setFileType(TextFileType fileType,
760                            CompletionManager completionManager)
761    {
762       fileType_ = fileType;
763       updateLanguage(completionManager, null);
764    }
765 
setEditorBehavior(EditorBehavior behavior)766    public void setEditorBehavior(EditorBehavior behavior)
767    {
768       behavior_ = behavior;
769    }
770 
771    @Override
setRnwCompletionContext(RnwCompletionContext rnwContext)772    public void setRnwCompletionContext(RnwCompletionContext rnwContext)
773    {
774       rnwContext_ = rnwContext;
775    }
776 
getActiveRnwCompletionContext()777    private RnwCompletionContext getActiveRnwCompletionContext()
778    {
779       // In embedded chunk editors, allow chunk completion behavior even in
780       // non-chunk file types (so e.g., completion of chunk options can happen
781       // in R code chunks)
782       if (behavior_ == EditorBehavior.AceBehaviorEmbedded)
783       {
784          return rnwContext_;
785       }
786 
787       // In other types of editors, restrict this behavior to file types that
788       // can execute chunks
789       if (!fileType_.canExecuteChunks())
790       {
791          return null;
792       }
793 
794       return rnwContext_;
795    }
796 
797    @Override
setCppCompletionContext(CppCompletionContext cppContext)798    public void setCppCompletionContext(CppCompletionContext cppContext)
799    {
800       cppContext_ = cppContext;
801    }
802 
803    @Override
setRCompletionContext(CompletionContext context)804    public void setRCompletionContext(CompletionContext context)
805    {
806       context_ = context;
807    }
808 
updateLanguage(boolean suppressCompletion)809    private void updateLanguage(boolean suppressCompletion)
810    {
811       if (fileType_ == null)
812          return;
813 
814       CompletionManager completionManager;
815 
816       if (!suppressCompletion && fileType_.getEditorLanguage().useRCompletion())
817       {
818          // GWT throws an exception if we bind Ace using 'AceEditor.this' below
819          // so work around that by just creating a final reference and use that
820          final AceEditor editor = this;
821 
822          completionManager = new DelegatingCompletionManager(this, context_)
823          {
824             @Override
825             protected void initialize(Map<Mode, CompletionManager> managers)
826             {
827                // R completion manager
828                if (fileType_.isR() || fileType_.isRmd() || fileType_.isCpp() ||
829                    fileType_.isRhtml() || fileType_.isRnw())
830                {
831                   managers.put(DocumentMode.Mode.R, new RCompletionManager(
832                         editor,
833                         editor,
834                         new CompletionPopupPanel(),
835                         server_,
836                         new Filter(),
837                         context_,
838                         getActiveRnwCompletionContext(),
839                         editor,
840                         behavior_));
841                }
842 
843                // Markdown completion manager
844                if (fileType_.isMarkdown() || fileType_.isRmd())
845                {
846                   managers.put(DocumentMode.Mode.MARKDOWN, MarkdownCompletionManager.create(
847                         editor,
848                         new CompletionPopupPanel(),
849                         server_,
850                         context_));
851                }
852 
853                // Python completion manager
854                if (fileType_.isPython() || fileType_.isRmd())
855                {
856                   managers.put(DocumentMode.Mode.PYTHON, PythonCompletionManager.create(
857                         editor,
858                         new CompletionPopupPanel(),
859                         server_,
860                         context_));
861                }
862 
863                // C++ completion manager
864                if (fileType_.isC())
865                {
866                   managers.put(DocumentMode.Mode.C_CPP, new CppCompletionManager(
867                         editor,
868                         new Filter(),
869                         cppContext_));
870                }
871 
872                // SQL completion manager
873                if (fileType_.isSql() || fileType_.isRmd())
874                {
875                   managers.put(DocumentMode.Mode.SQL, SqlCompletionManager.create(
876                         editor,
877                         new CompletionPopupPanel(),
878                         server_,
879                         context_));
880                }
881 
882                // Stan completion manager
883                if (fileType_.isStan() || fileType_.isRmd())
884                {
885                   managers.put(DocumentMode.Mode.STAN, StanCompletionManager.create(
886                         editor,
887                         new CompletionPopupPanel(),
888                         server_,
889                         context_));
890                }
891             }
892          };
893       }
894       else
895       {
896          completionManager = new NullCompletionManager();
897       }
898 
899       ScopeTreeManager scopeTreeManager = null;
900 
901       if (fileType_.isStan())
902       {
903          scopeTreeManager = new StanScopeTreeManager(this);
904       }
905 
906       updateLanguage(completionManager, scopeTreeManager);
907    }
908 
updateLanguage(CompletionManager completionManager, ScopeTreeManager scopeTreeManager)909    private void updateLanguage(CompletionManager completionManager,
910                                ScopeTreeManager scopeTreeManager)
911    {
912       clearLint();
913       if (fileType_ == null)
914          return;
915 
916       if (completionManager_ != null)
917       {
918          completionManager_.detach();
919          completionManager_ = null;
920       }
921 
922       if (scopes_ != null)
923       {
924          scopes_.detach();
925          scopes_ = null;
926       }
927 
928       completionManager_ = completionManager;
929       scopes_ = scopeTreeManager;
930 
931       updateKeyboardHandlers();
932       syncCompletionPrefs();
933       syncDiagnosticsPrefs();
934 
935       snippets_.ensureSnippetsLoaded();
936       getSession().setEditorMode(
937             fileType_.getEditorLanguage().getParserName(),
938             false);
939 
940       handlers_.fireEvent(new EditorModeChangedEvent(getModeId()));
941 
942       syncWrapLimit();
943    }
944 
945    @Override
syncCompletionPrefs()946    public void syncCompletionPrefs()
947    {
948       if (fileType_ == null)
949          return;
950 
951       boolean enabled = fileType_.getEditorLanguage().useAceLanguageTools();
952       boolean live = userPrefs_.codeCompletionOther().getValue() ==
953                                        UserPrefs.CODE_COMPLETION_OTHER_ALWAYS;
954       int characterThreshold = userPrefs_.codeCompletionCharacters().getValue();
955       int delay = userPrefs_.codeCompletionDelay().getValue();
956 
957       widget_.getEditor().setCompletionOptions(
958             enabled,
959             userPrefs_.enableSnippets().getValue(),
960             live,
961             characterThreshold,
962             delay);
963 
964    }
965 
966    @Override
syncDiagnosticsPrefs()967    public void syncDiagnosticsPrefs()
968    {
969       if (fileType_ == null)
970          return;
971 
972       boolean useWorker = userPrefs_.showDiagnosticsOther().getValue() &&
973             fileType_.getEditorLanguage().useAceLanguageTools();
974 
975       getSession().setUseWorker(useWorker);
976       getSession().setWorkerTimeout(
977             userPrefs_.backgroundDiagnosticsDelayMs().getValue());
978    }
979 
syncWrapLimit()980    private void syncWrapLimit()
981    {
982       // bail if there is no filetype yet
983       if (fileType_ == null)
984          return;
985 
986       // We originally observed that large word-wrapped documents
987       // would cause Chrome on Linux to freeze (bug #3207), eventually
988       // running of of memory. Running the profiler indicated that the
989       // time was being spent inside wrap width calculations in Ace.
990       // Looking at the Ace bug database there were other wrapping problems
991       // that were solvable by changing the wrap mode from "free" to a
992       // specific range. So, for Chrome on Linux we started syncing the
993       // wrap limit to the user-specified margin width.
994       //
995       // Unfortunately, this caused another problem whereby the ace
996       // horizontal scrollbar would show up over the top of the editor
997       // and the console (bug #3428). We tried reverting the fix to
998       // #3207 and sure enough this solved the horizontal scrollbar
999       // problem _and_ no longer froze Chrome (so perhaps there was a
1000       // bug in Chrome).
1001       //
1002       // In the meantime we added user pref to soft wrap to the margin
1003       // column, essentially allowing users to opt-in to the behavior
1004       // we used to fix the bug. So the net is:
1005       //
1006       // (1) To fix the horizontal scrollbar problem we reverted
1007       //     the wrap mode behavior we added from Chrome (under the
1008       //     assumption that the issue has been fixed in Chrome)
1009       //
1010       // (2) We added another check for desktop mode (since we saw
1011       //     the problem in both Chrome and Safari) to prevent the
1012       //     application of the problematic wrap mode setting.
1013       //
1014       // Perhaps there is an ace issue here as well, so the next time
1015       // we sync to Ace tip we should see if we can bring back the
1016       // wrapping option for Chrome (note the repro for this
1017       // is having a soft-wrapping source document in the editor that
1018       // exceed the horizontal threshold)
1019 
1020       // NOTE: we no longer do this at all since we observed the
1021       // scrollbar problem on desktop as well
1022    }
1023 
updateKeyboardHandlers()1024    private void updateKeyboardHandlers()
1025    {
1026       // clear out existing editor handlers (they will be refreshed if necessary)
1027       for (HandlerRegistration handler : editorEventListeners_)
1028          if (handler != null)
1029             handler.removeHandler();
1030       editorEventListeners_.clear();
1031 
1032       // save and restore Vim marks as they can be lost when refreshing
1033       // the keyboard handlers. this is necessary as keyboard handlers are
1034       // regenerated on each document save, and clearing the Vim handler will
1035       // clear any local Vim state.
1036       JsMap<Position> marks = JsMap.create().cast();
1037       if (useVimMode_)
1038          marks = widget_.getEditor().getMarks();
1039 
1040       // create a keyboard previewer for our special hooks
1041       AceKeyboardPreviewer previewer = new AceKeyboardPreviewer(completionManager_);
1042 
1043       // set default key handler
1044       if (useVimMode_)
1045          widget_.getEditor().setKeyboardHandler(KeyboardHandler.vim());
1046       else if (useEmacsKeybindings_)
1047          widget_.getEditor().setKeyboardHandler(KeyboardHandler.emacs());
1048       else
1049          widget_.getEditor().setKeyboardHandler(null);
1050 
1051       // add the previewer
1052       widget_.getEditor().addKeyboardHandler(previewer.getKeyboardHandler());
1053 
1054       // Listen for command execution
1055       editorEventListeners_.add(AceEditorNative.addEventListener(
1056             widget_.getEditor().getCommandManager(),
1057             "afterExec",
1058             (CommandWithArg<JavaScriptObject>) event -> events_.fireEvent(new AceAfterCommandExecutedEvent(event))));
1059 
1060       // Listen for keyboard activity
1061       editorEventListeners_.add(AceEditorNative.addEventListener(
1062             widget_.getEditor(),
1063             "keyboardActivity",
1064             (CommandWithArg<JavaScriptObject>) event -> events_.fireEvent(new AceKeyboardActivityEvent(event))));
1065 
1066       if (useVimMode_)
1067          widget_.getEditor().setMarks(marks);
1068    }
1069 
getCode()1070    public String getCode()
1071    {
1072       return getSession().getValue();
1073    }
1074 
setCode(String code, boolean preserveCursorPosition)1075    public void setCode(String code, boolean preserveCursorPosition)
1076    {
1077       // Calling setCode("", false) while the editor contains multiple lines of
1078       // content causes bug 2928: Flickering console when typing. Empirically,
1079       // first setting code to a single line of content and then clearing it,
1080       // seems to correct this problem.
1081       if (StringUtil.isNullOrEmpty(code))
1082          doSetCode(" ", preserveCursorPosition);
1083 
1084       doSetCode(code, preserveCursorPosition);
1085    }
1086 
doSetCode(String code, boolean preserveCursorPosition)1087    private void doSetCode(String code, boolean preserveCursorPosition)
1088    {
1089       // Filter out Escape characters that might have snuck in from an old
1090       // bug in 0.95. We can choose to remove this when 0.95 ships, hopefully
1091       // any documents that would be affected by this will be gone by then.
1092       code = code.replaceAll("\u001B", "");
1093 
1094       // Normalize newlines -- convert all of '\r', '\r\n', '\n\r' to '\n'.
1095       final String normalizedCode = StringUtil.normalizeNewLines(code);
1096 
1097       final AceEditorNative ed = widget_.getEditor();
1098 
1099       if (preserveCursorPosition)
1100       {
1101          withPreservedCursorPosition(() -> {
1102             // Setting the value directly on the document prevents undo/redo
1103             // stack from being blown away
1104             widget_.getEditor().getSession().getDocument().setValue(normalizedCode);
1105          });
1106       }
1107       else
1108       {
1109          ed.getSession().setValue(normalizedCode);
1110          ed.getSession().getSelection().moveCursorTo(0, 0, false);
1111       }
1112    }
1113 
withPreservedCursorPosition(Runnable runnable)1114    private void withPreservedCursorPosition(Runnable runnable)
1115    {
1116       final AceEditorNative ed = widget_.getEditor();
1117 
1118       final Position cursorPos;
1119       final int scrollTop, scrollLeft;
1120 
1121       cursorPos = ed.getSession().getSelection().getCursor();
1122       scrollTop = ed.getRenderer().getScrollTop();
1123       scrollLeft = ed.getRenderer().getScrollLeft();
1124 
1125       runnable.run();
1126 
1127       ed.getSession().getSelection().moveCursorTo(cursorPos.getRow(),
1128                                                   cursorPos.getColumn(),
1129                                                   false);
1130       scrollToY(scrollTop, 0);
1131       scrollToX(scrollLeft);
1132       Scheduler.get().scheduleDeferred(() ->
1133       {
1134          scrollToY(scrollTop, 0);
1135          scrollToX(scrollLeft);
1136       });
1137    }
1138 
getScrollLeft()1139    public int getScrollLeft()
1140    {
1141       return widget_.getEditor().getRenderer().getScrollLeft();
1142    }
1143 
scrollToX(int x)1144    public void scrollToX(int x)
1145    {
1146       widget_.getEditor().getRenderer().scrollToX(x);
1147    }
1148 
getScrollTop()1149    public int getScrollTop()
1150    {
1151       return widget_.getEditor().getRenderer().getScrollTop();
1152    }
1153 
scrollToY(int y, int animateMs)1154    public void scrollToY(int y, int animateMs)
1155    {
1156       // cancel any existing scroll animation
1157       if (scrollAnimator_ != null)
1158          scrollAnimator_.complete();
1159 
1160       if (animateMs == 0)
1161          widget_.getEditor().getRenderer().scrollToY(y);
1162       else
1163          scrollAnimator_ = new ScrollAnimator(y, animateMs);
1164 
1165       fireEvent(new ScrollYEvent(Position.create(getFirstVisibleRow(), 0)));
1166    }
1167 
insertCode(String code)1168    public void insertCode(String code)
1169    {
1170       insertCode(code, false);
1171    }
1172 
insertCode(String code, boolean blockMode)1173    public void insertCode(String code, boolean blockMode)
1174    {
1175       widget_.getEditor().insert(StringUtil.normalizeNewLines(code));
1176    }
1177 
applyChanges(TextChange[] changes)1178    public void applyChanges(TextChange[] changes)
1179    {
1180       applyChanges(changes, false);
1181    }
1182 
applyChanges(TextChange[] changes, boolean preserveCursorPosition)1183    public void applyChanges(TextChange[] changes, boolean preserveCursorPosition)
1184    {
1185       // special case for a single change that neither adds nor removes
1186       // (identity operation). we don't feed this through the code below
1187       // because a single non-mutating change will result in a selection
1188       // at the beginning of the file
1189       if (changes.length == 1 && changes[0].type == TextChange.Type.Equal)
1190          return;
1191 
1192       // application of changes (will run this below either with or w/o
1193       // preserving the cursor position)
1194       Runnable applyChanges = () -> {
1195          // alias apis
1196          AceEditorNative editor = widget_.getEditor();
1197          EditSession session = editor.getSession();
1198          Selection selection = session.getSelection();
1199          AceCommandManager commandManager = editor.getCommandManager();
1200 
1201          // function to advance the selection
1202          Consumer<Integer> advanceSelection = (Integer charsLeft) -> {
1203             Position startPos = selection.getCursor();
1204             Position newPos = advancePosition(session, startPos, charsLeft);
1205             selection.moveCursorTo(newPos.getRow(), newPos.getColumn(), false);
1206          };
1207 
1208          // if we have at least 1 change then set the cursor location
1209          // to the beginning of the file
1210          if (changes.length > 0)
1211             selection.moveCursorTo(0, 0, false);
1212 
1213          // process changes
1214          for (int i = 0; i<changes.length; i++)
1215          {
1216             // get change and length
1217             TextChange change = changes[i];
1218             int length = change.value.length();
1219 
1220             // insert text (selection will be advanced to the end of the string)
1221             if (change.type == TextChange.Type.Insert)
1222             {
1223                if (change.value.length() > 0)
1224                   commandManager.exec("insertstring", editor, change.value);
1225             }
1226 
1227             // remove text -- we advance past it and then use the "backspace"
1228             // command b/c ace gives nicer undo behavior for this action (compared
1229             // to executing the "del" command)
1230             else if (change.type == TextChange.Type.Delete)
1231             {
1232                Range newRange = selection.getRange();
1233                newRange.setEnd(advancePosition(session, selection.getCursor(), length));
1234                selection.setSelectionRange(newRange);
1235                commandManager.exec("backspace", editor);
1236             }
1237 
1238             // advance selection (unless this is the last change, in which
1239             // case it just represents advancing to the end of the file)
1240             else if (i != (changes.length-1))
1241             {
1242                advanceSelection.accept(length);
1243             }
1244          }
1245       };
1246 
1247       if (preserveCursorPosition)
1248          withPreservedCursorPosition(applyChanges);
1249       else
1250          applyChanges.run();
1251    }
1252 
advancePosition(EditSession session, Position startPos, Integer chars)1253    private static Position advancePosition(EditSession session, Position startPos, Integer chars)
1254    {
1255       // iterate through rows until we've consumed all the chars
1256       int row = startPos.getRow();
1257       int col = startPos.getColumn();
1258       while (row < session.getLength()) {
1259 
1260          // how many chars left in the current column?
1261          String line = session.getLine(row);
1262          // +1 is for the newline
1263          int charsLeftInLine = line.length() + 1 - col;
1264 
1265          // is the number of chars we still need to consume lte
1266          // the number of charsLeft?
1267          if (chars < charsLeftInLine)
1268          {
1269             col = col + chars;
1270             break;
1271          }
1272          else
1273          {
1274             chars -= charsLeftInLine;
1275             col = 0;
1276             row++;
1277          }
1278       }
1279 
1280       return Position.create(row, col);
1281    }
1282 
1283    @Override
positionFromIndex(int index)1284    public Position positionFromIndex(int index)
1285    {
1286       EditSession session = widget_.getEditor().getSession();
1287       return advancePosition(session, Position.create(0,0), index);
1288    }
1289 
1290    @Override
indexFromPosition(Position position)1291    public int indexFromPosition(Position position)
1292    {
1293       EditSession session = widget_.getEditor().getSession();
1294       int index = 0;
1295       int row = 0;
1296       while (row < position.getRow())
1297       {
1298          index += (session.getLine(row).length() + 1); // +1 for newline
1299          row++;
1300       }
1301       index += position.getColumn();
1302       return index;
1303    }
1304 
1305 
getCode(Position start, Position end)1306    public String getCode(Position start, Position end)
1307    {
1308       return getSession().getTextRange(Range.fromPoints(start, end));
1309    }
1310 
1311    @Override
search(String needle, boolean backwards, boolean wrap, boolean caseSensitive, boolean wholeWord, Position start, Range range, boolean regexpMode)1312    public InputEditorSelection search(String needle,
1313                                       boolean backwards,
1314                                       boolean wrap,
1315                                       boolean caseSensitive,
1316                                       boolean wholeWord,
1317                                       Position start,
1318                                       Range range,
1319                                       boolean regexpMode)
1320    {
1321       Search search = Search.create(needle,
1322                                     backwards,
1323                                     wrap,
1324                                     caseSensitive,
1325                                     wholeWord,
1326                                     start,
1327                                     range,
1328                                     regexpMode);
1329 
1330       Range resultRange = search.find(getSession());
1331       if (resultRange != null)
1332       {
1333          return createSelection(resultRange.getStart(), resultRange.getEnd());
1334       }
1335       else
1336       {
1337          return null;
1338       }
1339    }
1340 
1341    @Override
quickAddNext()1342    public void quickAddNext()
1343    {
1344       if (getNativeSelection().isEmpty())
1345       {
1346          getNativeSelection().selectWord();
1347          return;
1348       }
1349 
1350       String needle = getSelectionValue();
1351       Search search = Search.create(
1352             needle,
1353             false,
1354             true,
1355             true,
1356             false,
1357             getCursorPosition(),
1358             null,
1359             false);
1360 
1361       Range range = search.find(getSession());
1362       if (range == null)
1363          return;
1364 
1365       getNativeSelection().addRange(range, false);
1366       centerSelection();
1367    }
1368 
1369    @Override
insertCode(InputEditorPosition position, String content)1370    public void insertCode(InputEditorPosition position, String content)
1371    {
1372       insertCode(selectionToPosition(position), content);
1373    }
1374 
insertCode(Position position, String content)1375    public void insertCode(Position position, String content)
1376    {
1377       getSession().insert(position, content);
1378    }
1379 
1380    @Override
getCode(InputEditorSelection selection)1381    public String getCode(InputEditorSelection selection)
1382    {
1383       return getCode(((AceInputEditorPosition)selection.getStart()).getValue(),
1384                      ((AceInputEditorPosition)selection.getEnd()).getValue());
1385    }
1386 
1387    @Override
getLines()1388    public JsArrayString getLines()
1389    {
1390       return getLines(0, getSession().getLength());
1391    }
1392 
1393    @Override
getLines(int startRow, int endRow)1394    public JsArrayString getLines(int startRow, int endRow)
1395    {
1396       return getSession().getLines(startRow, endRow);
1397    }
1398 
focus()1399    public void focus()
1400    {
1401       widget_.getEditor().focus();
1402    }
1403 
isFocused()1404    public boolean isFocused()
1405    {
1406       return widget_.getEditor().isFocused();
1407    }
1408 
1409 
codeCompletion()1410    public void codeCompletion()
1411    {
1412       completionManager_.codeCompletion();
1413    }
1414 
goToHelp()1415    public void goToHelp()
1416    {
1417       completionManager_.goToHelp();
1418    }
1419 
goToDefinition()1420    public void goToDefinition()
1421    {
1422       completionManager_.goToDefinition();
1423    }
1424 
1425    class PrintIFrame extends DynamicIFrame
1426    {
PrintIFrame(String code, double fontSize)1427       public PrintIFrame(String code, double fontSize)
1428       {
1429          super("Print Frame");
1430          code_ = code;
1431          fontSize_ = fontSize;
1432 
1433          getElement().getStyle().setPosition(com.google.gwt.dom.client.Style.Position.ABSOLUTE);
1434          getElement().getStyle().setLeft(-5000, Unit.PX);
1435       }
1436 
1437       @Override
onFrameLoaded()1438       protected void onFrameLoaded()
1439       {
1440          Document doc = getDocument();
1441          PreElement pre = doc.createPreElement();
1442          pre.setInnerText(code_);
1443          pre.getStyle().setProperty("whiteSpace", "pre-wrap");
1444          pre.getStyle().setFontSize(fontSize_, Unit.PT);
1445          doc.getBody().appendChild(pre);
1446 
1447          getWindow().print();
1448 
1449          // Bug 1224: ace: print from source causes inability to reconnect
1450          // This was caused by the iframe being removed from the document too
1451          // quickly after the print job was sent. As a result, attempting to
1452          // navigate away from the page at any point afterwards would result
1453          // in the error "Document cannot change while printing or in Print
1454          // Preview". The only thing you could do is close the browser tab.
1455          // By inserting a 5-minute delay hopefully Firefox would be done with
1456          // whatever print related operations are important.
1457          Scheduler.get().scheduleFixedDelay(() ->
1458          {
1459             PrintIFrame.this.removeFromParent();
1460             return false;
1461          }, 1000 * 60 * 5);
1462       }
1463 
1464       private final String code_;
1465       private final double fontSize_;
1466    }
1467 
print()1468    public void print()
1469    {
1470       if (Desktop.hasDesktopFrame())
1471       {
1472          // the desktop frame prints the code directly
1473          Desktop.getFrame().printText(StringUtil.notNull(getCode()));
1474       }
1475       else
1476       {
1477          // in server mode, we render the code to an IFrame and then print
1478          // the frame using the browser
1479          PrintIFrame printIFrame = new PrintIFrame(
1480                getCode(),
1481                RStudioGinjector.INSTANCE.getUserPrefs().fontSizePoints().getValue());
1482          RootPanel.get().add(printIFrame);
1483       }
1484    }
1485 
getText()1486    public String getText()
1487    {
1488       return getSession().getLine(
1489             getSession().getSelection().getCursor().getRow());
1490    }
1491 
setText(String string)1492    public void setText(String string)
1493    {
1494       setCode(string, false);
1495       getSession().getSelection().moveCursorFileEnd();
1496    }
1497 
hasSelection()1498    public boolean hasSelection()
1499    {
1500       return !getSession().getSelection().getRange().isEmpty();
1501    }
1502 
getNativeSelection()1503    public final Selection getNativeSelection() {
1504       return widget_.getEditor().getSession().getSelection();
1505    }
1506 
getSelection()1507    public InputEditorSelection getSelection()
1508    {
1509       Range selection = getSession().getSelection().getRange();
1510       return new InputEditorSelection(
1511             new AceInputEditorPosition(getSession(), selection.getStart()),
1512             new AceInputEditorPosition(getSession(), selection.getEnd()));
1513 
1514    }
1515 
getSelectionValue()1516    public String getSelectionValue()
1517    {
1518       return getSession().getTextRange(
1519             getSession().getSelection().getRange());
1520    }
1521 
getSelectionStart()1522    public Position getSelectionStart()
1523    {
1524       return getSession().getSelection().getRange().getStart();
1525    }
1526 
getSelectionEnd()1527    public Position getSelectionEnd()
1528    {
1529       return getSession().getSelection().getRange().getEnd();
1530    }
1531 
1532    @Override
getSelectionRange()1533    public Range getSelectionRange()
1534    {
1535       return Range.fromPoints(getSelectionStart(), getSelectionEnd());
1536    }
1537 
1538    @Override
setSelectionRange(Range range)1539    public void setSelectionRange(Range range)
1540    {
1541       getSession().getSelection().setSelectionRange(range);
1542    }
1543 
setSelectionRanges(JsArray<Range> ranges)1544    public void setSelectionRanges(JsArray<Range> ranges)
1545    {
1546       int n = ranges.length();
1547       if (n == 0)
1548          return;
1549 
1550       if (vim_.isActive())
1551          vim_.exitVisualMode();
1552 
1553       setSelectionRange(ranges.get(0));
1554       for (int i = 1; i < n; i++)
1555          getNativeSelection().addRange(ranges.get(i), false);
1556 
1557       scrollCursorIntoViewIfNecessary();
1558    }
1559 
getLength(int row)1560    public int getLength(int row)
1561    {
1562       return getSession().getDocument().getLine(row).length();
1563    }
1564 
getRowCount()1565    public int getRowCount()
1566    {
1567       return getSession().getDocument().getLength();
1568    }
1569 
1570    @Override
getPixelWidth()1571    public int getPixelWidth()
1572    {
1573       Element[] content = DomUtils.getElementsByClassName(
1574             widget_.getElement(), "ace_content");
1575       if (content.length < 1)
1576          return widget_.getElement().getOffsetWidth();
1577       else
1578          return content[0].getOffsetWidth();
1579    }
1580 
getLine(int row)1581    public String getLine(int row)
1582    {
1583       return getSession().getLine(row);
1584    }
1585 
1586    @Override
getDocumentEnd()1587    public Position getDocumentEnd()
1588    {
1589       int lastRow = getRowCount() - 1;
1590       return Position.create(lastRow, getLength(lastRow));
1591    }
1592 
1593    @Override
setInsertMatching(boolean value)1594    public void setInsertMatching(boolean value)
1595    {
1596       widget_.getEditor().setInsertMatching(value);
1597    }
1598 
1599    @Override
setSurroundSelectionPref(String value)1600    public void setSurroundSelectionPref(String value)
1601    {
1602       widget_.getEditor().setSurroundSelectionPref(value);
1603    }
1604 
1605    @Override
createSelection(Position pos1, Position pos2)1606    public InputEditorSelection createSelection(Position pos1, Position pos2)
1607    {
1608       return new InputEditorSelection(
1609             new AceInputEditorPosition(getSession(), pos1),
1610             new AceInputEditorPosition(getSession(), pos2));
1611    }
1612 
1613    @Override
selectionToPosition(InputEditorPosition pos)1614    public Position selectionToPosition(InputEditorPosition pos)
1615    {
1616       // HACK: This cast is gross, InputEditorPosition should just become
1617       // AceInputEditorPosition
1618       return Position.create((Integer) pos.getLine(), pos.getPosition());
1619    }
1620 
1621    @Override
createInputEditorPosition(Position pos)1622    public InputEditorPosition createInputEditorPosition(Position pos)
1623    {
1624       return new AceInputEditorPosition(getSession(), pos);
1625    }
1626 
1627    @Override
getWords(TokenPredicate tokenPredicate, CharClassifier charClassifier, Position start, Position end)1628    public Iterable<Range> getWords(TokenPredicate tokenPredicate,
1629                                    CharClassifier charClassifier,
1630                                    Position start,
1631                                    Position end)
1632    {
1633       return new WordIterable(getSession(),
1634                               tokenPredicate,
1635                               charClassifier,
1636                               start,
1637                               end);
1638    }
1639 
1640    @Override
getSpellingDoc()1641    public SpellingDoc getSpellingDoc()
1642    {
1643       // detach anchors on dispose
1644       ArrayList<org.rstudio.studio.client.workbench.views.source.editors.text.ace.Anchor>
1645         anchors = new ArrayList<>();
1646 
1647       return new SpellingDoc() {
1648 
1649 
1650          @Override
1651          public Iterable<WordRange> getWords(int start, int end)
1652          {
1653             return new Iterable<WordRange>() {
1654 
1655                @Override
1656                public Iterator<WordRange> iterator()
1657                {
1658                   // get underlying iterator
1659                   Iterator<Range> ranges = AceEditor.this.getWords(
1660                         fileType_.getSpellCheckTokenPredicate(),
1661                         fileType_.getCharPredicate(),
1662                         positionFromIndex(start),
1663                         end != -1 ? positionFromIndex(end) : null).iterator();
1664 
1665                   // shim it on to spelling doc iterator
1666                   return new Iterator<WordRange>() {
1667 
1668                      @Override
1669                      public boolean hasNext()
1670                      {
1671                         return ranges.hasNext();
1672                      }
1673 
1674                      @Override
1675                      public WordRange next()
1676                      {
1677                         Range range = ranges.next();
1678                         return new WordRange(
1679                            indexFromPosition(range.getStart()),
1680                            indexFromPosition(range.getEnd())
1681                         );
1682                      }
1683                   };
1684                }
1685             };
1686          }
1687 
1688          @Override
1689          public Anchor createAnchor(int position)
1690          {
1691             // create ace anchor
1692             org.rstudio.studio.client.workbench.views.source.editors.text.ace.Anchor anchor =
1693               AceEditor.this.createAnchor(positionFromIndex(position));
1694 
1695             // track anchors for disposal
1696             anchors.add(anchor);
1697 
1698             // shim on spelling doc anchor
1699             return new Anchor() {
1700                @Override
1701                public int getPosition()
1702                {
1703                   return indexFromPosition(anchor.getPosition());
1704                }
1705             };
1706          }
1707 
1708          @Override
1709          public boolean shouldCheck(WordRange wordRange)
1710          {
1711             Range range = toAceRange(wordRange);
1712 
1713             // Don't spellcheck yaml
1714             Scope s = getScopeAtPosition(range.getStart());
1715             if (s != null && s.isYaml())
1716                return false;
1717 
1718             // This will capture all braced text in a way that the
1719             // highlight rules don't and shouldn't.
1720             String word = getTextForRange(range);
1721             int start = range.getStart().getColumn();
1722             int end = start + word.length();
1723             String line = getLine(range.getStart().getRow());
1724             Pattern p =  Pattern.create("\\{[^\\{\\}]*" + word + "[^\\{\\}]*\\}");
1725             Match m = p.match(line, 0);
1726             while (m != null)
1727             {
1728                // ensure that the match is the specific word we're looking
1729                // at to fix edge cases such as {asdf}asdf
1730                if (m.getIndex() < start &&
1731                    (m.getIndex() + m.getValue().length()) > end)
1732                   return false;
1733 
1734                m = m.nextMatch();
1735             }
1736 
1737             return true;
1738          }
1739 
1740          @Override
1741          public void setSelection(WordRange wordRange)
1742          {
1743             setSelectionRange(toAceRange(wordRange));
1744          }
1745 
1746          @Override
1747          public String getText(WordRange wordRange)
1748          {
1749             return getTextForRange(toAceRange(wordRange));
1750          }
1751 
1752          @Override
1753          public int getCursorPosition()
1754          {
1755             return indexFromPosition(AceEditor.this.getCursorPosition());
1756          }
1757 
1758          @Override
1759          public void replaceSelection(String text)
1760          {
1761             AceEditor.this.replaceSelection(text);
1762          }
1763 
1764          @Override
1765          public int getSelectionStart()
1766          {
1767             return indexFromPosition(AceEditor.this.getSelectionStart());
1768          }
1769 
1770          @Override
1771          public int getSelectionEnd()
1772          {
1773             return indexFromPosition(AceEditor.this.getSelectionEnd());
1774          }
1775 
1776          @Override
1777          public Rectangle getCursorBounds()
1778          {
1779             return AceEditor.this.getCursorBounds();
1780          }
1781 
1782          @Override
1783          public void moveCursorNearTop()
1784          {
1785             AceEditor.this.moveCursorNearTop();
1786          }
1787 
1788          private Range toAceRange(WordRange wordRange)
1789          {
1790             Position startPos = positionFromIndex(wordRange.start);
1791             Position endPos = positionFromIndex(wordRange.end);
1792             return Range.create(
1793                startPos.getRow(),
1794                startPos.getColumn(),
1795                endPos.getRow(),
1796                endPos.getColumn()
1797             );
1798          }
1799 
1800          @Override
1801          public void dispose()
1802          {
1803             anchors.forEach((anchor) -> { anchor.detach(); });
1804          }
1805       };
1806    }
1807 
1808 
1809 
1810 
1811    @Override
1812    public String getTextForRange(Range range)
1813    {
1814       return getSession().getTextRange(range);
1815    }
1816 
1817    @Override
1818    public Anchor createAnchor(Position pos)
1819    {
1820       return Anchor.createAnchor(getSession().getDocument(),
1821                                  pos.getRow(),
1822                                  pos.getColumn());
1823    }
1824 
1825    private void fixVerticalOffsetBug()
1826    {
1827       widget_.getEditor().getRenderer().fixVerticalOffsetBug();
1828    }
1829 
1830    @Override
1831    public String debug_getDocumentDump()
1832    {
1833       return widget_.getEditor().getSession().getDocument().getDocumentDump();
1834    }
1835 
1836    @Override
1837    public void debug_setSessionValueDirectly(String s)
1838    {
1839       widget_.getEditor().getSession().setValue(s);
1840    }
1841 
1842    public void setSelection(InputEditorSelection selection)
1843    {
1844       AceInputEditorPosition start = (AceInputEditorPosition)selection.getStart();
1845       AceInputEditorPosition end = (AceInputEditorPosition)selection.getEnd();
1846       getSession().getSelection().setSelectionRange(Range.fromPoints(
1847             start.getValue(), end.getValue()));
1848    }
1849 
1850    public Rectangle getCursorBounds()
1851    {
1852       Range range = getSession().getSelection().getRange();
1853       return toScreenCoordinates(range);
1854    }
1855 
1856    public Rectangle toScreenCoordinates(Range range)
1857    {
1858       Renderer renderer = widget_.getEditor().getRenderer();
1859       ScreenCoordinates start = renderer.textToScreenCoordinates(
1860                   range.getStart().getRow(),
1861                   range.getStart().getColumn());
1862       ScreenCoordinates end = renderer.textToScreenCoordinates(
1863                   range.getEnd().getRow(),
1864                   range.getEnd().getColumn());
1865       return new Rectangle(start.getPageX(),
1866                            start.getPageY(),
1867                            end.getPageX() - start.getPageX(),
1868                            renderer.getLineHeight());
1869    }
1870 
1871    public Position toDocumentPosition(ScreenCoordinates coordinates)
1872    {
1873       return widget_.getEditor().getRenderer().screenToTextCoordinates(
1874             coordinates.getPageX(),
1875             coordinates.getPageY());
1876    }
1877 
1878    public Range toDocumentRange(Rectangle rectangle)
1879    {
1880       Renderer renderer = widget_.getEditor().getRenderer();
1881       return Range.fromPoints(
1882             renderer.screenToTextCoordinates(rectangle.getLeft(), rectangle.getTop()),
1883             renderer.screenToTextCoordinates(rectangle.getRight(), rectangle.getBottom()));
1884    }
1885 
1886    @Override
1887    public Rectangle getPositionBounds(Position position)
1888    {
1889       Renderer renderer = widget_.getEditor().getRenderer();
1890       ScreenCoordinates start = renderer.textToScreenCoordinates(
1891             position.getRow(),
1892             position.getColumn());
1893 
1894       return new Rectangle(start.getPageX(), start.getPageY(),
1895                            (int) Math.round(renderer.getCharacterWidth()),
1896                            (int) (renderer.getLineHeight() * 0.8));
1897    }
1898 
1899    @Override
1900    public Rectangle getRangeBounds(Range range)
1901    {
1902       range = Range.toOrientedRange(range);
1903 
1904       Renderer renderer = widget_.getEditor().getRenderer();
1905       if (!range.isMultiLine())
1906       {
1907          ScreenCoordinates start = documentPositionToScreenCoordinates(range.getStart());
1908          ScreenCoordinates end   = documentPositionToScreenCoordinates(range.getEnd());
1909 
1910          int width  = (end.getPageX() - start.getPageX()) + (int) renderer.getCharacterWidth();
1911          int height = (end.getPageY() - start.getPageY()) + (int) renderer.getLineHeight();
1912 
1913          return new Rectangle(start.getPageX(), start.getPageY(), width, height);
1914       }
1915 
1916       Position startPos = range.getStart();
1917       Position endPos   = range.getEnd();
1918       int startRow = startPos.getRow();
1919       int endRow   = endPos.getRow();
1920 
1921       // figure out top left coordinates
1922       ScreenCoordinates topLeft = documentPositionToScreenCoordinates(Position.create(startRow, 0));
1923 
1924       // figure out bottom right coordinates (need to walk rows to figure out longest line)
1925       ScreenCoordinates bottomRight = documentPositionToScreenCoordinates(Position.create(endPos));
1926       for (int row = startRow; row <= endRow; row++)
1927       {
1928          Position rowEndPos = Position.create(row, getLength(row));
1929          ScreenCoordinates coords = documentPositionToScreenCoordinates(rowEndPos);
1930          if (coords.getPageX() > bottomRight.getPageX())
1931             bottomRight = ScreenCoordinates.create(coords.getPageX(), bottomRight.getPageY());
1932       }
1933 
1934       // construct resulting range
1935       int width  = (bottomRight.getPageX() - topLeft.getPageX()) + (int) renderer.getCharacterWidth();
1936       int height = (bottomRight.getPageY() - topLeft.getPageY()) + (int) renderer.getLineHeight();
1937       return new Rectangle(topLeft.getPageX(), topLeft.getPageY(), width, height);
1938    }
1939 
1940    @Override
1941    public Rectangle getPositionBounds(InputEditorPosition position)
1942    {
1943       Position pos = ((AceInputEditorPosition) position).getValue();
1944       return getPositionBounds(pos);
1945    }
1946 
1947    public Rectangle getBounds()
1948    {
1949       return new Rectangle(
1950             widget_.getAbsoluteLeft(),
1951             widget_.getAbsoluteTop(),
1952             widget_.getOffsetWidth(),
1953             widget_.getOffsetHeight());
1954    }
1955 
1956    public void setFocus(boolean focused)
1957    {
1958       if (focused)
1959          widget_.getEditor().focus();
1960       else
1961          widget_.getEditor().blur();
1962    }
1963 
1964    public void replaceRange(Range range, String text) {
1965       getSession().replace(range, text);
1966    }
1967 
1968    public String replaceSelection(String value, boolean collapseSelection)
1969    {
1970       Selection selection = getSession().getSelection();
1971       String oldValue = getSession().getTextRange(selection.getRange());
1972 
1973       replaceSelection(value);
1974 
1975       if (collapseSelection)
1976       {
1977          collapseSelection(false);
1978       }
1979 
1980       return oldValue;
1981    }
1982 
1983    public boolean isSelectionCollapsed()
1984    {
1985       return getSession().getSelection().isEmpty();
1986    }
1987 
1988    public boolean isCursorAtEnd()
1989    {
1990       int lastRow = getRowCount() - 1;
1991       Position cursorPos = getCursorPosition();
1992       return cursorPos.compareTo(Position.create(lastRow,
1993                                                  getLength(lastRow))) == 0;
1994    }
1995 
1996    public void clear()
1997    {
1998       setCode("", false);
1999    }
2000 
2001    public boolean inMultiSelectMode()
2002    {
2003       return widget_.getEditor().inMultiSelectMode();
2004    }
2005 
2006    public void exitMultiSelectMode()
2007    {
2008       widget_.getEditor().exitMultiSelectMode();
2009    }
2010 
2011    public void clearSelection()
2012    {
2013       widget_.getEditor().clearSelection();
2014    }
2015 
2016    public void collapseSelection(boolean collapseToStart)
2017    {
2018       Selection selection = getSession().getSelection();
2019       Range rng = selection.getRange();
2020       Position pos = collapseToStart ? rng.getStart() : rng.getEnd();
2021       selection.setSelectionRange(Range.fromPoints(pos, pos));
2022    }
2023 
2024    public InputEditorSelection getStart()
2025    {
2026       return new InputEditorSelection(
2027             new AceInputEditorPosition(getSession(), Position.create(0, 0)));
2028    }
2029 
2030    public InputEditorSelection getEnd()
2031    {
2032       EditSession session = getSession();
2033       int rows = session.getLength();
2034       Position end = Position.create(rows, session.getLine(rows).length());
2035       return new InputEditorSelection(new AceInputEditorPosition(session, end));
2036    }
2037 
2038    public String getCurrentLine()
2039    {
2040       int row = getSession().getSelection().getRange().getStart().getRow();
2041       return getSession().getLine(row);
2042    }
2043 
2044    public char getCharacterAtCursor()
2045    {
2046       Position cursorPos = getCursorPosition();
2047       int column = cursorPos.getColumn();
2048       String line = getLine(cursorPos.getRow());
2049       if (column == line.length())
2050          return '\0';
2051 
2052       return StringUtil.charAt(line, column);
2053    }
2054 
2055    public char getCharacterBeforeCursor()
2056    {
2057       Position cursorPos = getCursorPosition();
2058       int column = cursorPos.getColumn();
2059       if (column == 0)
2060          return '\0';
2061 
2062       String line = getLine(cursorPos.getRow());
2063       return StringUtil.charAt(line, column - 1);
2064    }
2065 
2066 
2067    public String getCurrentLineUpToCursor()
2068    {
2069       return getCurrentLine().substring(0, getCursorPosition().getColumn());
2070    }
2071 
2072    public int getCurrentLineNum()
2073    {
2074       Position pos = getCursorPosition();
2075       return getSession().documentToScreenRow(pos);
2076    }
2077 
2078    public int getCurrentLineCount()
2079    {
2080       return getSession().getScreenLength();
2081    }
2082 
2083    @Override
2084    public String getLanguageMode(Position position)
2085    {
2086       return getSession().getMode().getLanguageMode(position);
2087    }
2088 
2089    @Override
2090    public String getModeId()
2091    {
2092      return getSession().getMode().getId();
2093    }
2094 
2095    public void replaceCode(String code)
2096    {
2097       int endRow, endCol;
2098 
2099       endRow = getSession().getLength() - 1;
2100       if (endRow < 0)
2101       {
2102          endRow = 0;
2103          endCol = 0;
2104       }
2105       else
2106       {
2107          endCol = getSession().getLine(endRow).length();
2108       }
2109 
2110       Range range = Range.fromPoints(Position.create(0, 0),
2111                                      Position.create(endRow, endCol));
2112       getSession().replace(range, code);
2113    }
2114 
2115    public void replaceSelection(String code)
2116    {
2117       code = StringUtil.normalizeNewLines(code);
2118       Range selRange = getSession().getSelection().getRange();
2119       Position position = getSession().replace(selRange, code);
2120       Range range = Range.fromPoints(selRange.getStart(), position);
2121       getSession().getSelection().setSelectionRange(range);
2122       if (isEmacsModeOn()) clearEmacsMark();
2123    }
2124 
2125    public boolean moveSelectionToNextLine(boolean skipBlankLines)
2126    {
2127       int curRow = getSession().getSelection().getCursor().getRow();
2128       while (++curRow < getSession().getLength())
2129       {
2130          String line = getSession().getLine(curRow);
2131          Pattern pattern = Pattern.create("[^\\s]");
2132          Match match = pattern.match(line, 0);
2133          if (skipBlankLines && match == null)
2134             continue;
2135          int col =  (match != null) ? match.getIndex() : 0;
2136          getSession().getSelection().moveCursorTo(curRow, col, false);
2137          getSession().unfold(curRow, true);
2138          scrollCursorIntoViewIfNecessary();
2139          return true;
2140       }
2141       return false;
2142    }
2143 
2144    @Override
2145    public boolean moveSelectionToBlankLine()
2146    {
2147       int curRow = getSession().getSelection().getCursor().getRow();
2148 
2149       // if the current row is the last row then insert a new row
2150       if (curRow == (getSession().getLength() - 1))
2151       {
2152          int rowLen = getSession().getLine(curRow).length();
2153          getSession().getSelection().moveCursorTo(curRow, rowLen, false);
2154          insertCode("\n");
2155       }
2156 
2157       while (curRow < getSession().getLength())
2158       {
2159          String line = getSession().getLine(curRow).trim();
2160          if (line.length() == 0)
2161          {
2162             getSession().getSelection().moveCursorTo(curRow, 0, false);
2163             getSession().unfold(curRow, true);
2164             return true;
2165          }
2166 
2167          curRow++;
2168       }
2169       return false;
2170    }
2171 
2172    @Override
2173    public void expandSelection()
2174    {
2175       widget_.getEditor().expandSelection();
2176    }
2177 
2178    @Override
2179    public void shrinkSelection()
2180    {
2181       widget_.getEditor().shrinkSelection();
2182    }
2183 
2184    @Override
2185    public void expandRaggedSelection()
2186    {
2187       if (!inMultiSelectMode())
2188          return;
2189 
2190       // TODO: It looks like we need to use an alternative API when
2191       // using Vim mode.
2192       if (isVimModeOn())
2193          return;
2194 
2195       boolean hasSelection = hasSelection();
2196 
2197       Range[] ranges =
2198             widget_.getEditor().getSession().getSelection().getAllRanges();
2199 
2200       // Get the maximum columns for the current selection.
2201       int colMin = Integer.MAX_VALUE;
2202       int colMax = 0;
2203       for (Range range : ranges)
2204       {
2205          colMin = Math.min(range.getStart().getColumn(), colMin);
2206          colMax = Math.max(range.getEnd().getColumn(), colMax);
2207       }
2208 
2209       // For each range:
2210       //
2211       //    1. Set the left side of the selection to the minimum,
2212       //    2. Set the right side of the selection to the maximum,
2213       //       moving the cursor and inserting whitespace as necessary.
2214       for (Range range : ranges)
2215       {
2216          range.getStart().setColumn(colMin);
2217          range.getEnd().setColumn(colMax);
2218 
2219          String line = getLine(range.getStart().getRow());
2220          if (line.length() < colMax)
2221          {
2222             insertCode(
2223                   Position.create(range.getStart().getRow(), line.length()),
2224                   StringUtil.repeat(" ", colMax - line.length()));
2225          }
2226       }
2227 
2228       clearSelection();
2229       Selection selection = getNativeSelection();
2230       for (Range range : ranges)
2231       {
2232          if (hasSelection)
2233             selection.addRange(range, true);
2234          else
2235          {
2236             Range newRange = Range.create(
2237                   range.getEnd().getRow(),
2238                   range.getEnd().getColumn(),
2239                   range.getEnd().getRow(),
2240                   range.getEnd().getColumn());
2241             selection.addRange(newRange, true);
2242          }
2243       }
2244    }
2245 
2246    @Override
2247    public void clearSelectionHistory()
2248    {
2249       widget_.getEditor().clearSelectionHistory();
2250    }
2251 
2252    @Override
2253    public void reindent()
2254    {
2255       boolean emptySelection = getSelection().isEmpty();
2256       getSession().reindent(getSession().getSelection().getRange());
2257       if (emptySelection)
2258          moveSelectionToNextLine(false);
2259    }
2260 
2261    @Override
2262    public void reindent(Range range)
2263    {
2264       getSession().reindent(range);
2265    }
2266 
2267    @Override
2268    public void toggleCommentLines()
2269    {
2270       widget_.getEditor().toggleCommentLines();
2271    }
2272 
2273    public ChangeTracker getChangeTracker()
2274    {
2275       return new AceEditorChangeTracker();
2276    }
2277 
2278    // Because anchored selections create Ace event listeners, they
2279    // must be explicitly detached (otherwise they will listen for
2280    // edit events into perpetuity). The easiest way to facilitate this
2281    // is to have anchored selection tied to the lifetime of a particular
2282    // 'host' widget; this way, on detach, we can ensure that the associated
2283    // anchors are detached as well.
2284    public AnchoredSelection createAnchoredSelection(Widget hostWidget,
2285                                                     Position startPos,
2286                                                     Position endPos)
2287    {
2288       Anchor start = Anchor.createAnchor(getSession().getDocument(),
2289                                          startPos.getRow(),
2290                                          startPos.getColumn());
2291       Anchor end = Anchor.createAnchor(getSession().getDocument(),
2292                                        endPos.getRow(),
2293                                        endPos.getColumn());
2294       final AnchoredSelection selection = new AnchoredSelectionImpl(start, end);
2295       if (hostWidget != null)
2296       {
2297          hostWidget.addAttachHandler(event ->
2298          {
2299             if (!event.isAttached() && selection != null)
2300                selection.detach();
2301          });
2302       }
2303 
2304       return selection;
2305    }
2306 
2307    public AnchoredSelection createAnchoredSelection(Position start, Position end)
2308    {
2309       return createAnchoredSelection(null, start, end);
2310    }
2311 
2312    public void fitSelectionToLines(boolean expand)
2313    {
2314       Range range = getSession().getSelection().getRange();
2315       Position start = range.getStart();
2316       Position newStart = start;
2317 
2318       if (start.getColumn() > 0)
2319       {
2320          if (expand)
2321          {
2322             newStart = Position.create(start.getRow(), 0);
2323          }
2324          else
2325          {
2326             String firstLine = getSession().getLine(start.getRow());
2327             if (firstLine.substring(0, start.getColumn()).trim().length() == 0)
2328                newStart = Position.create(start.getRow(), 0);
2329          }
2330       }
2331 
2332       Position end = range.getEnd();
2333       Position newEnd = end;
2334       if (expand)
2335       {
2336          int endRow = end.getRow();
2337          if (endRow == newStart.getRow() || end.getColumn() > 0)
2338          {
2339             // If selection ends at the start of a line, keep the selection
2340             // there--unless that means less than one line will be selected
2341             // in total.
2342             newEnd = Position.create(
2343                   endRow, getSession().getLine(endRow).length());
2344          }
2345       }
2346       else
2347       {
2348          while (newEnd.getRow() != newStart.getRow())
2349          {
2350             String line = getSession().getLine(newEnd.getRow());
2351             if (line.substring(0, newEnd.getColumn()).trim().length() != 0)
2352                break;
2353 
2354             int prevRow = newEnd.getRow() - 1;
2355             int len = getSession().getLine(prevRow).length();
2356             newEnd = Position.create(prevRow, len);
2357          }
2358       }
2359 
2360       getSession().getSelection().setSelectionRange(
2361             Range.fromPoints(newStart, newEnd));
2362    }
2363 
2364    public int getSelectionOffset(boolean start)
2365    {
2366       Range range = getSession().getSelection().getRange();
2367       if (start)
2368          return range.getStart().getColumn();
2369       else
2370          return range.getEnd().getColumn();
2371    }
2372 
2373    public void onActivate()
2374    {
2375       Scheduler.get().scheduleFinally(() ->
2376       {
2377          widget_.onResize();
2378          widget_.onActivate();
2379          return false;
2380       });
2381    }
2382 
2383    public void onVisibilityChanged(boolean visible)
2384    {
2385       if (visible)
2386          widget_.getEditor().getRenderer().updateFontSize();
2387    }
2388 
2389    public void onResize()
2390    {
2391       widget_.onResize();
2392    }
2393 
2394    public void setHighlightSelectedLine(boolean on)
2395    {
2396       widget_.getEditor().setHighlightActiveLine(on);
2397    }
2398 
2399    public void setHighlightSelectedWord(boolean on)
2400    {
2401       widget_.getEditor().setHighlightSelectedWord(on);
2402    }
2403 
2404    public void setShowLineNumbers(boolean on)
2405    {
2406       widget_.getEditor().getRenderer().setShowGutter(on);
2407    }
2408 
2409    public boolean getUseSoftTabs()
2410    {
2411       return getSession().getUseSoftTabs();
2412    }
2413 
2414    public void setUseSoftTabs(boolean on)
2415    {
2416       getSession().setUseSoftTabs(on);
2417    }
2418 
2419    public void setScrollPastEndOfDocument(boolean enable)
2420    {
2421       widget_.getEditor().getRenderer().setScrollPastEnd(enable);
2422    }
2423 
2424    public void setHighlightRFunctionCalls(boolean highlight)
2425    {
2426       _setHighlightRFunctionCallsImpl(highlight);
2427       widget_.getEditor().retokenizeDocument();
2428    }
2429 
2430    public void setRainbowParentheses(boolean rainbow)
2431    {
2432       _setRainbowParenthesesImpl(rainbow);
2433       widget_.getEditor().retokenizeDocument();
2434    }
2435 
2436    public boolean getRainbowParentheses()
2437    {
2438       return _getRainbowParenthesesImpl();
2439    }
2440 
2441    public void setScrollLeft(int x)
2442    {
2443       getSession().setScrollLeft(x);
2444    }
2445 
2446    public void setScrollTop(int y)
2447    {
2448       getSession().setScrollTop(y);
2449    }
2450 
2451    public void scrollTo(int x, int y)
2452    {
2453       getSession().setScrollLeft(x);
2454       getSession().setScrollTop(y);
2455    }
2456 
2457    private native final void _setHighlightRFunctionCallsImpl(boolean highlight)
2458    /*-{
2459       var Mode = $wnd.require("mode/r_highlight_rules");
2460       Mode.setHighlightRFunctionCalls(highlight);
2461    }-*/;
2462 
2463    private native final void _setRainbowParenthesesImpl(boolean rainbow)
2464    /*-{
2465       var Mode = $wnd.require("mode/rainbow_paren_highlight_rules");
2466       Mode.setRainbowParentheses(rainbow);
2467    }-*/;
2468 
2469    private native final boolean _getRainbowParenthesesImpl()
2470    /*-{
2471      var Mode = $wnd.require("mode/rainbow_paren_highlight_rules");
2472      return Mode.getRainbowParentheses();
2473    }-*/;
2474 
2475    public void enableSearchHighlight()
2476    {
2477       widget_.getEditor().enableSearchHighlight();
2478    }
2479 
2480    public void disableSearchHighlight()
2481    {
2482       widget_.getEditor().disableSearchHighlight();
2483    }
2484 
2485    /**
2486     * Sets the soft wrap mode for the editor
2487     */
2488    public void setUseWrapMode(boolean useWrapMode)
2489    {
2490       getSession().setUseWrapMode(useWrapMode);
2491    }
2492 
2493    /**
2494     * Gets whether or not the editor is using soft wrapping
2495     */
2496    public boolean getUseWrapMode()
2497    {
2498       return getSession().getUseWrapMode();
2499    }
2500 
2501    public void setTabSize(int tabSize)
2502    {
2503       getSession().setTabSize(tabSize);
2504    }
2505 
2506    public void autoDetectIndentation(boolean on)
2507    {
2508       if (!on)
2509          return;
2510 
2511       JsArrayString lines = getLines();
2512       if (lines.length() < 5)
2513          return;
2514 
2515       int indentSize = StringUtil.detectIndent(lines);
2516       if (indentSize > 0)
2517       {
2518          setTabSize(indentSize);
2519          setUseSoftTabs(true);
2520       }
2521    }
2522 
2523    public void setShowInvisibles(boolean show)
2524    {
2525       widget_.getEditor().getRenderer().setShowInvisibles(show);
2526    }
2527 
2528    public void setShowIndentGuides(boolean show)
2529    {
2530       widget_.getEditor().getRenderer().setShowIndentGuides(show);
2531    }
2532 
2533    public void setBlinkingCursor(boolean blinking)
2534    {
2535       String style = blinking ? "ace" : "wide";
2536       widget_.getEditor().setCursorStyle(style);
2537    }
2538 
2539    public void setShowPrintMargin(boolean on)
2540    {
2541       widget_.getEditor().getRenderer().setShowPrintMargin(on);
2542    }
2543 
2544    @Override
2545    public void setUseEmacsKeybindings(boolean use)
2546    {
2547       if (widget_.getEditor().getReadOnly())
2548          return;
2549 
2550       useEmacsKeybindings_ = use;
2551       updateKeyboardHandlers();
2552    }
2553 
2554    @Override
2555    public boolean isEmacsModeOn()
2556    {
2557       return useEmacsKeybindings_;
2558    }
2559 
2560    @Override
2561    public void clearEmacsMark()
2562    {
2563       widget_.getEditor().clearEmacsMark();
2564    }
2565 
2566    @Override
2567    public void setUseVimMode(boolean use)
2568    {
2569       // no-op if the editor is read-only (since vim mode doesn't
2570       // work for read-only ace instances)
2571       if (widget_.getEditor().getReadOnly())
2572          return;
2573 
2574       useVimMode_ = use;
2575       updateKeyboardHandlers();
2576    }
2577 
2578    @Override
2579    public boolean isVimModeOn()
2580    {
2581       return useVimMode_;
2582    }
2583 
2584    @Override
2585    public boolean isVimInInsertMode()
2586    {
2587       return useVimMode_ && widget_.getEditor().isVimInInsertMode();
2588    }
2589 
2590    public void setPadding(int padding)
2591    {
2592       widget_.getEditor().getRenderer().setPadding(padding);
2593    }
2594 
2595    public void setPrintMarginColumn(int column)
2596    {
2597       widget_.getEditor().getRenderer().setPrintMarginColumn(column);
2598       syncWrapLimit();
2599    }
2600 
2601    @Override
2602    public JsArray<AceFold> getFolds()
2603    {
2604       return getSession().getAllFolds();
2605    }
2606 
2607    @Override
2608    public String getFoldState(int row)
2609    {
2610       AceFold fold = getSession().getFoldAt(row, 0);
2611       if (fold == null)
2612          return null;
2613 
2614       Position foldPos = fold.getStart();
2615       return getSession().getState(foldPos.getRow());
2616    }
2617 
2618    @Override
2619    public void addFold(Range range)
2620    {
2621       getSession().addFold("...", range);
2622    }
2623 
2624    @Override
2625    public void addFoldFromRow(int row)
2626    {
2627       FoldingRules foldingRules = getSession().getMode().getFoldingRules();
2628       if (foldingRules == null)
2629          return;
2630       Range range = foldingRules.getFoldWidgetRange(getSession(),
2631                                                     "markbegin",
2632                                                     row);
2633 
2634       if (range != null)
2635          addFold(range);
2636    }
2637 
2638    @Override
2639    public void unfold(AceFold fold)
2640    {
2641       getSession().unfold(Range.fromPoints(fold.getStart(), fold.getEnd()),
2642                           false);
2643    }
2644 
2645    @Override
2646    public void unfold(int row)
2647    {
2648       getSession().unfold(row, false);
2649    }
2650 
2651    @Override
2652    public void unfold(Range range)
2653    {
2654       getSession().unfold(range, false);
2655    }
2656 
2657    public void setReadOnly(boolean readOnly)
2658    {
2659       widget_.getEditor().setReadOnly(readOnly);
2660    }
2661 
2662    public HandlerRegistration addCursorChangedHandler(final CursorChangedEvent.Handler handler)
2663    {
2664       return widget_.addCursorChangedHandler(handler);
2665    }
2666 
2667    public HandlerRegistration addSaveCompletedHandler(SaveFileEvent.Handler handler)
2668    {
2669       return handlers_.addHandler(SaveFileEvent.TYPE, handler);
2670    }
2671 
2672    public HandlerRegistration addAttachHandler(AttachEvent.Handler handler)
2673    {
2674       return widget_.addAttachHandler(handler);
2675    }
2676 
2677    public HandlerRegistration addEditorFocusHandler(FocusHandler handler)
2678    {
2679       return widget_.addFocusHandler(handler);
2680    }
2681 
2682    public HandlerRegistration addEditorBlurHandler(BlurHandler handler)
2683    {
2684       return widget_.addBlurHandler(handler);
2685    }
2686 
2687    public HandlerRegistration addContextMenuHandler(ContextMenuHandler handler)
2688    {
2689       return widget_.addContextMenuHandler(handler);
2690    }
2691 
2692    public HandlerRegistration addScrollYHandler(ScrollYEvent.Handler handler)
2693    {
2694       return widget_.addScrollYHandler(handler);
2695    }
2696 
2697    public Scope getScopeAtPosition(Position position)
2698    {
2699       if (hasCodeModelScopeTree())
2700          return getSession().getMode().getCodeModel().getCurrentScope(position);
2701 
2702       if (scopes_ != null)
2703          return scopes_.getScopeAt(position);
2704 
2705       return null;
2706    }
2707 
2708    public Scope getCurrentScope()
2709    {
2710       return getScopeAtPosition(getCursorPosition());
2711    }
2712 
2713    @Override
2714    public String getNextLineIndent()
2715    {
2716       EditSession session = getSession();
2717 
2718       Position cursorPosition = getCursorPosition();
2719       int row = cursorPosition.getRow();
2720       String state = getSession().getState(row);
2721 
2722       String line = getCurrentLine().substring(
2723             0, cursorPosition.getColumn());
2724       String tab = session.getTabString();
2725       int tabSize = session.getTabSize();
2726 
2727       return session.getMode().getNextLineIndent(
2728             state,
2729             line,
2730             tab,
2731             tabSize,
2732             row);
2733    }
2734 
2735    @Override
2736    public Scope getCurrentChunk()
2737    {
2738       return getChunkAtPosition(getCursorPosition());
2739    }
2740 
2741    @Override
2742    public Scope getChunkAtPosition(Position position)
2743    {
2744       return getSession().getMode().getCodeModel().getCurrentChunk(position);
2745    }
2746 
2747    @Override
2748    public ScopeFunction getCurrentFunction(boolean allowAnonymous)
2749    {
2750       return getFunctionAtPosition(getCursorPosition(), allowAnonymous);
2751    }
2752 
2753    @Override
2754    public ScopeFunction getFunctionAtPosition(Position position,
2755                                               boolean allowAnonymous)
2756    {
2757       return getSession().getMode().getCodeModel().getCurrentFunction(
2758             position, allowAnonymous);
2759    }
2760 
2761    @Override
2762    public Scope getCurrentSection()
2763    {
2764       return getSectionAtPosition(getCursorPosition());
2765    }
2766 
2767    @Override
2768    public Scope getSectionAtPosition(Position position)
2769    {
2770       return getSession().getMode().getCodeModel().getCurrentSection(position);
2771    }
2772 
2773    public Position getCursorPosition()
2774    {
2775       return getSession().getSelection().getCursor();
2776    }
2777 
2778    public Position getCursorPositionScreen()
2779    {
2780       return widget_.getEditor().getCursorPositionScreen();
2781    }
2782 
2783    public void setCursorPosition(Position position)
2784    {
2785       getSession().getSelection().setSelectionRange(
2786             Range.fromPoints(position, position));
2787    }
2788 
2789    public void goToLineStart()
2790    {
2791       widget_.getEditor().getCommandManager().exec("gotolinestart", widget_.getEditor());
2792    }
2793 
2794    public void goToLineEnd()
2795    {
2796       widget_.getEditor().getCommandManager().exec("gotolineend", widget_.getEditor());
2797    }
2798 
2799    @Override
2800    public void moveCursorNearTop(int rowOffset)
2801    {
2802       int screenRow = getSession().documentToScreenRow(getCursorPosition());
2803       widget_.getEditor().scrollToRow(Math.max(0, screenRow - rowOffset));
2804    }
2805 
2806    @Override
2807    public void moveCursorForward()
2808    {
2809       moveCursorForward(1);
2810    }
2811 
2812    @Override
2813    public void moveCursorForward(int characters)
2814    {
2815       widget_.getEditor().moveCursorRight(characters);
2816    }
2817 
2818    @Override
2819    public void moveCursorBackward()
2820    {
2821       moveCursorBackward(1);
2822    }
2823 
2824    @Override
2825    public void moveCursorBackward(int characters)
2826    {
2827       widget_.getEditor().moveCursorLeft(characters);
2828    }
2829 
2830    @Override
2831    public void moveCursorNearTop()
2832    {
2833       moveCursorNearTop(7);
2834    }
2835 
2836    @Override
2837    public void ensureCursorVisible()
2838    {
2839       if (!widget_.getEditor().isRowFullyVisible(getCursorPosition().getRow()))
2840          moveCursorNearTop();
2841    }
2842 
2843    @Override
2844    public void ensureRowVisible(int row)
2845    {
2846       if (!widget_.getEditor().isRowFullyVisible(row))
2847          setCursorPosition(Position.create(row, 0));
2848    }
2849 
2850    @Override
2851    public void scrollCursorIntoViewIfNecessary(int rowsAround)
2852    {
2853       Position cursorPos = getCursorPosition();
2854       int cursorRow = cursorPos.getRow();
2855 
2856       if (cursorRow >= widget_.getEditor().getLastVisibleRow() - rowsAround)
2857       {
2858          Position alignPos = Position.create(cursorRow + rowsAround, 0);
2859          widget_.getEditor().getRenderer().alignCursor(alignPos, 1);
2860       }
2861       else if (cursorRow <= widget_.getEditor().getFirstVisibleRow() + rowsAround)
2862       {
2863          Position alignPos = Position.create(cursorRow - rowsAround, 0);
2864          widget_.getEditor().getRenderer().alignCursor(alignPos, 0);
2865       }
2866    }
2867 
2868    @Override
2869    public void scrollCursorIntoViewIfNecessary()
2870    {
2871       scrollCursorIntoViewIfNecessary(0);
2872    }
2873 
2874    @Override
2875    public boolean isCursorInSingleLineString()
2876    {
2877       return StringUtil.isEndOfLineInRStringState(getCurrentLineUpToCursor());
2878    }
2879 
2880    public void gotoPageUp()
2881    {
2882       widget_.getEditor().gotoPageUp();
2883    }
2884 
2885    public void gotoPageDown()
2886    {
2887       widget_.getEditor().gotoPageDown();
2888    }
2889 
2890    public void scrollToBottom()
2891    {
2892       SourcePosition pos = SourcePosition.create(getCurrentLineCount() - 1, 0);
2893       navigate(pos, false);
2894    }
2895 
2896    public void revealRange(Range range, boolean animate)
2897    {
2898       widget_.getEditor().revealRange(range, animate);
2899    }
2900 
2901    public CodeModel getCodeModel()
2902    {
2903       return getSession().getMode().getCodeModel();
2904    }
2905 
2906    public boolean hasCodeModel()
2907    {
2908       return getSession().getMode().hasCodeModel();
2909    }
2910 
2911    public boolean hasCodeModelScopeTree()
2912    {
2913       return hasCodeModel() && getCodeModel().hasScopes();
2914    }
2915 
2916    public void buildScopeTree()
2917    {
2918       // Builds the scope tree as a side effect
2919       if (hasCodeModelScopeTree())
2920          getScopeTree();
2921    }
2922 
2923    public int buildScopeTreeUpToRow(int row)
2924    {
2925       if (!hasCodeModelScopeTree())
2926          return 0;
2927 
2928       return getSession().getMode().getRCodeModel().buildScopeTreeUpToRow(row);
2929    }
2930 
2931    public JsArray<Scope> getScopeTree()
2932    {
2933       if (hasCodeModelScopeTree())
2934          return getSession().getMode().getCodeModel().getScopeTree();
2935 
2936       if (scopes_ != null)
2937          return scopes_.getScopeTree();
2938 
2939       return JavaScriptObject.createArray().cast();
2940    }
2941 
2942    @Override
2943    public InsertChunkInfo getInsertChunkInfo()
2944    {
2945       return getSession().getMode().getInsertChunkInfo();
2946    }
2947 
2948    @Override
2949    public void foldAll()
2950    {
2951       getSession().foldAll();
2952    }
2953 
2954    @Override
2955    public void unfoldAll()
2956    {
2957       getSession().unfoldAll();
2958    }
2959 
2960    @Override
2961    public void toggleFold()
2962    {
2963       getSession().toggleFold();
2964    }
2965 
2966    @Override
2967    public void setFoldStyle(String style)
2968    {
2969       getSession().setFoldStyle(style);
2970    }
2971 
2972    @Override
2973    public JsMap<Position> getMarks()
2974    {
2975       return widget_.getEditor().getMarks();
2976    }
2977 
2978    @Override
2979    public void setMarks(JsMap<Position> marks)
2980    {
2981       widget_.getEditor().setMarks(marks);
2982    }
2983 
2984    @Override
2985    public void jumpToMatching()
2986    {
2987       widget_.getEditor().jumpToMatching(false, false);
2988       scrollCursorIntoViewIfNecessary();
2989    }
2990 
2991    @Override
2992    public void selectToMatching()
2993    {
2994       widget_.getEditor().jumpToMatching(true, false);
2995       scrollCursorIntoViewIfNecessary();
2996    }
2997 
2998    @Override
2999    public void expandToMatching()
3000    {
3001       widget_.getEditor().jumpToMatching(true, true);
3002       scrollCursorIntoViewIfNecessary();
3003    }
3004 
3005    @Override
3006    public void addCursorAbove()
3007    {
3008       widget_.getEditor().execCommand("addCursorAbove");
3009    }
3010 
3011    @Override
3012    public void addCursorBelow()
3013    {
3014       widget_.getEditor().execCommand("addCursorBelow");
3015    }
3016 
3017    @Override
3018    public void editLinesFromStart()
3019    {
3020       editLines_.editLinesFromStart();
3021    }
3022 
3023    @Override
3024    public void moveLinesUp()
3025    {
3026       widget_.getEditor().execCommand("movelinesup");
3027    }
3028 
3029    @Override
3030    public void moveLinesDown()
3031    {
3032       widget_.getEditor().execCommand("movelinesdown");
3033    }
3034 
3035    @Override
3036    public void expandToLine()
3037    {
3038       widget_.getEditor().execCommand("expandtoline");
3039    }
3040 
3041    @Override
3042    public void copyLinesDown()
3043    {
3044       widget_.getEditor().execCommand("copylinesdown");
3045    }
3046 
3047    @Override
3048    public void joinLines()
3049    {
3050       widget_.getEditor().execCommand("joinlines");
3051    }
3052 
3053    @Override
3054    public void removeLine()
3055    {
3056       widget_.getEditor().execCommand("removeline");
3057    }
3058 
3059    @Override
3060    public void splitIntoLines()
3061    {
3062       widget_.getEditor().splitIntoLines();
3063    }
3064 
3065    @Override
3066    public int getFirstFullyVisibleRow()
3067    {
3068       return widget_.getEditor().getRenderer().getFirstFullyVisibleRow();
3069    }
3070 
3071    @Override
3072    public SourcePosition findFunctionPositionFromCursor(String functionName)
3073    {
3074       Scope func =
3075          getSession().getMode().getCodeModel().findFunctionDefinitionFromUsage(
3076                                                       getCursorPosition(),
3077                                                       functionName);
3078       if (func != null)
3079       {
3080          Position position = func.getPreamble();
3081          return SourcePosition.create(position.getRow(), position.getColumn());
3082       }
3083       else
3084       {
3085          return null;
3086       }
3087    }
3088 
3089    public JsArray<ScopeFunction> getAllFunctionScopes()
3090    {
3091       CodeModel codeModel = widget_.getEditor().getSession().getMode().getRCodeModel();
3092       if (codeModel == null)
3093          return null;
3094 
3095       return codeModel.getAllFunctionScopes();
3096    }
3097 
3098    @Override
3099    public void recordCurrentNavigationPosition()
3100    {
3101       fireRecordNavigationPosition(getCursorPosition());
3102    }
3103 
3104    @Override
3105    public void navigateToPosition(SourcePosition position,
3106                                   boolean recordCurrent)
3107    {
3108       navigateToPosition(position, recordCurrent, false, false);
3109    }
3110 
3111    @Override
3112    public void navigateToPosition(SourcePosition position,
3113                                   boolean recordCurrent,
3114                                   boolean highlightLine,
3115                                   boolean restoreCursorPosition)
3116    {
3117       if (recordCurrent)
3118          recordCurrentNavigationPosition();
3119 
3120       navigate(position, true, highlightLine, restoreCursorPosition);
3121    }
3122 
3123    @Override
3124    public void restorePosition(SourcePosition position)
3125    {
3126       navigate(position, false);
3127    }
3128 
3129    @Override
3130    public boolean isAtSourceRow(SourcePosition position)
3131    {
3132       Position currPos = getCursorPosition();
3133       return currPos.getRow() == position.getRow();
3134    }
3135 
3136    @Override
3137    public void highlightDebugLocation(SourcePosition startPosition,
3138                                       SourcePosition endPosition,
3139                                       boolean executing)
3140    {
3141       int firstRow = widget_.getEditor().getFirstVisibleRow();
3142       int lastRow = widget_.getEditor().getLastVisibleRow();
3143 
3144       // if the expression is large, let's just try to land in the middle
3145       int debugRow = (int) Math.floor(startPosition.getRow() + (
3146             endPosition.getRow() - startPosition.getRow())/2);
3147 
3148       // if the row at which the debugging occurs is inside a fold, unfold it
3149       getSession().unfold(debugRow, true);
3150 
3151       // if the line to be debugged is past or near the edges of the screen,
3152       // scroll it into view. allow some lines of context.
3153       if (debugRow <= (firstRow + DEBUG_CONTEXT_LINES) ||
3154           debugRow >= (lastRow - DEBUG_CONTEXT_LINES))
3155       {
3156          widget_.getEditor().scrollToLine(debugRow, true);
3157       }
3158 
3159       applyDebugLineHighlight(
3160             startPosition.asPosition(),
3161             endPosition.asPosition(),
3162             executing);
3163    }
3164 
3165    @Override
3166    public void endDebugHighlighting()
3167    {
3168       clearDebugLineHighlight();
3169    }
3170 
3171    @Override
3172    public HandlerRegistration addBreakpointSetHandler(
3173          BreakpointSetEvent.Handler handler)
3174    {
3175       return widget_.addBreakpointSetHandler(handler);
3176    }
3177 
3178    @Override
3179    public HandlerRegistration addBreakpointMoveHandler(
3180          BreakpointMoveEvent.Handler handler)
3181    {
3182       return widget_.addBreakpointMoveHandler(handler);
3183    }
3184 
3185    @Override
3186    public void addOrUpdateBreakpoint(Breakpoint breakpoint)
3187    {
3188       widget_.addOrUpdateBreakpoint(breakpoint);
3189    }
3190 
3191    @Override
3192    public void removeBreakpoint(Breakpoint breakpoint)
3193    {
3194       widget_.removeBreakpoint(breakpoint);
3195    }
3196 
3197    @Override
3198    public void toggleBreakpointAtCursor()
3199    {
3200       widget_.toggleBreakpointAtCursor();
3201    }
3202 
3203    @Override
3204    public void removeAllBreakpoints()
3205    {
3206       widget_.removeAllBreakpoints();
3207    }
3208 
3209    @Override
3210    public boolean hasBreakpoints()
3211    {
3212       return widget_.hasBreakpoints();
3213    }
3214 
3215    public void setChunkLineExecState(int start, int end, int state)
3216    {
3217       widget_.setChunkLineExecState(start, end, state);
3218    }
3219 
3220    private void navigate(SourcePosition srcPosition, boolean addToHistory)
3221    {
3222       navigate(srcPosition, addToHistory, false, false);
3223    }
3224 
3225    private void navigate(SourcePosition srcPosition,
3226                          boolean addToHistory,
3227                          boolean highlightLine,
3228                          boolean restoreCursorPosition)
3229    {
3230       // get existing cursor position
3231       Position previousCursorPos = getCursorPosition();
3232 
3233       // set cursor to function line
3234       Position position = Position.create(srcPosition.getRow(),
3235                                           srcPosition.getColumn());
3236       setCursorPosition(position);
3237 
3238       // skip whitespace if necessary
3239       if (srcPosition.getColumn() == 0)
3240       {
3241          int curRow = getSession().getSelection().getCursor().getRow();
3242          String line = getSession().getLine(curRow);
3243          int funStart = line.indexOf(line.trim());
3244          position = Position.create(curRow, funStart);
3245          setCursorPosition(position);
3246       }
3247 
3248       // scroll as necessary
3249       if (srcPosition.getScrollPosition() != -1)
3250          scrollToY(srcPosition.getScrollPosition(), 0);
3251       else if (position.getRow() != previousCursorPos.getRow())
3252          moveCursorNearTop();
3253       else
3254          ensureCursorVisible();
3255 
3256       // restore original cursor position or set focus
3257       if (restoreCursorPosition)
3258          setCursorPosition(previousCursorPos);
3259       else
3260          focus();
3261 
3262       if (highlightLine)
3263          applyLineHighlight(position.getRow());
3264 
3265       // add to navigation history if requested and our current mode
3266       // supports history navigation
3267       if (addToHistory)
3268          fireRecordNavigationPosition(position);
3269    }
3270 
3271    private void fireRecordNavigationPosition(Position pos)
3272    {
3273       SourcePosition srcPos = SourcePosition.create(pos.getRow(),
3274                                                     pos.getColumn());
3275       fireEvent(new RecordNavigationPositionEvent(srcPos));
3276    }
3277 
3278    @Override
3279    public HandlerRegistration addRecordNavigationPositionHandler(
3280                                     RecordNavigationPositionEvent.Handler handler)
3281    {
3282       return handlers_.addHandler(RecordNavigationPositionEvent.TYPE, handler);
3283    }
3284 
3285    @Override
3286    public HandlerRegistration addCommandClickHandler(
3287                                              CommandClickEvent.Handler handler)
3288    {
3289       return handlers_.addHandler(CommandClickEvent.TYPE, handler);
3290    }
3291 
3292    @Override
3293    public HandlerRegistration addFindRequestedHandler(
3294                                  FindRequestedEvent.Handler handler)
3295    {
3296       return handlers_.addHandler(FindRequestedEvent.TYPE, handler);
3297    }
3298 
3299    public void setFontSize(double size)
3300    {
3301       // No change needed--the AceEditorWidget uses the "normalSize" style
3302       // However, we do need to resize the gutter
3303       widget_.getEditor().getRenderer().updateFontSize();
3304       widget_.forceResize();
3305       widget_.getLineWidgetManager().syncLineWidgetHeights();
3306    }
3307 
3308    public HandlerRegistration addValueChangeHandler(
3309          ValueChangeHandler<Void> handler)
3310    {
3311       return handlers_.addHandler(ValueChangeEvent.getType(), handler);
3312    }
3313 
3314    public HandlerRegistration addFoldChangeHandler(
3315          FoldChangeEvent.Handler handler)
3316    {
3317       return handlers_.addHandler(FoldChangeEvent.TYPE, handler);
3318    }
3319 
3320    public HandlerRegistration addLineWidgetsChangedHandler(
3321                            LineWidgetsChangedEvent.Handler handler)
3322    {
3323       return handlers_.addHandler(LineWidgetsChangedEvent.TYPE, handler);
3324    }
3325 
3326    public boolean isScopeTreeReady(int row)
3327    {
3328       // NOTE: 'hasScopeTree()' implies JavaScript-side scope tree
3329       if (hasCodeModelScopeTree())
3330          return backgroundTokenizer_.isReady(row);
3331 
3332       if (scopes_ != null)
3333          return scopes_.isReady(row);
3334 
3335       return false;
3336    }
3337 
3338    public HandlerRegistration addScopeTreeReadyHandler(ScopeTreeReadyEvent.Handler handler)
3339    {
3340       return handlers_.addHandler(ScopeTreeReadyEvent.TYPE, handler);
3341    }
3342 
3343    public HandlerRegistration addActiveScopeChangedHandler(ActiveScopeChangedEvent.Handler handler)
3344    {
3345       return handlers_.addHandler(ActiveScopeChangedEvent.TYPE, handler);
3346    }
3347 
3348    public HandlerRegistration addRenderFinishedHandler(RenderFinishedEvent.Handler handler)
3349    {
3350       return widget_.addHandler(handler, RenderFinishedEvent.TYPE);
3351    }
3352 
3353    public HandlerRegistration addDocumentChangedHandler(DocumentChangedEvent.Handler handler)
3354    {
3355       return widget_.addHandler(handler, DocumentChangedEvent.TYPE);
3356    }
3357 
3358    public HandlerRegistration addCapturingKeyDownHandler(KeyDownHandler handler)
3359    {
3360       return widget_.addCapturingKeyDownHandler(handler);
3361    }
3362 
3363    public HandlerRegistration addCapturingKeyPressHandler(KeyPressHandler handler)
3364    {
3365       return widget_.addCapturingKeyPressHandler(handler);
3366    }
3367 
3368    public HandlerRegistration addCapturingKeyUpHandler(KeyUpHandler handler)
3369    {
3370       return widget_.addCapturingKeyUpHandler(handler);
3371    }
3372 
3373    public HandlerRegistration addUndoRedoHandler(UndoRedoEvent.Handler handler)
3374    {
3375       return widget_.addUndoRedoHandler(handler);
3376    }
3377 
3378    public HandlerRegistration addPasteHandler(PasteEvent.Handler handler)
3379    {
3380       return widget_.addPasteHandler(handler);
3381    }
3382 
3383    public HandlerRegistration addEditHandler(EditEvent.Handler handler)
3384    {
3385       return widget_.addHandler(handler, EditEvent.TYPE);
3386    }
3387 
3388    public HandlerRegistration addAceClickHandler(Handler handler)
3389    {
3390       return widget_.addAceClickHandler(handler);
3391    }
3392 
3393    public HandlerRegistration addEditorModeChangedHandler(
3394          EditorModeChangedEvent.Handler handler)
3395    {
3396       return handlers_.addHandler(EditorModeChangedEvent.TYPE, handler);
3397    }
3398 
3399    public JavaScriptObject getCleanStateToken()
3400    {
3401       return getSession().getUndoManager().peek();
3402    }
3403 
3404    public boolean checkCleanStateToken(JavaScriptObject token)
3405    {
3406       JavaScriptObject other = getSession().getUndoManager().peek();
3407       if (token == null ^ other == null)
3408          return false;
3409       return token == null || other.equals(token);
3410    }
3411 
3412    public void fireEvent(GwtEvent<?> event)
3413    {
3414       handlers_.fireEvent(event);
3415    }
3416 
3417    public Widget asWidget()
3418    {
3419       return widget_;
3420    }
3421 
3422    public EditSession getSession()
3423    {
3424       return widget_.getEditor().getSession();
3425    }
3426 
3427    public HandlerRegistration addBlurHandler(BlurHandler handler)
3428    {
3429       return widget_.addBlurHandler(handler);
3430    }
3431 
3432    public HandlerRegistration addMouseDownHandler(MouseDownHandler handler)
3433    {
3434       return widget_.addMouseDownHandler(handler);
3435    }
3436 
3437    public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler)
3438    {
3439       return widget_.addMouseMoveHandler(handler);
3440    }
3441 
3442    public HandlerRegistration addMouseUpHandler(MouseUpHandler handler)
3443    {
3444       return widget_.addMouseUpHandler(handler);
3445    }
3446 
3447    public HandlerRegistration addClickHandler(ClickHandler handler)
3448    {
3449       return widget_.addClickHandler(handler);
3450    }
3451 
3452    public HandlerRegistration addFocusHandler(FocusHandler handler)
3453    {
3454       return widget_.addFocusHandler(handler);
3455    }
3456 
3457    public AceEditorWidget getWidget()
3458    {
3459       return widget_;
3460    }
3461 
3462    public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
3463    {
3464       return widget_.addKeyDownHandler(handler);
3465    }
3466 
3467    public HandlerRegistration addKeyPressHandler(KeyPressHandler handler)
3468    {
3469       return widget_.addKeyPressHandler(handler);
3470    }
3471 
3472    public HandlerRegistration addKeyUpHandler(KeyUpHandler handler)
3473    {
3474       return widget_.addKeyUpHandler(handler);
3475    }
3476 
3477    public HandlerRegistration addSelectionChangedHandler(AceSelectionChangedEvent.Handler handler)
3478    {
3479       return widget_.addSelectionChangedHandler(handler);
3480    }
3481 
3482    public void autoHeight()
3483    {
3484       widget_.autoHeight();
3485    }
3486 
3487    public void forceCursorChange()
3488    {
3489       widget_.forceCursorChange();
3490    }
3491 
3492    public void scrollToLine(int row, boolean center)
3493    {
3494       widget_.getEditor().scrollToLine(row, center);
3495    }
3496 
3497    public void centerSelection()
3498    {
3499       widget_.getEditor().centerSelection();
3500    }
3501 
3502    public void alignCursor(Position position, double ratio)
3503    {
3504       widget_.getEditor().getRenderer().alignCursor(position, ratio);
3505    }
3506 
3507    public void forceImmediateRender()
3508    {
3509       widget_.getEditor().getRenderer().forceImmediateRender();
3510    }
3511 
3512    public void setNewLineMode(NewLineMode mode)
3513    {
3514       getSession().setNewLineMode(mode.getType());
3515    }
3516 
3517    public boolean isPasswordMode()
3518    {
3519       return passwordMode_;
3520    }
3521 
3522    public void setPasswordMode(boolean passwordMode)
3523    {
3524       passwordMode_ = passwordMode;
3525       widget_.getEditor().getRenderer().setPasswordMode(passwordMode);
3526    }
3527 
3528    public void setDisableOverwrite(boolean disableOverwrite)
3529    {
3530       getSession().setDisableOverwrite(disableOverwrite);
3531    }
3532 
3533    private Integer createLineHighlightMarker(int line, String style)
3534    {
3535       return createRangeHighlightMarker(Position.create(line, 0),
3536                                         Position.create(line+1, 0),
3537                                         style);
3538    }
3539 
3540    private Integer createRangeHighlightMarker(
3541          Position start,
3542          Position end,
3543          String style)
3544    {
3545       Range range = Range.fromPoints(start, end);
3546       return getSession().addMarker(range, style, "text", false);
3547    }
3548 
3549 
3550    private void applyLineHighlight(int line)
3551    {
3552       clearLineHighlight();
3553 
3554       if (!widget_.getEditor().getHighlightActiveLine())
3555       {
3556          lineHighlightMarkerId_ = createLineHighlightMarker(line,
3557                                                             "ace_find_line");
3558       }
3559    }
3560 
3561    private void clearLineHighlight()
3562    {
3563       if (lineHighlightMarkerId_ != null)
3564       {
3565          getSession().removeMarker(lineHighlightMarkerId_);
3566          lineHighlightMarkerId_ = null;
3567       }
3568    }
3569 
3570    private void applyDebugLineHighlight(
3571          Position startPos,
3572          Position endPos,
3573          boolean executing)
3574    {
3575       clearDebugLineHighlight();
3576       lineDebugMarkerId_ = createRangeHighlightMarker(
3577             startPos, endPos,
3578             "ace_active_debug_line");
3579       if (executing)
3580       {
3581          executionLine_ = startPos.getRow();
3582          widget_.getEditor().getRenderer().addGutterDecoration(
3583                executionLine_,
3584                "ace_executing-line");
3585       }
3586    }
3587 
3588    private void clearDebugLineHighlight()
3589    {
3590       if (lineDebugMarkerId_ != null)
3591       {
3592          getSession().removeMarker(lineDebugMarkerId_);
3593          lineDebugMarkerId_ = null;
3594       }
3595       if (executionLine_ != null)
3596       {
3597          widget_.getEditor().getRenderer().removeGutterDecoration(
3598                executionLine_,
3599                "ace_executing-line");
3600          executionLine_ = null;
3601       }
3602    }
3603 
3604    public void setPopupVisible(boolean visible)
3605    {
3606       popupVisible_ = visible;
3607    }
3608 
3609    public boolean isPopupVisible()
3610    {
3611       return popupVisible_;
3612    }
3613 
3614    public void selectAll(String needle)
3615    {
3616       widget_.getEditor().findAll(needle);
3617    }
3618 
3619    public void selectAll(String needle, Range range, boolean wholeWord, boolean caseSensitive)
3620    {
3621       widget_.getEditor().findAll(needle, range, wholeWord, caseSensitive);
3622    }
3623 
3624    public void moveCursorLeft()
3625    {
3626       moveCursorLeft(1);
3627    }
3628 
3629    public void moveCursorLeft(int times)
3630    {
3631       widget_.getEditor().moveCursorLeft(times);
3632    }
3633 
3634    public void moveCursorRight()
3635    {
3636       moveCursorRight(1);
3637    }
3638 
3639    public void moveCursorRight(int times)
3640    {
3641       widget_.getEditor().moveCursorRight(times);
3642    }
3643 
3644    public void expandSelectionLeft(int times)
3645    {
3646       widget_.getEditor().expandSelectionLeft(times);
3647    }
3648 
3649    public void expandSelectionRight(int times)
3650    {
3651       widget_.getEditor().expandSelectionRight(times);
3652    }
3653 
3654    public int getTabSize()
3655    {
3656       return widget_.getEditor().getSession().getTabSize();
3657    }
3658 
3659    // TODO: Enable similar logic for C++ mode?
3660    public int getStartOfCurrentStatement()
3661    {
3662       if (!DocumentMode.isSelectionInRMode(this))
3663          return -1;
3664 
3665       TokenCursor cursor =
3666             getSession().getMode().getCodeModel().getTokenCursor();
3667 
3668       if (!cursor.moveToPosition(getCursorPosition()))
3669          return -1;
3670 
3671       if (!cursor.moveToStartOfCurrentStatement())
3672          return -1;
3673 
3674       return cursor.getRow();
3675    }
3676 
3677    // TODO: Enable similar logic for C++ mode?
3678    public int getEndOfCurrentStatement()
3679    {
3680       if (!DocumentMode.isSelectionInRMode(this))
3681          return -1;
3682 
3683       TokenCursor cursor =
3684             getSession().getMode().getCodeModel().getTokenCursor();
3685 
3686       if (!cursor.moveToPosition(getCursorPosition()))
3687          return -1;
3688 
3689       if (!cursor.moveToEndOfCurrentStatement())
3690          return -1;
3691 
3692       return cursor.getRow();
3693    }
3694 
3695    private boolean rowEndsInBinaryOperatorOrOpenParen(int row)
3696    {
3697       // move to the last interesting token on this line
3698       JsArray<Token> tokens = getSession().getTokens(row);
3699       for (int i = tokens.length() - 1; i >= 0; i--)
3700       {
3701          Token t = tokens.get(i);
3702          if (t.hasType("text", "comment", "virtual-comment"))
3703             continue;
3704          if (t.getType()  == "keyword.operator" ||
3705              t.getType()  == "keyword.operator.infix" ||
3706              t.getValue() == "," ||
3707              t.getValue() == "(")
3708             return true;
3709          break;
3710       }
3711       return false;
3712    }
3713 
3714    private boolean rowIsEmptyOrComment(int row)
3715    {
3716       JsArray<Token> tokens = getSession().getTokens(row);
3717       for (int i = 0, n = tokens.length(); i < n; i++)
3718          if (!tokens.get(i).hasType("text", "comment", "virtual-comment"))
3719             return false;
3720       return true;
3721    }
3722 
3723    private boolean rowStartsWithClosingBracket(int row)
3724    {
3725       JsArray<Token> tokens = getSession().getTokens(row);
3726 
3727       int n = tokens.length();
3728       if (n == 0)
3729          return false;
3730 
3731       for (int i = 0; i < n; i++)
3732       {
3733          Token token = tokens.get(i);
3734          if (token.hasType("text"))
3735             continue;
3736 
3737          String tokenValue = token.getValue();
3738          return tokenValue == "}" ||
3739                 tokenValue == ")" ||
3740                 tokenValue == "]";
3741       }
3742 
3743       return false;
3744    }
3745 
3746    private boolean rowEndsWithOpenBracket(int row)
3747    {
3748       JsArray<Token> tokens = getSession().getTokens(row);
3749 
3750       int n = tokens.length();
3751       if (n == 0)
3752          return false;
3753 
3754       for (int i = 0; i < n; i++)
3755       {
3756          Token token = tokens.get(n - i - 1);
3757          if (token.hasType("text", "comment", "virtual-comment"))
3758             continue;
3759 
3760          String tokenValue = token.getValue();
3761          return tokenValue == "{" ||
3762                 tokenValue == "(" ||
3763                 tokenValue == "[";
3764       }
3765 
3766       return false;
3767    }
3768 
3769    /**
3770     * Finds the last non-empty line starting at the given line.
3771     *
3772     * @param initial Row to start on
3773     * @param limit Row at which to stop searching
3774     * @return Index of last non-empty line, or limit line if no empty lines
3775     *   were found.
3776     */
3777    private int findParagraphBoundary(int initial, int limit)
3778    {
3779       // no work to do if already at limit
3780       if (initial == limit)
3781          return initial;
3782 
3783       // walk towards limit
3784       int delta = limit > initial ? 1 : -1;
3785       for (int row = initial + delta; row != limit; row += delta)
3786       {
3787          if (getLine(row).trim().isEmpty())
3788             return row - delta;
3789       }
3790 
3791       // didn't find boundary
3792       return limit;
3793    }
3794 
3795    @Override
3796    public Range getParagraph(Position pos, int startRowLimit, int endRowLimit)
3797    {
3798       // find upper and lower paragraph boundaries
3799       return Range.create(
3800             findParagraphBoundary(pos.getRow(), startRowLimit), 0,
3801             findParagraphBoundary(pos.getRow(), endRowLimit)+ 1, 0);
3802    }
3803 
3804    @Override
3805    public Range getMultiLineExpr(Position pos, int startRowLimit, int endRowLimit)
3806    {
3807       if (DocumentMode.isSelectionInRMode(this))
3808       {
3809          return rMultiLineExpr(pos, startRowLimit, endRowLimit);
3810       }
3811       else
3812       {
3813          return getParagraph(pos, startRowLimit, endRowLimit);
3814       }
3815    }
3816 
3817    private Range rMultiLineExpr(Position pos, int startRowLimit, int endRowLimit)
3818    {
3819       // create token cursor (will be used to walk tokens as needed)
3820       TokenCursor c = getSession().getMode().getCodeModel().getTokenCursor();
3821 
3822       // assume start, end at current position
3823       int startRow = pos.getRow();
3824       int endRow   = pos.getRow();
3825 
3826       // expand to enclosing '(' or '['
3827       do
3828       {
3829          c.setRow(pos.getRow());
3830 
3831          // move forward over commented / empty lines
3832          int n = getSession().getLength();
3833          while (rowIsEmptyOrComment(c.getRow()))
3834          {
3835             if (c.getRow() == n - 1)
3836                break;
3837 
3838             c.setRow(c.getRow() + 1);
3839          }
3840 
3841          // move to last non-right-bracket token on line
3842          c.moveToEndOfRow(c.getRow());
3843          while (c.valueEquals(")") || c.valueEquals("]"))
3844             if (!c.moveToPreviousToken())
3845                break;
3846 
3847          // find the top-most enclosing bracket
3848          // check for function scope
3849          String[] candidates = new String[] {"(", "["};
3850          int savedRow = -1;
3851          int savedOffset = -1;
3852          while (c.findOpeningBracket(candidates, true))
3853          {
3854             // check for function scope
3855             if (c.valueEquals("(") &&
3856                 c.peekBwd(1).valueEquals("function") &&
3857                 c.peekBwd(2).isLeftAssign())
3858             {
3859                ScopeFunction scope = getFunctionAtPosition(c.currentPosition(), false);
3860                if (scope != null)
3861                   return Range.fromPoints(scope.getPreamble(), scope.getEnd());
3862             }
3863 
3864             // move off of opening bracket and continue lookup
3865             savedRow = c.getRow();
3866             savedOffset = c.getOffset();
3867             if (!c.moveToPreviousToken())
3868                break;
3869          }
3870 
3871          // if we found a row, use it
3872          if (savedRow != -1 && savedOffset != -1)
3873          {
3874             c.setRow(savedRow);
3875             c.setOffset(savedOffset);
3876             if (c.fwdToMatchingToken())
3877             {
3878                startRow = savedRow;
3879                endRow = c.getRow();
3880             }
3881          }
3882 
3883       }
3884       while (false);
3885 
3886       // check for lines of the form:
3887       //
3888       //     ) foo +
3889       //
3890       // that is, the line ends in a binary operator, but also
3891       // starts with a right bracket. in that case, we want to
3892       // move the cursor to the associated left bracket.
3893       if (rowEndsInBinaryOperatorOrOpenParen(startRow))
3894       {
3895          // move token cursor to that row
3896          c.moveToEndOfRow(startRow);
3897 
3898          // skip comments, operators, etc.
3899          boolean foundBracket = false;
3900          while (c.hasType("text", "comment", "virtual-comment", "keyword.operator"))
3901          {
3902             if (c.isRightBracket())
3903             {
3904                foundBracket = true;
3905                break;
3906             }
3907 
3908             if (!c.moveToPreviousToken())
3909                break;
3910 
3911             // if we moved back off the start row, break
3912             if (c.getRow() != startRow)
3913                break;
3914          }
3915 
3916          // if we landed on a closing bracket, look for its match
3917          // and then continue search from that row. otherwise,
3918          // just look back a single row
3919          if (foundBracket && (c.valueEquals(")") || c.valueEquals("]")))
3920          {
3921             if (c.bwdToMatchingToken())
3922             {
3923                startRow = c.getRow();
3924             }
3925          }
3926       }
3927 
3928       // discover start of current statement
3929       while (startRow >= startRowLimit)
3930       {
3931          // if we've hit the start of the document, bail
3932          if (startRow <= 0)
3933             break;
3934 
3935          // if the row starts with a closing bracket, expand to its match
3936          if (rowStartsWithClosingBracket(startRow))
3937          {
3938             c.moveToStartOfRow(startRow);
3939             if (c.bwdToMatchingToken())
3940             {
3941                startRow = c.getRow();
3942                continue;
3943             }
3944          }
3945 
3946          // check for binary operator on previous line
3947          int prevRow = startRow - 1;
3948          if (rowEndsInBinaryOperatorOrOpenParen(prevRow))
3949          {
3950             // move token cursor to that row
3951             c.moveToEndOfRow(prevRow);
3952 
3953             // skip comments, operators, etc.
3954             while (c.hasType("text", "comment", "virtual-comment", "keyword.operator"))
3955             {
3956                if (c.isRightBracket())
3957                   break;
3958 
3959                if (!c.moveToPreviousToken())
3960                   break;
3961             }
3962 
3963             // if we landed on a closing bracket, look for its match
3964             // and then continue search from that row. otherwise,
3965             // just look back a single row
3966             if (c.valueEquals(")") || c.valueEquals("]"))
3967             {
3968                if (c.bwdToMatchingToken())
3969                {
3970                   startRow = c.getRow();
3971                   continue;
3972                }
3973             }
3974             else
3975             {
3976                startRow--;
3977                continue;
3978             }
3979          }
3980 
3981          // keep going over blank, commented lines
3982          if (rowIsEmptyOrComment(prevRow))
3983          {
3984             startRow--;
3985             continue;
3986          }
3987 
3988          // keep going if we're in a multiline string
3989          String state = getSession().getState(prevRow);
3990          if (state == "qstring" || state == "qqstring")
3991          {
3992             startRow--;
3993             continue;
3994          }
3995 
3996          // bail out of the loop -- we've found the start of the statement
3997          break;
3998       }
3999 
4000       // discover end of current statement -- we search from the inferred statement
4001       // start, so that we can perform counting of matching pairs of brackets
4002       endRow = startRow;
4003 
4004       // NOTE: '[[' is not tokenized as a single token in our Ace tokenizer,
4005       // so it is not included here (this shouldn't cause issues in practice
4006       // since balanced pairs of '[' and '[[' would still imply a correct count
4007       // of matched pairs of '[' anyhow)
4008       int parenCount = 0;   // '(', ')'
4009       int braceCount = 0;   // '{', '}'
4010       int bracketCount = 0; // '[', ']'
4011 
4012       while (endRow <= endRowLimit)
4013       {
4014          // continue search if we're in a multi-line string
4015          // (forego updating our bracket counts)
4016          String state = getSession().getState(endRow);
4017          if (state == "qstring" || state == "qqstring")
4018          {
4019             endRow++;
4020             continue;
4021          }
4022 
4023          // update bracket token counts
4024          JsArray<Token> tokens = getTokens(endRow);
4025          for (Token token : JsUtil.asIterable(tokens))
4026          {
4027             String value = token.getValue();
4028 
4029             parenCount += value == "(" ? 1 : 0;
4030             parenCount -= value == ")" ? 1 : 0;
4031 
4032             braceCount += value == "{" ? 1 : 0;
4033             braceCount -= value == "}" ? 1 : 0;
4034 
4035             bracketCount += value == "[" ? 1 : 0;
4036             bracketCount -= value == "]" ? 1 : 0;
4037          }
4038 
4039          // continue search if line ends with binary operator
4040          if (rowEndsInBinaryOperatorOrOpenParen(endRow) || rowIsEmptyOrComment(endRow))
4041          {
4042             endRow++;
4043             continue;
4044          }
4045 
4046          // continue search if we have unbalanced brackets
4047          if (parenCount > 0 || braceCount > 0 || bracketCount > 0)
4048          {
4049             endRow++;
4050             continue;
4051          }
4052 
4053          // we had balanced brackets and no trailing binary operator; bail
4054          break;
4055       }
4056 
4057       // if we're unbalanced at this point, that means we tried to
4058       // expand in an unclosed expression -- just execute the current
4059       // line rather than potentially executing unintended code
4060       if (parenCount + braceCount + bracketCount > 0)
4061          return Range.create(pos.getRow(), 0, pos.getRow() + 1, 0);
4062 
4063       // shrink selection for empty lines at borders
4064       while (startRow < endRow && rowIsEmptyOrComment(startRow))
4065          startRow++;
4066 
4067       while (endRow > startRow && rowIsEmptyOrComment(endRow))
4068          endRow--;
4069 
4070       // fixup for single-line execution
4071       if (startRow > endRow)
4072          startRow = endRow;
4073 
4074       // if we've captured the body of a function definition, expand
4075       // to include whole definition
4076       c.setRow(startRow);
4077       c.setOffset(0);
4078       if (c.valueEquals("{") &&
4079           c.moveToPreviousToken() &&
4080           c.valueEquals(")") &&
4081           c.bwdToMatchingToken() &&
4082           c.moveToPreviousToken() &&
4083           c.valueEquals("function") &&
4084           c.moveToPreviousToken() &&
4085           c.isLeftAssign())
4086       {
4087          ScopeFunction fn = getFunctionAtPosition(c.currentPosition(), false);
4088          if (fn != null)
4089             return Range.fromPoints(fn.getPreamble(), fn.getEnd());
4090       }
4091 
4092       // construct range
4093       int endColumn = getSession().getLine(endRow).length();
4094       Range range = Range.create(startRow, 0, endRow, endColumn);
4095 
4096       // return empty range if nothing to execute
4097       if (getTextForRange(range).trim().isEmpty())
4098          range = Range.fromPoints(pos, pos);
4099 
4100       return range;
4101    }
4102 
4103    // ---- Annotation related operations
4104 
4105    public JsArray<AceAnnotation> getAnnotations()
4106    {
4107       return widget_.getAnnotations();
4108    }
4109 
4110    public void setAnnotations(JsArray<AceAnnotation> annotations)
4111    {
4112       widget_.setAnnotations(annotations);
4113    }
4114 
4115    @Override
4116    public void removeMarkersAtCursorPosition()
4117    {
4118       widget_.removeMarkersAtCursorPosition();
4119    }
4120 
4121    @Override
4122    public void removeMarkersOnCursorLine()
4123    {
4124       widget_.removeMarkersOnCursorLine();
4125    }
4126 
4127    @Override
4128    public void removeMarkers(BiPredicate<AceAnnotation, Marker> predicate)
4129    {
4130       widget_.removeMarkers(predicate);
4131    }
4132 
4133    @Override
4134    public void removeMarkersAtWord(String word)
4135    {
4136       widget_.removeMarkersAtWord(word);
4137    }
4138 
4139    @Override
4140    public void showLint(JsArray<LintItem> lint)
4141    {
4142       widget_.showLint(lint);
4143    }
4144 
4145    @Override
4146    public void clearLint()
4147    {
4148       widget_.clearLint();
4149    }
4150 
4151    @Override
4152    public void showInfoBar(String message)
4153    {
4154       if (infoBar_ == null)
4155       {
4156          infoBar_ = new AceInfoBar(widget_);
4157          widget_.addKeyDownHandler(event ->
4158          {
4159             if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE)
4160                infoBar_.hide();
4161          });
4162       }
4163 
4164       infoBar_.setText(message);
4165       infoBar_.show();
4166    }
4167 
4168    public AnchoredRange createAnchoredRange(Position start, Position end)
4169    {
4170       return widget_.getEditor().getSession().createAnchoredRange(start, end);
4171    }
4172 
4173    public void insertRoxygenSkeleton()
4174    {
4175       getSession().getMode().getCodeModel().insertRoxygenSkeleton();
4176    }
4177 
4178    public long getLastModifiedTime()
4179    {
4180       return lastModifiedTime_;
4181    }
4182 
4183    public long getLastCursorChangedTime()
4184    {
4185       return lastCursorChangedTime_;
4186    }
4187 
4188    public int getFirstVisibleRow()
4189    {
4190       return widget_.getEditor().getFirstVisibleRow();
4191    }
4192 
4193    public int getLastVisibleRow()
4194    {
4195       return widget_.getEditor().getLastVisibleRow();
4196    }
4197 
4198    public void blockIndent()
4199    {
4200       widget_.getEditor().blockIndent();
4201    }
4202 
4203    public void blockOutdent()
4204    {
4205       widget_.getEditor().blockOutdent();
4206    }
4207 
4208    public ScreenCoordinates documentPositionToScreenCoordinates(Position position)
4209    {
4210       return widget_.getEditor().getRenderer().textToScreenCoordinates(position);
4211    }
4212 
4213    public Position screenCoordinatesToDocumentPosition(int pageX, int pageY)
4214    {
4215       return widget_.getEditor().getRenderer().screenToTextCoordinates(pageX, pageY);
4216    }
4217 
4218    public boolean isPositionVisible(Position position)
4219    {
4220       return widget_.getEditor().isRowFullyVisible(position.getRow());
4221    }
4222 
4223    @Override
4224    public void tokenizeDocument()
4225    {
4226       widget_.getEditor().tokenizeDocument();
4227    }
4228 
4229    @Override
4230    public void retokenizeDocument()
4231    {
4232       widget_.getEditor().retokenizeDocument();
4233    }
4234 
4235    @Override
4236    public Token getTokenAt(int row, int column)
4237    {
4238       return getSession().getTokenAt(row, column);
4239    }
4240 
4241    @Override
4242    public Token getTokenAt(Position position)
4243    {
4244       return getSession().getTokenAt(position);
4245    }
4246 
4247    @Override
4248    public JsArray<Token> getTokens(int row)
4249    {
4250       return getSession().getTokens(row);
4251    }
4252 
4253    @Override
4254    public TokenIterator createTokenIterator()
4255    {
4256       return createTokenIterator(null);
4257    }
4258 
4259    @Override
4260    public TokenIterator createTokenIterator(Position position)
4261    {
4262       TokenIterator it = TokenIterator.create(getSession());
4263       if (position == null)
4264          position = Position.create(0, 0);
4265       it.moveToPosition(position);
4266       return it;
4267    }
4268 
4269 
4270    @Override
4271    public void beginCollabSession(CollabEditStartParams params,
4272          DirtyState dirtyState)
4273    {
4274       // suppress external value change events while the editor's contents are
4275       // being swapped out for the contents of the collab session--otherwise
4276       // there's going to be a lot of flickering as dirty state (etc) try to
4277       // keep up
4278       valueChangeSuppressed_ = true;
4279 
4280       collab_.beginCollabSession(this, params, dirtyState, () -> valueChangeSuppressed_ = false);
4281    }
4282 
4283    @Override
4284    public boolean hasActiveCollabSession()
4285    {
4286       return collab_.hasActiveCollabSession(this);
4287    }
4288 
4289    @Override
4290    public boolean hasFollowingCollabSession()
4291    {
4292       return collab_.hasFollowingCollabSession(this);
4293    }
4294 
4295    public void endCollabSession()
4296    {
4297       collab_.endCollabSession(this);
4298    }
4299 
4300    @Override
4301    public void setDragEnabled(boolean enabled)
4302    {
4303       widget_.setDragEnabled(enabled);
4304    }
4305 
4306    @Override
4307    public boolean isSnippetsTabStopManagerActive()
4308    {
4309       return isSnippetsTabStopManagerActiveImpl(widget_.getEditor());
4310    }
4311 
4312    private static final native
4313    boolean isSnippetsTabStopManagerActiveImpl(AceEditorNative editor)
4314    /*-{
4315       return editor.tabstopManager != null;
4316    }-*/;
4317 
4318    private boolean onInsertSnippet()
4319    {
4320       return snippets_.onInsertSnippet();
4321    }
4322 
4323    public void toggleTokenInfo()
4324    {
4325       toggleTokenInfo(widget_.getEditor());
4326    }
4327 
4328    private static final native void toggleTokenInfo(AceEditorNative editor) /*-{
4329       if (editor.tokenTooltip && editor.tokenTooltip.destroy) {
4330          editor.tokenTooltip.destroy();
4331       } else {
4332          var TokenTooltip = $wnd.require("ace/token_tooltip").TokenTooltip;
4333          editor.tokenTooltip = new TokenTooltip(editor);
4334       }
4335    }-*/;
4336 
4337    @Override
4338    public void addLineWidget(final LineWidget widget)
4339    {
4340       // position the element far offscreen if it's above the currently
4341       // visible row; Ace does not position line widgets above the viewport
4342       // until the document is scrolled there
4343       if (widget.getRow() < getFirstVisibleRow())
4344       {
4345          widget.getElement().getStyle().setTop(-10000, Unit.PX);
4346 
4347          // set left/right values so that the widget consumes space; necessary
4348          // to get layout offsets inside the widget while rendering but before
4349          // it comes onscreen
4350          widget.getElement().getStyle().setLeft(48, Unit.PX);
4351          widget.getElement().getStyle().setRight(15, Unit.PX);
4352       }
4353 
4354       widget_.getLineWidgetManager().addLineWidget(widget);
4355       adjustScrollForLineWidget(widget);
4356       fireLineWidgetsChanged();
4357    }
4358 
4359    @Override
4360    public void removeLineWidget(LineWidget widget)
4361    {
4362       widget_.getLineWidgetManager().removeLineWidget(widget);
4363       fireLineWidgetsChanged();
4364    }
4365 
4366    @Override
4367    public void removeAllLineWidgets()
4368    {
4369       widget_.getLineWidgetManager().removeAllLineWidgets();
4370       fireLineWidgetsChanged();
4371    }
4372 
4373    @Override
4374    public void onLineWidgetChanged(LineWidget widget)
4375    {
4376       // if the widget is above the viewport, this size change might push it
4377       // into visibility, so push it offscreen first
4378       if (widget.getRow() + 1 < getFirstVisibleRow())
4379          widget.getElement().getStyle().setTop(-10000, Unit.PX);
4380 
4381       widget_.getLineWidgetManager().onWidgetChanged(widget);
4382       adjustScrollForLineWidget(widget);
4383       fireLineWidgetsChanged();
4384    }
4385 
4386    @Override
4387    public JsArray<LineWidget> getLineWidgets()
4388    {
4389       return widget_.getLineWidgetManager().getLineWidgets();
4390    }
4391 
4392    @Override
4393    public LineWidget getLineWidgetForRow(int row)
4394    {
4395       return widget_.getLineWidgetManager().getLineWidgetForRow(row);
4396    }
4397 
4398 
4399    @Override
4400    public boolean hasLineWidgets()
4401    {
4402       return widget_.getLineWidgetManager().hasLineWidgets();
4403    }
4404 
4405    private void adjustScrollForLineWidget(LineWidget w)
4406    {
4407       // the cursor is above the line widget, so the line widget is going
4408       // to change the cursor position; adjust the scroll position to hold
4409       // the cursor in place
4410       if (getCursorPosition().getRow() > w.getRow())
4411       {
4412          int delta = w.getElement().getOffsetHeight() - w.getRenderedHeight();
4413 
4414          // skip if no change to report
4415          if (delta == 0)
4416             return;
4417 
4418          // we adjust the scrolltop on the session since it knows the
4419          // currently queued scroll position; the renderer only knows the
4420          // actual scroll position, which may not reflect unrendered changes
4421          getSession().setScrollTop(getSession().getScrollTop() + delta);
4422       }
4423 
4424       // mark the current height as rendered
4425       w.setRenderedHeight(w.getElement().getOffsetHeight());
4426    }
4427 
4428    @Override
4429    public JsArray<ChunkDefinition> getChunkDefs()
4430    {
4431       // chunk definitions are populated at render time, so don't return any
4432       // if we haven't rendered yet
4433       if (!isRendered())
4434          return null;
4435 
4436       JsArray<ChunkDefinition> chunks = JsArray.createArray().cast();
4437       JsArray<LineWidget> lineWidgets = getLineWidgets();
4438       ScopeList scopes = new ScopeList(this);
4439       for (int i = 0; i<lineWidgets.length(); i++)
4440       {
4441          LineWidget lineWidget = lineWidgets.get(i);
4442          if (lineWidget.getType() == ChunkDefinition.LINE_WIDGET_TYPE)
4443          {
4444             ChunkDefinition chunk = lineWidget.getData();
4445             chunks.push(chunk.with(lineWidget.getRow(),
4446                   TextEditingTargetNotebook.getKnitrChunkLabel(
4447                         lineWidget.getRow(), this, scopes)));
4448          }
4449       }
4450 
4451       return chunks;
4452    }
4453 
4454 
4455    @Override
4456    public boolean isRendered()
4457    {
4458       return widget_.isRendered();
4459    }
4460 
4461    @Override
4462    public boolean showChunkOutputInline()
4463    {
4464       return showChunkOutputInline_;
4465    }
4466 
4467    @Override
4468    public void setShowChunkOutputInline(boolean show)
4469    {
4470       showChunkOutputInline_ = show;
4471    }
4472 
4473    /**
4474     * Set an aria-label on the input element
4475     * @param label
4476     */
4477    public final void setTextInputAriaLabel(String label)
4478    {
4479       widget_.getEditor().setTextInputAriaLabel(label);
4480    }
4481 
4482    public void setTabAlwaysMovesFocus()
4483    {
4484       widget_.setTabKeyMode(TabKeyMode.AlwaysMoveFocus);
4485    }
4486 
4487    public final void setScrollSpeed(double speed)
4488    {
4489       widget_.getEditor().setScrollSpeed(speed);
4490    }
4491 
4492    public final void setIndentedSoftWrap(boolean softWrap)
4493    {
4494       widget_.getEditor().setIndentedSoftWrap(softWrap);
4495    }
4496 
4497    private void fireLineWidgetsChanged()
4498    {
4499       AceEditor.this.fireEvent(new LineWidgetsChangedEvent());
4500    }
4501 
4502    public static AceEditor getLastFocusedEditor()
4503    {
4504       return s_lastFocusedEditor;
4505    }
4506 
4507    public static void clearLastFocusedEditor()
4508    {
4509       s_lastFocusedEditor = null;
4510    }
4511 
4512    private static class BackgroundTokenizer
4513    {
4514       public BackgroundTokenizer(final AceEditor editor)
4515       {
4516          editor_ = editor;
4517 
4518          timer_ = new Timer()
4519          {
4520             @Override
4521             public void run()
4522             {
4523                // Stop our timer if we've tokenized up to the end of the document.
4524                if (row_ >= editor_.getRowCount())
4525                {
4526                   editor_.fireEvent(new ScopeTreeReadyEvent(
4527                         editor_.getScopeTree(),
4528                         editor_.getCurrentScope()));
4529                   return;
4530                }
4531 
4532                row_ += ROWS_TOKENIZED_PER_ITERATION;
4533                row_ = Math.max(row_, editor.buildScopeTreeUpToRow(row_));
4534                timer_.schedule(DELAY_MS);
4535             }
4536          };
4537 
4538          editor_.addDocumentChangedHandler(event ->
4539          {
4540             if (editor_.hasCodeModelScopeTree())
4541             {
4542                row_ = event.getEvent().getRange().getStart().getRow();
4543                timer_.schedule(DELAY_MS);
4544             }
4545          });
4546       }
4547 
4548       public boolean isReady(int row)
4549       {
4550          return row < row_;
4551       }
4552 
4553       private final AceEditor editor_;
4554       private final Timer timer_;
4555 
4556       private int row_ = 0;
4557 
4558       private static final int DELAY_MS = 5;
4559       private static final int ROWS_TOKENIZED_PER_ITERATION = 200;
4560    }
4561 
4562    private class ScrollAnimator
4563                  implements AnimationScheduler.AnimationCallback
4564    {
4565       public ScrollAnimator(int targetY, int ms)
4566       {
4567          targetY_ = targetY;
4568          startY_ = widget_.getEditor().getRenderer().getScrollTop();
4569          delta_ = targetY_ - startY_;
4570          ms_ = ms;
4571          handle_ = AnimationScheduler.get().requestAnimationFrame(this);
4572       }
4573 
4574       public void complete()
4575       {
4576          handle_.cancel();
4577          scrollAnimator_ = null;
4578       }
4579 
4580       @Override
4581       public void execute(double timestamp)
4582       {
4583          if (startTime_ < 0)
4584             startTime_ = timestamp;
4585          double elapsed = timestamp - startTime_;
4586          if (elapsed >= ms_)
4587          {
4588             scrollToY(targetY_, 0);
4589             complete();
4590             return;
4591          }
4592 
4593          // ease-out exponential
4594          scrollToY((int)(delta_ * (-Math.pow(2, -10 * elapsed / ms_) + 1)) + startY_, 0);
4595 
4596          // request next frame
4597          handle_ = AnimationScheduler.get().requestAnimationFrame(this);
4598       }
4599 
4600       private final int ms_;
4601       private final int targetY_;
4602       private final int startY_;
4603       private final int delta_;
4604       private double startTime_ = -1;
4605       private AnimationScheduler.AnimationHandle handle_;
4606    }
4607 
4608    private static final int DEBUG_CONTEXT_LINES = 2;
4609    private final HandlerManager handlers_ = new HandlerManager(this);
4610    private final AceEditorWidget widget_;
4611    private final SnippetHelper snippets_;
4612    private final AceEditorMonitor monitor_;
4613    private ScrollAnimator scrollAnimator_;
4614    private CompletionManager completionManager_;
4615    private ScopeTreeManager scopes_;
4616    private CodeToolsServerOperations server_;
4617    private UserPrefs userPrefs_;
4618    private KeyboardTracker keyboard_;
4619    private CollabEditor collab_;
4620    private Commands commands_;
4621    private EventBus events_;
4622    private TextFileType fileType_;
4623    private boolean passwordMode_;
4624    private boolean useEmacsKeybindings_ = false;
4625    private boolean useVimMode_ = false;
4626    private RnwCompletionContext rnwContext_;
4627    private CppCompletionContext cppContext_;
4628    private CompletionContext context_ = null;
4629    private Integer lineHighlightMarkerId_ = null;
4630    private Integer lineDebugMarkerId_ = null;
4631    private Integer executionLine_ = null;
4632    private boolean valueChangeSuppressed_ = false;
4633    private AceInfoBar infoBar_;
4634    private boolean showChunkOutputInline_ = false;
4635    private BackgroundTokenizer backgroundTokenizer_;
4636    private final Vim vim_;
4637    private final AceBackgroundHighlighter bgChunkHighlighter_;
4638    private final AceEditorBackgroundLinkHighlighter bgLinkHighlighter_;
4639    private int scrollTarget_ = 0;
4640    private HandlerRegistration scrollCompleteReg_;
4641    private final AceEditorMixins mixins_;
4642    private final AceEditorEditLinesHelper editLines_;
4643    private EditorBehavior behavior_;
4644 
4645    private static final ExternalJavaScriptLoader getLoader(StaticDataResource release)
4646    {
4647       return getLoader(release, null);
4648    }
4649 
4650    private static final ExternalJavaScriptLoader getLoader(StaticDataResource release,
4651                                                            StaticDataResource debug)
4652    {
4653       if (debug == null || !SuperDevMode.isActive())
4654          return new ExternalJavaScriptLoader(release.getSafeUri().asString());
4655       else
4656          return new ExternalJavaScriptLoader(debug.getSafeUri().asString());
4657    }
4658 
4659 
4660    private static final ExternalJavaScriptLoader aceLoader_ =
4661          getLoader(AceResources.INSTANCE.acejs(), AceResources.INSTANCE.acejsUncompressed());
4662 
4663 
4664    private static final ExternalJavaScriptLoader aceSupportLoader_ =
4665          getLoader(AceResources.INSTANCE.acesupportjs());
4666 
4667    private static final ExternalJavaScriptLoader vimLoader_ =
4668          getLoader(AceResources.INSTANCE.keybindingVimJs(),
4669                    AceResources.INSTANCE.keybindingVimUncompressedJs());
4670 
4671    private static final ExternalJavaScriptLoader emacsLoader_ =
4672          getLoader(AceResources.INSTANCE.keybindingEmacsJs(),
4673                    AceResources.INSTANCE.keybindingEmacsUncompressedJs());
4674 
4675    private static final ExternalJavaScriptLoader extLanguageToolsLoader_ =
4676          getLoader(AceResources.INSTANCE.extLanguageTools(),
4677                    AceResources.INSTANCE.extLanguageToolsUncompressed());
4678 
4679    private boolean popupVisible_;
4680 
4681    private final DiagnosticsBackgroundPopup diagnosticsBgPopup_;
4682 
4683    private long lastCursorChangedTime_;
4684    private long lastModifiedTime_;
4685    private String yankedText_ = null;
4686 
4687    private int activeEditEventType_ = EditEvent.TYPE_NONE;
4688 
4689    private static AceEditor s_lastFocusedEditor = null;
4690 
4691    private final List<HandlerRegistration> editorEventListeners_;
4692 
4693 }
4694