Subversion Repositories fastphp

Rev

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