1 /* 2 * PanmirrorEditorWidget.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 16 package org.rstudio.studio.client.panmirror; 17 18 19 import java.util.ArrayList; 20 21 import org.rstudio.core.client.CommandWithArg; 22 import org.rstudio.core.client.DebouncedCommand; 23 import org.rstudio.core.client.ExternalJavaScriptLoader; 24 import org.rstudio.core.client.HandlerRegistrations; 25 import org.rstudio.core.client.events.MouseDragHandler; 26 import org.rstudio.core.client.jsinterop.JsVoidFunction; 27 import org.rstudio.core.client.promise.PromiseWithProgress; 28 import org.rstudio.core.client.theme.res.ThemeResources; 29 import org.rstudio.core.client.widget.DockPanelSidebarDragHandler; 30 import org.rstudio.core.client.widget.HasFindReplace; 31 import org.rstudio.core.client.widget.IsHideableWidget; 32 import org.rstudio.studio.client.RStudioGinjector; 33 import org.rstudio.studio.client.application.events.ChangeFontSizeEvent; 34 import org.rstudio.studio.client.application.events.EventBus; 35 import org.rstudio.studio.client.palette.model.CommandPaletteEntryProvider; 36 import org.rstudio.studio.client.palette.model.CommandPaletteEntrySource; 37 import org.rstudio.studio.client.panmirror.command.PanmirrorMenuItem; 38 import org.rstudio.studio.client.panmirror.command.PanmirrorToolbar; 39 import org.rstudio.studio.client.panmirror.command.PanmirrorToolbarCommands; 40 import org.rstudio.studio.client.panmirror.command.PanmirrorToolbarMenu; 41 import org.rstudio.studio.client.panmirror.events.PanmirrorOutlineNavigationEvent; 42 import org.rstudio.studio.client.panmirror.events.PanmirrorOutlineVisibleEvent; 43 import org.rstudio.studio.client.panmirror.events.PanmirrorBlurEvent; 44 import org.rstudio.studio.client.panmirror.events.PanmirrorFindReplaceVisibleEvent; 45 import org.rstudio.studio.client.panmirror.events.PanmirrorFindReplaceVisibleEvent.Handler; 46 import org.rstudio.studio.client.panmirror.events.PanmirrorOutlineWidthEvent; 47 import org.rstudio.studio.client.panmirror.events.PanmirrorStateChangeEvent; 48 import org.rstudio.studio.client.panmirror.events.PanmirrorUpdatedEvent; 49 import org.rstudio.studio.client.panmirror.events.PanmirrorNavigationEvent; 50 import org.rstudio.studio.client.panmirror.events.PanmirrorFocusEvent; 51 import org.rstudio.studio.client.panmirror.findreplace.PanmirrorFindReplace; 52 import org.rstudio.studio.client.panmirror.findreplace.PanmirrorFindReplaceWidget; 53 import org.rstudio.studio.client.panmirror.format.PanmirrorFormat; 54 import org.rstudio.studio.client.panmirror.location.PanmirrorEditingLocation; 55 import org.rstudio.studio.client.panmirror.location.PanmirrorEditingOutlineLocation; 56 import org.rstudio.studio.client.panmirror.outline.PanmirrorOutlineItem; 57 import org.rstudio.studio.client.panmirror.outline.PanmirrorOutlineWidget; 58 import org.rstudio.studio.client.panmirror.pandoc.PanmirrorPandocFormat; 59 import org.rstudio.studio.client.panmirror.spelling.PanmirrorSpellingDoc; 60 import org.rstudio.studio.client.panmirror.theme.PanmirrorTheme; 61 import org.rstudio.studio.client.panmirror.theme.PanmirrorThemeCreator; 62 import org.rstudio.studio.client.panmirror.uitools.PanmirrorPandocFormatConfig; 63 import org.rstudio.studio.client.panmirror.uitools.PanmirrorUITools; 64 import org.rstudio.studio.client.panmirror.uitools.PanmirrorUIToolsFormat; 65 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs; 66 import org.rstudio.studio.client.workbench.prefs.model.UserState; 67 import org.rstudio.studio.client.workbench.views.source.editors.text.events.EditorThemeChangedEvent; 68 import org.rstudio.studio.client.workbench.views.source.editors.text.themes.AceTheme; 69 70 import com.google.gwt.dom.client.Style; 71 import com.google.gwt.event.shared.GwtEvent; 72 import com.google.gwt.event.shared.HandlerManager; 73 import com.google.gwt.event.shared.HandlerRegistration; 74 import com.google.gwt.layout.client.Layout.AnimationCallback; 75 import com.google.gwt.layout.client.Layout.Layer; 76 import com.google.gwt.user.client.Timer; 77 import com.google.gwt.user.client.ui.DockLayoutPanel; 78 import com.google.gwt.user.client.ui.HTML; 79 import com.google.gwt.user.client.ui.RequiresResize; 80 import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; 81 import com.google.inject.Inject; 82 83 import elemental2.core.JsObject; 84 import elemental2.promise.Promise; 85 import elemental2.promise.Promise.PromiseExecutorCallbackFn.RejectCallbackFn; 86 import elemental2.promise.Promise.PromiseExecutorCallbackFn.ResolveCallbackFn; 87 import jsinterop.annotations.JsOverlay; 88 import jsinterop.annotations.JsPackage; 89 import jsinterop.annotations.JsType; 90 import jsinterop.base.Js; 91 92 93 public class PanmirrorWidget extends DockLayoutPanel implements 94 IsHideableWidget, 95 RequiresResize, 96 CommandPaletteEntrySource, 97 PanmirrorUpdatedEvent.HasPanmirrorUpdatedHandlers, 98 PanmirrorStateChangeEvent.HasPanmirrorStateChangeHandlers, 99 PanmirrorOutlineVisibleEvent.HasPanmirrorOutlineVisibleHandlers, 100 PanmirrorOutlineWidthEvent.HasPanmirrorOutlineWidthHandlers, 101 PanmirrorFindReplaceVisibleEvent.HasPanmirrorFindReplaceVisibleHandlers, 102 PanmirrorNavigationEvent.HasPanmirrorNavigationHandlers, 103 PanmirrorBlurEvent.HasPanmirrorBlurHandlers, 104 PanmirrorFocusEvent.HasPanmirrorFocusHandlers 105 106 { 107 108 public static class Options 109 { 110 public boolean toolbar = true; 111 public boolean outline = false; 112 public double outlineWidth = 190; 113 public boolean border = false; 114 } 115 116 public static interface FormatSource 117 { getFormat(PanmirrorUIToolsFormat formatTools)118 PanmirrorFormat getFormat(PanmirrorUIToolsFormat formatTools); 119 } 120 create(PanmirrorContext context, FormatSource formatSource, PanmirrorOptions options, Options widgetOptions, int progressDelay, CommandWithArg<PanmirrorWidget> completed)121 public static void create(PanmirrorContext context, 122 FormatSource formatSource, 123 PanmirrorOptions options, 124 Options widgetOptions, 125 int progressDelay, 126 CommandWithArg<PanmirrorWidget> completed) { 127 128 PanmirrorWidget editorWidget = new PanmirrorWidget(widgetOptions); 129 130 Panmirror.load(() -> { 131 132 // get format (now that we have uiTools available) 133 PanmirrorFormat format = formatSource.getFormat(new PanmirrorUITools().format); 134 135 // create the editor 136 new PromiseWithProgress<>( 137 PanmirrorEditor.create(editorWidget.editorParent_.getElement(), context, format, options), 138 null, 139 progressDelay, 140 editor -> { 141 editorWidget.attachEditor(editor); 142 completed.execute(editorWidget); 143 } 144 ); 145 }); 146 } 147 PanmirrorWidget(Options options)148 private PanmirrorWidget(Options options) 149 { 150 super(Style.Unit.PX); 151 setSize("100%", "100%"); 152 153 // styles 154 if (options.border) 155 this.addStyleName(ThemeResources.INSTANCE.themeStyles().borderedIFrame()); 156 157 // toolbar 158 toolbar_ = new PanmirrorToolbar(); 159 addNorth(toolbar_, toolbar_.getHeight()); 160 setWidgetHidden(toolbar_, !options.toolbar); 161 162 163 // find replace 164 findReplace_ = new PanmirrorFindReplaceWidget(new PanmirrorFindReplaceWidget.Container() 165 { 166 @Override 167 public boolean isFindReplaceShowing() 168 { 169 return findReplaceShowing_; 170 } 171 @Override 172 public void showFindReplace(boolean show) 173 { 174 findReplaceShowing_ = show; 175 setWidgetHidden(findReplace_, !findReplaceShowing_); 176 177 toolbar_.setFindReplaceLatched(findReplaceShowing_); 178 179 PanmirrorFindReplaceVisibleEvent.fire(PanmirrorWidget.this, findReplaceShowing_); 180 181 if (findReplaceShowing_) 182 findReplace_.performFind(); 183 else 184 editor_.getFindReplace().clear(); 185 } 186 @Override 187 public PanmirrorFindReplace getFindReplace() 188 { 189 return editor_.getFindReplace(); 190 } 191 }); 192 addNorth(findReplace_, findReplace_.getHeight()); 193 setWidgetHidden(findReplace_, true); 194 195 // outline 196 outline_ = new PanmirrorOutlineWidget(); 197 addEast(outline_, options.outlineWidth); 198 setWidgetSize(outline_, options.outline ? options.outlineWidth : 0); 199 MouseDragHandler.addHandler( 200 outline_.getResizer(), 201 new DockPanelSidebarDragHandler(this, outline_) { 202 @Override 203 public void onResized(boolean visible) 204 { 205 // hide if we snapped to 0 width 206 if (!visible) 207 showOutline(false, 0); 208 209 // notify editor for layout 210 PanmirrorWidget.this.onResize(); 211 } 212 @Override 213 public void onPreferredWidth(double width) 214 { 215 PanmirrorOutlineWidthEvent.fire(PanmirrorWidget.this, width); 216 } 217 @Override 218 public void onPreferredVisibility(boolean visible) 219 { 220 PanmirrorOutlineVisibleEvent.fire(PanmirrorWidget.this, visible); 221 } 222 } 223 ); 224 225 RStudioGinjector.INSTANCE.injectMembers(this); 226 227 // editor 228 editorParent_ = new HTML(); 229 add(editorParent_); 230 } 231 232 @Inject initialize(UserPrefs userPrefs, UserState userState, EventBus events)233 public void initialize(UserPrefs userPrefs, 234 UserState userState, 235 EventBus events) 236 { 237 userPrefs_ = userPrefs; 238 userState_ = userState; 239 events_ = events; 240 } 241 isEditorAttached()242 public boolean isEditorAttached() 243 { 244 return super.isAttached() && editor_ != null; 245 } 246 attachEditor(PanmirrorEditor editor)247 private void attachEditor(PanmirrorEditor editor) 248 { 249 editor_ = editor; 250 251 // initialize css 252 syncEditorTheme(); 253 syncContentWidth(); 254 255 commands_ = new PanmirrorToolbarCommands(editor.commands()); 256 257 toolbar_.init(commands_, editor_.getMenus(), null); 258 259 outline_.addPanmirrorOutlineNavigationHandler(new PanmirrorOutlineNavigationEvent.Handler() { 260 @Override 261 public void onPanmirrorOutlineNavigation(PanmirrorOutlineNavigationEvent event) 262 { 263 editor_.navigate(PanmirrorNavigationType.Id, event.getId(), true); 264 } 265 }); 266 267 editorEventUnsubscribe_.add(editor_.subscribe(PanmirrorEvent.Update, (data) -> { 268 fireEvent(new PanmirrorUpdatedEvent()); 269 })); 270 271 // don't update outline eagerly (wait for 500ms delay in typing) 272 DebouncedCommand updateOutineOnIdle = new DebouncedCommand(500) 273 { 274 @Override 275 protected void execute() 276 { 277 updateOutline(); 278 } 279 }; 280 281 // don't sync ui eagerly (wait for 300ms delay in typing) 282 DebouncedCommand syncUI = new DebouncedCommand(300) { 283 284 @Override 285 protected void execute() 286 { 287 if (editor_ != null) 288 { 289 // sync toolbar commands 290 if (toolbar_ != null) 291 toolbar_.sync(false); 292 293 // sync outline selection 294 outline_.updateSelection(editor_.getSelection()); 295 } 296 } 297 298 }; 299 300 editorEventUnsubscribe_.add(editor_.subscribe(PanmirrorEvent.StateChange, (data) -> { 301 302 // sync ui (debounced) 303 syncUI.nudge(); 304 305 // fire to clients 306 fireEvent(new PanmirrorStateChangeEvent()); 307 })); 308 309 editorEventUnsubscribe_.add(editor_.subscribe(PanmirrorEvent.OutlineChange, (data) -> { 310 311 // sync outline 312 updateOutineOnIdle.nudge(); 313 314 })); 315 316 editorEventUnsubscribe_.add(editor_.subscribe(PanmirrorEvent.Navigate, (data) -> { 317 318 PanmirrorNavigation nav = Js.uncheckedCast(data); 319 fireEvent(new PanmirrorNavigationEvent(nav)); 320 })); 321 322 editorEventUnsubscribe_.add(editor_.subscribe(PanmirrorEvent.Blur, (data) -> { 323 fireEvent(new PanmirrorBlurEvent()); 324 })); 325 326 editorEventUnsubscribe_.add(editor_.subscribe(PanmirrorEvent.Focus, (data) -> { 327 fireEvent(new PanmirrorFocusEvent()); 328 })); 329 330 registrations_.add(events_.addHandler(EditorThemeChangedEvent.TYPE, 331 (EditorThemeChangedEvent event) -> { 332 new Timer() 333 { 334 @Override 335 public void run() 336 { 337 toolbar_.sync(true); 338 syncEditorTheme(event.getTheme()); 339 } 340 }.schedule(500); 341 })); 342 343 registrations_.add(events_.addHandler(ChangeFontSizeEvent.TYPE, (event) -> { 344 syncEditorTheme(); 345 })); 346 347 registrations_.add( 348 userPrefs_.visualMarkdownEditingMaxContentWidth().addValueChangeHandler((event) -> { 349 syncContentWidth(); 350 })); 351 352 registrations_.add( 353 userPrefs_.visualMarkdownEditingFontSizePoints().addValueChangeHandler((event) -> { 354 syncEditorTheme(); 355 }) 356 ); 357 } 358 destroy()359 public void destroy() 360 { 361 // detach registrations (outline events) 362 registrations_.removeHandler(); 363 364 if (editor_ != null) 365 { 366 // unsubscribe from editor events 367 for (JsVoidFunction unsubscribe : editorEventUnsubscribe_) 368 unsubscribe.call(); 369 editorEventUnsubscribe_.clear(); 370 371 // destroy editor 372 editor_.destroy(); 373 editor_ = null; 374 } 375 } 376 setTitle(String title)377 public void setTitle(String title) 378 { 379 editor_.setTitle(title); 380 } 381 getTitle()382 public String getTitle() 383 { 384 return editor_.getTitle(); 385 } 386 setMarkdown(String code, PanmirrorWriterOptions options, boolean emitUpdate, int progressDelay, CommandWithArg<JsObject> completed)387 public void setMarkdown(String code, 388 PanmirrorWriterOptions options, 389 boolean emitUpdate, 390 int progressDelay, 391 CommandWithArg<JsObject> completed) 392 { 393 new PromiseWithProgress<>( 394 editor_.setMarkdown(code, options, emitUpdate), 395 null, 396 progressDelay, 397 completed 398 ); 399 } 400 getMarkdown(PanmirrorWriterOptions options, int progressDelay, CommandWithArg<JsObject> completed)401 public void getMarkdown(PanmirrorWriterOptions options, int progressDelay, CommandWithArg<JsObject> completed) { 402 new PromiseWithProgress<>( 403 editor_.getMarkdown(options), 404 null, 405 progressDelay, 406 completed 407 ); 408 } 409 getCanonical(String code, PanmirrorWriterOptions options, int progressDelay, CommandWithArg<String> completed)410 public void getCanonical(String code, PanmirrorWriterOptions options, int progressDelay, CommandWithArg<String> completed) 411 { 412 new PromiseWithProgress<>( 413 editor_.getCanonical(code, options), 414 null, 415 progressDelay, 416 completed 417 ); 418 } 419 isInitialDoc()420 public boolean isInitialDoc() 421 { 422 return editor_.isInitialDoc(); 423 } 424 getFindReplace()425 public HasFindReplace getFindReplace() 426 { 427 return findReplace_; 428 } 429 getSpellingDoc()430 public PanmirrorSpellingDoc getSpellingDoc() 431 { 432 return editor_.getSpellingDoc(); 433 } 434 spellingInvalidateAllWords()435 public void spellingInvalidateAllWords() 436 { 437 if (editor_ != null) 438 editor_.spellingInvalidateAllWords(); 439 } 440 spellingInvalidateWord(String word)441 public void spellingInvalidateWord(String word) 442 { 443 if (editor_ != null) 444 editor_.spellingInvalidateWord(word); 445 } 446 showOutline(boolean show, double width)447 public void showOutline(boolean show, double width) 448 { 449 showOutline(show, width, false); 450 } 451 showOutline(boolean show, double width, boolean animate)452 public void showOutline(boolean show, double width, boolean animate) 453 { 454 // update outline if we are showing 455 if (show) 456 updateOutline(); 457 458 boolean visible = getWidgetSize(outline_) > 0; 459 if (show != visible) 460 { 461 setWidgetSize(outline_, show ? width : 0); 462 outline_.setAriaVisible(show); 463 if (animate) 464 { 465 int duration = (userPrefs_.reducedMotion().getValue() ? 0 : 500); 466 animate(duration, new AnimationCallback() { 467 @Override 468 public void onAnimationComplete() 469 { 470 resizeEditor(); 471 } 472 @Override 473 public void onLayout(Layer layer, double progress) 474 { 475 resizeEditor(); 476 } 477 }); 478 } 479 else 480 { 481 forceLayout(); 482 resizeEditor(); 483 } 484 } 485 } 486 showToolbar(boolean show)487 public void showToolbar(boolean show) 488 { 489 setWidgetHidden(toolbar_, !show); 490 } 491 insertChunk(String chunkPlaceholder, int rowOffset, int colOffset)492 public void insertChunk(String chunkPlaceholder, int rowOffset, int colOffset) 493 { 494 editor_.insertChunk(chunkPlaceholder, rowOffset, colOffset); 495 } 496 execCommand(String id)497 public boolean execCommand(String id) 498 { 499 return commands_.exec(id); 500 } 501 navigate(String type, String location, boolean recordCurrent)502 public void navigate(String type, String location, boolean recordCurrent) 503 { 504 // perform navigation 505 editor_.navigate(type, location, recordCurrent); 506 } 507 setKeybindings(PanmirrorKeybindings keybindings)508 public void setKeybindings(PanmirrorKeybindings keybindings) 509 { 510 editor_.setKeybindings(keybindings); 511 commands_ = new PanmirrorToolbarCommands(editor_.commands()); 512 toolbar_.init(commands_, editor_.getMenus(), null); 513 } 514 getHTML()515 public String getHTML() 516 { 517 return editor_.getHTML(); 518 } 519 getEditorFormat()520 public PanmirrorFormat getEditorFormat() 521 { 522 return editor_.getEditorFormat(); 523 } 524 getPandocFormat()525 public PanmirrorPandocFormat getPandocFormat() 526 { 527 return editor_.getPandocFormat(); 528 } 529 getPandocFormatConfig(boolean isRmd)530 public PanmirrorPandocFormatConfig getPandocFormatConfig(boolean isRmd) 531 { 532 return editor_.getPandocFormatConfig(isRmd); 533 } 534 getSelectedText()535 public String getSelectedText() 536 { 537 return editor_.getSelectedText(); 538 } 539 replaceSelection(String value)540 public void replaceSelection(String value) 541 { 542 editor_.replaceSelection(value); 543 } 544 getSelection()545 public PanmirrorSelection getSelection() 546 { 547 return editor_.getSelection(); 548 } 549 getEditingLocation()550 public PanmirrorEditingLocation getEditingLocation() 551 { 552 return editor_.getEditingLocation(); 553 } 554 getEditingOutlineLocation()555 public PanmirrorEditingOutlineLocation getEditingOutlineLocation() 556 { 557 return editor_.getEditingOutlineLocation(); 558 } 559 getOutline()560 public PanmirrorOutlineItem[] getOutline() 561 { 562 return editor_.getOutline(); 563 } 564 setEditingLocation( PanmirrorEditingOutlineLocation outlineLocation, PanmirrorEditingLocation previousLocation)565 public void setEditingLocation( 566 PanmirrorEditingOutlineLocation outlineLocation, 567 PanmirrorEditingLocation previousLocation) 568 { 569 editor_.setEditingLocation(outlineLocation, previousLocation); 570 } 571 focus()572 public void focus() 573 { 574 editor_.focus(); 575 } 576 blur()577 public void blur() 578 { 579 editor_.blur(); 580 } 581 showContextMenu(PanmirrorMenuItem[] items, int clientX, int clientY)582 public Promise<Boolean> showContextMenu(PanmirrorMenuItem[] items, int clientX, int clientY) 583 { 584 return new Promise<>((ResolveCallbackFn<Boolean> resolve, RejectCallbackFn reject) -> { 585 586 final PanmirrorToolbarMenu menu = new PanmirrorToolbarMenu(commands_); 587 menu.addCloseHandler((event) -> { 588 resolve.onInvoke(true); 589 }); 590 menu.addItems(items); 591 menu.setPopupPositionAndShow(new PositionCallback() { 592 @Override 593 public void setPosition(int offsetWidth, int offsetHeight) 594 { 595 menu.setPopupPosition(clientX, clientY); 596 } 597 }); 598 }); 599 } 600 getYamlFrontMatter()601 public String getYamlFrontMatter() 602 { 603 return editor_.getYamlFrontMatter(); 604 } 605 applyYamlFrontMatter(String yaml)606 public void applyYamlFrontMatter(String yaml) 607 { 608 editor_.applyYamlFrontMatter(yaml); 609 } 610 activateDevTools()611 public void activateDevTools() 612 { 613 ProseMirrorDevTools.load(() -> { 614 editor_.enableDevTools(ProseMirrorDevTools.applyDevTools); 615 }); 616 } 617 devToolsLoaded()618 public boolean devToolsLoaded() 619 { 620 return ProseMirrorDevTools.isLoaded(); 621 } 622 623 @Override addPanmirrorUpdatedHandler(PanmirrorUpdatedEvent.Handler handler)624 public HandlerRegistration addPanmirrorUpdatedHandler(PanmirrorUpdatedEvent.Handler handler) 625 { 626 return handlers_.addHandler(PanmirrorUpdatedEvent.getType(), handler); 627 } 628 629 @Override addPanmirrorStateChangeHandler(PanmirrorStateChangeEvent.Handler handler)630 public HandlerRegistration addPanmirrorStateChangeHandler(PanmirrorStateChangeEvent.Handler handler) 631 { 632 return handlers_.addHandler(PanmirrorStateChangeEvent.getType(), handler); 633 } 634 635 @Override addPanmirrorOutlineWidthHandler(PanmirrorOutlineWidthEvent.Handler handler)636 public HandlerRegistration addPanmirrorOutlineWidthHandler(PanmirrorOutlineWidthEvent.Handler handler) 637 { 638 return handlers_.addHandler(PanmirrorOutlineWidthEvent.getType(), handler); 639 } 640 641 @Override addPanmirrorOutlineVisibleHandler(PanmirrorOutlineVisibleEvent.Handler handler)642 public HandlerRegistration addPanmirrorOutlineVisibleHandler(PanmirrorOutlineVisibleEvent.Handler handler) 643 { 644 return handlers_.addHandler(PanmirrorOutlineVisibleEvent.getType(), handler); 645 } 646 647 @Override addPanmirrorFindReplaceVisibleHandler(Handler handler)648 public HandlerRegistration addPanmirrorFindReplaceVisibleHandler(Handler handler) 649 { 650 return handlers_.addHandler(PanmirrorFindReplaceVisibleEvent.getType(), handler); 651 } 652 653 @Override addPanmirrorNavigationHandler(PanmirrorNavigationEvent.Handler handler)654 public HandlerRegistration addPanmirrorNavigationHandler(PanmirrorNavigationEvent.Handler handler) 655 { 656 return handlers_.addHandler(PanmirrorNavigationEvent.getType(), handler); 657 } 658 659 @Override addPanmirrorBlurHandler(org.rstudio.studio.client.panmirror.events.PanmirrorBlurEvent.Handler handler)660 public HandlerRegistration addPanmirrorBlurHandler(org.rstudio.studio.client.panmirror.events.PanmirrorBlurEvent.Handler handler) 661 { 662 return handlers_.addHandler(PanmirrorBlurEvent.getType(), handler); 663 } 664 665 @Override addPanmirrorFocusHandler(org.rstudio.studio.client.panmirror.events.PanmirrorFocusEvent.Handler handler)666 public HandlerRegistration addPanmirrorFocusHandler(org.rstudio.studio.client.panmirror.events.PanmirrorFocusEvent.Handler handler) 667 { 668 return handlers_.addHandler(PanmirrorFocusEvent.getType(), handler); 669 } 670 671 672 @Override fireEvent(GwtEvent<?> event)673 public void fireEvent(GwtEvent<?> event) 674 { 675 handlers_.fireEvent(event); 676 } 677 678 679 @Override onResize()680 public void onResize() 681 { 682 if (toolbar_ != null) { 683 toolbar_.onResize(); 684 } 685 if (findReplace_ != null) { 686 findReplace_.onResize(); 687 } 688 if (editor_ != null) { 689 resizeEditor(); 690 } 691 } 692 693 @Override getPaletteEntryProvider()694 public CommandPaletteEntryProvider getPaletteEntryProvider() 695 { 696 return commands_; 697 } 698 updateOutline()699 private void updateOutline() 700 { 701 if (editor_ != null) // would be null during teardown of tab 702 { 703 PanmirrorOutlineItem[] outline = editor_.getOutline(); 704 outline_.updateOutline(outline); 705 outline_.updateSelection(editor_.getSelection()); 706 } 707 } 708 resizeEditor()709 private void resizeEditor() 710 { 711 editor_.resize(); 712 } 713 syncEditorTheme()714 private void syncEditorTheme() 715 { 716 syncEditorTheme(userState_.theme().getGlobalValue().cast()); 717 } 718 syncEditorTheme(AceTheme theme)719 private void syncEditorTheme(AceTheme theme) 720 { 721 PanmirrorTheme panmirrorTheme = PanmirrorThemeCreator.themeFromEditorTheme(theme, userPrefs_); 722 editor_.applyTheme(panmirrorTheme);; 723 } 724 syncContentWidth()725 private void syncContentWidth() 726 { 727 int contentWidth = userPrefs_.visualMarkdownEditingMaxContentWidth().getValue(); 728 editor_.setMaxContentWidth(contentWidth, 20); 729 } 730 731 732 private UserPrefs userPrefs_ = null; 733 private UserState userState_ = null; 734 private EventBus events_ = null; 735 736 private PanmirrorToolbar toolbar_ = null; 737 private boolean findReplaceShowing_ = false; 738 private PanmirrorFindReplaceWidget findReplace_ = null; 739 private PanmirrorOutlineWidget outline_ = null; 740 private HTML editorParent_ = null; 741 742 private PanmirrorEditor editor_ = null; 743 private PanmirrorToolbarCommands commands_ = null; 744 745 private final HandlerManager handlers_ = new HandlerManager(this); 746 private final HandlerRegistrations registrations_ = new HandlerRegistrations(); 747 private final ArrayList<JsVoidFunction> editorEventUnsubscribe_ = new ArrayList<>(); 748 } 749 750 751 @JsType(isNative = true, namespace = JsPackage.GLOBAL) 752 class ProseMirrorDevTools 753 { 754 @JsOverlay 755 public static void load(ExternalJavaScriptLoader.Callback onLoaded) 756 { 757 devtoolsLoader_.addCallback(onLoaded); 758 } 759 760 @JsOverlay 761 public static boolean isLoaded() 762 { 763 return devtoolsLoader_.isLoaded(); 764 } 765 766 public static JsObject applyDevTools; 767 768 @JsOverlay 769 private static final ExternalJavaScriptLoader devtoolsLoader_ = 770 new ExternalJavaScriptLoader("js/panmirror/prosemirror-dev-tools.min.js"); 771 } 772 773 774 775 776 777