Subversion Repositories fastphp

Rev

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

  1. unit EditorMain;
  2.  
  3. (*
  4.   This program requires
  5.   - Microsoft Internet Controls (TWebBrowser)
  6.     If you are using Delphi 10.1 Starter Edition, please import the ActiveX TLB
  7.     "Microsoft Internet Controls"
  8.   - SynEdit
  9.     You can obtain SynEdit via Embarcadero GetIt
  10. *)
  11.  
  12. // TODO: localize
  13. // TODO: wieso geht copy paste im twebbrowser nicht???
  14. // Wieso dauert webbrowser1 erste kompilierung so lange???
  15. // TODO: wieso kommt syntax fehler zweimal? einmal stderr einmal stdout?
  16. // TODO: Browser titlebar (link preview)
  17.  
  18. // Future ideas
  19. // - ToDo list
  20. // - verschiedene php versionen?
  21. // - webbrowser1 nur laden, wenn man den tab anwählt?
  22. // - doppelklick auf tab soll diesen schließen
  23. // - Onlinehelp (www) aufrufen
  24. // - Let all colors be adjustable
  25. // - code in bildschirmmitte?
  26.  
  27. interface
  28.  
  29. uses
  30.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  31.   Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
  32.   SynEditHighlighter, SynHighlighterPHP, SynEdit, SHDocVw_TLB, FindReplace,
  33.   System.Actions, Vcl.ActnList;
  34.  
  35. type
  36.   TForm1 = class(TForm)
  37.     PageControl1: TPageControl;
  38.     PlaintextTabSheet: TTabSheet;
  39.     HtmlTabSheet: TTabSheet;
  40.     Memo2: TMemo;
  41.     WebBrowser1: TWebBrowser;
  42.     Splitter1: TSplitter;
  43.     PageControl2: TPageControl;
  44.     TabSheet3: TTabSheet;
  45.     HelpTabsheet: TTabSheet;
  46.     WebBrowser2: TWebBrowser;
  47.     OpenDialog1: TOpenDialog;
  48.     Panel1: TPanel;
  49.     OpenDialog3: TOpenDialog;
  50.     SynEdit1: TSynEdit;
  51.     SynPHPSyn1: TSynPHPSyn;
  52.     Panel2: TPanel;
  53.     SynEditFocusTimer: TTimer;
  54.     Button1: TButton;
  55.     Button2: TButton;
  56.     Button3: TButton;
  57.     Button4: TButton;
  58.     Button5: TButton;
  59.     Button6: TButton;
  60.     ActionList: TActionList;
  61.     ActionFind: TAction;
  62.     ActionReplace: TAction;
  63.     ActionFindNext: TAction;
  64.     ActionGoto: TAction;
  65.     ActionSave: TAction;
  66.     ActionHelp: TAction;
  67.     ActionRun: TAction;
  68.     ActionESC: TAction;
  69.     Button7: TButton;
  70.     ActionOpen: TAction;
  71.     Button8: TButton;
  72.     procedure Run(Sender: TObject);
  73.     procedure FormShow(Sender: TObject);
  74.     procedure FormCreate(Sender: TObject);
  75.     procedure FormDestroy(Sender: TObject);
  76.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  77.     procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
  78.     procedure Memo2DblClick(Sender: TObject);
  79.     procedure WebBrowser1BeforeNavigate2(ASender: TObject;
  80.       const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  81.       Headers: OleVariant; var Cancel: WordBool);
  82.     procedure SynEditFocusTimerTimer(Sender: TObject);
  83.     procedure ActionFindExecute(Sender: TObject);
  84.     procedure ActionReplaceExecute(Sender: TObject);
  85.     procedure ActionFindNextExecute(Sender: TObject);
  86.     procedure ActionGotoExecute(Sender: TObject);
  87.     procedure ActionSaveExecute(Sender: TObject);
  88.     procedure ActionHelpExecute(Sender: TObject);
  89.     procedure ActionRunExecute(Sender: TObject);
  90.     procedure ActionESCExecute(Sender: TObject);
  91.     procedure SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  92.       MousePos: TPoint; var Handled: Boolean);
  93.     procedure SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  94.       MousePos: TPoint; var Handled: Boolean);
  95.     procedure ActionOpenExecute(Sender: TObject);
  96.     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  97.   private
  98.     CurSearchTerm: string;
  99.     HlpPrevPageIndex: integer;
  100.     SrcRep: TFindReplace;
  101.     procedure Help;
  102.     function MarkUpLineReference(cont: string): string;
  103.   protected
  104.     ChmIndex: TMemIniFile;
  105.     procedure GotoLineNo(LineNo:integer);
  106.     function GetScrapFile: string;
  107.   end;
  108.  
  109. var
  110.   Form1: TForm1;
  111.  
  112. implementation
  113.  
  114. {$R *.dfm}
  115.  
  116. uses
  117.   Functions, StrUtils, WebBrowserUtils, FastPHPUtils, Math, ShellAPI;
  118.  
  119. // TODO: FindPrev ?
  120. procedure TForm1.ActionFindNextExecute(Sender: TObject);
  121. begin
  122.   SrcRep.FindNext;
  123. end;
  124.  
  125. procedure TForm1.ActionGotoExecute(Sender: TObject);
  126. var
  127.   val: string;
  128.   lineno: integer;
  129. begin
  130.   // TODO: VK_LMENU does not work! only works with AltGr but not Alt
  131.   // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
  132.  
  133.   InputQuery('Go to', 'Line number:', val);
  134.   if not TryStrToInt(val, lineno) then
  135.   begin
  136.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  137.     exit;
  138.   end;
  139.   GotoLineNo(lineno);
  140. end;
  141.  
  142. procedure TForm1.ActionHelpExecute(Sender: TObject);
  143. begin
  144.   Help;
  145.   if PageControl2.ActivePage = HelpTabsheet then
  146.     WebBrowser2.SetFocus
  147.   else if PageControl2.ActivePage = TabSheet3{Scrap} then
  148.     SynEdit1.SetFocus;
  149. end;
  150.  
  151. procedure TForm1.ActionOpenExecute(Sender: TObject);
  152. begin
  153.   If OpenDialog3.Execute then
  154.   begin
  155.     ShellExecute(0, 'open', PChar(ParamStr(0)), PChar(OpenDialog3.FileName), '', SW_NORMAL);
  156.   end;
  157. end;
  158.  
  159. procedure TForm1.ActionReplaceExecute(Sender: TObject);
  160. begin
  161.   SrcRep.ReplaceExecute;
  162. end;
  163.  
  164. procedure TForm1.ActionRunExecute(Sender: TObject);
  165. begin
  166.   Run(Sender);
  167.   SynEdit1.SetFocus;
  168. end;
  169.  
  170. procedure TForm1.ActionSaveExecute(Sender: TObject);
  171. begin
  172.   SynEdit1.Lines.SaveToFile(GetScrapFile);
  173. end;
  174.  
  175. procedure TForm1.ActionESCExecute(Sender: TObject);
  176. begin
  177.   if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
  178.      (HelpTabsheet.TabVisible) then
  179.   begin
  180.     PageControl2.ActivePageIndex := HlpPrevPageIndex;
  181.     HelpTabsheet.TabVisible := false;
  182.   end;
  183.  
  184.   // Dirty hack...
  185.   SrcRep._FindDialog.CloseDialog;
  186.   SrcRep._ReplaceDialog.CloseDialog;
  187. end;
  188.  
  189. procedure TForm1.ActionFindExecute(Sender: TObject);
  190. begin
  191.   SrcRep.FindExecute;
  192. end;
  193.  
  194. procedure TForm1.Run(Sender: TObject);
  195. begin
  196.   memo2.Lines.Text := '';
  197.   Webbrowser1.Clear;
  198.   Screen.Cursor := crHourGlass;
  199.   Application.ProcessMessages;
  200.  
  201.   try
  202.     SynEdit1.Lines.SaveToFile(GetScrapFile);
  203.  
  204.     memo2.Lines.Text := RunPHPScript(GetScrapFile);
  205.  
  206.     Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
  207.  
  208.     if IsTextHTML(memo2.lines.text) then
  209.       PageControl1.ActivePage := HtmlTabSheet
  210.     else
  211.       PageControl1.ActivePage := PlaintextTabSheet;
  212.   finally
  213.     Screen.Cursor := crDefault;
  214.   end;
  215. end;
  216.  
  217. procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  218.   MousePos: TPoint; var Handled: Boolean);
  219. begin
  220.   if ssCtrl in Shift then
  221.   begin
  222.     SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
  223.   end;
  224. end;
  225.  
  226. procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  227.   MousePos: TPoint; var Handled: Boolean);
  228. begin
  229.   if ssCtrl in Shift then
  230.   begin
  231.     SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
  232.   end;
  233. end;
  234.  
  235. procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
  236. begin
  237.   SynEditFocusTimer.Enabled := false;
  238.   Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
  239.   SynEdit1.SetFocus;
  240. end;
  241.  
  242. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  243.   const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  244.   Headers: OleVariant; var Cancel: WordBool);
  245. var
  246.   s, myURL: string;
  247.   lineno: integer;
  248.   p: integer;
  249. begin
  250.   {$REGION 'Line number references (PHP errors and warnings)'}
  251.   if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
  252.   begin
  253.     try
  254.       s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
  255.       if not TryStrToInt(s, lineno) then exit;
  256.       GotoLineNo(lineno);
  257.       SynEditFocusTimer.Enabled := true;
  258.     finally
  259.       Cancel := true;
  260.     end;
  261.     Exit;
  262.   end;
  263.   {$ENDREGION}
  264.  
  265.   {$REGION 'Intelligent browser (executes PHP scripts)'}
  266.   if URL <> 'about:blank' then
  267.   begin
  268.     myUrl := URL;
  269.  
  270.     p := Pos('?', myUrl);
  271.     if p >= 1 then myURL := copy(myURL, 1, p-1);
  272.  
  273.     // TODO: myURL urldecode
  274.     // TODO: maybe we could even open that file in the editor!
  275.  
  276.     if FileExists(myURL) and (EndsText('.php', myURL) or EndsText('.php3', myURL) or EndsText('.php4', myURL) or EndsText('.php5', myURL) or EndsText('.phps', myURL)) then
  277.     begin
  278.       WebBrowser1.LoadHTML(GetDosOutput('"'+GetPHPExe+'" "'+myURL+'"', ExtractFileDir(Application.ExeName)), myUrl);
  279.       Cancel := true;
  280.     end;
  281.   end;
  282.   {$ENDREGION}
  283. end;
  284.  
  285. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  286. begin
  287.   FastPHPConfig.WriteInteger('User', 'FontSize', SynEdit1.Font.Size);
  288. end;
  289.  
  290. procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  291. var
  292.   r: integer;
  293. begin
  294.   if SynEdit1.Modified then
  295.   begin
  296.     if ParamStr(1) <> '' then
  297.     begin
  298.       r := MessageDlg('Do you want to save?', mtConfirmation, mbYesNoCancel, 0);
  299.       if r = mrCancel then
  300.       begin
  301.         CanClose := false;
  302.         Exit;
  303.       end
  304.       else if r = mrYes then
  305.       begin
  306.         SynEdit1.Lines.SaveToFile(GetScrapFile);
  307.         CanClose := true;
  308.       end;
  309.     end
  310.     else
  311.     begin
  312.       SynEdit1.Lines.SaveToFile(GetScrapFile);
  313.       CanClose := true;
  314.     end;
  315.   end;
  316. end;
  317.  
  318. procedure TForm1.FormCreate(Sender: TObject);
  319. begin
  320.   HlpPrevPageIndex := -1;
  321.   CurSearchTerm := '';
  322.   Caption := Caption + ' - ' + GetScrapFile;
  323.   SrcRep := TFindReplace.Create(self);
  324.   SrcRep.Editor := SynEdit1;
  325. end;
  326.  
  327. procedure TForm1.FormDestroy(Sender: TObject);
  328. begin
  329.   if Assigned(ChmIndex) then
  330.   begin
  331.     FreeAndNil(ChmIndex);
  332.   end;
  333.   FreeAndNil(SrcRep);
  334. end;
  335.  
  336. procedure TForm1.FormShow(Sender: TObject);
  337. var
  338.   ScrapFile: string;
  339. begin
  340.   ScrapFile := GetScrapFile;
  341.   if ScrapFile = '' then
  342.   begin
  343.     Application.Terminate; // Close;
  344.     exit;
  345.   end;
  346.   if FileExists(ScrapFile) then
  347.     SynEdit1.Lines.LoadFromFile(ScrapFile)
  348.   else
  349.     SynEdit1.Lines.Clear;
  350.  
  351.   PageControl1.ActivePage := PlaintextTabSheet;
  352.  
  353.   PageControl2.ActivePageIndex := 0; // Scraps
  354.   HelpTabsheet.TabVisible := false;
  355.  
  356.   SynEdit1.Font.Size := FastPHPConfig.ReadInteger('User', 'FontSize', SynEdit1.Font.Size);
  357.   SynEdit1.SetFocus;
  358. end;
  359.  
  360. function TForm1.GetScrapFile: string;
  361. begin
  362.   if ParamStr(1) <> '' then
  363.     result := ParamStr(1)
  364.   else
  365.     result := FastPHPConfig.ReadString('Paths', 'ScrapFile', '');
  366.   if not FileExists(result) then
  367.   begin
  368.     if not OpenDialog3.Execute then
  369.     begin
  370.       result := '';
  371.       exit;
  372.     end
  373.     else
  374.       result := OpenDialog3.FileName;
  375.  
  376.     if not DirectoryExists(ExtractFilePath(result)) then
  377.     begin
  378.       ShowMessage('Path does not exist!');
  379.       result := '';
  380.       exit;
  381.     end;
  382.  
  383.     SynEdit1.Lines.Clear;
  384.     SynEdit1.Lines.SaveToFile(result);
  385.  
  386.     FastPHPConfig.WriteString('Paths', 'ScrapFile', result);
  387.   end;
  388. end;
  389.  
  390. procedure TForm1.Help;
  391. var
  392.   IndexFile, chmFile, w, url: string;
  393.   internalHtmlFile: string;
  394. begin
  395.   if not Assigned(ChmIndex) then
  396.   begin
  397.     IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
  398.     IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
  399.     if FileExists(IndexFile) then
  400.     begin
  401.       ChmIndex := TMemIniFile.Create(IndexFile);
  402.     end;
  403.   end;
  404.  
  405.   if Assigned(ChmIndex) then
  406.   begin
  407.     IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
  408.     // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
  409.  
  410.     chmFile := ChangeFileExt(IndexFile, '.chm');
  411.     if not FileExists(chmFile) then
  412.     begin
  413.       FreeAndNil(ChmIndex);
  414.     end;
  415.   end;
  416.  
  417.   if not Assigned(ChmIndex) then
  418.   begin
  419.     if not OpenDialog1.Execute then exit;
  420.  
  421.     chmFile := OpenDialog1.FileName;
  422.     if not FileExists(chmFile) then exit;
  423.  
  424.     IndexFile := ChangeFileExt(chmFile, '.ini');
  425.  
  426.     if not FileExists(IndexFile) then
  427.     begin
  428.       Panel1.Align := alClient;
  429.       Panel1.Visible := true;
  430.       Panel1.BringToFront;
  431.       Screen.Cursor := crHourGlass;
  432.       Application.ProcessMessages;
  433.       try
  434.         if not ParseCHM(chmFile) then
  435.         begin
  436.           ShowMessage('The CHM file is not a valid PHP documentation. Cannot use help.');
  437.           exit;
  438.         end;
  439.       finally
  440.         Screen.Cursor := crDefault;
  441.         Panel1.Visible := false;
  442.       end;
  443.  
  444.       if not FileExists(IndexFile) then
  445.       begin
  446.         ShowMessage('Unknown error. Cannot use help.');
  447.         exit;
  448.       end;
  449.     end;
  450.  
  451.     FastPHPConfig.WriteString('Paths', 'HelpIndex', IndexFile);
  452.     FastPHPConfig.UpdateFile;
  453.  
  454.     ChmIndex := TMemIniFile.Create(IndexFile);
  455.   end;
  456.  
  457.   w := GetWordUnderCaret(SynEdit1);
  458.   if w = '' then exit;
  459.   if CharInSet(w[1], ['0'..'9']) then exit;
  460.   w := StringReplace(w, '_', '-', [rfReplaceAll]);
  461.   w := LowerCase(w);
  462.   CurSearchTerm := w;
  463.  
  464.   internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, '');
  465.   if internalHtmlFile = '' then
  466.   begin
  467.     HelpTabsheet.TabVisible := false;
  468.     HlpPrevPageIndex := -1;
  469.     ShowMessage('No help for "'+CurSearchTerm+'" available');
  470.     Exit;
  471.   end;
  472.  
  473.   url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
  474.  
  475.   HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
  476.   HelpTabsheet.TabVisible := true;
  477.   PageControl2.ActivePage := HelpTabsheet;
  478.   WebBrowser2.Navigate(url);
  479.   WebBrowser2.Wait;
  480. end;
  481.  
  482. procedure TForm1.GotoLineNo(LineNo:integer);
  483. var
  484.   line: string;
  485.   i: integer;
  486. begin
  487.   SynEdit1.GotoLineAndCenter(LineNo);
  488.  
  489.   // Skip indent
  490.   line := SynEdit1.Lines[SynEdit1.CaretY];
  491.   for i := 1 to Length(line) do
  492.   begin
  493.     if not CharInSet(line[i], [' ', #9]) then
  494.     begin
  495.       SynEdit1.CaretX := i-1;
  496.       break;
  497.     end;
  498.   end;
  499.  
  500.   PageControl2.ActivePage := TabSheet3{Scrap};
  501.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  502. end;
  503.  
  504. procedure TForm1.PageControl2Changing(Sender: TObject;
  505.   var AllowChange: Boolean);
  506. begin
  507.   if PageControl2.ActivePage = HelpTabsheet then
  508.     HlpPrevPageIndex := -1
  509.   else
  510.     HlpPrevPageIndex := PageControl2.ActivePageIndex;
  511.  
  512.   AllowChange := true;
  513. end;
  514.  
  515. procedure TForm1.Memo2DblClick(Sender: TObject);
  516. var
  517.   line: string;
  518.   p, lineno: integer;
  519. begin
  520.   line := memo2.Lines.Strings[Memo2.CaretPos.Y];
  521.   p := Pos(' on line ', line);
  522.   if p = 0 then exit;
  523.   line := copy(line, p+length(' on line '), 99);
  524.   if not TryStrToInt(line, lineno) then exit;
  525.   GotoLineNo(lineno);
  526. end;
  527.  
  528. function TForm1.MarkUpLineReference(cont: string): string;
  529. var
  530.   p, a, b: integer;
  531.   num: integer;
  532.   insert_a, insert_b: string;
  533. begin
  534.   // TODO: make it more specific to PHP error messages. "on line" is too broad.
  535.   p := Pos(' on line ', cont);
  536.   while p >= 1 do
  537.   begin
  538.     a := p+1;
  539.     b := p+length(' on line ');
  540.     num := 0;
  541.     while CharInSet(cont[b], ['0'..'9']) do
  542.     begin
  543.       num := num*10 + StrToInt(cont[b]);
  544.       inc(b);
  545.     end;
  546.  
  547.     insert_b := '</a>';
  548.     insert_a := '<a href="'+FASTPHP_GOTO_URI_PREFIX+IntToStr(num)+'">';
  549.  
  550.     insert(insert_b, cont, b);
  551.     insert(insert_a, cont, a);
  552.  
  553.     p := b + Length(insert_a) + Length(insert_b);
  554.  
  555.     p := PosEx(' on line ', cont, p+1);
  556.   end;
  557.  
  558.   result := cont;
  559. end;
  560.  
  561. end.
  562.