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