Subversion Repositories fastphp

Rev

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

Rev Author Line No. Line
8 daniel-mar 1
unit EditorMain;
2 daniel-mar 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
// TODO: wieso geht copy paste im twebbrowser nicht???
14
// Wieso dauert webbrowser1 erste kompilierung so lange???
5 daniel-mar 15
// TODO: wieso kommt syntax fehler zweimal? einmal stderr einmal stdout?
16
// TODO: Browser titlebar (link preview)
2 daniel-mar 17
 
18
// Future ideas
19
// - ToDo list
20
// - verschiedene php versionen?
21
// - webbrowser1 nur laden, wenn man den tab anwählt?
22
// - doppelklick auf tab soll diesen schließen
5 daniel-mar 23
// - Onlinehelp (www) aufrufen
13 daniel-mar 24
// - Let all colors be adjustable
25
// - code in bildschirmmitte?
2 daniel-mar 26
 
27
interface
28
 
29
uses
30
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
4 daniel-mar 31
  Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
13 daniel-mar 32
  SynEditHighlighter, SynHighlighterPHP, SynEdit, SHDocVw_TLB, FindReplace,
33
  System.Actions, Vcl.ActnList;
2 daniel-mar 34
 
35
type
36
  TForm1 = class(TForm)
37
    PageControl1: TPageControl;
38
    PlaintextTabSheet: TTabSheet;
39
    HtmlTabSheet: TTabSheet;
40
    Memo2: TMemo;
41
    WebBrowser1: TWebBrowser;
42
    Splitter1: TSplitter;
43
    PageControl2: TPageControl;
44
    TabSheet3: TTabSheet;
45
    HelpTabsheet: TTabSheet;
46
    WebBrowser2: TWebBrowser;
47
    OpenDialog1: TOpenDialog;
48
    Panel1: TPanel;
49
    OpenDialog3: TOpenDialog;
4 daniel-mar 50
    SynEdit1: TSynEdit;
51
    SynPHPSyn1: TSynPHPSyn;
5 daniel-mar 52
    Panel2: TPanel;
53
    SynEditFocusTimer: TTimer;
54
    Button1: TButton;
55
    Button2: TButton;
56
    Button3: TButton;
13 daniel-mar 57
    Button4: TButton;
58
    Button5: TButton;
59
    Button6: TButton;
60
    ActionList: TActionList;
61
    ActionFind: TAction;
62
    ActionReplace: TAction;
63
    ActionFindNext: TAction;
64
    ActionGoto: TAction;
65
    ActionSave: TAction;
66
    ActionHelp: TAction;
67
    ActionRun: TAction;
68
    ActionESC: TAction;
69
    Button7: TButton;
15 daniel-mar 70
    ActionOpen: TAction;
71
    Button8: TButton;
2 daniel-mar 72
    procedure Run(Sender: TObject);
73
    procedure FormShow(Sender: TObject);
74
    procedure FormCreate(Sender: TObject);
75
    procedure FormDestroy(Sender: TObject);
76
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
77
    procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
5 daniel-mar 78
    procedure Memo2DblClick(Sender: TObject);
79
    procedure WebBrowser1BeforeNavigate2(ASender: TObject;
80
      const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
81
      Headers: OleVariant; var Cancel: WordBool);
82
    procedure SynEditFocusTimerTimer(Sender: TObject);
13 daniel-mar 83
    procedure ActionFindExecute(Sender: TObject);
84
    procedure ActionReplaceExecute(Sender: TObject);
85
    procedure ActionFindNextExecute(Sender: TObject);
86
    procedure ActionGotoExecute(Sender: TObject);
87
    procedure ActionSaveExecute(Sender: TObject);
88
    procedure ActionHelpExecute(Sender: TObject);
89
    procedure ActionRunExecute(Sender: TObject);
90
    procedure ActionESCExecute(Sender: TObject);
91
    procedure SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
92
      MousePos: TPoint; var Handled: Boolean);
93
    procedure SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
94
      MousePos: TPoint; var Handled: Boolean);
15 daniel-mar 95
    procedure ActionOpenExecute(Sender: TObject);
96
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
2 daniel-mar 97
  private
98
    CurSearchTerm: string;
99
    HlpPrevPageIndex: integer;
13 daniel-mar 100
    SrcRep: TFindReplace;
2 daniel-mar 101
    procedure Help;
