/// <summary>
        /// Called before character type is passed down to the core editor
        /// along the controll chain. Gives language-specific controller
        /// a chance to initiate different action and potentially 'eat'
        /// the character. For example, in R typing 'abc[TAB] should bring
        /// up intellisense list rather than actually insert the tab character.
        /// </summary>
        /// <returns>
        /// True if character was handled and should not be
        /// passed down to core editor or false otherwise.
        /// </returns>
        public override bool OnPreTypeChar(char typedCharacter)
        {
            // Allow tab to bring intellisense if
            //  a) REditorSettings.ShowCompletionOnTab true
            //  b) Position is at the end of a string so we bring completion for files
            //  c) There is no selection
            if (typedCharacter == '\t' && !HasActiveCompletionSession && TextView.Selection.StreamSelectionSpan.Length == 0)
            {
                // if previous character is identifier character, bring completion list
                SnapshotPoint?position = REditorDocument.MapCaretPositionFromView(TextView);
                if (position.HasValue)
                {
                    int pos = position.Value;
                    var doc = REditorDocument.FromTextBuffer(position.Value.Snapshot.TextBuffer);
                    if (!doc.IsPositionInComment(pos))
                    {
                        if (pos > 0 && pos <= position.Value.Snapshot.Length)
                        {
                            bool endOfIdentifier = RTokenizer.IsIdentifierCharacter(position.Value.Snapshot[pos - 1]);
                            bool showCompletion  = endOfIdentifier && _settings.ShowCompletionOnTab;
                            if (!showCompletion)
                            {
                                var    document = REditorDocument.FromTextBuffer(position.Value.Snapshot.TextBuffer);
                                string directory;
                                showCompletion = RCompletionEngine.CanShowFileCompletion(document.EditorTree.AstRoot, pos, out directory);
                            }
                            if (showCompletion)
                            {
                                ShowCompletion(autoShownCompletion: false);
                                return(true); // eat the character
                            }
                        }
                    }
                }
            }
            else if (typedCharacter == '#')
            {
                if (TryInsertRoxygenBlock())
                {
                    return(true);
                }
            }

            return(base.OnPreTypeChar(typedCharacter));
        }
Beispiel #2
0
        public void AugmentSignatureHelpSession(ISignatureHelpSession session, IList <ISignature> signatures)
        {
            if (!REditorSettings.SignatureHelpEnabled || session.IsDismissed)
            {
                return;
            }

            var document = REditorDocument.TryFromTextBuffer(_textBuffer);

            if (document != null && !document.EditorTree.IsReady)
            {
                document.EditorTree.InvokeWhenReady((p) => {
                    var broker = EditorShell.Current.ExportProvider.GetExportedValue <ISignatureHelpBroker>();
                    broker.TriggerSignatureHelp((ITextView)p);
                }, session.TextView, this.GetType(), processNow: true);
            }
            AugmentSignatureHelpSession(session, signatures, document.EditorTree.AstRoot, TriggerSignatureHelp);
        }
Beispiel #3
0
        public static ISuggestedActionsSource FromViewAndBuffer(ITextView textView, ITextBuffer textBuffer, ICoreShell shell)
        {
            var suggestedActionsSource = ServiceManager.GetService <RSuggestedActionSource>(textView);

            if (suggestedActionsSource == null)
            {
                // Check for detached documents in the interactive window projected buffers
                var document = REditorDocument.TryFromTextBuffer(textBuffer);
                if (document == null || document.IsClosed)
                {
                    return(null);
                }
                IEnumerable <IRSuggestedActionProvider> suggestedActionProviders = ComponentLocator <IRSuggestedActionProvider> .ImportMany(shell.CompositionService).Select(p => p.Value);

                suggestedActionsSource = new RSuggestedActionSource(textView, textBuffer, suggestedActionProviders, shell);
            }
            return(suggestedActionsSource);
        }
Beispiel #4
0
        /// <summary>
        /// Formats statement that the caret is at
        /// </summary>
        public static void FormatCurrentStatement(ITextView textView, ITextBuffer textBuffer, bool limitAtCaret = false, int caretOffset = 0)
        {
            SnapshotPoint?caretPoint = REditorDocument.MapCaretPositionFromView(textView);

            if (!caretPoint.HasValue)
            {
                return;
            }
            IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);

            if (document != null)
            {
                ITextSnapshot snapshot = textBuffer.CurrentSnapshot;
                AstRoot       ast      = document.EditorTree.AstRoot;
                IAstNode      node     = ast.GetNodeOfTypeFromPosition <IStatement>(Math.Max(0, caretPoint.Value + caretOffset)) as IAstNode;
                FormatNode(textView, textBuffer, node, limit: caretPoint.Value);
            }
        }
Beispiel #5
0
        /// <summary>
        /// Formats statement that the caret is at
        /// </summary>
        public static void FormatCurrentStatement(ITextView textView, ITextBuffer textBuffer)
        {
            SnapshotPoint?caretPoint = MapCaretToBuffer(textView, textBuffer);

            if (!caretPoint.HasValue)
            {
                return;
            }
            IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);

            if (document != null)
            {
                ITextSnapshot snapshot = textBuffer.CurrentSnapshot;
                AstRoot       ast      = document.EditorTree.AstRoot;
                IAstNode      node     = ast.GetNodeOfTypeFromPosition <IStatement>(Math.Max(0, caretPoint.Value - 1)) as IAstNode;
                FormatNode(textView, textBuffer, node);
            }
        }
