1 // SyntaxModeService.cs
4 // Mike Krüger <mkrueger@novell.com>
6 // Copyright (c) 2007 Novell, Inc (http://www.novell.com)
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
29 using System
.Collections
.Generic
;
30 using System
.Collections
.ObjectModel
;
32 using System
.Reflection
;
33 using System
.Threading
;
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
{
47 List
<string> result
= new List
<string> ();
48 foreach (string style
in styles
.Keys
) {
49 if (!result
.Contains (style
))
52 foreach (string style
in styleLookup
.Keys
) {
53 if (!result
.Contains (style
))
56 return result
.ToArray ();
60 public static Style
GetColorStyle (Gtk
.Widget widget
, string name
)
62 if (styles
.ContainsKey (name
))
64 if (styleLookup
.ContainsKey (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
];
80 public static IXmlProvider
GetProvider (Style style
)
82 if (styleLookup
.ContainsKey (style
.Name
))
83 return styleLookup
[style
.Name
];
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 ();
93 styles
[name
] = Style
.LoadFrom (reader
);
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 ();
105 SyntaxMode mode
= SyntaxMode
.Read (reader
);
106 foreach (string mime
in mode
.MimeType
.Split (';')) {
107 syntaxModes
[mime
] = mode
;
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
);
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 ();
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
))
142 if (!mode
.Value
.Validate (style
.Value
)) {
143 System
.Console
.WriteLine(mode
.Key
+ " failed to validate against:" + style
.Key
);
146 checkedModes
.Add (mode
.Value
);
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;
173 end
= System
.Math
.Min (end
, doc
.Length
);
174 Span curSpan
= spanStack
.Count
> 0 ? spanStack
.Peek () : null;
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]) {
181 for (int j
= 0; j
< curSpan
.Escape
.Length
&& offset
+ j
< doc
.Length
; j
++) {
182 if (doc
.GetCharAt (offset
+ j
) != curSpan
.Escape
[j
]) {
188 offset
+= curSpan
.Escape
.Length
- 1;
194 if (curSpan
.End
[endOffset
] == ch
) {
196 if (endOffset
>= curSpan
.End
.Length
) {
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;
204 } else if (endOffset
!= 0) {
206 if (curSpan
.End
[endOffset
] == ch
) {
211 if (String
.IsNullOrEmpty (curSpan
.Rule
))
215 if (spanTree
!= null && spanTree
.ContainsKey (ch
)) {
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
)) {
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
))) {
236 spanStack
.Push (span
);
238 curRule
= doc
.SyntaxMode
.GetRule (curSpan
.Rule
);
239 spanTree
= curRule
!= null ? curRule
.spanStarts
: null;
241 offset
+= span
.Begin
.Length
- 1;
249 spanTree
= curRule
!= null ? curRule
.spanStarts
: null;
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
)
261 for (int i
= 0; i
< spans1
.Length
; i
++) {
262 if (spans1
[i
] != spans2
[i
]) {
269 static Queue
<UpdateWorker
> updateQueue
= new Queue
<UpdateWorker
> ();
278 public UpdateWorker (Document doc
, SyntaxMode mode
, int startOffset
, int endOffset
)
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;
290 end
= System
.Math
.Min (end
, doc
.Length
);
291 Span curSpan
= spanStack
.Count
> 0 ? spanStack
.Peek () : null;
293 for (int offset
= start
; offset
< end
; offset
++) {
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
) {
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]) {
305 for (int j
= 0; j
< curSpan
.Escape
.Length
&& offset
+ j
< doc
.Length
; j
++) {
306 if (doc
.GetCharAt (offset
+ j
) != curSpan
.Escape
[j
]) {
313 offset
+= curSpan
.Escape
.Length
- 1;
319 if (curSpan
.End
[endOffset
] == ch
) {
321 if (endOffset
>= curSpan
.End
.Length
) {
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;
329 } else if (endOffset
!= 0) {
331 if (curSpan
.End
[endOffset
] == ch
) {
336 if (String
.IsNullOrEmpty (curSpan
.Rule
))
340 if (spanTree
!= null && spanTree
.ContainsKey (ch
)) {
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
)) {
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
))) {
361 spanStack
.Push (span
);
363 curRule
= doc
.SyntaxMode
.GetRule (curSpan
.Rule
);
364 spanTree
= curRule
!= null ? curRule
.spanStarts
: null;
365 offset
+= span
.Begin
.Length
- 1;
375 spanTree
= curRule
!= null ? curRule
.spanStarts
: null;
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
> ();
389 LineSegment line
= iter
.Current
;
390 if (line
== null || line
.Offset
< 0)
393 List
<Span
> spanList
= new List
<Span
> (spanStack
.ToArray ());
395 for (int i
= 0; i
< spanList
.Count
; i
++) {
396 if (spanList
[i
].StopAtEol
) {
397 spanList
.RemoveAt (i
);
401 Span
[] newSpans
= spanList
.ToArray ();
402 if (line
.Offset
> endOffset
) {
403 bool equal
= IsEqual (line
.StartSpan
, newSpans
);
408 line
.StartSpan
= newSpans
.Length
> 0 ? newSpans
: null;
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
)
416 } while (iter
.MoveNext ());
418 GLib
.Timeout
.Add (0, delegate {
419 doc
.RequestUpdate (new UpdateAll ());
420 doc
.CommitDocumentUpdate ();
427 // static bool updateIsRunning = false;
428 // static void Update (object o)
430 // updateIsRunning = false;
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 ()
447 while (updateQueue
.Count
> 0) {
448 UpdateWorker worker
= null;
450 worker
= updateQueue
.Dequeue ();
454 queueSignal
.WaitOne ();
458 public static void StartUpdate (Document doc
, SyntaxMode mode
, int startOffset
, int endOffset
)
461 updateQueue
.Enqueue (new UpdateWorker (doc
, mode
, startOffset
, endOffset
));
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"))
478 using (XmlTextReader reader
= new XmlTextReader (fileName
)) {
479 string styleName
= Scan (reader
, Style
.NameAttribute
);
480 return !String
.IsNullOrEmpty (styleName
);
482 } catch (Exception
) {
487 public static bool IsValidSyntaxMode (string fileName
)
489 if (!fileName
.EndsWith ("SyntaxMode.xml"))
492 using (XmlTextReader reader
= new XmlTextReader (fileName
)) {
493 string mimeTypes
= Scan (reader
, SyntaxMode
.MimeTypesAttribute
);
494 return !String
.IsNullOrEmpty (mimeTypes
);
496 } catch (Exception
) {
501 public static void LoadStylesAndModes (string path
)
503 foreach (string file
in Directory
.GetFiles (path
)) {
504 if (!file
.EndsWith (".xml"))
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
);
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
);
521 public static void LoadStylesAndModes (Assembly assembly
)
523 foreach (string resource
in assembly
.GetManifestResourceNames ()) {
524 if (!resource
.EndsWith (".xml"))
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
;
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
);
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"));