5 daniel-mar 102
    function MarkUpLineReference(cont: string): string;
2 daniel-mar 103
  protected
104
    ChmIndex: TMemIniFile;
5 daniel-mar 105
    procedure GotoLineNo(LineNo:integer);
2 daniel-mar 106
    function GetScrapFile: string;
107
  end;
108
 
109
var
110
  Form1: TForm1;
111
 
112
implementation
113
 
114
{$R *.dfm}
115
 
116
uses
15 daniel-mar 117
  Functions, StrUtils, WebBrowserUtils, FastPHPUtils, Math, ShellAPI;
2 daniel-mar 118
 
13 daniel-mar 119
// TODO: FindPrev ?
120
procedure TForm1.ActionFindNextExecute(Sender: TObject);
121
begin
122
  SrcRep.FindNext;
123
end;
124
 
125
procedure TForm1.ActionGotoExecute(Sender: TObject);
5 daniel-mar 126
var
127
  val: string;
128
  lineno: integer;
129
begin
13 daniel-mar 130
  // TODO: VK_LMENU does not work! only works with AltGr but not Alt
131
  // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
5 daniel-mar 132
 
13 daniel-mar 133
  InputQuery('Go to', 'Line number:', val);
134
  if not TryStrToInt(val, lineno) then
135
  begin
136
    if SynEdit1.CanFocus then SynEdit1.SetFocus;
137
    exit;
138
  end;
139
  GotoLineNo(lineno);
140
end;
5 daniel-mar 141
 
13 daniel-mar 142
procedure TForm1.ActionHelpExecute(Sender: TObject);
143
begin
144
  Help;
145
  if PageControl2.ActivePage = HelpTabsheet then
146
    WebBrowser2.SetFocus
147
  else if PageControl2.ActivePage = TabSheet3{Scrap} then
148
    SynEdit1.SetFocus;
149
end;
8 daniel-mar 150
 
15 daniel-mar 151
procedure TForm1.ActionOpenExecute(Sender: TObject);
152
begin
153
  If OpenDialog3.Execute then
154
  begin
155
    ShellExecute(0, 'open', PChar(ParamStr(0)), PChar(OpenDialog3.FileName), '', SW_NORMAL);
156
  end;
157
end;
158
 
13 daniel-mar 159
procedure TForm1.ActionReplaceExecute(Sender: TObject);
160
begin
161
  SrcRep.ReplaceExecute;
162
end;
5 daniel-mar 163
 
13 daniel-mar 164
procedure TForm1.ActionRunExecute(Sender: TObject);
165
begin
166
  Run(Sender);
167
  SynEdit1.SetFocus;
168
end;
5 daniel-mar 169
 
13 daniel-mar 170
procedure TForm1.ActionSaveExecute(Sender: TObject);
171
begin
172
  SynEdit1.Lines.SaveToFile(GetScrapFile);
173
end;
174
 
175
procedure TForm1.ActionESCExecute(Sender: TObject);
176
begin
177
  if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
178
     (HelpTabsheet.TabVisible) then
179
  begin
180
    PageControl2.ActivePageIndex := HlpPrevPageIndex;
181
    HelpTabsheet.TabVisible := false;
2 daniel-mar 182
  end;
13 daniel-mar 183
 
184
  // Dirty hack...
185
  SrcRep._FindDialog.CloseDialog;
186
  SrcRep._ReplaceDialog.CloseDialog;
2 daniel-mar 187
end;
188
 
13 daniel-mar 189
procedure TForm1.ActionFindExecute(Sender: TObject);
190
begin
191
  SrcRep.FindExecute;
192
end;
193
 
2 daniel-mar 194
procedure TForm1.Run(Sender: TObject);
195
begin
5 daniel-mar 196
  memo2.Lines.Text := '';
8 daniel-mar 197
  Webbrowser1.Clear;
5 daniel-mar 198
  Screen.Cursor := crHourGlass;
199
  Application.ProcessMessages;
200
 
201
  try
202
    SynEdit1.Lines.SaveToFile(GetScrapFile);
203
 
8 daniel-mar 204
    memo2.Lines.Text := RunPHPScript(GetScrapFile);
5 daniel-mar 205
 
8 daniel-mar 206
    Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
5 daniel-mar 207
 
208
    if IsTextHTML(memo2.lines.text) then
