Subversion Repositories fastphp

Rev

Rev 10 | Rev 14 | 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;
2 daniel-mar 70
    procedure Run(Sender: TObject);
71
    procedure FormShow(Sender: TObject);
72
    procedure FormCreate(Sender: TObject);
73
    procedure FormDestroy(Sender: TObject);
74
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
75
    procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
5 daniel-mar 76
    procedure Memo2DblClick(Sender: TObject);
77
    procedure WebBrowser1BeforeNavigate2(ASender: TObject;
78
      const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
79
      Headers: OleVariant; var Cancel: WordBool);
80
    procedure SynEditFocusTimerTimer(Sender: TObject);
13 daniel-mar 81
    procedure ActionFindExecute(Sender: TObject);
82
    procedure ActionReplaceExecute(Sender: TObject);
83
    procedure ActionFindNextExecute(Sender: TObject);
84
    procedure ActionGotoExecute(Sender: TObject);
85
    procedure ActionSaveExecute(Sender: TObject);
86
    procedure ActionHelpExecute(Sender: TObject);
87
    procedure ActionRunExecute(Sender: TObject);
88
    procedure ActionESCExecute(Sender: TObject);
89
    procedure SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
90
      MousePos: TPoint; var Handled: Boolean);
91
    procedure SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
92
      MousePos: TPoint; var Handled: Boolean);
2 daniel-mar 93
  private
94
    CurSearchTerm: string;
95
    HlpPrevPageIndex: integer;
13 daniel-mar 96
    SrcRep: TFindReplace;
2 daniel-mar 97
    procedure Help;
5 daniel-mar 98
    function MarkUpLineReference(cont: string): string;
2 daniel-mar 99
  protected
100
    ChmIndex: TMemIniFile;
5 daniel-mar 101
    procedure GotoLineNo(LineNo:integer);
2 daniel-mar 102
    function GetScrapFile: string;
103
  end;
104
 
105
var
106
  Form1: TForm1;
107
 
108
implementation
109
 
110
{$R *.dfm}
111
 
112
uses
13 daniel-mar 113
  Functions, StrUtils, WebBrowserUtils, FastPHPUtils, Math;
2 daniel-mar 114
 
13 daniel-mar 115
// TODO: FindPrev ?
116
procedure TForm1.ActionFindNextExecute(Sender: TObject);
117
begin
118
  SrcRep.FindNext;
119
end;
120
 
121
procedure TForm1.ActionGotoExecute(Sender: TObject);
5 daniel-mar 122
var
123
  val: string;
124
  lineno: integer;
125
begin
13 daniel-mar 126
  // TODO: VK_LMENU does not work! only works with AltGr but not Alt
127
  // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
5 daniel-mar 128
 
13 daniel-mar 129
  InputQuery('Go to', 'Line number:', val);
130
  if not TryStrToInt(val, lineno) then
131
  begin
132
    if SynEdit1.CanFocus then SynEdit1.SetFocus;
133
    exit;
134
  end;
135
  GotoLineNo(lineno);
136
end;
5 daniel-mar 137
 
13 daniel-mar 138
procedure TForm1.ActionHelpExecute(Sender: TObject);
139
begin
140
  Help;
141
  if PageControl2.ActivePage = HelpTabsheet then
142
    WebBrowser2.SetFocus
143
  else if PageControl2.ActivePage = TabSheet3{Scrap} then
144
    SynEdit1.SetFocus;
145
end;
8 daniel-mar 146
 
13 daniel-mar 147
procedure TForm1.ActionReplaceExecute(Sender: TObject);
148
begin
149
  SrcRep.ReplaceExecute;
150
end;
5 daniel-mar 151
 
13 daniel-mar 152
procedure TForm1.ActionRunExecute(Sender: TObject);
153
begin
154
  Run(Sender);
155
  SynEdit1.SetFocus;
156
end;
5 daniel-mar 157
 
13 daniel-mar 158
procedure TForm1.ActionSaveExecute(Sender: TObject);
159
begin
160
  SynEdit1.Lines.SaveToFile(GetScrapFile);
161
end;
162
 
163
procedure TForm1.ActionESCExecute(Sender: TObject);
164
begin
165
  if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
166
     (HelpTabsheet.TabVisible) then
