Subversion Repositories fastphp

Rev

Rev 7 | Rev 10 | 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
 
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
5 daniel-mar 31
// - Onlinehelp (www) aufrufen
2 daniel-mar 32
 
33
interface
34
 
35
uses
36
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
4 daniel-mar 37
  Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
38
  SynEditHighlighter, SynHighlighterPHP, SynEdit, SHDocVw_TLB;
2 daniel-mar 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;
4 daniel-mar 55
    SynEdit1: TSynEdit;
56
    SynPHPSyn1: TSynPHPSyn;
5 daniel-mar 57
    Panel2: TPanel;
58
    SynEditFocusTimer: TTimer;
59
    Button1: TButton;
60
    Button2: TButton;
61
    Button3: TButton;
2 daniel-mar 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);
5 daniel-mar 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);
2 daniel-mar 76
  private
77
    CurSearchTerm: string;
78
    HlpPrevPageIndex: integer;
79
    procedure Help;
80
    procedure ApplicationOnMessage(var Msg: tagMSG; var Handled: Boolean);
5 daniel-mar 81
    function MarkUpLineReference(cont: string): string;
2 daniel-mar 82
  protected
83
    ChmIndex: TMemIniFile;
5 daniel-mar 84
    procedure GotoLineNo(LineNo:integer);
2 daniel-mar 85
    function GetScrapFile: string;
86
  end;
87
 
88
var
89
  Form1: TForm1;
90
 
91
implementation
92
 
93
{$R *.dfm}
94
 
95
uses
8 daniel-mar 96
  Functions, StrUtils, WebBrowserUtils, FastPHPUtils;
2 daniel-mar 97
 
98
procedure TForm1.ApplicationOnMessage(var Msg: tagMSG; var Handled: Boolean);
5 daniel-mar 99
var
100
  val: string;
101
  lineno: integer;
102
begin
2 daniel-mar 103
  case Msg.message of
104
    WM_KEYUP:
105
    begin
106
      case Msg.wParam of
5 daniel-mar 107
        {$REGION 'Esc'}
2 daniel-mar 108
        VK_ESCAPE:
109
        begin
5 daniel-mar 110
          Handled := true;
2 daniel-mar 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;
5 daniel-mar 119
        {$ENDREGION}
120
 
8 daniel-mar 121
        {$REGION 'Ctrl+G (Go to line)'}
5 daniel-mar 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
 
8 daniel-mar 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)'}
5 daniel-mar 148
        VK_F1:
149
        begin
150
          if SynEdit1.Focused then
151
          begin
152
            Handled := true;
153
            Help;
154
          end;
155
        end;
8 daniel-mar 156
        {$ENDREGION}
5 daniel-mar 157
 
8 daniel-mar 158
        {$REGION 'F5 (Run)'}
5 daniel-mar 159
        VK_F5:
160
        begin
161
          Run(Self);
162
        end;
8 daniel-mar 163
        {$ENDREGION}
5 daniel-mar 164
 
8 daniel-mar 165
        {$REGION 'F9 (Run)'}
5 daniel-mar 166
        VK_F9:
167
        begin
168
          Run(Self);
169
        end;
8 daniel-mar 170
        {$ENDREGION}
2 daniel-mar 171
      end;
172
    end;
173
  end;
174
end;
175
 
176
procedure TForm1.Run(Sender: TObject);
177
begin
5 daniel-mar 178
  memo2.Lines.Text := '';
8 daniel-mar 179
  Webbrowser1.Clear;
5 daniel-mar 180
  Screen.Cursor := crHourGlass;
181
  Application.ProcessMessages;
182
 
183
  try
184
    SynEdit1.Lines.SaveToFile(GetScrapFile);
185
 
8 daniel-mar 186
    memo2.Lines.Text := RunPHPScript(GetScrapFile);
5 daniel-mar 187
 
8 daniel-mar 188
    Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
5 daniel-mar 189
 
190
    if IsTextHTML(memo2.lines.text) then
191
      PageControl1.ActivePage := HtmlTabSheet
192
    else
193
      PageControl1.ActivePage := PlaintextTabSheet;
194
  finally
195
    Screen.Cursor := crDefault;
2 daniel-mar 196
  end;
5 daniel-mar 197
end;
2 daniel-mar 198
 
5 daniel-mar 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;
2 daniel-mar 205
 
5 daniel-mar 206
procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
207
  const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
208
  Headers: OleVariant; var Cancel: WordBool);
209
var
8 daniel-mar 210
  s, myURL: string;
5 daniel-mar 211
  lineno: integer;
7 daniel-mar 212
  p: integer;