209
      PageControl1.ActivePage := HtmlTabSheet
210
    else
211
      PageControl1.ActivePage := PlaintextTabSheet;
212
  finally
213
    Screen.Cursor := crDefault;
2 daniel-mar 214
  end;
5 daniel-mar 215
end;
2 daniel-mar 216
 
13 daniel-mar 217
procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
218
  MousePos: TPoint; var Handled: Boolean);
219
begin
220
  if ssCtrl in Shift then
221
  begin
222
    SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
223
  end;
224
end;
225
 
226
procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
227
  MousePos: TPoint; var Handled: Boolean);
228
begin
229
  if ssCtrl in Shift then
230
  begin
231
    SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
232
  end;
233
end;
234
 
5 daniel-mar 235
procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
236
begin
237
  SynEditFocusTimer.Enabled := false;
238
  Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
239
  SynEdit1.SetFocus;
240
end;
2 daniel-mar 241
 
5 daniel-mar 242
procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
243
  const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
244
  Headers: OleVariant; var Cancel: WordBool);
245
var
8 daniel-mar 246
  s, myURL: string;
5 daniel-mar 247
  lineno: integer;
7 daniel-mar 248
  p: integer;
5 daniel-mar 249
begin
7 daniel-mar 250
  {$REGION 'Line number references (PHP errors and warnings)'}
8 daniel-mar 251
  if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
5 daniel-mar 252
  begin
253
    try
8 daniel-mar 254
      s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
5 daniel-mar 255
      if not TryStrToInt(s, lineno) then exit;
256
      GotoLineNo(lineno);
257
      SynEditFocusTimer.Enabled := true;
258
    finally
259
      Cancel := true;
260
    end;
8 daniel-mar 261
    Exit;
5 daniel-mar 262
  end;
7 daniel-mar 263
  {$ENDREGION}
264
 
8 daniel-mar 265
  {$REGION 'Intelligent browser (executes PHP scripts)'}
7 daniel-mar 266
  if URL <> 'about:blank' then
267
  begin
268
    myUrl := URL;
269
 
8 daniel-mar 270
    p := Pos('?', myUrl);
271
    if p >= 1 then myURL := copy(myURL, 1, p-1);
7 daniel-mar 272
 
8 daniel-mar 273
    // TODO: myURL urldecode
274
    // TODO: maybe we could even open that file in the editor!
7 daniel-mar 275
 
8 daniel-mar 276
    if FileExists(myURL) and (EndsText('.php', myURL) or EndsText('.php3', myURL) or EndsText('.php4', myURL) or EndsText('.php5', myURL) or EndsText('.phps', myURL)) then
7 daniel-mar 277
    begin
8 daniel-mar 278
      WebBrowser1.LoadHTML(GetDosOutput('"'+GetPHPExe+'" "'+myURL+'"', ExtractFileDir(Application.ExeName)), myUrl);
7 daniel-mar 279
      Cancel := true;
280
    end;
281
  end;
282
  {$ENDREGION}
5 daniel-mar 283
end;
2 daniel-mar 284
 
285
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
286
begin
13 daniel-mar 287
  FastPHPConfig.WriteInteger('User', 'FontSize', SynEdit1.Font.Size);
2 daniel-mar 288
end;
289
 
15 daniel-mar 290
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
291
var
292
  r: integer;
293
begin
294
  if SynEdit1.Modified then
295
  begin
296
    if ParamStr(1) <> '' then
297
    begin
298
      r := MessageDlg('Do you want to save?', mtConfirmation, mbYesNoCancel, 0);
299
      if r = mrCancel then
300
      begin
301
        CanClose := false;
302
        Exit;
303
      end
304
      else if r = mrYes then
305
      begin
306
        SynEdit1.Lines.SaveToFile(GetScrapFile);
307
        CanClose := true;
308
      end;
309
    end
310
    else
311
    begin
312
      SynEdit1.Lines.SaveToFile(GetScrapFile);
313
      CanClose := true;
314
    end;
315
  end;
316
end;
317
 
2 daniel-mar 318
procedure TForm1.FormCreate(Sender: TObject);
319
begin
320
  HlpPrevPageIndex := -1;
321
  CurSearchTerm := '';
13 daniel-mar 322
  Caption := Caption + ' - ' + GetScrapFile;
323
  SrcRep := TFindReplace.Create(self);