167
  begin
168
    PageControl2.ActivePageIndex := HlpPrevPageIndex;
169
    HelpTabsheet.TabVisible := false;
2 daniel-mar 170
  end;
13 daniel-mar 171
 
172
  // Dirty hack...
173
  SrcRep._FindDialog.CloseDialog;
174
  SrcRep._ReplaceDialog.CloseDialog;
2 daniel-mar 175
end;
176
 
13 daniel-mar 177
procedure TForm1.ActionFindExecute(Sender: TObject);
178
begin
179
  SrcRep.FindExecute;
180
end;
181
 
2 daniel-mar 182
procedure TForm1.Run(Sender: TObject);
183
begin
5 daniel-mar 184
  memo2.Lines.Text := '';
8 daniel-mar 185
  Webbrowser1.Clear;
5 daniel-mar 186
  Screen.Cursor := crHourGlass;
187
  Application.ProcessMessages;
188
 
189
  try
190
    SynEdit1.Lines.SaveToFile(GetScrapFile);
191
 
8 daniel-mar 192
    memo2.Lines.Text := RunPHPScript(GetScrapFile);
5 daniel-mar 193
 
8 daniel-mar 194
    Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
5 daniel-mar 195
 
196
    if IsTextHTML(memo2.lines.text) then
197
      PageControl1.ActivePage := HtmlTabSheet
198
    else
199
      PageControl1.ActivePage := PlaintextTabSheet;
200
  finally
201
    Screen.Cursor := crDefault;
2 daniel-mar 202
  end;
5 daniel-mar 203
end;
2 daniel-mar 204
 
13 daniel-mar 205
procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
206
  MousePos: TPoint; var Handled: Boolean);
207
begin
208
  if ssCtrl in Shift then
209
  begin
210
    SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
211
  end;
212
end;
213
 
214
procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
215
  MousePos: TPoint; var Handled: Boolean);
216
begin
217
  if ssCtrl in Shift then
218
  begin
219
    SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
220
  end;
221
end;
222
 
5 daniel-mar 223
procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
224
begin
225
  SynEditFocusTimer.Enabled := false;
226
  Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
227
  SynEdit1.SetFocus;
228
end;
2 daniel-mar 229
 
5 daniel-mar 230
procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
231
  const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
232
  Headers: OleVariant; var Cancel: WordBool);
233
var
8 daniel-mar 234
  s, myURL: string;
5 daniel-mar 235
  lineno: integer;
7 daniel-mar 236
  p: integer;
5 daniel-mar 237
begin
7 daniel-mar 238
  {$REGION 'Line number references (PHP errors and warnings)'}
8 daniel-mar 239
  if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
5 daniel-mar 240
  begin
241
    try
8 daniel-mar 242
      s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
5 daniel-mar 243
      if not TryStrToInt(s, lineno) then exit;
244
      GotoLineNo(lineno);
245
      SynEditFocusTimer.Enabled := true;
246
    finally
247
      Cancel := true;
248
    end;
8 daniel-mar 249
    Exit;
5 daniel-mar 250
  end;
7 daniel-mar 251
  {$ENDREGION}
252
 
8 daniel-mar 253
  {$REGION 'Intelligent browser (executes PHP scripts)'}
7 daniel-mar 254
  if URL <> 'about:blank' then
255
  begin
256
    myUrl := URL;
257
 
8 daniel-mar 258
    p := Pos('?', myUrl);
259
    if p >= 1 then myURL := copy(myURL, 1, p-1);
7 daniel-mar 260
 
8 daniel-mar 261
    // TODO: myURL urldecode
262
    // TODO: maybe we could even open that file in the editor!
7 daniel-mar 263
 
8 daniel-mar 264
    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 265
    begin
8 daniel-mar 266
      WebBrowser1.LoadHTML(GetDosOutput('"'+GetPHPExe+'" "'+myURL+'"', ExtractFileDir(Application.ExeName)), myUrl);
7 daniel-mar 267
      Cancel := true;
268
    end;
269
  end;
270
  {$ENDREGION}
5 daniel-mar 271
end;
2 daniel-mar 272
 
273
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
274
begin
4 daniel-mar 275
  SynEdit1.Lines.SaveToFile(GetScrapFile);
