private static bool GetFunction(AstRoot astRoot, ref int position, out FunctionCall functionCall, out Variable functionVariable) { functionVariable = null; functionCall = astRoot.GetNodeOfTypeFromPosition<FunctionCall>(position); if (functionCall == null && position > 0) { // Retry in case caret is at the very end of function signature // that does not have final close brace yet. functionCall = astRoot.GetNodeOfTypeFromPosition<FunctionCall>(position - 1, includeEnd: true); if(functionCall != null) { // But if signature does have closing brace and caret // is beyond it, we are really otuside of the signature. if(functionCall.CloseBrace != null && position >= functionCall.CloseBrace.End) { return false; } if (position > functionCall.SignatureEnd) { position = functionCall.SignatureEnd; } } } if (functionCall != null && functionCall.Children.Count > 0) { functionVariable = functionCall.Children[0] as Variable; return functionVariable != null; } return false; }
/// <summary> /// Locates function or variable definition given the item name and /// the position of the name in the text buffer. /// </summary> /// <returns>AST node that defines the specified item</returns> public static IAstNode FindItemDefinition(this AstRoot ast, int position, string itemName) { var scope = ast.GetNodeOfTypeFromPosition <IScope>(position); var func = scope.FindFunctionDefinitionByName(itemName, position); return(func ?? scope.FindVariableDefinitionByName(itemName, position)); }
/// <summary> /// Enumerates function definitions applicable to the current scope. /// Returns definitions that appear in the file up to the specified /// position. /// </summary> public static IEnumerable <RFunction> GetFunctionsFromPosition(this AstRoot ast, int position) { var scope = ast.GetNodeOfTypeFromPosition <IScope>(position); var variables = scope.GetApplicableVariables(position); return(variables.Where(x => x.Value is RFunction).Select(x => x.Value as RFunction)); }
public static string IsInLibraryStatement(this AstRoot ast, int position) { var fc = ast.GetNodeOfTypeFromPosition <FunctionCall>(position); if (fc?.RightOperand != null) { var funcName = ast.TextProvider.GetText(fc.RightOperand); if (funcName == "library" && fc.Arguments.Count == 1) { return(ast.TextProvider.GetText(fc.Arguments[0])); } } return(string.Empty); }
public static string IsInLibraryStatement(this AstRoot ast, int position) { var fc = ast.GetNodeOfTypeFromPosition <FunctionCall>(position); if (fc != null && fc.RightOperand != null) { string funcName = ast.TextProvider.GetText(fc.RightOperand); if (funcName.Equals("library", StringComparison.Ordinal)) { if (fc.Arguments.Count == 1) { return(ast.TextProvider.GetText(fc.Arguments[0])); } } } return(string.Empty); }
/// <summary> /// Locates function or variable definition given the item name and /// the position of the name in the text buffer. /// </summary> /// <returns>AST node that defines the specified item</returns> public static IAstNode FindItemDefinition(this AstRoot ast, int position, string itemName) { var scope = ast.GetNodeOfTypeFromPosition <IScope>(position); var func = scope.FindFunctionDefinitionByName(itemName, position); if (func != null) { return(func); } else { var v = scope.FindVariableDefinitionByName(itemName, position); if (v != null) { return(v); } } return(null); }
public static bool FormatRangeExact(ITextView textView, ITextBuffer textBuffer, ITextRange formatRange, AstRoot ast, RFormatOptions options, int scopeStatementPosition, bool respectUserIndent = true) { ITextSnapshot snapshot = textBuffer.CurrentSnapshot; Span spanToFormat = new Span(formatRange.Start, formatRange.Length); string spanText = snapshot.GetText(spanToFormat.Start, spanToFormat.Length); string trimmedSpanText = spanText.Trim(); if (trimmedSpanText == "}") { // Locate opening { and its statement var scopeNode = ast.GetNodeOfTypeFromPosition<IAstNodeWithScope>(spanToFormat.Start); if (scopeNode != null) { scopeStatementPosition = scopeNode.Start; } } RFormatter formatter = new RFormatter(options); string formattedText = formatter.Format(trimmedSpanText); formattedText = formattedText.Trim(); // there may be inserted line breaks after { formattedText = IndentLines(textBuffer, spanToFormat.Start, ast, formattedText, options, scopeStatementPosition, respectUserIndent); if (!spanText.Equals(formattedText, StringComparison.Ordinal)) { var selectionTracker = new RSelectionTracker(textView, textBuffer); RTokenizer tokenizer = new RTokenizer(); IReadOnlyTextRangeCollection<RToken> oldTokens = tokenizer.Tokenize(spanText); IReadOnlyTextRangeCollection<RToken> newTokens = tokenizer.Tokenize(formattedText); IncrementalTextChangeApplication.ApplyChangeByTokens( textBuffer, new TextStream(spanText), new TextStream(formattedText), oldTokens, newTokens, formatRange, Resources.AutoFormat, selectionTracker); return true; } return false; }
public static IFunctionDefinition FindFunctionDefinition(ITextBuffer textBuffer, AstRoot ast, int position, out Variable v) { v = null; var exp = ast.GetNodeOfTypeFromPosition<IExpressionStatement>(position); return exp?.GetVariableOrFunctionDefinition(out v); }
public static IAstNode GetIndentDefiningNode(AstRoot ast, int position) { IScope scope = ast.GetNodeOfTypeFromPosition<IScope>(position); // Scope indentation is defined by its parent statement. IAstNodeWithScope parentStatement = scope.Parent as IAstNodeWithScope; if (parentStatement != null && parentStatement.Scope == scope) { return parentStatement; } return scope; }
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; }
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); }
/// <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; }
/// <summary> /// Determines if a given position is in area where user /// specified indentation must be respected. For example, /// in multi-line list of function arguments, /// multi-line expressions and so on. /// </summary> private static bool RespectUserIndent(ITextBuffer textBuffer, AstRoot ast, int position) { // Look up nearest expression IAstNode node = ast.GetNodeOfTypeFromPosition<Expression>(position); if (IsMultilineNode(textBuffer, node)) { return true; } node = ast.GetNodeOfTypeFromPosition<FunctionDefinition>(position); if (IsMultilineNode(textBuffer, node)) { return true; } node = ast.GetNodeOfTypeFromPosition<FunctionCall>(position); if (IsMultilineNode(textBuffer, node)) { return true; } return false; }