Subversion Repositories fastphp

Rev

Rev 4 | 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.     if not FileExists(phpExe) then
  173.     begin
  174.       phpExe := FastPHPConfig.ReadString('Paths', 'PHPInterpreter', '');
  175.       if not FileExists(phpExe) then
  176.       begin
  177.         if not OpenDialog2.Execute then exit;
  178.         if not FileExists(OpenDialog2.FileName) then exit;
  179.         phpExe := OpenDialog2.FileName;
  180.  
  181.         if not IsValidPHPExe(phpExe) then
  182.         begin
  183.           ShowMessage('This is not a valid PHP executable.');
  184.           exit;
  185.         end;
  186.  
  187.         FastPHPConfig.WriteString('Paths', 'PHPInterpreter', phpExe);
  188.         FastPHPConfig.UpdateFile;
  189.       end;
  190.     end;
  191.  
  192.     SynEdit1.Lines.SaveToFile(GetScrapFile);
  193.  
  194.     memo2.Lines.Text := GetDosOutput('"'+phpExe+'" "'+GetScrapFile+'"', ExtractFileDir(Application.ExeName));
  195.  
  196.     BrowseContent(Webbrowser1, MarkUpLineReference(memo2.Lines.Text));
  197.  
  198.     if IsTextHTML(memo2.lines.text) then
  199.       PageControl1.ActivePage := HtmlTabSheet
  200.     else
  201.       PageControl1.ActivePage := PlaintextTabSheet;
  202.   finally
  203.     Screen.Cursor := crDefault;
  204.   end;
  205. end;
  206.  
  207. procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
  208. begin
  209.   SynEditFocusTimer.Enabled := false;
  210.   Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
  211.   SynEdit1.SetFocus;
  212. end;
  213.  
  214. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  215.   const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  216.   Headers: OleVariant; var Cancel: WordBool);
  217. const
  218.   MAG_BEGIN = 'fastphp://gotoline/';
  219. var
  220.   s: string;
  221.   lineno: integer;
  222. begin
  223.   if Copy(URL, 1, length(MAG_BEGIN)) = MAG_BEGIN then
  224.   begin
  225.     try
  226.       s := copy(URL, length(MAG_BEGIN)+1, 99);
  227.       if not TryStrToInt(s, lineno) then exit;
  228.       GotoLineNo(lineno);
  229.       SynEditFocusTimer.Enabled := true;
  230.     finally
  231.       Cancel := true;
  232.     end;
  233.   end;
  234. end;
  235.  
  236. procedure TForm1.Button1Click(Sender: TObject);
  237. begin
  238.   Run(Sender);
  239.   SynEdit1.SetFocus;
  240. end;
  241.  
  242. procedure TForm1.Button2Click(Sender: TObject);
  243. begin
  244.   Help;
  245.   if PageControl2.ActivePage = HelpTabsheet then
  246.     WebBrowser2.SetFocus
  247.   else if PageControl2.ActivePage = TabSheet3{Scrap} then
  248.     SynEdit1.SetFocus;
  249. end;
  250.  
  251. procedure TForm1.Button3Click(Sender: TObject);
  252. var
  253.   val: string;
  254.   lineno: integer;
  255. begin
  256.   InputQuery('Go to', 'Line number:', val);
  257.   if not TryStrToInt(val, lineno) then exit;
  258.   GotoLineNo(lineno);
  259. end;
  260.  
  261. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  262. begin
  263.   SynEdit1.Lines.SaveToFile(GetScrapFile);
  264. end;
  265.  
  266. procedure TForm1.FormCreate(Sender: TObject);
  267. begin
  268.   HlpPrevPageIndex := -1;
  269.   CurSearchTerm := '';
  270.   Application.OnMessage := ApplicationOnMessage;
  271.  
  272.   FastPHPConfig := TMemIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
  273. end;
  274.  
  275. procedure TForm1.FormDestroy(Sender: TObject);
  276. begin
  277.   if Assigned(ChmIndex) then
  278.   begin
  279.     FreeAndNil(ChmIndex);
  280.   end;
  281.  
  282.   FastPHPConfig.UpdateFile;
  283.   FreeAndNil(FastPHPConfig);
  284. end;
  285.  
  286. procedure TForm1.FormShow(Sender: TObject);
  287. var
  288.   ScrapFile: string;
  289. begin
  290.   ScrapFile := GetScrapFile;
  291.   if ScrapFile = '' then
  292.   begin
  293.     Close;
  294.     exit;
  295.   end;
  296.   SynEdit1.Lines.LoadFromFile(ScrapFile);
  297.  
  298.   PageControl1.ActivePage := PlaintextTabSheet;
  299.  
  300.   PageControl2.ActivePageIndex := 0; // Scraps
  301.   HelpTabsheet.TabVisible := false;
  302.  
  303.   SynEdit1.SetFocus;
  304. end;
  305.  
  306. function TForm1.GetScrapFile: string;
  307. begin
  308.   result := FastPHPConfig.ReadString('Paths', 'ScrapFile', '');
  309.   if not FileExists(result) then
  310.   begin
  311.     if not OpenDialog3.Execute then
  312.     begin
  313.       result := '';
  314.       exit;
  315.     end;
  316.  
  317.     result := OpenDialog3.FileName;
  318.  
  319.     if not DirectoryExists(ExtractFilePath(result)) then
  320.     begin
  321.       ShowMessage('Path does not exist!');
  322.       result := '';
  323.       exit;
  324.     end;
  325.  
  326.     SynEdit1.Lines.Clear;
  327.     SynEdit1.Lines.SaveToFile(result);
  328.  
  329.     FastPHPConfig.WriteString('Paths', 'ScrapFile', result);
  330.   end;
  331. end;
  332.  
  333. procedure TForm1.Help;
  334. var
  335.   IndexFile, chmFile, w, url: string;
  336.   internalHtmlFile: string;
  337. begin
  338.   if not Assigned(ChmIndex) then
  339.   begin
  340.     IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
  341.     IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
  342.     if FileExists(IndexFile) then
  343.     begin
  344.       ChmIndex := TMemIniFile.Create(IndexFile);
  345.     end;
  346.   end;
  347.  
  348.   if Assigned(ChmIndex) then
  349.   begin
  350.     IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
  351.     // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
  352.  
  353.     chmFile := ChangeFileExt(IndexFile, '.chm');
  354.     if not FileExists(chmFile) then
  355.     begin
  356.       FreeAndNil(ChmIndex);
  357.     end;
  358.   end;
  359.  
  360.   if not Assigned(ChmIndex) then
  361.   begin
  362.     if not OpenDialog1.Execute then exit;
  363.  
  364.     chmFile := OpenDialog1.FileName;
  365.     if not FileExists(chmFile) then exit;
  366.  
  367.     IndexFile := ChangeFileExt(chmFile, '.ini');
  368.  
  369.     if not FileExists(IndexFile) then
  370.     begin
  371.       Panel1.Align := alClient;
  372.       Panel1.Visible := true;
  373.       Panel1.BringToFront;
  374.       Screen.Cursor := crHourGlass;
  375.       Application.ProcessMessages;
  376.       try
  377.         if not ParseCHM(chmFile) then
  378.         begin
  379.           ShowMessage('The CHM file is not a valid PHP documentation. Cannot use help.');
  380.           exit;
  381.         end;
  382.       finally
  383.         Screen.Cursor := crDefault;
  384.         Panel1.Visible := false;
  385.       end;
  386.  
  387.       if not FileExists(IndexFile) then
  388.       begin
  389.         ShowMessage('Unknown error. Cannot use help.');
  390.         exit;
  391.       end;
  392.     end;
  393.  
  394.     FastPHPConfig.WriteString('Paths', 'HelpIndex', IndexFile);
  395.     FastPHPConfig.UpdateFile;
  396.  
  397.     ChmIndex := TMemIniFile.Create(IndexFile);
  398.   end;
  399.  
  400.   w := GetWordUnderCaret(SynEdit1);
  401.   if w = '' then exit;
  402.   if w[1] in ['0'..'9'] then exit;  
  403.   w := StringReplace(w, '_', '-', [rfReplaceAll]);
  404.   w := LowerCase(w);
  405.   CurSearchTerm := w;
  406.  
  407.   internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, '');
  408.   if internalHtmlFile = '' then
  409.   begin
  410.     HelpTabsheet.TabVisible := false;
  411.     HlpPrevPageIndex := -1;
  412.     ShowMessage('No help for "'+CurSearchTerm+'" available');
  413.     Exit;
  414.   end;
  415.  
  416.   url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
  417.  
  418.   HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
  419.   HelpTabsheet.TabVisible := true;
  420.   PageControl2.ActivePage := HelpTabsheet;
  421.   BrowseURL(WebBrowser2, url);
  422. end;
  423.  
  424. procedure TForm1.GotoLineNo(LineNo:integer);
  425. var
  426.   line: string;
  427.   i: integer;
  428. begin
  429.   SynEdit1.GotoLineAndCenter(LineNo);
  430.  
  431.   // Skip indent
  432.   line := SynEdit1.Lines[SynEdit1.CaretY];
  433.   for i := 1 to Length(line) do
  434.   begin
  435.     if not (line[i] in [' ', #9]) then
  436.     begin
  437.       SynEdit1.CaretX := i-1;
  438.       break;
  439.     end;
  440.   end;
  441.  
  442.   PageControl2.ActivePage := TabSheet3{Scrap};
  443.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  444. end;
  445.  
  446. procedure TForm1.Memo2DblClick(Sender: TObject);
  447. var
  448.   line: string;
  449.   p, lineno: integer;
  450. begin
  451.   line := memo2.Lines.Strings[Memo2.CaretPos.Y];
  452.   p := Pos(' on line ', line);
  453.   if p = 0 then exit;
  454.   line := copy(line, p+length(' on line '), 99);
  455.   if not TryStrToInt(line, lineno) then exit;
  456.   GotoLineNo(lineno);
  457. end;
  458.  
  459. procedure TForm1.PageControl2Changing(Sender: TObject;
  460.   var AllowChange: Boolean);
  461. begin
  462.   if PageControl2.ActivePage = HelpTabsheet then
  463.     HlpPrevPageIndex := -1
  464.   else
  465.     HlpPrevPageIndex := PageControl2.ActivePageIndex;
  466.  
  467.   AllowChange := true;
  468. end;
  469.  
  470. function TForm1.MarkUpLineReference(cont: string): string;
  471. var
  472.   p, a, b: integer;
  473.   num: integer;
  474.   insert_a, insert_b: string;
  475. begin
  476.   // TODO: make it more specific to PHP error messages. "on line" is too broad.
  477.   p := Pos(' on line ', cont);
  478.   while p >= 1 do
  479.   begin
  480.     a := p+1;
  481.     b := p+length(' on line ');
  482.     num := 0;
  483.     while cont[b] in ['0'..'9'] do
  484.     begin
  485.       num := num*10 + StrToInt(cont[b]);
  486.       inc(b);
  487.     end;
  488.  
  489.     insert_b := '</a>';
  490.     insert_a := '<a href="fastphp://gotoline/'+IntToStr(num)+'">';
  491.  
  492.     insert(insert_b, cont, b);
  493.     insert(insert_a, cont, a);
  494.  
  495.     p := b + Length(insert_a) + Length(insert_b);
  496.  
  497.     p := PosEx(' on line ', cont, p+1);
  498.   end;
  499.  
  500.   result := cont;
  501. end;
  502.  
  503. end.
  504.