13 daniel-mar 276
  FastPHPConfig.WriteInteger('User', 'FontSize', SynEdit1.Font.Size);
2 daniel-mar 277
end;
278
 
279
procedure TForm1.FormCreate(Sender: TObject);
280
begin
281
  HlpPrevPageIndex := -1;
282
  CurSearchTerm := '';
13 daniel-mar 283
  Caption := Caption + ' - ' + GetScrapFile;
284
  SrcRep := TFindReplace.Create(self);
285
  SrcRep.Editor := SynEdit1;
2 daniel-mar 286
end;
287
 
288
procedure TForm1.FormDestroy(Sender: TObject);
289
begin
290
  if Assigned(ChmIndex) then
291
  begin
292
    FreeAndNil(ChmIndex);
293
  end;
13 daniel-mar 294
  FreeAndNil(SrcRep);
2 daniel-mar 295
end;
296
 
297
procedure TForm1.FormShow(Sender: TObject);
298
var
299
  ScrapFile: string;
300
begin
301
  ScrapFile := GetScrapFile;
302
  if ScrapFile = '' then
303
  begin
10 daniel-mar 304
    Application.Terminate; // Close;
2 daniel-mar 305
    exit;
306
  end;
4 daniel-mar 307
  SynEdit1.Lines.LoadFromFile(ScrapFile);
2 daniel-mar 308
 
309
  PageControl1.ActivePage := PlaintextTabSheet;
310
 
311
  PageControl2.ActivePageIndex := 0; // Scraps
312
  HelpTabsheet.TabVisible := false;
5 daniel-mar 313
 
13 daniel-mar 314
  SynEdit1.Font.Size := FastPHPConfig.ReadInteger('User', 'FontSize', SynEdit1.Font.Size);
5 daniel-mar 315
  SynEdit1.SetFocus;
2 daniel-mar 316
end;
317
 
318
function TForm1.GetScrapFile: string;
319
begin
13 daniel-mar 320
  if FileExists(ParamStr(1)) then
321
    result := ParamStr(1)
322
  else
323
    result := FastPHPConfig.ReadString('Paths', 'ScrapFile', '');
2 daniel-mar 324
  if not FileExists(result) then
325
  begin
326
    if not OpenDialog3.Execute then
327
    begin
328
      result := '';
329
      exit;
330
    end;
331
 
332
    result := OpenDialog3.FileName;
333
 
334
    if not DirectoryExists(ExtractFilePath(result)) then
335
    begin
336
      ShowMessage('Path does not exist!');
337
      result := '';
338
      exit;
339
    end;
340
 
4 daniel-mar 341
    SynEdit1.Lines.Clear;
342
    SynEdit1.Lines.SaveToFile(result);
2 daniel-mar 343
 
344
    FastPHPConfig.WriteString('Paths', 'ScrapFile', result);
345
  end;
346
end;
347
 
348
procedure TForm1.Help;
349
var
350
  IndexFile, chmFile, w, url: string;
351
  internalHtmlFile: string;
352
begin
353
  if not Assigned(ChmIndex) then
354
  begin
355
    IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
356
    IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
357
    if FileExists(IndexFile) then
358
    begin
359
      ChmIndex := TMemIniFile.Create(IndexFile);
360
    end;
361
  end;
362
 
363
  if Assigned(ChmIndex) then
364
  begin
365
    IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
366
    // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
367
 
368
    chmFile := ChangeFileExt(IndexFile, '.chm');
369
    if not FileExists(chmFile) then
370
    begin
371
      FreeAndNil(ChmIndex);
372
    end;
373
  end;
374
 
375
  if not Assigned(ChmIndex) then
376
  begin
377
    if not OpenDialog1.Execute then exit;
378
 
379
    chmFile := OpenDialog1.FileName;
380
    if not FileExists(chmFile) then exit;
381
 
382
    IndexFile := ChangeFileExt(chmFile, '.ini');
383
 
384
    if not FileExists(IndexFile) then
385
    begin
386
      Panel1.Align := alClient;
387
      Panel1.Visible := true;
388
      Panel1.BringToFront;
389
      Screen.Cursor := crHourGlass;
390
      Application.ProcessMessages;
391
      try
