Subversion Repositories fastphp

Rev

Rev 6 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. unit Unit1;
  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.  
  14. // TODO: wieso geht copy paste im twebbrowser nicht???
  15. // Wieso dauert webbrowser1 erste kompilierung so lange???
  16. // TODO: wieso kommt syntax fehler zweimal? einmal stderr einmal stdout?
  17. // TODO: Browser titlebar (link preview)
  18.  
  19. // TODO: strg+f / h
  20. // TODO: font bigger
  21. // TODO: code in bildschirmmitte?
  22. // TODO: regelmäßig scrap zwischenspeichern, oder bei strg+s
  23.  
  24. // Future ideas
  25. // - ToDo list
  26. // - Open/Save real files
  27. // - multiple scraps?
  28. // - verschiedene php versionen?
  29. // - webbrowser1 nur laden, wenn man den tab anwählt?
  30. // - doppelklick auf tab soll diesen schließen
  31. // - Strg+S
  32. // - Onlinehelp (www) aufrufen
  33.  
  34. interface
  35.  
  36. uses
  37.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  38.   Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
  39.   SynEditHighlighter, SynHighlighterPHP, SynEdit, SHDocVw_TLB;
  40.  
  41. type
  42.   TForm1 = class(TForm)
  43.     PageControl1: TPageControl;
  44.     PlaintextTabSheet: TTabSheet;
  45.     HtmlTabSheet: TTabSheet;
  46.     Memo2: TMemo;
  47.     WebBrowser1: TWebBrowser;
  48.     Splitter1: TSplitter;
  49.     PageControl2: TPageControl;
  50.     TabSheet3: TTabSheet;
  51.     HelpTabsheet: TTabSheet;
  52.     WebBrowser2: TWebBrowser;
  53.     OpenDialog1: TOpenDialog;
  54.     Panel1: TPanel;
  55.     OpenDialog2: TOpenDialog;
  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.     procedure Run(Sender: TObject);
  65.     procedure FormShow(Sender: TObject);
  66.     procedure FormCreate(Sender: TObject);
  67.     procedure FormDestroy(Sender: TObject);
  68.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  69.     procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
  70.     procedure Memo2DblClick(Sender: TObject);
  71.     procedure WebBrowser1BeforeNavigate2(ASender: TObject;
  72.       const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  73.       Headers: OleVariant; var Cancel: WordBool);
  74.     procedure SynEditFocusTimerTimer(Sender: TObject);
  75.     procedure Button1Click(Sender: TObject);
  76.     procedure Button2Click(Sender: TObject);
  77.     procedure Button3Click(Sender: TObject);
  78.   private
  79.     CurSearchTerm: string;
  80.     HlpPrevPageIndex: integer;
  81.     procedure Help;
  82.     procedure ApplicationOnMessage(var Msg: tagMSG; var Handled: Boolean);
  83.     function MarkUpLineReference(cont: string): string;
  84.   protected
  85.     FastPHPConfig: TMemIniFile;
  86.     ChmIndex: TMemIniFile;
  87.     procedure GotoLineNo(LineNo:integer);
  88.     function GetScrapFile: string;
  89.   end;
  90.  
  91. var
  92.   Form1: TForm1;
  93.  
  94. implementation
  95.  
  96. {$R *.dfm}
  97.  
  98. uses
  99.   Functions, StrUtils;
  100.  
  101. procedure TForm1.ApplicationOnMessage(var Msg: tagMSG; var Handled: Boolean);
  102. var
  103.   val: string;
  104.   lineno: integer;
  105. begin
  106.   case Msg.message of
  107.     WM_KEYUP:
  108.     begin
  109.       case Msg.wParam of
  110.         {$REGION 'Esc'}
  111.         VK_ESCAPE:
  112.         begin
  113.           Handled := true;
  114.           // It is necessary to use Application.OnMessage, because Form1.KeyPreview does not work when TWebBrowser has the focus
  115.           if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
  116.              (HelpTabsheet.TabVisible) then
  117.           begin
  118.             PageControl2.ActivePageIndex := HlpPrevPageIndex;
  119.             HelpTabsheet.TabVisible := false;
  120.           end;
  121.         end;
  122.         {$ENDREGION}
  123.  
  124.         {$REGION 'Ctrl+G : Go to line'}
  125.         ord('G'):
  126.         begin
  127.           // TODO: VK_LMENU does not work! only works with AltGr but not Alt
  128.           // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
  129.           if (GetKeyState(VK_CONTROL) < 0) then
  130.           begin
  131.             Handled := true;
  132.             InputQuery('Go to', 'Line number:', val);
  133.             if not TryStrToInt(val, lineno) then exit;
  134.             GotoLineNo(lineno);
  135.           end;
  136.         end;
  137.         {$ENDREGION}
  138.  
  139.         VK_F1:
  140.         begin
  141.           if SynEdit1.Focused then
  142.           begin
  143.             Handled := true;
  144.             Help;
  145.           end;
  146.         end;
  147.  
  148.         VK_F5:
  149.         begin
  150.           Run(Self);
  151.         end;
  152.  
  153.         VK_F9:
  154.         begin
  155.           Run(Self);
  156.         end;
  157.       end;
  158.     end;
  159.   end;
  160. end;
  161.  
  162. procedure TForm1.Run(Sender: TObject);
  163. var
  164.   phpExe: string;
  165. begin
  166.   memo2.Lines.Text := '';
  167.   BrowseContent(Webbrowser1, memo2.Lines.Text);
  168.   Screen.Cursor := crHourGlass;
  169.   Application.ProcessMessages;
  170.  
  171.   try
  172.     phpExe := FastPHPConfig.ReadString('Paths', 'PHPInterpreter', '');
  173.     if not FileExists(phpExe) then
  174.     begin
  175.       if not OpenDialog2.Execute then exit;
  176.       if not FileExists(OpenDialog2.FileName) then exit;
  177.       phpExe := OpenDialog2.FileName;
  178.  
  179.       if not IsValidPHPExe(phpExe) then
  180.       begin
  181.         ShowMessage('This is not a valid PHP executable.');
  182.         exit;
  183.       end;
  184.  
  185.       FastPHPConfig.WriteString('Paths', 'PHPInterpreter', phpExe);
  186.       FastPHPConfig.UpdateFile;
  187.     end;
  188.  
  189.     SynEdit1.Lines.SaveToFile(GetScrapFile);
  190.  
  191.     memo2.Lines.Text := GetDosOutput('"'+phpExe+'" "'+GetScrapFile+'"', ExtractFileDir(Application.ExeName));
  192.  
  193.     BrowseContent(Webbrowser1, MarkUpLineReference(memo2.Lines.Text));
  194.  
  195.     if IsTextHTML(memo2.lines.text) then
  196.       PageControl1.ActivePage := HtmlTabSheet
  197.     else
  198.       PageControl1.ActivePage := PlaintextTabSheet;
  199.   finally
  200.     Screen.Cursor := crDefault;
  201.   end;
  202. end;
  203.  
  204. procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
  205. begin
  206.   SynEditFocusTimer.Enabled := false;
  207.   Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
  208.   SynEdit1.SetFocus;
  209. end;
  210.  
  211. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  212.   const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  213.   Headers: OleVariant; var Cancel: WordBool);
  214. const
  215.   MAG_BEGIN = 'fastphp://gotoline/';
  216. var
  217.   s, myURL, phpExe, scrapDir: string;
  218.   lineno: integer;
  219.   p: integer;
  220. begin
  221.   {$REGION 'Line number references (PHP errors and warnings)'}
  222.   if Copy(URL, 1, length(MAG_BEGIN)) = MAG_BEGIN then
  223.   begin
  224.     try
  225.       s := copy(URL, length(MAG_BEGIN)+1, 99);
  226.       if not TryStrToInt(s, lineno) then exit;
  227.       GotoLineNo(lineno);
  228.       SynEditFocusTimer.Enabled := true;
  229.     finally
  230.       Cancel := true;
  231.     end;
  232.   end;
  233.   {$ENDREGION}
  234.  
  235.   {$REGION 'Intelligent browser'}
  236.   if URL <> 'about:blank' then
  237.   begin
  238.     p := Pos('?', URL);
  239.     myUrl := URL;
  240.  
  241.     myURL := StringReplace(myURL, 'about:', '', []); // TODO: ??? wenn ich von about:blank komme, dann ist ein link about:xyz.php !
  242.  
  243.     // TODO: unabhängig vom scrap verzeichnis machen!
  244.     scrapDir := FastPHPConfig.ReadString('Paths', 'ScrapFile', '');
  245.     myURL := ExtractFileDir({Application.ExeName}scrapDir) + '\' + myURL;
  246.  
  247.     if p >= 1 then myURL := copy(myURL, 1, p-1);
  248.     if FileExists(myURL) then
  249.     begin
  250.       phpExe := FastPHPConfig.ReadString('Paths', 'PHPInterpreter', ''); // TODO: check if available (auslagern)
  251.  
  252.       BrowseContent(WebBrowser1, GetDosOutput('"'+phpExe+'" "'+myURL+'"', ExtractFileDir(Application.ExeName)));
  253.       Cancel := true;
  254.     end;
  255.   end;
  256.   {$ENDREGION}
  257. end;
  258.  
  259. procedure TForm1.Button1Click(Sender: TObject);
  260. begin
  261.   Run(Sender);
  262.   SynEdit1.SetFocus;
  263. end;
  264.  
  265. procedure TForm1.Button2Click(Sender: TObject);
  266. begin
  267.   Help;
  268.   if PageControl2.ActivePage = HelpTabsheet then
  269.     WebBrowser2.SetFocus
  270.   else if PageControl2.ActivePage = TabSheet3{Scrap} then
  271.     SynEdit1.SetFocus;
  272. end;
  273.  
  274. procedure TForm1.Button3Click(Sender: TObject);
  275. var
  276.   val: string;
  277.   lineno: integer;
  278. begin
  279.   InputQuery('Go to', 'Line number:', val);
  280.   if not TryStrToInt(val, lineno) then
  281.   begin
  282.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  283.     exit;
  284.   end;
  285.   GotoLineNo(lineno);
  286. end;
  287.  
  288. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  289. begin
  290.   SynEdit1.Lines.SaveToFile(GetScrapFile);
  291. end;
  292.  
  293. procedure TForm1.FormCreate(Sender: TObject);
  294. begin
  295.   HlpPrevPageIndex := -1;
  296.   CurSearchTerm := '';
  297.   Application.OnMessage := ApplicationOnMessage;
  298.  
  299.   FastPHPConfig := TMemIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
  300. end;
  301.  
  302. procedure TForm1.FormDestroy(Sender: TObject);
  303. begin
  304.   if Assigned(ChmIndex) then
  305.   begin
  306.     FreeAndNil(ChmIndex);
  307.   end;
  308.  
  309.   FastPHPConfig.UpdateFile;
  310.   FreeAndNil(FastPHPConfig);
  311. end;
  312.  
  313. procedure TForm1.FormShow(Sender: TObject);
  314. var
  315.   ScrapFile: string;
  316. begin
  317.   ScrapFile := GetScrapFile;
  318.   if ScrapFile = '' then
  319.   begin
  320.     Close;
  321.     exit;
  322.   end;
  323.   SynEdit1.Lines.LoadFromFile(ScrapFile);
  324.  
  325.   PageControl1.ActivePage := PlaintextTabSheet;
  326.  
  327.   PageControl2.ActivePageIndex := 0; // Scraps
  328.   HelpTabsheet.TabVisible := false;
  329.  
  330.   SynEdit1.SetFocus;
  331. end;
  332.  
  333. function TForm1.GetScrapFile: string;
  334. begin
  335.   result := FastPHPConfig.ReadString('Paths', 'ScrapFile', '');
  336.   if not FileExists(result) then
  337.   begin
  338.     if not OpenDialog3.Execute then
  339.     begin
  340.       result := '';
  341.       exit;
  342.     end;
  343.  
  344.     result := OpenDialog3.FileName;
  345.  
  346.     if not DirectoryExists(ExtractFilePath(result)) then
  347.     begin
  348.       ShowMessage('Path does not exist!');
  349.       result := '';
  350.       exit;
  351.     end;
  352.  
  353.     SynEdit1.Lines.Clear;
  354.     SynEdit1.Lines.SaveToFile(result);
  355.  
  356.     FastPHPConfig.WriteString('Paths', 'ScrapFile', result);
  357.   end;
  358. end;
  359.  
  360. procedure TForm1.Help;
  361. var
  362.   IndexFile, chmFile, w, url: string;
  363.   internalHtmlFile: string;
  364. begin
  365.   if not Assigned(ChmIndex) then
  366.   begin
  367.     IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
  368.     IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
  369.     if FileExists(IndexFile) then
  370.     begin
  371.       ChmIndex := TMemIniFile.Create(IndexFile);
  372.     end;
  373.   end;
  374.  
  375.   if Assigned(ChmIndex) then
  376.   begin
  377.     IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
  378.     // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
  379.  
  380.     chmFile := ChangeFileExt(IndexFile, '.chm');
  381.     if not FileExists(chmFile) then
  382.     begin
  383.       FreeAndNil(ChmIndex);
  384.     end;
  385.   end;
  386.  
  387.   if not Assigned(ChmIndex) then
  388.   begin
  389.     if not OpenDialog1.Execute then exit;
  390.  
  391.     chmFile := OpenDialog1.FileName;
  392.     if not FileExists(chmFile) then exit;
  393.  
  394.     IndexFile := ChangeFileExt(chmFile, '.ini');
  395.  
  396.     if not FileExists(IndexFile) then
  397.     begin
  398.       Panel1.Align := alClient;
  399.       Panel1.Visible := true;
  400.       Panel1.BringToFront;
  401.       Screen.Cursor := crHourGlass;
  402.       Application.ProcessMessages;
  403.       try
  404.         if not ParseCHM(chmFile) then
  405.         begin
  406.           ShowMessage('The CHM file is not a valid PHP documentation. Cannot use help.');
  407.           exit;
  408.         end;
  409.       finally
  410.         Screen.Cursor := crDefault;
  411.         Panel1.Visible := false;
  412.       end;
  413.  
  414.       if not FileExists(IndexFile) then
  415.       begin
  416.         ShowMessage('Unknown error. Cannot use help.');
  417.         exit;
  418.       end;
  419.     end;
  420.  
  421.     FastPHPConfig.WriteString('Paths', 'HelpIndex', IndexFile);
  422.     FastPHPConfig.UpdateFile;
  423.  
  424.     ChmIndex := TMemIniFile.Create(IndexFile);
  425.   end;
  426.  
  427.   w := GetWordUnderCaret(SynEdit1);
  428.   if w = '' then exit;
  429.   if w[1] in ['0'..'9'] then exit;  
  430.   w := StringReplace(w, '_', '-', [rfReplaceAll]);
  431.   w := LowerCase(w);
  432.   CurSearchTerm := w;
  433.  
  434.   internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, '');
  435.   if internalHtmlFile = '' then
  436.   begin
  437.     HelpTabsheet.TabVisible := false;
  438.     HlpPrevPageIndex := -1;
  439.     ShowMessage('No help for "'+CurSearchTerm+'" available');
  440.     Exit;
  441.   end;
  442.  
  443.   url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
  444.  
  445.   HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
  446.   HelpTabsheet.TabVisible := true;
  447.   PageControl2.ActivePage := HelpTabsheet;
  448.   BrowseURL(WebBrowser2, url);
  449. end;
  450.  
  451. procedure TForm1.GotoLineNo(LineNo:integer);
  452. var
  453.   line: string;
  454.   i: integer;
  455. begin
  456.   SynEdit1.GotoLineAndCenter(LineNo);
  457.  
  458.   // Skip indent
  459.   line := SynEdit1.Lines[SynEdit1.CaretY];
  460.   for i := 1 to Length(line) do
  461.   begin
  462.     if not (line[i] in [' ', #9]) then
  463.     begin
  464.       SynEdit1.CaretX := i-1;
  465.       break;
  466.     end;
  467.   end;
  468.  
  469.   PageControl2.ActivePage := TabSheet3{Scrap};
  470.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  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. procedure TForm1.PageControl2Changing(Sender: TObject;
  487.   var AllowChange: Boolean);
  488. begin
  489.   if PageControl2.ActivePage = HelpTabsheet then
  490.     HlpPrevPageIndex := -1
  491.   else
  492.     HlpPrevPageIndex := PageControl2.ActivePageIndex;
  493.  
  494.   AllowChange := true;
  495. end;
  496.  
  497. function TForm1.MarkUpLineReference(cont: string): string;
  498. var
  499.   p, a, b: integer;
  500.   num: integer;
  501.   insert_a, insert_b: string;
  502. begin
  503.   // TODO: make it more specific to PHP error messages. "on line" is too broad.
  504.   p := Pos(' on line ', cont);
  505.   while p >= 1 do
  506.   begin
  507.     a := p+1;
  508.     b := p+length(' on line ');
  509.     num := 0;
  510.     while cont[b] in ['0'..'9'] do
  511.     begin
  512.       num := num*10 + StrToInt(cont[b]);
  513.       inc(b);
  514.     end;
  515.  
  516.     insert_b := '</a>';
  517.     insert_a := '<a href="fastphp://gotoline/'+IntToStr(num)+'">';
  518.  
  519.     insert(insert_b, cont, b);
  520.     insert(insert_a, cont, a);
  521.  
  522.     p := b + Length(insert_a) + Length(insert_b);
  523.  
  524.     p := PosEx(' on line ', cont, p+1);
  525.   end;
  526.  
  527.   result := cont;
  528. end;
  529.  
  530. end.
  531.