Subversion Repositories fastphp

Rev

Rev 10 | Rev 14 | 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.     procedure Run(Sender: TObject);
  71.     procedure FormShow(Sender: TObject);
  72.     procedure FormCreate(Sender: TObject);
  73.     procedure FormDestroy(Sender: TObject);
  74.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  75.     procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
  76.     procedure Memo2DblClick(Sender: TObject);
  77.     procedure WebBrowser1BeforeNavigate2(ASender: TObject;
  78.       const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  79.       Headers: OleVariant; var Cancel: WordBool);
  80.     procedure SynEditFocusTimerTimer(Sender: TObject);
  81.     procedure ActionFindExecute(Sender: TObject);
  82.     procedure ActionReplaceExecute(Sender: TObject);
  83.     procedure ActionFindNextExecute(Sender: TObject);
  84.     procedure ActionGotoExecute(Sender: TObject);
  85.     procedure ActionSaveExecute(Sender: TObject);
  86.     procedure ActionHelpExecute(Sender: TObject);
  87.     procedure ActionRunExecute(Sender: TObject);
  88.     procedure ActionESCExecute(Sender: TObject);
  89.     procedure SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  90.       MousePos: TPoint; var Handled: Boolean);
  91.     procedure SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  92.       MousePos: TPoint; var Handled: Boolean);
  93.   private
  94.     CurSearchTerm: string;
  95.     HlpPrevPageIndex: integer;
  96.     SrcRep: TFindReplace;
  97.     procedure Help;
  98.     function MarkUpLineReference(cont: string): string;
  99.   protected
  100.     ChmIndex: TMemIniFile;
  101.     procedure GotoLineNo(LineNo:integer);
  102.     function GetScrapFile: string;
  103.   end;
  104.  
  105. var
  106.   Form1: TForm1;
  107.  
  108. implementation
  109.  
  110. {$R *.dfm}
  111.  
  112. uses
  113.   Functions, StrUtils, WebBrowserUtils, FastPHPUtils, Math;
  114.  
  115. // TODO: FindPrev ?
  116. procedure TForm1.ActionFindNextExecute(Sender: TObject);
  117. begin
  118.   SrcRep.FindNext;
  119. end;
  120.  
  121. procedure TForm1.ActionGotoExecute(Sender: TObject);
  122. var
  123.   val: string;
  124.   lineno: integer;
  125. begin
  126.   // TODO: VK_LMENU does not work! only works with AltGr but not Alt
  127.   // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
  128.  
  129.   InputQuery('Go to', 'Line number:', val);
  130.   if not TryStrToInt(val, lineno) then
  131.   begin
  132.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  133.     exit;
  134.   end;
  135.   GotoLineNo(lineno);
  136. end;
  137.  
  138. procedure TForm1.ActionHelpExecute(Sender: TObject);
  139. begin
  140.   Help;
  141.   if PageControl2.ActivePage = HelpTabsheet then
  142.     WebBrowser2.SetFocus
  143.   else if PageControl2.ActivePage = TabSheet3{Scrap} then
  144.     SynEdit1.SetFocus;
  145. end;
  146.  
  147. procedure TForm1.ActionReplaceExecute(Sender: TObject);
  148. begin
  149.   SrcRep.ReplaceExecute;
  150. end;
  151.  
  152. procedure TForm1.ActionRunExecute(Sender: TObject);
  153. begin
  154.   Run(Sender);
  155.   SynEdit1.SetFocus;
  156. end;
  157.  
  158. procedure TForm1.ActionSaveExecute(Sender: TObject);
  159. begin
  160.   SynEdit1.Lines.SaveToFile(GetScrapFile);
  161. end;
  162.  
  163. procedure TForm1.ActionESCExecute(Sender: TObject);
  164. begin
  165.   if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
  166.      (HelpTabsheet.TabVisible) then
  167.   begin
  168.     PageControl2.ActivePageIndex := HlpPrevPageIndex;
  169.     HelpTabsheet.TabVisible := false;
  170.   end;
  171.  
  172.   // Dirty hack...
  173.   SrcRep._FindDialog.CloseDialog;
  174.   SrcRep._ReplaceDialog.CloseDialog;
  175. end;
  176.  
  177. procedure TForm1.ActionFindExecute(Sender: TObject);
  178. begin
  179.   SrcRep.FindExecute;
  180. end;
  181.  
  182. procedure TForm1.Run(Sender: TObject);
  183. begin
  184.   memo2.Lines.Text := '';
  185.   Webbrowser1.Clear;
  186.   Screen.Cursor := crHourGlass;
  187.   Application.ProcessMessages;
  188.  
  189.   try
  190.     SynEdit1.Lines.SaveToFile(GetScrapFile);
  191.  
  192.     memo2.Lines.Text := RunPHPScript(GetScrapFile);
  193.  
  194.     Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
  195.  
  196.     if IsTextHTML(memo2.lines.text) then
  197.       PageControl1.ActivePage := HtmlTabSheet
  198.     else
  199.       PageControl1.ActivePage := PlaintextTabSheet;
  200.   finally
  201.     Screen.Cursor := crDefault;
  202.   end;
  203. end;
  204.  
  205. procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  206.   MousePos: TPoint; var Handled: Boolean);
  207. begin
  208.   if ssCtrl in Shift then
  209.   begin
  210.     SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
  211.   end;
  212. end;
  213.  
  214. procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  215.   MousePos: TPoint; var Handled: Boolean);
  216. begin
  217.   if ssCtrl in Shift then
  218.   begin
  219.     SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
  220.   end;
  221. end;
  222.  
  223. procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
  224. begin
  225.   SynEditFocusTimer.Enabled := false;
  226.   Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
  227.   SynEdit1.SetFocus;
  228. end;
  229.  
  230. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  231.   const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  232.   Headers: OleVariant; var Cancel: WordBool);
  233. var
  234.   s, myURL: string;
  235.   lineno: integer;
  236.   p: integer;
  237. begin
  238.   {$REGION 'Line number references (PHP errors and warnings)'}
  239.   if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
  240.   begin
  241.     try
  242.       s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
  243.       if not TryStrToInt(s, lineno) then exit;
  244.       GotoLineNo(lineno);
  245.       SynEditFocusTimer.Enabled := true;
  246.     finally
  247.       Cancel := true;
  248.     end;
  249.     Exit;
  250.   end;
  251.   {$ENDREGION}
  252.  
  253.   {$REGION 'Intelligent browser (executes PHP scripts)'}
  254.   if URL <> 'about:blank' then
  255.   begin
  256.     myUrl := URL;
  257.  
  258.     p := Pos('?', myUrl);
  259.     if p >= 1 then myURL := copy(myURL, 1, p-1);
  260.  
  261.     // TODO: myURL urldecode
  262.     // TODO: maybe we could even open that file in the editor!
  263.  
  264.     if FileExists(myURL) and (EndsText('.php', myURL) or EndsText('.php3', myURL) or EndsText('.php4', myURL) or EndsText('.php5', myURL) or EndsText('.phps', myURL)) then
  265.     begin
  266.       WebBrowser1.LoadHTML(GetDosOutput('"'+GetPHPExe+'" "'+myURL+'"', ExtractFileDir(Application.ExeName)), myUrl);
  267.       Cancel := true;
  268.     end;
  269.   end;
  270.   {$ENDREGION}
  271. end;
  272.  
  273. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  274. begin
  275.   SynEdit1.Lines.SaveToFile(GetScrapFile);
  276.   FastPHPConfig.WriteInteger('User', 'FontSize', SynEdit1.Font.Size);
  277. end;
  278.  
  279. procedure TForm1.FormCreate(Sender: TObject);
  280. begin
  281.   HlpPrevPageIndex := -1;
  282.   CurSearchTerm := '';
  283.   Caption := Caption + ' - ' + GetScrapFile;
  284.   SrcRep := TFindReplace.Create(self);
  285.   SrcRep.Editor := SynEdit1;
  286. end;
  287.  
  288. procedure TForm1.FormDestroy(Sender: TObject);
  289. begin
  290.   if Assigned(ChmIndex) then
  291.   begin
  292.     FreeAndNil(ChmIndex);
  293.   end;
  294.   FreeAndNil(SrcRep);
  295. end;
  296.  
  297. procedure TForm1.FormShow(Sender: TObject);
  298. var
  299.   ScrapFile: string;
  300. begin
  301.   ScrapFile := GetScrapFile;
  302.   if ScrapFile = '' then
  303.   begin
  304.     Application.Terminate; // Close;
  305.     exit;
  306.   end;
  307.   SynEdit1.Lines.LoadFromFile(ScrapFile);
  308.  
  309.   PageControl1.ActivePage := PlaintextTabSheet;
  310.  
  311.   PageControl2.ActivePageIndex := 0; // Scraps
  312.   HelpTabsheet.TabVisible := false;
  313.  
  314.   SynEdit1.Font.Size := FastPHPConfig.ReadInteger('User', 'FontSize', SynEdit1.Font.Size);
  315.   SynEdit1.SetFocus;
  316. end;
  317.  
  318. function TForm1.GetScrapFile: string;
  319. begin
  320.   if FileExists(ParamStr(1)) then
  321.     result := ParamStr(1)
  322.   else
  323.     result := FastPHPConfig.ReadString('Paths', 'ScrapFile', '');
  324.   if not FileExists(result) then
  325.   begin
  326.     if not OpenDialog3.Execute then
  327.     begin
  328.       result := '';
  329.       exit;
  330.     end;
  331.  
  332.     result := OpenDialog3.FileName;
  333.  
  334.     if not DirectoryExists(ExtractFilePath(result)) then
  335.     begin
  336.       ShowMessage('Path does not exist!');
  337.       result := '';
  338.       exit;
  339.     end;
  340.  
  341.     SynEdit1.Lines.Clear;
  342.     SynEdit1.Lines.SaveToFile(result);
  343.  
  344.     FastPHPConfig.WriteString('Paths', 'ScrapFile', result);
  345.   end;
  346. end;
  347.  
  348. procedure TForm1.Help;
  349. var
  350.   IndexFile, chmFile, w, url: string;
  351.   internalHtmlFile: string;
  352. begin
  353.   if not Assigned(ChmIndex) then
  354.   begin
  355.     IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
  356.     IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
  357.     if FileExists(IndexFile) then
  358.     begin
  359.       ChmIndex := TMemIniFile.Create(IndexFile);
  360.     end;
  361.   end;
  362.  
  363.   if Assigned(ChmIndex) then
  364.   begin
  365.     IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
  366.     // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
  367.  
  368.     chmFile := ChangeFileExt(IndexFile, '.chm');
  369.     if not FileExists(chmFile) then
  370.     begin
  371.       FreeAndNil(ChmIndex);
  372.     end;
  373.   end;
  374.  
  375.   if not Assigned(ChmIndex) then
  376.   begin
  377.     if not OpenDialog1.Execute then exit;
  378.  
  379.     chmFile := OpenDialog1.FileName;
  380.     if not FileExists(chmFile) then exit;
  381.  
  382.     IndexFile := ChangeFileExt(chmFile, '.ini');
  383.  
  384.     if not FileExists(IndexFile) then
  385.     begin
  386.       Panel1.Align := alClient;
  387.       Panel1.Visible := true;
  388.       Panel1.BringToFront;
  389.       Screen.Cursor := crHourGlass;
  390.       Application.ProcessMessages;
  391.       try
  392.         if not ParseCHM(chmFile) then
  393.         begin
  394.           ShowMessage('The CHM file is not a valid PHP documentation. Cannot use help.');
  395.           exit;
  396.         end;
  397.       finally
  398.         Screen.Cursor := crDefault;
  399.         Panel1.Visible := false;
  400.       end;
  401.  
  402.       if not FileExists(IndexFile) then
  403.       begin
  404.         ShowMessage('Unknown error. Cannot use help.');
  405.         exit;
  406.       end;
  407.     end;
  408.  
  409.     FastPHPConfig.WriteString('Paths', 'HelpIndex', IndexFile);
  410.     FastPHPConfig.UpdateFile;
  411.  
  412.     ChmIndex := TMemIniFile.Create(IndexFile);
  413.   end;
  414.  
  415.   w := GetWordUnderCaret(SynEdit1);
  416.   if w = '' then exit;
  417.   if CharInSet(w[1], ['0'..'9']) then exit;
  418.   w := StringReplace(w, '_', '-', [rfReplaceAll]);
  419.   w := LowerCase(w);
  420.   CurSearchTerm := w;
  421.  
  422.   internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, '');
  423.   if internalHtmlFile = '' then
  424.   begin
  425.     HelpTabsheet.TabVisible := false;
  426.     HlpPrevPageIndex := -1;
  427.     ShowMessage('No help for "'+CurSearchTerm+'" available');
  428.     Exit;
  429.   end;
  430.  
  431.   url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
  432.  
  433.   HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
  434.   HelpTabsheet.TabVisible := true;
  435.   PageControl2.ActivePage := HelpTabsheet;
  436.   WebBrowser2.Navigate(url);
  437.   WebBrowser2.Wait;
  438. end;
  439.  
  440. procedure TForm1.GotoLineNo(LineNo:integer);
  441. var
  442.   line: string;
  443.   i: integer;
  444. begin
  445.   SynEdit1.GotoLineAndCenter(LineNo);
  446.  
  447.   // Skip indent
  448.   line := SynEdit1.Lines[SynEdit1.CaretY];
  449.   for i := 1 to Length(line) do
  450.   begin
  451.     if not CharInSet(line[i], [' ', #9]) then
  452.     begin
  453.       SynEdit1.CaretX := i-1;
  454.       break;
  455.     end;
  456.   end;
  457.  
  458.   PageControl2.ActivePage := TabSheet3{Scrap};
  459.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  460. end;
  461.  
  462. procedure TForm1.PageControl2Changing(Sender: TObject;
  463.   var AllowChange: Boolean);
  464. begin
  465.   if PageControl2.ActivePage = HelpTabsheet then
  466.     HlpPrevPageIndex := -1
  467.   else
  468.     HlpPrevPageIndex := PageControl2.ActivePageIndex;
  469.  
  470.   AllowChange := true;
  471. end;
  472.  
  473. procedure TForm1.Memo2DblClick(Sender: TObject);
  474. var
  475.   line: string;
  476.   p, lineno: integer;
  477. begin
  478.   line := memo2.Lines.Strings[Memo2.CaretPos.Y];
  479.   p := Pos(' on line ', line);
  480.   if p = 0 then exit;
  481.   line := copy(line, p+length(' on line '), 99);
  482.   if not TryStrToInt(line, lineno) then exit;
  483.   GotoLineNo(lineno);
  484. end;
  485.  
  486. function TForm1.MarkUpLineReference(cont: string): string;
  487. var
  488.   p, a, b: integer;
  489.   num: integer;
  490.   insert_a, insert_b: string;
  491. begin
  492.   // TODO: make it more specific to PHP error messages. "on line" is too broad.
  493.   p := Pos(' on line ', cont);
  494.   while p >= 1 do
  495.   begin
  496.     a := p+1;
  497.     b := p+length(' on line ');
  498.     num := 0;
  499.     while CharInSet(cont[b], ['0'..'9']) do
  500.     begin
  501.       num := num*10 + StrToInt(cont[b]);
  502.       inc(b);
  503.     end;
  504.  
  505.     insert_b := '</a>';
  506.     insert_a := '<a href="'+FASTPHP_GOTO_URI_PREFIX+IntToStr(num)+'">';
  507.  
  508.     insert(insert_b, cont, b);
  509.     insert(insert_a, cont, a);
  510.  
  511.     p := b + Length(insert_a) + Length(insert_b);
  512.  
  513.     p := PosEx(' on line ', cont, p+1);
  514.   end;
  515.  
  516.   result := cont;
  517. end;
  518.  
  519. end.
  520.