1
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
2
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
4
using ICSharpCode.Core;
6
using System.Collections.Generic;
7
using System.Diagnostics;
9
using System.Threading;
10
using System.Windows.Threading;
11
using ICSharpCode.AvalonEdit.Document;
12
using ICSharpCode.AvalonEdit.Rendering;
13
using ICSharpCode.AvalonEdit.Xml;
14
using ICSharpCode.SharpDevelop;
15
using ICSharpCode.SharpDevelop.Dom;
16
using ICSharpCode.SharpDevelop.Editor;
17
using ICSharpCode.SharpDevelop.Gui;
18
using ICSharpCode.XmlEditor;
20
namespace ICSharpCode.XamlBinding
22
using Task = System.Threading.Tasks.Task;
23
using Tasks = System.Threading.Tasks;
25
public class XamlColorizer : DocumentColorizingTransformer, ILineTracker, IDisposable
27
public struct Highlight {
28
public IMember Member { get; set; }
29
public HighlightingInfo Info { get; set; }
32
public sealed class HighlightTask {
34
readonly string fileName;
35
readonly string lineText;
41
public HighlightTask(ITextEditor editor, DocumentLine currentLine, TextView textView)
43
this.fileName = editor.FileName;
44
this.textView = textView;
45
this.snapshot = editor.Document.CreateSnapshot();
46
this.lineText = textView.Document.GetText(currentLine);
47
this.offset = currentLine.Offset;
48
this.task = new Task(Process);
51
public HighlightTask(ITextEditor editor, DocumentLine currentLine, TextView textView, IList<Highlight> oldHighlightData)
52
: this(editor, currentLine, textView)
54
this.results = oldHighlightData;
57
IList<Highlight> results;
60
public Highlight[] GetResults()
65
return results.ToArray();
76
public bool CompletedSuccessfully {
78
return task.IsCompleted && task.Status == Tasks.TaskStatus.RanToCompletion;
82
public bool Invalid { get; set; }
87
List<Highlight> results = new List<Highlight>();
89
foreach (HighlightingInfo info in GetInfo()) {
90
IMember member = null;
92
// Commented out because task doesn't come with cancellation support in .NET 4.0 Beta 2
93
// (use CancellationToken instead)
94
// I didn't have to remove any call to task.Cancel(), so apparently this was dead code.
95
//if (task.IsCancellationRequested) {
96
// task.AcknowledgeCancellation();
99
// TODO: implement cancellation support
101
if (!info.Token.StartsWith("xmlns", StringComparison.OrdinalIgnoreCase)) {
102
MemberResolveResult rr = XamlResolver.Resolve(info.Token, info.Context) as MemberResolveResult;
103
member = (rr != null) ? rr.ResolvedMember : null;
106
results.Add(new Highlight() { Member = member, Info = info });
110
this.results = results;
112
WorkbenchSingleton.SafeThreadAsyncCall(InvokeRedraw);
113
} catch (Exception e) {
114
WorkbenchSingleton.SafeThreadAsyncCall(() => MessageService.ShowException(e));
121
textView.Redraw(this.offset, this.lineText.Length, DispatcherPriority.Background);
124
IEnumerable<HighlightingInfo> GetInfo()
127
XamlContext context = null;
130
if (index + 1 >= lineText.Length)
133
index = lineText.IndexOfAny(index + 1, '=', '.');
135
context = CompletionDataHelper.ResolveContext(snapshot, fileName, offset + index);
137
if (context.ActiveElement == null || context.InAttributeValueOrMarkupExtension || context.InCommentOrCData)
144
switch (context.Description) {
145
case XamlContextDescription.AtTag:
146
token = context.ActiveElement.Name;
147
int propertyNameIndex = token.IndexOf('.');
149
if (propertyNameIndex == -1)
152
propertyName = token.Substring(propertyNameIndex + 1);
153
startIndex = lineText.IndexOf(propertyName, index, StringComparison.Ordinal);
155
case XamlContextDescription.InTag:
156
if (lineText[index] == '.' || context.Attribute == null)
159
token = propertyName = context.Attribute.Name;
160
startIndex = lineText.LastIndexOf(propertyName, index, StringComparison.Ordinal);
166
if (startIndex > -1) {
167
yield return new HighlightingInfo(token, startIndex, startIndex + propertyName.Length, offset, context);
170
} while (index > -1);
174
Dictionary<DocumentLine, HighlightTask> highlightCache = new Dictionary<DocumentLine, HighlightTask>();
176
public ITextEditor Editor { get; set; }
178
public AvalonEdit.Rendering.TextView TextView { get; set; }
180
public XamlColorizer(ITextEditor editor, TextView textView)
182
this.Editor = editor;
183
this.TextView = textView;
185
this.weakLineTracker = WeakLineTracker.Register(this.Editor.Document.GetService(typeof(TextDocument)) as TextDocument, this);
187
ParserService.LoadSolutionProjectsThreadEnded += ParserServiceLoadSolutionProjectsThreadEnded;
189
colorizers.Add(this);
192
static IList<XamlColorizer> colorizers = new List<XamlColorizer>();
194
public static void RefreshAll()
196
foreach (XamlColorizer colorizer in colorizers) {
197
colorizer.RebuildDocument();
198
colorizer.TextView.Redraw();
202
void ParserServiceLoadSolutionProjectsThreadEnded(object sender, EventArgs e)
204
WorkbenchSingleton.SafeThreadAsyncCall(
206
highlightCache.Clear();
212
WeakLineTracker weakLineTracker;
215
public void Dispose()
218
ParserService.LoadSolutionProjectsThreadEnded -= ParserServiceLoadSolutionProjectsThreadEnded;
219
weakLineTracker.Deregister();
220
colorizers.Remove(this);
225
protected override void ColorizeLine(DocumentLine line)
227
if (!highlightCache.ContainsKey(line)) {
228
HighlightTask task = new HighlightTask(this.Editor, line, this.TextView);
230
highlightCache.Add(line, task);
232
HighlightTask task = highlightCache[line];
233
var results = task.GetResults();
234
if (results != null) {
235
foreach (var result in results) {
236
ColorizeMember(result.Info, line, result.Member);
240
ColorizeInvalidated();
243
void ColorizeMember(HighlightingInfo info, DocumentLine line, IMember member)
246
Action<VisualLineElement> handler = null;
248
if (info.Token.StartsWith(info.Context.XamlNamespacePrefix + ":", StringComparison.Ordinal))
249
handler = HighlightProperty;
250
else if (info.Context.IgnoredXmlns.Any(item => info.Token.StartsWith(item + ":", StringComparison.Ordinal)))
251
handler = HighlightIgnored;
252
else if (member != null) {
253
if (member is IEvent)
254
handler = HighlightEvent;
256
handler = HighlightProperty;
258
if (info.Token.StartsWith("xmlns", StringComparison.OrdinalIgnoreCase) || info.Token.StartsWith(Utils.GetNamespacePrefix(CompletionDataHelper.MarkupCompatibilityNamespace, info.Context) + ":", StringComparison.OrdinalIgnoreCase))
259
handler = HighlightNamespaceDeclaration;
260
else if (info.Token.StartsWith("xml:", StringComparison.OrdinalIgnoreCase))
261
handler = HighlightProperty;
263
Core.LoggingService.Debug(info.Token + " not highlighted; line " + line.LineNumber);
266
ChangeLinePart(line.Offset + info.StartOffset, line.Offset + info.EndOffset, handler);
267
} catch (ArgumentOutOfRangeException) {}
270
void ColorizeInvalidated()
272
foreach (var item in highlightCache.ToArray()) {
273
if (item.Key.IsDeleted) {
274
highlightCache.Remove(item.Key);
277
if (item.Value.Invalid) {
278
var newTask = new HighlightTask(this.Editor, item.Key, this.TextView, item.Value.GetResults());
280
highlightCache[item.Key] = newTask;
285
void InvalidateLines(DocumentLine line)
287
DocumentLine current = line;
288
while (current != null) {
290
if (highlightCache.TryGetValue(current, out task))
293
current = current.NextLine;
297
#region highlight helpers
298
void HighlightProperty(VisualLineElement element)
300
element.TextRunProperties.SetForegroundBrush(XamlBindingOptions.PropertyForegroundColor.ToBrush());
301
element.TextRunProperties.SetBackgroundBrush(XamlBindingOptions.PropertyBackgroundColor.ToBrush());
304
void HighlightEvent(VisualLineElement element)
306
element.TextRunProperties.SetForegroundBrush(XamlBindingOptions.EventForegroundColor.ToBrush());
307
element.TextRunProperties.SetBackgroundBrush(XamlBindingOptions.EventBackgroundColor.ToBrush());
310
void HighlightNamespaceDeclaration(VisualLineElement element)
312
element.TextRunProperties.SetForegroundBrush(XamlBindingOptions.NamespaceDeclarationForegroundColor.ToBrush());
313
element.TextRunProperties.SetBackgroundBrush(XamlBindingOptions.NamespaceDeclarationBackgroundColor.ToBrush());
316
void HighlightIgnored(VisualLineElement element)
318
element.TextRunProperties.SetForegroundBrush(XamlBindingOptions.IgnoredForegroundColor.ToBrush());
319
element.TextRunProperties.SetBackgroundBrush(XamlBindingOptions.IgnoredBackgroundColor.ToBrush());
323
#region ILineTracker implementation
324
public void BeforeRemoveLine(DocumentLine line)
326
InvalidateLines(line.NextLine);
329
public void SetLineLength(DocumentLine line, int newTotalLength)
331
InvalidateLines(line);
334
public void LineInserted(DocumentLine insertionPos, DocumentLine newLine)
336
InvalidateLines(newLine);
339
public void RebuildDocument()
341
highlightCache.Clear();
345
public struct HighlightingInfo
353
public HighlightingInfo(string token, int startOffset, int endOffset, int lineOffset, XamlContext context)
356
throw new ArgumentNullException("token");
358
throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be greater 0");
360
throw new ArgumentOutOfRangeException("endOffset", endOffset, "Value must be greater 0");
362
throw new ArgumentOutOfRangeException("lineOffset", lineOffset, "Value must be greater 0");
364
throw new ArgumentNullException("context");
367
this.startOffset = startOffset;
368
this.endOffset = endOffset;
369
this.lineOffset = lineOffset;
370
this.context = context;
373
public string Token {
374
get { return token; }
377
public int StartOffset {
378
get { return startOffset; }
381
public int EndOffset {
382
get { return endOffset; }
385
public int LineOffset {
386
get { return lineOffset; }
389
public XamlContext Context {
390
get { return context; }