This repository has been archived by the owner on Jan 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 169
/
Copy pathQsSmartIndent.cs
151 lines (134 loc) · 6.05 KB
/
QsSmartIndent.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
using Microsoft.VisualStudio.Utilities;
using System;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
namespace Microsoft.Quantum.QsLanguageExtensionVS
{
[ContentType("Q#")]
[Export(typeof(ISmartIndentProvider))]
internal class QsSmartIndentProvider : ISmartIndentProvider
{
public ISmartIndent CreateSmartIndent(ITextView textView) => new QsSmartIndent(textView);
}
internal class QsSmartIndent : ISmartIndent
{
/// <summary>
/// A list of all opening and closing bracket pairs that affect indentation.
/// </summary>
private static readonly IImmutableList<(string open, string close)> brackets = ImmutableList.Create(new[]
{
("[", "]"),
("(", ")"),
("{", "}")
});
/// <summary>
/// The text view that this smart indent is handling indentation for.
/// </summary>
private readonly ITextView textView;
/// <summary>
/// Creates a new smart indent.
/// </summary>
/// <param name="textView">The text view that this smart indent is handling indentation for.</param>
public QsSmartIndent(ITextView textView)
{
this.textView = textView;
// The ISmartIndent interface is only for indenting blank lines or indenting after pressing enter. To
// decrease the indent after typing a closing bracket, we have to watch for changes manually.
textView.TextBuffer.ChangedHighPriority += TextBuffer_ChangedHighPriority;
}
public void Dispose()
{
}
/// <summary>
/// Returns the number of spaces to place at the start of the line, or null if there is no desired indentation.
/// </summary>
public int? GetDesiredIndentation(ITextSnapshotLine line)
{
// Note: The ISmartIndent interface requires that the return type is nullable, but we always return a
// value.
if (line.LineNumber == 0)
return 0;
ITextSnapshotLine lastNonEmptyLine = GetLastNonEmptyLine(line);
int desiredIndent = GetIndentation(lastNonEmptyLine.GetText());
int indentSize = textView.Options.GetIndentSize();
if (StartsBlock(lastNonEmptyLine.GetText()))
desiredIndent += indentSize;
if (EndsBlock(line.GetText()))
desiredIndent -= indentSize;
return Math.Max(0, desiredIndent);
}
private void TextBuffer_ChangedHighPriority(object sender, TextContentChangedEventArgs e)
{
foreach (ITextChange change in e.Changes)
{
var line = e.After.GetLineFromPosition(change.NewPosition);
var column = change.NewPosition - line.Start.Position;
if (EndsBlock(change.NewText) && IsInIndentation(line, column))
{
int indent = GetIndentation(line.GetText());
int desiredIndent = GetDesiredIndentation(line) ?? 0;
if (indent != desiredIndent)
e.After.TextBuffer.Replace(
new Span(line.Start.Position, line.GetText().TakeWhile(char.IsWhiteSpace).Count()),
CreateIndentation(desiredIndent));
}
}
}
/// <summary>
/// Returns the current indentation of the line in number of spaces.
/// </summary>
private int GetIndentation(string line) =>
line
.TakeWhile(char.IsWhiteSpace)
.Aggregate(0, (indent, c) => indent + (c == '\t' ? textView.Options.GetTabSize() : 1));
/// <summary>
/// Returns a string containing spaces or tabs (depending on the text view options) to match the given
/// indentation.
/// </summary>
private string CreateIndentation(int indent)
{
if (textView.Options.IsConvertTabsToSpacesEnabled())
return new string(' ', indent);
else
return
new string('\t', indent / textView.Options.GetTabSize()) +
new string(' ', indent % textView.Options.GetTabSize());
}
/// <summary>
/// Returns true if the end of the line starts a block.
/// </summary>
private static bool StartsBlock(string line) =>
brackets.Any(bracket => line.TrimEnd().EndsWith(bracket.open));
/// <summary>
/// Returns true if the beginning of the line ends a block.
/// </summary>
private static bool EndsBlock(string line) =>
brackets.Any(bracket => line.TrimStart().StartsWith(bracket.close));
/// <summary>
/// Returns true if the column occurs within the indentation region at the beginning of the line.
/// </summary>
private static bool IsInIndentation(ITextSnapshotLine line, int column) =>
line.GetText().Substring(0, column).All(char.IsWhiteSpace);
/// <summary>
/// Returns the last non-empty line before the given line. A non-empty line is any line that contains at least
/// one non-whitespace character. If all of the lines before the given line are empty, returns the first line of
/// the snapshot instead.
/// <para/>
/// Returns null if the given line is the first line in the snapshot.
/// </summary>
private static ITextSnapshotLine GetLastNonEmptyLine(ITextSnapshotLine line)
{
int lineNumber = line.LineNumber - 1;
if (lineNumber < 0)
return null;
ITextSnapshot snapshot = line.Snapshot;
while (lineNumber > 0 && string.IsNullOrWhiteSpace(snapshot.GetLineFromLineNumber(lineNumber).GetText()))
lineNumber--;
return snapshot.GetLineFromLineNumber(lineNumber);
}
}
}