324
  SrcRep.Editor := SynEdit1;
2 daniel-mar 325
end;
326
 
327
procedure TForm1.FormDestroy(Sender: TObject);
328
begin
329
  if Assigned(ChmIndex) then
330
  begin
331
    FreeAndNil(ChmIndex);
332
  end;
13 daniel-mar 333
  FreeAndNil(SrcRep);
2 daniel-mar 334
end;
335
 
336
procedure TForm1.FormShow(Sender: TObject);
337
var
338
  ScrapFile: string;
339
begin
340
  ScrapFile := GetScrapFile;
341
  if ScrapFile = '' then
342
  begin
10 daniel-mar 343
    Application.Terminate; // Close;
2 daniel-mar 344
    exit;
345
  end;
15 daniel-mar 346
  if FileExists(ScrapFile) then
347
    SynEdit1.Lines.LoadFromFile(ScrapFile)
348
  else
349
    SynEdit1.Lines.Clear;
2 daniel-mar 350
 
351
  PageControl1.ActivePage := PlaintextTabSheet;
352
 
353
  PageControl2.ActivePageIndex := 0; // Scraps
354
  HelpTabsheet.TabVisible := false;
5 daniel-mar 355
 
13 daniel-mar 356
  SynEdit1.Font.Size := FastPHPConfig.ReadInteger('User', 'FontSize', SynEdit1.Font.Size);
5 daniel-mar 357
  SynEdit1.SetFocus;
2 daniel-mar 358
end;
359
 
360
function TForm1.GetScrapFile: string;
361
begin
15 daniel-mar 362
  if ParamStr(1) <> '' then
13 daniel-mar 363
    result := ParamStr(1)
364
  else
365
    result := FastPHPConfig.ReadString('Paths', 'ScrapFile', '');
2 daniel-mar 366
  if not FileExists(result) then
367
  begin
368
    if not OpenDialog3.Execute then
369
    begin
370
      result := '';
371
      exit;
15 daniel-mar 372
    end
373
    else
374
      result := OpenDialog3.FileName;
2 daniel-mar 375
 
376
    if not DirectoryExists(ExtractFilePath(result)) then
377
    begin
378
      ShowMessage('Path does not exist!');
379
      result := '';
380
      exit;
381
    end;
382
 
4 daniel-mar 383
    SynEdit1.Lines.Clear;
384
    SynEdit1.Lines.SaveToFile(result);
2 daniel-mar 385
 
386
    FastPHPConfig.WriteString('Paths', 'ScrapFile', result);
387
  end;
388
end;
389
 
390
procedure TForm1.Help;
391
var
392
  IndexFile, chmFile, w, url: string;
393
  internalHtmlFile: string;
394
begin
395
  if not Assigned(ChmIndex) then
396
  begin
397
    IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
398
    IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
399
    if FileExists(IndexFile) then
400
    begin
401
      ChmIndex := TMemIniFile.Create(IndexFile);
402
    end;
403
  end;
404
 
405
  if Assigned(ChmIndex) then
406
  begin
407
    IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
408
    // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
409
 
410
    chmFile := ChangeFileExt(IndexFile, '.chm');
411
    if not FileExists(chmFile) then
412
    begin
413
      FreeAndNil(ChmIndex);
414
    end;
415
  end;
416
 
417
  if not Assigned(ChmIndex) then
418
  begin
419
    if not OpenDialog1.Execute then exit;
420
 
421
    chmFile := OpenDialog1.FileName;
422
    if not FileExists(chmFile) then exit;
423
 
424
    IndexFile := ChangeFileExt(chmFile, '.ini');
425
 
426
    if not FileExists(IndexFile) then
427
    begin
428
      Panel1.Align := alClient;
429
      Panel1.Visible := true;
430
      Panel1.BringToFront;
431
      Screen.Cursor := crHourGlass;
432
      Application.ProcessMessages;
433
      try
434
        if not ParseCHM(chmFile) then
435
        begin
436
          ShowMessage('The CHM file is not a valid PHP documentation. Cannot use help.');
437
          exit;
438
        end;
439
      finally
440
        Screen.Cursor := crDefault;
441
        Panel1.Visible := false;
442
      end;
443
 
444
      if not FileExists(IndexFile) then
445
      begin
446
        ShowMessage('Unknown error. Cannot use help.');