392
        if not ParseCHM(chmFile) then
393
        begin
394
          ShowMessage('The CHM file is not a valid PHP documentation. Cannot use help.');
395
          exit;
396
        end;
397
      finally
398
        Screen.Cursor := crDefault;
399
        Panel1.Visible := false;
400
      end;
401
 
402
      if not FileExists(IndexFile) then
403
      begin
404
        ShowMessage('Unknown error. Cannot use help.');
405
        exit;
406
      end;
407
    end;
408
 
409
    FastPHPConfig.WriteString('Paths', 'HelpIndex', IndexFile);
410
    FastPHPConfig.UpdateFile;
411
 
412
    ChmIndex := TMemIniFile.Create(IndexFile);
413
  end;
414
 
4 daniel-mar 415
  w := GetWordUnderCaret(SynEdit1);
2 daniel-mar 416
  if w = '' then exit;
8 daniel-mar 417
  if CharInSet(w[1], ['0'..'9']) then exit;
2 daniel-mar 418
  w := StringReplace(w, '_', '-', [rfReplaceAll]);
419
  w := LowerCase(w);
420
  CurSearchTerm := w;
421
 
422
  internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, '');
423
  if internalHtmlFile = '' then
424
  begin
425
    HelpTabsheet.TabVisible := false;
426
    HlpPrevPageIndex := -1;
427
    ShowMessage('No help for "'+CurSearchTerm+'" available');
428
    Exit;
429
  end;
430
 
431
  url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
432
 
433
  HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
434
  HelpTabsheet.TabVisible := true;
435
  PageControl2.ActivePage := HelpTabsheet;
8 daniel-mar 436
  WebBrowser2.Navigate(url);
437
  WebBrowser2.Wait;
2 daniel-mar 438
end;
439
 
5 daniel-mar 440
procedure TForm1.GotoLineNo(LineNo:integer);
441
var
442
  line: string;
443
  i: integer;
2 daniel-mar 444
begin
5 daniel-mar 445
  SynEdit1.GotoLineAndCenter(LineNo);
446
 
447
  // Skip indent
448
  line := SynEdit1.Lines[SynEdit1.CaretY];
449
  for i := 1 to Length(line) do
450
  begin
8 daniel-mar 451
    if not CharInSet(line[i], [' ', #9]) then
5 daniel-mar 452
    begin
453
      SynEdit1.CaretX := i-1;
454
      break;
455
    end;
456
  end;
457
 
458
  PageControl2.ActivePage := TabSheet3{Scrap};
459
  if SynEdit1.CanFocus then SynEdit1.SetFocus;
2 daniel-mar 460
end;
461
 
8 daniel-mar 462
procedure TForm1.PageControl2Changing(Sender: TObject;
463
  var AllowChange: Boolean);
464
begin
465
  if PageControl2.ActivePage = HelpTabsheet then
466
    HlpPrevPageIndex := -1
467
  else
468
    HlpPrevPageIndex := PageControl2.ActivePageIndex;
469
 
470
  AllowChange := true;
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
 
486
function TForm1.MarkUpLineReference(cont: string): string;
487
var
488
  p, a, b: integer;
489
  num: integer;
490
  insert_a, insert_b: string;
491
begin
492
  // TODO: make it more specific to PHP error messages. "on line" is too broad.
493
  p := Pos(' on line ', cont);
494
  while p >= 1 do
495
  begin
496
    a := p+1;
497
    b := p+length(' on line ');
498
    num := 0;
8 daniel-mar 499
    while CharInSet(cont[b], ['0'..'9']) do
5 daniel-mar 500
    begin
501
      num := num*10 + StrToInt(cont[b]);
502
      inc(b);
503
    end;
504
 
505
    insert_b := '</a>';
8 daniel-mar 506
    insert_a := '<a href="'+FASTPHP_GOTO_URI_PREFIX+IntToStr(num)+'">';
5 daniel-mar 507
 
508
    insert(insert_b, cont, b);
509
    insert(insert_a, cont, a);
510
 
511
    p := b + Length(insert_a) + Length(insert_b);
512
 
513
    p := PosEx(' on line ', cont, p+1);
514
  end;
515
 
516
  result := cont;
517
end;
518
 
2 daniel-mar 519
end.