New test.
[mono.git] / mcs / tools / sqlsharp / gui / gtk-sharp / SqlEditorSharp.cs
1 //
2 // SqlEditor.cs - writen in C# using GTK#
3 //
4 // Authors:
5 //     Daniel Morgan <danmorg@sc.rr.com>
6 //     Rodrigo Moya <rodrigo@gnome-db.org>
7 //
8 // (c)copyright 2002 Daniel Morgan
9 // (c)copyright 2002 Rodrigo Moya
10 //
11 // SqlEditorSharp is based on the gnome-db-sql-editor.c in libgnomedb.
12 // SqlEditorSharp falls under the GPL license and is included
13 // in SQL# For GTK#.  SQL# For GTK# is a database query tool for Mono.
14 //
15
16 namespace SqlEditorSharp 
17 {
18         using System;
19         using Gtk;
20         using Gdk;
21         using GLib;
22         using System.Collections;
23         using System.IO;
24         using System.Text;
25         using System.Runtime.InteropServices;
26         using System.Diagnostics;
27         using Mono.Data.SqlSharp.Gui.GtkSharp;
28
29         /// <summary> SqlEditor Class</summary>
30         /// <remarks>
31         /// </remarks>
32         public class SqlEditorSharp : Gtk.VBox 
33         {
34                 // Fields
35
36                 // text tags for TextTagTable in TextBuffer
37                 private TextTag freecomment_tag;
38                 private TextTag linecomment_tag; 
39                 private TextTag singlequotedconstant_tag;
40                 private TextTag sql_tag;
41                 private TextTag normaltext_tag;
42
43                 // determine if something has changed beyond a line
44                 // updating one line is faster than the whole buffer
45                 //private int line_last_changed;
46                 //private int last_freecomment_count;
47
48                 // settings
49                 private bool use_hi_lighting;
50                 private string family;
51
52                 // widgets
53                 private ScrolledWindow scroll;
54                 private TextView sqlTextView;
55                 private TextBuffer sqlTextBuffer;
56                 private EditorTab tab = null;
57
58                 // Constructors
59
60                 public SqlEditorSharp() : base(false, 4) {               
61                         scroll = new ScrolledWindow (
62                                 new Adjustment (0.0, 0.0, 0.0, 0.0, 0.0, 0.0), 
63                                 new Adjustment (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
64                         scroll.HscrollbarPolicy = Gtk.PolicyType.Automatic;
65                         scroll.VscrollbarPolicy = Gtk.PolicyType.Automatic;
66                         scroll.ShadowType = Gtk.ShadowType.In;
67                         this.PackStart (scroll, true, true, 0);
68
69                         // default font famly for SQL editor
70                         family = "courier";
71
72                         // other default settings
73                         use_hi_lighting = false;
74
75                         // create text tag table
76                         TextTagTable textTagTable = new TextTagTable ();
77
78                         // anything else is normaltext
79                         normaltext_tag = new TextTag ("normaltext");
80                         normaltext_tag.Family = family;
81                         normaltext_tag.Foreground = "black";
82                         normaltext_tag.Style = Pango.Style.Normal;
83                         textTagTable.Add (normaltext_tag);
84        
85                         // SQL Keywords - SELECT FROM WHERE, etc
86                         sql_tag = new TextTag ("sql");
87                         sql_tag.Family = family;
88                         sql_tag.Foreground = "blue";
89                         sql_tag.Style = Pango.Style.Normal;
90                         textTagTable.Add (sql_tag);
91
92                         // c like free comment - used within a SQL statement
93                         freecomment_tag = new TextTag ("freecomment");
94                         freecomment_tag.Family = family;
95                         freecomment_tag.Foreground = "darkgreen";
96                         freecomment_tag.Style = Pango.Style.Italic;
97                         textTagTable.Add (freecomment_tag);
98
99                         // c++ like line comment, but using two hyphens
100                         linecomment_tag = new TextTag ("linecomment");
101                         linecomment_tag.Family = family;
102                         linecomment_tag.Foreground = "darkgreen";
103                         linecomment_tag.Style = Pango.Style.Italic;
104                         textTagTable.Add (linecomment_tag);
105
106                         /* single quoted constant - WHERE COL1 = 'ABC' */
107                         singlequotedconstant_tag = new TextTag ("singlequotedconstant");
108                         singlequotedconstant_tag.Family = family;
109                         singlequotedconstant_tag.Foreground = "red";
110                         singlequotedconstant_tag.Style = Pango.Style.Normal;
111                         textTagTable.Add (singlequotedconstant_tag);
112
113                         // create TextBuffer and TextView
114                         sqlTextBuffer = new TextBuffer (textTagTable);
115                         sqlTextView = new TextView (sqlTextBuffer);
116
117                         // allow it to be edited
118                         sqlTextView.Editable = true;
119
120                         //line_last_changed = -1;
121                         //last_freecomment_count = -1;
122
123                         // attach OnTextChanged callback function
124                         // to "changed" signal so we can do something
125                         // when the text has changed in the buffer
126                         sqlTextBuffer.Changed += new EventHandler (OnTextChanged);
127
128                         // add the TextView to the ScrolledWindow
129                         scroll.Add (sqlTextView);                                       
130                 }
131
132                 // Public Properties
133
134                 public TextBuffer Buffer {
135                         get {
136                                 return sqlTextBuffer;
137                         }
138                 }
139
140                 public TextView View {
141                         get {
142                                 return sqlTextView;
143                         }
144                 }
145
146                 public EditorTab Tab {
147                         get {
148                                 return tab;
149                         }
150                         set {
151                                 tab = value;
152                         }
153
154                 }
155
156                 public bool UseSyntaxHiLighting {
157                         get {
158                                 return use_hi_lighting;
159                         }
160
161                         set {
162                                 use_hi_lighting = value;
163                         }
164                 }
165
166                 // Private Methods
167
168                 void OnTextChanged (object o, EventArgs args) 
169                 {
170                         if(tab != null)
171                                 tab.label.Text = tab.basefilename + " *";
172
173                         SqlSharpGtk.DebugWriteLine ("[[[[[ Syntax Hi-Light Text BEGIN ]]]]]");
174
175                         if (use_hi_lighting == true) {
176                                 SyntaxHiLightText ();
177                         }
178                         
179                         SqlSharpGtk.DebugWriteLine ("[[[[[ Syntax Hi-Light Text END   ]]]]]\n");
180                 }
181
182                 void SyntaxHiLightText () 
183                 {
184                         TextIter start_iter, end_iter, 
185                                 iter, insert_iter;
186                         TextIter match_start1, match_end1, 
187                                 match_start2, match_end2;
188                         int char_count = 0;
189                         int hyphen = 0, single_quotes = 0;
190                         string text = String.Empty;
191                         int i = 0, start_con = 0, end_con = 0;
192                         //int line = 0;
193                         //int freecomment_count = 0;
194                         int start_word = -1;
195                         TextMark insert_mark;
196                         char ch = ' ';
197
198                         insert_mark = sqlTextBuffer.InsertMark;
199                         sqlTextBuffer.GetIterAtMark (out insert_iter, insert_mark);
200                         //line = insert_iter.Line;
201                         
202                         /* get the starting and ending text iterators */
203                         sqlTextBuffer.GetIterAtOffset (out start_iter, 0);
204                         char_count = sqlTextBuffer.CharCount;
205                         sqlTextBuffer.GetIterAtOffset (out end_iter, char_count);
206                         
207                         SqlSharpGtk.DebugWriteLine ("char_count: " + char_count);
208                         
209                         /* since line is not same - redo all */
210                         //if (line != line_last_changed) {
211                         /* remove all previously applied tags */
212                         sqlTextBuffer.RemoveAllTags (start_iter, end_iter);
213
214                         /* apply the entire buffer to the normaltext tag */
215                         sqlTextBuffer.ApplyTag (normaltext_tag, start_iter, end_iter);
216                         //}
217                         //else { /* just worry about current insertion line */
218                         //      /* get start iter */
219                         //      if (insert_iter.StartsLine () == true) {
220                         //              start_iter = insert_iter;
221                         //      }
222                         //      else {
223                         //              start_iter = insert_iter;
224                         //              start_iter.LineOffset = 0;
225                         //      }
226                         //      /* get end iter */      
227                         //      end_iter.ForwardToLineEnd ();
228                         //      char_count = start_iter.CharsInLine;
229                         //      
230                         //      /* remove all previously applied tags */
231                         //      sqlTextBuffer.RemoveAllTags (start_iter, end_iter);
232                         //      
233                         //      /* apply the entire buffer to the normaltext tag */
234                         //      sqlTextBuffer.ApplyTag (normaltext_tag,
235                         //              start_iter, end_iter);
236                         //
237                         //      /* get the starting and ending text iterators */
238                         //      sqlTextBuffer.GetIterAtOffset (out start_iter, 0);
239                         //      char_count = sqlTextBuffer.CharCount;
240                         //      sqlTextBuffer.GetIterAtOffset (out end_iter, char_count);
241                         //}
242
243                         /*  ------------------------------------
244                          *  Free Comments (sort of like c style) 
245                          *  ------------------------------------
246                          *  except in SQL, a c like comment occurs within
247                          *  a SQL statement
248                          */ 
249                         match_start1 = start_iter; // dummy
250                         match_end1 = end_iter; // dummy
251                         match_start2 = start_iter; // dummy
252                         match_end2 = end_iter; // dummy
253
254                         while (start_iter.IsEnd == false) {
255                                 // FIXME: match_start1, match_end1, end_iter
256                                 //        need to be set to have ref in front
257                                 //        Problem with TextIter's ForwardSearch()
258                                 //        in GTK# (not GTK+)
259                                 if (start_iter.ForwardSearch (
260                                         "/*",
261                                         TextSearchFlags.TextOnly,
262                                         out match_start1, 
263                                         out match_end1,
264                                         end_iter) == true) {
265
266                                         /* beginning of free comment found */ 
267                                         //freecomment_count++;
268                                         // FIXME: fix match_start2, match_end2, end_iter
269                                         //        with ref if front
270                                         if (match_end1.ForwardSearch (
271                                                 "*/",
272                                                 TextSearchFlags.TextOnly, 
273                                                 out match_start2,
274                                                 out match_end2,
275                                                 end_iter) == true) {
276
277                                                 // ending of free comment found, 
278                                                 // now hi-light comment
279                                                 sqlTextBuffer.ApplyTag (
280                                                         freecomment_tag,
281                                                         match_start1,
282                                                         match_end2);
283                                                 match_end2.ForwardChars (1);
284                                                 start_iter = match_end2;
285                                         }
286                                         else {
287                                                 // if no end found, 
288                                                 // hi-light to the end, 
289                                                 // to let the user know 
290                                                 // the ending asterisk slash is missing 
291                                                 ApplyTag (
292                                                         freecomment_tag,
293                                                         normaltext_tag,
294                                                         match_start1,
295                                                         end_iter);
296                                                 break;
297                                         }
298                                 }
299                                 else
300                                         break;
301                         }
302
303                         /* if free comments is different than last time,
304                          * invalidate line_last_changed - causes 
305                          * a complete redo (instead hi-lighting just the current line -
306                          * do the whole buffer)
307                          * THIS IS JUST AN ATTEMPT FOR SPEED
308                          */
309                         //if (freecomment_count != last_freecomment_count) {
310                         //      line_last_changed = -1;
311                         //}
312
313                         /*********************************************************************
314                          * See if the following needs hi-lighting:
315                          * - Line Comments (sort of like C++ slash slash comments 
316                          *   but uses hypen hyphen and it is based at the beginning of a line)
317                          * - Single-Quoted Constants ( WHERE COL1 = 'ABC' )
318                          * - SQL keywords (SELECT, FROM, WHERE, UPDATE, etc)
319                          *********************************************************************/
320                         //if (line != line_last_changed) {
321                         sqlTextBuffer.GetIterAtOffset (out start_iter, 0);
322                         //}
323                         //else {
324                         //      if (insert_iter.StartsLine () == true) {
325                         //              start_iter = insert_iter;
326                         //      }
327                         //      else {
328                         //              start_iter = insert_iter;
329                         //              start_iter.LineOffset = 0;
330                         //      }
331                         //}
332
333                         // get starting and ending iters 
334                         // and character count of line
335                         char_count = sqlTextBuffer.CharCount;
336                         sqlTextBuffer.GetIterAtOffset (out end_iter, char_count);
337                         
338                         // for each line, look for:
339                         // line comments, constants, and keywoards 
340                         do {    
341                                 iter = start_iter;
342                                 iter.ForwardToLineEnd ();
343                                 text = sqlTextBuffer.GetText (
344                                         start_iter, iter, false);
345
346                                 // look for line comment
347                                 char_count = start_iter.CharsInLine;
348                                 hyphen = 0; 
349                                 for (i = 0; i < char_count - 1; i++) {
350                                         switch (text[i]) {
351                                         case '-':
352                                                 if (hyphen == 1) {
353                                                         hyphen = 2;
354                                                         // line comment found
355                                                         i = char_count;
356
357                                                         ApplyTag (
358                                                                 linecomment_tag, 
359                                                                 normaltext_tag,
360                                                                 start_iter, 
361                                                                 iter);
362                                                 }
363                                                 else {
364                                                         hyphen = 1;
365                                                 }
366                                                 break;
367                                         case ' ':
368                                                 // continue
369                                                 break;
370                                         default:
371                                                 // this line is not line commented
372                                                 i = char_count; // break out of for loop
373                                                 break;
374                                         }
375                                 }
376                                 // if not line commented, 
377                                 // look for singled quoted constants 
378                                 // and keywords
379                                 if (hyphen < 2) {
380                                         if (start_iter.IsEnd == true)
381                                                 break; // break out of for loop
382
383                                         start_word = -1;
384                                         single_quotes = 0;
385
386                                         LookForSingleQuotesAndWords (
387                                                 ref start_iter, 
388                                                 text, char_count,
389                                                 ref start_word, 
390                                                 ref single_quotes, 
391                                                 ref start_con, 
392                                                 ref end_con);
393                                 }
394                                 
395                         } while (start_iter.ForwardLine () == true);
396         
397
398                         // POOR ATTEMPTS AT SPEED - last_freecomment_count 
399                         // and line_last_changed 
400                         //
401                         //last_freecomment_count = freecomment_count;
402                         //line_last_changed = line;
403                 }
404
405                 void LookForSingleQuotesAndWords (ref TextIter start_iter, 
406                                         string text, int char_count,
407                                         ref int start_word, ref int single_quotes, 
408                                         ref int start_con, ref int end_con) 
409                 {
410                         TextIter match_start1, match_end1;
411                         int i;
412                         char ch;
413
414                         for (i = 0; i < char_count; i++) {
415                                 match_start1 = start_iter;
416                                 match_end1 = start_iter;
417                                                 
418                                 if (match_end1.IsEnd == true)
419                                         break;
420
421                                 if (CharHasTag (start_iter, 
422                                         freecomment_tag, i) 
423                                         == false) {
424                                                         
425                                         if (single_quotes == 0 && 
426                                                 start_word == -1) {
427
428                                                 ch = text[i];
429                                                 if (ch == '\'') {
430                                                         single_quotes = 1;
431                                                         start_con = i + 1;
432                                                 }
433                                                 else if (Char.IsLetter (ch)) {
434                                                         start_word = i;
435                                                 }
436                                                 else {
437                                                         // continue
438                                                 }
439                                         }
440                                         else if (single_quotes == 1) {
441                                                 ch = text[i];
442                                                 switch (ch) {
443                                                 case '\'':
444                                                         // single quoted constant
445                                                         end_con = i;
446                 
447                                                         // get starting and
448                                                         // ending of constant 
449                                                         // excluding quotes
450                                                         ApplyTagOffsets (
451                                                                 start_iter,
452                                                                 start_con, i,
453                                                                 singlequotedconstant_tag,
454                                                                 normaltext_tag);
455
456                                                         single_quotes = 0;
457                                                         break;
458                                                 default:
459                                                         break;
460                                                 }
461                                         }
462                                         else if (start_word != -1) {
463                                                 ch = text[i];
464                                                 // is character alphabetic, numeric, or '_'
465                                                 if (Char.IsLetterOrDigit (ch) || 
466                                                         ch == '_') {
467
468                                                         // continue 
469                                                 }
470                                                 else {
471                                                         // using start_word 
472                                                         // and i offsets, 
473                                                         // get word
474                                                         if (IsTextSQL (text, start_word, i)) {
475                                                                 // word is a SQL keyword, 
476                                                                 // hi-light word
477                                                                 ApplyTagOffsets (
478                                                                         start_iter,
479                                                                         start_word, i,
480                                                                         sql_tag,
481                                                                         normaltext_tag);
482                                                         }
483                                                         start_word = -1;
484                                                         switch (text[i]) {
485                                                         case '\'':
486                                                                 single_quotes = 1;
487                                                                 start_con = i + 1;
488                                                                 break;
489                                                         default:
490                                                                 break;
491                                                         }
492                                                 }
493                                         }
494                                 } 
495                         }
496                         if( start_word != -1) {
497                                 if (IsTextSQL (text, start_word, i)) {
498                                         // word is a SQL keyword, 
499                                         // hi-light word
500                                         ApplyTagOffsets(
501                                                 start_iter,
502                                                 start_word, i,
503                                                 sql_tag,
504                                                 normaltext_tag);
505                                 }
506                         }
507                 }
508
509                 void ApplyTag ( TextTag apply_tag, TextTag remove_tag,
510                                 TextIter start_iter, TextIter end_iter ) 
511                 {
512 #if DEBUG
513                         DebugText(start_iter, end_iter, "ApplyTag() " + 
514                                 "remove: " + remove_tag.Name + 
515                                 " apply: " + apply_tag.Name);
516 #endif // DEBUG
517
518                         sqlTextBuffer.RemoveTag (
519                                 remove_tag, 
520                                 start_iter, end_iter);
521
522                         sqlTextBuffer.ApplyTag (
523                                 apply_tag,
524                                 start_iter, end_iter);
525                 }
526
527                 void ApplyTagOffsets (TextIter start_iter, 
528                                 int start_offset, int end_offset, 
529                                 TextTag apply_tag,
530                                 TextTag remove_tag) 
531                 {
532                         TextIter begin_iter, end_iter;
533         
534                         begin_iter = start_iter;
535                         end_iter = start_iter;
536
537                         begin_iter.LineOffset = start_offset;
538                         end_iter.LineOffset = end_offset;
539         
540 #if DEBUG               
541                         DebugText(start_iter, end_iter, "ApplyTagOffsets() " + 
542                                 "remove: " + remove_tag.Name + 
543                                 " apply: " + apply_tag.Name +
544                                 " start: " + start_offset.ToString() +
545                                 " end: " + end_offset.ToString());
546 #endif
547
548                         sqlTextBuffer.RemoveTag (remove_tag,
549                                 begin_iter, end_iter);
550
551                         sqlTextBuffer.ApplyTag (apply_tag,
552                                 begin_iter, end_iter);
553                 }
554
555                 /* is word a SQL keyword? */
556                 bool IsTextSQL (string text, int begin, int end) 
557                 {
558                         string keyword = "";
559
560                         int i;
561                         int text_len;
562                         if(text.Equals(String.Empty))
563                                 return false;
564
565                         if(begin < 0)
566                                 return false;
567
568                         if(end < 2)
569                                 return false;
570
571                         if(begin >= end)
572                                 return false;
573
574                         text_len = end - begin;
575                         if(text_len < 1)
576                                 return false;
577
578 #if DEBUG
579                         SqlSharpGtk.DebugWriteLine("IsTextSQL - " +
580                                 "begin: " + begin.ToString() +
581                                 " end: " + end.ToString() +
582                                 " text_len: " + text_len);
583                         SqlSharpGtk.DebugWriteLine("[TEXT BEGIN]");
584                         SqlSharpGtk.DebugWriteLine(text);
585                         SqlSharpGtk.DebugWriteLine("[TEXT END  ]");
586 #endif // DEBUG
587
588                         for (i = 0; sql_keywords[i] != String.Empty; i++) {
589                                 if(text_len == sql_keywords[i].Length) {
590                         
591                                         SqlSharpGtk.DebugWriteLine(
592                                                 "Test length: " + text_len + 
593                                                 " keyword: " + keyword);
594                                         
595                                         try {
596                                                 keyword = text.Substring (begin, text_len);
597                                         }
598                                         catch(ArgumentOutOfRangeException a) {
599                                                 Console.WriteLine ("Internal Error: SqlSharpGtk: text.Substring() ArgumentOutOfRange");
600                                         }
601
602                                         keyword = keyword.ToUpper();
603
604                                         if(keyword.Equals (sql_keywords [i]))
605                                                 return true;
606                                 }
607                         }
608
609                         return false;
610                 }
611
612                 // does the character at offset in the GtkTextIter has
613                 // this text tag applied?
614                 bool CharHasTag(TextIter iter, 
615                                 TextTag tag, int char_offset_in_line) 
616                 {
617
618                         TextIter offset_iter;
619
620                         offset_iter = iter;
621                         offset_iter.LineOffset = char_offset_in_line;
622                 
623                         return offset_iter.HasTag (tag);
624                 }
625
626                 public void Clear() 
627                 {
628                         TextIter start, end;
629                         start = sqlTextBuffer.StartIter;
630                         end = sqlTextBuffer.EndIter;
631                         sqlTextBuffer.Delete(start,end);
632                 }
633
634                 public void LoadFromFile(string inFilename) 
635                 {
636                         StreamReader sr = new StreamReader(inFilename);
637                         Clear();
638                         string NextLine;
639                         string line;
640                         
641                         while((NextLine = sr.ReadLine()) != null) {
642                                 line = NextLine + "\n";
643                                 sqlTextBuffer.Insert (sqlTextBuffer.EndIter, line);
644                         }
645                         sr.Close();
646                 }
647
648                 public void SaveToFile(string outFilename) 
649                 {
650                         TextIter start_iter, iter;
651                         string text;
652                         StreamWriter sw = null;
653                         
654                         sw = new StreamWriter(outFilename);                     
655                         sqlTextBuffer.GetIterAtOffset (out iter, 0);
656                         start_iter = iter;
657                         while (iter.ForwardLine()) {
658                                 text = sqlTextBuffer.GetText(start_iter, iter, false);
659                                 sw.Write(text);
660                                 start_iter = iter;
661                         }
662                         text = sqlTextBuffer.GetText(start_iter, iter, false);
663                         sw.Write(text);
664                         sw.Close();
665                         sw = null;
666                 }
667
668                 void DebugText (TextIter iter_start, TextIter iter_end,
669                                 string debugMessage) 
670                 {
671
672 #if DEBUG
673                         string text = sqlTextBuffer.GetText (
674                                 iter_start, iter_end, false);
675                         string msg = 
676                                 "[DEBUG-TEXT]: " + 
677                                 debugMessage +
678                                 " (" +
679                                 text +
680                                 ")";
681                         SqlSharpGtk.DebugWriteLine(msg);
682 #endif // DEBUG
683                 }
684
685                 static readonly string[] sql_keywords = 
686                         new string[] {
687                                              "DELETE",
688                                              "FROM",
689                                              "SELECT",
690                                              "UPDATE",
691                                              "SET",
692                                              "INSERT",
693                                              "INTO",
694                                              "VALUES",
695                                              "WHERE",
696                                              "COUNT",
697                                              "SUM",
698                                              "MAX",
699                                              "MIN",
700                                              "AVG",
701                                              "DROP",
702                                              "ALTER",
703                                              "CREATE",
704                                              "VIEW",
705                                              "TABLE",
706                                              "AS",
707                                              "AND",
708                                              "OR",
709                                              "ORDER",
710                                              "GROUP",
711                                              "BY",
712                                              "HAVING",
713                                              "IS",
714                                              "NULL",
715                                              "NOT",
716                                              "COMMIT",
717                                              "ROLLBACK",
718                                              "EXISTS",
719                                              "IN",
720                                              "LIKE",
721                                              "GRANT",
722                                              "REVOKE",
723                                              "ON",
724                                              "TO",
725                                              String.Empty
726                                      };
727         }
728 }