home *** CD-ROM | disk | FTP | other *** search
/ PC Plus SuperCD (UK) 2000 March / pcp161b.iso / handson / archive / Issue160 / files / SyntaxEditor / main.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-10-18  |  9.8 KB  |  308 lines

  1. unit main;
  2.  
  3. (* PC PLUS Sample Delphi syntax-colouring application.
  4.  
  5.    Illustrates a simple way to read a plain text file containing program code,
  6.    colour it according to its syntax, by adding RTF codes as necessary,
  7.    then fill a RichEdit control with the coloured code.
  8.  
  9.    Syntax recognised:
  10.    - Delphi keywords
  11.    - Strings
  12.    - Curly-brace comments
  13.    - Starred comments
  14.    - Single-line comments
  15.  
  16.    Deficiencies:
  17.  
  18.    The principal deficiency is that the editor does not do on-the-fly colour
  19.    coding of strings and comments.
  20.  
  21.    It needs to be improved so that it automatically colours strings and
  22.    un-colours them if the string delimiters are deleted.
  23.  
  24.    It should similarly colour and un-colour comments when their delimiters
  25.    are deleted.
  26.  
  27.    Note that dealing with comments correctly is a tougher task than dealing
  28.    with strings. The really big problem here is dealing with long multi-line
  29.    comments. For example, it should be possible to enclose a large block
  30.    of code between pairs of curly-brace or starred comments and have the entire
  31.    block immediately recoloured as a comment. When the comment delimiters are
  32.    deleted, the code should be recoloured as normal.
  33.  
  34.    - does not deal correctly with colour-coding after cut and paste operations
  35.      or when two lines require recolouring (e.g. if you press the Enter key
  36.      to divide the token beginend into begin on one line and end on the next,
  37.      then only the end token will be colour-coded.
  38.  
  39.    - does not check if it's ok to load a new file (i.e. if the current
  40.      file has been modified and needs to be saved first). This can be checked
  41.      using the RichEdit1.Modified property.
  42.  
  43.    - Limited error-handling
  44.      This can be improved by adding exception handling when attempting
  45.      to read and write from streams and by testing the 'status'
  46.      parameter after every read-write operation.
  47.  
  48.    - Could be optimised
  49.      I have not gone to any lengths to fine-tune my code for efficiency. The
  50.      speed of file loading is perfectly adequate with files up to 500K or so.
  51.      With files in excess of a Megabyte, there is a noticable delay - but only
  52.      of a few seconds (on a 333MHz PC), which seems quite reasonable. All the
  53.      same, there are places in the code where the efficiency could certainly
  54.      be improved.
  55.  
  56.    - Bugs?
  57.      This is only a sample application and I haven't had the time (or the
  58.      inclination!) to test this rigorously. No doubt there are bugs of varying
  59.      degrees of unsubtlety lurking in here somewhere. Have fun find them and
  60.      squashing them!
  61.      
  62.    Author: Huw Collingbourne.
  63.  
  64. *)
  65.  
  66. // The following comments have been added to test colour-coding when
  67. // loading this source file.
  68. { This is a
  69.   2-line comment}
  70. interface
  71.       { 'a string'  (*a starred
  72.          comment *)  inside a standard one  }
  73. (* This is a starred comment *)
  74. (* {a standard comment} inside a starred one        *)
  75. // { standard comment after line comment }
  76. { // line comment in standard comment }
  77.  
  78. uses
  79.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  80.   StdCtrls, ComCtrls, ExtCtrls,
  81.   Syntax, EditConsts, RichEditUtils, StrUtils, Menus;
  82.  
  83. type
  84.   chararray = array[0..255] of char;
  85.      // ReadStatus defines type of the status parameter
  86.   ReadStatus = SUCCESS..READERROR;
  87.  
  88.   TForm1 = class(TForm)
  89.     RichEdit1: TRichEdit;
  90.     OpenDialog1: TOpenDialog;
  91.     SaveDialog1: TSaveDialog;
  92.     MainMenu1: TMainMenu;
  93.     File1: TMenuItem;
  94.     NewMenuItem: TMenuItem;
  95.     SaveMenuItem: TMenuItem;
  96.     SaveAsMenuItem: TMenuItem;
  97.     N1: TMenuItem;
  98.     ExitMenuItem: TMenuItem;
  99.     Print1: TMenuItem;
  100.     LoadMenuItem: TMenuItem;
  101.     PrintDialog1: TPrintDialog;
  102.     StatusBar1: TStatusBar;
  103.     procedure FormCreate(Sender: TObject);
  104.     procedure NewMenuItemClick(Sender: TObject);
  105.     procedure LoadMenuItemClick(Sender: TObject);
  106.     procedure SaveAsMenuItemClick(Sender: TObject);
  107.     procedure SaveMenuItemClick(Sender: TObject);
  108.     procedure ExitMenuItemClick(Sender: TObject);
  109.     procedure Print1Click(Sender: TObject);
  110.   private
  111.     { Private declarations }
  112.   public
  113.     SourceFileName : string;
  114.     procedure SetSourceFileName( FN : String);
  115.     procedure FormatLineAt( Red: TRichEdit; num : LongInt );
  116.     procedure SetNormalAttributes( atts : TTextAttributes );
  117.     procedure SetKeywordAttributes( atts : TTextAttributes );
  118.     procedure LoadFile(FN : string );
  119.     procedure OnChangeHandler(Sender: TObject);
  120.     function NeedsColouring( atts : TTextAttributes ) : boolean;
  121.   end;
  122.  
  123. const
  124.      APPNAME = 'Huw''s Colour-coded thing';
  125.      INFILE = '';
  126. var
  127.    Form1 : TForm1;
  128.  
  129. implementation
  130.   {$R *.DFM}
  131.  
  132. function TForm1.NeedsColouring( atts : TTextAttributes ) : boolean;
  133. { NOTE: This function is a bit of a cheat. It simply does not attempt
  134.         to recolour existing strings or comments - that is, if the
  135.         selected text has the attributes of a comment or string, it
  136.         doesn't attempt to do any recolouring.
  137.  
  138.         The problem with this is that:
  139.         a) it does not colour newly added strings or comments
  140.         b) it does not un-colour code that when the string or
  141.            comment delimiters are deleted. }
  142. begin
  143.   if ((atts.Color = STRINGCOL) and (atts.Style = STRINGSTYLE))
  144.      or  ((atts.Color = COMMENTCOL) and (atts.Style = COMMENTSTYLE)) then
  145.      result := false
  146.   else
  147.      result := true;
  148. end;
  149.  
  150. procedure TForm1.setNormalAttributes( atts : TTextAttributes );
  151. begin
  152.  // recolour selected text using plain text attributes unless
  153.  // the selected text already has those attributes, in which case, do nothing
  154.   if not ((atts.Color = PLAINCOL) and (atts.Style = PLAINSTYLE)) then
  155.   begin
  156.     RichEdit1.SelAttributes.Style := PLAINSTYLE;
  157.     RichEdit1.selAttributes.Color := PLAINCOL;
  158.   end;
  159. end;
  160.  
  161. procedure TForm1.setKeyWordAttributes( atts : TTextAttributes );
  162. begin
  163.  // recolour selected text using keyword text attributes unless
  164.  // the selected text already has those attributes, in which case, do nothing
  165. if not ((atts.Color = KWCOL) and (atts.Style = KWSTYLE)) then
  166.   begin
  167.     RichEdit1.SelAttributes.Style := KWSTYLE;
  168.     RichEdit1.selAttributes.Color := KWCOL;
  169.   end;
  170. end;
  171.  
  172.  
  173. procedure TForm1.FormatLineAt( Red: TRichEdit; num : LongInt );
  174.                                     // num is RichEdit1.Lines[num]
  175. var
  176.    ti : tokenindexes;
  177.    i, numfound : integer;
  178.    StartIndex : LongInt;
  179.    s : string;
  180.    textatts : TTextAttributes;
  181. begin
  182.   s := red.Lines[num];
  183.   TokensFoundAt( s, numfound, ti );
  184.   StartIndex := GetLineSelStartIndex( Red, num );
  185.   for i := 1 to numfound do
  186.   begin
  187.      Red.SelStart := StartIndex + ti[i].tstart;
  188.      Red.SelLength := ti[i].tend;
  189.      textatts := Red.SelAttributes;
  190.      if NeedsColouring( textatts ) then //!! This is my quick getout clause!
  191.      begin
  192.        if KeyWord( Red.SelText ) then
  193.        begin
  194.           //!! Here test for existing attributes and only reformat if necessary
  195.            SetKeyWordAttributes( textatts );
  196.            //!! move selection to end of keyword and re-set normal attributes
  197.            Red.SelStart := Red.SelStart + Red.SelLength;
  198.            Red.SelLength := 1;
  199.            SetNormalAttributes( textatts );
  200.        end
  201.        else
  202.            SetNormalAttributes( textatts );
  203.       end; // if NeedsColouring
  204.    end;
  205. end;
  206.  
  207.  
  208. procedure TForm1.LoadFile( FN : string );
  209. var
  210.    msin, msout : TMemoryStream;
  211. begin
  212.    msin := TMemoryStream.Create;
  213.    msout := TMemoryStream.Create;
  214.    msin.LoadFromFile(FN);
  215.    msin.Position := 0;  // set pos to 0 prior to loading from stream
  216.    msout.Position := 0;
  217.    WriteRTFHeader( msout );
  218.    processStream(msin, msout);
  219.    WriteRTFTerminator( msout );
  220.    msout.Position := 0;
  221.    RichEdit1.Lines.LoadFromStream(msout);
  222.    msin.Free;
  223.    msout.Free;
  224. end;
  225.  
  226. procedure TForm1.FormCreate(Sender: TObject);
  227. begin
  228.   RichEdit1.Plaintext := false;
  229.   SetSourceFileName( 'Untitled' );
  230.   RichEdit1.OnChange := OnChangeHandler;
  231. end;
  232.  
  233. procedure TForm1.OnChangeHandler(Sender: TObject);
  234. var
  235.    startSelPos : Longint;
  236. begin
  237.    RichEdit1.OnChange := nil;
  238.    HideSelection(RichEdit1);
  239.    startSelPos := RichEdit1.SelStart;
  240.    FormatLineAt(RichEdit1,GetCurrLineNum(RichEdit1));
  241.    RichEdit1.SelStart := startSelPos;
  242.    ShowSelection(RichEdit1);
  243.    RichEdit1.OnChange := OnChangeHandler;
  244. end;
  245.  
  246. procedure TForm1.SetSourceFileName(FN: String);
  247. begin
  248.   SourceFileName := FN;
  249.   Caption := Format('%s [ %s ]', [APPNAME,ExtractFileName(FN)]);
  250. end;
  251.  
  252. procedure TForm1.NewMenuItemClick(Sender: TObject);
  253. begin
  254.   SetSourceFileName( 'Untitled' );
  255.   RichEdit1.Lines.Clear;
  256. end;
  257.  
  258. procedure TForm1.LoadMenuItemClick(Sender: TObject);
  259. begin
  260.   if OpenDialog1.Execute then
  261.   begin
  262.      LoadFile( OpenDialog1.FileName );
  263.      SetSourceFileName(OpenDialog1.FileName);
  264.   end;
  265. end;
  266.  
  267. procedure TForm1.SaveAsMenuItemClick(Sender: TObject);
  268. begin
  269.   if SaveDialog1.Execute then
  270.   begin
  271.     if FileExists(SaveDialog1.FileName) then
  272.       if MessageDlg(Format('Save over existing %s?', [SaveDialog1.FileName]),
  273.         mtConfirmation, mbYesNoCancel, 0) = idYes then
  274.     begin
  275.        RichEdit1.PlainText := true;
  276.        RichEdit1.Lines.SaveToFile(SaveDialog1.FileName);
  277.        RichEdit1.PlainText := false;
  278.        SetSourceFileName(SaveDialog1.FileName);
  279.     end;
  280.   end;
  281. end;
  282.  
  283. procedure TForm1.SaveMenuItemClick(Sender: TObject);
  284. begin
  285.   if SourceFileName = 'Untitled' then
  286.     SaveAsMenuItemClick(Sender)
  287.   else
  288.   begin
  289.      RichEdit1.PlainText := true;
  290.      RichEdit1.Lines.SaveToFile(SaveDialog1.FileName);
  291.      RichEdit1.PlainText := false;
  292.   end;
  293. end;
  294.  
  295. procedure TForm1.ExitMenuItemClick(Sender: TObject);
  296. begin
  297.   if MessageDlg('Exit the Editor?',mtConfirmation,[mbYes, mbNo],0) = mrYes then
  298.     Close;
  299. end;
  300.  
  301. procedure TForm1.Print1Click(Sender: TObject);
  302. begin
  303.   if PrintDialog1.Execute then
  304.     RichEdit1.Print(SourceFileName);
  305. end;
  306.  
  307. end.
  308.