* Makefile.am:
[monodevelop.git] / main / src / addins / Mono.Texteditor / Mono.TextEditor.Highlighting / SyntaxModeService.cs
blobbc6bb167dfeddb3174454394ed3893be368a0e88
1 // SyntaxModeService.cs
2 //
3 // Author:
4 // Mike Krüger <mkrueger@novell.com>
5 //
6 // Copyright (c) 2007 Novell, Inc (http://www.novell.com)
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 // THE SOFTWARE.
28 using System;
29 using System.Collections.Generic;
30 using System.Collections.ObjectModel;
31 using System.IO;
32 using System.Reflection;
33 using System.Threading;
34 using System.Xml;
36 namespace Mono.TextEditor.Highlighting
38 public static class SyntaxModeService
40 static Dictionary<string, SyntaxMode> syntaxModes = new Dictionary<string, SyntaxMode> ();
41 static Dictionary<string, Style> styles = new Dictionary<string, Style> ();
42 static Dictionary<string, IXmlProvider> syntaxModeLookup = new Dictionary<string, IXmlProvider> ();
43 static Dictionary<string, IXmlProvider> styleLookup = new Dictionary<string, IXmlProvider> ();
45 public static string[] Styles {
46 get {
47 List<string> result = new List<string> ();
48 foreach (string style in styles.Keys) {
49 if (!result.Contains (style))
50 result.Add (style);
52 foreach (string style in styleLookup.Keys) {
53 if (!result.Contains (style))
54 result.Add (style);
56 return result.ToArray ();
60 public static Style GetColorStyle (Gtk.Widget widget, string name)
62 if (styles.ContainsKey (name))
63 return styles [name];
64 if (styleLookup.ContainsKey (name)) {
65 LoadStyle (name);
66 return GetColorStyle (widget, name);
68 return new DefaultStyle (widget);
71 public static IXmlProvider GetProvider (SyntaxMode mode)
73 foreach (string mimeType in mode.MimeType.Split (';')) {
74 if (syntaxModeLookup.ContainsKey (mimeType))
75 return syntaxModeLookup[mimeType];
77 return null;
80 public static IXmlProvider GetProvider (Style style)
82 if (styleLookup.ContainsKey (style.Name))
83 return styleLookup[style.Name];
84 return null;
87 static void LoadStyle (string name)
89 if (!styleLookup.ContainsKey (name))
90 throw new System.ArgumentException ("Style " + name + " not found", "name");
91 XmlTextReader reader = styleLookup [name].Open ();
92 try {
93 styles [name] = Style.LoadFrom (reader);
94 } finally {
95 reader.Close ();
99 static void LoadSyntaxMode (string mimeType)
101 if (!syntaxModeLookup.ContainsKey (mimeType))
102 throw new System.ArgumentException ("Syntax mode for mime:" + mimeType + " not found", "mimeType");
103 XmlTextReader reader = syntaxModeLookup [mimeType].Open ();
104 try {
105 SyntaxMode mode = SyntaxMode.Read (reader);
106 foreach (string mime in mode.MimeType.Split (';')) {
107 syntaxModes [mime] = mode;
109 } finally {
110 reader.Close ();
114 public static SyntaxMode GetSyntaxMode (string mimeType)
116 if (syntaxModes.ContainsKey (mimeType))
117 return syntaxModes [mimeType];
118 if (syntaxModeLookup.ContainsKey (mimeType)) {
119 LoadSyntaxMode (mimeType);
120 syntaxModeLookup.Remove (mimeType);
121 return GetSyntaxMode (mimeType);
123 return null;
126 public static bool ValidateAllSyntaxModes ()
128 foreach (string mime in new List<string> (syntaxModeLookup.Keys)) {
129 GetSyntaxMode (mime);
131 syntaxModeLookup.Clear ();
132 foreach (string style in new List<string> (styleLookup.Keys)) {
133 GetColorStyle (null, style);
135 styleLookup.Clear ();
136 bool result = true;
137 foreach (KeyValuePair<string, Style> style in styles) {
138 HashSet<SyntaxMode> checkedModes = new HashSet<SyntaxMode> ();
139 foreach (KeyValuePair<string, SyntaxMode> mode in syntaxModes) {
140 if (checkedModes.Contains (mode.Value))
141 continue;
142 if (!mode.Value.Validate (style.Value)) {
143 System.Console.WriteLine(mode.Key + " failed to validate against:" + style.Key);
144 result = false;
146 checkedModes.Add (mode.Value);
149 return result;
152 public static void Remove (Style style)
154 if (styles.ContainsKey (style.Name))
155 styles.Remove (style.Name);
156 if (styleLookup.ContainsKey (style.Name))
157 styleLookup.Remove (style.Name);
159 public static void Remove (SyntaxMode mode)
161 foreach (string mimeType in mode.MimeType.Split (';')) {
162 if (syntaxModes.ContainsKey (mimeType))
163 syntaxModes.Remove (mimeType);
164 if (syntaxModeLookup.ContainsKey (mimeType))
165 syntaxModeLookup.Remove (mimeType);
169 public static void ScanSpans (Document doc, Rule rule, Stack<Span> spanStack, int start, int end)
171 Dictionary<char, Span[]> spanTree = rule != null ? rule.spanStarts : null;
172 int endOffset = 0;
173 end = System.Math.Min (end, doc.Length);
174 Span curSpan = spanStack.Count > 0 ? spanStack.Peek () : null;
175 Rule curRule = rule;
176 for (int offset = start; offset < end; offset++) {
177 char ch = doc.GetCharAt (offset);
178 if (curSpan != null && !String.IsNullOrEmpty (curSpan.End)) {
179 if (!String.IsNullOrEmpty (curSpan.Escape) && offset + 1 < end && endOffset == 0 && doc.GetCharAt (offset + 1) == curSpan.End[0]) {
180 bool match = true;
181 for (int j = 0; j < curSpan.Escape.Length && offset + j < doc.Length; j++) {
182 if (doc.GetCharAt (offset + j) != curSpan.Escape[j]) {
183 match = false;
184 break;
187 if (match) {
188 offset += curSpan.Escape.Length - 1;
189 continue;
194 if (curSpan.End[endOffset] == ch) {
195 endOffset++;
196 if (endOffset >= curSpan.End.Length) {
197 spanStack.Pop ();
198 curSpan = spanStack.Count > 0 ? spanStack.Peek () : null;
199 curRule = curSpan != null ? doc.SyntaxMode.GetRule (curSpan.Rule) : rule;
200 spanTree = curRule != null ? curRule.spanStarts : null;
201 endOffset = 0;
202 continue;
204 } else if (endOffset != 0) {
205 endOffset = 0;
206 if (curSpan.End[endOffset] == ch) {
207 offset--;
208 continue;
211 if (String.IsNullOrEmpty (curSpan.Rule))
212 continue;
215 if (spanTree != null && spanTree.ContainsKey (ch)) {
216 bool found = false;
217 foreach (Span span in spanTree[ch]) {
218 bool mismatch = false;
219 for (int j = 1; j < span.Begin.Length; j++) {
220 if (offset + j >= doc.Length || span.Begin [j] != doc.GetCharAt (offset + j)) {
221 mismatch = true;
222 break;
225 if (!mismatch && span.BeginFlags.Contains ("firstNonWs")) {
226 LineSegment line = doc.GetLineByOffset (offset);
227 for (int k = line.Offset; k < offset; k++) {
228 if (!Char.IsWhiteSpace (doc.GetCharAt (k))) {
229 mismatch = true;
230 break;
235 if (!mismatch) {
236 spanStack.Push (span);
237 curSpan = span;
238 curRule = doc.SyntaxMode.GetRule (curSpan.Rule);
239 spanTree = curRule != null ? curRule.spanStarts : null;
240 found = true;
241 offset += span.Begin.Length - 1;
242 endOffset = 0;
243 break;
246 if (found)
247 continue;
248 } else {
249 spanTree = curRule != null ? curRule.spanStarts : null;
251 // skip:
252 // ;
255 static bool IsEqual (Span[] spans1, Span[] spans2)
257 if (spans1 == null || spans1.Length == 0)
258 return spans2 == null || spans2.Length == 0;
259 if (spans2 == null || spans1.Length != spans2.Length)
260 return false;
261 for (int i = 0; i < spans1.Length; i++) {
262 if (spans1[i] != spans2[i]) {
263 return false;
266 return true;
269 static Queue<UpdateWorker> updateQueue = new Queue<UpdateWorker> ();
271 class UpdateWorker
273 Document doc;
274 SyntaxMode mode;
275 int startOffset;
276 int endOffset;
278 public UpdateWorker (Document doc, SyntaxMode mode, int startOffset, int endOffset)
280 this.doc = doc;
281 this.mode = mode;
282 this.startOffset = startOffset;
283 this.endOffset = endOffset;
286 protected void ScanSpansThreaded (Document doc, Rule rule, Stack<Span> spanStack, int start, int end)
288 Dictionary<char, Span[]> spanTree = rule != null ? rule.spanStarts : null;
289 int endOffset = 0;
290 end = System.Math.Min (end, doc.Length);
291 Span curSpan = spanStack.Count > 0 ? spanStack.Peek () : null;
292 Rule curRule = rule;
293 for (int offset = start; offset < end; offset++) {
294 char ch;
295 try {
296 // document may have been changed and the thread is still running
297 // (however a new highlighting thread will be created after each document change)
298 ch = doc.GetCharAt (offset);
299 } catch (Exception) {
300 return;
302 if (curSpan != null && !String.IsNullOrEmpty (curSpan.End)) {
303 if (!String.IsNullOrEmpty (curSpan.Escape) && offset + 1 < end && endOffset == 0 && doc.GetCharAt (offset + 1) == curSpan.End[0]) {
304 bool match = true;
305 for (int j = 0; j < curSpan.Escape.Length && offset + j < doc.Length; j++) {
306 if (doc.GetCharAt (offset + j) != curSpan.Escape[j]) {
307 match = false;
308 break;
312 if (match) {
313 offset += curSpan.Escape.Length - 1;
314 continue;
319 if (curSpan.End[endOffset] == ch) {
320 endOffset++;
321 if (endOffset >= curSpan.End.Length) {
322 spanStack.Pop ();
323 curSpan = spanStack.Count > 0 ? spanStack.Peek () : null;
324 curRule = curSpan != null ? doc.SyntaxMode.GetRule (curSpan.Rule) : rule;
325 spanTree = curRule != null ? curRule.spanStarts : null;
326 endOffset = 0;
327 continue;
329 } else if (endOffset != 0) {
330 endOffset = 0;
331 if (curSpan.End[endOffset] == ch) {
332 offset--;
333 continue;
336 if (String.IsNullOrEmpty (curSpan.Rule))
337 continue;
340 if (spanTree != null && spanTree.ContainsKey (ch)) {
341 bool found = false;
342 foreach (Span span in spanTree[ch]) {
343 bool mismatch = false;
344 for (int j = 1; j < span.Begin.Length; j++) {
345 if (offset + j >= doc.Length || span.Begin [j] != doc.GetCharAt (offset + j)) {
346 mismatch = true;
347 break;
350 if (!mismatch && span.BeginFlags.Contains ("firstNonWs")) {
351 LineSegment line = doc.GetLineByOffset (offset);
352 for (int k = line.Offset; k < offset; k++) {
353 if (!Char.IsWhiteSpace (doc.GetCharAt (k))) {
354 mismatch = true;
355 break;
360 if (!mismatch) {
361 spanStack.Push (span);
362 curSpan = span;
363 curRule = doc.SyntaxMode.GetRule (curSpan.Rule);
364 spanTree = curRule != null ? curRule.spanStarts : null;
365 offset += span.Begin.Length - 1;
366 endOffset = 0;
368 found = true;
369 break;
372 if (found)
373 continue;
374 } else {
375 spanTree = curRule != null ? curRule.spanStarts : null;
377 // skip:
378 // ;
382 public void InnerRun ()
384 bool doUpdate = false;
385 RedBlackTree<LineSegmentTree.TreeNode>.RedBlackTreeIterator iter = doc.GetLineByOffset (startOffset).Iter;
386 Stack<Span> spanStack = iter.Current.StartSpan != null ? new Stack<Span> (iter.Current.StartSpan) : new Stack<Span> ();
388 do {
389 LineSegment line = iter.Current;
390 if (line == null || line.Offset < 0)
391 break;
393 List<Span> spanList = new List<Span> (spanStack.ToArray ());
394 spanList.Reverse ();
395 for (int i = 0; i < spanList.Count; i++) {
396 if (spanList[i].StopAtEol) {
397 spanList.RemoveAt (i);
398 i--;
401 Span[] newSpans = spanList.ToArray ();
402 if (line.Offset > endOffset) {
403 bool equal = IsEqual (line.StartSpan, newSpans);
404 doUpdate |= !equal;
405 if (equal)
406 break;
408 line.StartSpan = newSpans.Length > 0 ? newSpans : null;
409 Rule rule = mode;
410 if (spanStack.Count > 0 && !String.IsNullOrEmpty (spanStack.Peek ().Rule))
411 rule = mode.GetRule (spanStack.Peek ().Rule) ?? mode;
413 ScanSpansThreaded (doc, rule, spanStack, line.Offset, line.EndOffset);
414 while (spanStack.Count > 0 && spanStack.Peek ().StopAtEol)
415 spanStack.Pop ();
416 } while (iter.MoveNext ());
417 if (doUpdate) {
418 GLib.Timeout.Add (0, delegate {
419 doc.RequestUpdate (new UpdateAll ());
420 doc.CommitDocumentUpdate ();
421 return false;
427 // static bool updateIsRunning = false;
428 // static void Update (object o)
429 // {
430 // updateIsRunning = false;
431 // }
433 // static readonly object syncObject = new object();
434 static Thread updateThread = null;
435 static AutoResetEvent queueSignal = new AutoResetEvent (false);
437 static void StartUpdateThread ()
439 updateThread = new Thread (ProcessQueue);
440 updateThread.IsBackground = true;
441 updateThread.Start();
444 static void ProcessQueue ()
446 while (true) {
447 while (updateQueue.Count > 0) {
448 UpdateWorker worker = null;
449 lock (updateQueue) {
450 worker = updateQueue.Dequeue ();
452 worker.InnerRun ();
454 queueSignal.WaitOne ();
458 public static void StartUpdate (Document doc, SyntaxMode mode, int startOffset, int endOffset)
460 lock (updateQueue) {
461 updateQueue.Enqueue (new UpdateWorker (doc, mode, startOffset, endOffset));
463 queueSignal.Set ();
466 static string Scan (XmlTextReader reader, string attribute)
468 while (reader.Read () && !reader.IsStartElement ())
470 return reader.GetAttribute (attribute);
473 public static bool IsValidStyle (string fileName)
475 if (!fileName.EndsWith ("Style.xml"))
476 return false;
477 try {
478 using (XmlTextReader reader = new XmlTextReader (fileName)) {
479 string styleName = Scan (reader, Style.NameAttribute);
480 return !String.IsNullOrEmpty (styleName);
482 } catch (Exception) {
483 return false;
487 public static bool IsValidSyntaxMode (string fileName)
489 if (!fileName.EndsWith ("SyntaxMode.xml"))
490 return false;
491 try {
492 using (XmlTextReader reader = new XmlTextReader (fileName)) {
493 string mimeTypes = Scan (reader, SyntaxMode.MimeTypesAttribute);
494 return !String.IsNullOrEmpty (mimeTypes);
496 } catch (Exception) {
497 return false;
501 public static void LoadStylesAndModes (string path)
503 foreach (string file in Directory.GetFiles (path)) {
504 if (!file.EndsWith (".xml"))
505 continue;
506 if (file.EndsWith ("SyntaxMode.xml")) {
507 XmlTextReader reader = new XmlTextReader (file);
508 string mimeTypes = Scan (reader, SyntaxMode.MimeTypesAttribute);
509 foreach (string mimeType in mimeTypes.Split (';')) {
510 syntaxModeLookup [mimeType] = new UrlXmlProvider (file);
512 reader.Close ();
513 } else if (file.EndsWith ("Style.xml")) {
514 XmlTextReader reader = new XmlTextReader (file);
515 string styleName = Scan (reader, Style.NameAttribute);
516 styleLookup [styleName] = new UrlXmlProvider (file);
517 reader.Close ();
521 public static void LoadStylesAndModes (Assembly assembly)
523 foreach (string resource in assembly.GetManifestResourceNames ()) {
524 if (!resource.EndsWith (".xml"))
525 continue;
526 if (resource.EndsWith ("SyntaxMode.xml")) {
527 XmlTextReader reader = new XmlTextReader (assembly.GetManifestResourceStream (resource));
528 string mimeTypes = Scan (reader, SyntaxMode.MimeTypesAttribute);
529 ResourceXmlProvider provider = new ResourceXmlProvider (assembly, resource);
530 foreach (string mimeType in mimeTypes.Split (';')) {
531 syntaxModeLookup [mimeType] = provider;
533 reader.Close ();
534 } else if (resource.EndsWith ("Style.xml")) {
535 XmlTextReader reader = new XmlTextReader (assembly.GetManifestResourceStream (resource));
536 string styleName = Scan (reader, Style.NameAttribute);
537 styleLookup [styleName] = new ResourceXmlProvider (assembly, resource);
538 reader.Close ();
543 public static void AddSyntaxMode (IXmlProvider provider)
545 using (XmlTextReader reader = provider.Open ()) {
546 string mimeTypes = Scan (reader, SyntaxMode.MimeTypesAttribute);
547 foreach (string mimeType in mimeTypes.Split (';')) {
548 syntaxModeLookup [mimeType] = provider;
553 public static void RemoveSyntaxMode (IXmlProvider provider)
555 using (XmlTextReader reader = provider.Open ()) {
556 string mimeTypes = Scan (reader, SyntaxMode.MimeTypesAttribute);
557 foreach (string mimeType in mimeTypes.Split (';')) {
558 syntaxModeLookup.Remove (mimeType);
563 public static void AddStyle (IXmlProvider provider)
565 using (XmlTextReader reader = provider.Open ()) {
566 string styleName = Scan (reader, Style.NameAttribute);
567 styleLookup [styleName] = provider;
570 public static void RemoveStyle (IXmlProvider provider)
572 using (XmlTextReader reader = provider.Open ()) {
573 string styleName = Scan (reader, Style.NameAttribute);
574 styleLookup.Remove (styleName);
578 static SyntaxModeService ()
580 StartUpdateThread ();
581 LoadStylesAndModes (typeof (SyntaxModeService).Assembly);
582 SyntaxModeService.GetSyntaxMode ("text/x-csharp").AddSemanticRule ("Comment", new HighlightUrlSemanticRule ("comment"));
583 SyntaxModeService.GetSyntaxMode ("text/x-csharp").AddSemanticRule ("XmlDocumentation", new HighlightUrlSemanticRule ("comment"));
584 SyntaxModeService.GetSyntaxMode ("text/x-csharp").AddSemanticRule ("String", new HighlightUrlSemanticRule ("string"));