Beispiel #6
0
        public static void FormatCurrentScope(ITextView textView, ITextBuffer textBuffer, bool indentCaret)
        {
            // Figure out caret position in the document text buffer
            SnapshotPoint?caretPoint = REditorDocument.MapCaretPositionFromView(textView);

            if (!caretPoint.HasValue)
            {
                return;
            }
            IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);

            if (document != null)
            {
                // Make sure AST is up to date
                document.EditorTree.EnsureTreeReady();
                var           ast      = document.EditorTree.AstRoot;
                ITextSnapshot snapshot = textBuffer.CurrentSnapshot;

                // Find scope to format
                IScope scope = ast.GetNodeOfTypeFromPosition <IScope>(caretPoint.Value);

                using (var undoAction = EditorShell.Current.CreateCompoundAction(textView, textView.TextBuffer)) {
                    undoAction.Open(Resources.AutoFormat);
                    // Now format the scope
                    bool changed = RangeFormatter.FormatRange(textView, textBuffer, scope, REditorSettings.FormatOptions);
                    if (indentCaret)
                    {
                        // Formatting may change AST and the caret position so we need to reacquire both
                        caretPoint = REditorDocument.MapCaretPositionFromView(textView);
                        if (caretPoint.HasValue)
                        {
                            document.EditorTree.EnsureTreeReady();
                            ast   = document.EditorTree.AstRoot;
                            scope = ast.GetNodeOfTypeFromPosition <IScope>(caretPoint.Value);
                            IndentCaretInNewScope(textView, scope, caretPoint.Value, REditorSettings.FormatOptions);
                        }
                    }
                    if (changed)
                    {
                        undoAction.Commit();
                    }
                }
            }
        }
Beispiel #7
0
        public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg)
        {
            if (!REditorSettings.FormatOnPaste || TextView.Selection.Mode != TextSelectionMode.Stream)
            {
                return(CommandResult.NotSupported);
            }

            string text = ClipboardDataProvider.GetData(DataFormats.UnicodeText) as string;

            if (text == null)
            {
                text = ClipboardDataProvider.GetData(DataFormats.Text) as string;
            }

            if (text != null)
            {
                var rSpans = TextView.BufferGraph.MapDownToFirstMatch(
                    TextView.Selection.StreamSelectionSpan.SnapshotSpan,
                    SpanTrackingMode.EdgeInclusive,
                    snapshot => snapshot.TextBuffer.ContentType.IsOfType(RContentTypeDefinition.ContentType)
                    );
                if (rSpans.Count > 0)
                {
                    var targetSpan = rSpans[rSpans.Count - 1];

                    IREditorDocument document = REditorDocument.TryFromTextBuffer(targetSpan.Snapshot.TextBuffer);
                    if (document != null)
                    {
                        int insertionPoint = targetSpan.Start;
                        targetSpan.Snapshot.TextBuffer.Replace(targetSpan, text);
                        document.EditorTree.EnsureTreeReady();

                        // We don't want to format inside strings
                        if (!document.EditorTree.AstRoot.IsPositionInsideString(insertionPoint))
                        {
                            RangeFormatter.FormatRange(TextView, targetSpan.Snapshot.TextBuffer,
                                                       new TextRange(insertionPoint, text.Length), document.EditorTree.AstRoot,
                                                       REditorSettings.FormatOptions);
                        }
                    }
                }
            }
            return(CommandResult.Executed);
        }
        protected override void OnTextBufferDisposing(ITextBuffer textBuffer)
        {
            IEditorInstance editorInstance = ServiceManager.GetService <IEditorInstance>(textBuffer);

            if (editorInstance != null)
            {
                editorInstance.Dispose();
            }
            else
            {
                IREditorDocument doc = REditorDocument.TryFromTextBuffer(textBuffer);
                if (doc != null)
                {
                    doc.Dispose();
                }
            }

            base.OnTextBufferDisposing(textBuffer);
        }
Beispiel #9
0
        private static SnapshotPoint?GetCaretPointInBuffer(ITextView textView, out IEditorTree tree)
        {
            tree = null;
            IREditorDocument document = REditorDocument.TryFromTextBuffer(textView.TextBuffer);

            if (document != null)
            {
                tree = document.EditorTree;
                tree.EnsureTreeReady();
                return(textView.BufferGraph.MapDownToFirstMatch(
                           textView.Caret.Position.BufferPosition,
                           PointTrackingMode.Positive,
                           snapshot => snapshot.TextBuffer.ContentType.IsOfType(RContentTypeDefinition.ContentType),
                           PositionAffinity.Successor
                           ));
            }

            return(null);
        }
