Subversion Repositories fastphp

Rev

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