Subversion Repositories fastphp

Rev

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