private static int DeterminePosition(IEditorBuffer editorBuffer, int caretPosition) { var snapshot = editorBuffer.CurrentSnapshot; IEditorLine line = null; var lineNumber = snapshot.GetLineNumberFromPosition(caretPosition); for (var i = lineNumber; i < snapshot.LineCount; i++) { line = snapshot.GetLineFromLineNumber(i); if (line.Length > 0) { break; } } if (line == null || line.Length == 0) { return(-1); } var offset = line.Length - line.GetText().TrimStart().Length + 1; var linePosition = line.Start + offset; return(linePosition < snapshot.Length ? linePosition : -1); }
public static int OuterIndentSizeFromLine(IEditorLine line, RFormatOptions options) { var lineText = line.GetText(); var leadingWhitespace = lineText.Substring(0, lineText.Length - lineText.TrimStart().Length); return(IndentBuilder.TextIndentInSpaces(leadingWhitespace, options.TabSize)); }
/// <summary> /// Attempts to insert Roxygen documentation block based /// on the user function signature. /// </summary> public static bool TryInsertBlock(IEditorBuffer editorBuffer, AstRoot ast, int position) { // First determine if position is right before the function declaration var snapshot = editorBuffer.CurrentSnapshot; IEditorLine line = null; var lineNumber = snapshot.GetLineNumberFromPosition(position); for (int i = lineNumber; i < snapshot.LineCount; i++) { line = snapshot.GetLineFromLineNumber(i); if (line.Length > 0) { break; } } if (line == null || line.Length == 0) { return(false); } Variable v; int offset = line.Length - line.GetText().TrimStart().Length + 1; if (line.Start + offset >= snapshot.Length) { return(false); } var fd = ast.FindFunctionDefinition(line.Start + offset, out v); if (fd != null && v != null && !string.IsNullOrEmpty(v.Name)) { int definitionStart = Math.Min(v.Start, fd.Start); var insertionSpan = GetRoxygenBlockPosition(snapshot, definitionStart); if (insertionSpan != null) { string lineBreak = snapshot.GetLineFromPosition(position).LineBreak; if (string.IsNullOrEmpty(lineBreak)) { lineBreak = "\n"; } string block = GenerateRoxygenBlock(v.Name, fd, lineBreak); if (block.Length > 0) { if (insertionSpan.Length == 0) { editorBuffer.Insert(insertionSpan.Start, block + lineBreak); } else { editorBuffer.Replace(insertionSpan, block); } return(true); } } } return(false); }
public static int InnerIndentSizeFromLine(IEditorLine line, RFormatOptions options) { var lineText = line.GetText(); var leadingWhitespace = lineText.Substring(0, lineText.Length - lineText.TrimStart().Length); var indentbuilder = new IndentBuilder(options.IndentType, options.IndentSize, options.TabSize); return(IndentBuilder.TextIndentInSpaces(leadingWhitespace + indentbuilder.SingleIndentString, options.TabSize)); }
private static bool LineHasContinuation(IEditorLine line) { var tokenizer = new RTokenizer(); var tokens = tokenizer.Tokenize(line.GetText()); var lastTokenType = tokens.Count > 0 ? tokens[tokens.Count - 1].TokenType : RTokenType.Unknown; return(lastTokenType == RTokenType.Operator || lastTokenType == RTokenType.Comma); }
internal static bool CommentLine(IEditorLine line) { string lineText = line.GetText(); if (!string.IsNullOrWhiteSpace(lineText)) { int leadingWsLength = lineText.Length - lineText.TrimStart().Length; line.Snapshot.EditorBuffer.Insert(line.Start + leadingWsLength, "#"); return(true); } return(false); }
internal static bool UncommentLine(IEditorLine line) { string lineText = line.GetText(); if (!string.IsNullOrWhiteSpace(lineText)) { int leadingWsLength = lineText.Length - lineText.TrimStart().Length; if (leadingWsLength < lineText.Length) { if (lineText[leadingWsLength] == '#') { line.Snapshot.EditorBuffer.Delete(new TextRange(line.Start + leadingWsLength, 1)); return(true); } } } return(false); }
public static string GetItemAtPosition(IEditorLine line, int position, Func <RTokenType, bool> tokenTypeCheck, out ITextRange span) { string lineText = line.GetText(); var offset = 0; var positionInTokens = position - line.Start; var tokenizer = new RTokenizer(); var tokens = tokenizer.Tokenize(lineText); var tokenIndex = tokens.GetItemContaining(positionInTokens); if (tokenIndex >= 0) { var token = tokens[tokenIndex]; if (token.TokenType == RTokenType.Comment) { // Tokenize inside comment since we do want F1 to work inside // commented out code, code samples or Roxygen blocks. positionInTokens -= token.Start; var positionAfterHash = token.Start + 1; tokens = tokenizer.Tokenize(lineText.Substring(positionAfterHash, token.Length - 1)); tokenIndex = tokens.GetItemContaining(positionInTokens); if (tokenIndex >= 0) { token = tokens[tokenIndex]; offset = positionAfterHash; } } if (tokenTypeCheck(token.TokenType)) { var start = token.Start + offset; var end = Math.Min(start + token.Length, line.End); span = TextRange.FromBounds(line.Start + start, line.Start + end); // return view span return(lineText.Substring(start, end - start)); } } span = TextRange.EmptyRange; return(string.Empty); }
/// <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="settings">Editor settings</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(IEditorLine line, IREditorSettings settings, AstRoot ast = null, int originalIndentSizeInSpaces = -1, bool formatting = false) { var editorBuffer = line.Snapshot.EditorBuffer; IEditorLine prevLine = null; if (line.LineNumber == 0) { // Nothing to indent at the first line return(0); } ast = ast ?? editorBuffer.GetEditorDocument <IREditorDocument>()?.EditorTree?.AstRoot; if (ast == null) { return(0); } // 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); var 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, settings) + settings.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(), settings.TabSize); if (fc.CloseBrace == null || fc.CloseBrace.End >= (formatting ? line.Start : line.End)) { fcIndentSize += settings.IndentSize; } return(fcIndentSize); } return(GetBlockIndent(line, settings)); } return(originalIndentSizeInSpaces); } } } } // Candidate position #1 is first non-whitespace character // in the the previous line var 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. } } } 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?.Length ?? int.MaxValue).FirstOrDefault(); var 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) { if (scope.Parent is IAstNodeWithScope parentStarement && 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, settings) + settings.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, settings) + settings.IndentSize); } // Line is not part of the scope, provide regular indent return(OuterIndentSizeFromNode(editorBuffer, scopeStatement, settings.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) { var endOfScopeLine = editorBuffer.CurrentSnapshot.GetLineNumberFromPosition(scopeStatement.Scope.CloseCurlyBrace.Start); if (endOfScopeLine <= line.LineNumber) { return(OuterIndentSizeFromNode(editorBuffer, scopeStatement, settings.FormatOptions)); } } if (scopeStatement.Scope.OpenCurlyBrace != null && settings.FormatOptions.BracesOnNewLine) { var startOfScopeLine = editorBuffer.CurrentSnapshot.GetLineNumberFromPosition(scopeStatement.Scope.OpenCurlyBrace.Start); if (startOfScopeLine == line.LineNumber) { return(OuterIndentSizeFromNode(editorBuffer, scopeStatement, settings.FormatOptions)); } } // We are inside a scope so provide inner indent return(InnerIndentSizeFromNode(editorBuffer, scopeStatement, settings.FormatOptions)); } // Try locate the scope itself, if any if (scope?.OpenCurlyBrace != null) { if (scope.CloseCurlyBrace != null) { var endOfScopeLine = editorBuffer.CurrentSnapshot.GetLineNumberFromPosition(scope.CloseCurlyBrace.Start); if (endOfScopeLine == line.LineNumber) { return(OuterIndentSizeFromNode(editorBuffer, scope, settings.FormatOptions)); } } return(InnerIndentSizeFromNode(editorBuffer, scope, settings.FormatOptions)); } return(0); }