Beispiel #10
0
        private static IKeywordScopeStatement GetFormatScope(ITextView textView, ITextBuffer textBuffer, AstRoot ast)
        {
            SnapshotPoint?caret = REditorDocument.MapCaretPositionFromView(textView);

            if (caret.HasValue)
            {
                try {
                    int lineNumber             = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(caret.Value.Position);
                    ITextSnapshotLine line     = textBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber);
                    string            lineText = line.GetText();
                    if (lineText.TrimEnd().EndsWith("}", StringComparison.Ordinal))
                    {
                        IKeywordScopeStatement scopeStatement = ast.GetNodeOfTypeFromPosition <IKeywordScopeStatement>(caret.Value);
                        return(scopeStatement);
                    }
                } catch (Exception) { }
            }
            return(null);
        }
        private bool TryInsertRoxygenBlock()
        {
            SnapshotPoint?point = REditorDocument.MapCaretPositionFromView(TextView);

            if (point.HasValue)
            {
                var snapshot = _textBuffer.CurrentSnapshot;
                var line     = snapshot.GetLineFromPosition(point.Value);

                if (line.LineNumber < snapshot.LineCount - 1 && point.Value == line.End && line.GetText().EqualsOrdinal("##"))
                {
                    var nextLine = snapshot.GetLineFromLineNumber(line.LineNumber + 1);
                    var document = REditorDocument.FromTextBuffer(_textBuffer);
                    document.EditorTree.EnsureTreeReady();
                    return(RoxygenBlock.TryInsertBlock(_textBuffer, document.EditorTree.AstRoot, nextLine.Start));
                }
            }
            return(false);
        }
Beispiel #12
0
        public EditorErrorTag(IEditorTree editorTree, IValidationError error)
            : base(GetErrorType(error), error.Message)
        {
            _textBuffer = editorTree.TextBuffer;

            var document = REditorDocument.FromTextBuffer(editorTree.TextBuffer);

            FileName = document?.FilePath;

            Description = error.Message;
            TaskType    = GetTaskType(error);

            _range = error;

            if (_range == null || _range.Start < 0)
            {
                _range = TextRange.EmptyRange;
            }
        }
Beispiel #13
0
        private void HandleDrop(DragDropInfo dragDropInfo, string userFolder) {
            var dataObject = dragDropInfo.Data;

            var document = REditorDocument.FindInProjectedBuffers(_wpfTextView.TextBuffer);
            Debug.Assert(document != null, "Text view does not have associated R document.");

            var text = dataObject.GetPlainText(userFolder, dragDropInfo.KeyStates);
            var line = _wpfTextView.TextViewLines.GetTextViewLineContainingYCoordinate(dragDropInfo.Location.Y);
            line = line ?? _wpfTextView.TextViewLines.LastVisibleLine;
            if (line == null) {
                return;
            }

            var bufferPosition = line.GetBufferPositionFromXCoordinate(dragDropInfo.Location.X);
            bufferPosition = bufferPosition ?? line.End;

            var textBuffer = _wpfTextView.TextBuffer;
            var dropPosition = bufferPosition.Value;

            if (_settings.FormatOnPaste) {
                _wpfTextView.Caret.MoveTo(dropPosition);
            }

            if (text.StartsWithOrdinal(Environment.NewLine) && Whitespace.IsNewLineBeforePosition(new TextProvider(textBuffer.CurrentSnapshot), dropPosition)) {
                text = text.TrimStart();
            }

            var es = _shell.GetService<IApplicationEditorSupport>();
            using (var undoAction = es.CreateCompoundAction(_wpfTextView, textBuffer)) {
                undoAction.Open(Resources.DragDropOperation);
                textBuffer.Replace(new Span(dropPosition, 0), text);

                if (_settings.FormatOnPaste) {
                    RangeFormatter.FormatRange(_wpfTextView, document.TextBuffer, new TextRange(dropPosition, text.Length), _shell.GetService<IREditorSettings>().FormatOptions, _shell);
                }

                if (_wpfTextView.Selection != null) {
                    _wpfTextView.Caret.MoveTo(_wpfTextView.Selection.End);
                }
                undoAction.Commit();
            }
        }
Beispiel #14
0
        /// <summary>
        /// Inserts a snippet based on a shortcut string.
        /// </summary>
        public int StartSnippetInsertion(out bool snippetInserted)
        {
            int hr = VSConstants.E_FAIL;

            snippetInserted = false;

            // Get the text at the current caret position and
            // determine if it is a snippet shortcut.
            if (!TextView.Caret.InVirtualSpace)
            {
                SnapshotPoint caretPoint = TextView.Caret.Position.BufferPosition;

                var document = REditorDocument.FindInProjectedBuffers(TextView.TextBuffer);
                // Document may be null in tests
                var textBuffer = document != null ? document.TextBuffer : TextView.TextBuffer;
                var expansion  = textBuffer.GetBufferAdapter <IVsExpansion>();
                _earlyEndExpansionHappened = false;

                Span        span;
                var         shortcut = TextView.GetItemBeforeCaret(out span, x => true);
                VsExpansion?exp      = _cache.GetExpansion(shortcut);

                var ts = TextSpanFromViewSpan(span);
                if (exp.HasValue && ts.HasValue)
                {
                    // Insert into R buffer
                    hr = expansion.InsertNamedExpansion(exp.Value.title, exp.Value.path, ts.Value, this, RGuidList.RLanguageServiceGuid, 0, out _expansionSession);
                    if (_earlyEndExpansionHappened)
                    {
                        // EndExpansion was called before InsertExpansion returned, so set _expansionSession
                        // to null to indicate that there is no active expansion session. This can occur when
                        // the snippet inserted doesn't have any expansion fields.
                        _expansionSession          = null;
                        _earlyEndExpansionHappened = false;
                    }
                    ErrorHandler.ThrowOnFailure(hr);
                    snippetInserted = true;
                    return(hr);
                }
            }
            return(hr);
        }
