<commit_before>/*
 * TextEditingTargetWidget.java
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
package org.rstudio.studio.client.workbench.views.source.editors.text;

import java.util.List;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.*;

import org.rstudio.core.client.BrowseCap;
import org.rstudio.core.client.MathUtil;
import org.rstudio.core.client.command.AppCommand;
import org.rstudio.core.client.command.KeyboardShortcut;
import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.events.EnsureHeightEvent;
import org.rstudio.core.client.events.EnsureHeightHandler;
import org.rstudio.core.client.events.EnsureVisibleEvent;
import org.rstudio.core.client.events.EnsureVisibleHandler;
import org.rstudio.core.client.events.MouseDragHandler;
import org.rstudio.core.client.layout.RequiresVisibilityChanged;
import org.rstudio.core.client.theme.res.ThemeResources;
import org.rstudio.core.client.widget.*;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.ImageMenuItem;
import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
import org.rstudio.studio.client.common.filetypes.TextFileType;
import org.rstudio.studio.client.common.icons.StandardIcons;
import org.rstudio.studio.client.rmarkdown.RmdOutput;
import org.rstudio.studio.client.rmarkdown.events.RmdOutputFormatChangedEvent;
import org.rstudio.studio.client.rsconnect.RSConnect;
import org.rstudio.studio.client.rsconnect.ui.RSConnectPublishButton;
import org.rstudio.studio.client.shiny.model.ShinyApplicationParams;
import org.rstudio.studio.client.shiny.ui.ShinyViewerTypePopupMenu;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.model.SessionUtils;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
import org.rstudio.studio.client.workbench.views.edit.ui.EditDialog;
import org.rstudio.studio.client.workbench.views.source.DocumentOutlineWidget;
import org.rstudio.studio.client.workbench.views.source.PanelWithToolbars;
import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetToolbar;
import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTarget.Display;
import org.rstudio.studio.client.workbench.views.source.editors.text.findreplace.FindReplaceBar;
import org.rstudio.studio.client.workbench.views.source.editors.text.rmd.TextEditingTargetNotebook;
import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBar;
import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBarWidget;
import org.rstudio.studio.client.workbench.views.source.model.DocUpdateSentinel;
import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;

public class TextEditingTargetWidget
      extends ResizeComposite
      implements Display, RequiresVisibilityChanged
{
   public TextEditingTargetWidget(final TextEditingTarget target,
                                  DocUpdateSentinel docUpdateSentinel,
                                  Commands commands,
                                  UIPrefs uiPrefs,
                                  FileTypeRegistry fileTypeRegistry,
                                  final DocDisplay editor,
                                  TextFileType fileType,
                                  String extendedType,
                                  EventBus events,
                                  Session session)
   {
      target_ = target;
      docUpdateSentinel_ = docUpdateSentinel;
      commands_ = commands;
      uiPrefs_ = uiPrefs;
      session_ = session;
      fileTypeRegistry_ = fileTypeRegistry;
      editor_ = editor;
      extendedType_ = extendedType;
      sourceOnSave_ = new CheckBox();
      srcOnSaveLabel_ =
                  new CheckboxLabel(sourceOnSave_, "Source on Save").getLabel();
      statusBar_ = new StatusBarWidget();
      shinyViewerMenu_ = RStudioGinjector.INSTANCE.getShinyViewerTypePopupMenu();
      handlerManager_ = new HandlerManager(this);
      
      findReplace_ = new TextEditingTargetFindReplace(
         new TextEditingTargetFindReplace.Container()
         {  
            @Override
            public AceEditor getEditor()
            {
               return (AceEditor)editor_;
            }
            
            @Override
            public void insertFindReplace(FindReplaceBar findReplaceBar)
            {
               Widget beforeWidget = null;
               if (warningBar_ != null && warningBar_.isAttached())
                  beforeWidget = warningBar_;
               panel_.insertNorth(findReplaceBar,
                                  findReplaceBar.getHeight(),
                                  beforeWidget);
               
            }
            
            @Override
            public void removeFindReplace(FindReplaceBar findReplaceBar)
            {
               panel_.remove(findReplaceBar);
            } 
         });
      
      editorPanel_ = new DockLayoutPanel(Unit.PX);
      docOutlineWidget_ = new DocumentOutlineWidget(target);
      
      editorPanel_.addEast(docOutlineWidget_, 0);
      editorPanel_.add(editor.asWidget());
      
      MouseDragHandler.addHandler(
            docOutlineWidget_.getLeftSeparator(),
            new MouseDragHandler()
            {
               double initialWidth_ = 0;
               
               @Override
               public void beginDrag(MouseDownEvent event)
               {
                  initialWidth_ = editorPanel_.getWidgetSize(docOutlineWidget_);
               }
               
               @Override
               public void onDrag(MouseDragEvent event)
               {
                  double initialWidth = initialWidth_;
                  double xDiff = event.getTotalDelta().getMouseX();
                  double newSize = initialWidth + xDiff;
                  
                  // We allow an extra pixel here just to 'hide' the border
                  // if the outline is maximized, since the 'separator'
                  // lives as part of the outline instead of 'between' the
                  // two widgets
                  double maxSize = editorPanel_.getOffsetWidth() + 1;
                  
                  double clamped = MathUtil.clamp(newSize, 0, maxSize);
                  
                  // If the size is below '5px', interpret this as a request
                  // to close the outline widget.
                  if (clamped < 5)
                     clamped = 0;
                  
                  editorPanel_.setWidgetSize(docOutlineWidget_, clamped);
                  toggleDocOutlineButton_.setLatched(clamped != 0);
                  editor_.onResize();
               }
               
               @Override
               public void endDrag()
               {
                  double size = editorPanel_.getWidgetSize(docOutlineWidget_);
                  
                  // We only update the preferred size if the user hasn't closed
                  // the widget.
                  if (size > 0)
                     target_.setPreferredOutlineWidgetSize(size);
                  
                  target_.setPreferredOutlineWidgetVisibility(size > 0);
               }
            });
      
      panel_ = new PanelWithToolbars(
            toolbar_ = createToolbar(fileType),
            editorPanel_,
            statusBar_);
      
      adaptToFileType(fileType);
      
      editor.addFocusHandler(new FocusHandler()
      {
         @Override
         public void onFocus(FocusEvent event)
         {
            toggleDocOutlineButton_.setLatched(
                  docOutlineWidget_.getOffsetWidth() > 0);
         }
      });

      initWidget(panel_);
   }
   
   public void initWidgetSize()
   {
      if (target_.getPreferredOutlineWidgetVisibility())
      {
         double size = target_.getPreferredOutlineWidgetSize();
         editorPanel_.setWidgetSize(docOutlineWidget_, size);
      }
   }
   
   public void toggleDocumentOutline()
   {
      toggleDocOutlineButton_.click();
   }
   
   private StatusBarWidget statusBar_;

   private Toolbar createToolbar(TextFileType fileType)
   {
      Toolbar toolbar = new EditingTargetToolbar(commands_, true);

      toolbar.addLeftWidget(commands_.saveSourceDoc().createToolbarButton());
      sourceOnSave_.getElement().getStyle().setMarginRight(0, Unit.PX);
      toolbar.addLeftWidget(sourceOnSave_);
      srcOnSaveLabel_.getElement().getStyle().setMarginRight(9, Unit.PX);
      toolbar.addLeftWidget(srcOnSaveLabel_);

      toolbar.addLeftSeparator();
      toolbar.addLeftWidget(commands_.checkSpelling().createToolbarButton());
      
      toolbar.addLeftWidget(findReplace_.createFindReplaceButton());
      toolbar.addLeftWidget(createCodeTransformMenuButton());
      
      notebookSeparatorWidget_ = toolbar.addLeftSeparator();
      toolbar.addLeftWidget(notebookToolbarButton_ = 
            commands_.compileNotebook().createToolbarButton());
      
      int mod = BrowseCap.hasMetaKey() ? KeyboardShortcut.META : 
         KeyboardShortcut.CTRL;
      String cmdText = 
        new KeyboardShortcut(mod + KeyboardShortcut.SHIFT, 'K').toString(true);
      cmdText = DomUtils.htmlToText(cmdText);
      notebookToolbarButton_.setTitle("Compile Notebook (" + cmdText + ")");
      
      texSeparatorWidget_ = toolbar.addLeftSeparator();
      toolbar.addLeftWidget(texToolbarButton_ = createLatexFormatButton());
      
      toolbar.addLeftSeparator();
      toolbar.addLeftWidget(previewHTMLButton_ = commands_.previewHTML().createToolbarButton());
      knitDocumentButton_ = commands_.knitDocument().createToolbarButton(false);
      knitDocumentButton_.getElement().getStyle().setMarginRight(0, Unit.PX);
      toolbar.addLeftWidget(knitDocumentButton_);
      toolbar.addLeftWidget(compilePdfButton_ = commands_.compilePDF().createToolbarButton());
      rmdFormatButton_ = new ToolbarPopupMenuButton(false, true);
      toolbar.addLeftWidget(rmdFormatButton_);
      
      ToolbarPopupMenu rmdOptionsMenu = new ToolbarPopupMenu();
      rmdOptionsMenu.addItem(commands_.editRmdFormatOptions().createMenuItem(false));
      
      rmdOptionsButton_ = new ToolbarButton(
            null,  
            StandardIcons.INSTANCE.options(),
            rmdOptionsMenu, 
            false);
      
      toolbar.addLeftWidget(rmdOptionsButton_);

      toolbar.addLeftSeparator();
      toolbar.addLeftWidget(commands_.synctexSearch().createToolbarButton());

      toolbar.addRightWidget(insertChunkButton_ = commands_.insertChunk().createToolbarButton());
      toolbar.addRightWidget(runButton_ = commands_.executeCode().createToolbarButton(false));
      toolbar.addRightSeparator();
      toolbar.addRightWidget(runLastButton_ = commands_.executeLastCode().createToolbarButton(false));
      toolbar.addRightWidget(goToPrevButton_ = commands_.goToPrevSection().createToolbarButton(false));
      toolbar.addRightWidget(goToNextButton_ = commands_.goToNextSection().createToolbarButton(false));
      toolbar.addRightSeparator();
      final String SOURCE_BUTTON_TITLE = "Source the active document"; 
      
      sourceButton_ = new ToolbarButton(
            "Source", 
            commands_.sourceActiveDocument().getImageResource(), 
            new ClickHandler() 
            {
               @Override
               public void onClick(ClickEvent event)
               {
                  if (uiPrefs_.sourceWithEcho().getValue())
                     commands_.sourceActiveDocumentWithEcho().execute();
                  else
                     commands_.sourceActiveDocument().execute();
               }
            });
      
      sourceButton_.setTitle(SOURCE_BUTTON_TITLE);
      toolbar.addRightWidget(sourceButton_);
      
      uiPrefs_.sourceWithEcho().addValueChangeHandler(
                                       new ValueChangeHandler<Boolean>() {
         @Override
         public void onValueChange(ValueChangeEvent<Boolean> event)
         {
            if (event.getValue())
               sourceButton_.setTitle(SOURCE_BUTTON_TITLE + " (with echo)");
            else
               sourceButton_.setTitle(SOURCE_BUTTON_TITLE);
         }
      });
            
      ToolbarPopupMenu sourceMenu = new ToolbarPopupMenu();
      sourceMenu.addItem(commands_.sourceActiveDocument().createMenuItem(false));
      sourceMenu.addItem(commands_.sourceActiveDocumentWithEcho().createMenuItem(false));
         
      sourceMenuButton_ = new ToolbarButton(sourceMenu, true);
      toolbar.addRightWidget(sourceMenuButton_);  

      //toolbar.addRightSeparator();
     
      toolbar.addRightWidget(chunksRunButton_ = commands_.executeCode().createToolbarButton(false));
      ToolbarPopupMenu chunksMenu = new ToolbarPopupMenu();
      chunksMenu.addItem(commands_.executeCode().createMenuItem(false));
      if (uiPrefs_.showRmdChunkOutputInline().getValue())
      {
         chunksMenu.addSeparator();
         chunksMenu.addItem(new UIPrefMenuItem<Boolean>(
               uiPrefs_.autoRunSetupChunk(), true, "Run Setup Chunk Automatically", 
               uiPrefs_));
      }
      chunksMenu.addSeparator();
      chunksMenu.addItem(commands_.executeSetupChunk().createMenuItem(false));
      chunksMenu.addItem(commands_.executePreviousChunks().createMenuItem(false));
      chunksMenu.addItem(commands_.executeCurrentChunk().createMenuItem(false));
      chunksMenu.addItem(commands_.executeNextChunk().createMenuItem(false));
      chunksMenu.addSeparator();
      chunksMenu.addItem(commands_.executeAllCode().createMenuItem(false));
      chunksButton_ = new ToolbarButton(
                       chunksMenu, 
                       true);
      toolbar.addRightWidget(chunksButton_);
      
      ToolbarPopupMenu shinyLaunchMenu = shinyViewerMenu_;
      shinyLaunchButton_ = new ToolbarButton(
                       shinyLaunchMenu, 
                       true);
      shinyLaunchButton_.setVisible(false);
      toolbar.addRightWidget(shinyLaunchButton_);
      if (SessionUtils.showPublishUi(session_, uiPrefs_))
      {
         toolbar.addRightSeparator();
         publishButton_ = new RSConnectPublishButton(
               RSConnect.CONTENT_TYPE_APP, false, null);
         toolbar.addRightWidget(publishButton_);
      }
      
      toggleDocOutlineButton_ = new LatchingToolbarButton(
         "",
            StandardIcons.INSTANCE.outline(),
            new ClickHandler()
            {
               @Override
               public void onClick(ClickEvent event)
               {
                  final double initialSize = editorPanel_.getWidgetSize(docOutlineWidget_);
                  
                  // Clicking the icon toggles the outline widget's visibility. The
                  // 'destination' below is the width we would like to set -- we
                  // animate to that position for a slightly nicer visual treatment.
                  final double destination = docOutlineWidget_.getOffsetWidth() > 5
                        ? 0
                        : target_.getPreferredOutlineWidgetSize();
                  
                  // Update tooltip ('Show'/'Hide' depending on current visibility)
                  String title = toggleDocOutlineButton_.getTitle();
                  if (destination != 0)
                     title = title.replace("Show ", "Hide ");
                  else
                     title = title.replace("Hide ", "Show ");
                  toggleDocOutlineButton_.setTitle(title);
                  
                  toggleDocOutlineButton_.setLatched(destination != 0);
                  
                  new Animation()
                  {
                     @Override
                     protected void onUpdate(double progress)
                     {
                        double size =
                              destination * progress +
                              initialSize * (1 - progress);
                        editorPanel_.setWidgetSize(docOutlineWidget_, size);
                        editor_.onResize();
                     }
                     
                     @Override
                     protected void onComplete()
                     {
                        target_.setPreferredOutlineWidgetVisibility(destination != 0);
                     }
                  }.run(500);
               }
            });
      
      // Time-out setting the latch just to ensure the document outline
      // has actually been appropriately rendered.
      new Timer()
      {
         @Override
         public void run()
         {
            String title = commands_.toggleDocumentOutline().getTooltip();
            title = editorPanel_.getWidgetSize(docOutlineWidget_) > 0
                  ? title.replace("Show ", "Hide ")
                  : title.replace("Hide ", "Show ");
            toggleDocOutlineButton_.setTitle(title);
            toggleDocOutlineButton_.setLatched(docOutlineWidget_.getOffsetWidth() > 0);
         }
      }.schedule(100);
      
      toolbar.addRightSeparator();
      toolbar.addRightWidget(toggleDocOutlineButton_);
      
      return toolbar;
   }
   
   private ToolbarButton createLatexFormatButton()
   {
      ToolbarPopupMenu texMenu = new TextEditingTargetLatexFormatMenu(editor_,
                                                                      uiPrefs_);
    
      ToolbarButton texButton = new ToolbarButton(
                           "Format", 
                           fileTypeRegistry_.getIconForFilename("foo.tex"), 
                           texMenu, 
                           false);
      return texButton;
   }
   
   private Widget createCodeTransformMenuButton()
   {
      if (codeTransform_ == null)
      {
         ImageResource icon = ThemeResources.INSTANCE.codeTransform();

         ToolbarPopupMenu menu = new ToolbarPopupMenu();
         menu.addItem(commands_.codeCompletion().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.goToHelp().createMenuItem(false));
         menu.addItem(commands_.goToFunctionDefinition().createMenuItem(false));
         menu.addItem(commands_.findUsages().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.extractFunction().createMenuItem(false));
         menu.addItem(commands_.extractLocalVariable().createMenuItem(false));
         menu.addItem(commands_.renameInScope().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.reflowComment().createMenuItem(false));
         menu.addItem(commands_.commentUncomment().createMenuItem(false));
         menu.addItem(commands_.insertRoxygenSkeleton().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.reindent().createMenuItem(false));
         menu.addItem(commands_.reformatCode().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.showDiagnosticsActiveDocument().createMenuItem(false));
         menu.addItem(commands_.showDiagnosticsProject().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.profileCode().createMenuItem(false));
         codeTransform_ = new ToolbarButton("", icon, menu);
         codeTransform_.setTitle("Code Tools");
      }
      
      return codeTransform_;
   }
   
   public void adaptToExtendedFileType(String extendedType)
   {
      extendedType_ = extendedType;
      adaptToFileType(editor_.getFileType());
   }

   public void adaptToFileType(TextFileType fileType)
   {
      editor_.setFileType(fileType);
      boolean canCompilePdf = fileType.canCompilePDF();
      boolean canKnitToHTML = fileType.canKnitToHTML();
      boolean canCompileNotebook = fileType.canCompileNotebook();
      boolean canSource = fileType.canSource();
      boolean canSourceWithEcho = fileType.canSourceWithEcho();
      boolean canSourceOnSave = fileType.canSourceOnSave();
      boolean canExecuteCode = fileType.canExecuteCode();
      boolean canExecuteChunks = fileType.canExecuteChunks();
      boolean isPlainMarkdown = fileType.isPlainMarkdown();
      boolean isCpp = fileType.isCpp();
      boolean isScript = fileType.isScript();
      boolean isRMarkdown2 = extendedType_.equals("rmarkdown");
      boolean canPreviewFromR = fileType.canPreviewFromR();
      
      // don't show the run buttons for cpp files, or R files in Shiny
      runButton_.setVisible(canExecuteCode && !canExecuteChunks && !isCpp && 
            !isShinyFile());
      runLastButton_.setVisible(runButton_.isVisible() && !canExecuteChunks);
      
      // chunk oriented buttons     
      insertChunkButton_.setVisible(canExecuteChunks);
      goToPrevButton_.setVisible(fileType.canGoNextPrevSection());
      goToNextButton_.setVisible(fileType.canGoNextPrevSection());
      
      sourceOnSave_.setVisible(canSourceOnSave);
      srcOnSaveLabel_.setVisible(canSourceOnSave);
      if (fileType.isRd() || canPreviewFromR)
         srcOnSaveLabel_.setText(fileType.getPreviewButtonText() + " on Save");
      else
         srcOnSaveLabel_.setText("Source on Save");
      codeTransform_.setVisible(
            (canExecuteCode && !fileType.canAuthorContent()) ||
            fileType.isC() || fileType.isStan());   
     
      sourceButton_.setVisible(canSource && !isPlainMarkdown);
      sourceMenuButton_.setVisible(canSourceWithEcho && 
                                   !isPlainMarkdown && 
                                   !isScript &&
                                   !canPreviewFromR);
   
      texSeparatorWidget_.setVisible(canCompilePdf);
      texToolbarButton_.setVisible(canCompilePdf);
      compilePdfButton_.setVisible(canCompilePdf);
      chunksButton_.setVisible(canExecuteChunks);
      chunksRunButton_.setVisible(canExecuteChunks);
      
      notebookSeparatorWidget_.setVisible(canCompileNotebook);
      notebookToolbarButton_.setVisible(canCompileNotebook);
      
      knitDocumentButton_.setVisible(canKnitToHTML);
      
      setRmdFormatButtonVisible(isRMarkdown2);
      rmdOptionsButton_.setVisible(isRMarkdown2);
      rmdOptionsButton_.setEnabled(isRMarkdown2);
     
      if (isShinyFile())
      {
         sourceOnSave_.setVisible(false);
         srcOnSaveLabel_.setVisible(false);
         runButton_.setVisible(false);
         sourceMenuButton_.setVisible(false);
         chunksButton_.setVisible(false);
         shinyLaunchButton_.setVisible(true);
         setSourceButtonFromShinyState();
      }
      else
      {
         shinyLaunchButton_.setVisible(false);
         setSourceButtonFromScriptState(isScript, 
                                        canPreviewFromR,
                                        fileType.getPreviewButtonText());
      }
      
      // set the content type based on the extended type
      setPublishPath(extendedType_, publishPath_);
      
      // make toggle outline visible if we have a scope tree
      toggleDocOutlineButton_.setVisible(fileType.canShowScopeTree());
      if (!fileType.canShowScopeTree())
      {
         editorPanel_.setWidgetSize(docOutlineWidget_, 0);
         toggleDocOutlineButton_.setLatched(false);
      }
      
      toolbar_.invalidateSeparators();
   }
   
   private boolean isShinyFile()
   {
      return extendedType_ != null &&
             extendedType_.startsWith(SourceDocument.XT_SHINY_PREFIX);
   }

   public HasValue<Boolean> getSourceOnSave()
   {
      return sourceOnSave_;
   }

   public void ensureVisible()
   {
      fireEvent(new EnsureVisibleEvent());
   }
   
   @Override
   public void onResize() 
   {
      super.onResize();
      manageToolbarSizes();
      ResizeEvent.fire(this, getOffsetWidth(), getOffsetHeight());
     
   }

   private void manageToolbarSizes()
   {
      // sometimes width is passed in as 0 (not sure why)
      int width = getOffsetWidth();
      if (width == 0)
         return;
      
      texToolbarButton_.setText(width < 520 ? "" : "Format");
      runButton_.setText(((width < 480) || isShinyFile()) ? "" : "Run");
      compilePdfButton_.setText(width < 450 ? "" : "Compile PDF");
      previewHTMLButton_.setText(width < 450 ? "" : previewCommandText_);
      knitDocumentButton_.setText(width < 450 ? "" : knitCommandText_);
      
      if (editor_.getFileType().isRd() || editor_.getFileType().canPreviewFromR())
      {
         String preview = editor_.getFileType().getPreviewButtonText();
         srcOnSaveLabel_.setText(width < 450 ? preview : preview + " on Save");
      }
      else
         srcOnSaveLabel_.setText(width < 450 ? "Source" : "Source on Save");
      sourceButton_.setText(width < 400 ? "" : sourceCommandText_);
      chunksRunButton_.setText(width < 400 ? "" : "Run");
   }
   
   
   public void showWarningBar(String warning)
   {
      if (warningBar_ == null)
      {
         warningBar_ = new InfoBar(InfoBar.WARNING, new ClickHandler() {
            @Override
            public void onClick(ClickEvent event)
            {
               hideWarningBar();
            }
            
         });
      }
      warningBar_.setText(warning);
      panel_.insertNorth(warningBar_, warningBar_.getHeight(), null);
   }

   public void hideWarningBar()
   {
      if (warningBar_ != null)
      {
         panel_.remove(warningBar_);
      }
   }

   public void showFindReplace(boolean defaultForward)
   {
      findReplace_.showFindReplace(defaultForward);
   }
   
   @Override
   public void findNext()
   {
      findReplace_.findNext();
   }
   
   @Override
   public void findSelectAll()
   {
      findReplace_.selectAll();
   }

   @Override
   public void findPrevious()
   {
      findReplace_.findPrevious();
   }
   
   @Override
   public void findFromSelection()
   {
      findReplace_.findFromSelection();
   }
   
   @Override
   public void replaceAndFind()
   {
      findReplace_.replaceAndFind();
   }

   public void onActivate()
   {
      editor_.onActivate();
      
      Scheduler.get().scheduleDeferred(new ScheduledCommand() {

         @Override
         public void execute()
         {
            manageToolbarSizes(); 
         }
      });
   }

   public void setFontSize(double size)
   {
      editor_.setFontSize(size);
   }

   public StatusBar getStatusBar()
   {
      return statusBar_;
   }

   @Override
   public void debug_dumpContents()
   {
      String dump = editor_.debug_getDocumentDump();
      new EditDialog(dump, false, false, new ProgressOperationWithInput<String>()
      {
         @Override
         public void execute(String input, ProgressIndicator indicator)
         {
            indicator.onCompleted();
         }
      }).showModal();
   }

   @Override
   public void debug_importDump()
   {
      new EditDialog("", false, false, new ProgressOperationWithInput<String>()
      {
         @Override
         public void execute(String input, ProgressIndicator indicator)
         {
            indicator.onCompleted();
            if (input == null)
               return;

            input = input.replaceAll("[ \\r\\n]+", " ");
            String[] chars = input.split(" ");

            StringBuilder sb = new StringBuilder();
            for (String s : chars)
            {
               if (s.equals("."))
                  sb.append('\n');
               else
                  sb.append((char)Integer.parseInt(s));
            }

            editor_.debug_setSessionValueDirectly(sb.toString());
         }
      }).showModal();
   }

   // Called by the owning TextEditingTarget to notify the widget that the 
   // Shiny application associated with this widget has changed state.
   @Override
   public void onShinyApplicationStateChanged(String state)
   {
      shinyAppState_ = state;
      setSourceButtonFromShinyState();
   }
   
   @Override
   public void setFormatOptions(TextFileType fileType,
                                boolean showRmdFormatMenu,
                                boolean canEditFormatOptions,
                                List<String> options, 
                                List<String> values, 
                                List<String> extensions, 
                                String selectedOption)
   { 
      if (!canEditFormatOptions)
      {
         setFormatText("");
      }
      
      setRmdFormatButtonVisible(showRmdFormatMenu);
      rmdFormatButton_.setEnabled(showRmdFormatMenu);
      rmdFormatButton_.clearMenu();
      int parenPos = selectedOption.indexOf('(');
         if (parenPos != -1)
            selectedOption = selectedOption.substring(0, parenPos).trim();
 
         setFormatText(selectedOption);
         String prefix = fileType.isPlainMarkdown() ? "Preview " : "Knit to ";
      for (int i = 0; i < Math.min(options.size(), values.size()); i++)
      {
         String ext = extensions.get(i);
         ImageResource img = ext != null ? 
               fileTypeRegistry_.getIconForFilename("output." + ext) :
               fileTypeRegistry_.getIconForFilename("Makefile");
         final String valueName = values.get(i);
         ScheduledCommand cmd = new ScheduledCommand()
         {
            @Override
            public void execute()
            {
               handlerManager_.fireEvent(
                     new RmdOutputFormatChangedEvent(valueName));
            }
         };
         MenuItem item = ImageMenuItem.create(img, 
                                              prefix + options.get(i), 
                                              cmd, 2);
         rmdFormatButton_.addMenuItem(item, values.get(i));
      }
      
      if (session_.getSessionInfo().getKnitParamsAvailable())
      {
         final AppCommand knitWithParams = commands_.knitWithParameters();
         rmdFormatButton_.addSeparator();
         ScheduledCommand cmd = new ScheduledCommand()
         {<commit_msg>indentation and spacing in TextEditingTargetWidget<commit_after>/*
 * TextEditingTargetWidget.java
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
package org.rstudio.studio.client.workbench.views.source.editors.text;

import java.util.List;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.*;

import org.rstudio.core.client.BrowseCap;
import org.rstudio.core.client.MathUtil;
import org.rstudio.core.client.command.AppCommand;
import org.rstudio.core.client.command.KeyboardShortcut;
import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.events.EnsureHeightEvent;
import org.rstudio.core.client.events.EnsureHeightHandler;
import org.rstudio.core.client.events.EnsureVisibleEvent;
import org.rstudio.core.client.events.EnsureVisibleHandler;
import org.rstudio.core.client.events.MouseDragHandler;
import org.rstudio.core.client.layout.RequiresVisibilityChanged;
import org.rstudio.core.client.theme.res.ThemeResources;
import org.rstudio.core.client.widget.*;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.ImageMenuItem;
import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
import org.rstudio.studio.client.common.filetypes.TextFileType;
import org.rstudio.studio.client.common.icons.StandardIcons;
import org.rstudio.studio.client.rmarkdown.RmdOutput;
import org.rstudio.studio.client.rmarkdown.events.RmdOutputFormatChangedEvent;
import org.rstudio.studio.client.rsconnect.RSConnect;
import org.rstudio.studio.client.rsconnect.ui.RSConnectPublishButton;
import org.rstudio.studio.client.shiny.model.ShinyApplicationParams;
import org.rstudio.studio.client.shiny.ui.ShinyViewerTypePopupMenu;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.model.SessionUtils;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
import org.rstudio.studio.client.workbench.views.edit.ui.EditDialog;
import org.rstudio.studio.client.workbench.views.source.DocumentOutlineWidget;
import org.rstudio.studio.client.workbench.views.source.PanelWithToolbars;
import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetToolbar;
import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTarget.Display;
import org.rstudio.studio.client.workbench.views.source.editors.text.findreplace.FindReplaceBar;
import org.rstudio.studio.client.workbench.views.source.editors.text.rmd.TextEditingTargetNotebook;
import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBar;
import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBarWidget;
import org.rstudio.studio.client.workbench.views.source.model.DocUpdateSentinel;
import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;

public class TextEditingTargetWidget
      extends ResizeComposite
      implements Display, RequiresVisibilityChanged
{
   public TextEditingTargetWidget(final TextEditingTarget target,
                                  DocUpdateSentinel docUpdateSentinel,
                                  Commands commands,
                                  UIPrefs uiPrefs,
                                  FileTypeRegistry fileTypeRegistry,
                                  final DocDisplay editor,
                                  TextFileType fileType,
                                  String extendedType,
                                  EventBus events,
                                  Session session)
   {
      target_ = target;
      docUpdateSentinel_ = docUpdateSentinel;
      commands_ = commands;
      uiPrefs_ = uiPrefs;
      session_ = session;
      fileTypeRegistry_ = fileTypeRegistry;
      editor_ = editor;
      extendedType_ = extendedType;
      sourceOnSave_ = new CheckBox();
      srcOnSaveLabel_ =
                  new CheckboxLabel(sourceOnSave_, "Source on Save").getLabel();
      statusBar_ = new StatusBarWidget();
      shinyViewerMenu_ = RStudioGinjector.INSTANCE.getShinyViewerTypePopupMenu();
      handlerManager_ = new HandlerManager(this);
      
      findReplace_ = new TextEditingTargetFindReplace(
         new TextEditingTargetFindReplace.Container()
         {  
            @Override
            public AceEditor getEditor()
            {
               return (AceEditor)editor_;
            }
            
            @Override
            public void insertFindReplace(FindReplaceBar findReplaceBar)
            {
               Widget beforeWidget = null;
               if (warningBar_ != null && warningBar_.isAttached())
                  beforeWidget = warningBar_;
               panel_.insertNorth(findReplaceBar,
                                  findReplaceBar.getHeight(),
                                  beforeWidget);
               
            }
            
            @Override
            public void removeFindReplace(FindReplaceBar findReplaceBar)
            {
               panel_.remove(findReplaceBar);
            } 
         });
      
      editorPanel_ = new DockLayoutPanel(Unit.PX);
      docOutlineWidget_ = new DocumentOutlineWidget(target);
      
      editorPanel_.addEast(docOutlineWidget_, 0);
      editorPanel_.add(editor.asWidget());
      
      MouseDragHandler.addHandler(
            docOutlineWidget_.getLeftSeparator(),
            new MouseDragHandler()
            {
               double initialWidth_ = 0;
               
               @Override
               public void beginDrag(MouseDownEvent event)
               {
                  initialWidth_ = editorPanel_.getWidgetSize(docOutlineWidget_);
               }
               
               @Override
               public void onDrag(MouseDragEvent event)
               {
                  double initialWidth = initialWidth_;
                  double xDiff = event.getTotalDelta().getMouseX();
                  double newSize = initialWidth + xDiff;
                  
                  // We allow an extra pixel here just to 'hide' the border
                  // if the outline is maximized, since the 'separator'
                  // lives as part of the outline instead of 'between' the
                  // two widgets
                  double maxSize = editorPanel_.getOffsetWidth() + 1;
                  
                  double clamped = MathUtil.clamp(newSize, 0, maxSize);
                  
                  // If the size is below '5px', interpret this as a request
                  // to close the outline widget.
                  if (clamped < 5)
                     clamped = 0;
                  
                  editorPanel_.setWidgetSize(docOutlineWidget_, clamped);
                  toggleDocOutlineButton_.setLatched(clamped != 0);
                  editor_.onResize();
               }
               
               @Override
               public void endDrag()
               {
                  double size = editorPanel_.getWidgetSize(docOutlineWidget_);
                  
                  // We only update the preferred size if the user hasn't closed
                  // the widget.
                  if (size > 0)
                     target_.setPreferredOutlineWidgetSize(size);
                  
                  target_.setPreferredOutlineWidgetVisibility(size > 0);
               }
            });
      
      panel_ = new PanelWithToolbars(
            toolbar_ = createToolbar(fileType),
            editorPanel_,
            statusBar_);
      
      adaptToFileType(fileType);
      
      editor.addFocusHandler(new FocusHandler()
      {
         @Override
         public void onFocus(FocusEvent event)
         {
            toggleDocOutlineButton_.setLatched(
                  docOutlineWidget_.getOffsetWidth() > 0);
         }
      });

      initWidget(panel_);
   }
   
   public void initWidgetSize()
   {
      if (target_.getPreferredOutlineWidgetVisibility())
      {
         double size = target_.getPreferredOutlineWidgetSize();
         editorPanel_.setWidgetSize(docOutlineWidget_, size);
      }
   }
   
   public void toggleDocumentOutline()
   {
      toggleDocOutlineButton_.click();
   }
   
   private StatusBarWidget statusBar_;

   private Toolbar createToolbar(TextFileType fileType)
   {
      Toolbar toolbar = new EditingTargetToolbar(commands_, true);

      toolbar.addLeftWidget(commands_.saveSourceDoc().createToolbarButton());
      sourceOnSave_.getElement().getStyle().setMarginRight(0, Unit.PX);
      toolbar.addLeftWidget(sourceOnSave_);
      srcOnSaveLabel_.getElement().getStyle().setMarginRight(9, Unit.PX);
      toolbar.addLeftWidget(srcOnSaveLabel_);

      toolbar.addLeftSeparator();
      toolbar.addLeftWidget(commands_.checkSpelling().createToolbarButton());
      
      toolbar.addLeftWidget(findReplace_.createFindReplaceButton());
      toolbar.addLeftWidget(createCodeTransformMenuButton());
      
      notebookSeparatorWidget_ = toolbar.addLeftSeparator();
      toolbar.addLeftWidget(notebookToolbarButton_ = 
            commands_.compileNotebook().createToolbarButton());
      
      int mod = BrowseCap.hasMetaKey() ? KeyboardShortcut.META : 
         KeyboardShortcut.CTRL;
      String cmdText = 
        new KeyboardShortcut(mod + KeyboardShortcut.SHIFT, 'K').toString(true);
      cmdText = DomUtils.htmlToText(cmdText);
      notebookToolbarButton_.setTitle("Compile Notebook (" + cmdText + ")");
      
      texSeparatorWidget_ = toolbar.addLeftSeparator();
      toolbar.addLeftWidget(texToolbarButton_ = createLatexFormatButton());
      
      toolbar.addLeftSeparator();
      toolbar.addLeftWidget(previewHTMLButton_ = commands_.previewHTML().createToolbarButton());
      knitDocumentButton_ = commands_.knitDocument().createToolbarButton(false);
      knitDocumentButton_.getElement().getStyle().setMarginRight(0, Unit.PX);
      toolbar.addLeftWidget(knitDocumentButton_);
      toolbar.addLeftWidget(compilePdfButton_ = commands_.compilePDF().createToolbarButton());
      rmdFormatButton_ = new ToolbarPopupMenuButton(false, true);
      toolbar.addLeftWidget(rmdFormatButton_);
      
      ToolbarPopupMenu rmdOptionsMenu = new ToolbarPopupMenu();
      rmdOptionsMenu.addItem(commands_.editRmdFormatOptions().createMenuItem(false));
      
      rmdOptionsButton_ = new ToolbarButton(
            null,  
            StandardIcons.INSTANCE.options(),
            rmdOptionsMenu, 
            false);
      
      toolbar.addLeftWidget(rmdOptionsButton_);

      toolbar.addLeftSeparator();
      toolbar.addLeftWidget(commands_.synctexSearch().createToolbarButton());

      toolbar.addRightWidget(insertChunkButton_ = commands_.insertChunk().createToolbarButton());
      toolbar.addRightWidget(runButton_ = commands_.executeCode().createToolbarButton(false));
      toolbar.addRightSeparator();
      toolbar.addRightWidget(runLastButton_ = commands_.executeLastCode().createToolbarButton(false));
      toolbar.addRightWidget(goToPrevButton_ = commands_.goToPrevSection().createToolbarButton(false));
      toolbar.addRightWidget(goToNextButton_ = commands_.goToNextSection().createToolbarButton(false));
      toolbar.addRightSeparator();
      final String SOURCE_BUTTON_TITLE = "Source the active document"; 
      
      sourceButton_ = new ToolbarButton(
            "Source", 
            commands_.sourceActiveDocument().getImageResource(), 
            new ClickHandler() 
            {
               @Override
               public void onClick(ClickEvent event)
               {
                  if (uiPrefs_.sourceWithEcho().getValue())
                     commands_.sourceActiveDocumentWithEcho().execute();
                  else
                     commands_.sourceActiveDocument().execute();
               }
            });
      
      sourceButton_.setTitle(SOURCE_BUTTON_TITLE);
      toolbar.addRightWidget(sourceButton_);
      
      uiPrefs_.sourceWithEcho().addValueChangeHandler(
                                       new ValueChangeHandler<Boolean>() {
         @Override
         public void onValueChange(ValueChangeEvent<Boolean> event)
         {
            if (event.getValue())
               sourceButton_.setTitle(SOURCE_BUTTON_TITLE + " (with echo)");
            else
               sourceButton_.setTitle(SOURCE_BUTTON_TITLE);
         }
      });
            
      ToolbarPopupMenu sourceMenu = new ToolbarPopupMenu();
      sourceMenu.addItem(commands_.sourceActiveDocument().createMenuItem(false));
      sourceMenu.addItem(commands_.sourceActiveDocumentWithEcho().createMenuItem(false));
         
      sourceMenuButton_ = new ToolbarButton(sourceMenu, true);
      toolbar.addRightWidget(sourceMenuButton_);  

      //toolbar.addRightSeparator();
     
      toolbar.addRightWidget(chunksRunButton_ = commands_.executeCode().createToolbarButton(false));
      ToolbarPopupMenu chunksMenu = new ToolbarPopupMenu();
      chunksMenu.addItem(commands_.executeCode().createMenuItem(false));
      if (uiPrefs_.showRmdChunkOutputInline().getValue())
      {
         chunksMenu.addSeparator();
         chunksMenu.addItem(new UIPrefMenuItem<Boolean>(
               uiPrefs_.autoRunSetupChunk(), true, "Run Setup Chunk Automatically", 
               uiPrefs_));
      }
      chunksMenu.addSeparator();
      chunksMenu.addItem(commands_.executeSetupChunk().createMenuItem(false));
      chunksMenu.addItem(commands_.executePreviousChunks().createMenuItem(false));
      chunksMenu.addItem(commands_.executeCurrentChunk().createMenuItem(false));
      chunksMenu.addItem(commands_.executeNextChunk().createMenuItem(false));
      chunksMenu.addSeparator();
      chunksMenu.addItem(commands_.executeAllCode().createMenuItem(false));
      chunksButton_ = new ToolbarButton(
                       chunksMenu, 
                       true);
      toolbar.addRightWidget(chunksButton_);
      
      ToolbarPopupMenu shinyLaunchMenu = shinyViewerMenu_;
      shinyLaunchButton_ = new ToolbarButton(
                       shinyLaunchMenu, 
                       true);
      shinyLaunchButton_.setVisible(false);
      toolbar.addRightWidget(shinyLaunchButton_);
      if (SessionUtils.showPublishUi(session_, uiPrefs_))
      {
         toolbar.addRightSeparator();
         publishButton_ = new RSConnectPublishButton(
               RSConnect.CONTENT_TYPE_APP, false, null);
         toolbar.addRightWidget(publishButton_);
      }
      
      toggleDocOutlineButton_ = new LatchingToolbarButton(
         "",
            StandardIcons.INSTANCE.outline(),
            new ClickHandler()
            {
               @Override
               public void onClick(ClickEvent event)
               {
                  final double initialSize = editorPanel_.getWidgetSize(docOutlineWidget_);
                  
                  // Clicking the icon toggles the outline widget's visibility. The
                  // 'destination' below is the width we would like to set -- we
                  // animate to that position for a slightly nicer visual treatment.
                  final double destination = docOutlineWidget_.getOffsetWidth() > 5
                        ? 0
                        : target_.getPreferredOutlineWidgetSize();
                  
                  // Update tooltip ('Show'/'Hide' depending on current visibility)
                  String title = toggleDocOutlineButton_.getTitle();
                  if (destination != 0)
                     title = title.replace("Show ", "Hide ");
                  else
                     title = title.replace("Hide ", "Show ");
                  toggleDocOutlineButton_.setTitle(title);
                  
                  toggleDocOutlineButton_.setLatched(destination != 0);
                  
                  new Animation()
                  {
                     @Override
                     protected void onUpdate(double progress)
                     {
                        double size =
                              destination * progress +
                              initialSize * (1 - progress);
                        editorPanel_.setWidgetSize(docOutlineWidget_, size);
                        editor_.onResize();
                     }
                     
                     @Override
                     protected void onComplete()
                     {
                        target_.setPreferredOutlineWidgetVisibility(destination != 0);
                     }
                  }.run(500);
               }
            });
      
      // Time-out setting the latch just to ensure the document outline
      // has actually been appropriately rendered.
      new Timer()
      {
         @Override
         public void run()
         {
            String title = commands_.toggleDocumentOutline().getTooltip();
            title = editorPanel_.getWidgetSize(docOutlineWidget_) > 0
                  ? title.replace("Show ", "Hide ")
                  : title.replace("Hide ", "Show ");
            toggleDocOutlineButton_.setTitle(title);
            toggleDocOutlineButton_.setLatched(docOutlineWidget_.getOffsetWidth() > 0);
         }
      }.schedule(100);
      
      toolbar.addRightSeparator();
      toolbar.addRightWidget(toggleDocOutlineButton_);
      
      return toolbar;
   }
   
   private ToolbarButton createLatexFormatButton()
   {
      ToolbarPopupMenu texMenu = new TextEditingTargetLatexFormatMenu(editor_,
                                                                      uiPrefs_);
    
      ToolbarButton texButton = new ToolbarButton(
                           "Format", 
                           fileTypeRegistry_.getIconForFilename("foo.tex"), 
                           texMenu, 
                           false);
      return texButton;
   }
   
   private Widget createCodeTransformMenuButton()
   {
      if (codeTransform_ == null)
      {
         ImageResource icon = ThemeResources.INSTANCE.codeTransform();

         ToolbarPopupMenu menu = new ToolbarPopupMenu();
         menu.addItem(commands_.codeCompletion().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.goToHelp().createMenuItem(false));
         menu.addItem(commands_.goToFunctionDefinition().createMenuItem(false));
         menu.addItem(commands_.findUsages().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.extractFunction().createMenuItem(false));
         menu.addItem(commands_.extractLocalVariable().createMenuItem(false));
         menu.addItem(commands_.renameInScope().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.reflowComment().createMenuItem(false));
         menu.addItem(commands_.commentUncomment().createMenuItem(false));
         menu.addItem(commands_.insertRoxygenSkeleton().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.reindent().createMenuItem(false));
         menu.addItem(commands_.reformatCode().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.showDiagnosticsActiveDocument().createMenuItem(false));
         menu.addItem(commands_.showDiagnosticsProject().createMenuItem(false));
         menu.addSeparator();
         menu.addItem(commands_.profileCode().createMenuItem(false));
         codeTransform_ = new ToolbarButton("", icon, menu);
         codeTransform_.setTitle("Code Tools");
      }
      
      return codeTransform_;
   }
   
   public void adaptToExtendedFileType(String extendedType)
   {
      extendedType_ = extendedType;
      adaptToFileType(editor_.getFileType());
   }

   public void adaptToFileType(TextFileType fileType)
   {
      editor_.setFileType(fileType);
      boolean canCompilePdf = fileType.canCompilePDF();
      boolean canKnitToHTML = fileType.canKnitToHTML();
      boolean canCompileNotebook = fileType.canCompileNotebook();
      boolean canSource = fileType.canSource();
      boolean canSourceWithEcho = fileType.canSourceWithEcho();
      boolean canSourceOnSave = fileType.canSourceOnSave();
      boolean canExecuteCode = fileType.canExecuteCode();
      boolean canExecuteChunks = fileType.canExecuteChunks();
      boolean isPlainMarkdown = fileType.isPlainMarkdown();
      boolean isCpp = fileType.isCpp();
      boolean isScript = fileType.isScript();
      boolean isRMarkdown2 = extendedType_.equals("rmarkdown");
      boolean canPreviewFromR = fileType.canPreviewFromR();
      
      // don't show the run buttons for cpp files, or R files in Shiny
      runButton_.setVisible(canExecuteCode && !canExecuteChunks && !isCpp && 
            !isShinyFile());
      runLastButton_.setVisible(runButton_.isVisible() && !canExecuteChunks);
      
      // chunk oriented buttons     
      insertChunkButton_.setVisible(canExecuteChunks);
      goToPrevButton_.setVisible(fileType.canGoNextPrevSection());
      goToNextButton_.setVisible(fileType.canGoNextPrevSection());
      
      sourceOnSave_.setVisible(canSourceOnSave);
      srcOnSaveLabel_.setVisible(canSourceOnSave);
      if (fileType.isRd() || canPreviewFromR)
         srcOnSaveLabel_.setText(fileType.getPreviewButtonText() + " on Save");
      else
         srcOnSaveLabel_.setText("Source on Save");
      codeTransform_.setVisible(
            (canExecuteCode && !fileType.canAuthorContent()) ||
            fileType.isC() || fileType.isStan());   
     
      sourceButton_.setVisible(canSource && !isPlainMarkdown);
      sourceMenuButton_.setVisible(canSourceWithEcho && 
                                   !isPlainMarkdown && 
                                   !isScript &&
                                   !canPreviewFromR);
   
      texSeparatorWidget_.setVisible(canCompilePdf);
      texToolbarButton_.setVisible(canCompilePdf);
      compilePdfButton_.setVisible(canCompilePdf);
      chunksButton_.setVisible(canExecuteChunks);
      chunksRunButton_.setVisible(canExecuteChunks);
      
      notebookSeparatorWidget_.setVisible(canCompileNotebook);
      notebookToolbarButton_.setVisible(canCompileNotebook);
      
      knitDocumentButton_.setVisible(canKnitToHTML);
      
      setRmdFormatButtonVisible(isRMarkdown2);
      rmdOptionsButton_.setVisible(isRMarkdown2);
      rmdOptionsButton_.setEnabled(isRMarkdown2);
     
      if (isShinyFile())
      {
         sourceOnSave_.setVisible(false);
         srcOnSaveLabel_.setVisible(false);
         runButton_.setVisible(false);
         sourceMenuButton_.setVisible(false);
         chunksButton_.setVisible(false);
         shinyLaunchButton_.setVisible(true);
         setSourceButtonFromShinyState();
      }
      else
      {
         shinyLaunchButton_.setVisible(false);
         setSourceButtonFromScriptState(isScript, 
                                        canPreviewFromR,
                                        fileType.getPreviewButtonText());
      }
      
      // set the content type based on the extended type
      setPublishPath(extendedType_, publishPath_);
      
      // make toggle outline visible if we have a scope tree
      toggleDocOutlineButton_.setVisible(fileType.canShowScopeTree());
      if (!fileType.canShowScopeTree())
      {
         editorPanel_.setWidgetSize(docOutlineWidget_, 0);
         toggleDocOutlineButton_.setLatched(false);
      }
      
      toolbar_.invalidateSeparators();
   }
   
   private boolean isShinyFile()
   {
      return extendedType_ != null &&
             extendedType_.startsWith(SourceDocument.XT_SHINY_PREFIX);
   }

   public HasValue<Boolean> getSourceOnSave()
   {
      return sourceOnSave_;
   }

   public void ensureVisible()
   {
      fireEvent(new EnsureVisibleEvent());
   }
   
   @Override
   public void onResize() 
   {
      super.onResize();
      manageToolbarSizes();
      ResizeEvent.fire(this, getOffsetWidth(), getOffsetHeight());
     
   }

   private void manageToolbarSizes()
   {
      // sometimes width is passed in as 0 (not sure why)
      int width = getOffsetWidth();
      if (width == 0)
         return;
      
      texToolbarButton_.setText(width < 520 ? "" : "Format");
      runButton_.setText(((width < 480) || isShinyFile()) ? "" : "Run");
      compilePdfButton_.setText(width < 450 ? "" : "Compile PDF");
      previewHTMLButton_.setText(width < 450 ? "" : previewCommandText_);
      knitDocumentButton_.setText(width < 450 ? "" : knitCommandText_);
      
      if (editor_.getFileType().isRd() || editor_.getFileType().canPreviewFromR())
      {
         String preview = editor_.getFileType().getPreviewButtonText();
         srcOnSaveLabel_.setText(width < 450 ? preview : preview + " on Save");
      }
      else
         srcOnSaveLabel_.setText(width < 450 ? "Source" : "Source on Save");
      sourceButton_.setText(width < 400 ? "" : sourceCommandText_);
      chunksRunButton_.setText(width < 400 ? "" : "Run");
   }
   
   
   public void showWarningBar(String warning)
   {
      if (warningBar_ == null)
      {
         warningBar_ = new InfoBar(InfoBar.WARNING, new ClickHandler() {
            @Override
            public void onClick(ClickEvent event)
            {
               hideWarningBar();
            }
            
         });
      }
      warningBar_.setText(warning);
      panel_.insertNorth(warningBar_, warningBar_.getHeight(), null);
   }

   public void hideWarningBar()
   {
      if (warningBar_ != null)
      {
         panel_.remove(warningBar_);
      }
   }

   public void showFindReplace(boolean defaultForward)
   {
      findReplace_.showFindReplace(defaultForward);
   }
   
   @Override
   public void findNext()
   {
      findReplace_.findNext();
   }
   
   @Override
   public void findSelectAll()
   {
      findReplace_.selectAll();
   }

   @Override
   public void findPrevious()
   {
      findReplace_.findPrevious();
   }
   
   @Override
   public void findFromSelection()
   {
      findReplace_.findFromSelection();
   }
   
   @Override
   public void replaceAndFind()
   {
      findReplace_.replaceAndFind();
   }

   public void onActivate()
   {
      editor_.onActivate();
      
      Scheduler.get().scheduleDeferred(new ScheduledCommand() {

         @Override
         public void execute()
         {
            manageToolbarSizes(); 
         }
      });
   }

   public void setFontSize(double size)
   {
      editor_.setFontSize(size);
   }

   public StatusBar getStatusBar()
   {
      return statusBar_;
   }

   @Override
   public void debug_dumpContents()
   {
      String dump = editor_.debug_getDocumentDump();
      new EditDialog(dump, false, false, new ProgressOperationWithInput<String>()
      {
         @Override
         public void execute(String input, ProgressIndicator indicator)
         {
            indicator.onCompleted();
         }
      }).showModal();
   }

   @Override
   public void debug_importDump()
   {
      new EditDialog("", false, false, new ProgressOperationWithInput<String>()
      {
         @Override
         public void execute(String input, ProgressIndicator indicator)
         {
            indicator.onCompleted();
            if (input == null)
               return;

            input = input.replaceAll("[ \\r\\n]+", " ");
            String[] chars = input.split(" ");

            StringBuilder sb = new StringBuilder();
            for (String s : chars)
            {
               if (s.equals("."))
                  sb.append('\n');
               else
                  sb.append((char)Integer.parseInt(s));
            }

            editor_.debug_setSessionValueDirectly(sb.toString());
         }
      }).showModal();
   }

   // Called by the owning TextEditingTarget to notify the widget that the 
   // Shiny application associated with this widget has changed state.
   @Override
   public void onShinyApplicationStateChanged(String state)
   {
      shinyAppState_ = state;
      setSourceButtonFromShinyState();
   }
   
   @Override
   public void setFormatOptions(TextFileType fileType,
                                boolean showRmdFormatMenu,
                                boolean canEditFormatOptions,
                                List<String> options, 
                                List<String> values, 
                                List<String> extensions, 
                                String selectedOption)
   { 
      if (!canEditFormatOptions)
      {
         setFormatText("");
      }
      
      setRmdFormatButtonVisible(showRmdFormatMenu);
      rmdFormatButton_.setEnabled(showRmdFormatMenu);
      rmdFormatButton_.clearMenu();
      
      int parenPos = selectedOption.indexOf('(');
      if (parenPos != -1)
          selectedOption = selectedOption.substring(0, parenPos).trim();
 
      setFormatText(selectedOption);
      
      String prefix = fileType.isPlainMarkdown() ? "Preview " : "Knit to ";
      
      for (int i = 0; i < Math.min(options.size(), values.size()); i++)
      {
         String ext = extensions.get(i);
         ImageResource img = ext != null ? 
               fileTypeRegistry_.getIconForFilename("output." + ext) :
               fileTypeRegistry_.getIconForFilename("Makefile");
         final String valueName = values.get(i);
         ScheduledCommand cmd = new ScheduledCommand()
         {
            @Override
            public void execute()
            {
               handlerManager_.fireEvent(
                     new RmdOutputFormatChangedEvent(valueName));
            }
         };
         MenuItem item = ImageMenuItem.create(img, 
                                              prefix + options.get(i), 
                                              cmd, 2);
         rmdFormatButton_.addMenuItem(item, values.get(i));
      }
      
      if (session_.getSessionInfo().getKnitParamsAvailable())
      {
         final AppCommand knitWithParams = commands_.knitWithParameters();
         rmdFormatButton_.addSeparator();
         ScheduledCommand cmd = new ScheduledCommand()
         {
{'fraction_non_alphanumeric': 0.09066209066209066, 'fraction_numerical': 0.00238333571666905, 'mean_word_length': 2.2287487816139127, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 50, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6653207', 'n_tokens_mistral': 16493, 'n_tokens_neox': 16091, 'n_words': 2720}