Subversion Repositories fastphp

Rev

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