Beispiel #15
0
 private void UpdateCurrentParameter()
 {
     if (SubjectBuffer != null && TextView != null)
     {
         IREditorDocument document = REditorDocument.TryFromTextBuffer(SubjectBuffer);
         if (document != null)
         {
             SnapshotPoint?p = REditorDocument.MapCaretPositionFromView(TextView);
             if (p.HasValue)
             {
                 document.EditorTree.EnsureTreeReady();
                 ComputeCurrentParameter(document.EditorTree.AstRoot, p.Value.Position);
             }
             else
             {
                 SignatureHelp.DismissSession(TextView);
             }
         }
     }
 }
Beispiel #16
0
        public MassiveChange(ITextView textView, ITextBuffer textBuffer, string description)
        {
            _textBuffer = textBuffer;

            var undoManagerProvider = EditorShell.Current.ExportProvider.GetExport <ITextBufferUndoManagerProvider>().Value;
            var undoManager         = undoManagerProvider.GetTextBufferUndoManager(textView.TextBuffer);

            ITextUndoTransaction innerTransaction = undoManager.TextBufferUndoHistory.CreateTransaction(description);

            _transaction = new TextUndoTransactionThatRollsBackProperly(innerTransaction);

            _transaction.AddUndo(new StartMassiveChangeUndoUnit(_textBuffer));

            IREditorDocument document = REditorDocument.TryFromTextBuffer(_textBuffer);

            if (document != null)
            {
                document.BeginMassiveChange();
            }
        }
Beispiel #17
0
        public static SnapshotPoint?FindCurrentItemDefinition(ITextView textView, ITextBuffer textBuffer)
        {
            Span   span;
            string itemName = textView.GetIdentifierUnderCaret(out span);

            if (!string.IsNullOrEmpty(itemName))
            {
                var position = REditorDocument.MapCaretPositionFromView(textView);
                if (position.HasValue)
                {
                    var document       = REditorDocument.FromTextBuffer(textBuffer);
                    var itemDefinition = document.EditorTree.AstRoot.FindItemDefinition(position.Value, itemName);
                    if (itemDefinition != null)
                    {
                        return(textView.MapUpToBuffer(itemDefinition.Start, textView.TextBuffer));
                    }
                }
            }
            return(null);
        }
Beispiel #18
0
        private static bool CanFormatLine(ITextView textView, ITextBuffer textBuffer, int lineOffset)
        {
            // Do not format inside strings. At this point AST may be empty due to the nature
            // of [destructive] changes made to the document. We have to resort to tokenizer.
            // In order to keep performance good during typing we'll use token stream from the classifier.
            SnapshotPoint?caretPoint = REditorDocument.MapCaretPositionFromView(textView);

            if (caretPoint.HasValue)
            {
                var snapshot   = textBuffer.CurrentSnapshot;
                int lineNumber = snapshot.GetLineNumberFromPosition(caretPoint.Value.Position);
                var line       = snapshot.GetLineFromLineNumber(lineNumber + lineOffset);

                var classifier = ServiceManager.GetService <RClassifier>(textBuffer);
                var tokenIndex = classifier.Tokens.GetItemContaining(line.Start);

                return(tokenIndex < 0 || classifier.Tokens[tokenIndex].TokenType != RTokenType.String);
            }
            return(false);
        }
Beispiel #19
0
        public static string GetVariableName(ITextView textView, ITextSnapshot snapshot)
        {
            SnapshotPoint?pt = REditorDocument.MapCaretPositionFromView(textView);

            if (pt.HasValue && pt.Value > 0)
            {
                int i = pt.Value - 1;
                for (; i >= 0; i--)
                {
                    char ch = snapshot[i];
                    if (!RTokenizer.IsIdentifierCharacter(ch) && ch != '$' && ch != '@')
                    {
                        break;
                    }
                }

                return(snapshot.GetText(Span.FromBounds(i + 1, pt.Value)));
            }

            return(string.Empty);
        }
Beispiel #20
0
        public void Dispose()
        {
            IREditorDocument document = REditorDocument.TryFromTextBuffer(_textBuffer);
            bool             changed  = true;

            if (document != null)
            {
                changed = document.EndMassiveChange();
            }

            if (!changed)
            {
                _transaction.Cancel();
            }
            else
            {
                _transaction.AddUndo(new EndMassiveChangeUndoUnit(_textBuffer));
                _transaction.Complete();
            }

            _transaction.Dispose();
        }
