/// <summary> /// Appends indentation to each line so formatted text appears properly /// indented inside the host document (script block in HTML page). /// </summary> private void IndentLines(IEditorBuffer textBuffer, ITextRange range, AstRoot ast, int originalIndentSizeInSpaces) { var snapshot = textBuffer.CurrentSnapshot; var firstLine = snapshot.GetLineFromPosition(range.Start); var lastLine = snapshot.GetLineFromPosition(range.End); var document = textBuffer.GetEditorDocument <IREditorDocument>(); for (var i = firstLine.LineNumber; i <= lastLine.LineNumber; i++) { // Snapshot is updated after each insertion so do not cache var line = textBuffer.CurrentSnapshot.GetLineFromLineNumber(i); var indent = SmartIndenter.GetSmartIndent(line, _settings, ast, originalIndentSizeInSpaces, formatting: true); if (indent > 0 && line.Length > 0 && line.Start >= range.Start) { // Check current indentation and correct for the difference int currentIndentSize = IndentBuilder.TextIndentInSpaces(line.GetText(), _settings.TabSize); indent = Math.Max(0, indent - currentIndentSize); if (indent > 0) { var indentString = IndentBuilder.GetIndentString(indent, _settings.IndentType, _settings.TabSize); textBuffer.Insert(line.Start, indentString); if (document == null) { // Typically this is a test scenario only. In the real editor // instance document is available and automatically updates AST // when whitespace inserted, not no manual update is necessary. ast.ReflectTextChange(line.Start, 0, indentString.Length, textBuffer.CurrentSnapshot); } } } } }
/// <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); }
/// <summary> /// Incrementally applies whitespace change to the buffer /// having old and new tokens produced from the 'before formatting' /// and 'after formatting' versions of the same text. /// </summary> /// <param name="editorBuffer">Text buffer to apply changes to</param> /// <param name="oldTextProvider">Text provider of the text fragment before formatting</param> /// <param name="newTextProvider">Text provider of the formatted text</param> /// <param name="oldTokens">Tokens from the 'before' text fragment</param> /// <param name="newTokens">Tokens from the 'after' text fragment</param> /// <param name="formatRange">Range that is being formatted in the text buffer</param> /// <param name="transactionName">Not used in VS Code</param> /// <param name="selectionTracker">Not used in VS Code</param> /// <param name="additionalAction"> /// Action to perform after changes are applies by undo unit is not yet closed. /// </param> public void ApplyChange( IEditorBuffer editorBuffer, ITextProvider oldTextProvider, ITextProvider newTextProvider, IReadOnlyList <ITextRange> oldTokens, IReadOnlyList <ITextRange> newTokens, ITextRange formatRange, string transactionName, ISelectionTracker selectionTracker, Action additionalAction = null) { Debug.Assert(oldTokens.Count == newTokens.Count); if (oldTokens.Count != newTokens.Count) { return; } var edits = CalculateChanges(oldTextProvider, newTextProvider, oldTokens, newTokens, formatRange); foreach (var e in edits) { if (string.IsNullOrEmpty(e.NewText)) { editorBuffer.Delete(e.Range); } else if (e.Range.Length > 0) { editorBuffer.Replace(e.Range, e.NewText); } else { editorBuffer.Insert(e.Range.Start, e.NewText); } } additionalAction?.Invoke(); }
/// <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 linePosition = DeterminePosition(editorBuffer, position); if (linePosition < 0) { return(false); } if (!DetermineFunction(ast, linePosition, out IFunctionDefinition fd, out IVariable v, out FunctionCall fc)) { return(false); } int definitionStart; if (fd != null && v != null) { definitionStart = Math.Min(v.Start, fd.Start); } else if (fc != null) { definitionStart = fc.Start; } else { return(false); } var insertionSpan = GetRoxygenBlockPosition(editorBuffer.CurrentSnapshot, definitionStart); if (insertionSpan == null) { return(false); } var lineBreak = editorBuffer.CurrentSnapshot.GetLineFromPosition(position).LineBreak; if (string.IsNullOrEmpty(lineBreak)) { lineBreak = "\n"; } var block = fd != null ? GenerateRoxygenBlock(v.Name, fd, lineBreak) : GenerateRoxygenBlock(fc, lineBreak); if (block.Length == 0) { return(false); } if (insertionSpan.Length == 0) { editorBuffer.Insert(insertionSpan.Start, block + lineBreak); } else { editorBuffer.Replace(insertionSpan, block); } return(true); }