Subversion Repositories fastphp

Rev

Rev 4 | 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
2 daniel-mar 172
    if not FileExists(phpExe) then
173
    begin
5 daniel-mar 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;
2 daniel-mar 180
 
5 daniel-mar 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;
2 daniel-mar 189
      end;
5 daniel-mar 190
    end;
2 daniel-mar 191
 
5 daniel-mar 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;
2 daniel-mar 204
  end;
5 daniel-mar 205
end;
2 daniel-mar 206
 
5 daniel-mar 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;
2 daniel-mar 213
 
5 daniel-mar 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;
2 daniel-mar 235
 
5 daniel-mar 236
procedure TForm1.Button1Click(Sender: TObject);
237
begin
238
  Run(Sender);
239
  SynEdit1.SetFocus;
240
end;
2 daniel-mar 241
 
5 daniel-mar 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;
2 daniel-mar 249
end;
250
 
5 daniel-mar 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
 
2 daniel-mar 261
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
262
begin
4 daniel-mar 263
  SynEdit1.Lines.SaveToFile(GetScrapFile);
2 daniel-mar 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;
4 daniel-mar 296
  SynEdit1.Lines.LoadFromFile(ScrapFile);
2 daniel-mar 297
 
298
  PageControl1.ActivePage := PlaintextTabSheet;
299
 
300
  PageControl2.ActivePageIndex := 0; // Scraps
301
  HelpTabsheet.TabVisible := false;
5 daniel-mar 302
 
303
  SynEdit1.SetFocus;
2 daniel-mar 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
 
4 daniel-mar 326
    SynEdit1.Lines.Clear;
327
    SynEdit1.Lines.SaveToFile(result);
2 daniel-mar 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
 
4 daniel-mar 400
  w := GetWordUnderCaret(SynEdit1);
2 daniel-mar 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
 
5 daniel-mar 424
procedure TForm1.GotoLineNo(LineNo:integer);
425
var
426
  line: string;
427
  i: integer;
2 daniel-mar 428
begin
5 daniel-mar 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;
2 daniel-mar 444
end;
445
 
5 daniel-mar 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
 
2 daniel-mar 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
 
5 daniel-mar 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
 
2 daniel-mar 503
end.