447
        exit;
448
      end;
449
    end;
450
 
451
    FastPHPConfig.WriteString('Paths', 'HelpIndex', IndexFile);
452
    FastPHPConfig.UpdateFile;
453
 
454
    ChmIndex := TMemIniFile.Create(IndexFile);
455
  end;
456
 
4 daniel-mar 457
  w := GetWordUnderCaret(SynEdit1);
2 daniel-mar 458
  if w = '' then exit;
8 daniel-mar 459
  if CharInSet(w[1], ['0'..'9']) then exit;
2 daniel-mar 460
  w := StringReplace(w, '_', '-', [rfReplaceAll]);
461
  w := LowerCase(w);
462
  CurSearchTerm := w;
463
 
464
  internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, '');
465
  if internalHtmlFile = '' then
466
  begin
467
    HelpTabsheet.TabVisible := false;
468
    HlpPrevPageIndex := -1;
469
    ShowMessage('No help for "'+CurSearchTerm+'" available');
470
    Exit;
471
  end;
472
 
473
  url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
474
 
475
  HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
476
  HelpTabsheet.TabVisible := true;
477
  PageControl2.ActivePage := HelpTabsheet;
8 daniel-mar 478
  WebBrowser2.Navigate(url);
479
  WebBrowser2.Wait;
2 daniel-mar 480
end;
481
 
5 daniel-mar 482
procedure TForm1.GotoLineNo(LineNo:integer);
483
var
484
  line: string;
485
  i: integer;
2 daniel-mar 486
begin
5 daniel-mar 487
  SynEdit1.GotoLineAndCenter(LineNo);
488
 
489
  // Skip indent
490
  line := SynEdit1.Lines[SynEdit1.CaretY];
491
  for i := 1 to Length(line) do
492
  begin
8 daniel-mar 493
    if not CharInSet(line[i], [' ', #9]) then
5 daniel-mar 494
    begin
495
      SynEdit1.CaretX := i-1;
496
      break;
497
    end;
498
  end;
499
 
500
  PageControl2.ActivePage := TabSheet3{Scrap};
501
  if SynEdit1.CanFocus then SynEdit1.SetFocus;
2 daniel-mar 502
end;
503
 
8 daniel-mar 504
procedure TForm1.PageControl2Changing(Sender: TObject;
505
  var AllowChange: Boolean);
506
begin
507
  if PageControl2.ActivePage = HelpTabsheet then
508
    HlpPrevPageIndex := -1
509
  else
510
    HlpPrevPageIndex := PageControl2.ActivePageIndex;
511
 
512
  AllowChange := true;
513
end;
514
 
5 daniel-mar 515
procedure TForm1.Memo2DblClick(Sender: TObject);
516
var
517
  line: string;
518
  p, lineno: integer;
519
begin
520
  line := memo2.Lines.Strings[Memo2.CaretPos.Y];
521
  p := Pos(' on line ', line);
522
  if p = 0 then exit;
523
  line := copy(line, p+length(' on line '), 99);
524
  if not TryStrToInt(line, lineno) then exit;
525
  GotoLineNo(lineno);
526
end;
527
 
528
function TForm1.MarkUpLineReference(cont: string): string;
529
var
530
  p, a, b: integer;
531
  num: integer;
532
  insert_a, insert_b: string;
533
begin
534
  // TODO: make it more specific to PHP error messages. "on line" is too broad.
535
  p := Pos(' on line ', cont);
536
  while p >= 1 do
537
  begin
538
    a := p+1;
539
    b := p+length(' on line ');
540
    num := 0;
8 daniel-mar 541
    while CharInSet(cont[b], ['0'..'9']) do
5 daniel-mar 542
    begin
543
      num := num*10 + StrToInt(cont[b]);
544
      inc(b);
545
    end;
546
 
547
    insert_b := '</a>';
8 daniel-mar 548
    insert_a := '<a href="'+FASTPHP_GOTO_URI_PREFIX+IntToStr(num)+'">';
5 daniel-mar 549
 
550
    insert(insert_b, cont, b);
551
    insert(insert_a, cont, a);
552
 
553
    p := b + Length(insert_a) + Length(insert_b);
554
 
555
    p := PosEx(' on line ', cont, p+1);
556
  end;
557
 
558
  result := cont;
559
end;
560
 
2 daniel-mar 561
end.