Beispiel #21
0
        private void HandleAddRemoveBuffers(ReadOnlyCollection <ITextBuffer> addedBuffers, ReadOnlyCollection <ITextBuffer> removedBuffers)
        {
            foreach (var tb in addedBuffers)
            {
                if (tb.ContentType.IsOfType(RContentTypeDefinition.ContentType))
                {
                    var doc = REditorDocument.TryFromTextBuffer(tb);
                    if (doc == null)
                    {
                        var editorDocument = new REditorDocument(tb, _shell);
                    }
                }
            }

            foreach (var tb in removedBuffers)
            {
                if (tb.ContentType.IsOfType(RContentTypeDefinition.ContentType))
                {
                    var doc = REditorDocument.TryFromTextBuffer(tb);
                    doc?.Close();
                }
            }
        }
Beispiel #22
0
        public static void FormatScope(ITextView textView, ITextBuffer textBuffer, int position, bool indentCaret)
        {
            IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);

            if (document != null)
            {
                document.EditorTree.EnsureTreeReady();

                int           baseIndentPosition = -1;
                ITextSnapshot snapshot           = textBuffer.CurrentSnapshot;
                AstRoot       ast   = document.EditorTree.AstRoot;
                IScope        scope = ast.GetNodeOfTypeFromPosition <IScope>(position);

                // Scope indentation is defined by its parent statement.
                IAstNodeWithScope parentStatement = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(position);
                if (parentStatement != null && parentStatement.Scope == scope)
                {
                    ITextSnapshotLine baseLine = snapshot.GetLineFromPosition(parentStatement.Start);
                    baseIndentPosition = baseLine.Start;
                }
                FormatScope(textView, textBuffer, ast, scope, baseIndentPosition, indentCaret);
            }
        }
        /// <summary>
        /// Should this key press trigger a completion session?
        /// </summary>
        public override bool IsTriggerChar(char typedCharacter)
        {
            if (!HasActiveCompletionSession)
            {
                switch (typedCharacter)
                {
                case '$':
                case '@':
                    return(true);

                case ':':
                    return(RCompletionContext.IsCaretInNamespace(TextView));

                case '(':
                    return(RCompletionContext.IsCaretInLibraryStatement(TextView));

                default:
                    if (_settings.ShowCompletionOnFirstChar)
                    {
                        SnapshotPoint?position = REditorDocument.MapCaretPositionFromView(TextView);
                        if (position.HasValue)
                        {
                            int pos      = position.Value;
                            var snapshot = position.Value.Snapshot;
                            // Trigger on first character
                            if (RTokenizer.IsIdentifierCharacter(typedCharacter) && !char.IsDigit(typedCharacter))
                            {
                                // Ignore if this is not the first character
                                return(pos <= 1 || (pos > 1 && !RTokenizer.IsIdentifierCharacter(snapshot[pos - 2])));
                            }
                        }
                    }
                    break;
                }
            }
            return(false);
        }
Beispiel #24
0
        public static void HandleAutoformat(ITextView textView, char typedChar)
        {
            if (!REditorSettings.AutoFormat || IgnoreOnce)
            {
                IgnoreOnce = false;
                return;
            }

            SnapshotPoint?rPoint = GetCaretPointInBuffer(textView);

            if (!rPoint.HasValue)
            {
                return;
            }

            var document = REditorDocument.FromTextBuffer(textView.TextBuffer);
            var et       = document.EditorTree;
            var ast      = et.GetCurrentRootOrPreviousIfNotReady();

            // We don't want to auto-format inside strings
            if (ast.IsPositionInsideString(rPoint.Value.Position))
            {
                return;
            }

            ITextBuffer subjectBuffer = rPoint.Value.Snapshot.TextBuffer;

            if (typedChar.IsLineBreak())
            {
                // Special case for hitting caret after } and before 'else'. We do want to format
                // the construct as '} else {' but if user types Enter after } and we auto-format
                // it will look as if the editor just eats the Enter. Instead, we will not be
                // autoformatting in this specific case. User can always format either the document
                // or select the block and reformat it.
                if (!IsBetweenCurlyAndElse(subjectBuffer, rPoint.Value.Position))
                {
                    var scopeStatement = GetFormatScope(textView, subjectBuffer, ast, -1);
                    // Do not format large scope blocks for performance reasons
                    if (scopeStatement != null && scopeStatement.Length < 200)
                    {
                        FormatOperations.FormatNode(textView, subjectBuffer, scopeStatement);
                    }
                    else
                    {
                        FormatOperations.FormatLine(textView, subjectBuffer, -1);
                    }
                }
            }
            else if (typedChar == ';')
            {
                // Verify we are at the end of the string and not in a middle
                // of another string or inside a statement.
                ITextSnapshotLine line = subjectBuffer.CurrentSnapshot.GetLineFromPosition(rPoint.Value.Position);
                int    positionInLine  = rPoint.Value.Position - line.Start;
                string lineText        = line.GetText();
                if (positionInLine >= lineText.TrimEnd().Length)
                {
                    FormatOperations.FormatLine(textView, subjectBuffer, 0);
                }
            }
            else if (typedChar == '}')
            {
                FormatOperations.FormatCurrentStatement(textView, subjectBuffer);
            }
        }
