Subversion Repositories fastphp

Rev

Rev 6 | Rev 8 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
unit Unit1;
2
 
4 daniel-mar 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
 
2 daniel-mar 12
// TODO: localize
13
 
14
// TODO: wieso geht copy paste im twebbrowser nicht???
15
// Wieso dauert webbrowser1 erste kompilierung so lange???
5 daniel-mar 16
// TODO: wieso kommt syntax fehler zweimal? einmal stderr einmal stdout?
17
// TODO: Browser titlebar (link preview)
2 daniel-mar 18
 
5 daniel-mar 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
 
2 daniel-mar 24
// Future ideas
25
// - ToDo list
26
// - Open/Save real files
4 daniel-mar 27
// - multiple scraps?
2 daniel-mar 28
// - verschiedene php versionen?
29
// - webbrowser1 nur laden, wenn man den tab anwählt?
30
// - doppelklick auf tab soll diesen schließen
4 daniel-mar 31
// - Strg+S
5 daniel-mar 32
// - Onlinehelp (www) aufrufen
2 daniel-mar 33
 
34
interface
35
 
36
uses
37
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
4 daniel-mar 38
  Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
39
  SynEditHighlighter, SynHighlighterPHP, SynEdit, SHDocVw_TLB;
2 daniel-mar 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;
4 daniel-mar 57
    SynEdit1: TSynEdit;
58
    SynPHPSyn1: TSynPHPSyn;
5 daniel-mar 59
    Panel2: TPanel;
60
    SynEditFocusTimer: TTimer;
61
    Button1: TButton;
62
    Button2: TButton;
63
    Button3: TButton;
2 daniel-mar 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);
5 daniel-mar 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);
2 daniel-mar 78
  private
79
    CurSearchTerm: string;
80
    HlpPrevPageIndex: integer;
81
    procedure Help;
82
    procedure ApplicationOnMessage(var Msg: tagMSG; var Handled: Boolean);
5 daniel-mar 83
    function MarkUpLineReference(cont: string): string;
2 daniel-mar 84
  protected
85
    FastPHPConfig: TMemIniFile;
86
    ChmIndex: TMemIniFile;
5 daniel-mar 87
    procedure GotoLineNo(LineNo:integer);
2 daniel-mar 88
    function GetScrapFile: string;
89
  end;
90
 
91
var
92
  Form1: TForm1;
93
 
94
implementation
95
 
96
{$R *.dfm}
97
 
98
uses
5 daniel-mar 99
  Functions, StrUtils;
2 daniel-mar 100
 
101
procedure TForm1.ApplicationOnMessage(var Msg: tagMSG; var Handled: Boolean);
5 daniel-mar 102
var
103
  val: string;
104
  lineno: integer;
105
begin
2 daniel-mar 106
  case Msg.message of
107
    WM_KEYUP:
108
    begin
109
      case Msg.wParam of
5 daniel-mar 110
        {$REGION 'Esc'}
2 daniel-mar 111
        VK_ESCAPE:
112
        begin
5 daniel-mar 113
          Handled := true;
2 daniel-mar 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;
5 daniel-mar 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;
2 daniel-mar 157
      end;
158
    end;
159
  end;
160
end;
161
 
162
procedure TForm1.Run(Sender: TObject);
163
var
164
  phpExe: string;
165
begin
5 daniel-mar 166
  memo2.Lines.Text := '';
167
  BrowseContent(Webbrowser1, memo2.Lines.Text);
168
  Screen.Cursor := crHourGlass;
169
  Application.ProcessMessages;
170
 
171
  try
7 daniel-mar 172
    phpExe := FastPHPConfig.ReadString('Paths', 'PHPInterpreter', '');
2 daniel-mar 173
    if not FileExists(phpExe) then
174
    begin
7 daniel-mar 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
5 daniel-mar 180
      begin
7 daniel-mar 181
        ShowMessage('This is not a valid PHP executable.');
182
        exit;
183
      end;
2 daniel-mar 184
 
7 daniel-mar 185
      FastPHPConfig.WriteString('Paths', 'PHPInterpreter', phpExe);
186
      FastPHPConfig.UpdateFile;
5 daniel-mar 187
    end;
2 daniel-mar 188
 
5 daniel-mar 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;
2 daniel-mar 201
  end;
5 daniel-mar 202
end;
2 daniel-mar 203
 
5 daniel-mar 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;
2 daniel-mar 210
 
5 daniel-mar 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
7 daniel-mar 217
  s, myURL, phpExe, scrapDir: string;
5 daniel-mar 218
  lineno: integer;
7 daniel-mar 219
  p: integer;
5 daniel-mar 220
begin
7 daniel-mar 221
  {$REGION 'Line number references (PHP errors and warnings)'}
5 daniel-mar 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;
7 daniel-mar 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}
5 daniel-mar 257
end;
2 daniel-mar 258
 
5 daniel-mar 259
procedure TForm1.Button1Click(Sender: TObject);
260
begin
261
  Run(Sender);
262
  SynEdit1.SetFocus;
263
end;
2 daniel-mar 264
 
5 daniel-mar 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;
2 daniel-mar 272
end;
273
 
5 daniel-mar 274
procedure TForm1.Button3Click(Sender: TObject);
275
var
276
  val: string;
277
  lineno: integer;
278
begin
279
  InputQuery('Go to', 'Line number:', val);
6 daniel-mar 280
  if not TryStrToInt(val, lineno) then
281
  begin
282
    if SynEdit1.CanFocus then SynEdit1.SetFocus;
283
    exit;
284
  end;
5 daniel-mar 285
  GotoLineNo(lineno);
286
end;
287
 
2 daniel-mar 288
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
289
begin
4 daniel-mar 290
  SynEdit1.Lines.SaveToFile(GetScrapFile);
2 daniel-mar 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;
4 daniel-mar 323
  SynEdit1.Lines.LoadFromFile(ScrapFile);
2 daniel-mar 324
 
325
  PageControl1.ActivePage := PlaintextTabSheet;
326
 
327
  PageControl2.ActivePageIndex := 0; // Scraps
328
  HelpTabsheet.TabVisible := false;
5 daniel-mar 329
 
330
  SynEdit1.SetFocus;
2 daniel-mar 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
 
4 daniel-mar 353
    SynEdit1.Lines.Clear;
354
    SynEdit1.Lines.SaveToFile(result);
2 daniel-mar 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
 
4 daniel-mar 427
  w := GetWordUnderCaret(SynEdit1);
2 daniel-mar 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
 
5 daniel-mar 451
procedure TForm1.GotoLineNo(LineNo:integer);
452
var
453
  line: string;
454
  i: integer;
2 daniel-mar 455
begin
5 daniel-mar 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;
2 daniel-mar 471
end;
472
 
5 daniel-mar 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
 
2 daniel-mar 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
 
5 daniel-mar 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
 
2 daniel-mar 530
end.