5 daniel-mar 213
begin
7 daniel-mar 214
  {$REGION 'Line number references (PHP errors and warnings)'}
8 daniel-mar 215
  if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
5 daniel-mar 216
  begin
217
    try
8 daniel-mar 218
      s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
5 daniel-mar 219
      if not TryStrToInt(s, lineno) then exit;
220
      GotoLineNo(lineno);
221
      SynEditFocusTimer.Enabled := true;
222
    finally
223
      Cancel := true;
224
    end;
8 daniel-mar 225
    Exit;
5 daniel-mar 226
  end;
7 daniel-mar 227
  {$ENDREGION}
228
 
8 daniel-mar 229
  {$REGION 'Intelligent browser (executes PHP scripts)'}
7 daniel-mar 230
  if URL <> 'about:blank' then
231
  begin
232
    myUrl := URL;
233
 
8 daniel-mar 234
    p := Pos('?', myUrl);
235
    if p >= 1 then myURL := copy(myURL, 1, p-1);
7 daniel-mar 236
 
8 daniel-mar 237
    // TODO: myURL urldecode
238
    // TODO: maybe we could even open that file in the editor!
7 daniel-mar 239
 
8 daniel-mar 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
7 daniel-mar 241
    begin
8 daniel-mar 242
      WebBrowser1.LoadHTML(GetDosOutput('"'+GetPHPExe+'" "'+myURL+'"', ExtractFileDir(Application.ExeName)), myUrl);
7 daniel-mar 243
      Cancel := true;
244
    end;
245
  end;
246
  {$ENDREGION}
5 daniel-mar 247
end;
2 daniel-mar 248
 
5 daniel-mar 249
procedure TForm1.Button1Click(Sender: TObject);
250
begin
251
  Run(Sender);
252
  SynEdit1.SetFocus;
253
end;
2 daniel-mar 254
 
5 daniel-mar 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;
2 daniel-mar 262
end;
263
 
5 daniel-mar 264
procedure TForm1.Button3Click(Sender: TObject);
265
var
266
  val: string;
267
  lineno: integer;
268
begin
269
  InputQuery('Go to', 'Line number:', val);
6 daniel-mar 270
  if not TryStrToInt(val, lineno) then
271
  begin
272
    if SynEdit1.CanFocus then SynEdit1.SetFocus;
273
    exit;
274
  end;
5 daniel-mar 275
  GotoLineNo(lineno);
276
end;
277
 
2 daniel-mar 278
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
279
begin
4 daniel-mar 280
  SynEdit1.Lines.SaveToFile(GetScrapFile);
2 daniel-mar 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;
4 daniel-mar 308
  SynEdit1.Lines.LoadFromFile(ScrapFile);
2 daniel-mar 309
 
310
  PageControl1.ActivePage := PlaintextTabSheet;
311
 
312
  PageControl2.ActivePageIndex := 0; // Scraps
313
  HelpTabsheet.TabVisible := false;
5 daniel-mar 314
 
315
  SynEdit1.SetFocus;
2 daniel-mar 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
 
4 daniel-mar 338
    SynEdit1.Lines.Clear;
339
    SynEdit1.Lines.SaveToFile(result);
2 daniel-mar 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
 
4 daniel-mar 412
  w := GetWordUnderCaret(SynEdit1);
2 daniel-mar 413
  if w = '' then exit;
8 daniel-mar 414
  if CharInSet(w[1], ['0'..'9']) then exit;
2 daniel-mar 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;
8 daniel-mar 433
  WebBrowser2.Navigate(url);
434
  WebBrowser2.Wait;
2 daniel-mar 435
end;
436
 
5 daniel-mar 437
procedure TForm1.GotoLineNo(LineNo:integer);
438
var
439
  line: string;
440
  i: integer;
2 daniel-mar 441
begin
5 daniel-mar 442
  SynEdit1.GotoLineAndCenter(LineNo);
443
 
444
  // Skip indent
445
  line := SynEdit1.Lines[SynEdit1.CaretY];
446
  for i := 1 to Length(line) do
447
  begin
8 daniel-mar 448
    if not CharInSet(line[i], [' ', #9]) then
5 daniel-mar 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;
2 daniel-mar 457
end;
458
 
8 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
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;
8 daniel-mar 496
    while CharInSet(cont[b], ['0'..'9']) do
5 daniel-mar 497
    begin
498
      num := num*10 + StrToInt(cont[b]);
499
      inc(b);
500
    end;
501
 
502
    insert_b := '</a>';
8 daniel-mar 503
    insert_a := '<a href="'+FASTPHP_GOTO_URI_PREFIX+IntToStr(num)+'">';
5 daniel-mar 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
 
2 daniel-mar 516
end.