Beispiel #25
0
        public static bool IsCaretInLibraryStatement(ITextView textView)
        {
            try {
                SnapshotPoint?bufferPosition = REditorDocument.MapCaretPositionFromView(textView);
                if (bufferPosition.HasValue)
                {
                    ITextSnapshot     snapshot      = bufferPosition.Value.Snapshot;
                    int               caretPosition = bufferPosition.Value.Position;
                    ITextSnapshotLine line          = snapshot.GetLineFromPosition(caretPosition);

                    if (line.Length < 8 || caretPosition < line.Start + 8 || snapshot[caretPosition - 1] != '(')
                    {
                        return(false);
                    }

                    int start = -1;
                    int end   = -1;

                    for (int i = caretPosition - 2; i >= 0; i--)
                    {
                        if (!char.IsWhiteSpace(snapshot[i]))
                        {
                            end = i + 1;
                            break;
                        }
                    }

                    if (end <= 0)
                    {
                        return(false);
                    }

                    for (int i = end - 1; i >= 0; i--)
                    {
                        if (char.IsWhiteSpace(snapshot[i]))
                        {
                            start = i + 1;
                            break;
                        }
                        else if (i == 0)
                        {
                            start = 0;
                            break;
                        }
                    }

                    if (start < 0 || end <= start)
                    {
                        return(false);
                    }

                    start -= line.Start;
                    end   -= line.Start;

                    string s = line.GetText().Substring(start, end - start);
                    if (s == "library" || s == "require")
                    {
                        return(true);
                    }
                }
            } catch (ArgumentException) { }

            return(false);
        }
Beispiel #26
0
        public static int GetSmartIndent(ITextSnapshotLine line, AstRoot ast = null)
        {
            ITextBuffer textBuffer = line.Snapshot.TextBuffer;

            if (ast == null)
            {
                IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);
                if (document == null)
                {
                    return(0);
                }
                ast = document.EditorTree.AstRoot;
            }

            if (line.LineNumber > 0)
            {
                ITextSnapshotLine prevLine = line.Snapshot.GetLineFromLineNumber(line.LineNumber - 1);

                string prevLineText  = prevLine.GetText();
                int    nonWsPosition = prevLine.Start + (prevLineText.Length - prevLineText.TrimStart().Length) + 1;

                IAstNodeWithScope scopeStatement = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(nonWsPosition);
                if (scopeStatement != null)
                {
                    if (scopeStatement.Scope == null)
                    {
                        // No scope of any kind, use block indent
                        return(GetBlockIndent(line) + REditorSettings.IndentSize);
                    }

                    if (scopeStatement.Scope is SimpleScope)
                    {
                        // There is statement with a simple scope above. We need to check
                        // if the line that is being formatted is actually part of this scope.
                        if (line.Start < scopeStatement.Scope.End)
                        {
                            // Indent line one level deeper that the statement
                            return(GetBlockIndent(line) + REditorSettings.IndentSize);
                        }

                        // Line is not part of the scope, hence regular indent
                        return(OuterIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions));
                    }

                    // Check if line is the last line in scope and if so,
                    // it should be indented at the outer indent
                    if (scopeStatement.Scope.CloseCurlyBrace != null)
                    {
                        int endOfScopeLine = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(scopeStatement.Scope.CloseCurlyBrace.Start);
                        if (endOfScopeLine == line.LineNumber)
                        {
                            return(OuterIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions));
                        }
                    }

                    return(InnerIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions));
                }
            }

            IAstNodeWithScope node = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(line.Start);

            if (node != null && node.Scope != null && node.Scope.OpenCurlyBrace != null)
            {
                return(InnerIndentSizeFromNode(textBuffer, node, REditorSettings.FormatOptions));
            }

            // See if we are in function arguments and indent at the function level
            var fc = ast.GetNodeOfTypeFromPosition <FunctionCall>(line.Start);

            if (fc != null && fc.Arguments != null && fc.OpenBrace != null && line.Start >= fc.OpenBrace.End)
            {
                return(InnerIndentSizeFromNode(textBuffer, fc, REditorSettings.FormatOptions));
            }

            // If nothing is found, default to block indent
            return(GetBlockIndent(line));
        }
