/*
* AceEditor.java
*
* Copyright (C) 2009-16 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.ArrayList;
import java.util.List;
import com.google.gwt.animation.client.AnimationScheduler;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.PreElement;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import org.rstudio.core.client.CommandWithArg;
import org.rstudio.core.client.ElementIds;
import org.rstudio.core.client.ExternalJavaScriptLoader;
import org.rstudio.core.client.ExternalJavaScriptLoader.Callback;
import org.rstudio.core.client.Rectangle;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.command.KeyboardShortcut.KeySequence;
import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.dom.WindowEx;
import org.rstudio.core.client.js.JsMap;
import org.rstudio.core.client.js.JsObject;
import org.rstudio.core.client.js.JsUtil;
import org.rstudio.core.client.regex.Match;
import org.rstudio.core.client.regex.Pattern;
import org.rstudio.core.client.resources.StaticDataResource;
import org.rstudio.core.client.widget.DynamicIFrame;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.Desktop;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.SuperDevMode;
import org.rstudio.studio.client.common.codetools.CodeToolsServerOperations;
import org.rstudio.studio.client.common.debugging.model.Breakpoint;
import org.rstudio.studio.client.common.filetypes.DocumentMode;
import org.rstudio.studio.client.common.filetypes.TextFileType;
import org.rstudio.studio.client.server.Void;
import org.rstudio.studio.client.workbench.MainWindowObject;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.model.ChangeTracker;
import org.rstudio.studio.client.workbench.model.EventBasedChangeTracker;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefsAccessor;
import org.rstudio.studio.client.workbench.snippets.SnippetHelper;
import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager;
import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager.InitCompletionFilter;
import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionPopupPanel;
import org.rstudio.studio.client.workbench.views.console.shell.assist.NullCompletionManager;
import org.rstudio.studio.client.workbench.views.console.shell.assist.RCompletionManager;
import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorPosition;
import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorSelection;
import org.rstudio.studio.client.workbench.views.output.lint.DiagnosticsBackgroundPopup;
import org.rstudio.studio.client.workbench.views.output.lint.model.AceAnnotation;
import org.rstudio.studio.client.workbench.views.output.lint.model.LintItem;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.*;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceClickEvent.Handler;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Mode.InsertChunkInfo;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Renderer.ScreenCoordinates;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.CharClassifier;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.TokenPredicate;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.WordIterable;
import org.rstudio.studio.client.workbench.views.source.editors.text.cpp.CppCompletionContext;
import org.rstudio.studio.client.workbench.views.source.editors.text.cpp.CppCompletionManager;
import org.rstudio.studio.client.workbench.views.source.editors.text.events.*;
import org.rstudio.studio.client.workbench.views.source.editors.text.rmd.ChunkDefinition;
import org.rstudio.studio.client.workbench.views.source.editors.text.rmd.TextEditingTargetNotebook;
import org.rstudio.studio.client.workbench.views.source.events.CollabEditStartParams;
import org.rstudio.studio.client.workbench.views.source.events.RecordNavigationPositionEvent;
import org.rstudio.studio.client.workbench.views.source.events.RecordNavigationPositionHandler;
import org.rstudio.studio.client.workbench.views.source.events.SaveFileEvent;
import org.rstudio.studio.client.workbench.views.source.events.SaveFileHandler;
import org.rstudio.studio.client.workbench.views.source.model.DirtyState;
import org.rstudio.studio.client.workbench.views.source.model.RnwCompletionContext;
import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
public class AceEditor implements DocDisplay,
InputEditorDisplay,
NavigableSourceEditor
{
public enum NewLineMode
{
Windows("windows"),
Unix("unix"),
Auto("auto");
NewLineMode(String type)
{
this.type = type;
}
public String getType()
{
return type;
}
private String type;
}
private class Filter implements InitCompletionFilter
{
public boolean shouldComplete(NativeEvent event)
{
// Never complete if there's an active selection
Range range = getSession().getSelection().getRange();
if (!range.isEmpty())
return false;
// Don't consider Tab to be a completion if we're at the start of a
// line (e.g. only zero or more whitespace characters between the
// beginning of the line and the cursor)
if (event != null && event.getKeyCode() != KeyCodes.KEY_TAB)
return true;
// Short-circuit if the user has explicitly opted in
if (uiPrefs_.allowTabMultilineCompletion().getValue())
return true;
int col = range.getStart().getColumn();
if (col == 0)
return false;
String line = getSession().getLine(range.getStart().getRow());
return line.substring(0, col).trim().length() != 0;
}
}
private class AnchoredSelectionImpl implements AnchoredSelection
{
private AnchoredSelectionImpl(Anchor start, Anchor end)
{
start_ = start;
end_ = end;
}
public String getValue()
{
return getSession().getTextRange(getRange());
}
public void apply()
{
getSession().getSelection().setSelectionRange(
getRange());
}
public Range getRange()
{
return Range.fromPoints(start_.getPosition(), end_.getPosition());
}
public void detach()
{
start_.detach();
end_.detach();
}
private final Anchor start_;
private final Anchor end_;
}
private class AceEditorChangeTracker extends EventBasedChangeTracker<Void>
{
private AceEditorChangeTracker()
{
super(AceEditor.this);
AceEditor.this.addFoldChangeHandler(new org.rstudio.studio.client.workbench.views.source.editors.text.events.FoldChangeEvent.Handler()
{
@Override
public void onFoldChange(FoldChangeEvent event)
{
changed_ = true;
}
});
AceEditor.this.addLineWidgetsChangedHandler(new org.rstudio.studio.client.workbench.views.source.editors.text.events.LineWidgetsChangedEvent.Handler() {
@Override
public void onLineWidgetsChanged(LineWidgetsChangedEvent event)
{
changed_ = true;
}
});
}
@Override
public ChangeTracker fork()
{
AceEditorChangeTracker forked = new AceEditorChangeTracker();
forked.changed_ = changed_;
return forked;
}
}
public static void preload()
{
load(null);
}
public static void load(final Command command)
{
aceLoader_.addCallback(new Callback()
{
public void onLoaded()
{
aceSupportLoader_.addCallback(new Callback()
{
public void onLoaded()
{
extLanguageToolsLoader_.addCallback(new Callback()
{
public void onLoaded()
{
vimLoader_.addCallback(new Callback()
{
public void onLoaded()
{
emacsLoader_.addCallback(new Callback()
{
public void onLoaded()
{
if (command != null)
command.execute();
}
});
}
});
}
});
}
});
}
});
}
public static final native AceEditor getEditor(Element el)
/*-{
for (; el != null; el = el.parentElement)
if (el.$RStudioAceEditor != null)
return el.$RStudioAceEditor;
}-*/;
private static final native void attachToWidget(Element el, AceEditor editor)
/*-{
el.$RStudioAceEditor = editor;
}-*/;
private static final native void detachFromWidget(Element el)
/*-{
el.$RStudioAceEditor = null;
}-*/;
@Inject
public AceEditor()
{
widget_ = new AceEditorWidget();
snippets_ = new SnippetHelper(this);
editorEventListeners_ = new ArrayList<HandlerRegistration>();
mixins_ = new AceEditorMixins(this);
ElementIds.assignElementId(widget_.getElement(), ElementIds.SOURCE_TEXT_EDITOR);
completionManager_ = new NullCompletionManager();
diagnosticsBgPopup_ = new DiagnosticsBackgroundPopup(this);
RStudioGinjector.INSTANCE.injectMembers(this);
backgroundTokenizer_ = new BackgroundTokenizer(this);
vim_ = new Vim(this);
bgLinkHighlighter_ = new AceEditorBackgroundLinkHighlighter(this);
bgChunkHighlighter_ = new AceBackgroundHighlighter(this);
widget_.addValueChangeHandler(new ValueChangeHandler<Void>()
{
public void onValueChange(ValueChangeEvent<Void> evt)
{
if (!valueChangeSuppressed_)
{
ValueChangeEvent.fire(AceEditor.this, null);
}
}
});
widget_.addFoldChangeHandler(new FoldChangeEvent.Handler()
{
@Override
public void onFoldChange(FoldChangeEvent event)
{
AceEditor.this.fireEvent(new FoldChangeEvent());
}
});
addPasteHandler(new PasteEvent.Handler()
{
@Override
public void onPaste(PasteEvent event)
{
if (completionManager_ != null)
completionManager_.onPaste(event);
final Position start = getSelectionStart();
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
@Override
public void execute()
{
Range range = Range.fromPoints(start, getSelectionEnd());
indentPastedRange(range);
}
});
}
});
// handle click events
addAceClickHandler(new AceClickEvent.Handler()
{
@Override
public void onAceClick(AceClickEvent event)
{
fixVerticalOffsetBug();
if (DomUtils.isCommandClick(event.getNativeEvent()))
{
// eat the event so ace doesn't do anything with it
event.preventDefault();
event.stopPropagation();
// go to function definition
fireEvent(new CommandClickEvent(event));
}
else
{
// if the focus in the Help pane or another iframe
// we need to make sure to get it back
WindowEx.get().focus();
}
}
});
lastCursorChangedTime_ = 0;
addCursorChangedHandler(new CursorChangedHandler()
{
@Override
public void onCursorChanged(CursorChangedEvent event)
{
fixVerticalOffsetBug();
clearLineHighlight();
lastCursorChangedTime_ = System.currentTimeMillis();
}
});
lastModifiedTime_ = 0;
addValueChangeHandler(new ValueChangeHandler<Void>()
{
@Override
public void onValueChange(ValueChangeEvent<Void> event)
{
lastModifiedTime_ = System.currentTimeMillis();
clearDebugLineHighlight();
}
});
widget_.addAttachHandler(new AttachEvent.Handler()
{
@Override
public void onAttachOrDetach(AttachEvent event)
{
if (event.isAttached())
attachToWidget(widget_.getElement(), AceEditor.this);
else
detachFromWidget(widget_.getElement());
if (!event.isAttached())
{
for (HandlerRegistration handler : editorEventListeners_)
handler.removeHandler();
editorEventListeners_.clear();
if (completionManager_ != null)
{
completionManager_.detach();
completionManager_ = null;
}
}
}
});
widget_.addFocusHandler(new FocusHandler()
{
@Override
public void onFocus(FocusEvent event)
{
String id = AceEditor.this.getWidget().getElement().getId();
MainWindowObject.lastFocusedEditor().set(id);
}
});
events_.addHandler(
AceEditorCommandEvent.TYPE,
new AceEditorCommandEvent.Handler()
{
@Override
public void onEditorCommand(AceEditorCommandEvent event)
{
// skip this if this is only for the actively focused Ace instance
if (event.getExecutionPolicy() == AceEditorCommandEvent.EXECUTION_POLICY_FOCUSED &&
!AceEditor.this.isFocused())
{
return;
}
switch (event.getCommand())
{
case AceEditorCommandEvent.YANK_REGION: yankRegion(); break;
case AceEditorCommandEvent.YANK_BEFORE_CURSOR: yankBeforeCursor(); break;
case AceEditorCommandEvent.YANK_AFTER_CURSOR: yankAfterCursor(); break;
case AceEditorCommandEvent.PASTE_LAST_YANK: pasteLastYank(); break;
case AceEditorCommandEvent.INSERT_ASSIGNMENT_OPERATOR: insertAssignmentOperator(); break;
case AceEditorCommandEvent.INSERT_PIPE_OPERATOR: insertPipeOperator(); break;
case AceEditorCommandEvent.JUMP_TO_MATCHING: jumpToMatching(); break;
case AceEditorCommandEvent.SELECT_TO_MATCHING: selectToMatching(); break;
case AceEditorCommandEvent.EXPAND_TO_MATCHING: expandToMatching(); break;
case AceEditorCommandEvent.ADD_CURSOR_ABOVE: addCursorAbove(); break;
case AceEditorCommandEvent.ADD_CURSOR_BELOW: addCursorBelow(); break;
}
}
});
}
public void yankRegion()
{
if (isVimModeOn() && !isVimInInsertMode())
return;
// no-op if there is no selection
String selectionValue = getSelectionValue();
if (StringUtil.isNullOrEmpty(selectionValue))
return;
if (Desktop.isDesktop())
{
Desktop.getFrame().clipboardCut();
if (isEmacsModeOn())
clearEmacsMark();
}
else
{
yankedText_ = getSelectionValue();
replaceSelection("");
}
}
public void yankBeforeCursor()
{
if (isVimModeOn() && !isVimInInsertMode())
return;
Position cursorPos = getCursorPosition();
setSelectionRange(Range.fromPoints(
Position.create(cursorPos.getRow(), 0),
cursorPos));
if (Desktop.isDesktop())
{
commands_.cutDummy().execute();
if (isEmacsModeOn()) clearEmacsMark();
}
else
{
yankedText_ = getSelectionValue();
replaceSelection("");
}
}
public void yankAfterCursor()
{
if (isVimModeOn() && !isVimInInsertMode())
return;
Position cursorPos = getCursorPosition();
String line = getLine(cursorPos.getRow());
int lineLength = line.length();
// if the cursor is already at the end of the line
// (allowing for trailing whitespace), then eat the
// newline as well; otherwise, just eat to end of line
String rest = line.substring(cursorPos.getColumn());
if (rest.trim().isEmpty())
{
setSelectionRange(Range.fromPoints(
cursorPos,
Position.create(cursorPos.getRow() + 1, 0)));
}
else
{
setSelectionRange(Range.fromPoints(
cursorPos,
Position.create(cursorPos.getRow(), lineLength)));
}
if (Desktop.isDesktop())
{
Desktop.getFrame().clipboardCut();
if (isEmacsModeOn())
clearEmacsMark();
}
else
{
yankedText_ = getSelectionValue();
replaceSelection("");
}
}
public void pasteLastYank()
{
if (isVimModeOn() && !isVimInInsertMode())
return;
if (Desktop.isDesktop())
{
Desktop.getFrame().clipboardPaste();
}
else
{
if (yankedText_ == null)
return;
replaceSelection(yankedText_);
setCursorPosition(getSelectionEnd());
}
}
public void insertAssignmentOperator()
{
if (DocumentMode.isCursorInRMode(this))
insertAssignmentOperatorImpl("<-");
else
insertAssignmentOperatorImpl("=");
}
@SuppressWarnings("deprecation")
private void insertAssignmentOperatorImpl(String op)
{
boolean hasWhitespaceBefore =
Character.isSpace(getCharacterBeforeCursor()) ||
(!hasSelection() && getCursorPosition().getColumn() == 0);
String insertion = hasWhitespaceBefore
? op + " "
: " " + op + " ";
insertCode(insertion, false);
}
@SuppressWarnings("deprecation")
public void insertPipeOperator()
{
boolean hasWhitespaceBefore =
Character.isSpace(getCharacterBeforeCursor()) ||
(!hasSelection() && getCursorPosition().getColumn() == 0);
if (hasWhitespaceBefore)
insertCode("%>% ", false);
else
insertCode(" %>% ", false);
}
private void indentPastedRange(Range range)
{
if (fileType_ == null ||
!fileType_.canAutoIndent() ||
!RStudioGinjector.INSTANCE.getUIPrefs().reindentOnPaste().getValue())
{
return;
}
String firstLinePrefix = getSession().getTextRange(
Range.fromPoints(Position.create(range.getStart().getRow(), 0),
range.getStart()));
if (firstLinePrefix.trim().length() != 0)
{
Position newStart = Position.create(range.getStart().getRow() + 1, 0);
if (newStart.compareTo(range.getEnd()) >= 0)
return;
range = Range.fromPoints(newStart, range.getEnd());
}
getSession().reindent(range);
}
public AceCommandManager getCommandManager()
{
return getWidget().getEditor().getCommandManager();
}
public void setEditorCommandBinding(String id, List<KeySequence> keys)
{
getWidget().getEditor().getCommandManager().rebindCommand(id, keys);
}
public void resetCommands()
{
AceCommandManager manager = AceCommandManager.create();
JsObject commands = manager.getCommands();
for (String key : JsUtil.asIterable(commands.keys()))
{
AceCommand command = commands.getObject(key);
getWidget().getEditor().getCommandManager().addCommand(command);
}
}
@Inject
void initialize(CodeToolsServerOperations server,
UIPrefs uiPrefs,
CollabEditor collab,
Commands commands,
EventBus events)
{
server_ = server;
uiPrefs_ = uiPrefs;
collab_ = collab;
commands_ = commands;
events_ = events;
}
public TextFileType getFileType()
{
return fileType_;
}
public void setFileType(TextFileType fileType)
{
setFileType(fileType, false);
}
public void setFileType(TextFileType fileType, boolean suppressCompletion)
{
fileType_ = fileType;
updateLanguage(suppressCompletion);
}
public void setFileType(TextFileType fileType,
CompletionManager completionManager)
{
fileType_ = fileType;
updateLanguage(completionManager);
}
@Override
public void setRnwCompletionContext(RnwCompletionContext rnwContext)
{
rnwContext_ = rnwContext;
}
@Override
public void setCppCompletionContext(CppCompletionContext cppContext)
{
cppContext_ = cppContext;
}
@Override
public void setRCompletionContext(RCompletionContext rContext)
{
rContext_ = rContext;
}
private void updateLanguage(boolean suppressCompletion)
{
if (fileType_ == null)
return;
CompletionManager completionManager;
if (!suppressCompletion)
{
if (fileType_.getEditorLanguage().useRCompletion())
{
completionManager = new RCompletionManager(
this,
this,
new CompletionPopupPanel(),
server_,
new Filter(),
rContext_,
fileType_.canExecuteChunks() ? rnwContext_ : null,
this,
false);
// if this is cpp then we use our own completion manager
// that can optionally delegate to the R completion manager
if (fileType_.isC() || fileType_.isRmd())
{
completionManager = new CppCompletionManager(
this,
new Filter(),
cppContext_,
completionManager);
}
}
else
completionManager = new NullCompletionManager();
}
else
completionManager = new NullCompletionManager();
updateLanguage(completionManager);
}
private void updateLanguage(CompletionManager completionManager)
{
clearLint();
if (fileType_ == null)
return;
if (completionManager_ != null)
{
completionManager_.detach();
completionManager_ = null;
}
completionManager_ = completionManager;
updateKeyboardHandlers();
syncCompletionPrefs();
syncDiagnosticsPrefs();
snippets_.ensureSnippetsLoaded();
getSession().setEditorMode(
fileType_.getEditorLanguage().getParserName(),
false);
handlers_.fireEvent(new EditorModeChangedEvent(getModeId()));
getSession().setUseWrapMode(fileType_.getWordWrap());
syncWrapLimit();
}
@Override
public void syncCompletionPrefs()
{
if (fileType_ == null)
return;
boolean enabled = fileType_.getEditorLanguage().useAceLanguageTools();
boolean live = uiPrefs_.codeCompleteOther().getValue().equals(
UIPrefsAccessor.COMPLETION_ALWAYS);
int characterThreshold = uiPrefs_.alwaysCompleteCharacters().getValue();
int delay = uiPrefs_.alwaysCompleteDelayMs().getValue();
widget_.getEditor().setCompletionOptions(
enabled,
uiPrefs_.enableSnippets().getValue(),
live,
characterThreshold,
delay);
}
@Override
public void syncDiagnosticsPrefs()
{
if (fileType_ == null)
return;
boolean useWorker = uiPrefs_.showDiagnosticsOther().getValue() &&
fileType_.getEditorLanguage().useAceLanguageTools();
getSession().setUseWorker(useWorker);
getSession().setWorkerTimeout(
uiPrefs_.backgroundDiagnosticsDelayMs().getValue());
}
private void syncWrapLimit()
{
// bail if there is no filetype yet
if (fileType_ == null)
return;
// We originally observed that large word-wrapped documents
// would cause Chrome on Liunx to freeze (bug #3207), eventually
// running of of memory. Running the profiler indicated that the
// time was being spent inside wrap width calculations in Ace.
// Looking at the Ace bug database there were other wrapping problems
// that were solvable by changing the wrap mode from "free" to a
// specific range. So, for Chrome on Linux we started syncing the
// wrap limit to the user-specified margin width.
//
// Unfortunately, this caused another problem whereby the ace
// horizontal scrollbar would show up over the top of the editor
// and the console (bug #3428). We tried reverting the fix to
// #3207 and sure enough this solved the horizontal scrollbar
// problem _and_ no longer froze Chrome (so perhaps there was a
// bug in Chrome).
//
// In the meantime we added user pref to soft wrap to the margin
// column, essentially allowing users to opt-in to the behavior
// we used to fix the bug. So the net is:
//
// (1) To fix the horizontal scrollbar problem we revereted
// the wrap mode behavior we added from Chrome (under the
// assumption that the issue has been fixed in Chrome)
//
// (2) We added another check for desktop mode (since we saw
// the problem in both Chrome and Safari) to prevent the
// application of the problematic wrap mode setting.
//
// Perhaps there is an ace issue here as well, so the next time
// we sync to Ace tip we should see if we can bring back the
// wrapping option for Chrome (note the repro for this
// is having a soft-wrapping source document in the editor that
// exceed the horizontal threshold)
// NOTE: we no longer do this at all since we observed the
// scollbar problem on desktop as well
}
private void updateKeyboardHandlers()
{
// clear out existing editor handlers (they will be refreshed if necessary)
for (HandlerRegistration handler : editorEventListeners_)
if (handler != null)
handler.removeHandler();
editorEventListeners_.clear();
// save and restore Vim marks as they can be lost when refreshing
// the keyboard handlers. this is necessary as keyboard handlers are
// regenerated on each document save, and clearing the Vim handler will
// clear any local Vim state.
JsMap<Position> marks = JsMap.create().cast();
if (useVimMode_)
marks = widget_.getEditor().getMarks();
// create a keyboard previewer for our special hooks
AceKeyboardPreviewer previewer = new AceKeyboardPreviewer(completionManager_);
// set default key handler
if (useVimMode_)
widget_.getEditor().setKeyboardHandler(KeyboardHandler.vim());
else if (useEmacsKeybindings_)
widget_.getEditor().setKeyboardHandler(KeyboardHandler.emacs());
else
widget_.getEditor().setKeyboardHandler(null);
// add the previewer
widget_.getEditor().addKeyboardHandler(previewer.getKeyboardHandler());
// Listen for command execution
editorEventListeners_.add(AceEditorNative.addEventListener(
widget_.getEditor().getCommandManager(),
"afterExec",
new CommandWithArg<JavaScriptObject>()
{
@Override
public void execute(JavaScriptObject event)
{
events_.fireEvent(new AceAfterCommandExecutedEvent(event));
}
}));
// Listen for keyboard activity
editorEventListeners_.add(AceEditorNative.addEventListener(
widget_.getEditor(),
"keyboardActivity",
new CommandWithArg<JavaScriptObject>()
{
@Override
public void execute(JavaScriptObject event)
{
events_.fireEvent(new AceKeyboardActivityEvent(event));
}
}));
if (useVimMode_)
widget_.getEditor().setMarks(marks);
}
public String getCode()
{
return getSession().getValue();
}
public void setCode(String code, boolean preserveCursorPosition)
{
// Calling setCode("", false) while the editor contains multiple lines of
// content causes bug 2928: Flickering console when typing. Empirically,
// first setting code to a single line of content and then clearing it,
// seems to correct this problem.
if (StringUtil.isNullOrEmpty(code))
doSetCode(" ", preserveCursorPosition);
doSetCode(code, preserveCursorPosition);
}
private void doSetCode(String code, boolean preserveCursorPosition)
{
// Filter out Escape characters that might have snuck in from an old
// bug in 0.95. We can choose to remove this when 0.95 ships, hopefully
// any documents that would be affected by this will be gone by then.
code = code.replaceAll("\u001B", "");
// Normalize newlines -- convert all of '\r', '\r\n', '\n\r' to '\n'.
code = StringUtil.normalizeNewLines(code);
final AceEditorNative ed = widget_.getEditor();
if (preserveCursorPosition)
{
final Position cursorPos;
final int scrollTop, scrollLeft;
cursorPos = ed.getSession().getSelection().getCursor();
scrollTop = ed.getRenderer().getScrollTop();
scrollLeft = ed.getRenderer().getScrollLeft();
// Setting the value directly on the document prevents undo/redo
// stack from being blown away
widget_.getEditor().getSession().getDocument().setValue(code);
ed.getSession().getSelection().moveCursorTo(cursorPos.getRow(),
cursorPos.getColumn(),
false);
ed.getRenderer().scrollToY(scrollTop);
ed.getRenderer().scrollToX(scrollLeft);
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
@Override
public void execute()
{
ed.getRenderer().scrollToY(scrollTop);
ed.getRenderer().scrollToX(scrollLeft);
}
});
}
else
{
ed.getSession().setValue(code);
ed.getSession().getSelection().moveCursorTo(0, 0, false);
}
}
public int getScrollLeft()
{
return widget_.getEditor().getRenderer().getScrollLeft();
}
public void scrollToX(int x)
{
widget_.getEditor().getRenderer().scrollToX(x);
}
public int getScrollTop()
{
return widget_.getEditor().getRenderer().getScrollTop();
}
public void scrollToY(int y, int animateMs)
{
// cancel any existing scroll animation
if (scrollAnimator_ != null)
scrollAnimator_.complete();
if (animateMs == 0)
widget_.getEditor().getRenderer().scrollToY(y);
else
scrollAnimator_ = new ScrollAnimator(y, animateMs);
}
public void insertCode(String code)
{
insertCode(code, false);
}
public void insertCode(String code, boolean blockMode)
{
widget_.getEditor().insert(StringUtil.normalizeNewLines(code));
}
public String getCode(Position start, Position end)
{
return getSession().getTextRange(Range.fromPoints(start, end));
}
@Override
public InputEditorSelection search(String needle,
boolean backwards,
boolean wrap,
boolean caseSensitive,
boolean wholeWord,
Position start,
Range range,
boolean regexpMode)
{
Search search = Search.create(needle,
backwards,
wrap,
caseSensitive,
wholeWord,
start,
range,
regexpMode);
Range resultRange = search.find(getSession());
if (resultRange != null)
{
return createSelection(resultRange.getStart(), resultRange.getEnd());
}
else
{
return null;
}
}
@Override
public void quickAddNext()
{
if (getNativeSelection().isEmpty())
{
getNativeSelection().selectWord();
return;
}
String needle = getSelectionValue();
Search search = Search.create(
needle,
false,
true,
true,
true,
getCursorPosition(),
null,
false);
Range range = search.find(getSession());
if (range == null)
return;
getNativeSelection().addRange(range, false);
centerSelection();
}
@Override
public void insertCode(InputEditorPosition position, String content)
{
insertCode(selectionToPosition(position), content);
}
public void insertCode(Position position, String content)
{
getSession().insert(position, content);
}
@Override
public String getCode(InputEditorSelection selection)
{
return getCode(((AceInputEditorPosition)selection.getStart()).getValue(),
((AceInputEditorPosition)selection.getEnd()).getValue());
}
@Override
public JsArrayString getLines()
{
return getLines(0, getSession().getLength());
}
@Override
public JsArrayString getLines(int startRow, int endRow)
{
return getSession().getLines(startRow, endRow);
}
public void focus()
{
widget_.getEditor().focus();
}
public boolean isFocused()
{
return widget_.getEditor().isFocused();
}
public void codeCompletion()
{
completionManager_.codeCompletion();
}
public void goToHelp()
{
completionManager_.goToHelp();
}
public void goToFunctionDefinition()
{
completionManager_.goToFunctionDefinition();
}
class PrintIFrame extends DynamicIFrame
{
public PrintIFrame(String code, double fontSize)
{
code_ = code;
fontSize_ = fontSize;
getElement().getStyle().setPosition(com.google.gwt.dom.client.Style.Position.ABSOLUTE);
getElement().getStyle().setLeft(-5000, Unit.PX);
}
@Override
protected void onFrameLoaded()
{
Document doc = getDocument();
PreElement pre = doc.createPreElement();
pre.setInnerText(code_);
pre.getStyle().setProperty("whiteSpace", "pre-wrap");
pre.getStyle().setFontSize(fontSize_, Unit.PT);
doc.getBody().appendChild(pre);
getWindow().print();
// Bug 1224: ace: print from source causes inability to reconnect
// This was caused by the iframe being removed from the document too
// quickly after the print job was sent. As a result, attempting to
// navigate away from the page at any point afterwards would result
// in the error "Document cannot change while printing or in Print
// Preview". The only thing you could do is close the browser tab.
// By inserting a 5-minute delay hopefully Firefox would be done with
// whatever print related operations are important.
Scheduler.get().scheduleFixedDelay(new RepeatingCommand()
{
public boolean execute()
{
PrintIFrame.this.removeFromParent();
return false;
}
}, 1000 * 60 * 5);
}
private final String code_;
private final double fontSize_;
}
public void print()
{
PrintIFrame printIFrame = new PrintIFrame(
getCode(),
RStudioGinjector.INSTANCE.getUIPrefs().fontSize().getValue());
RootPanel.get().add(printIFrame);
}
public String getText()
{
return getSession().getLine(
getSession().getSelection().getCursor().getRow());
}
public void setText(String string)
{
setCode(string, false);
getSession().getSelection().moveCursorFileEnd();
}
public boolean hasSelection()
{
return !getSession().getSelection().getRange().isEmpty();
}
public final Selection getNativeSelection() {
return widget_.getEditor().getSession().getSelection();
}
public InputEditorSelection getSelection()
{
Range selection = getSession().getSelection().getRange();
return new InputEditorSelection(
new AceInputEditorPosition(getSession(), selection.getStart()),
new AceInputEditorPosition(getSession(), selection.getEnd()));
}
public String getSelectionValue()
{
return getSession().getTextRange(
getSession().getSelection().getRange());
}
public Position getSelectionStart()
{
return getSession().getSelection().getRange().getStart();
}
public Position getSelectionEnd()
{
return getSession().getSelection().getRange().getEnd();
}
@Override
public Range getSelectionRange()
{
return Range.fromPoints(getSelectionStart(), getSelectionEnd());
}
@Override
public void setSelectionRange(Range range)
{
getSession().getSelection().setSelectionRange(range);
}
public void setSelectionRanges(JsArray<Range> ranges)
{
int n = ranges.length();
if (n == 0)
return;
if (vim_.isActive())
vim_.exitVisualMode();
setSelectionRange(ranges.get(0));
for (int i = 1; i < n; i++)
getNativeSelection().addRange(ranges.get(i), false);
}
public int getLength(int row)
{
return getSession().getDocument().getLine(row).length();
}
public int getRowCount()
{
return getSession().getDocument().getLength();
}
@Override
public int getPixelWidth()
{
Element[] content = DomUtils.getElementsByClassName(
widget_.getElement(), "ace_content");
if (content.length < 1)
return widget_.getElement().getOffsetWidth();
else
return content[0].getOffsetWidth();
}
public String getLine(int row)
{
return getSession().getLine(row);
}
@Override
public Position getDocumentEnd()
{
int lastRow = getRowCount() - 1;
return Position.create(lastRow, getLength(lastRow));
}
@Override
public void setInsertMatching(boolean value)
{
widget_.getEditor().setInsertMatching(value);
}
@Override
public void setSurroundSelectionPref(String value)
{
widget_.getEditor().setSurroundSelectionPref(value);
}
@Override
public InputEditorSelection createSelection(Position pos1, Position pos2)
{
return new InputEditorSelection(
new AceInputEditorPosition(getSession(), pos1),
new AceInputEditorPosition(getSession(), pos2));
}
@Override
public Position selectionToPosition(InputEditorPosition pos)
{
// HACK: This cast is gross, InputEditorPosition should just become
// AceInputEditorPosition
return Position.create((Integer) pos.getLine(), pos.getPosition());
}
@Override
public InputEditorPosition createInputEditorPosition(Position pos)
{
return new AceInputEditorPosition(getSession(), pos);
}
@Override
public Iterable<Range> getWords(TokenPredicate tokenPredicate,
CharClassifier charClassifier,
Position start,
Position end)
{
return new WordIterable(getSession(),
tokenPredicate,
charClassifier,
start,
end);
}
@Override
public String getTextForRange(Range range)
{
return getSession().getTextRange(range);
}
@Override
public Anchor createAnchor(Position pos)
{
return Anchor.createAnchor(getSession().getDocument(),
pos.getRow(),
pos.getColumn());
}
private void fixVerticalOffsetBug()
{
widget_.getEditor().getRenderer().fixVerticalOffsetBug();
}
@Override
public String debug_getDocumentDump()
{
return widget_.getEditor().getSession().getDocument().getDocumentDump();
}
@Override
public void debug_setSessionValueDirectly(String s)
{
widget_.getEditor().getSession().setValue(s);
}
public void setSelection(InputEditorSelection selection)
{
AceInputEditorPosition start = (AceInputEditorPosition)selection.getStart();
AceInputEditorPosition end = (AceInputEditorPosition)selection.getEnd();
getSession().getSelection().setSelectionRange(Range.fromPoints(
start.getValue(), end.getValue()));
}
public Rectangle getCursorBounds()
{
Range range = getSession().getSelection().getRange();
return toScreenCoordinates(range);
}
public Rectangle toScreenCoordinates(Range range)
{
Renderer renderer = widget_.getEditor().getRenderer();
ScreenCoordinates start = renderer.textToScreenCoordinates(
range.getStart().getRow(),
range.getStart().getColumn());
ScreenCoordinates end = renderer.textToScreenCoordinates(
range.getEnd().getRow(),
range.getEnd().getColumn());
return new Rectangle(start.getPageX(),
start.getPageY(),
end.getPageX() - start.getPageX(),
renderer.getLineHeight());
}
public Position toDocumentPosition(ScreenCoordinates coordinates)
{
return widget_.getEditor().getRenderer().screenToTextCoordinates(
coordinates.getPageX(),
coordinates.getPageY());
}
public Range toDocumentRange(Rectangle rectangle)
{
Renderer renderer = widget_.getEditor().getRenderer();
return Range.fromPoints(
renderer.screenToTextCoordinates(rectangle.getLeft(), rectangle.getTop()),
renderer.screenToTextCoordinates(rectangle.getRight(), rectangle.getBottom()));
}
@Override
public Rectangle getPositionBounds(Position position)
{
Renderer renderer = widget_.getEditor().getRenderer();
ScreenCoordinates start = renderer.textToScreenCoordinates(
position.getRow(),
position.getColumn());
return new Rectangle(start.getPageX(), start.getPageY(),
(int) Math.round(renderer.getCharacterWidth()),
(int) (renderer.getLineHeight() * 0.8));
}
@Override
public Rectangle getRangeBounds(Range range)
{
range = Range.toOrientedRange(range);
Renderer renderer = widget_.getEditor().getRenderer();
if (!range.isMultiLine())
{
ScreenCoordinates start = documentPositionToScreenCoordinates(range.getStart());
ScreenCoordinates end = documentPositionToScreenCoordinates(range.getEnd());
int width = (end.getPageX() - start.getPageX()) + (int) renderer.getCharacterWidth();
int height = (end.getPageY() - start.getPageY()) + (int) renderer.getLineHeight();
return new Rectangle(start.getPageX(), start.getPageY(), width, height);
}
Position startPos = range.getStart();
Position endPos = range.getEnd();
int startRow = startPos.getRow();
int endRow = endPos.getRow();
// figure out top left coordinates
ScreenCoordinates topLeft = documentPositionToScreenCoordinates(Position.create(startRow, 0));
// figure out bottom right coordinates (need to walk rows to figure out longest line)
ScreenCoordinates bottomRight = documentPositionToScreenCoordinates(Position.create(endPos));
for (int row = startRow; row <= endRow; row++)
{
Position rowEndPos = Position.create(row, getLength(row));
ScreenCoordinates coords = documentPositionToScreenCoordinates(rowEndPos);
if (coords.getPageX() > bottomRight.getPageX())
bottomRight = ScreenCoordinates.create(coords.getPageX(), bottomRight.getPageY());
}
// construct resulting range
int width = (bottomRight.getPageX() - topLeft.getPageX()) + (int) renderer.getCharacterWidth();
int height = (bottomRight.getPageY() - topLeft.getPageY()) + (int) renderer.getLineHeight();
return new Rectangle(topLeft.getPageX(), topLeft.getPageY(), width, height);
}
@Override
public Rectangle getPositionBounds(InputEditorPosition position)
{
Position pos = ((AceInputEditorPosition) position).getValue();
return getPositionBounds(pos);
}
public Rectangle getBounds()
{
return new Rectangle(
widget_.getAbsoluteLeft(),
widget_.getAbsoluteTop(),
widget_.getOffsetWidth(),
widget_.getOffsetHeight());
}
public void setFocus(boolean focused)
{
if (focused)
widget_.getEditor().focus();
else
widget_.getEditor().blur();
}
public void replaceRange(Range range, String text) {
getSession().replace(range, text);
}
public String replaceSelection(String value, boolean collapseSelection)
{
Selection selection = getSession().getSelection();
String oldValue = getSession().getTextRange(selection.getRange());
replaceSelection(value);
if (collapseSelection)
{
collapseSelection(false);
}
return oldValue;
}
public boolean isSelectionCollapsed()
{
return getSession().getSelection().isEmpty();
}
public boolean isCursorAtEnd()
{
int lastRow = getRowCount() - 1;
Position cursorPos = getCursorPosition();
return cursorPos.compareTo(Position.create(lastRow,
getLength(lastRow))) == 0;
}
public void clear()
{
setCode("", false);
}
public boolean inMultiSelectMode()
{
return widget_.getEditor().inMultiSelectMode();
}
public void exitMultiSelectMode()
{
widget_.getEditor().exitMultiSelectMode();
}
public void clearSelection()
{
widget_.getEditor().clearSelection();
}
public void collapseSelection(boolean collapseToStart)
{
Selection selection = getSession().getSelection();
Range rng = selection.getRange();
Position pos = collapseToStart ? rng.getStart() : rng.getEnd();
selection.setSelectionRange(Range.fromPoints(pos, pos));
}
public InputEditorSelection getStart()
{
return new InputEditorSelection(
new AceInputEditorPosition(getSession(), Position.create(0, 0)));
}
public InputEditorSelection getEnd()
{
EditSession session = getSession();
int rows = session.getLength();
Position end = Position.create(rows, session.getLine(rows).length());
return new InputEditorSelection(new AceInputEditorPosition(session, end));
}
public String getCurrentLine()
{
int row = getSession().getSelection().getRange().getStart().getRow();
return getSession().getLine(row);
}
public char getCharacterAtCursor()
{
Position cursorPos = getCursorPosition();
int column = cursorPos.getColumn();
String line = getLine(cursorPos.getRow());
if (column == line.length())
return '\0';
return line.charAt(column);
}
public char getCharacterBeforeCursor()
{
Position cursorPos = getCursorPosition();
int column = cursorPos.getColumn();
if (column == 0)
return '\0';
String line = getLine(cursorPos.getRow());
return line.charAt(column - 1);
}
public String getCurrentLineUpToCursor()
{
return getCurrentLine().substring(0, getCursorPosition().getColumn());
}
public int getCurrentLineNum()
{
Position pos = getCursorPosition();
return getSession().documentToScreenRow(pos);
}
public int getCurrentLineCount()
{
return getSession().getScreenLength();
}
@Override
public String getLanguageMode(Position position)
{
return getSession().getMode().getLanguageMode(position);
}
@Override
public String getModeId()
{
return getSession().getMode().getId();
}
public void replaceCode(String code)
{
int endRow, endCol;
endRow = getSession().getLength() - 1;
if (endRow < 0)
{
endRow = 0;
endCol = 0;
}
else
{
endCol = getSession().getLine(endRow).length();
}
Range range = Range.fromPoints(Position.create(0, 0),
Position.create(endRow, endCol));
getSession().replace(range, code);
}
public void replaceSelection(String code)
{
code = StringUtil.normalizeNewLines(code);
Range selRange = getSession().getSelection().getRange();
Position position = getSession().replace(selRange, code);
Range range = Range.fromPoints(selRange.getStart(), position);
getSession().getSelection().setSelectionRange(range);
if (isEmacsModeOn()) clearEmacsMark();
}
public boolean moveSelectionToNextLine(boolean skipBlankLines)
{
int curRow = getSession().getSelection().getCursor().getRow();
while (++curRow < getSession().getLength())
{
String line = getSession().getLine(curRow);
Pattern pattern = Pattern.create("[^\\s]");
Match match = pattern.match(line, 0);
if (skipBlankLines && match == null)
continue;
int col = (match != null) ? match.getIndex() : 0;
getSession().getSelection().moveCursorTo(curRow, col, false);
getSession().unfold(curRow, true);
scrollCursorIntoViewIfNecessary();
return true;
}
return false;
}
@Override
public boolean moveSelectionToBlankLine()
{
int curRow = getSession().getSelection().getCursor().getRow();
// if the current row is the last row then insert a new row
if (curRow == (getSession().getLength() - 1))
{
int rowLen = getSession().getLine(curRow).length();
getSession().getSelection().moveCursorTo(curRow, rowLen, false);
insertCode("\n");
}
while (curRow < getSession().getLength())
{
String line = getSession().getLine(curRow);
if (line.length() == 0)
{
getSession().getSelection().moveCursorTo(curRow, 0, false);
getSession().unfold(curRow, true);
return true;
}
curRow++;
}
return false;
}
@Override
public void expandSelection()
{
widget_.getEditor().expandSelection();
}
@Override
public void shrinkSelection()
{
widget_.getEditor().shrinkSelection();
}
@Override
public void expandRaggedSelection()
{
if (!inMultiSelectMode())
return;
// TODO: It looks like we need to use an alternative API when
// using Vim mode.
if (isVimModeOn())
return;
boolean hasSelection = hasSelection();
Range[] ranges =
widget_.getEditor().getSession().getSelection().getAllRanges();
// Get the maximum columns for the current selection.
int colMin = Integer.MAX_VALUE;
int colMax = 0;
for (Range range : ranges)
{
colMin = Math.min(range.getStart().getColumn(), colMin);
colMax = Math.max(range.getEnd().getColumn(), colMax);
}
// For each range:
//
// 1. Set the left side of the selection to the minimum,
// 2. Set the right side of the selection to the maximum,
// moving the cursor and inserting whitespace as necessary.
for (Range range : ranges)
{
range.getStart().setColumn(colMin);
range.getEnd().setColumn(colMax);
String line = getLine(range.getStart().getRow());
if (line.length() < colMax)
{
insertCode(
Position.create(range.getStart().getRow(), line.length()),
StringUtil.repeat(" ", colMax - line.length()));
}
}
clearSelection();
Selection selection = getNativeSelection();
for (Range range : ranges)
{
if (hasSelection)
selection.addRange(range, true);
else
{
Range newRange = Range.create(
range.getEnd().getRow(),
range.getEnd().getColumn(),
range.getEnd().getRow(),
range.getEnd().getColumn());
selection.addRange(newRange, true);
}
}
}
@Override
public void clearSelectionHistory()
{
widget_.getEditor().clearSelectionHistory();
}
@Override
public void reindent()
{
boolean emptySelection = getSelection().isEmpty();
getSession().reindent(getSession().getSelection().getRange());
if (emptySelection)
moveSelectionToNextLine(false);
}
@Override
public void reindent(Range range)
{
getSession().reindent(range);
}
@Override
public void toggleCommentLines()
{
widget_.getEditor().toggleCommentLines();
}
public ChangeTracker getChangeTracker()
{
return new AceEditorChangeTracker();
}
// Because anchored selections create Ace event listeners, they
// must be explicitly detached (otherwise they will listen for
// edit events into perpetuity). The easiest way to facilitate this
// is to have anchored selection tied to the lifetime of a particular
// 'host' widget; this way, on detach, we can ensure that the associated
// anchors are detached as well.
public AnchoredSelection createAnchoredSelection(Widget hostWidget,
Position startPos,
Position endPos)
{
Anchor start = Anchor.createAnchor(getSession().getDocument(),
startPos.getRow(),
startPos.getColumn());
Anchor end = Anchor.createAnchor(getSession().getDocument(),
endPos.getRow(),
endPos.getColumn());
final AnchoredSelection selection = new AnchoredSelectionImpl(start, end);
if (hostWidget != null)
{
hostWidget.addAttachHandler(new AttachEvent.Handler()
{
@Override
public void onAttachOrDetach(AttachEvent event)
{
if (!event.isAttached() && selection != null)
selection.detach();
}
});
}
return selection;
}
public AnchoredSelection createAnchoredSelection(Position start, Position end)
{
return createAnchoredSelection(null, start, end);
}
public void fitSelectionToLines(boolean expand)
{
Range range = getSession().getSelection().getRange();
Position start = range.getStart();
Position newStart = start;
if (start.getColumn() > 0)
{
if (expand)
{
newStart = Position.create(start.getRow(), 0);
}
else
{
String firstLine = getSession().getLine(start.getRow());
if (firstLine.substring(0, start.getColumn()).trim().length() == 0)
newStart = Position.create(start.getRow(), 0);
}
}
Position end = range.getEnd();
Position newEnd = end;
if (expand)
{
int endRow = end.getRow();
if (endRow == newStart.getRow() || end.getColumn() > 0)
{
// If selection ends at the start of a line, keep the selection
// there--unless that means less than one line will be selected
// in total.
newEnd = Position.create(
endRow, getSession().getLine(endRow).length());
}
}
else
{
while (newEnd.getRow() != newStart.getRow())
{
String line = getSession().getLine(newEnd.getRow());
if (line.substring(0, newEnd.getColumn()).trim().length() != 0)
break;
int prevRow = newEnd.getRow() - 1;
int len = getSession().getLine(prevRow).length();
newEnd = Position.create(prevRow, len);
}
}
getSession().getSelection().setSelectionRange(
Range.fromPoints(newStart, newEnd));
}
public int getSelectionOffset(boolean start)
{
Range range = getSession().getSelection().getRange();
if (start)
return range.getStart().getColumn();
else
return range.getEnd().getColumn();
}
public void onActivate()
{
Scheduler.get().scheduleFinally(new RepeatingCommand()
{
public boolean execute()
{
widget_.onResize();
widget_.onActivate();
return false;
}
});
}
public void onVisibilityChanged(boolean visible)
{
if (visible)
widget_.getEditor().getRenderer().updateFontSize();
}
public void onResize()
{
widget_.onResize();
}
public void setHighlightSelectedLine(boolean on)
{
widget_.getEditor().setHighlightActiveLine(on);
}
public void setHighlightSelectedWord(boolean on)
{
widget_.getEditor().setHighlightSelectedWord(on);
}
public void setShowLineNumbers(boolean on)
{
widget_.getEditor().getRenderer().setShowGutter(on);
}
public void setUseSoftTabs(boolean on)
{
getSession().setUseSoftTabs(on);
}
public void setScrollPastEndOfDocument(boolean enable)
{
widget_.getEditor().getRenderer().setScrollPastEnd(enable);
}
public void setHighlightRFunctionCalls(boolean highlight)
{
_setHighlightRFunctionCallsImpl(highlight);
widget_.getEditor().retokenizeDocument();
}
public void setScrollLeft(int x)
{
getSession().setScrollLeft(x);
}
public void setScrollTop(int y)
{
getSession().setScrollTop(y);
}
public void scrollTo(int x, int y)
{
getSession().setScrollLeft(x);
getSession().setScrollTop(y);
}
private native final void _setHighlightRFunctionCallsImpl(boolean highlight)
/*-{
var Mode = $wnd.require("mode/r_highlight_rules");
Mode.setHighlightRFunctionCalls(highlight);
}-*/;
public void enableSearchHighlight()
{
widget_.getEditor().enableSearchHighlight();
}
public void disableSearchHighlight()
{
widget_.getEditor().disableSearchHighlight();
}
/**
* Warning: This will be overridden whenever the file type is set
*/
public void setUseWrapMode(boolean useWrapMode)
{
getSession().setUseWrapMode(useWrapMode);
}
public void setTabSize(int tabSize)
{
getSession().setTabSize(tabSize);
}
public void setShowInvisibles(boolean show)
{
widget_.getEditor().getRenderer().setShowInvisibles(show);
}
public void setShowIndentGuides(boolean show)
{
widget_.getEditor().getRenderer().setShowIndentGuides(show);
}
public void setBlinkingCursor(boolean blinking)
{
widget_.getEditor().getRenderer().setBlinkingCursor(blinking);
}
public void setShowPrintMargin(boolean on)
{
widget_.getEditor().getRenderer().setShowPrintMargin(on);
}
@Override
public void setUseEmacsKeybindings(boolean use)
{
if (widget_.getEditor().getReadOnly())
return;
useEmacsKeybindings_ = use;
updateKeyboardHandlers();
}
@Override
public boolean isEmacsModeOn()
{
return useEmacsKeybindings_;
}
@Override
public void clearEmacsMark()
{
widget_.getEditor().clearEmacsMark();
}
@Override
public void setUseVimMode(boolean use)
{
// no-op if the editor is read-only (since vim mode doesn't
// work for read-only ace instances)
if (widget_.getEditor().getReadOnly())
return;
useVimMode_ = use;
updateKeyboardHandlers();
}
@Override
public boolean isVimModeOn()
{
return useVimMode_;
}
@Override
public boolean isVimInInsertMode()
{
return useVimMode_ && widget_.getEditor().isVimInInsertMode();
}
public void setPadding(int padding)
{
widget_.getEditor().getRenderer().setPadding(padding);
}
public void setPrintMarginColumn(int column)
{
widget_.getEditor().getRenderer().setPrintMarginColumn(column);
syncWrapLimit();
}
@Override
public JsArray<AceFold> getFolds()
{
return getSession().getAllFolds();
}
@Override
public String getFoldState(int row)
{
AceFold fold = getSession().getFoldAt(row, 0);
if (fold == null)
return null;
Position foldPos = fold.getStart();
return getSession().getState(foldPos.getRow());
}
@Override
public void addFold(Range range)
{
getSession().addFold("...", range);
}
@Override
public void addFoldFromRow(int row)
{
FoldingRules foldingRules = getSession().getMode().getFoldingRules();
if (foldingRules == null)
return;
Range range = foldingRules.getFoldWidgetRange(getSession(),
"markbegin",
row);
if (range != null)
addFold(range);
}
@Override
public void unfold(AceFold fold)
{
getSession().unfold(Range.fromPoints(fold.getStart(), fold.getEnd()),
false);
}
@Override
public void unfold(int row)
{
getSession().unfold(row, false);
}
@Override
public void unfold(Range range)
{
getSession().unfold(range, false);
}
public void setReadOnly(boolean readOnly)
{
widget_.getEditor().setReadOnly(readOnly);
}
public HandlerRegistration addCursorChangedHandler(final CursorChangedHandler handler)
{
return widget_.addCursorChangedHandler(handler);
}
public HandlerRegistration addSaveCompletedHandler(SaveFileHandler handler)
{
return handlers_.addHandler(SaveFileEvent.TYPE, handler);
}
public HandlerRegistration addAttachHandler(AttachEvent.Handler handler)
{
return widget_.addAttachHandler(handler);
}
public HandlerRegistration addEditorFocusHandler(FocusHandler handler)
{
return widget_.addFocusHandler(handler);
}
public Scope getCurrentScope()
{
return getSession().getMode().getCodeModel().getCurrentScope(
getCursorPosition());
}
public Scope getScopeAtPosition(Position position)
{
return getSession().getMode().getCodeModel().getCurrentScope(position);
}
@Override
public String getNextLineIndent()
{
EditSession session = getSession();
Position cursorPosition = getCursorPosition();
int row = cursorPosition.getRow();
String state = getSession().getState(row);
String line = getCurrentLine().substring(
0, cursorPosition.getColumn());
String tab = session.getTabString();
int tabSize = session.getTabSize();
return session.getMode().getNextLineIndent(
state,
line,
tab,
tabSize,
row);
}
public Scope getCurrentChunk()
{
return getCurrentChunk(getCursorPosition());
}
@Override
public Scope getCurrentChunk(Position position)
{
return getSession().getMode().getCodeModel().getCurrentChunk(position);
}
@Override
public ScopeFunction getCurrentFunction(boolean allowAnonymous)
{
return getFunctionAtPosition(getCursorPosition(), allowAnonymous);
}
@Override
public ScopeFunction getFunctionAtPosition(Position position,
boolean allowAnonymous)
{
return getSession().getMode().getCodeModel().getCurrentFunction(
position, allowAnonymous);
}
@Override
public Scope getCurrentSection()
{
return getSectionAtPosition(getCursorPosition());
}
@Override
public Scope getSectionAtPosition(Position position)
{
return getSession().getMode().getCodeModel().getCurrentSection(position);
}
public Position getCursorPosition()
{
return getSession().getSelection().getCursor();
}
public Position getCursorPositionScreen()
{
return widget_.getEditor().getCursorPositionScreen();
}
public void setCursorPosition(Position position)
{
getSession().getSelection().setSelectionRange(
Range.fromPoints(position, position));
}
public void goToLineStart()
{
widget_.getEditor().getCommandManager().exec("gotolinestart", widget_.getEditor());
}
public void goToLineEnd()
{
widget_.getEditor().getCommandManager().exec("gotolineend", widget_.getEditor());
}
@Override
public void moveCursorNearTop(int rowOffset)
{
int screenRow = getSession().documentToScreenRow(getCursorPosition());
widget_.getEditor().scrollToRow(Math.max(0, screenRow - rowOffset));
}
@Override
public void moveCursorForward()
{
moveCursorForward(1);
}
@Override
public void moveCursorForward(int characters)
{
widget_.getEditor().moveCursorRight(characters);
}
@Override
public void moveCursorBackward()
{
moveCursorBackward(1);
}
@Override
public void moveCursorBackward(int characters)
{
widget_.getEditor().moveCursorLeft(characters);
}
@Override
public void moveCursorNearTop()
{
moveCursorNearTop(7);
}
@Override
public void ensureCursorVisible()
{
if (!widget_.getEditor().isRowFullyVisible(getCursorPosition().getRow()))
moveCursorNearTop();
}
@Override
public void ensureRowVisible(int row)
{
if (!widget_.getEditor().isRowFullyVisible(row))
setCursorPosition(Position.create(row, 0));
}
@Override
public void scrollCursorIntoViewIfNecessary(int rowsAround)
{
Position cursorPos = getCursorPosition();
int cursorRow = cursorPos.getRow();
if (cursorRow >= widget_.getEditor().getLastVisibleRow() - rowsAround)
{
Position alignPos = Position.create(cursorRow + rowsAround, 0);
widget_.getEditor().getRenderer().alignCursor(alignPos, 1);
}
else if (cursorRow <= widget_.getEditor().getFirstVisibleRow() + rowsAround)
{
Position alignPos = Position.create(cursorRow - rowsAround, 0);
widget_.getEditor().getRenderer().alignCursor(alignPos, 0);
}
}
@Override
public void scrollCursorIntoViewIfNecessary()
{
scrollCursorIntoViewIfNecessary(0);
}
@Override
public boolean isCursorInSingleLineString()
{
return StringUtil.isEndOfLineInRStringState(getCurrentLineUpToCursor());
}
public void gotoPageUp()
{
widget_.getEditor().gotoPageUp();
}
public void gotoPageDown()
{
widget_.getEditor().gotoPageDown();
}
public void scrollToBottom()
{
SourcePosition pos = SourcePosition.create(getCurrentLineCount() - 1, 0);
navigate(pos, false);
}
public void revealRange(Range range, boolean animate)
{
widget_.getEditor().revealRange(range, animate);
}
public CodeModel getCodeModel()
{
return getSession().getMode().getCodeModel();
}
public boolean hasCodeModel()
{
return getSession().getMode().hasCodeModel();
}
public boolean hasScopeTree()
{
return hasCodeModel() && getCodeModel().hasScopes();
}
public void buildScopeTree()
{
// Builds the scope tree as a side effect
if (hasScopeTree())
getScopeTree();
}
public int buildScopeTreeUpToRow(int row)
{
if (!hasScopeTree())
return 0;
return getSession().getMode().getRCodeModel().buildScopeTreeUpToRow(row);
}
public JsArray<Scope> getScopeTree()
{
return getSession().getMode().getCodeModel().getScopeTree();
}
@Override
public InsertChunkInfo getInsertChunkInfo()
{
return getSession().getMode().getInsertChunkInfo();
}
@Override
public void foldAll()
{
getSession().foldAll();
}
@Override
public void unfoldAll()
{
getSession().unfoldAll();
}
@Override
public void toggleFold()
{
getSession().toggleFold();
}
@Override
public void setFoldStyle(String style)
{
getSession().setFoldStyle(style);
}
@Override
public JsMap<Position> getMarks()
{
return widget_.getEditor().getMarks();
}
@Override
public void setMarks(JsMap<Position> marks)
{
widget_.getEditor().setMarks(marks);
}
@Override
public void jumpToMatching()
{
widget_.getEditor().jumpToMatching(false, false);
}
@Override
public void selectToMatching()
{
widget_.getEditor().jumpToMatching(true, false);
}
@Override
public void expandToMatching()
{
widget_.getEditor().jumpToMatching(true, true);
}
@Override
public void addCursorAbove()
{
widget_.getEditor().execCommand("addCursorAbove");
}
@Override
public void addCursorBelow()
{
widget_.getEditor().execCommand("addCursorBelow");
}
@Override
public void splitIntoLines()
{
widget_.getEditor().splitIntoLines();
}
@Override
public int getFirstFullyVisibleRow()
{
return widget_.getEditor().getRenderer().getFirstFullyVisibleRow();
}
@Override
public SourcePosition findFunctionPositionFromCursor(String functionName)
{
Scope func =
getSession().getMode().getCodeModel().findFunctionDefinitionFromUsage(
getCursorPosition(),
functionName);
if (func != null)
{
Position position = func.getPreamble();
return SourcePosition.create(position.getRow(), position.getColumn());
}
else
{
return null;
}
}
public JsArray<ScopeFunction> getAllFunctionScopes()
{
CodeModel codeModel = widget_.getEditor().getSession().getMode().getRCodeModel();
if (codeModel == null)
return null;
return codeModel.getAllFunctionScopes();
}
@Override
public void recordCurrentNavigationPosition()
{
fireRecordNavigationPosition(getCursorPosition());
}
@Override
public void navigateToPosition(SourcePosition position,
boolean recordCurrent)
{
navigateToPosition(position, recordCurrent, false);
}
@Override
public void navigateToPosition(SourcePosition position,
boolean recordCurrent,
boolean highlightLine)
{
if (recordCurrent)
recordCurrentNavigationPosition();
navigate(position, true, highlightLine);
}
@Override
public void restorePosition(SourcePosition position)
{
navigate(position, false);
}
@Override
public boolean isAtSourceRow(SourcePosition position)
{
Position currPos = getCursorPosition();
return currPos.getRow() == position.getRow();
}
@Override
public void highlightDebugLocation(SourcePosition startPosition,
SourcePosition endPosition,
boolean executing)
{
int firstRow = widget_.getEditor().getFirstVisibleRow();
int lastRow = widget_.getEditor().getLastVisibleRow();
// if the expression is large, let's just try to land in the middle
int debugRow = (int) Math.floor(startPosition.getRow() + (
endPosition.getRow() - startPosition.getRow())/2);
// if the row at which the debugging occurs is inside a fold, unfold it
getSession().unfold(debugRow, true);
// if the line to be debugged is past or near the edges of the screen,
// scroll it into view. allow some lines of context.
if (debugRow <= (firstRow + DEBUG_CONTEXT_LINES) ||
debugRow >= (lastRow - DEBUG_CONTEXT_LINES))
{
widget_.getEditor().scrollToLine(debugRow, true);
}
applyDebugLineHighlight(
startPosition.asPosition(),
endPosition.asPosition(),
executing);
}
@Override
public void endDebugHighlighting()
{
clearDebugLineHighlight();
}
@Override
public HandlerRegistration addBreakpointSetHandler(
BreakpointSetEvent.Handler handler)
{
return widget_.addBreakpointSetHandler(handler);
}
@Override
public HandlerRegistration addBreakpointMoveHandler(
BreakpointMoveEvent.Handler handler)
{
return widget_.addBreakpointMoveHandler(handler);
}
@Override
public void addOrUpdateBreakpoint(Breakpoint breakpoint)
{
widget_.addOrUpdateBreakpoint(breakpoint);
}
@Override
public void removeBreakpoint(Breakpoint breakpoint)
{
widget_.removeBreakpoint(breakpoint);
}
@Override
public void toggleBreakpointAtCursor()
{
widget_.toggleBreakpointAtCursor();
}
@Override
public void removeAllBreakpoints()
{
widget_.removeAllBreakpoints();
}
@Override
public boolean hasBreakpoints()
{
return widget_.hasBreakpoints();
}
public void setChunkLineExecState(int start, int end, int state)
{
widget_.setChunkLineExecState(start, end, state);
}
private void navigate(SourcePosition srcPosition, boolean addToHistory)
{
navigate(srcPosition, addToHistory, false);
}
private void navigate(SourcePosition srcPosition,
boolean addToHistory,
boolean highlightLine)
{
// get existing cursor position
Position previousCursorPos = getCursorPosition();
// set cursor to function line
Position position = Position.create(srcPosition.getRow(),
srcPosition.getColumn());
setCursorPosition(position);
// skip whitespace if necessary
if (srcPosition.getColumn() == 0)
{
int curRow = getSession().getSelection().getCursor().getRow();
String line = getSession().getLine(curRow);
int funStart = line.indexOf(line.trim());
position = Position.create(curRow, funStart);
setCursorPosition(position);
}
// scroll as necessary
if (srcPosition.getScrollPosition() != -1)
scrollToY(srcPosition.getScrollPosition(), 0);
else if (position.getRow() != previousCursorPos.getRow())
moveCursorNearTop();
else
ensureCursorVisible();
// set focus
focus();
if (highlightLine)
applyLineHighlight(position.getRow());
// add to navigation history if requested and our current mode
// supports history navigation
if (addToHistory)
fireRecordNavigationPosition(position);
}
private void fireRecordNavigationPosition(Position pos)
{
SourcePosition srcPos = SourcePosition.create(pos.getRow(),
pos.getColumn());
fireEvent(new RecordNavigationPositionEvent(srcPos));
}
@Override
public HandlerRegistration addRecordNavigationPositionHandler(
RecordNavigationPositionHandler handler)
{
return handlers_.addHandler(RecordNavigationPositionEvent.TYPE, handler);
}
@Override
public HandlerRegistration addCommandClickHandler(
CommandClickEvent.Handler handler)
{
return handlers_.addHandler(CommandClickEvent.TYPE, handler);
}
@Override
public HandlerRegistration addFindRequestedHandler(
FindRequestedEvent.Handler handler)
{
return handlers_.addHandler(FindRequestedEvent.TYPE, handler);
}
public void setFontSize(double size)
{
// No change needed--the AceEditorWidget uses the "normalSize" style
// However, we do need to resize the gutter
widget_.getEditor().getRenderer().updateFontSize();
widget_.forceResize();
widget_.getLineWidgetManager().syncLineWidgetHeights();
}
public HandlerRegistration addValueChangeHandler(
ValueChangeHandler<Void> handler)
{
return handlers_.addHandler(ValueChangeEvent.getType(), handler);
}
public HandlerRegistration addFoldChangeHandler(
FoldChangeEvent.Handler handler)
{
return handlers_.addHandler(FoldChangeEvent.TYPE, handler);
}
public HandlerRegistration addLineWidgetsChangedHandler(
LineWidgetsChangedEvent.Handler handler)
{
return handlers_.addHandler(LineWidgetsChangedEvent.TYPE, handler);
}
public boolean isScopeTreeReady(int row)
{
return backgroundTokenizer_.isReady(row);
}
public HandlerRegistration addScopeTreeReadyHandler(ScopeTreeReadyEvent.Handler handler)
{
return handlers_.addHandler(ScopeTreeReadyEvent.TYPE, handler);
}
public HandlerRegistration addRenderFinishedHandler(RenderFinishedEvent.Handler handler)
{
return widget_.addHandler(handler, RenderFinishedEvent.TYPE);
}
public HandlerRegistration addDocumentChangedHandler(DocumentChangedEvent.Handler handler)
{
return widget_.addHandler(handler, DocumentChangedEvent.TYPE);
}
public HandlerRegistration addCapturingKeyDownHandler(KeyDownHandler handler)
{
return widget_.addCapturingKeyDownHandler(handler);
}
public HandlerRegistration addCapturingKeyPressHandler(KeyPressHandler handler)
{
return widget_.addCapturingKeyPressHandler(handler);
}
public HandlerRegistration addCapturingKeyUpHandler(KeyUpHandler handler)
{
return widget_.addCapturingKeyUpHandler(handler);
}
public HandlerRegistration addUndoRedoHandler(UndoRedoHandler handler)
{
return widget_.addUndoRedoHandler(handler);
}
public HandlerRegistration addPasteHandler(PasteEvent.Handler handler)
{
return widget_.addPasteHandler(handler);
}
public HandlerRegistration addAceClickHandler(Handler handler)
{
return widget_.addAceClickHandler(handler);
}
public HandlerRegistration addEditorModeChangedHandler(
EditorModeChangedEvent.Handler handler)
{
return handlers_.addHandler(EditorModeChangedEvent.TYPE, handler);
}
public JavaScriptObject getCleanStateToken()
{
return getSession().getUndoManager().peek();
}
public boolean checkCleanStateToken(JavaScriptObject token)
{
JavaScriptObject other = getSession().getUndoManager().peek();
if (token == null ^ other == null)
return false;
return token == null || other.equals(token);
}
public void fireEvent(GwtEvent<?> event)
{
handlers_.fireEvent(event);
}
public Widget asWidget()
{
return widget_;
}
public EditSession getSession()
{
return widget_.getEditor().getSession();
}
public HandlerRegistration addBlurHandler(BlurHandler handler)
{
return widget_.addBlurHandler(handler);
}
public HandlerRegistration addMouseDownHandler(MouseDownHandler handler)
{
return widget_.addMouseDownHandler(handler);
}
public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler)
{
return widget_.addMouseMoveHandler(handler);
}
public HandlerRegistration addMouseUpHandler(MouseUpHandler handler)
{
return widget_.addMouseUpHandler(handler);
}
public HandlerRegistration addClickHandler(ClickHandler handler)
{
return widget_.addClickHandler(handler);
}
public HandlerRegistration addFocusHandler(FocusHandler handler)
{
return widget_.addFocusHandler(handler);
}
public AceEditorWidget getWidget()
{
return widget_;
}
public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
{
return widget_.addKeyDownHandler(handler);
}
public HandlerRegistration addKeyPressHandler(KeyPressHandler handler)
{
return widget_.addKeyPressHandler(handler);
}
public HandlerRegistration addKeyUpHandler(KeyUpHandler handler)
{
return widget_.addKeyUpHandler(handler);
}
public HandlerRegistration addSelectionChangedHandler(AceSelectionChangedEvent.Handler handler)
{
return widget_.addSelectionChangedHandler(handler);
}
public void autoHeight()
{
widget_.autoHeight();
}
public void forceCursorChange()
{
widget_.forceCursorChange();
}
public void scrollToCursor(ScrollPanel scrollPanel,
int paddingVert,
int paddingHoriz)
{
DomUtils.ensureVisibleVert(
scrollPanel.getElement(),
widget_.getEditor().getRenderer().getCursorElement(),
paddingVert);
DomUtils.ensureVisibleHoriz(
scrollPanel.getElement(),
widget_.getEditor().getRenderer().getCursorElement(),
paddingHoriz, paddingHoriz,
false);
}
public void scrollToLine(int row, boolean center)
{
widget_.getEditor().scrollToLine(row, center);
}
public void centerSelection()
{
widget_.getEditor().centerSelection();
}
public void alignCursor(Position position, double ratio)
{
widget_.getEditor().getRenderer().alignCursor(position, ratio);
}
public void forceImmediateRender()
{
widget_.getEditor().getRenderer().forceImmediateRender();
}
public void setNewLineMode(NewLineMode mode)
{
getSession().setNewLineMode(mode.getType());
}
public boolean isPasswordMode()
{
return passwordMode_;
}
public void setPasswordMode(boolean passwordMode)
{
passwordMode_ = passwordMode;
widget_.getEditor().getRenderer().setPasswordMode(passwordMode);
}
public void setDisableOverwrite(boolean disableOverwrite)
{
getSession().setDisableOverwrite(disableOverwrite);
}
private Integer createLineHighlightMarker(int line, String style)
{
return createRangeHighlightMarker(Position.create(line, 0),
Position.create(line+1, 0),
style);
}
private Integer createRangeHighlightMarker(
Position start,
Position end,
String style)
{
Range range = Range.fromPoints(start, end);
return getSession().addMarker(range, style, "text", false);
}
private void applyLineHighlight(int line)
{
clearLineHighlight();
if (!widget_.getEditor().getHighlightActiveLine())
{
lineHighlightMarkerId_ = createLineHighlightMarker(line,
"ace_find_line");
}
}
private void clearLineHighlight()
{
if (lineHighlightMarkerId_ != null)
{
getSession().removeMarker(lineHighlightMarkerId_);
lineHighlightMarkerId_ = null;
}
}
private void applyDebugLineHighlight(
Position startPos,
Position endPos,
boolean executing)
{
clearDebugLineHighlight();
lineDebugMarkerId_ = createRangeHighlightMarker(
startPos, endPos,
"ace_active_debug_line");
if (executing)
{
executionLine_ = startPos.getRow();
widget_.getEditor().getRenderer().addGutterDecoration(
executionLine_,
"ace_executing-line");
}
}
private void clearDebugLineHighlight()
{
if (lineDebugMarkerId_ != null)
{
getSession().removeMarker(lineDebugMarkerId_);
lineDebugMarkerId_ = null;
}
if (executionLine_ != null)
{
widget_.getEditor().getRenderer().removeGutterDecoration(
executionLine_,
"ace_executing-line");
executionLine_ = null;
}
}
public void setPopupVisible(boolean visible)
{
popupVisible_ = visible;
}
public boolean isPopupVisible()
{
return popupVisible_;
}
public void selectAll(String needle)
{
widget_.getEditor().findAll(needle);
}
public void selectAll(String needle, Range range, boolean wholeWord, boolean caseSensitive)
{
widget_.getEditor().findAll(needle, range, wholeWord, caseSensitive);
}
public void moveCursorLeft()
{
moveCursorLeft(1);
}
public void moveCursorLeft(int times)
{
widget_.getEditor().moveCursorLeft(times);
}
public void moveCursorRight()
{
moveCursorRight(1);
}
public void moveCursorRight(int times)
{
widget_.getEditor().moveCursorRight(times);
}
public void expandSelectionLeft(int times)
{
widget_.getEditor().expandSelectionLeft(times);
}
public void expandSelectionRight(int times)
{
widget_.getEditor().expandSelectionRight(times);
}
public int getTabSize()
{
return widget_.getEditor().getSession().getTabSize();
}
// TODO: Enable similar logic for C++ mode?
public int getStartOfCurrentStatement()
{
if (!DocumentMode.isSelectionInRMode(this))
return -1;
TokenCursor cursor =
getSession().getMode().getCodeModel().getTokenCursor();
if (!cursor.moveToPosition(getCursorPosition()))
return -1;
if (!cursor.moveToStartOfCurrentStatement())
return -1;
return cursor.getRow();
}
// TODO: Enable similar logic for C++ mode?
public int getEndOfCurrentStatement()
{
if (!DocumentMode.isSelectionInRMode(this))
return -1;
TokenCursor cursor =
getSession().getMode().getCodeModel().getTokenCursor();
if (!cursor.moveToPosition(getCursorPosition()))
return -1;
if (!cursor.moveToEndOfCurrentStatement())
return -1;
return cursor.getRow();
}
private boolean rowEndsInBinaryOp(int row)
{
// move to the last interesting token on this line
JsArray<Token> tokens = getSession().getTokens(row);
for (int i = tokens.length() - 1; i >= 0; i--)
{
Token t = tokens.get(i);
if (t.hasType("text", "comment", "virtual-comment"))
continue;
if (t.getType() == "keyword.operator" ||
t.getType() == "keyword.operator.infix" ||
t.getValue() == ",")
return true;
break;
}
return false;
}
private boolean rowIsEmptyOrComment(int row)
{
JsArray<Token> tokens = getSession().getTokens(row);
for (int i = 0, n = tokens.length(); i < n; i++)
if (!tokens.get(i).hasType("text", "comment", "virtual-comment"))
return false;
return true;
}
private boolean rowStartsWithClosingBracket(int row)
{
JsArray<Token> tokens = getSession().getTokens(row);
int n = tokens.length();
if (n == 0)
return false;
for (int i = 0; i < n; i++)
{
Token token = tokens.get(i);
if (token.hasType("text"))
continue;
String tokenValue = token.getValue();
return tokenValue.equals("}") ||
tokenValue.equals(")") ||
tokenValue.equals("]");
}
return false;
}
private boolean rowEndsWithOpenBracket(int row)
{
JsArray<Token> tokens = getSession().getTokens(row);
int n = tokens.length();
if (n == 0)
return false;
for (int i = 0; i < n; i++)
{
Token token = tokens.get(n - i - 1);
if (token.hasType("text", "comment", "virtual-comment"))
continue;
String tokenValue = token.getValue();
return tokenValue.equals("{") ||
tokenValue.equals("(") ||
tokenValue.equals("[");
}
return false;
}
/**
* Finds the last non-empty line starting at the given line.
*
* @param initial Row to start on
* @param limit Row at which to stop searching
* @return Index of last non-empty line, or limit line if no empty lines
* were found.
*/
private int findParagraphBoundary(int initial, int limit)
{
// no work to do if already at limit
if (initial == limit)
return initial;
// walk towards limit
int delta = limit > initial ? 1 : -1;
for (int row = initial + delta; row != limit; row += delta)
{
if (getLine(row).trim().isEmpty())
return row - delta;
}
// didn't find boundary
return limit;
}
@Override
public Range getParagraph(Position pos, int startRowLimit, int endRowLimit)
{
// find upper and lower paragraph boundaries
return Range.create(
findParagraphBoundary(pos.getRow(), startRowLimit), 0,
findParagraphBoundary(pos.getRow(), endRowLimit)+ 1, 0);
}
@Override
public Range getMultiLineExpr(Position pos, int startRowLimit, int endRowLimit)
{
if (!DocumentMode.isSelectionInRMode(this))
return null;
// create token cursor (will be used to walk tokens as needed)
TokenCursor c = getSession().getMode().getCodeModel().getTokenCursor();
// assume start, end at current position
int startRow = pos.getRow();
int endRow = pos.getRow();
// expand to enclosing '(' or '['
do
{
c.setRow(pos.getRow());
// move forward over commented / empty lines
int n = getSession().getLength();
while (rowIsEmptyOrComment(c.getRow()))
{
if (c.getRow() == n - 1)
break;
c.setRow(c.getRow() + 1);
}
// move to last non-right-bracket token on line
c.moveToEndOfRow(c.getRow());
while (c.valueEquals(")") || c.valueEquals("]"))
if (!c.moveToPreviousToken())
break;
// find the top-most enclosing bracket
// check for function scope
String[] candidates = new String[] {"(", "["};
int savedRow = -1;
int savedOffset = -1;
while (c.findOpeningBracket(candidates, true))
{
// check for function scope
if (c.valueEquals("(") &&
c.peekBwd(1).valueEquals("function") &&
c.peekBwd(2).isLeftAssign())
{
ScopeFunction scope = getFunctionAtPosition(c.currentPosition(), false);
if (scope != null)
return Range.fromPoints(scope.getPreamble(), scope.getEnd());
}
// move off of opening bracket and continue lookup
savedRow = c.getRow();
savedOffset = c.getOffset();
if (!c.moveToPreviousToken())
break;
}
// if we found a row, use it
if (savedRow != -1 && savedOffset != -1)
{
c.setRow(savedRow);
c.setOffset(savedOffset);
if (c.fwdToMatchingToken())
{
startRow = savedRow;
endRow = c.getRow();
}
}
} while (false);
// discover start of current statement
while (startRow >= startRowLimit)
{
// if the row starts with an open bracket, expand to its match
if (rowStartsWithClosingBracket(startRow))
{
c.moveToStartOfRow(startRow);
if (c.bwdToMatchingToken())
{
startRow = c.getRow();
continue;
}
}
else if (startRow >= 0 && rowEndsInBinaryOp(startRow - 1) || rowIsEmptyOrComment(startRow - 1))
{
startRow--;
continue;
}
break;
}
// discover end of current statement -- we search from the inferred statement
// start, so that we can perform counting of matching pairs of brackets
endRow = startRow;
// NOTE: '[[' is not tokenized as a single token in our Ace tokenizer,
// so it is not included here (this shouldn't cause issues in practice
// since balanced pairs of '[' and '[[' would still imply a correct count
// of matched pairs of '[' anyhow)
int parenCount = 0; // '(', ')'
int braceCount = 0; // '{', '}'
int bracketCount = 0; // '[', ']'
while (endRow <= endRowLimit)
{
// update bracket token counts
JsArray<Token> tokens = getTokens(endRow);
for (Token token : JsUtil.asIterable(tokens))
{
String value = token.getValue();
parenCount += value.equals("(") ? 1 : 0;
parenCount -= value.equals(")") ? 1 : 0;
braceCount += value.equals("{") ? 1 : 0;
braceCount -= value.equals("}") ? 1 : 0;
bracketCount += value.equals("[") ? 1 : 0;
bracketCount -= value.equals("]") ? 1 : 0;
}
// continue search if line ends with binary operator
if (rowEndsInBinaryOp(endRow) || rowIsEmptyOrComment(endRow))
{
endRow++;
continue;
}
// continue search if we have unbalanced brackets
if (parenCount > 0 || braceCount > 0 || bracketCount > 0)
{
endRow++;
continue;
}
// we had balanced brackets and no trailing binary operator; bail
break;
}
// shrink selection for empty lines at borders
while (startRow < endRow && rowIsEmptyOrComment(startRow))
startRow++;
while (endRow > startRow && rowIsEmptyOrComment(endRow))
endRow--;
// fixup for single-line execution
if (startRow > endRow)
startRow = endRow;
// if we've captured the body of a function definition, expand
// to include whole definition
c.setRow(startRow);
c.setOffset(0);
if (c.valueEquals("{") &&
c.moveToPreviousToken() &&
c.valueEquals(")") &&
c.bwdToMatchingToken() &&
c.moveToPreviousToken() &&
c.valueEquals("function") &&
c.moveToPreviousToken() &&
c.isLeftAssign())
{
ScopeFunction fn = getFunctionAtPosition(c.currentPosition(), false);
if (fn != null)
return Range.fromPoints(fn.getPreamble(), fn.getEnd());
}
// construct range
int endColumn = getSession().getLine(endRow).length();
Range range = Range.create(startRow, 0, endRow, endColumn);
// return empty range if nothing to execute
if (getTextForRange(range).trim().isEmpty())
range = Range.fromPoints(pos, pos);
return range;
}
// ---- Annotation related operations
public JsArray<AceAnnotation> getAnnotations()
{
return widget_.getAnnotations();
}
public void setAnnotations(JsArray<AceAnnotation> annotations)
{
widget_.setAnnotations(annotations);
}
@Override
public void removeMarkersAtCursorPosition()
{
widget_.removeMarkersAtCursorPosition();
}
@Override
public void removeMarkersOnCursorLine()
{
widget_.removeMarkersOnCursorLine();
}
@Override
public void showLint(JsArray<LintItem> lint)
{
widget_.showLint(lint);
}
@Override
public void clearLint()
{
widget_.clearLint();
}
@Override
public void showInfoBar(String message)
{
if (infoBar_ == null)
{
infoBar_ = new AceInfoBar(widget_);
widget_.addKeyDownHandler(new KeyDownHandler()
{
@Override
public void onKeyDown(KeyDownEvent event)
{
if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE)
infoBar_.hide();
}
});
}
infoBar_.setText(message);
infoBar_.show();
}
public AnchoredRange createAnchoredRange(Position start, Position end)
{
return widget_.getEditor().getSession().createAnchoredRange(start, end);
}
public void insertRoxygenSkeleton()
{
getSession().getMode().getCodeModel().insertRoxygenSkeleton();
}
public long getLastModifiedTime()
{
return lastModifiedTime_;
}
public long getLastCursorChangedTime()
{
return lastCursorChangedTime_;
}
public int getFirstVisibleRow()
{
return widget_.getEditor().getFirstVisibleRow();
}
public int getLastVisibleRow()
{
return widget_.getEditor().getLastVisibleRow();
}
public void blockOutdent()
{
widget_.getEditor().blockOutdent();
}
public ScreenCoordinates documentPositionToScreenCoordinates(Position position)
{
return widget_.getEditor().getRenderer().textToScreenCoordinates(position);
}
public Position screenCoordinatesToDocumentPosition(int pageX, int pageY)
{
return widget_.getEditor().getRenderer().screenToTextCoordinates(pageX, pageY);
}
public boolean isPositionVisible(Position position)
{
return widget_.getEditor().isRowFullyVisible(position.getRow());
}
@Override
public void tokenizeDocument()
{
widget_.getEditor().tokenizeDocument();
}
@Override
public void retokenizeDocument()
{
widget_.getEditor().retokenizeDocument();
}
@Override
public Token getTokenAt(int row, int column)
{
return getSession().getTokenAt(row, column);
}
@Override
public Token getTokenAt(Position position)
{
return getSession().getTokenAt(position);
}
@Override
public JsArray<Token> getTokens(int row)
{
return getSession().getTokens(row);
}
@Override
public TokenIterator createTokenIterator()
{
return createTokenIterator(null);
}
@Override
public TokenIterator createTokenIterator(Position position)
{
TokenIterator it = TokenIterator.create(getSession());
if (position != null)
it.moveToPosition(position);
return it;
}
@Override
public void beginCollabSession(CollabEditStartParams params,
DirtyState dirtyState)
{
// suppress external value change events while the editor's contents are
// being swapped out for the contents of the collab session--otherwise
// there's going to be a lot of flickering as dirty state (etc) try to
// keep up
valueChangeSuppressed_ = true;
collab_.beginCollabSession(this, params, dirtyState, new Command()
{
@Override
public void execute()
{
valueChangeSuppressed_ = false;
}
});
}
@Override
public boolean hasActiveCollabSession()
{
return collab_.hasActiveCollabSession(this);
}
@Override
public boolean hasFollowingCollabSession()
{
return collab_.hasFollowingCollabSession(this);
}
public void endCollabSession()
{
collab_.endCollabSession(this);
}
@Override
public void setDragEnabled(boolean enabled)
{
widget_.setDragEnabled(enabled);
}
@Override
public boolean isSnippetsTabStopManagerActive()
{
return isSnippetsTabStopManagerActiveImpl(widget_.getEditor());
}
private static final native
boolean isSnippetsTabStopManagerActiveImpl(AceEditorNative editor)
/*-{
return editor.tabstopManager != null;
}-*/;
@Override
public boolean onInsertSnippet()
{
return snippets_.onInsertSnippet();
}
public void toggleTokenInfo()
{
toggleTokenInfo(widget_.getEditor());
}
private static final native void toggleTokenInfo(AceEditorNative editor) /*-{
if (editor.tokenTooltip && editor.tokenTooltip.destroy) {
editor.tokenTooltip.destroy();
} else {
var TokenTooltip = $wnd.require("ace/token_tooltip").TokenTooltip;
editor.tokenTooltip = new TokenTooltip(editor);
}
}-*/;
@Override
public void addLineWidget(final LineWidget widget)
{
// position the element far offscreen if it's above the currently
// visible row; Ace does not position line widgets above the viewport
// until the document is scrolled there
if (widget.getRow() < getFirstVisibleRow())
{
widget.getElement().getStyle().setTop(-10000, Unit.PX);
// set left/right values so that the widget consumes space; necessary
// to get layout offsets inside the widget while rendering but before
// it comes onscreen
widget.getElement().getStyle().setLeft(48, Unit.PX);
widget.getElement().getStyle().setRight(15, Unit.PX);
}
widget_.getLineWidgetManager().addLineWidget(widget);
adjustScrollForLineWidget(widget);
fireLineWidgetsChanged();
}
@Override
public void removeLineWidget(LineWidget widget)
{
widget_.getLineWidgetManager().removeLineWidget(widget);
fireLineWidgetsChanged();
}
@Override
public void removeAllLineWidgets()
{
widget_.getLineWidgetManager().removeAllLineWidgets();
fireLineWidgetsChanged();
}
@Override
public void onLineWidgetChanged(LineWidget widget)
{
// if the widget is above the viewport, this size change might push it
// into visibility, so push it offscreen first
if (widget.getRow() + 1 < getFirstVisibleRow())
widget.getElement().getStyle().setTop(-10000, Unit.PX);
widget_.getLineWidgetManager().onWidgetChanged(widget);
adjustScrollForLineWidget(widget);
fireLineWidgetsChanged();
}
@Override
public JsArray<LineWidget> getLineWidgets()
{
return widget_.getLineWidgetManager().getLineWidgets();
}
@Override
public LineWidget getLineWidgetForRow(int row)
{
return widget_.getLineWidgetManager().getLineWidgetForRow(row);
}
@Override
public boolean hasLineWidgets()
{
return widget_.getLineWidgetManager().hasLineWidgets();
}
private void adjustScrollForLineWidget(LineWidget w)
{
// the cursor is above the line widget, so the line widget is going
// to change the cursor position; adjust the scroll position to hold
// the cursor in place
if (getCursorPosition().getRow() > w.getRow())
{
int delta = w.getElement().getOffsetHeight() - w.getRenderedHeight();
// skip if no change to report
if (delta == 0)
return;
// we adjust the scrolltop on the session since it knows the
// currently queued scroll position; the renderer only knows the
// actual scroll position, which may not reflect unrendered changes
getSession().setScrollTop(getSession().getScrollTop() + delta);
}
// mark the current height as rendered
w.setRenderedHeight(w.getElement().getOffsetHeight());
}
@Override
public JsArray<ChunkDefinition> getChunkDefs()
{
// chunk definitions are populated at render time, so don't return any
// if we haven't rendered yet
if (!isRendered())
return null;
JsArray<ChunkDefinition> chunks = JsArray.createArray().cast();
JsArray<LineWidget> lineWidgets = getLineWidgets();
ScopeList scopes = new ScopeList(this);
for (int i = 0; i<lineWidgets.length(); i++)
{
LineWidget lineWidget = lineWidgets.get(i);
if (lineWidget.getType().equals(ChunkDefinition.LINE_WIDGET_TYPE))
{
ChunkDefinition chunk = lineWidget.getData();
chunks.push(chunk.with(lineWidget.getRow(),
TextEditingTargetNotebook.getKnitrChunkLabel(
lineWidget.getRow(), this, scopes)));
}
}
return chunks;
}
@Override
public boolean isRendered()
{
return widget_.isRendered();
}
@Override
public boolean showChunkOutputInline()
{
return showChunkOutputInline_;
}
@Override
public void setShowChunkOutputInline(boolean show)
{
showChunkOutputInline_ = show;
}
private void fireLineWidgetsChanged()
{
AceEditor.this.fireEvent(new LineWidgetsChangedEvent());
}
private static class BackgroundTokenizer
{
public BackgroundTokenizer(final AceEditor editor)
{
editor_ = editor;
timer_ = new Timer()
{
@Override
public void run()
{
// Stop our timer if we've tokenized up to the end of the document.
if (row_ >= editor_.getRowCount())
{
editor_.fireEvent(new ScopeTreeReadyEvent(
editor_.getScopeTree(),
editor_.getCurrentScope()));
return;
}
row_ += ROWS_TOKENIZED_PER_ITERATION;
row_ = Math.max(row_, editor.buildScopeTreeUpToRow(row_));
timer_.schedule(DELAY_MS);
}
};
editor_.addDocumentChangedHandler(new DocumentChangedEvent.Handler()
{
@Override
public void onDocumentChanged(DocumentChangedEvent event)
{
row_ = event.getEvent().getRange().getStart().getRow();
timer_.schedule(DELAY_MS);
}
});
}
public boolean isReady(int row)
{
return row < row_;
}
private final AceEditor editor_;
private final Timer timer_;
private int row_ = 0;
private static final int DELAY_MS = 5;
private static final int ROWS_TOKENIZED_PER_ITERATION = 200;
}
private class ScrollAnimator
implements AnimationScheduler.AnimationCallback
{
public ScrollAnimator(int targetY, int ms)
{
targetY_ = targetY;
startY_ = widget_.getEditor().getRenderer().getScrollTop();
delta_ = targetY_ - startY_;
ms_ = ms;
handle_ = AnimationScheduler.get().requestAnimationFrame(this);
}
public void complete()
{
handle_.cancel();
scrollAnimator_ = null;
}
@Override
public void execute(double timestamp)
{
if (startTime_ < 0)
startTime_ = timestamp;
double elapsed = timestamp - startTime_;
if (elapsed >= ms_)
{
widget_.getEditor().getRenderer().scrollToY(targetY_);
complete();
return;
}
// ease-out exponential
widget_.getEditor().getRenderer().scrollToY(
(int)(delta_ * (-Math.pow(2, -10 * elapsed / ms_) + 1)) + startY_);
// request next frame
handle_ = AnimationScheduler.get().requestAnimationFrame(this);
}
private final int ms_;
private final int targetY_;
private final int startY_;
private final int delta_;
private double startTime_ = -1;
private AnimationScheduler.AnimationHandle handle_;
}
private static final int DEBUG_CONTEXT_LINES = 2;
private final HandlerManager handlers_ = new HandlerManager(this);
private final AceEditorWidget widget_;
private final SnippetHelper snippets_;
private ScrollAnimator scrollAnimator_;
private CompletionManager completionManager_;
private CodeToolsServerOperations server_;
private UIPrefs uiPrefs_;
private CollabEditor collab_;
private Commands commands_;
private EventBus events_;
private TextFileType fileType_;
private boolean passwordMode_;
private boolean useEmacsKeybindings_ = false;
private boolean useVimMode_ = false;
private RnwCompletionContext rnwContext_;
private CppCompletionContext cppContext_;
private RCompletionContext rContext_ = null;
private Integer lineHighlightMarkerId_ = null;
private Integer lineDebugMarkerId_ = null;
private Integer executionLine_ = null;
private boolean valueChangeSuppressed_ = false;
private AceInfoBar infoBar_;
private boolean showChunkOutputInline_ = false;
private BackgroundTokenizer backgroundTokenizer_;
private final Vim vim_;
private final AceBackgroundHighlighter bgChunkHighlighter_;
private final AceEditorBackgroundLinkHighlighter bgLinkHighlighter_;
private int scrollTarget_ = 0;
private HandlerRegistration scrollCompleteReg_;
private final AceEditorMixins mixins_;
private static final ExternalJavaScriptLoader getLoader(StaticDataResource release)
{
return getLoader(release, null);
}
private static final ExternalJavaScriptLoader getLoader(StaticDataResource release,
StaticDataResource debug)
{
if (debug == null || !SuperDevMode.isActive())
return new ExternalJavaScriptLoader(release.getSafeUri().asString());
else
return new ExternalJavaScriptLoader(debug.getSafeUri().asString());
}
private static final ExternalJavaScriptLoader aceLoader_ =
getLoader(AceResources.INSTANCE.acejs(), AceResources.INSTANCE.acejsUncompressed());
private static final ExternalJavaScriptLoader aceSupportLoader_ =
getLoader(AceResources.INSTANCE.acesupportjs());
private static final ExternalJavaScriptLoader vimLoader_ =
getLoader(AceResources.INSTANCE.keybindingVimJs(),
AceResources.INSTANCE.keybindingVimUncompressedJs());
private static final ExternalJavaScriptLoader emacsLoader_ =
getLoader(AceResources.INSTANCE.keybindingEmacsJs(),
AceResources.INSTANCE.keybindingEmacsUncompressedJs());
private static final ExternalJavaScriptLoader extLanguageToolsLoader_ =
getLoader(AceResources.INSTANCE.extLanguageTools(),
AceResources.INSTANCE.extLanguageToolsUncompressed());
private boolean popupVisible_;
private final DiagnosticsBackgroundPopup diagnosticsBgPopup_;
private long lastCursorChangedTime_;
private long lastModifiedTime_;
private String yankedText_ = null;
private final List<HandlerRegistration> editorEventListeners_;
}