Subversion Repositories fastphp

Rev

Rev 47 | Rev 57 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit EditorMain;
  2.  
  3. {$Include 'FastPHP.inc'}
  4.  
  5. (*
  6.   This program requires
  7.   - Microsoft Internet Controls (TWebBrowser)
  8.     If you are using Delphi 10.1 Starter Edition, please import the ActiveX TLB
  9.     "Microsoft Internet Controls"
  10.   - SynEdit
  11.     You can obtain SynEdit via Embarcadero GetIt
  12. *)
  13.  
  14. // TODO: localize
  15. // TODO: wieso geht copy paste im twebbrowser nicht???
  16. // TODO: Wieso dauert webbrowser1 erste kompilierung so lange???
  17. // TODO: wieso kommt syntax fehler zweimal? einmal stderr einmal stdout?
  18. // TODO: Browser titlebar (link preview)
  19. // TODO: "jump to next/prev todo" buttons/shortcuts
  20. // TODO: "increase/decrease indent" buttons/shortcuts
  21.  
  22. // Future ideas
  23. // - code insight
  24. // - verschiedene php versionen?
  25. // - webbrowser1 nur laden, wenn man den tab anwählt?
  26. // - doppelklick auf tab soll diesen schließen
  27. // - Onlinehelp (www) aufrufen
  28. // - Let all colors be adjustable
  29. // - code in bildschirmmitte (horizontal)?
  30.  
  31. interface
  32.  
  33. uses
  34.   // TODO: "{$IFDEF USE_SHDOCVW_TLB}_TLB{$ENDIF}" does not work with Delphi 10.2
  35.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  36.   Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
  37.   SynEditHighlighter, SynHighlighterPHP, SynEdit, ShDocVw_TLB, FindReplace,
  38.   ActnList, SynEditMiscClasses, SynEditSearch, RunPHP, ImgList, SynUnicode,
  39.   System.ImageList, System.Actions, Vcl.Menus;
  40.  
  41. {.$DEFINE OnlineHelp}
  42.  
  43. type
  44.   TForm1 = class(TForm)
  45.     PageControl1: TPageControl;
  46.     PlaintextTabSheet: TTabSheet;
  47.     HtmlTabSheet: TTabSheet;
  48.     Memo2: TMemo;
  49.     WebBrowser1: TWebBrowser;
  50.     Splitter1: TSplitter;
  51.     PageControl2: TPageControl;
  52.     CodeTabsheet: TTabSheet;
  53.     HelpTabsheet: TTabSheet;
  54.     WebBrowser2: TWebBrowser;
  55.     OpenDialog1: TOpenDialog;
  56.     Panel1: TPanel;
  57.     OpenDialog3: TOpenDialog;
  58.     SynEdit1: TSynEdit;
  59.     SynPHPSyn1: TSynPHPSyn;
  60.     Panel2: TPanel;
  61.     SynEditFocusTimer: TTimer;
  62.     Button1: TButton;
  63.     Button2: TButton;
  64.     Button3: TButton;
  65.     Button4: TButton;
  66.     Button5: TButton;
  67.     Button6: TButton;
  68.     ActionList: TActionList;
  69.     ActionFind: TAction;
  70.     ActionReplace: TAction;
  71.     ActionFindNext: TAction;
  72.     ActionGoto: TAction;
  73.     ActionSave: TAction;
  74.     ActionHelp: TAction;
  75.     ActionRun: TAction;
  76.     ActionESC: TAction;
  77.     Button7: TButton;
  78.     ActionOpen: TAction;
  79.     Button8: TButton;
  80.     Button9: TButton;
  81.     ActionFindPrev: TAction;
  82.     Timer1: TTimer;
  83.     ActionSpaceToTab: TAction;
  84.     Button11: TButton;
  85.     SynEditSearch1: TSynEditSearch;
  86.     TreeView1: TTreeView;
  87.     Splitter2: TSplitter;
  88.     btnLint: TButton;
  89.     ActionLint: TAction;
  90.     ImageList1: TImageList;
  91.     RunPopup: TPopupMenu;
  92.     OpeninIDE1: TMenuItem;
  93.     ActionRunConsole: TAction;
  94.     Runinconsole1: TMenuItem;
  95.     procedure Run(Sender: TObject);
  96.     procedure RunConsole(Sender: TObject);
  97.     procedure FormShow(Sender: TObject);
  98.     procedure FormCreate(Sender: TObject);
  99.     procedure FormDestroy(Sender: TObject);
  100.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  101.     procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
  102.     procedure Memo2DblClick(Sender: TObject);
  103.     (*
  104.     {$IFDEF USE_SHDOCVW_TLB}
  105.     *)
  106.     procedure WebBrowser1BeforeNavigate2(ASender: TObject;
  107.       const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  108.       Headers: OleVariant; var Cancel: WordBool);
  109.     (*
  110.     {$ELSE}
  111.     procedure WebBrowser1BeforeNavigate2(ASender: TObject;
  112.       const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  113.       Headers: OleVariant; var Cancel: WordBool);
  114.     {$ENDIF}
  115.     *)
  116.     procedure BeforeNavigate(const URL: OleVariant; var Cancel: WordBool);
  117.     procedure SynEditFocusTimerTimer(Sender: TObject);
  118.     procedure ActionFindExecute(Sender: TObject);
  119.     procedure ActionReplaceExecute(Sender: TObject);
  120.     procedure ActionFindNextExecute(Sender: TObject);
  121.     procedure ActionGotoExecute(Sender: TObject);
  122.     procedure ActionSaveExecute(Sender: TObject);
  123.     procedure ActionHelpExecute(Sender: TObject);
  124.     procedure ActionRunExecute(Sender: TObject);
  125.     procedure ActionESCExecute(Sender: TObject);
  126.     procedure SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  127.       MousePos: TPoint; var Handled: Boolean);
  128.     procedure SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  129.       MousePos: TPoint; var Handled: Boolean);
  130.     procedure ActionOpenExecute(Sender: TObject);
  131.     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  132.     procedure Memo2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  133.     procedure ActionFindPrevExecute(Sender: TObject);
  134.     procedure SynEdit1MouseCursor(Sender: TObject;
  135.       const aLineCharPos: TBufferCoord; var aCursor: TCursor);
  136.     procedure Timer1Timer(Sender: TObject);
  137.     procedure ActionSpaceToTabExecute(Sender: TObject);
  138.     procedure TreeView1DblClick(Sender: TObject);
  139.     procedure SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X, Y,
  140.       Line: Integer; Mark: TSynEditMark);
  141.     procedure SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas;
  142.       TransientType: TTransientType);
  143.     procedure ActionLintExecute(Sender: TObject);
  144.     procedure ActionRunConsoleExecute(Sender: TObject);
  145.     procedure SynEdit1Change(Sender: TObject);
  146.   private
  147.     CurSearchTerm: string;
  148.     HlpPrevPageIndex: integer;
  149.     SrcRep: TSynEditFindReplace;
  150.     {$IFDEF OnlineHelp}
  151.     gOnlineHelpWord: string;
  152.     {$ENDIF}
  153.     procedure Help;
  154.     function MarkUpLineReference(cont: string): string;
  155.     function InputRequestCallback(var data: AnsiString): boolean;
  156.     function OutputNotifyCallback(const data: AnsiString): boolean;
  157.   protected
  158.     ChmIndex: TMemIniFile;
  159.     FScrapFile: string;
  160.     codeExplorer: TRunCodeExplorer;
  161.     procedure GotoLineNo(LineNo: integer);
  162.     function GetScrapFile: string;
  163.     procedure StartCodeExplorer;
  164.     procedure RefreshModifySign;
  165.   end;
  166.  
  167. var
  168.   Form1: TForm1;
  169.  
  170. implementation
  171.  
  172. {$R *.dfm}
  173.  
  174. {$R Cursors.res}
  175.  
  176. uses
  177.   Functions, StrUtils, WebBrowserUtils, FastPHPUtils, Math, ShellAPI, RichEdit,
  178.   FastPHPTreeView, ImageListEx, FastPHPConfig;
  179.  
  180. const
  181.   crMouseGutter = 1;
  182.  
  183. procedure TForm1.RefreshModifySign;
  184. var
  185.   tmp: string;
  186. begin
  187.   tmp := Caption;
  188.  
  189.   tmp := StringReplace(tmp, '*', '', [rfReplaceAll]);
  190.   if SynEdit1.Modified then tmp := tmp + '*';
  191.  
  192.   if Caption <> tmp then Caption := tmp;
  193. end;
  194.  
  195. procedure TForm1.ActionFindNextExecute(Sender: TObject);
  196. begin
  197.   SrcRep.FindNext;
  198. end;
  199.  
  200. procedure TForm1.ActionFindPrevExecute(Sender: TObject);
  201. begin
  202.   SrcRep.FindPrev;
  203. end;
  204.  
  205. procedure TForm1.ActionGotoExecute(Sender: TObject);
  206. var
  207.   val: string;
  208.   lineno: integer;
  209. begin
  210.   // TODO: VK_LMENU does not work! only works with AltGr but not Alt
  211.   // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
  212.  
  213.   InputQuery('Go to', 'Line number:', val);
  214.   if not TryStrToInt(val, lineno) then
  215.   begin
  216.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  217.     exit;
  218.   end;
  219.   GotoLineNo(lineno);
  220. end;
  221.  
  222. procedure TForm1.ActionHelpExecute(Sender: TObject);
  223. begin
  224.   Help;
  225.   if PageControl2.ActivePage = HelpTabsheet then
  226.     WebBrowser2.SetFocus
  227.   else if PageControl2.ActivePage = CodeTabsheet then
  228.     SynEdit1.SetFocus;
  229. end;
  230.  
  231. procedure TForm1.ActionLintExecute(Sender: TObject);
  232. begin
  233.   Run(Sender);
  234.   SynEdit1.SetFocus;
  235. end;
  236.  
  237. procedure TForm1.ActionOpenExecute(Sender: TObject);
  238. begin
  239.   If OpenDialog3.Execute then
  240.   begin
  241.     ShellExecute(0, 'open', PChar(ParamStr(0)), PChar(OpenDialog3.FileName), '', SW_NORMAL);
  242.   end;
  243. end;
  244.  
  245. procedure TForm1.ActionReplaceExecute(Sender: TObject);
  246. begin
  247.   SrcRep.ReplaceExecute;
  248. end;
  249.  
  250. procedure TForm1.ActionRunConsoleExecute(Sender: TObject);
  251. begin
  252.   RunConsole(Sender);
  253.   SynEdit1.SetFocus;
  254. end;
  255.  
  256. procedure TForm1.ActionRunExecute(Sender: TObject);
  257. begin
  258.   Run(Sender);
  259.   SynEdit1.SetFocus;
  260. end;
  261.  
  262. procedure TForm1.ActionSaveExecute(Sender: TObject);
  263. begin
  264.   SynEdit1.Lines.SaveToFile(GetScrapFile);
  265.   SynEdit1.Modified := false;
  266.   RefreshModifySign;
  267. end;
  268.  
  269. procedure TForm1.ActionSpaceToTabExecute(Sender: TObject);
  270.  
  271.     function SpacesAtBeginning(line: string): integer;
  272.     begin
  273.       result := 0;
  274.       if Trim(line) = '' then exit;
  275.       while line[result+1] = ' ' do
  276.       begin
  277.         inc(result);
  278.       end;
  279.     end;
  280.  
  281.     function GuessIndent(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}): integer;
  282.       function _Check(indent: integer): boolean;
  283.       var
  284.         i: integer;
  285.       begin
  286.         result := true;
  287.         for i := 0 to lines.Count-1 do
  288.           if SpacesAtBeginning(lines.Strings[i]) mod indent <> 0 then
  289.           begin
  290.             // ShowMessageFmt('Zeile "%s" nicht durch %d teilbar!', [lines.strings[i], indent]);
  291.             result := false;
  292.             exit;
  293.           end;
  294.       end;
  295.     var
  296.       i: integer;
  297.     begin
  298.       for i := 8 downto 2 do
  299.       begin
  300.         if _Check(i) then
  301.         begin
  302.           result := i;
  303.           exit;
  304.         end;
  305.       end;
  306.       result := -1;
  307.     end;
  308.  
  309.     procedure SpaceToTab(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}; indent: integer);
  310.     var
  311.       i, spaces: integer;
  312.     begin
  313.       for i := 0 to lines.Count-1 do
  314.       begin
  315.         spaces := SpacesAtBeginning(lines.Strings[i]);
  316.         lines.Strings[i] := StringOfChar(#9, spaces div indent) + StringOfChar(' ', spaces mod indent) + Copy(lines.Strings[i], spaces+1, Length(lines.Strings[i])-spaces);
  317.       end;
  318.     end;
  319.  
  320.     function SpacesAvailable(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}): boolean;
  321.     var
  322.       i, spaces: integer;
  323.     begin
  324.       for i := 0 to lines.Count-1 do
  325.       begin
  326.         spaces := SpacesAtBeginning(lines.Strings[i]);
  327.         if spaces > 0 then
  328.         begin
  329.           result := true;
  330.           exit;
  331.         end;
  332.       end;
  333.       result := false;
  334.       exit;
  335.     end;
  336.  
  337. var
  338.   val: string;
  339.   ind: integer;
  340. resourcestring
  341.   SNoLinesAvailable = 'No lines with spaces at the beginning available';
  342. begin
  343.   // TODO: if something is selected, only process the selected part
  344.  
  345.   if not SpacesAvailable(SynEdit1.Lines) then
  346.   begin
  347.     MessageDlg(SNoLinesAvailable, mtInformation, [mbOk], 0);
  348.     exit;
  349.   end;
  350.  
  351.   ind := GuessIndent(SynEdit1.Lines);
  352.   if ind <> -1 then val := IntToStr(ind);
  353.  
  354.   InputQuery('Spaces to tabs', 'Indent:', val); // TODO: handle CANCEL correctly...
  355.   if TryStrToInt(Trim(val), ind) then
  356.   begin
  357.     if ind = 0 then exit;
  358.     SpaceToTab(SynEdit1.Lines, ind);
  359.   end;
  360.  
  361.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  362. end;
  363.  
  364. procedure TForm1.ActionESCExecute(Sender: TObject);
  365. begin
  366.   if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
  367.      (HelpTabsheet.TabVisible) then
  368.   begin
  369.     PageControl2.ActivePageIndex := HlpPrevPageIndex;
  370.     HelpTabsheet.TabVisible := false;
  371.   end;
  372.  
  373.   // Dirty hack...
  374.   SrcRep.CloseDialogs;
  375. end;
  376.  
  377. procedure TForm1.ActionFindExecute(Sender: TObject);
  378. begin
  379.   SrcRep.FindExecute;
  380. end;
  381.  
  382. var
  383.   firstTimeBrowserLoad: boolean = true;
  384. procedure TForm1.Run(Sender: TObject);
  385. var
  386.   bakTS: TTabSheet;
  387. begin
  388.   memo2.Lines.Text := '';
  389.  
  390.   if firstTimeBrowserLoad then
  391.   begin
  392.     bakTS := PageControl1.ActivePage;
  393.     try
  394.       PageControl1.ActivePage := HtmlTabSheet; // Required for the first time, otherwise, WebBrowser1.Clear will hang
  395.       Webbrowser1.Clear;
  396.     finally
  397.       PageControl1.ActivePage := bakTS;
  398.     end;
  399.     firstTimeBrowserLoad := false;
  400.   end
  401.   else
  402.     Webbrowser1.Clear;
  403.  
  404.   Screen.Cursor := crHourGlass;
  405.   Application.ProcessMessages;
  406.  
  407.   try
  408.     ActionSave.Execute; // TODO: if it is not the scrap file: do not save the file, since the user did not intended to save... better create a temporary file and run it instead.
  409.  
  410.     memo2.Lines.Text := RunPHPScript(GetScrapFile, Sender=ActionLint, False);
  411.  
  412.     Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
  413.  
  414.     if IsTextHTML(memo2.lines.text) then
  415.       PageControl1.ActivePage := HtmlTabSheet
  416.     else
  417.       PageControl1.ActivePage := PlaintextTabSheet;
  418.   finally
  419.     Screen.Cursor := crDefault;
  420.   end;
  421. end;
  422.  
  423. procedure TForm1.RunConsole(Sender: TObject);
  424. begin
  425.   ActionSave.Execute; // TODO: if it is not the scrap file: do not save the file, since the user did not intended to save... better create a temporary file and run it instead.
  426.   RunPHPScript(GetScrapFile, Sender=ActionLint, True);
  427. end;
  428.  
  429. procedure TForm1.SynEdit1Change(Sender: TObject);
  430. begin
  431.   RefreshModifySign;
  432. end;
  433.  
  434. procedure TForm1.SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X,
  435.   Y, Line: Integer; Mark: TSynEditMark);
  436. begin
  437.   (*
  438.   TSynEdit(Sender).CaretX := 1;
  439.   TSynEdit(Sender).CaretY := Line;
  440.   TSynEdit(Sender).SelLength := Length(TSynEdit(Sender).LineText);
  441.   *)
  442. end;
  443.  
  444. procedure TForm1.SynEdit1MouseCursor(Sender: TObject; const aLineCharPos: TBufferCoord; var aCursor: TCursor);
  445. {$IFDEF OnlineHelp}
  446. var
  447.   Line: Integer;
  448.   Column: Integer;
  449.   word: string;
  450. begin
  451.   Line  := aLineCharPos.Line-1;
  452.   Column := aLineCharPos.Char-1;
  453.   word := GetWordUnderPos(TSynEdit(Sender), Line, Column);
  454.   if word <> gOnlineHelpWord then
  455.   begin
  456.     gOnlineHelpWord := word;
  457.     Timer1.Enabled := false;
  458.     Timer1.Enabled := true;
  459.   end;
  460. {$ELSE}
  461. begin
  462. {$ENDIF}
  463. end;
  464.  
  465. procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  466.   MousePos: TPoint; var Handled: Boolean);
  467. begin
  468.   if ssCtrl in Shift then
  469.   begin
  470.     SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
  471.     Handled := true;
  472.   end
  473.   else Handled := false;
  474. end;
  475.  
  476. procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  477.   MousePos: TPoint; var Handled: Boolean);
  478. begin
  479.   if ssCtrl in Shift then
  480.   begin
  481.     SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
  482.     Handled := true;
  483.   end
  484.   else Handled := false;
  485. end;
  486.  
  487. procedure TForm1.SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType);
  488. var
  489.   Editor: TSynEdit;
  490.   OpenChars: array of WideChar;//[0..2] of WideChar=();
  491.   CloseChars: array of WideChar;//[0..2] of WideChar=();
  492.  
  493.   function IsCharBracket(AChar: WideChar): Boolean;
  494.   begin
  495.     case AChar of
  496.       '{','[','(','<','}',']',')','>':
  497.         Result := True;
  498.       else
  499.         Result := False;
  500.     end;
  501.   end;
  502.  
  503.   function CharToPixels(P: TBufferCoord): TPoint;
  504.   begin
  505.     Result := Editor.RowColumnToPixels(Editor.BufferToDisplayPos(P));
  506.   end;
  507.  
  508. const
  509.   COLOR_FG = clGreen;
  510.   COLOR_BG = clLime;
  511. var
  512.   P: TBufferCoord;
  513.   Pix: TPoint;
  514.   D: TDisplayCoord;
  515.   S: UnicodeString;
  516.   I: Integer;
  517.   Attri: TSynHighlighterAttributes;
  518.   ArrayLength: Integer;
  519.   start: Integer;
  520.   TmpCharA, TmpCharB: WideChar;
  521. begin      
  522.   // Source: https://github.com/SynEdit/SynEdit/blob/master/Demos/OnPaintTransientDemo/Unit1.pas
  523.  
  524.   if TSynEdit(Sender).SelAvail then exit;
  525.   Editor := TSynEdit(Sender);
  526.   ArrayLength:= 3;
  527.  
  528.   (*
  529.   if (Editor.Highlighter = shHTML) or (Editor.Highlighter = shXML) then
  530.     inc(ArrayLength);
  531.   *)
  532.  
  533.   SetLength(OpenChars, ArrayLength);
  534.   SetLength(CloseChars, ArrayLength);
  535.   for i := 0 to ArrayLength - 1 do
  536.   begin
  537.     case i of
  538.       0: begin OpenChars[i] := '('; CloseChars[i] := ')'; end;
  539.       1: begin OpenChars[i] := '{'; CloseChars[i] := '}'; end;
  540.       2: begin OpenChars[i] := '['; CloseChars[i] := ']'; end;
  541.       3: begin OpenChars[i] := '<'; CloseChars[i] := '>'; end;
  542.     end;
  543.   end;
  544.  
  545.   P := Editor.CaretXY;
  546.   D := Editor.DisplayXY;
  547.  
  548.   Start := Editor.SelStart;
  549.  
  550.   if (Start > 0) and (Start <= length(Editor.Text)) then
  551.     TmpCharA := Editor.Text[Start]
  552.   else
  553.     TmpCharA := #0;
  554.  
  555.   if (Start > 0){Added by VTS} and (Start < length(Editor.Text)) then
  556.     TmpCharB := Editor.Text[Start + 1]
  557.   else
  558.     TmpCharB := #0;
  559.  
  560.   if not IsCharBracket(TmpCharA) and not IsCharBracket(TmpCharB) then exit;
  561.   S := TmpCharB;
  562.   if not IsCharBracket(TmpCharB) then
  563.   begin
  564.     P.Char := P.Char - 1;
  565.     S := TmpCharA;
  566.   end;
  567.   Editor.GetHighlighterAttriAtRowCol(P, S, Attri);
  568.  
  569.   if (Editor.Highlighter.SymbolAttribute = Attri) then
  570.   begin
  571.     for i := low(OpenChars) to High(OpenChars) do
  572.     begin
  573.       if (S = OpenChars[i]) or (S = CloseChars[i]) then
  574.       begin
  575.         Pix := CharToPixels(P);
  576.  
  577.         Editor.Canvas.Brush.Style := bsSolid;//Clear;
  578.         Editor.Canvas.Font.Assign(Editor.Font);
  579.         Editor.Canvas.Font.Style := Attri.Style;
  580.  
  581.         if (TransientType = ttAfter) then
  582.         begin
  583.           Editor.Canvas.Font.Color := COLOR_FG;
  584.           Editor.Canvas.Brush.Color := COLOR_BG;
  585.         end
  586.         else
  587.         begin
  588.           Editor.Canvas.Font.Color := Attri.Foreground;
  589.           Editor.Canvas.Brush.Color := Attri.Background;
  590.         end;
  591.         if Editor.Canvas.Font.Color = clNone then
  592.           Editor.Canvas.Font.Color := Editor.Font.Color;
  593.         if Editor.Canvas.Brush.Color = clNone then
  594.           Editor.Canvas.Brush.Color := Editor.Color;
  595.  
  596.         Editor.Canvas.TextOut(Pix.X, Pix.Y, S);
  597.         P := Editor.GetMatchingBracketEx(P);
  598.  
  599.         if (P.Char > 0) and (P.Line > 0) then
  600.         begin
  601.           Pix := CharToPixels(P);
  602.           if Pix.X > Editor.Gutter.Width then
  603.           begin
  604.             {$REGION 'Added by ViaThinkSoft'}
  605.             if (TransientType = ttAfter) then
  606.             begin
  607.               Editor.Canvas.Font.Color := COLOR_FG;
  608.               Editor.Canvas.Brush.Color := COLOR_BG;
  609.             end
  610.             else
  611.             begin
  612.               Editor.Canvas.Font.Color := Attri.Foreground;
  613.               Editor.Canvas.Brush.Color := Attri.Background;
  614.             end;
  615.             if Editor.Canvas.Font.Color = clNone then
  616.               Editor.Canvas.Font.Color := Editor.Font.Color;
  617.             if Editor.Canvas.Brush.Color = clNone then
  618.               Editor.Canvas.Brush.Color := Editor.Color;
  619.             {$ENDREGION}
  620.             if S = OpenChars[i] then
  621.               Editor.Canvas.TextOut(Pix.X, Pix.Y, CloseChars[i])
  622.             else Editor.Canvas.TextOut(Pix.X, Pix.Y, OpenChars[i]);
  623.           end;
  624.         end;
  625.       end;
  626.     end;
  627.     Editor.Canvas.Brush.Style := bsSolid;
  628.   end;
  629. end;
  630.  
  631. procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
  632. begin
  633.   SynEditFocusTimer.Enabled := false;
  634.   Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
  635.   SynEdit1.SetFocus;
  636. end;
  637.  
  638. procedure TForm1.Timer1Timer(Sender: TObject);
  639. begin
  640.   {$IFDEF OnlineHelp}
  641.   Timer1.Enabled := false;
  642.  
  643.   // TODO: Insert a small online help hint
  644.   //Caption := gOnlineHelpWord;
  645.   {$ENDIF}
  646. end;
  647.  
  648. procedure TForm1.TreeView1DblClick(Sender: TObject);
  649. var
  650.   tn: TTreeNode;
  651.   lineNo: integer;
  652. begin
  653.   tn := TTreeView(Sender).Selected;
  654.   if tn = nil then exit;
  655.   lineNo := Integer(tn.Data);
  656.   if lineNo > 0 then GotoLineNo(lineNo);
  657. end;
  658.  
  659. (*
  660. {$IFDEF USE_SHDOCVW_TLB}
  661. *)
  662. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  663.   const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  664.   Headers: OleVariant; var Cancel: WordBool);
  665. begin
  666.   BeforeNavigate(URL, Cancel);
  667. end;
  668. (*
  669. {$ELSE}
  670. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  671.   const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  672.   Headers: OleVariant; var Cancel: WordBool);
  673. begin
  674.   BeforeNavigate(URL, Cancel);
  675. end;
  676. {$ENDIF}
  677. *)
  678.  
  679. procedure TForm1.BeforeNavigate(const URL: OleVariant; var Cancel: WordBool);
  680. var
  681.   s, myURL: string;
  682.   lineno: integer;
  683.   p: integer;
  684. begin
  685.   {$REGION 'Line number references (PHP errors and warnings)'}
  686.   if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
  687.   begin
  688.     try
  689.       s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
  690.       if not TryStrToInt(s, lineno) then exit;
  691.       GotoLineNo(lineno);
  692.       SynEditFocusTimer.Enabled := true;
  693.     finally
  694.       Cancel := true;
  695.     end;
  696.     Exit;
  697.   end;
  698.   {$ENDREGION}
  699.  
  700.   {$REGION 'Intelligent browser (executes PHP scripts)'}
  701.   if URL <> 'about:blank' then
  702.   begin
  703.     myUrl := URL;
  704.  
  705.     p := Pos('?', myUrl);
  706.     if p >= 1 then myURL := copy(myURL, 1, p-1);
  707.  
  708.     // TODO: myURL urldecode
  709.     // TODO: maybe we could even open that file in the editor!
  710.  
  711.     if FileExists(myURL) and (EndsText('.php', myURL) or EndsText('.php3', myURL) or EndsText('.php4', myURL) or EndsText('.php5', myURL) or EndsText('.phps', myURL)) then
  712.     begin
  713.       WebBrowser1.LoadHTML(RunPHPScript(myURL), myUrl);
  714.       Cancel := true;
  715.     end;
  716.   end;
  717.   {$ENDREGION}
  718. end;
  719.  
  720. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  721. begin
  722.   TFastPHPConfig.FontSize := SynEdit1.Font.Size;
  723. end;
  724.  
  725. procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  726. var
  727.   r: integer;
  728. begin
  729.   if SynEdit1.Modified then
  730.   begin
  731.     if ParamStr(1) <> '' then
  732.     begin
  733.       r := MessageDlg('Do you want to save?', mtConfirmation, mbYesNoCancel, 0);
  734.       if r = mrCancel then
  735.       begin
  736.         CanClose := false;
  737.         Exit;
  738.       end
  739.       else if r = mrYes then
  740.       begin
  741.         ActionSave.Execute;
  742.         CanClose := true;
  743.       end;
  744.     end
  745.     else
  746.     begin
  747.       ActionSave.Execute;
  748.       CanClose := true;
  749.     end;
  750.   end;
  751. end;
  752.  
  753. procedure TForm1.FormCreate(Sender: TObject);
  754. var
  755.   exeDir: string;
  756. begin
  757.   HlpPrevPageIndex := -1;
  758.   CurSearchTerm := '';
  759.   Caption := Caption + ' - ' + GetScrapFile;
  760.   SrcRep := TSynEditFindReplace.Create(self);
  761.   SrcRep.Editor := SynEdit1;
  762.   SynEdit1.Gutter.Gradient := HighColorWindows;
  763.  
  764.   Screen.Cursors[crMouseGutter] := LoadCursor(hInstance, 'MOUSEGUTTER');
  765.   SynEdit1.Gutter.Cursor := crMouseGutter;
  766.  
  767.   exeDir := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
  768.   if FileExists(exeDir + 'codeexplorer.bmp') then ImageList1.LoadAndSplitImages(exeDir + 'codeexplorer.bmp');
  769. end;
  770.  
  771. procedure TForm1.FormDestroy(Sender: TObject);
  772. begin
  773.   if Assigned(ChmIndex) then
  774.   begin
  775.     FreeAndNil(ChmIndex);
  776.   end;
  777.   FreeAndNil(SrcRep);
  778.  
  779.   if Assigned(codeExplorer) then
  780.   begin
  781.     codeExplorer.Terminate;
  782.     codeExplorer.WaitFor;
  783.     FreeAndNil(codeExplorer);
  784.   end;
  785. end;
  786.  
  787. procedure TForm1.FormShow(Sender: TObject);
  788. var
  789.   ScrapFile: string;
  790.   tmpFontSize: integer;
  791. begin
  792.   ScrapFile := GetScrapFile;
  793.   if ScrapFile = '' then
  794.   begin
  795.     Application.Terminate; // Close;
  796.     exit;
  797.   end;
  798.   if FileExists(ScrapFile) then
  799.     SynEdit1.Lines.LoadFromFile(ScrapFile)
  800.   else
  801.     SynEdit1.Lines.Clear;
  802.  
  803.   PageControl1.ActivePage := PlaintextTabSheet;
  804.  
  805.   PageControl2.ActivePage := CodeTabsheet;
  806.   HelpTabsheet.TabVisible := false;
  807.  
  808.   tmpFontSize := TFastPHPConfig.FontSize;
  809.   if tmpFontSize <> -1 then SynEdit1.Font.Size := tmpFontSize;
  810.   SynEdit1.SetFocus;
  811.  
  812.   DoubleBuffered := true;
  813.   StartCodeExplorer;
  814. end;
  815.  
  816. procedure TForm1.StartCodeExplorer;
  817. begin
  818.   codeExplorer := TRunCodeExplorer.Create(true);
  819.   codeExplorer.InputRequestCallback := InputRequestCallback;
  820.   codeExplorer.OutputNotifyCallback := OutputNotifyCallback;
  821.   codeExplorer.PhpExe := GetPHPExe;
  822.   codeExplorer.PhpFile := IncludeTrailingPathDelimiter(ExtractFileDir(Application.ExeName)) + 'codeexplorer.php'; // GetScrapFile;
  823.   codeExplorer.WorkDir := ExtractFileDir(Application.ExeName);
  824.   codeExplorer.Resume;
  825. end;
  826.  
  827. function TForm1.GetScrapFile: string;
  828. var
  829.   tmpPath: string;
  830. begin
  831.   if FScrapFile <> '' then
  832.   begin
  833.     result := FScrapFile;
  834.     exit;
  835.   end;
  836.  
  837.   if ParamStr(1) <> '' then
  838.   begin
  839.     // Program was started with a filename
  840.  
  841.     result := ParamStr(1);
  842.  
  843.     if not FileExists(result) then
  844.     begin
  845.       case MessageDlg(Format('File %s does not exist. Create it?', [result]), mtConfirmation, mbYesNoCancel, 0) of
  846.         mrYes:
  847.           try
  848.             SynEdit1.Lines.SaveToFile(result);
  849.           except
  850.             on E: Exception do
  851.             begin
  852.               MessageDlg(E.Message, mtError, [mbOk], 0);
  853.               Application.Terminate;
  854.               result := '';
  855.               exit;
  856.             end;
  857.           end;
  858.         mrNo:
  859.           begin
  860.             Application.Terminate;
  861.             result := '';
  862.             exit;
  863.           end;
  864.         mrCancel:
  865.           begin
  866.             Application.Terminate;
  867.             result := '';
  868.             exit;
  869.           end;
  870.       end;
  871.     end;
  872.   end
  873.   else
  874.   begin
  875.     // Program is started without filename -> use scrap file
  876.  
  877.     result := TFastPHPConfig.ScrapFile;
  878.  
  879.     if not FileExists(result) then
  880.     begin
  881.       repeat
  882.         {$REGION 'Determinate opendialog initial directory'}
  883.         if result <> '' then
  884.         begin
  885.           tmpPath := ExtractFilePath(result);
  886.           if DirectoryExists(tmpPath) then
  887.           begin
  888.             OpenDialog3.InitialDir := tmpPath;
  889.             OpenDialog3.FileName := Result;
  890.           end
  891.           else
  892.           begin
  893.             OpenDialog3.InitialDir := GetMyDocumentsFolder;
  894.           end;
  895.         end
  896.         else
  897.         begin
  898.           OpenDialog3.InitialDir := GetMyDocumentsFolder;
  899.         end;
  900.         {$ENDREGION}
  901.  
  902.         if not OpenDialog3.Execute then
  903.         begin
  904.           Application.Terminate;
  905.           result := '';
  906.           exit;
  907.         end;
  908.  
  909.         if not DirectoryExists(ExtractFilePath(OpenDialog3.FileName)) then
  910.         begin
  911.           MessageDlg('Path does not exist! Please try again.', mtWarning, [mbOk], 0);
  912.         end
  913.         else
  914.         begin
  915.           result := OpenDialog3.FileName;
  916.         end;
  917.       until result <> '';
  918.  
  919.       if not FileExists(result) then
  920.       begin
  921.         try
  922.           // Try saving the file; check if we have permissions
  923.           //SynEdit1.Lines.Clear;
  924.           SynEdit1.Lines.SaveToFile(result);
  925.         except
  926.           on E: Exception do
  927.           begin
  928.             MessageDlg(E.Message, mtError, [mbOk], 0);
  929.             Application.Terminate;
  930.             result := '';
  931.             exit;
  932.           end;
  933.         end;
  934.       end;
  935.  
  936.       TFastPHPConfig.ScrapFile := result;
  937.       FScrapFile := result;
  938.     end;
  939.   end;
  940. end;
  941.  
  942. procedure TForm1.Help;
  943. var
  944.   IndexFile, chmFile, w, OriginalWord, url: string;
  945.   internalHtmlFile: string;
  946. begin
  947.   if not Assigned(ChmIndex) then
  948.   begin
  949.     IndexFile := TFastPHPConfig.HelpIndex;
  950.     IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
  951.     if FileExists(IndexFile) then
  952.     begin
  953.       ChmIndex := TMemIniFile.Create(IndexFile);
  954.     end;
  955.   end;
  956.  
  957.   if Assigned(ChmIndex) then
  958.   begin
  959.     IndexFile := TFastPHPConfig.HelpIndex;
  960.     // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
  961.  
  962.     chmFile := ChangeFileExt(IndexFile, '.chm');
  963.     if not FileExists(chmFile) then
  964.     begin
  965.       FreeAndNil(ChmIndex);
  966.     end;
  967.   end;
  968.  
  969.   if not Assigned(ChmIndex) then
  970.   begin
  971.     if not OpenDialog1.Execute then exit;
  972.  
  973.     chmFile := OpenDialog1.FileName;
  974.     if not FileExists(chmFile) then exit;
  975.  
  976.     IndexFile := ChangeFileExt(chmFile, '.ini');
  977.  
  978.     if not FileExists(IndexFile) then
  979.     begin
  980.       Panel1.Align := alClient;
  981.       Panel1.Visible := true;
  982.       Panel1.BringToFront;
  983.       Screen.Cursor := crHourGlass;
  984.       Application.ProcessMessages;
  985.       try
  986.         if not ParseCHM(chmFile) then
  987.         begin
  988.           MessageDlg('The CHM file is not a valid PHP documentation. Cannot use help.', mtError, [mbOk], 0);
  989.           exit;
  990.         end;
  991.       finally
  992.         Screen.Cursor := crDefault;
  993.         Panel1.Visible := false;
  994.       end;
  995.  
  996.       if not FileExists(IndexFile) then
  997.       begin
  998.         MessageDlg('Unknown error. Cannot use help.', mtError, [mbOk], 0);
  999.         exit;
  1000.       end;
  1001.     end;
  1002.  
  1003.     TFastPHPConfig.HelpIndex := IndexFile;
  1004.  
  1005.     ChmIndex := TMemIniFile.Create(IndexFile);
  1006.   end;
  1007.  
  1008.   w := GetWordUnderCaret(SynEdit1);
  1009.   if w = '' then exit;
  1010.   {$IFDEF UNICODE}
  1011.   if CharInSet(w[1], ['0'..'9']) then exit;
  1012.   {$ELSE}
  1013.   if w[1] in ['0'..'9'] then exit;
  1014.   {$ENDIF}
  1015.  
  1016.   Originalword := w;
  1017. //  w := StringReplace(w, '_', '-', [rfReplaceAll]);
  1018.   w := LowerCase(w);
  1019.   CurSearchTerm := w;
  1020.  
  1021.   internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, '');
  1022.   if internalHtmlFile = '' then
  1023.   begin
  1024.     HelpTabsheet.TabVisible := false;
  1025.     HlpPrevPageIndex := -1;
  1026.     ShowMessageFmt('No help for "%s" available', [Originalword]);
  1027.     Exit;
  1028.   end;
  1029.  
  1030.   url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
  1031.  
  1032.   HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
  1033.   HelpTabsheet.TabVisible := true;
  1034.   PageControl2.ActivePage := HelpTabsheet;
  1035.   WebBrowser2.Navigate(url);
  1036.   WebBrowser2.Wait;
  1037. end;
  1038.  
  1039. procedure TForm1.GotoLineNo(LineNo:integer);
  1040. var
  1041.   line: string;
  1042.   i: integer;
  1043. begin
  1044.   SynEdit1.GotoLineAndCenter(LineNo);
  1045.  
  1046.   // Skip indent
  1047.   line := SynEdit1.Lines[SynEdit1.CaretY];
  1048.   for i := 1 to Length(line) do
  1049.   begin
  1050.     {$IFDEF UNICODE}
  1051.     if not CharInSet(line[i], [' ', #9]) then
  1052.     {$ELSE}
  1053.     if not (line[i] in [' ', #9]) then
  1054.     {$ENDIF}
  1055.     begin
  1056.       SynEdit1.CaretX := i-1;
  1057.       break;
  1058.     end;
  1059.   end;
  1060.  
  1061.   PageControl2.ActivePage := CodeTabsheet;
  1062.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  1063. end;
  1064.  
  1065. procedure TForm1.PageControl2Changing(Sender: TObject;
  1066.   var AllowChange: Boolean);
  1067. begin
  1068.   if PageControl2.ActivePage = HelpTabsheet then
  1069.     HlpPrevPageIndex := -1
  1070.   else
  1071.     HlpPrevPageIndex := PageControl2.ActivePageIndex;
  1072.  
  1073.   AllowChange := true;
  1074. end;
  1075.  
  1076. procedure TForm1.Memo2DblClick(Sender: TObject);
  1077. var
  1078.   line: string;
  1079.  
  1080.   procedure _process(toFind: string);
  1081.   var
  1082.     p, lineno: integer;
  1083.   begin
  1084.     if FileSystemCaseSensitive then
  1085.       p := Pos(toFind, line)
  1086.     else
  1087.       p := Pos(LowerCase(toFind), LowerCase(line));
  1088.     if p <> 0 then
  1089.     begin
  1090.       line := copy(line, p+length(toFind), 99);
  1091.       if not TryStrToInt(line, lineno) then exit;
  1092.       GotoLineNo(lineno);
  1093.     end;
  1094.   end;
  1095.  
  1096. begin
  1097.   line := memo2.Lines.Strings[Memo2.CaretPos.Y];
  1098.  
  1099.   {$REGION 'Possibility 1: filename.php:lineno'}
  1100.   _process(ExtractFileName(GetScrapFile) + ':');
  1101.   {$ENDREGION}
  1102.  
  1103.   {$REGION 'Possibility 2: on line xx'}
  1104.   _process(ExtractFileName(GetScrapFile) + ' on line ');
  1105.   {$ENDREGION}
  1106. end;
  1107.  
  1108. procedure TForm1.Memo2KeyDown(Sender: TObject; var Key: Word;
  1109.   Shift: TShiftState);
  1110. begin
  1111.   if ((ssCtrl in Shift) and (Key = 65)) then TMemo(Sender).SelectAll;
  1112. end;
  1113.  
  1114. function TForm1.MarkUpLineReference(cont: string): string;
  1115.  
  1116.   procedure _process(toFind: string);
  1117.   var
  1118.     p, a, b: integer;
  1119.     num: integer;
  1120.     insert_a, insert_b: string;
  1121.   begin
  1122.     if FileSystemCaseSensitive then
  1123.       p := Pos(toFind, cont)
  1124.     else
  1125.       p := Pos(LowerCase(toFind), LowerCase(cont));
  1126.     while p >= 1 do
  1127.     begin
  1128.       a := p;
  1129.       b := p + length(toFind);
  1130.       num := 0;
  1131.       {$IFDEF UNICODE}
  1132.       while CharInSet(cont[b], ['0'..'9']) do
  1133.       {$ELSE}
  1134.       while cont[b] in ['0'..'9'] do
  1135.       {$ENDIF}
  1136.       begin
  1137.         num := num*10 + StrToInt(cont[b]);
  1138.         inc(b);
  1139.       end;
  1140.  
  1141.       insert_b := '</a>';
  1142.       insert_a := '<a href="' + FASTPHP_GOTO_URI_PREFIX + IntToStr(num) + '">';
  1143.  
  1144.       insert(insert_b, cont, b);
  1145.       insert(insert_a, cont, a);
  1146.  
  1147.       p := b + Length(insert_a) + Length(insert_b);
  1148.  
  1149.       p := PosEx(toFind, cont, p+1);
  1150.     end;
  1151.   end;
  1152.  
  1153. begin
  1154.   {$REGION 'Possibility 1: filename.php:lineno'}
  1155.   _process(ExtractFileName(GetScrapFile) + ':');
  1156.   {$ENDREGION}
  1157.  
  1158.   {$REGION 'Possibility 2: on line xx'}
  1159.   _process(ExtractFileName(GetScrapFile) + ' on line ');
  1160.   {$ENDREGION}
  1161.  
  1162.   result := cont;
  1163. end;
  1164.  
  1165. function TForm1.InputRequestCallback(var data: AnsiString): boolean;
  1166. begin
  1167.   data := UTF8Encode(SynEdit1.Text);
  1168.   result := true;
  1169. end;
  1170.  
  1171. function TForm1.OutputNotifyCallback(const data: AnsiString): boolean;
  1172. begin
  1173.   result := TreeView1.FillWithFastPHPData(data);
  1174. end;
  1175.  
  1176. end.
  1177.