Beispiel #27
0
        public static void HandleAutoformat(ITextView textView, IEditorShell editorShell, char typedChar)
        {
            if (!REditorSettings.AutoFormat)
            {
                return;
            }

            if (!REditorSettings.FormatScope && typedChar == '}')
            {
                return;
            }

            SnapshotPoint?rPoint = GetCaretPointInBuffer(textView);

            if (!rPoint.HasValue)
            {
                return;
            }

            var document = REditorDocument.FromTextBuffer(textView.TextBuffer);
            var ast      = document.EditorTree.AstRoot;

            // Make sure we are not formatting damaging the projected range in R Markdown
            // which looks like ```{r. 'r' should not separate from {.
            var host = ContainedLanguageHost.GetHost(textView, document.TextBuffer, editorShell);

            if (host != null && !host.CanFormatLine(textView, document.TextBuffer, document.TextBuffer.CurrentSnapshot.GetLineNumberFromPosition(rPoint.Value)))
            {
                return;
            }

            // We don't want to auto-format inside strings
            if (ast.IsPositionInsideString(rPoint.Value.Position))
            {
                return;
            }

            ITextBuffer subjectBuffer = rPoint.Value.Snapshot.TextBuffer;

            if (typedChar.IsLineBreak())
            {
                // Special case for hitting caret after } and before 'else'. We do want to format
                // the construct as '} else {' but if user types Enter after } and we auto-format
                // it will look as if the editor just eats the Enter. Instead, we will not be
                // autoformatting in this specific case. User can always format either the document
                // or select the block and reformat it.
                if (!IsBetweenCurlyAndElse(subjectBuffer, rPoint.Value.Position))
                {
                    var scopeStatement = GetFormatScope(textView, subjectBuffer, ast);
                    // Do not format large scope blocks for performance reasons
                    if (scopeStatement != null && scopeStatement.Length < 200)
                    {
                        FormatOperations.FormatNode(textView, subjectBuffer, editorShell, scopeStatement);
                    }
                    else if (CanFormatLine(textView, subjectBuffer, -1))
                    {
                        FormatOperations.FormatViewLine(textView, subjectBuffer, -1, editorShell);
                    }
                }
            }
            else if (typedChar == ';')
            {
                // Verify we are at the end of the string and not in a middle
                // of another string or inside a statement.
                ITextSnapshotLine line = subjectBuffer.CurrentSnapshot.GetLineFromPosition(rPoint.Value.Position);
                int    positionInLine  = rPoint.Value.Position - line.Start;
                string lineText        = line.GetText();
                if (positionInLine >= lineText.TrimEnd().Length)
                {
                    FormatOperations.FormatViewLine(textView, subjectBuffer, 0, editorShell);
                }
            }
            else if (typedChar == '}')
            {
                FormatOperations.FormatCurrentStatement(textView, subjectBuffer, editorShell, limitAtCaret: true, caretOffset: -1);
            }
        }
Beispiel #28
0
        private bool IsInRCode()
        {
            var caret = REditorDocument.MapCaretPositionFromView(TextView);

            return(caret.HasValue);
        }
