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