/// <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)); }
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); }
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); }
/// <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); } }
/// <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); } }
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(); } } } }
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); }
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); }
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); }
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; } }
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(); } }
/// <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); }
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); } } } }
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(); } }
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); }
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); }
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); }
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(); }
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(); } } }
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); }
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); } }
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); }
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)); }
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); } }
private bool IsInRCode() { var caret = REditorDocument.MapCaretPositionFromView(TextView); return(caret.HasValue); }
/// <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); }