Beispiel #29
0
        /// <summary>
        /// Determines level of indentation in the line from AST and surrounding context.
        /// Called when user hits ENTER and editor needs to know level of indentation in
        /// the new line as well as when code is being auto-formatted and range formatter
        /// needs to know how to indent freshly formatted lines of code.
        /// </summary>
        /// <param name="line">Line to find the indent for</param>
        /// <param name="ast">Optional AST</param>
        /// <param name="formatting">
        /// Indicates if current call is from formatter or
        /// from the core editor for indentation when user typed Enter.
        /// </param>
        /// <returns>Level of indent in spaces</returns>
        public static int GetSmartIndent(ITextSnapshotLine line, AstRoot ast = null,
                                         int originalIndentSizeInSpaces      = -1, bool formatting = false)
        {
            ITextBuffer       textBuffer = line.Snapshot.TextBuffer;
            ITextSnapshotLine prevLine   = null;

            if (line.LineNumber == 0)
            {
                // Nothing to indent at the first line
                return(0);
            }

            if (ast == null)
            {
                IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);
                if (document == null)
                {
                    return(0);
                }
                ast = document.EditorTree.AstRoot;
            }

            // The challenge here is to find scope to base the indent on.
            // The scope may or may not have braces and may or may not be closed.
            // Current line is normally empty so we use previous line data assuming
            // it is not empty. If previous line is empty, we do not look up
            // to the nearest non-empty. This is the same as C# behavior.
            // So we need to locate nearest node that implements IAstNodeWithScope
            // or the scope (implemeting IScope) itself is scope is just '{ }'.

            // First try based on the previous line. We will try start of the line
            // like in 'if(...)' { in order to locate 'if' and then, if nothing is found,
            // try end of the line as in 'x <- function(...) {'
            prevLine = line.Snapshot.GetLineFromLineNumber(line.LineNumber - 1);
            string prevLineText = prevLine.GetText();

            if (prevLineText.Trim().Equals("else", StringComparison.Ordinal))
            {
                // Quick short circuit for new 'else' since it is not in the ASt yet.
                return(GetBlockIndent(line) + REditorSettings.IndentSize);
            }

            // First, let's see if we are in a function argument list and then indent based on
            // the opening brace position. This needs to be done before looking for scopes
            // since function definition is a scope-defining statement.
            // Examples: 'call(a,\n<Enter>' or 'x <- function(a,<Enter>'
            if (prevLine.Length > 0)
            {
                var fc1 = ast.GetNodeOfTypeFromPosition <IFunction>(prevLine.End - 1);
                var fc2 = ast.GetNodeOfTypeFromPosition <IFunction>(line.Start);
                // Pick narrowest function. This happens when function definition appears
                // inside the argument list such as list(a = function(...)).
                var fc = fc2 ?? fc1;
                if (fc != null && fc.Arguments != null && fc.OpenBrace != null)
                {
                    if (fc.CloseBrace == null || fc.CloseBrace.End > prevLine.End)
                    {
                        // We only want to indent here if position is in arguments and not in the function scope.
                        if (line.Start >= fc.OpenBrace.End && !(fc.CloseBrace != null && line.Start >= fc.CloseBrace.End))
                        {
                            if (originalIndentSizeInSpaces < 0)
                            {
                                // Indent one level deeper from the function definition line.
                                var fcLine = line.Snapshot.GetLineFromPosition(fc.Start);
                                if (fcLine.LineNumber == prevLine.LineNumber)
                                {
                                    int fcIndentSize = IndentBuilder.TextIndentInSpaces(fcLine.GetText(), REditorSettings.TabSize);
                                    if (fc.CloseBrace == null || fc.CloseBrace.End >= (formatting ? line.Start : line.End))
                                    {
                                        fcIndentSize += REditorSettings.IndentSize;
                                    }
                                    return(fcIndentSize);
                                }
                                else
                                {
                                    return(GetBlockIndent(line));
                                }
                            }
                            else
                            {
                                return(originalIndentSizeInSpaces);
                            }
                        }
                    }
                }
            }

            // Candidate position #1 is first non-whitespace character
            // in the the previous line
            int startOfNoWsOnPreviousLine = prevLine.Start + (prevLineText.Length - prevLineText.TrimStart().Length) + 1;

            // Try current new line so in case of 'if () { } else { | }' we find
            // the 'else' which defines the scope and not the parent 'if'.
            var scopeStatement1 = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(line.Start);

            if (scopeStatement1 == null)
            {
                // If not found, try previous line that may define the indent
                scopeStatement1 = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(startOfNoWsOnPreviousLine);
                if (scopeStatement1 == null)
                {
                    // Line start position works for typical scope-defining statements like if() or while()
                    // but it won't find function definition in 'x <- function(a) {'
                    // Try end of the line instead
                    var lastNonWsOnPreviousLine = Math.Max(0, prevLineText.TrimEnd().Length - 1);
                    scopeStatement1 = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(lastNonWsOnPreviousLine);
                    // Verify that line we are asked to provide the smart indent for is actually inside
                    // this scope since we could technically find end of x <- function(a) {}
                    // when we went up one line.
                    if (scopeStatement1?.Scope?.CloseCurlyBrace != null && !scopeStatement1.Contains(line.Start))
                    {
                        scopeStatement1 = null; // line is outside of this scope.
                    }
                }
            }

            IAstNodeWithScope scopeStatement;
            var scopeStatement2 = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(startOfNoWsOnPreviousLine);

            // Locate standalone scope which is not a statement, if any
            var scope = ast.GetNodeOfTypeFromPosition <IScope>(prevLine.End);

            // Pick the narrowest item
            // so in case of
            //  x <- function() {
            //      if(...)<Enter>
            //  }
            // we will use the 'if' and not the function definition
            var scopeCandidates = new List <IAstNode>()
            {
                scopeStatement1, scopeStatement2, scope
            };
            var smallestScope = scopeCandidates.OrderBy(x => x != null ? x.Length : Int32.MaxValue).FirstOrDefault();

            scopeStatement = smallestScope as IAstNodeWithScope;
            scope          = smallestScope as IScope;

            // If IScope is a scope defined by the parent statement, use
            // the parent statement so in
            // x <- function(...) {
            //      |
            // }
            // the indent in scope is defined by the function and not by the opening {
            if (scope != null)
            {
                var parentStarement = scope.Parent as IAstNodeWithScope;
                if (parentStarement != null && parentStarement.Scope == scope)
                {
                    scopeStatement = parentStarement;
                    scope          = null;
                }
            }

            if (scopeStatement != null)
            {
                if (scopeStatement.Scope == null)
                {
                    // There is nothing after statement that allows simple scope
                    // such as in 'if(...)EOF'
                    return(GetBlockIndent(line) + REditorSettings.IndentSize);
                }

                if (scopeStatement.Scope is SimpleScope)
                {
                    // There is statement with a simple scope above such as 'if' without { }.
                    // We need to check if the line that is being formatted is part of this scope.
                    if (line.Start < scopeStatement.Scope.End)
                    {
                        // Indent line one level deeper that the statement
                        return(GetBlockIndent(line) + REditorSettings.IndentSize);
                    }
                    // Line is not part of the scope, provide regular indent
                    return(OuterIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions));
                }

                // Check if line is the last line in a real scope (i.e. scope with { }) and only consists
                // of the closing }, it should be indented at the outer indent so closing scope aligns with
                // the beginning of the statement.
                if (scopeStatement.Scope.CloseCurlyBrace != null)
                {
                    int endOfScopeLine = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(scopeStatement.Scope.CloseCurlyBrace.Start);
                    if (endOfScopeLine <= line.LineNumber)
                    {
                        return(OuterIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions));
                    }
                }

                if (scopeStatement.Scope.OpenCurlyBrace != null && REditorSettings.FormatOptions.BracesOnNewLine)
                {
                    int startOfScopeLine = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(scopeStatement.Scope.OpenCurlyBrace.Start);
                    if (startOfScopeLine == line.LineNumber)
                    {
                        return(OuterIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions));
                    }
                }

                // We are inside a scope so provide inner indent
                return(InnerIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions));
            }

            // Try locate the scope itself, if any
            if (scope != null && scope.OpenCurlyBrace != null)
            {
                if (scope.CloseCurlyBrace != null)
                {
                    int endOfScopeLine = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(scope.CloseCurlyBrace.Start);
                    if (endOfScopeLine == line.LineNumber)
                    {
                        return(OuterIndentSizeFromNode(textBuffer, scope, REditorSettings.FormatOptions));
                    }
                }
                return(InnerIndentSizeFromNode(textBuffer, scope, REditorSettings.FormatOptions));
            }

            return(0);
        }