-
-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
(GH-41) Added support for auto-indentation in Cake files.
- Loading branch information
Showing
6 changed files
with
336 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Cake.VisualStudio.Editor | ||
{ | ||
/// <summary> | ||
/// An indentation result represents where the indent should be placed. It conveys this through | ||
/// a pair of values. A position in the existing document where the indent should be relative, | ||
/// and the number of columns after that the indent should be placed at. | ||
/// | ||
/// This pairing provides flexibility to the implementor to compute the indentation results in | ||
/// a variety of ways. For example, one implementation may wish to express indentation of a | ||
/// newline as being four columns past the start of the first token on a previous line. Another | ||
/// may wish to simply express the indentation as an absolute amount from the start of the | ||
/// current line. With this tuple, both forms can be expressed, and the implementor does not | ||
/// have to convert from one to the other. | ||
/// </summary> | ||
internal struct IndentationResult | ||
{ | ||
/// <summary> | ||
/// The base position in the document that the indent should be relative to. This position | ||
/// can occur on any line (including the current line, or a previous line). | ||
/// </summary> | ||
public int BasePosition { get; } | ||
|
||
/// <summary> | ||
/// The number of columns the indent should be at relative to the BasePosition's column. | ||
/// </summary> | ||
public int Offset { get; } | ||
|
||
public IndentationResult(int basePosition, int offset) : this() | ||
{ | ||
this.BasePosition = basePosition; | ||
this.Offset = offset; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.Contracts; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using System.Windows.Media.TextFormatting; | ||
using Microsoft.VisualStudio.Text; | ||
using Microsoft.VisualStudio.Text.Editor; | ||
using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; | ||
|
||
namespace Cake.VisualStudio.Editor | ||
{ | ||
internal static class ITextSnapshotLineExtensions | ||
{ | ||
/// <summary> | ||
/// Returns the first non-whitespace position on the given line, or null if | ||
/// the line is empty or contains only whitespace. | ||
/// </summary> | ||
public static int? GetFirstNonWhitespacePosition(this ITextSnapshotLine line) | ||
{ | ||
var text = line.GetText(); | ||
|
||
for (int i = 0; i < text.Length; i++) | ||
{ | ||
if (!char.IsWhiteSpace(text[i])) | ||
{ | ||
return line.Start + i; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/// <summary> | ||
/// Returns the first non-whitespace position on the given line as an offset | ||
/// from the start of the line, or null if the line is empty or contains only | ||
/// whitespace. | ||
/// </summary> | ||
public static int? GetFirstNonWhitespaceOffset(this ITextSnapshotLine line) | ||
{ | ||
var text = line.GetText(); | ||
|
||
for (int i = 0; i < text.Length; i++) | ||
{ | ||
if (!char.IsWhiteSpace(text[i])) | ||
{ | ||
return i; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/// <summary> | ||
/// Determines whether the specified line is empty or contains whitespace only. | ||
/// </summary> | ||
public static bool IsEmptyOrWhitespace(this ITextSnapshotLine line) | ||
{ | ||
var text = line.GetText(); | ||
|
||
for (int i = 0; i < text.Length; i++) | ||
{ | ||
if (!char.IsWhiteSpace(text[i])) | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
public static ITextSnapshotLine GetPreviousMatchingLine(this ITextSnapshotLine line, Func<ITextSnapshotLine, bool> predicate) | ||
{ | ||
if (line.LineNumber <= 0) | ||
{ | ||
return null; | ||
} | ||
|
||
var snapshot = line.Snapshot; | ||
for (int lineNumber = line.LineNumber - 1; lineNumber >= 0; lineNumber--) | ||
{ | ||
var currentLine = snapshot.GetLineFromLineNumber(lineNumber); | ||
if (!predicate(currentLine)) | ||
{ | ||
continue; | ||
} | ||
|
||
return currentLine; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public static int GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(this ITextSnapshotLine line, IEditorOptions editorOptions) | ||
{ | ||
return line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(editorOptions.GetTabSize()); | ||
} | ||
|
||
public static int GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(this ITextSnapshotLine line, int tabSize) | ||
{ | ||
return line.GetText().GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(tabSize); | ||
} | ||
|
||
public static int GetColumnFromLineOffset(this ITextSnapshotLine line, int lineOffset, IEditorOptions editorOptions) | ||
{ | ||
return line.GetText().GetColumnFromLineOffset(lineOffset, editorOptions.GetTabSize()); | ||
} | ||
|
||
public static int GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(this string line, int tabSize) | ||
{ | ||
var firstNonWhitespaceChar = line.GetFirstNonWhitespaceOffset(); | ||
|
||
if (firstNonWhitespaceChar.HasValue) | ||
{ | ||
return line.GetColumnFromLineOffset(firstNonWhitespaceChar.Value, tabSize); | ||
} | ||
else | ||
{ | ||
// It's all whitespace, so go to the end | ||
return line.GetColumnFromLineOffset(line.Length, tabSize); | ||
} | ||
} | ||
|
||
public static int? GetFirstNonWhitespaceOffset(this string line) | ||
{ | ||
for (int i = 0; i < line.Length; i++) | ||
{ | ||
if (!char.IsWhiteSpace(line[i])) | ||
{ | ||
return i; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public static string GetLeadingWhitespace(this string lineText) | ||
{ | ||
var firstOffset = lineText.GetFirstNonWhitespaceOffset(); | ||
|
||
return firstOffset.HasValue | ||
? lineText.Substring(0, firstOffset.Value) | ||
: lineText; | ||
} | ||
|
||
public static int GetColumnFromLineOffset(this string line, int endPosition, int tabSize) | ||
{ | ||
return ConvertTabToSpace(line, tabSize, 0, endPosition); | ||
} | ||
|
||
public static int ConvertTabToSpace(this string textSnippet, int tabSize, int initialColumn, int endPosition) | ||
{ | ||
int column = initialColumn; | ||
|
||
// now this will calculate indentation regardless of actual content on the buffer except TAB | ||
for (int i = 0; i < endPosition; i++) | ||
{ | ||
if (textSnippet[i] == '\t') | ||
{ | ||
column += tabSize - column % tabSize; | ||
} | ||
else | ||
{ | ||
column++; | ||
} | ||
} | ||
|
||
return column - initialColumn; | ||
} | ||
|
||
/// <summary> | ||
/// Checks if the given line at the given snapshot index starts with the provided value. | ||
/// </summary> | ||
public static bool StartsWith(this ITextSnapshotLine line, int index, string value, bool ignoreCase) | ||
{ | ||
var snapshot = line.Snapshot; | ||
if (index + value.Length > snapshot.Length) | ||
{ | ||
return false; | ||
} | ||
|
||
for (int i = 0; i < value.Length; i++) | ||
{ | ||
var snapshotIndex = index + i; | ||
var actualCharacter = snapshot[snapshotIndex]; | ||
var expectedCharacter = value[i]; | ||
|
||
if (ignoreCase) | ||
{ | ||
actualCharacter = char.ToLowerInvariant(actualCharacter); | ||
expectedCharacter = char.ToLowerInvariant(expectedCharacter); | ||
} | ||
|
||
if (actualCharacter != expectedCharacter) | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
using Cake.VisualStudio.Helpers; | ||
using Microsoft.VisualStudio.Text; | ||
using Microsoft.VisualStudio.Text.Editor; | ||
using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; | ||
|
||
namespace Cake.VisualStudio.Editor | ||
{ | ||
class SmartIndent : ISmartIndent | ||
{ | ||
private ITextView _textView; | ||
private readonly int _tabSize; | ||
private readonly IEditorOptions _options; | ||
|
||
public SmartIndent(ITextView textView, IEditorOptions options) : this(textView) | ||
{ | ||
_options = options; | ||
_tabSize = _options.GetTabSize(); | ||
} | ||
|
||
public SmartIndent(ITextView textView) | ||
{ | ||
_textView = textView; | ||
_tabSize = 4; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
} | ||
|
||
public int? GetDesiredIndentation(ITextSnapshotLine line) | ||
{ | ||
var offset = 0; | ||
var prevLine = line.GetPreviousMatchingLine(l => !string.IsNullOrWhiteSpace(l.GetText())); | ||
if (prevLine.RequiresOffset("{")) offset += _tabSize; | ||
if (prevLine.RequiresOffset("(")) offset += _tabSize / 2; | ||
var prevOffset = GetPreviousOffset(prevLine); | ||
return CalculateOffset(prevOffset, offset); | ||
} | ||
|
||
private int CalculateOffset(int prevOffset, int offset) | ||
{ | ||
var i = prevOffset + offset; | ||
return offset == _tabSize ? i%_tabSize == 0 ? i : i - _tabSize/2 : i; | ||
} | ||
|
||
private int GetPreviousOffset(ITextSnapshotLine prevLine) | ||
{ | ||
return _options == null ? prevLine.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(_tabSize) : | ||
prevLine.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(_options); | ||
//return isEmpty ? 0 : prevLine.Length - 1; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System; | ||
using System.ComponentModel.Composition; | ||
using Cake.VisualStudio.Helpers; | ||
using Microsoft.VisualStudio.Text.Editor; | ||
using Microsoft.VisualStudio.Utilities; | ||
|
||
namespace Cake.VisualStudio.Editor | ||
{ | ||
[Export(typeof(ISmartIndentProvider))] | ||
[ContentType(Constants.CakeContentType)] | ||
class SmartIndentProvider : ISmartIndentProvider | ||
{ | ||
[Import] private IEditorOptionsFactoryService Factory { get; set; } | ||
|
||
public ISmartIndent CreateSmartIndent(ITextView textView) | ||
{ | ||
if (textView == null) | ||
{ | ||
throw new ArgumentNullException(nameof(textView)); | ||
} | ||
|
||
return Factory == null | ||
? new SmartIndent(textView) | ||
: new SmartIndent(textView, Factory.GetOptions(textView)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters