diff options
author | Graeme Geldenhuys <graeme@mastermaths.co.za> | 2011-07-16 19:56:28 +0200 |
---|---|---|
committer | Graeme Geldenhuys <graeme@mastermaths.co.za> | 2011-07-16 19:56:28 +0200 |
commit | 7eee7a7d0ffb55fbee2b7d2e3f2903ad1f7711fc (patch) | |
tree | 94417b26c048f55849efb1fc92ecfdff0d1a58a0 /examples/apps/ide | |
parent | 2122524e4d56618197e4f0ddd69db49f3552bbfb (diff) | |
parent | 0a6e1179e7f192f4350a01074de86f77f0e927ca (diff) | |
download | fpGUI-7eee7a7d0ffb55fbee2b7d2e3f2903ad1f7711fc.tar.xz |
Merged fpgIDE project as a subdirectory examples/apps/ide/
Diffstat (limited to 'examples/apps/ide')
30 files changed, 16049 insertions, 0 deletions
diff --git a/examples/apps/ide/TODO b/examples/apps/ide/TODO new file mode 100644 index 00000000..bbd18ddf --- /dev/null +++ b/examples/apps/ide/TODO @@ -0,0 +1,51 @@ + + Personal todo list for fpGUI IDE project + + +Legend +====== +[ ] - not started yet +[o] - started but not complete +[x] - completed task. + + +fpGUI IDE +========= +[ ] Double click on lines in Messages window jumps to code line. +[ ] add a Console Output window to the IDE. +[ ] integrate GDB/MI into the IDE. + [ ] porting of basic unit to fpGUI + [ ] integration of Compiler Settings Dialog + [ ] Watches window implementation + [ ] Call Stack window + [ ] Watchpoints window + [ ] Assembly window + [ ] CPU window +[ ] Syntax highlighting with descent speed. +[ ] Basic Search dialog +[ ] Find in Files dialog +[ ] Regex support in all search dialogs +[ ] External Tools setup and usage +[ ] Keyboard Shortcuts dialog +[ ] Refactoring Tools +[ ] Interface/Implementation jumping within the editor using the normal + Ctrl+Shift+[up|down] key combo. +[ ] Variable tab stop support in the editor +[ ] Elastic Tabstops implementation. +[ ] Global Macro support. These macros exist in the IDE across projects. +[ ] Unit Testing framework integration (with DUnit2 project) +[ ] Code Templates support +[ ] File Browser tabsheet implementation. File navigation and opening of files. +[ ] GoTo Line Number dialog +[ ] Converting all UI to MiG Layout Manager based dialogs. + + +Completed +========= +[x] Project management. +[x] Project based marco support with hard-coded system macros +[x] pipe compiler messages out to the Messages window. +[x] Procedure List dialog + + + diff --git a/examples/apps/ide/images/constructor_16.bmp b/examples/apps/ide/images/constructor_16.bmp Binary files differnew file mode 100644 index 00000000..992fe262 --- /dev/null +++ b/examples/apps/ide/images/constructor_16.bmp diff --git a/examples/apps/ide/images/destructor_16.bmp b/examples/apps/ide/images/destructor_16.bmp Binary files differnew file mode 100644 index 00000000..0f0d0c2b --- /dev/null +++ b/examples/apps/ide/images/destructor_16.bmp diff --git a/examples/apps/ide/images/function_16.bmp b/examples/apps/ide/images/function_16.bmp Binary files differnew file mode 100644 index 00000000..8929a90b --- /dev/null +++ b/examples/apps/ide/images/function_16.bmp diff --git a/examples/apps/ide/images/gears_16.bmp b/examples/apps/ide/images/gears_16.bmp Binary files differnew file mode 100644 index 00000000..c9633504 --- /dev/null +++ b/examples/apps/ide/images/gears_16.bmp diff --git a/examples/apps/ide/images/gutter_vertical.bmp b/examples/apps/ide/images/gutter_vertical.bmp Binary files differnew file mode 100644 index 00000000..882e2f34 --- /dev/null +++ b/examples/apps/ide/images/gutter_vertical.bmp diff --git a/examples/apps/ide/src/builderthread.pas b/examples/apps/ide/src/builderthread.pas new file mode 100644 index 00000000..bfdc48b1 --- /dev/null +++ b/examples/apps/ide/src/builderthread.pas @@ -0,0 +1,128 @@ +unit BuilderThread; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils; + +type + TOutputLineEvent = procedure(Sender: TObject; const ALine: string) of object; + + TBuilderThread = class(TThread) + private + FBuildMode: integer; + FOnAvailableOutput: TOutputLineEvent; + OutputLine: string; + procedure DoOutputLine; + protected + procedure Execute; override; + public + procedure AfterConstruction; override; + property BuildMode: integer read FBuildMode write FBuildMode; + property OnAvailableOutput: TOutputLineEvent read FOnAvailableOutput write FOnAvailableOutput; + end; + +implementation + +uses + project + ,process + ,fpg_base + ,fpg_iniutils + ,fpg_utils + ,ideconst + ,idemacros + ; + +{ TBuilderThread } + +procedure TBuilderThread.AfterConstruction; +begin + inherited AfterConstruction; + FBuildMode := -1; // signals use of project's default build mode + FreeOnTerminate := True; +end; + +procedure TBuilderThread.Execute; +const + BufSize = 1024; //4096; +var + p: TProcess; + c: TfpgString; + unitdir: TfpgString; + Buf: string; + Count: integer; + i: integer; + LineStart: integer; +begin + unitdir := GProject.ProjectDir + GProject.UnitOutputDir; + unitdir := GMacroList.ExpandMacro(unitdir); + if not fpgDirectoryExists(unitdir) then + begin + {$IFDEF DEBUG} + writeln('DEBUG: TBuilderThread.Execute - Creating dir: ' + unitdir); + {$ENDIF} + fpgForceDirectories(unitDir); + end; + + p := TProcess.Create(nil); + p.Options := [poUsePipes, poStdErrToOutPut]; + p.ShowWindow := swoShowNormal; + p.CurrentDirectory := GProject.ProjectDir; + + // build compilation string + c := gINI.ReadString(cEnvironment, 'Compiler', ''); + c := c + GProject.GenerateCmdLine(False, BuildMode); + c := GMacroList.ExpandMacro(c); + +// AddMessage('Compile command: ' + c); + p.CommandLine := c; + try + p.Execute; + + { Now process the output } + OutputLine:=''; + SetLength(Buf,BufSize); + repeat + if (p.Output<>nil) then + begin + Count:=p.Output.Read(Buf[1],Length(Buf)); + end + else + Count:=0; + LineStart:=1; + i:=1; + while i<=Count do + begin + if Buf[i] in [#10,#13] then + begin + OutputLine:=OutputLine+Copy(Buf,LineStart,i-LineStart); + Synchronize(@DoOutputLine); + OutputLine:=''; + if (i<Count) and (Buf[i+1] in [#10,#13]) and (Buf[i]<>Buf[i+1]) then + inc(i); + LineStart:=i+1; + end; + inc(i); + end; + OutputLine:=Copy(Buf,LineStart,Count-LineStart+1); + until Count=0; + if OutputLine <> '' then + Synchronize(@DoOutputLine); + p.WaitOnExit; + finally + FreeAndNil(p); + end; + +end; + +procedure TBuilderThread.DoOutputLine; +begin + if Assigned(FOnAvailableOutput) then + FOnAvailableOutput(self, OutputLine); +end; + +end. + diff --git a/examples/apps/ide/src/filemonitor.pas b/examples/apps/ide/src/filemonitor.pas new file mode 100644 index 00000000..0e0fa7a8 --- /dev/null +++ b/examples/apps/ide/src/filemonitor.pas @@ -0,0 +1,32 @@ +unit filemonitor; + +{$mode objfpc} + +interface + +uses + Classes, SysUtils, fpg_main; + +type + TFileMonitor = class(TThread) + private + FInterval: integer; + public + procedure Execute; override; + property Interval: integer read FInterval write FInterval; + end; + +implementation + +{ TFileMonitor } + +procedure TFileMonitor.Execute; +begin + while not Terminated do + begin + + end; +end; + +end. + diff --git a/examples/apps/ide/src/fpg_textedit.pas b/examples/apps/ide/src/fpg_textedit.pas new file mode 100644 index 00000000..02aa5285 --- /dev/null +++ b/examples/apps/ide/src/fpg_textedit.pas @@ -0,0 +1,1881 @@ +{ + fpGUI - Free Pascal GUI Toolkit + + Copyright (C) 2006 - 2010 See the file AUTHORS.txt, included in this + distribution, for details of the copyright. + + See the file COPYING.modifiedLGPL, included in this distribution, + for details about redistributing fpGUI. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + Description: + A new Memo component. It's actually more a TextEdit or MulitLineEdit + component because it has a lot more features than simply a Memo. Features + include: gutter, line numbers in gutter, right edge margin, syntax + highlighting, much more optimised etc... +} + +unit fpg_textedit; + +{$mode objfpc}{$H+} +{.$Define gDEBUG} + +interface + +uses + Classes, SysUtils, fpg_base, fpg_main, fpg_widget, + fpg_scrollbar; + +type + // forward declaration + TfpgBaseTextEdit = class; + + TfpgGutter = class(TfpgWidget) + private + FOwner: TfpgBaseTextEdit; // convenience reference variable + FDigits: Integer; + FShowNum: Boolean; + FSpace: Integer; + FStartNum: Integer; + FZeroStart: Boolean; + procedure SetDigits(const AValue: Integer); + procedure SetShowNum(const AValue: Boolean); + procedure SetSpace(const AValue: Integer); + procedure SetStartNum(const AValue: Integer); + procedure DrawLineNums; + procedure SetZeroStart(const AValue: Boolean); + protected + procedure HandlePaint; override; + procedure HandleMouseScroll(x, y: integer; shiftstate: TShiftState; delta: smallint); override; + public + constructor CreateGutter(AOwner: TfpgBaseTextEdit); + function GetClientRect: TfpgRect; override; + property LeadingDigits: Integer read FDigits write SetDigits default 0; + property ShowNum: Boolean read FShowNum write SetShowNum default True; + property Space: Integer read FSpace write SetSpace default 2; + property StartNum: Integer read FStartNum write SetStartNum default 1; + property Width default 35; + property ZeroStart: Boolean read FZeroStart write SetZeroStart default False; + end; + + + TfpgDrawLineEvent = procedure(Sender: TObject; ALineText: TfpgString; + ALineIndex: Integer; ACanvas: TfpgCanvas; ATextRect: TfpgRect; + var AllowSelfDraw: Boolean) of object; + + + TfpgBaseTextEdit = class(TfpgWidget) + private + FFont: TfpgFont; + FFullRedraw: Boolean; + FLines: TStrings; + CaretPos: TPoint; + FOnDrawLine: TfpgDrawLineEvent; + FScrollBarStyle: TfpgScrollStyle; + MousePos: TPoint; + FChrW: Integer; + FChrH: Integer; + FTopLine: Integer; + FVisLines: Integer; + FVisCols: Integer; + StartNo, EndNo, StartOffs, EndOffs: Integer; + // Selection start and end line number + FSelStartNo, FSelEndNo: Integer; + // Selection start and end column + FSelStartOffs, FSelEndOffs: Integer; + FTabWidth: Integer; + HPos, VPos, XSize, YSize: Integer; + FMaxScrollH: Integer; + FVScrollBar: TfpgScrollBar; + FHScrollBar: TfpgScrollBar; + FTracking: Boolean; + FSelDrag: Boolean; + FSelected, FSelMouseDwn: Boolean; + FGutterPan: TfpgGutter; + FRightEdge: Boolean; + FRightEdgeCol: Integer; + FLineChanged: Integer; // force only one line to repaint if greater than -1 + + FLastScrollEventTime: TTime; // in milliseconds + FLastScrollEventTimeBefore: TTime; // in milliseconds + fmousewheelfrequmin: double; + fmousewheelfrequmax: double; + fmousewheeldeltamin: double; + fmousewheeldeltamax: double; + fmousewheelaccelerationmax: double; + + fwheelsensitivity: double; + function GetFontDesc: string; + function GetGutterShowLineNumbers: Boolean; + function GetGutterVisible: Boolean; + function GetHScrollPos: Integer; + function GetVScrollPos: Integer; + procedure SetFontDesc(const AValue: string); + procedure SetGutterShowLineNumbers(const AValue: Boolean); + procedure SetGutterVisible(const AValue: Boolean); + procedure SetHScrollPos(const AValue: Integer); + procedure SetLines(const AValue: TStrings); + procedure SetScrollBarStyle(const AValue: TfpgScrollStyle); + procedure SetTabWidth(const AValue: Integer); + procedure SetVScrollPos(const AValue: Integer); + procedure UpdateCharBounds; + procedure GetSelBounds(var AStartNo, AEndNo, AStartOffs, AEndOffs: Integer); + procedure UpdateScrollBars; + procedure VScrollBarMove(Sender: TObject; position: integer); + procedure HScrollBarMove(Sender: TObject; position: integer); + procedure SetVPos(p: Integer); + procedure SetHPos(p: Integer); + procedure UpdateScrollBarCoords; + procedure UpdateGutterCoords; + procedure KeyboardCaretNav(const ShiftState: TShiftState; const AKeyCode: Word); + procedure InitMemoObjects; + procedure SetRightEdge(const AValue: Boolean); + procedure SetRightEdgeCol(const AValue: Integer); + function calcmousewheeldelta(var info: TfpgMsgParmMouse; const fmin,fmax,deltamin,deltamax: double): double; + function mousewheelacceleration(const avalue: double): double; + function mousewheelacceleration(const avalue: integer): integer; + protected + { -- internal events -- } + procedure HandleShow; override; + procedure HandleResize(AWidth, AHeight: TfpgCoord); override; + procedure HandlePaint; override; + procedure HandleMouseEnter; override; + procedure HandleMouseExit; override; + procedure HandleLMouseDown(x, y: integer; shiftstate: TShiftState); override; + procedure HandleMouseScroll(x, y: integer; shiftstate: TShiftState; delta: smallint); override; + procedure HandleKeyPress(var keycode: word; var shiftstate: TShiftState; var consumed: boolean); override; + procedure HandleKeyChar(var AText: TfpgChar; var shiftstate: TShiftState; var consumed: boolean); override; + { -- local widget functions -- } + procedure DrawVisible; virtual; + procedure DrawLine(const ALineIndex, Y: Integer); virtual; + procedure FormatLine(const ALineIndex, X, Y: Integer); + procedure DrawCaret(const X, Y: Integer); virtual; + { -- to be published --} + property FontDesc: string read GetFontDesc write SetFontDesc; + property FullRedraw: Boolean read FFullRedraw write FFullRedraw default False; + property GutterVisible: Boolean read GetGutterVisible write SetGutterVisible default False; + property GutterShowLineNumbers: Boolean read GetGutterShowLineNumbers write SetGutterShowLineNumbers default True; + property Lines: TStrings read FLines write SetLines; + property ScrollBarStyle: TfpgScrollStyle read FScrollBarStyle write SetScrollBarStyle default ssAutoBoth; + property TabWidth: Integer read FTabWidth write SetTabWidth default 8; + property Tracking: Boolean read FTracking write FTracking default True; + property OnDrawLine: TfpgDrawLineEvent read FOnDrawLine write FOnDrawLine; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + function GetClientRect: TfpgRect; override; + function GetWordAtPos(const X, Y: Integer; out XBegin: Integer): TfpgString; + procedure GetRowColAtPos(const X, Y: Integer; out Row, Col: Integer); + procedure Clear; + procedure ScrollTo(X, Y: Integer); + procedure GotoLine(ALine: integer); + procedure DeleteSelection; + procedure SaveToFile(const AFileName: TfpgString); + procedure LoadFromFile(const AFileName: TfpgString); + property FontHeight: Integer read FChrH; + property FontWidth: Integer read FChrW; + property ScrollPos_H: Integer read GetHScrollPos write SetHScrollPos; + property ScrollPos_V: Integer read GetVScrollPos write SetVScrollPos; + property TopLine: Integer read FTopLine; + property VisibleLines: Integer read FVisLines; + property RightEdge: Boolean read FRightEdge write SetRightEdge default False; + property RightEdgeCol: Integer read FRightEdgeCol write SetRightEdgeCol default 80; + end; + + + TfpgTextEdit = class(TfpgBaseTextEdit) + published + property FontDesc; + property FullRedraw; + property GutterVisible; + property GutterShowLineNumbers; + property Lines; + property ScrollBarStyle; + property TabWidth; + property Tracking; + property OnDrawLine; + end; + + +implementation + +uses + fpg_dialogs{, fpg_constants}, fpg_stringutils, fpg_utils, math; + + +function GetNextWord(SLine: TfpgString; var PosX: Integer): Boolean; +const + ValidChars = ['a'..'z', 'A'..'Z', '0'..'9', '#']; +var + I, RetX: Integer; + FindNext: Boolean; + c: TfpgChar; +begin + Result := False; + if PosX > UTF8Length(SLine) then Exit; + FindNext := False; + RetX := 0; + for I := PosX to UTF8Length(SLine) do + begin + c := fpgCharAt(SLine, I); + { TODO -cUnicode Error : We need to fix c[i] usage. Also improve ValidChars definition. } + if not FindNext and not (c[1] in ValidChars) then + begin + FindNext := True; + Continue; + end; + if FindNext and (c[1] in ValidChars) then + begin + RetX := I; + Result := True; + Break; + end; + end; + if RetX < 1 then + Result := False; + PosX := RetX; +end; + + +{ TfpgGutter } + +procedure TfpgGutter.SetDigits(const AValue: Integer); +begin + if FDigits=AValue then exit; + FDigits:=AValue; +end; + +procedure TfpgGutter.SetShowNum(const AValue: Boolean); +begin + if FShowNum=AValue then exit; + FShowNum:=AValue; + Invalidate; +end; + +procedure TfpgGutter.SetSpace(const AValue: Integer); +begin + if FSpace=AValue then exit; + FSpace:=AValue; +end; + +procedure TfpgGutter.SetStartNum(const AValue: Integer); +begin + if FStartNum=AValue then exit; + FStartNum:=AValue; +end; + +procedure TfpgGutter.DrawLineNums; +var + r: TfpgRect; + I, MaxI, W, H, ZeroL: Integer; + s: TfpgString; + ltxtflags: TfpgTextFlags; +begin + if not FShowNum then + Exit; //==> + w := GetClientRect.Width - FSpace - 1; + H := FOwner.FChrH; + MaxI := FOwner.FVisLines; + ltxtflags := [txtRight, txtVCenter]; + Canvas.SetFont(FOwner.FFont); + r.SetRect(2, 0, W, H); + + for i := 0 to MaxI do + begin +// writeln('i=', i); + if FZeroStart then + S := IntToStr(FStartNum + i - 1) + else + S := IntToStr(FStartNum + i); + for ZeroL := Length(S) to FDigits do + S := '0' + S; + r.Top := i * h; + Canvas.DrawText(r, S, ltxtflags); + end; +end; + +procedure TfpgGutter.SetZeroStart(const AValue: Boolean); +begin + if FZeroStart=AValue then exit; + FZeroStart:=AValue; +end; + +procedure TfpgGutter.HandlePaint; +begin + inherited HandlePaint; + Canvas.Clear(clWindowBackground); + // Gutter right border + Canvas.SetColor(clHilite2); + Canvas.DrawLine(Width - 2, 0, Width - 2, Height - 1); + Canvas.SetColor(clShadow1); + Canvas.DrawLine(Width - 1, 0, Width - 1, Height - 1); + DrawLineNums; +end; + +procedure TfpgGutter.HandleMouseScroll(x, y: integer; shiftstate: TShiftState; + delta: smallint); +var + msg: TfpgMessageParams; +begin + inherited HandleMouseScroll(x, y, shiftstate, delta); + fillchar(msg, sizeof(msg), 0); // zero out the record - initialize it + msg.mouse.x := x; + msg.mouse.y := y; + msg.mouse.shiftstate := shiftstate; + msg.mouse.delta := delta; + fpgPostMessage(self, FOwner.FVScrollBar, FPGM_SCROLL, msg); +end; + +constructor TfpgGutter.CreateGutter(AOwner: TfpgBaseTextEdit); +begin + inherited Create(AOwner); + FOwner := AOwner; + FDigits := 0; + FShowNum := True; + FSpace := 2; + FStartNum := 1; + FZeroStart := False; + Width := 35; +end; + +function TfpgGutter.GetClientRect: TfpgRect; +begin + Result := inherited GetClientRect; + Result.Width := Result.Width - 2; // border right line takes up two pixels +end; + +{ TfpgBaseTextEdit } + +procedure TfpgBaseTextEdit.SetLines(const AValue: TStrings); +begin + FLines.Assign(AValue); + Invalidate; +end; + +procedure TfpgBaseTextEdit.SetScrollBarStyle(const AValue: TfpgScrollStyle); +begin + if FScrollBarStyle = AValue then + Exit; //==> + FScrollBarStyle := AValue; + UpdateScrollBarCoords; +end; + +function TfpgBaseTextEdit.GetFontDesc: string; +begin + Result := FFont.FontDesc; +end; + +function TfpgBaseTextEdit.GetGutterShowLineNumbers: Boolean; +begin + Result := FGutterPan.ShowNum; +end; + +function TfpgBaseTextEdit.GetGutterVisible: Boolean; +begin + Result := FGutterPan.Visible; +end; + +function TfpgBaseTextEdit.GetHScrollPos: Integer; +begin + Result := HPos; +end; + +function TfpgBaseTextEdit.GetVScrollPos: Integer; +begin + Result := VPos; +end; + +procedure TfpgBaseTextEdit.SetFontDesc(const AValue: string); +begin + FFont.Free; + FFont := fpgGetFont(AValue); + Invalidate; +end; + +procedure TfpgBaseTextEdit.SetGutterShowLineNumbers(const AValue: Boolean); +begin + FGutterPan.ShowNum := AValue; +end; + +procedure TfpgBaseTextEdit.SetGutterVisible(const AValue: Boolean); +begin + FGutterPan.Visible := AValue; + if FGutterPan.Visible then + UpdateGutterCoords; + Invalidate; +end; + +procedure TfpgBaseTextEdit.SetHScrollPos(const AValue: Integer); +begin + SetHPos(AValue); +end; + +procedure TfpgBaseTextEdit.SetTabWidth(const AValue: Integer); +begin + if AValue < 1 then + begin + { todo: add these to resourcestring section } + if csDesigning in ComponentState then + TfpgMessageDialog.Information(ClassName + ' Tip', 'Value for TabWidth must be greater than 0.'); + Exit; //==> + end; + FTabWidth := AValue; +end; + +procedure TfpgBaseTextEdit.SetVScrollPos(const AValue: Integer); +begin + SetVPos(AValue); +end; + +procedure TfpgBaseTextEdit.UpdateCharBounds; +begin + FChrW := FFont.TextWidth('W'); + FChrH := FFont.Height; + FVisLines := (GetClientRect.Height div FChrH) + 1; + if FGutterPan.Visible then + FVisCols := (GetClientRect.Width - FGutterPan.Width) div FChrW + else + FVisCols := GetClientRect.Width div FChrW; +end; + +{ Re-order StartXXX and EndXXX if user is selecting backwards } +procedure TfpgBaseTextEdit.GetSelBounds(var AStartNo, AEndNo, AStartOffs, + AEndOffs: Integer); +begin + if FSelStartNo <= FSelEndNo then + begin + AStartNo := FSelStartNo; + AEndNo := FSelEndNo; + if not ((AStartNo = AEndNo) and (FSelStartOffs > FSelEndOffs)) then + begin + AStartOffs := FSelStartOffs; + AEndOffs := FSelEndOffs; + end + else + begin + AStartOffs := FSelEndOffs; + AEndOffs := FSelStartOffs; + end; + end + else + begin + AStartNo := FSelEndNo; + AEndNo := FSelStartNo; + AStartOffs := FSelEndOffs; + AEndOffs := FSelStartOffs; + end; +end; + +procedure TfpgBaseTextEdit.UpdateScrollBars; +begin + FVScrollBar.Min := 0; + FVScrollBar.PageSize := FVisLines - 4; + FVScrollBar.Max := FLines.Count - FVisLines + 1; // +1 is so the last line is completely visible + FVScrollBar.Position := VPos; + if FLines.Count > 0 then + FVScrollBar.SliderSize := FVisLines / FLines.Count; + FVScrollBar.Visible := FLines.Count > FVisLines; + if FVScrollBar.Visible then + FVScrollBar.RepaintSlider; + + FHScrollBar.Min := 0; + FHScrollBar.PageSize := FVisCols div 2; //FMaxScrollH div 4; + FHScrollBar.Max := FMaxScrollH - FVisCols + 1;// div 2; + FHScrollBar.Position := HPos; + FHScrollBar.SliderSize := FVisCols / FMaxScrollH; + FHScrollBar.Visible := FMaxScrollH > FVisCols; + if FHScrollBar.Visible then + FHScrollBar.RepaintSlider; + + UpdateScrollBarCoords; + UpdateCharBounds; +end; + +procedure TfpgBaseTextEdit.VScrollBarMove(Sender: TObject; position: integer); +begin + //if FDropList.Visible then + //FDropList.Visible := False; + //FDropTimeCount := 0; + //FLastDropPos.x := -1; + //FLastDropPos.y := -1; + if FTracking then + SetVPos(position); + //case ScrollCode of + //SB_LINEUP: SetVPos(VPos - 1); + //SB_LINEDOWN: SetVPos(VPos + 1); + //SB_PAGEUP: SetVPos(VPos - FVisLines); + //SB_PAGEDOWN: SetVPos(VPos + FVisLines); + //SB_THUMBPOSITION: SetVPos(Pos); + //SB_THUMBTRACK: if FTracking then SetVPos(Pos); + //SB_TOP: SetVPos(0); + //SB_BOTTOM: SetVPos(YSize); + //end; +end; + +procedure TfpgBaseTextEdit.HScrollBarMove(Sender: TObject; position: integer); +begin + //if FDropList.Visible then + //FDropList.Visible := False; + //FDropTimeCount := 0; + //FLastDropPos.x := -1; + //FLastDropPos.y := -1; + + if FTracking then + SetHPos(position); + + //case ScrollCode of + //SB_LINERIGHT: SetHPos(HPos + 1); + //SB_LINELEFT: SetHPos(HPos - 1); + //SB_PAGEUP: SetHPos(HPos - FVisLines); + //SB_PAGEDOWN: SetHPos(HPos + FVisLines); + //SB_THUMBPOSITION: SetHPos(Pos); + //SB_THUMBTRACK: if FTracking then SetHPos(Pos); + //SB_TOP: SetHPos(0); + //SB_BOTTOM: SetHPos(XSize); + //end; +end; + +procedure TfpgBaseTextEdit.SetVPos(p: Integer); +var + OldPos: Integer; +// R: TfpgRect; +begin + OldPos := VPos; + VPos := p; + + {$IFDEF gDEBUG} + writeln('OldPos:', OldPos, ' NewPos:', VPos, ' SB.Max:', FVScrollBar.Max); + {$ENDIF} + +// FVScrollBar.Position := VPos; + +// R := GetClientRect; + if OldPos - VPos <> 0 then + begin + { todo: implement scrolling children } +// ScrollChildren(0, (OldPos - VPos) * FChrH); + FTopLine := VPos; + + if FFullRedraw then + Invalidate + else + if (FTopLine + (FVisLines-1)) <= FLines.Count then + Invalidate; + { TODO : Implement scrolling events } + //if Assigned(FOnScrolled_V) then + //FOnScrolled_V(Self); + //if Assigned(FOnTextScrolled) then + //FOnTextScrolled(Self, FTopLine, FTopLine + FVisLines + 1,HPos, FMaxScrollH); + end; +end; + +procedure TfpgBaseTextEdit.SetHPos(p: Integer); +var + OldPos: Integer; +// R: TfpgRect; +begin + OldPos := HPos; + HPos := p; + + {$IFDEF gDEBUG} + writeln('OldPos:', OldPos, ' NewPos:', HPos, ' SB.Max:', FHScrollBar.Max); + {$ENDIF} + +// R := GetClientRect; + if OldPos - HPos <> 0 then + begin + { TODO : Implemente scrolling children } +// ScrollChildren((OldPos - HPos), 0); + //if FFullRedraw then + Invalidate; + //else + //DrawVisible; + { TODO : Implement scrolling events } + //if Assigned(FOnScrolled_H) then + //FOnScrolled_H(Self); + //if Assigned(FOnTextScrolled) then + //FOnTextScrolled(Self, FTopLine, FTopLine + FVisLines, HPos, FMaxScrollH); + end; +end; + +procedure TfpgBaseTextEdit.UpdateScrollBarCoords; +var + HWidth: integer; + VHeight: integer; + r: TfpgRect; +begin + r := GetClientRect; + VHeight := r.Height; + HWidth := r.Width; + + FHScrollBar.Top := Height - FHScrollBar.Height - r.Top; + FHScrollBar.Left := r.Top; + FHScrollBar.Width := HWidth; + + FVScrollBar.Top := r.Top; + FVScrollBar.Left := Width - FVScrollBar.Width - r.Top; + FVScrollBar.Height := VHeight; + + FVScrollBar.UpdateWindowPosition; + FHScrollBar.UpdateWindowPosition; +end; + +procedure TfpgBaseTextEdit.UpdateGutterCoords; +var + r: TfpgRect; +begin + r := GetClientRect; + if FGutterPan.Visible then + FGutterPan.SetPosition(r.Left, r.Top, FGutterPan.Width, r.Height); +end; + +{ This procedure is used to set caret position on keyboard navigation and + to set selection if Shift key is pressed. } +procedure TfpgBaseTextEdit.KeyboardCaretNav(const ShiftState: TShiftState; const AKeyCode: Word); +var + SaveXCaret: Integer; + + procedure CtrlKeyLeftKey; + var + S: TfpgString; + XB: Integer; + begin + S := GetWordAtPos(CaretPos.X, CaretPos.Y, XB); + if (S <> '') and (XB > -1) then + begin + if FSelected then + FSelEndOffs := XB; + CaretPos.X := XB; + end + else + begin + if FSelected then + FSelEndOffs := 0; + CaretPos.X := 0; + end; + end; + + procedure CtrlKeyRightKey; + var + S: TfpgString; + I: Integer; + NotFindIt: Boolean; + begin + if CaretPos.Y <= pred(FLines.Count) then + begin + NotFindIt := True; + while NotFindIt do + begin + S := FLines[CaretPos.Y]; + I := CaretPos.X; + if GetNextWord(S, I) then + begin + CaretPos.X := I - 1; + NotFindIt := False; + end + else + begin + CaretPos.Y := CaretPos.Y + 1; + CaretPos.X := 0; + NotFindIt := False; + end; + if CaretPos.Y > pred(FLines.Count) then + begin + NotFindIt := False; + CaretPos.X := 0; + end; + end; + end + else + CaretPos.X := 0; + end; + +begin + case AKeyCode of + keyLeft: + begin + CaretPos.X := CaretPos.X - 1; + if CaretPos.X < 0 then + begin + if CaretPos.Y > 0 then + begin + if CaretPos.Y <= (FLines.Count-1) then + begin + if (ssCtrl in ShiftState) and (CaretPos.Y > 0) then + begin + CaretPos.Y := CaretPos.Y - 1; + CaretPos.X := UTF8Length(FLines[CaretPos.Y]); + if FSelected then + begin + FSelEndNo := CaretPos.Y; + FSelEndOffs := CaretPos.X; + end; + Exit; + end; + end; + CaretPos.Y := CaretPos.Y - 1; + CaretPos.X := UTF8Length(FLines[CaretPos.Y]); + end + else + begin + CaretPos.X := 0; + end; + end; + if ssShift in ShiftState then + begin + if not FSelected then + begin + if CaretPos.Y <= (FLines.Count-1) then + if CaretPos.X > UTF8Length(FLines[CaretPos.Y]) then + CaretPos.Y := UTF8Length(FLines[CaretPos.Y]) - 1; + FSelected := True; + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X + 1; + FSelEndNo := CaretPos.Y; + if ssCtrl in ShiftState then + CtrlKeyLeftKey + else + FSelEndOffs := CaretPos.X; + end + else + begin + FSelEndNo := CaretPos.Y; + if ssCtrl in ShiftState then + CtrlKeyLeftKey + else + FSelEndOffs := CaretPos.X; + if FSelEndNo <= (FLines.Count-1) then + begin + if FSelEndOffs > UTF8Length(FLines[FSelEndNo]) then + begin + FSelEndOffs := UTF8Length(FLines[FSelEndNo]) - 1; + CaretPos.X := FSelEndOffs; + end; + end + else + begin + FSelEndOffs := 0; + CaretPos.X := 0; + end; + end; + FSelected := (FSelStartNo <> FSelEndNo) or (FSelStartOffs <> FSelEndOffs); + Exit; + end; + if FSelected then + begin + FSelected := False; + end; + if ssCtrl in ShiftState then + begin + CtrlKeyLeftKey; + end; + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X; + end; + + keyRight: + begin + CaretPos.X := CaretPos.X + 1; + if CaretPos.X > FMaxScrollH then + begin + FMaxScrollH := FMaxScrollH + 2; + UpdateScrollBars; + end; + if ssShift in ShiftState then + begin + if not FSelected then + begin + FSelected := True; + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X - 1; + if ssCtrl in ShiftState then + CtrlKeyRightKey; + FSelEndNo := CaretPos.Y; + FSelEndOffs := CaretPos.X; + end + else + begin + if ssCtrl in ShiftState then + CtrlKeyRightKey; + FSelEndNo := CaretPos.Y; + FSelEndOffs := CaretPos.X; + end; + FSelected := (FSelStartNo <> FSelEndNo) or (FSelStartOffs <> FSelEndOffs); + Exit; + end; + if FSelected then + begin + FSelected := False; + end; + if ssCtrl in ShiftState then + begin + CtrlKeyRightKey; + end; + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X; + end; + + keyUp: + begin + if CaretPos.Y = 0 then + Exit; + if not (ssShift in ShiftState) and not (ssCtrl in ShiftState) then + begin + CaretPos.Y := CaretPos.Y - 1; + // scroll text + if FVScrollBar.Visible and (CaretPos.Y < FTopLine) then + FVScrollBar.LineUp; + if FSelected then + begin + FSelected := False; + Exit; + end; + FSelStartNo := CaretPos.Y; + Exit; + end + else if (ssCtrl in ShiftState) and not (ssShift in ShiftState) then + begin + CaretPos.Y := CaretPos.Y - 1; + if FVScrollBar.Visible then + FVScrollBar.LineUp; // VScrollBarMove(self, FVScrollBar.Position-1); + FSelStartNo := CaretPos.Y; + Exit; + end + else if not (ssCtrl in ShiftState) and (ssShift in ShiftState) then + begin + CaretPos.Y := CaretPos.Y - 1; + if not FSelected then + begin + FSelStartNo := CaretPos.Y + 1; + FSelStartOffs := CaretPos.X; + FSelEndNo := CaretPos.Y; + FSelEndOffs := CaretPos.X; + FSelected := True; + end + else + begin + FSelEndNo := CaretPos.Y; + FSelEndOffs := CaretPos.X; + FSelected := (FSelStartNo <> FSelEndNo) or (FSelStartOffs <> FSelEndOffs); + end; + end; + end; + + keyDown: + begin + if CaretPos.Y >= FLines.Count then + Exit; + if not (ssShift in ShiftState) and not (ssCtrl in ShiftState) then + begin + CaretPos.Y := CaretPos.Y + 1; + // scroll text + if FVScrollBar.Visible and (CaretPos.Y > FTopLine+FVisLines-2) then + FVScrollBar.LineDown; + if FSelected then + begin + FSelected := False; + Exit; + end; + FSelStartNo := CaretPos.Y; + Exit; + end + else if (ssCtrl in ShiftState) and not (ssShift in ShiftState) then + begin + CaretPos.Y := CaretPos.Y + 1; + if FVScrollBar.Visible then + FVScrollBar.LineDown; // VScrollBarMove(self, FVScrollBar.Position+1); + FSelStartNo := CaretPos.Y; + Exit; + end + else if not (ssCtrl in ShiftState) and (ssShift in ShiftState) then + begin + CaretPos.Y := CaretPos.Y + 1; + if not FSelected then + begin + FSelStartNo := CaretPos.Y - 1; + FSelStartOffs := CaretPos.X; + FSelEndNo := CaretPos.Y; + FSelEndOffs := CaretPos.X; + FSelected := True; + end + else + begin + FSelEndNo := CaretPos.Y; + FSelEndOffs := CaretPos.X; + FSelected := (FSelStartNo <> FSelEndNo) or (FSelStartOffs <> FSelEndOffs); + end; + end; + end; + + keyHome: + begin + if not (ssCtrl in ShiftState) and not (ssShift in ShiftState) then + begin + CaretPos.X := 0; + if FSelected then + begin + FSelected := False; + Exit; + end; + end; + if ssCtrl in ShiftState then + begin + if ssShift in ShiftState then + begin + if not FSelected then + begin + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X; + FSelected := True; + end; + CaretPos.Y := 0; + CaretPos.X := 0; + FSelEndNo := 0; + FSelEndOffs := 0; + end + else + begin + CaretPos.Y := 0; + CaretPos.X := 0; + end; + Exit; + end; + if ssShift in ShiftState then + begin + if not FSelected then + begin + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X; + FSelected := True; + end; + CaretPos.X := 0; + FSelEndNo := CaretPos.Y; + FSelEndOffs := 0; + if FSelEndNo = FSelStartNo then + FSelected := (FSelStartOffs <> FSelEndOffs); + end; + end; + + keyEnd: + begin + if not (ssCtrl in ShiftState) and not (ssShift in ShiftState) then + begin + if CaretPos.Y <= pred(FLines.Count) then + CaretPos.X := Length(FLines[CaretPos.Y]) + else + CaretPos.X := 0; + end; + if ssCtrl in ShiftState then + begin + if ssShift in ShiftState then + begin + if not FSelected then + begin + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X; + FSelected := True; + end; + CaretPos.Y := pred(FLines.Count); + CaretPos.X := Length(FLines[CaretPos.Y]); + FSelEndNo := pred(FLines.Count); + FSelEndOffs := Length(FLines[CaretPos.Y]); + end else + begin + CaretPos.Y := pred(FLines.Count); + CaretPos.X := Length(FLines[CaretPos.Y]); + end; + Exit; + end; + if ssShift in ShiftState then + begin + if not FSelected then + begin + FSelStartNo := CaretPos.Y; + if CaretPos.Y <= pred(FLines.Count) then + if CaretPos.X > Length(FLines[CaretPos.Y]) then + CaretPos.X := Length(FLines[CaretPos.Y]); + FSelStartOffs := CaretPos.X; + FSelected := True; + end; + if CaretPos.Y <= pred(FLines.Count) then + CaretPos.X := Length(FLines[CaretPos.Y]) + else + CaretPos.X := 0; + FSelEndNo := CaretPos.Y; + FSelEndOffs := CaretPos.X; + if FSelEndNo = FSelStartNo then + FSelected := (FSelStartOffs <> FSelEndOffs); + end; + end; + + keyPageUp, keyPageDown: + begin + if not FSelected then + begin + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X; + end; + SaveXCaret := CaretPos.Y - FTopLine; + if AKeyCode = keyPageUp then + begin + if VPos = 0 then + begin + CaretPos.Y := 0; + CaretPos.X := 0; + end + else + begin + // scroll text + if FVScrollBar.Visible then + FVScrollBar.PageUp; + // restore caret at same line offset as before + CaretPos.Y := FTopLine + SaveXCaret; + end; + end + else + begin + if VPos > (FLines.Count - FVisLines) then + begin + CaretPos.Y := FLines.Count-1; + CaretPos.X := Length(FLines[CaretPos.Y]); + end + else + begin + // scroll text + if FVScrollBar.Visible then + FVScrollBar.PageDown; + // restore caret at same line offset as before + CaretPos.Y := FTopLine + SaveXCaret; + end; + end; + if ssShift in ShiftState then + begin + FSelEndNo := CaretPos.Y; + FSelEndOffs := CaretPos.X; + if not FSelected then + FSelected := True; + end; + end; + end; +end; + +procedure TfpgBaseTextEdit.InitMemoObjects; +begin + FGutterPan := TfpgGutter.CreateGutter(Self); + with FGutterPan do + begin + Left := -Width - 1; + Visible := False; + end; +end; + +procedure TfpgBaseTextEdit.SetRightEdge(const AValue: Boolean); +begin + if FRightEdge <> AValue then + begin + FRightEdge := AValue; + Invalidate; + end; +end; + +procedure TfpgBaseTextEdit.SetRightEdgeCol(const AValue: Integer); +var + v: Integer; +begin + v := AValue; + if v < 20 then v := 20; + if v > 160 then v := 160; + if FRightEdgeCol <> v then + begin + FRightEdgeCol := v; + if FRightEdge then + Invalidate; + end; +end; + +procedure TfpgBaseTextEdit.HandleShow; +begin + inherited HandleShow; + HandleResize(Width, Height); +end; + +procedure TfpgBaseTextEdit.HandleResize(AWidth, AHeight: TfpgCoord); +begin + inherited HandleResize(AWidth, AHeight); + if HasHandle then + begin + UpdateCharBounds; + UpdateScrollBars; + UpdateGutterCoords; + end; +end; + +procedure TfpgBaseTextEdit.HandlePaint; +begin + Canvas.ClearClipRect; + if FLineChanged > -1 then + begin + { TODO: We would like Vertical and Underline cursor painting at some point } + DrawLine(FLineChanged, CaretPos.Y * FChrH); + FLineChanged := -1; + Exit; + end; + + // normal house keeping + Canvas.Clear(clBoxColor); + fpgStyle.DrawControlFrame(Canvas, 0, 0, Width, Height); + Canvas.Font := FFont; + Canvas.SetClipRect(GetClientRect); + + // do the actual drawing + DrawVisible; + DrawCaret(CaretPos.X, CaretPos.Y); + Canvas.ClearClipRect; + + // The little square in the bottom right corner + if FHScrollBar.Visible and FVScrollBar.Visible then + begin + Canvas.SetColor(clButtonFace); + Canvas.FillRectangle(FHScrollBar.Left+FHScrollBar.Width, + FVScrollBar.Top+FVScrollBar.Height, + FVScrollBar.Width, + FHScrollBar.Height); + end; +end; + +procedure TfpgBaseTextEdit.HandleMouseEnter; +begin + inherited HandleMouseEnter; + MouseCursor := mcIBeam; +end; + +procedure TfpgBaseTextEdit.HandleMouseExit; +begin + inherited HandleMouseExit; + MouseCursor := mcDefault; +end; + +procedure TfpgBaseTextEdit.HandleLMouseDown(x, y: integer; shiftstate: TShiftState); +var + RNo: Integer; + CNo: Integer; +begin + inherited HandleLMouseDown(x, y, shiftstate); + if FGutterPan.Visible and (X <= FGutterPan.Width) then Exit; //==> + + GetRowColAtPos(X + HPos * FChrW, Y + VPos * FChrH, RNo, CNo); + CaretPos.X := CNo; + CaretPos.Y := RNo; + FSelDrag := False; + if (RNo in [FSelStartNo..FSelEndNo]) or (RNo in [FSelEndNo..FSelStartNo]) then + begin + if (FSelStartNo = FSelEndNo) and ((CNo in [FSelStartOffs..FSelEndOffs]) or + (CNo in [FSelEndOffs..FSelStartOffs])) then FSelDrag := True; + if FSelStartNo <> FSelEndNo then + begin + FSelDrag := True; + if (RNo = FSelStartNo) and (FSelStartNo < FSelEndNo) and (CNo < FSelStartOffs) then + FSelDrag := False; + if (RNo = FSelStartNo) and (FSelStartNo > FSelEndNo) and (CNo > FSelStartOffs) then + FSelDrag := False; + if (RNo = FSelEndNo) and (FSelStartNo < FSelEndNo) and (CNo > FSelStartOffs) then + FSelDrag := False; + if (RNo = FSelEndNo) and (FSelStartNo > FSelEndNo) and (CNo < FSelStartOffs) then + FSelDrag := False; + end; + end; + if FSelDrag then + begin +// writeln(' SelDrag is True!!!!'); +// Exit; //==> + end; + if not (ssShift in ShiftState) then + begin + if FSelected then + begin + { Erase old selection, if any... } + FSelected := False; + end; + FSelStartNo := RNo; + FSelEndNo := FSelStartNo; + FSelStartOffs := CNo; + FSelEndOffs := FSelStartOffs; +// FSelected := True; + FSelMouseDwn := True; + end + else + begin + FSelEndNo := RNo; + FSelEndOffs := CNo; + FSelected := True; + end; + Invalidate; +end; + +function TfpgBaseTextEdit.calcmousewheeldelta(var info: TfpgMsgParmMouse; + const fmin,fmax,deltamin,deltamax: double): double; +var + frequ: double; +begin + if (FLastScrollEventTime <> 0) and (FLastScrollEventTime <> info.timestamp) then + begin + frequ := 0.00003 /(info.timestamp-FLastScrollEventTime); // Hz + {$IFDEF gDEBUG} + writeln('frequ = ', Format('%3.9f', [frequ])); + {$ENDIF} + if frequ > fmax then + begin + frequ := fmax; + end; + if frequ < fmin then + begin + frequ := fmin; + end; + result := (frequ*(deltamax-deltamin)+(deltamin*fmax-deltamax*fmin)) / (fmax-fmin); + end + else + begin + result := deltamin; + end; +{ + if d > 0 then // down + begin + d := - d; + end; +} + {$IFDEF gDEBUG} + writeln('result = ', Format('%3.6f', [result])); + {$ENDIF} +end; + +function TfpgBaseTextEdit.mousewheelacceleration(const avalue: double): double; +var + info: TfpgMsgParmMouse; + d: double; +begin + info.timestamp := FLastScrollEventTime + FLastScrollEventTime - + FLastScrollEventTimeBefore; + d := calcmousewheeldelta(info,fmousewheelfrequmin,fmousewheelfrequmax,1, + fmousewheelaccelerationmax); + result := avalue * d; +end; + +function TfpgBaseTextEdit.mousewheelacceleration(const avalue: integer): integer; +begin + result:= round(mousewheelacceleration(avalue*1.0)); +end; + +procedure TfpgBaseTextEdit.HandleMouseScroll(x, y: integer; shiftstate: TShiftState; + delta: smallint); +var + msg: TfpgMessageParams; + ldelta: integer; +begin + inherited HandleMouseScroll(x, y, shiftstate, delta); + fillchar(msg, sizeof(msg), 0); // zero out the record - initialize it + msg.mouse.x := x; + msg.mouse.y := y; + msg.mouse.shiftstate := shiftstate; + + FLastScrollEventTimeBefore := FLastScrollEventTime; + FLastScrollEventTime := Now; + + { calculate a modified delta based on mouse scroll sensitivity setting } + ldelta := round(mousewheelacceleration(delta*fwheelsensitivity)); + + msg.mouse.delta := ldelta; + + fpgPostMessage(self, FVScrollBar, FPGM_SCROLL, msg); +end; + +procedure TfpgBaseTextEdit.HandleKeyPress(var keycode: word; + var shiftstate: TShiftState; var consumed: boolean); +var + SLine: TfpgString; + AddS: TfpgString; + Y: Integer; + X: Integer; + CaretScroll: Boolean; +begin + {$IFDEF gDEBUG} + writeln('>> TfpgBaseTextEdit.HandleKeyPress'); + {$ENDIF} +// inherited HandleKeyPress(keycode, shiftstate, consumed); + + { Add lines as we go, so we can cursor past EOF. } + { todo: This behaviour should be optional } + if CaretPos.Y > pred(FLines.Count) then + begin + FLines.Add(''); + FVScrollBar.Max := FVScrollBar.Max + 1; + Exit; //==> + end; +// if (keycode = keyEscape) or (ssCtrl in ShiftState) then +// Exit; //==> + + if FSelected then + begin + DeleteSelection; + if keycode = keyBackSpace then + Exit; //==> + end; + SLine := FLines[CaretPos.Y]; + + case keycode of + keyBackspace: + begin + if UTF8Length(SLine) >= CaretPos.X then + X := CaretPos.X + else + begin + X := UTF8Length(SLine); + CaretPos.X := X; + end; + UTF8Delete(SLine, X, 1); + FLines[CaretPos.Y] := SLine; + CaretPos.X := CaretPos.X - 1; + if CaretPos.X < 0 then + begin + if CaretPos.Y > 0 then + begin + AddS := FLines[CaretPos.Y]; { store any text from current line } + FLines.Delete(CaretPos.Y); + CaretPos.Y := CaretPos.Y - 1; + CaretPos.X := UTF8Length(FLines[CaretPos.Y]); { reposition cursor } + if AddS <> '' then + FLines[CaretPos.Y] := FLines[CaretPos.Y] + AddS; { add stored text to new current line } + end + else + begin + CaretPos.X := 0; + end; + end; + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X; + end; + + keyTab: + begin + end; + + keyReturn: + begin + AddS := ''; + if UTF8Length(SLine) > CaretPos.X then + begin + AddS := Copy(SLine, CaretPos.X + 1, Length(SLine) - CaretPos.X + 1); + Delete(SLine, CaretPos.X + 1, Length(SLine) - CaretPos.X); + FLines[CaretPos.Y] := SLine; + end; + if CaretPos.Y = pred(FLines.Count) then + FLines.Add(AddS) + else + if CaretPos.Y < pred(FLines.Count) then + FLines.Insert(CaretPos.Y + 1, AddS) + else + if CaretPos.Y > FLines.Count then + FLines.Add(''); { ??? } + CaretPos.Y := CaretPos.Y + 1; + CaretPos.X := 0; + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X; + end; + + keyLeft, keyRight, keyUp, keyDown, keyHome, keyEnd, keyPrior, keyNext: + begin + KeyboardCaretNav(ShiftState, keycode); + CaretScroll := True; + end; + end; + + if CaretPos.X > HPos + FVisCols then + ScrollPos_H := CaretPos.X - FVisCols + else if CaretPos.X < HPos then + ScrollPos_H := CaretPos.X; + + if CaretPos.Y < (FTopLine+1) then + ScrollPos_V := CaretPos.Y + else if CaretPos.Y > (FTopLine + FVisLines - 2) then + ScrollPos_V := CaretPos.Y - FVisLines + 2; + + Invalidate; + {$IFDEF gDEBUG} + writeln('<< TfpgBaseTextEdit.HandleKeyPress'); + {$ENDIF} +end; + +procedure TfpgBaseTextEdit.HandleKeyChar(var AText: TfpgChar; + var shiftstate: TShiftState; var consumed: boolean); +var + SLine: TfpgString; + Fill: Integer; +begin + {$IFDEF gDEBUG} + writeln('>> TfpgBaseTextEdit.HandleKeyChar'); + {$ENDIF} + if not consumed then + begin + // Handle only printable characters + // UTF-8 characters beyond ANSI range are supposed to be printable + if ((Ord(AText[1]) > 31) and (Ord(AText[1]) < 127)) or (Length(AText) > 1) then + begin + SLine := FLines[CaretPos.Y]; + + { cursor was somewhere in whitespace, so we need to fill up the spaces } + if UTF8Length(SLine) < CaretPos.X + 1 then + for Fill := Length(SLine) to CaretPos.X + 1 do + SLine := SLine + ' '; + + UTF8Insert(AText, SLine, CaretPos.X + 1); + FLines[CaretPos.Y] := SLine; + CaretPos.X := CaretPos.X + 1; + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X; + consumed := True; + end; + end; + + if consumed then + RePaint + else + inherited HandleKeyChar(AText, shiftstate, consumed); + {$IFDEF gDEBUG} + writeln('<< TfpgBaseTextEdit.HandleKeyChar'); + {$ENDIF} +end; + +procedure TfpgBaseTextEdit.DrawVisible; +var + I, Y, cntVis: Integer; +begin + Y := 0; + cntVis := 1; + GetSelBounds(StartNo, EndNo, StartOffs, EndOffs); + + // Draw right edge line first, so text can draw over it. + if FRightEdge then + begin + if not FGutterPan.Visible then + begin + with Canvas do + begin + Canvas.Color := clShadow1; // FEnvironment.RightEdgeColor; + Canvas.DrawLine((FRightEdgeCol * FChrW) - (HPos * FChrW), GetClientRect.Top, (FRightEdgeCol * FChrW) - (HPos * FChrW), GetClientRect.Height); + end; + end else + with Canvas do + begin + Canvas.Color := clShadow1; // FEnvironment.RightEdgeColor; + Canvas.DrawLine((FRightEdgeCol * FChrW) - (HPos * FChrW) + FGutterPan.Width, GetClientRect.Top, (FRightEdgeCol * FChrW) - (HPos * FChrW) + FGutterPan.Width, GetClientRect.Height); + end; + end; + + // Draw lines of text + for I := FTopLine to FTopLine + FVisLines do + begin + DrawLine(I, Y); + Y := Y + FChrH; + cntVis := cntVis + 1; + if cntVis > FVisLines then + Break; //==> + end; +end; + +procedure TfpgBaseTextEdit.DrawLine(const ALineIndex, Y: Integer); +var + X: Integer; + GSz: Integer; +begin + if FGutterPan.Visible then + begin + GSz := FGutterPan.Width + GetClientRect.Left + 1; + if FGutterPan.ShowNum and (FGutterPan.StartNum <> FTopLine + 1) then + begin + FGutterPan.StartNum := FTopLine + 1; + FGutterPan.Invalidate; + end; + end + else + GSz := GetClientRect.Left + 1; // gutter size if no gutter panel + + if ALineIndex < FLines.Count then + begin + X := -(HPos * FChrW) + GSz; + FormatLine(ALineIndex, X, Y); + end; +end; + +procedure TfpgBaseTextEdit.FormatLine(const ALineIndex, X, Y: Integer); +var + S, CorrectS, SS: TfpgString; + TI, Si, Ei, T: Integer; + R: TfpgRect; + AllowDraw: Boolean; +begin + if FLines.Count = 0 then + Exit; //==> + if (ALineIndex < 0) or (ALineIndex > FLines.Count-1) then + Exit; //==> + S := FLines[ALineIndex]; + if Pos(#9, S) > 0 then + begin + CorrectS := ''; + for TI := 1 to Length(S) do // no need to use utf8 version here + begin + if S[TI] = #9 then + begin + for T := 1 to FTabWidth do + CorrectS := CorrectS + ' '; + end + else + CorrectS := CorrectS + S[TI]; + end; + S := CorrectS; + end; { if } + + { start drawing formatted text } + R.SetRect(X, Y, UTF8Length(S) * FChrW, FChrH); + AllowDraw := True; + { end-user can hook in here to do syntax highlighting and other custom drawing } + if Assigned(FOnDrawLine) then + FOnDrawLine(self, S, ALineIndex, Canvas, R, AllowDraw); + { Draw simple text line... } + if AllowDraw then + begin + Canvas.DrawText(R, S); + end; + if FSelected then + begin + Canvas.TextColor := clWhite; + Canvas.Color := fpgColorToRGB(clSelection); + if (ALineIndex > StartNo) and (ALineIndex < EndNo) then // whole line is selected + begin + R.SetRect(X, Y, UTF8Length(S) * FChrW, FChrH); + Canvas.FillRectangle(R); + Canvas.DrawText(R, S); + end + else + begin + Ei := EndOffs; + Si := StartOffs; + if (ALineIndex = StartNo) and (ALineIndex = EndNo) then // start/end selection on same line + begin + SS := UTF8Copy(S, Si + 1, UTF8Length(S) - Si); + if Ei > UTF8Length(S) then + SS := UTF8Copy(S, Si + 1, UTF8Length(S) - Si) + else + SS := UTF8Copy(S, Si + 1, Ei - Si); + R.SetRect(X+(Si * FChrW), Y, (UTF8Length(SS) * FChrW), FChrH); + Canvas.FillRectangle(R); + Canvas.DrawText(R, SS); + end + else + begin + if (ALineIndex = StartNo) and (ALineIndex < EndNo) then + begin + SS := UTF8Copy(S, Si + 1, UTF8Length(S) - Si); + R.SetRect(X+(Si * FChrW), Y, (UTF8Length(SS) * FChrW), FChrH); + Canvas.FillRectangle(R); + Canvas.DrawText(R, SS); + end + else + begin + if (ALineIndex > StartNo) and (ALineIndex = EndNo) then + begin + if Ei > UTF8Length(S) then + Ei := UTF8Length(S); + SS := UTF8Copy(S, 1, Ei); + R.SetRect(X, Y, (UTF8Length(SS) * FChrW), FChrH); + Canvas.FillRectangle(R); + Canvas.DrawText(R, SS); + end; + end; + end; + end; + end; { if FSelected... } +// end; { if AllowDraw... } + + if UTF8Length(S) > FMaxScrollH then + begin + FMaxScrollH := UTF8Length(S); + UpdateScrollBars; + end; +end; + +procedure TfpgBaseTextEdit.DrawCaret(const X, Y: Integer); +var + Xp, Yp: Integer; +begin + if csDesigning in ComponentState then + Exit; //==> + + {$IFDEF gDEBUG} + writeln('X:', X, ' Y:', Y, ' FTopLine:', FTopLine, ' HPos:', HPos, ' VPos:', VPos); + {$ENDIF} + + if (Y < FTopLine) or (Y > FTopLine + FVisLines) then + begin + fpgCaret.UnSetCaret(Canvas); + Exit; //==> + end; + Yp := ((Y - FTopLine) * FChrH) + 1; + Xp := ((X - HPos) * FChrW) + GetClientRect.Left; + + if FGutterPan.Visible then + Xp := Xp + FGutterPan.Width; + if (Xp < 0) or (Xp > GetClientRect.Width) then + begin + fpgCaret.UnSetCaret(Canvas); + Exit; //==> + end; + //with Canvas do + //begin + //if ShowCaret then + //begin + //Pen.Mode := pmNotMerge; + //Pen.Color := Font.Color; + //end else + //begin + //if not FSelected then + //Pen.Color := Self.Color + //else + //Pen.Color := FEnvironment.SelectionBackground; + //Pen.Mode := pmCopy; + //end; + //MoveTo(Xp, Yp); + //LineTo(Xp, Yp + FChrH); + //Pen.Mode := pmCopy; + //end; + if Focused then + fpgCaret.SetCaret(Canvas, Xp, Yp, fpgCaret.Width, FFont.Height) + else + fpgCaret.UnSetCaret(Canvas); + + if not FSelected then + begin + FSelStartNo := CaretPos.Y; + FSelStartOffs := CaretPos.X; + end; +end; + +constructor TfpgBaseTextEdit.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + Focusable := True; + FFont := fpgGetFont('#Edit1'); + Width := 320; + Height := 240; + FLines := TStringList.Create; + CaretPos.x := 0; + CaretPos.y := 0; + FTopLine := 0; + FTabWidth := 8; + FMaxScrollH := 1; + VPos := 0; + HPos := 0; + FTracking := True; + FFullRedraw := False; + FSelected := False; + FRightEdge := False; + FRightEdgeCol := 80; + FLineChanged := -1; + + fmousewheelfrequmin := 1; + fmousewheelfrequmax := 100; + fmousewheeldeltamin := 0.05; + fmousewheeldeltamax := 30; + fmousewheelaccelerationmax := 30; + fwheelsensitivity := 1.5; + + FLastScrollEventTime := 0; + FLastScrollEventTimeBefore := 0; + + FVScrollBar := TfpgScrollBar.Create(self); + FVScrollBar.Orientation := orVertical; + FVScrollBar.OnScroll := @VScrollBarMove; + FVScrollBar.Visible := False; + + FHScrollBar := TfpgScrollBar.Create(self); + FHScrollBar.Orientation := orHorizontal; + FHScrollBar.OnScroll := @HScrollBarMove; +// FHScrollBar.ScrollStep := 5; + FHScrollBar.Visible := False; + + InitMemoObjects; +end; + +destructor TfpgBaseTextEdit.Destroy; +begin + FLines.Free; + FFont.Free; + inherited Destroy; +end; + +function TfpgBaseTextEdit.GetClientRect: TfpgRect; +begin + // widget has a 2 pixel 3D border + Result.SetRect(2, 2, Width-4, Height-4); + if FVScrollBar.Visible then + Result.Width := Result.Width - FVScrollBar.Width; + if FHScrollBar.Visible then + Result.Height := Result.Height - FHScrollBar.Height; +end; + +function TfpgBaseTextEdit.GetWordAtPos(const X, Y: Integer; out XBegin: Integer): TfpgString; +{ todo: This needs to be made UTF8 compliant! It currently is not. } +const + ValidChars = ['a'..'z', 'A'..'Z', '0'..'9', '#']; +var + S: TfpgString; + C: Char; + I, Si, Ei, CrX: Integer; + lX: integer; +begin + Result := ''; + XBegin := -1; + Si := 0; + Ei := 0; + lX := X; + if Y > pred(FLines.Count) then Exit; //==> + S := FLines[Y]; + if S = '' then Exit; //==> + if lX > UTF8Length(S) - 1 then + lX := UTF8Length(S) - 1; + if not (S[lX + 1] in ValidChars) then + begin + CrX := lX - 1; + for I := CrX downto 1 do + begin + C := S[I + 1]; + if (C in ValidChars) then + begin + lX := I; + Break; + end; + end; + if lX = 0 then Exit; //==> + end; + for I := (lX + 1) downto 1 do + if S[I] in ValidChars then + Si := I + else + Break; + for I := (lX + 1) to Length(S) do + if S[I] in ValidChars then + Ei := I + 1 + else + Break; + if Ei >= Si then + begin + Result := UTF8Copy(S, Si, Ei - Si); + XBegin := Si - 1; + end; +end; + +procedure TfpgBaseTextEdit.GetRowColAtPos(const X, Y: Integer; out Row, Col: Integer); +var + Fine: Integer; + lX: Integer; +begin + Row := Y div FChrH; + if Row > Flines.Count then + Row := FLines.Count; + + lX := X - GetClientRect.Left; + if FGutterPan.Visible then + begin + if lX < FGutterPan.Width then + lX := FGutterPan.Width; + Col := (lX - FGutterPan.Width) div FChrW; + Fine := (lX - FGutterPan.Width) mod FChrW; + end + else + begin + if lX < 0 then + lX := 0; + Col := lX div FChrW; + Fine := lX mod FChrW; + end; + if Fine > (FChrW div 2) - 1 then + Col := Col + 1; +end; + +procedure TfpgBaseTextEdit.Clear; +begin + CaretPos.x := 0; + CaretPos.y := 0; + ScrollTo(0, 0); + FSelStartNo := 0; + FSelStartOffs := 0; + FSelEndNo := 0; + FSelEndOffs := 0; + FLines.Clear; + FSelected := False; + Invalidate; +end; + +procedure TfpgBaseTextEdit.ScrollTo(X, Y: Integer); +begin + SetVPos(Y div FChrH); + SetHPos(X div FChrW); +end; + +procedure TfpgBaseTextEdit.GotoLine(ALine: integer); +begin + CaretPos.X := 0; + CaretPos.Y := ALine; + ScrollPos_V := ALine-5; // scrolling a few lines short so cursor is not on top line +end; + +procedure TfpgBaseTextEdit.DeleteSelection; +begin + { TODO : Implement DeleteSelection } +end; + +procedure TfpgBaseTextEdit.SaveToFile(const AFileName: TfpgString); +var + BuffList: TStringList; + SLine: TfpgString; + I, P: Integer; + Replace: Boolean; +begin + BuffList := TStringList.Create; + try + BuffList.Assign(FLines); + for I := 0 to pred(BuffList.Count) do + begin + SLine := BuffList[I]; + P := UTF8Length(SLine); + Replace := (P > 0) and (SLine <> ''); + if Replace then + begin + while (fpgCharAt(SLine, P) = ' ') do + begin + UTF8Delete(SLine, P, 1); + P := UTF8Length(SLine); + end; + BuffList[I] := SLine; + end; + end; + BuffList.SaveToFile(AFileName); + finally + BuffList.Free; + end; +end; + +procedure TfpgBaseTextEdit.LoadFromFile(const AFileName: TfpgString); +begin + if not fpgFileExists(AFileName) then + Exit; //==> + Clear; + FLines.LoadFromFile(AFileName); + HandleResize(Width, Height); + Invalidate; +end; + + +end. + diff --git a/examples/apps/ide/src/fpgide.lpi b/examples/apps/ide/src/fpgide.lpi new file mode 100644 index 00000000..2e877727 --- /dev/null +++ b/examples/apps/ide/src/fpgide.lpi @@ -0,0 +1,150 @@ +<?xml version="1.0"?> +<CONFIG> + <ProjectOptions> + <Version Value="9"/> + <General> + <Flags> + <SaveOnlyProjectUnits Value="True"/> + <MainUnitHasCreateFormStatements Value="False"/> + <MainUnitHasTitleStatement Value="False"/> + </Flags> + <SessionStorage Value="InProjectDir"/> + <MainUnit Value="0"/> + <UseAppBundle Value="False"/> + </General> + <VersionInfo> + <Language Value=""/> + <CharSet Value=""/> + </VersionInfo> + <BuildModes Count="1"> + <Item1 Name="default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <IgnoreBinaries Value="False"/> + <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/> + <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/> + </local> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="fpgui_toolkit"/> + </Item1> + </RequiredPackages> + <Units Count="16"> + <Unit0> + <Filename Value="fpgide.lpr"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpgide"/> + </Unit0> + <Unit1> + <Filename Value="frm_main.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="frm_main"/> + </Unit1> + <Unit2> + <Filename Value="frm_configureide.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="frm_configureide"/> + </Unit2> + <Unit3> + <Filename Value="ideconst.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="ideconst"/> + </Unit3> + <Unit4> + <Filename Value="idemacros.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="idemacros"/> + </Unit4> + <Unit5> + <Filename Value="frm_debug.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="frm_debug"/> + </Unit5> + <Unit6> + <Filename Value="project.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="Project"/> + </Unit6> + <Unit7> + <Filename Value="unitlist.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="UnitList"/> + </Unit7> + <Unit8> + <Filename Value="frm_projectoptions.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="frm_projectoptions"/> + </Unit8> + <Unit9> + <Filename Value="ideutils.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="ideutils"/> + </Unit9> + <Unit10> + <Filename Value="builderthread.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="BuilderThread"/> + </Unit10> + <Unit11> + <Filename Value="ideimages.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="ideimages"/> + </Unit11> + <Unit12> + <Filename Value="stringhelpers.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="stringhelpers"/> + </Unit12> + <Unit13> + <Filename Value="frm_procedurelist.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="frm_procedurelist"/> + </Unit13> + <Unit14> + <Filename Value="mpaslex.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="mPasLex"/> + </Unit14> + <Unit15> + <Filename Value="filemonitor.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="filemonitor"/> + </Unit15> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="9"/> + <Target> + <Filename Value="fpgide.elf32"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="units/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <AllowLabel Value="False"/> + <CPPInline Value="False"/> + <UseAnsiStrings Value="False"/> + </SyntaxOptions> + </Parsing> + <Linking> + <Debugging> + <UseHeaptrc Value="True"/> + </Debugging> + </Linking> + <Other> + <CompilerMessages> + <UseMsgFile Value="True"/> + </CompilerMessages> + <CompilerPath Value="$(CompPath)"/> + </Other> + </CompilerOptions> +</CONFIG> diff --git a/examples/apps/ide/src/fpgide.lpr b/examples/apps/ide/src/fpgide.lpr new file mode 100644 index 00000000..ad9827d1 --- /dev/null +++ b/examples/apps/ide/src/fpgide.lpr @@ -0,0 +1,33 @@ +program fpgide; + +{$mode objfpc}{$H+} + +uses + {$IFDEF UNIX} + cthreads, + {$ENDIF} + Classes, fpg_base, fpg_main, frm_main, frm_configureide, ideconst, idemacros, + frm_debug, project, unitlist, frm_projectoptions, ideutils, builderthread, + ideimages, stringhelpers, frm_procedurelist, mPasLex, filemonitor; + + +procedure MainProc; +var + frm: TMainForm; +begin +// FPG_DEFAULT_FONT_DESC := 'DejaVu Sans-9'; + fpgApplication.Initialize; + RegisterIDEImages; + frm := TMainForm.Create(nil); + try + frm.Show; + fpgApplication.Run; + finally + frm.Free; + end; +end; + +begin + MainProc; +end. + diff --git a/examples/apps/ide/src/fpgide.prj b/examples/apps/ide/src/fpgide.prj new file mode 100644 index 00000000..5c6e06a5 --- /dev/null +++ b/examples/apps/ide/src/fpgide.prj @@ -0,0 +1,1387 @@ +[componentpalette] +order0=0 +order1=0 +order2=0 +order3=0 +order4=0 +order5=0 +order6=0 +order7=0 +order8=0 +order9=0 +order10=0 +order11=0 +order12=0 +order13=0 +[projectoptions] +projectdir=/media/flash16gig/programming/fpgide/src +projectfilename=/media/flash16gig/programming/fpgide/src/fpgide.prj +findinfiledialog=40 + [findinfileadialogfo.subdirs] + value=1 + [findinfileadialogfo.inopenfiles] + value=0 + [findinfileadialogfo.wholeword] + value=0 + [findinfileadialogfo.casesensitive] + value=0 + [findinfileadialogfo.indirectories] + value=1 + [findinfileadialogfo.mask] + value="*.pas" "*.pp" "*.inc" + history=1 + "*.pas" "*.pp" "*.inc" + [findinfileadialogfo.dir] + filenames=1 + /media/flash16gig/programming/msegui_svn/lib/common/ + filehistory=4 + /media/flash16gig/programming/msegui_svn/lib/common/ + /media/flash16gig/programming/Kylix_addons/ + /media/flash16gig/programming/fpgide/src/ + /home/graemeg/programming/lazarus/ide/ + filefilterindex=0 + filefilter="*.pas" "*.pp" "*.inc" + filecolwidth=174 + [findinfileadialogfo.findtext] + value=mousewheelacceleration + history=6 + mousewheelacceleration + CLK_TCK + TBuilderThread + Output.Read + LoadSettings + + [findinfileadialogfo] + stackedunder= + x=268 + y=415 + cx=406 + cy=296 +finddialog=8 + [finddialogfo.selectedonly] + value=0 + [finddialogfo] + stackedunder= + x=319 + y=205 + cx=363 + cy=118 +options=99 + [projectoptionsfo.twidgetgrid3] + propcolwidthref=732 + sortdescent0=0 + sortdescent1=0 + sortdescent2=0 + width3=128 + sortdescent3=0 + width4=352 + sortdescent4=0 + width5=202 + sortdescent5=0 + [projectoptionsfo.twidgetgrid4] + propcolwidthref=543 + width0=96 + sortdescent0=0 + width1=13 + sortdescent1=0 + width2=73 + sortdescent2=0 + width3=260 + sortdescent3=0 + width4=273 + sortdescent4=0 + sortcol=-1 + col=-1073741823 + row=-1073741823 + rowheight=19 + [projectoptionsfo.newfile] + firsttab=0 + index=2 + [projectoptionsfo.fontaliasgrid] + propcolwidthref=402 + width0=98 + sortdescent0=0 + width1=391 + sortdescent1=0 + width2=30 + sortdescent2=0 + width3=50 + sortdescent3=0 + width4=50 + sortdescent4=0 + width5=50 + sortdescent5=0 + width6=70 + sortdescent6=0 + [projectoptionsfo.macrosplitter] + x=0 + y=182 + xprop=1 + yprop=0.35922330097087 + [projectoptionsfo.macrogrid] + propcolwidthref=526 + sortdescent0=0 + sortdescent1=0 + sortdescent2=0 + sortdescent3=0 + sortdescent4=0 + sortdescent5=0 + width6=146 + sortdescent6=0 + width7=514 + sortdescent7=0 + [projectoptionsfo.makegroupbox] + firsttab=0 + index=2 + [projectoptionsfo.exceptionsgrid] + propcolwidthref=703 + width0=47 + sortdescent0=0 + width1=697 + sortdescent1=0 + [projectoptionsfo.ttabwidget1] + firsttab=0 + index=0 + [projectoptionsfo.grid] + propcolwidthref=504 + width0=219 + sortdescent0=0 + width1=497 + sortdescent1=0 + [projectoptionsfo.filefiltergrid] + propcolwidthref=611 + width0=112 + sortdescent0=0 + width1=604 + sortdescent1=0 + [projectoptionsfo.ttabwidget2] + firsttab=0 + index=0 + [projectoptionsfo.tabwidget] + firsttab=0 + index=0 + [projectoptionsfo] + stackedunder= + x=151 + y=68 + cx=752 + cy=572 +settings=8 + [settingsfo] + x=88 + y=108 + cx=415 + cy=604 + wsize=0 + active=1 + visible=1 +copymessages=0 +closemessages=1 +checkmethods=1 +showgrid=1 +snaptogrid=1 +moveonfirstclick=1 +gridsizex=8 +gridsizey=8 +autoindent=1 +blockindent=2 +rightmarginon=1 +rightmarginchars=80 +scrollheight=0 +tabstops=2 +spacetabs=1 +tabindent=0 +editfontname=DejaVu Sans Mono +editfontheight=15 +editfontwidth=0 +editfontextraspace=0 +editfontcolor=-1879048183 +editbkcolor=-1879048186 +statementcolor=14745599 +editfontantialiased=1 +editmarkbrackets=1 +backupfilecount=0 +encoding=0 +codetemplatedirs=1 + ${TEMPLATEDIR} +usercolors=30 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +usercolorcomment=30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +stoponexception=0 +valuehints=1 +activateonbreak=1 +showconsole=1 +externalconsole=0 +gdbdownload=0 +downloadalways=0 +startupbkpt=0 +startupbkpton=0 +gdbsimulator=0 +gdbserverwait=0 +nogdbserverexit=0 +exceptclassnames=1 + EconvertError +exceptignore=1 + 0 +settingsfile=/media/flash16gig/programming/fpgide/src/fpgide_settings.prj +sigsettings=27 + 1,1,T,F + 3,3,T,F + 4,4,T,F + 6,6,T,F + 7,7,T,F + 8,8,T,F + 9,9,T,F + 10,10,T,F + 11,11,T,F + 12,12,T,F + 13,13,T,F + 15,15,T,F + 16,16,T,F + 17,17,F,F + 18,18,T,F + 19,19,T,F + 20,20,T,F + 21,21,T,F + 22,22,T,F + 23,23,T,F + 24,24,T,F + 25,25,T,F + 26,26,T,F + 27,27,T,F + 28,28,T,F + 29,29,T,F + 30,30,T,F +modulenames=0 +moduletypes=0 +modulefiles=0 +mainfile=${PROJECTNAME}.lpr +targetfile=${PROJECTNAME}${EXEEXT} +messageoutputfile= +makecommand=${COMPILER} +makedir= +debugcommand=${DEBUGGER} +debugoptions= +debugtarget= +runcommand= +remoteconnection= +uploadcommand= +gdbprocessor=i386 +gdbservercommand= +gdbservercommandattach= +beforeload= +afterload= +beforerun= +defaultmake=4 +befcommand=0 +befcommandon=0 +aftcommand=0 +aftcommandon=0 +makeoptions=5 + -l -Mobjfpc -Sh + -gl -O- + -B + -O2 -XX -Xs -CX + -FUunits/${TargetCPU}-${TargetOS}/ +makeoptionson=5 + 63 + 31 + 34 + 32 + 63 +macroon=6 + 1 + 3 + 6 + 4 + 63 + 63 +macronames=6 + TargetCPU + TargetOS + TargetCPU + TargetOS + FPGUI_DIR + HelpPath +macrovalues=6 + x86_64 + linux + i386 + win32 + /home/graemeg/programming/fpgui + /home/graemeg/programming/FPC_Docs/inf/ +macrogroup=1 +groupcomments=6 + Linux 64-bit + Linux 32-bit + Windows 32-bit + + + +sourcedirs=4 + ${FPGUI_DIR}/src/corelib/gdi/X/ + ${FPGUI_DIR}/src/corelib/x11/ + ${FPGUI_DIR}/src/*/ + ./ +defines=0 +defineson=0 +unitdirs=6 + ${FPGUI_DIR}/lib/${TargetCPU}-${TargetOS}/ + ${FPGUI_DIR}/src/ + ${FPGUI_DIR}/src/corelib/gdi/ + ${FPGUI_DIR}/src/corelib/x11/ + ${FPGUI_DIR}/src/gui/ + ${FPGUI_DIR}/src/corelib/ +unitdirson=6 + 65537 + 131102 + 196616 + 196614 + 65566 + 65566 +unitpref=-Fu +incpref=-Fi +libpref=-Fl +objpref=-Fo +targpref=-o +sourcefilemasks=5 + "*.pas" "*.dpr" "*.pp" "*.inc" "*.lpr" + "*.c" "*.cc" "*.h" + "*.mfm" + "*.ipf" + "*.sql" +syntaxdeffiles=5 + ${SYNTAXDEFDIR}pascal_dark.sdef + ${SYNTAXDEFDIR}cpp.sdef + ${SYNTAXDEFDIR}objecttext.sdef + ${SYNTAXDEFDIR}ipf2.sdef + ${SYNTAXDEFDIR}sql.sdef +filemasknames=5 + Source + Forms + Text + IPF help + All Files +filemasks=5 + "*.pp" "*.pas" "*.inc" "*.dpr" "*.lpr" + *.mfm + *.txt + *.ipf + * +toolsave=2 + -1 + 0 +toolhide=2 + 0 + 0 +toolparse=2 + 0 + 0 +toolmenus=2 + fpGUI &UI Designer + fpGUI &DocView +toolfiles=2 + ${FPGUI_DIR}/uidesigner/units/${TargetCPU}-${TargetOS}/uidesigner + ${FPGUI_DIR}/docview/src/units/${TargetCPU}-${TargetOS}/docview +toolparams=2 + ${CURSOURCEFILE} + ${HelpPath} -k ${cursword} +fontalias=0 +fontancestors=0 +fontnames=0 +fontheights=0 +fontwidths=0 +fontoptions=0 +fontxscales=0 +scriptbeforecopy= +scriptaftercopy= +newprojectfiles=4 + ${TEMPLATEDIR}/fpgui_single_unit/project.pas + ${TEMPLATEDIR}fpgui/units/i386-linux/placeholder.txt + ${TEMPLATEDIR}fpgui/units/i386-win32/placeholder.txt + ${TEMPLATEDIR}fpgui/units/x86_64-linux/placeholder.txt +newprojectfilesdest=4 + ${%PROJECTNAME%}.pas + ${%PROJECTDIR%}units/i386-linux/placeholder.txt + ${%PROJECTDIR%}units/i386-win32/placeholder.txt + ${%PROJECTDIR%}units/x86_64-linux/placeholder.txt +expandprojectfilemacros=4 + 1 + -1 + -1 + -1 +loadprojectfile=4 + 1 + 0 + 0 + 0 +newfinames=3 + Program + Unit + Textfile +newfinfilters=3 + "*.pas" "*.pp" + "*.pas" "*.pp" + +newfiexts=3 + pas + pas + +newfisources=3 + ${TEMPLATEDIR}default/program.pas + ${TEMPLATEDIR}default/unit.pas + +newfonames=2 + Mainform + Simple Form +newfonamebases=2 + + +newinheritedforms=2 + 0 + 0 +newfosources=2 + ${TEMPLATEDIR}fpgui/mainform.pas + ${TEMPLATEDIR}fpgui/simpleform.pas +newfoforms=2 + + +[breakpoints] +on=1 + -1 +path=1 + /media/flash16gig/programming/fpgide/src/fpg_textedit.pas +line=1 + 1257 +address=1 + 135547161 +addbkpt=1 + 0 +ignore=1 + 2 +condition=1 + +panels=0 +units= + ( + modulefilenames=0 + modulenames=0 + moduleclassnames=0 + a=0,4149,0,Pascal Units + c=19 + ( + file=/media/flash16gig/programming/fpgide/src/builderthread.pas + kind=1 + a=0,4100,0,builderthread.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/debugger.pas + kind=1 + a=0,4100,0,debugger.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/fpgide.lpr + kind=1 + a=0,4100,0,fpgide.lpr + ) + ( + file=/media/flash16gig/programming/fpgide/src/fpg_textedit.pas + kind=1 + a=0,4100,0,fpg_textedit.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/frm_configureide.pas + kind=1 + a=0,4100,0,frm_configureide.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/frm_debug.pas + kind=1 + a=0,4100,0,frm_debug.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/frm_main.pas + kind=1 + a=0,4100,0,frm_main.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/frm_procedurelist.pas + kind=1 + a=0,4100,0,frm_procedurelist.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/frm_projectoptions.pas + kind=1 + a=0,4100,0,frm_projectoptions.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/ideconst.pas + kind=1 + a=0,4100,0,ideconst.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/ideimages.pas + kind=1 + a=0,4100,0,ideimages.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/idemacros.pas + kind=1 + a=0,4100,0,idemacros.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/ideutils.pas + kind=1 + a=0,4100,0,ideutils.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/mPasLex.pas + kind=1 + a=0,4100,0,mPasLex.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/msetypes.pas + kind=1 + a=0,4100,0,msetypes.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/proclistimages.inc + kind=1 + a=0,4100,0,proclistimages.inc + ) + ( + file=/media/flash16gig/programming/fpgide/src/project.pas + kind=1 + a=0,4100,0,project.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/stringhelpers.pas + kind=1 + a=0,4100,0,stringhelpers.pas + ) + ( + file=/media/flash16gig/programming/fpgide/src/unitlist.pas + kind=1 + a=0,4100,0,unitlist.pas + ) + ) +cmodules= + ( + a=0,4132,0,C Modules + ) +files= + ( + a=0,4132,0,Text Files + ) +[componentstore] +storedir=/home/graemeg/programming/msegui_svn/apps/ide/compstore/ +filename= +[layout] +windowlayout=474 + [mainfo.openfile] + filenames=0 + filehistory=10 + /media/flash16gig/programming/msegui_svn/lib/common/kernel/msescrollbar.pas + /media/flash16gig/programming/msegui_svn/lib/common/kernel/i386-linux/mseguiintf.pas + /media/flash16gig/programming/fpgide/src/synregexpr.pas + /home/graemeg/programming/msegui_svn/README.TXT + /media/flash16gig/programming/fpgide/src/fpg_textedit.pas + /media/flash16gig/programming/Kylix_addons/Sort_Code/GenericUtils.pas + /media/flash16gig/programming/Kylix_addons/Sort_Code/ProcedureList.pas + /media/flash16gig/programming/fpgide/src/mPasLex.pas + /media/flash16gig/programming/fpgide/src/frm_procedurelist.pas + /home/graemeg/programming/fpc-2.5.1/src/packages/gdbm/examples/testgdbm.pp + filefilterindex=4 + filefilter=* + filecolwidth=219 + [mainfo.basedock] + splitdir=1 + useroptions=15488 + [mainfo] + stackedunder= + x=66 + y=49 + cx=693 + cy=87 + wsize=0 + active=1 + visible=1 + [targetconsolefo] + splitdir=0 + useroptions=16489 + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=889 + y=594 + cx=551 + cy=306 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=1 + [threadsfo] + splitdir=0 + useroptions=16489 + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=37 + y=270 + cx=349 + cy=276 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=0 + [memoryfo] + splitdir=0 + useroptions=16507 + stackedunder=componentpalettefo + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=100 + y=100 + cx=453 + cy=354 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=0 + [memoryfo.add] + value=0 + [memoryfo.memon] + value=0 + [memoryfo.bitwidth] + value=0 + [memoryfo.cnt] + value=0 + [disassfo] + splitdir=0 + useroptions=16491 + stackedunder=stackfo + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=162 + y=502 + cx=564 + cy=210 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=0 + [findinfilefo] + splitdir=0 + useroptions=16491 + stackedunder=targetconsolefo + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=351 + y=53 + cx=1089 + cy=363 + rcx=0 + rcy=0 + [projecttreefo] + splitdir=0 + useroptions=16491 + stackedunder=breakpointsfo + parent= + mdistate=3 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=6 + y=186 + cx=233 + cy=892 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=1 + [projecttreefo.grid] + propcolwidthref=35 + width0=188 + sortdescent0=0 + width1=29 + sortdescent1=0 + sortcol=-1 + col=0 + row=9 + rowheight=19 + [stackfo] + splitdir=0 + useroptions=16489 + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=407 + y=349 + cx=254 + cy=180 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=0 + [watchpointsfo] + splitdir=0 + useroptions=16489 + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=537 + y=26 + cx=483 + cy=210 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=0 + [watchpointsfo.grid] + propcolwidthref=352 + width0=16 + sortdescent0=0 + values1=0 + values1_ci=-1 + width1=33 + sortdescent1=0 + values2=0 + width2=158 + sortdescent2=0 + width4=34 + sortdescent4=0 + values5=0 + values5_ci=-1 + width5=38 + sortdescent5=0 + values6=0 + width6=184 + sortdescent6=0 + [breakpointsfo] + splitdir=0 + useroptions=16491 + stackedunder=findinfilefo + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=107 + y=404 + cx=660 + cy=128 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=1 + [breakpointsfo.bkptson] + value=1 + [objectinspectorfo] + splitdir=0 + useroptions=16491 + stackedunder=symbolfo + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=665 + y=156 + cx=355 + cy=484 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=0 + [objectinspectorfo.grid] + propcolwidthref=345 + width0=83 + sortdescent0=0 + width1=256 + sortdescent1=0 + [symbolfo] + splitdir=0 + useroptions=16511 + stackedunder=componentstorefo + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=1079 + y=763 + cx=361 + cy=137 + rcx=0 + rcy=0 + children=1 + container,0,0,351,137 + wsize=0 + active=0 + visible=0 + [symbolfo.grid] + propcolwidthref=222 + values0=0 + width0=111 + sortdescent0=0 + width1=135 + sortdescent1=0 + [symbolfo.symbol] + [watchfo] + splitdir=0 + useroptions=24681 + parent= + mdistate=3 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=713 + y=600 + cx=727 + cy=300 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=0 + [watchfo.grid] + propcolwidthref=555 + values0=0 + values0_ci=-1 + width0=13 + sortdescent0=0 + values1=0 + width1=119 + sortdescent1=0 + values3=0 + values3_ci=-1 + width3=12 + sortdescent3=0 + values4=0 + values4_ci=-1 + width4=13 + sortdescent4=0 + [watchfo.watchon] + [watchfo.expression] + [watchfo.watcheson] + value=0 + [messagefo] + splitdir=0 + useroptions=49259 + stackedunder=projecttreefo + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=5 + y=24 + cx=1046 + cy=193 + rcx=1046 + rcy=754 + wsize=0 + active=0 + visible=0 + [componentstorefo] + splitdir=0 + useroptions=24675 + stackedunder=messagefo + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=100 + y=100 + cx=445 + cy=354 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=0 + [componentstorefo.grid] + propcolwidthref=435 + width0=111 + sortdescent0=0 + sortdescent1=0 + width2=112 + sortdescent2=0 + width3=185 + sortdescent3=0 + [componentstorefo.storefiledialog] + filenames=1 + /home/mse/test/msegui/apps/ide/compstore/ + lastdir=/home/mse/test/msegui/apps/ide/compstore/ + filehistory=0 + filefilterindex=0 + filefilter= + filecolwidth=0 + [componentstorefo.groupfiledialog] + filenames=1 + /home/mse/test/msegui/apps/ide/compstore/ + lastdir=/home/mse/test/msegui/apps/ide/compstore/ + filehistory=0 + filefilterindex=0 + filefilter= + filecolwidth=0 + [componentstorefo.compfiledialog] + filenames=0 + lastdir= + filehistory=0 + filefilterindex=0 + filefilter= + filecolwidth=0 + [componentpalettefo] + splitdir=0 + useroptions=24683 + stackedunder=cpui386fo + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=340 + y=26 + cx=457 + cy=129 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=0 + [componentpalettefo.componentpages] + activetab=4 + [debuggerfo] + splitdir=0 + useroptions=24683 + parent=mainfo.basedock + visible=1 + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=0 + y=0 + cx=693 + cy=45 + rcx=0 + rcy=0 + [sourcefo] + splitdir=0 + useroptions=16489 + parent= + mdistate=3 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=249 + y=186 + cx=1034 + cy=891 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=1 + [actionsmo.watchesonact] + checked=0 + [actionsmo.bluedotsonact] + checked=1 + [cpui386fo] + irqoff=0 + splitdir=0 + useroptions=16491 + stackedunder=threadsfo + parent= + mdistate=0 + nx=0 + ny=0 + ncx=0 + ncy=0 + x=312 + y=341 + cx=283 + cy=210 + rcx=0 + rcy=0 + wsize=0 + active=0 + visible=0 +[edit] +hintwidth=414 +hintheight=19 +finddtext=fwheelsensitivity +findhistory=10 + fwheelsensitivity + deltamin + fmousewheelfrequmin + flastmousewheeltimestampbefore + lasteventtime + buttonpress + FPGM_SCROLL + cbDefaultMakeCol + tpLeft + left +findoptions=1 +editpos=38 + 0,0 + 39,779 + 0,0 + 0,1 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 27,230 + 0,-1073741823 + 26,1684 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 22,166 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 57,823 + 0,-1073741823 + 0,-1073741823 + 0,-1073741823 + 0,0 + 14,843 + 57,1200 + 6,16460 + 51,611 +bookmarks0=0 +bookmarks1=0 +bookmarks2=0 +bookmarks3=2 + 84,4 + 1052,5 +bookmarks4=0 +bookmarks5=0 +bookmarks6=0 +bookmarks7=0 +bookmarks8=0 +bookmarks9=0 +bookmarks10=0 +bookmarks11=0 +bookmarks12=0 +bookmarks13=2 + 1224,2 + 1683,3 +bookmarks14=0 +bookmarks15=0 +bookmarks16=0 +bookmarks17=0 +bookmarks18=0 +bookmarks19=0 +bookmarks20=0 +bookmarks21=0 +bookmarks22=0 +bookmarks23=0 +bookmarks24=0 +bookmarks25=0 +bookmarks26=0 +bookmarks27=0 +bookmarks28=0 +bookmarks29=0 +bookmarks30=0 +bookmarks31=0 +bookmarks32=0 +bookmarks33=0 +bookmarks34=0 +bookmarks35=1 + 1197,1 +bookmarks36=0 +bookmarks37=0 +sourcefiles=38 + /media/flash16gig/programming/fpgide/src/synregexpr.pas + /media/flash16gig/programming/fpgide/src/frm_projectoptions.pas + /media/flash16gig/programming/fpgide/src/ideutils.pas + /media/flash16gig/programming/fpgide/src/frm_main.pas + /media/flash16gig/programming/fpgide/src/unitlist.pas + /media/flash16gig/programming/fpgide/src/project.pas + /media/flash16gig/programming/fpgide/src/stringhelpers.pas + /media/flash16gig/programming/fpgide/src/ideimages.pas + /media/flash16gig/programming/fpgide/src/frm_debug.pas + /media/flash16gig/programming/fpgide/src/idemacros.pas + /media/flash16gig/programming/fpgide/src/ideconst.pas + /media/flash16gig/programming/fpgide/src/frm_configureide.pas + /home/graemeg/programming/fpgui/src/gui/fpg_form.pas + /media/flash16gig/programming/fpgide/src/fpg_textedit.pas + /home/graemeg/programming/fpgui/src/corelib/fpg_main.pas + /media/flash16gig/programming/fpgide/src/builderthread.pas + /home/graemeg/programming/lazarus/ide/checkcompileropts.pas + /media/flash16gig/programming/fpgide/src/frm_procedurelist.pas + /media/flash16gig/programming/Kylix_addons/Sort_Code/ProcedureList.pas + /media/flash16gig/programming/Kylix_addons/Sort_Code/GenericUtils.pas + /home/graemeg/programming/fpgui/src/corelib/fpg_base.pas + /home/graemeg/programming/fpgui/src/gui/fpg_grid.pas + /home/graemeg/programming/fpgui/src/gui/fpg_customgrid.pas + /home/graemeg/programming/fpgui/src/gui/fpg_basegrid.pas + /home/graemeg/programming/fpgui/src/corelib/fpg_imagelist.pas + /media/flash16gig/programming/fpgide/src/proclistimages.inc + /home/graemeg/programming/fpgui/src/corelib/fpg_imgfmt_bmp.pas + /home/graemeg/programming/fpgui/src/corelib/fpg_stdimages.pas + /home/graemeg/programming/fpgui/src/gui/fpg_edit.pas + /home/graemeg/programming/fpgui/src/corelib/fpg_widget.pas + /home/graemeg/programming/fpgui/src/corelib/keys.inc + /media/flash16gig/programming/fpgide/src/debugger.pas + /media/flash16gig/programming/fpgide/src/mPasLex.pas + /media/flash16gig/programming/fpgide/src/msetypes.pas + /media/flash16gig/programming/msegui_svn/lib/common/kernel/i386-linux/mseguiintf.pas + /media/flash16gig/programming/msegui_svn/lib/common/kernel/msescrollbar.pas + /media/flash16gig/programming/msegui_svn/lib/common/kernel/msegui.pas + /home/graemeg/programming/fpgui/src/gui/fpg_scrollbar.pas +relpaths=38 + synregexpr.pas + frm_projectoptions.pas + ideutils.pas + frm_main.pas + unitlist.pas + project.pas + stringhelpers.pas + ideimages.pas + frm_debug.pas + idemacros.pas + ideconst.pas + frm_configureide.pas + ../../../../../home/graemeg/programming/fpgui/src/gui/fpg_form.pas + fpg_textedit.pas + ../../../../../home/graemeg/programming/fpgui/src/corelib/fpg_main.pas + builderthread.pas + ../../../../../home/graemeg/programming/lazarus/ide/checkcompileropts.pas + frm_procedurelist.pas + ../../Kylix_addons/Sort_Code/ProcedureList.pas + ../../Kylix_addons/Sort_Code/GenericUtils.pas + ../../../../../home/graemeg/programming/fpgui/src/corelib/fpg_base.pas + ../../../../../home/graemeg/programming/fpgui/src/gui/fpg_grid.pas + ../../../../../home/graemeg/programming/fpgui/src/gui/fpg_customgrid.pas + ../../../../../home/graemeg/programming/fpgui/src/gui/fpg_basegrid.pas + ../../../../../home/graemeg/programming/fpgui/src/corelib/fpg_imagelist.pas + proclistimages.inc + ../../../../../home/graemeg/programming/fpgui/src/corelib/fpg_imgfmt_bmp.pas + ../../../../../home/graemeg/programming/fpgui/src/corelib/fpg_stdimages.pas + ../../../../../home/graemeg/programming/fpgui/src/gui/fpg_edit.pas + ../../../../../home/graemeg/programming/fpgui/src/corelib/fpg_widget.pas + ../../../../../home/graemeg/programming/fpgui/src/corelib/keys.inc + debugger.pas + mPasLex.pas + msetypes.pas + ../../msegui_svn/lib/common/kernel/i386-linux/mseguiintf.pas + ../../msegui_svn/lib/common/kernel/msescrollbar.pas + ../../msegui_svn/lib/common/kernel/msegui.pas + ../../../../../home/graemeg/programming/fpgui/src/gui/fpg_scrollbar.pas +ismoduletexts=38 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +modules=0 +visiblemodules=0 +[sourcefo.tabwidget] +order=38 + 21 + 2 + 3 + 0 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 1 + 12 + 13 + 37 + 20 + 35 + 36 + 34 + 14 + 15 + 16 + 17 + 31 + 32 + 33 + 28 + 29 + 18 + 19 + 30 + 22 + 23 + 24 + 25 + 26 + 27 +tabsize=169 +firsttab=0 +index=14 +[components] +[selecteditpage] +colwidth=156 +x=160 +y=124 +cx=704 +cy=473 +[progparams] +parameters= +progparamhistory=0 +workingdirectory= +envvarons=0 +envvarnames=0 +envvarvalues=0 diff --git a/examples/apps/ide/src/fpgide.project b/examples/apps/ide/src/fpgide.project new file mode 100644 index 00000000..ac5e3635 --- /dev/null +++ b/examples/apps/ide/src/fpgide.project @@ -0,0 +1,55 @@ +[ProjectOptions] +ProjectName=fpgide.project +MainUnit=fpgide.lpr +TargetFile=fpgide${EXEEXT} +DefaultMake=0 +MakeOptionsCount=5 +MakeOptionEnabled1=1,1,1,1,1,1 +MakeOptionEnabled2=1,1,1,1,1,0 +MakeOptionEnabled3=1,1,0,0,0,1 +MakeOptionEnabled4=0,0,0,0,1,1 +MacroCount=6 +Macro1=TargetCPU=x86_64 +Macro2=TargetOS=linux +Macro3=TargetCPU=i386 +Macro4=TargetOS=win32 +Macro5=FPGUI_DIR=/home/graemeg/programming/fpgui/ +Macro6=tiOPF_fpGUI_Dir=/home/graemeg/programming/3rdParty/tiOPF2/src/ +UnitDirsCount=7 +UnitDirEnabled1=1,1,1,1,0,0,0,1,0,0 +UnitDirEnabled2=1,1,1,1,0,0,1,1,0,0 +UnitDirEnabled3=1,1,1,0,0,0,1,1,0,0 +UnitDirEnabled4=0,0,0,1,0,0,1,1,0,0 +UnitDirEnabled5=1,1,1,1,0,0,1,0,0,0 +UnitDirEnabled6=1,1,1,1,0,0,1,0,0,0 +UnitDirEnabled7=0,0,0,0,1,1,1,0,0,0 +UnitDirEnabled8=0,0,0,0,0,1,1,0,0,0 +UnitOutputDir=units/${TARGET}/ +MakeOptionEnabled5=1,0,0,0,0,0 +MakeOption1=-l -Mobjfpc -Sch +MakeOption2=-gl -O- +MakeOption3=-B +MakeOption4=-O2 -XX -Xs -CX +MakeOption5=-veiw +UnitDir1=${FPGUIDIR}src/ +UnitDir2=${FPGUIDIR}src/corelib/ +UnitDir3=${FPGUIDIR}src/corelib/x11/ +UnitDir4=${FPGUIDIR}src/corelib/gdi/ +UnitDir5=${FPGUIDIR}src/gui/ +UnitDir6=${FPGUIDIR}src/gui/db/ +UnitDir7=${FPGUILIBDIR} + +[Units] +UnitCount=12 +Unit1=fpg_textedit.pas,-1 +Unit2=fpgide.lpr,0 +Unit3=frm_configureide.pas,0 +Unit4=frm_debug.pas,0 +Unit5=frm_main.pas,-1 +Unit6=frm_procedurelist.pas,0 +Unit7=frm_projectoptions.pas,0 +Unit8=ideconst.pas,-1 +Unit9=idemacros.pas,0 +Unit10=ideutils.pas,-1 +Unit11=project.pas,-1 +Unit12=unitlist.pas,-1 diff --git a/examples/apps/ide/src/frm_configureide.pas b/examples/apps/ide/src/frm_configureide.pas new file mode 100644 index 00000000..b3fb02da --- /dev/null +++ b/examples/apps/ide/src/frm_configureide.pas @@ -0,0 +1,741 @@ +unit frm_configureide; + +{$mode objfpc}{$H+} + +interface + +uses + SysUtils, Classes, fpg_base, fpg_main, fpg_form, fpg_button, fpg_editbtn, + fpg_label, fpg_tab, fpg_edit, fpg_grid, fpg_listbox, idemacros, fpg_combobox, + fpg_checkbox; + +type + TConfigureIDEForm = class(TfpgForm) + private + {@VFD_HEAD_BEGIN: ConfigureIDEForm} + btnCancel: TfpgButton; + btnOK: TfpgButton; + pcSettings: TfpgPageControl; + tsEnvironment: TfpgTabSheet; + tsEditor: TfpgTabSheet; + tsShortcuts: TfpgTabSheet; + Label1: TfpgLabel; + edtFPCSrcDir: TfpgDirectoryEdit; + edtFPGuiDir: TfpgDirectoryEdit; + edtFPGuiLibDir: TfpgDirectoryEdit; + edtSyntaxDefDir: TfpgDirectoryEdit; + edtTempateDir: TfpgDirectoryEdit; + edtCompiler: TfpgFileNameEdit; + edtDebugger: TfpgFileNameEdit; + Label2: TfpgLabel; + Label3: TfpgLabel; + Label4: TfpgLabel; + Label5: TfpgLabel; + Label6: TfpgLabel; + Label7: TfpgLabel; + Label8: TfpgLabel; + Label9: TfpgLabel; + Label11: TfpgLabel; + edtEditorFont: TfpgFontEdit; + edtExeExt: TfpgEdit; + edtTarget: TfpgEdit; + grdShortcuts: TfpgStringGrid; + tsSyntaxDefs: TfpgTabSheet; + tsFileFilters: TfpgTabSheet; + grdSyntaxDefs: TfpgStringGrid; + grdFileFilters: TfpgStringGrid; + tsExtTools: TfpgTabSheet; + Label10: TfpgLabel; + edtExtToolMenu: TfpgEdit; + Label12: TfpgLabel; + edtExtToolFile: TfpgFileNameEdit; + Label13: TfpgLabel; + edtExtToolParams: TfpgEdit; + btnExtToolAdd: TfpgButton; + btnExtToolDel: TfpgButton; + lbExtTools: TfpgListBox; + cbTabPosition: TfpgComboBox; + Label14: TfpgLabel; + cbSyntaxHighlighting: TfpgCheckBox; + {@VFD_HEAD_END: ConfigureIDEForm} + // so we can get correct hints, but still undo with the Cancel button + FInternalMacroList: TIDEMacroList; + procedure BeforeShowHint(Sender: TObject; var AHint: TfpgString); + procedure LoadSettings; + procedure SaveSettings; + procedure SaveToMacroList(AList: TIDEMacroList); + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + procedure AfterCreate; override; + end; + +{@VFD_NEWFORM_DECL} + +procedure DisplayConfigureIDE; + + +implementation + +uses + fpg_dialogs + ,fpg_iniutils + ,fpg_widget + ,ideconst + ; + +type + // Used to get access to the Protected properties + TDirectoryEditFriend = class(TfpgDirectoryEdit); + + +procedure DisplayConfigureIDE; +var + frm: TConfigureIDEForm; + Result: Boolean; +begin + frm := TConfigureIDEForm.Create(nil); + try + frm.LoadSettings; + Result := frm.ShowModal = mrOK; + if Result then + begin + frm.SaveSettings; + end; + finally + frm.Free; + end; +end; + +{@VFD_NEWFORM_IMPL} + +procedure TConfigureIDEForm.BeforeShowHint(Sender: TObject; var AHint: TfpgString); +var + s: TfpgString; + c: TfpgWidget; +begin + if Sender is TfpgWidget then + c := TfpgWidget(Sender) + else + Exit; // should never occur, but lets just be safe + + if (c.Name = 'FEdit') and ((c.Parent is TfpgDirectoryEdit) or (c.Parent is TfpgFileNameEdit)) then + begin + if c.Parent <> nil then + c := c.Parent + else + Exit; // lets just be safe again + end; + + // controls that may contain macros + if c is TfpgDirectoryEdit then + s := TfpgDirectoryEdit(c).Directory + else if c is TfpgFileNameEdit then + s := TfpgFileNameEdit(c).FileName + else if c is TfpgEdit then + s := TfpgEdit(c).Text; + + AHint := s; + + if FInternalMacroList.StrHasMacros(s) then + begin + SaveToMacroList(FInternalMacroList); + AHint := FInternalMacroList.ExpandMacro(s); + end; +end; + +procedure TConfigureIDEForm.LoadSettings; +begin + edtFPCSrcDir.Directory := gINI.ReadString(cEnvironment, 'FPCSrcDir', ''); + edtFPGuiDir.Directory := gINI.ReadString(cEnvironment, 'FPGuiDir', ''); + edtFPGuiLibDir.Directory := gINI.ReadString(cEnvironment, 'FPGuiLibDir', GMacroList.FindByName(cMacro_FPGuiLibDir).Value); + edtSyntaxDefDir.Directory := gINI.ReadString(cEnvironment, 'SyntaxDefDir', GMacroList.FindByName(cMacro_SyntaxDefDir).Value); + edtTempateDir.Directory := gINI.ReadString(cEnvironment, 'TemplateDir', GMacroList.FindByName(cMacro_TemplateDir).Value); + edtCompiler.Filename := gINI.ReadString(cEnvironment, 'Compiler', ''); + edtDebugger.Filename := gINI.ReadString(cEnvironment, 'Debugger', 'gdb'); + edtExeExt.Text := gINI.ReadString(cEnvironment, 'ExeExt', ''); + edtTarget.Text := gINI.ReadString(cEnvironment, 'Target', GMacroList.FindByName(cMacro_Target).Value); + edtEditorFont.FontDesc := gINI.ReadString(cEditor, 'Font', '#Edit2'); + cbTabPosition.FocusItem := gINI.ReadInteger(cEditor, 'TabPosition', 0); + cbSyntaxHighlighting.Checked := gINI.ReadBool(cEditor, 'SyntaxHighlighting', True); +end; + +procedure TConfigureIDEForm.SaveSettings; +begin + gINI.WriteString(cEnvironment, 'FPCSrcDir', edtFPCSrcDir.Directory); + gINI.WriteString(cEnvironment, 'FPGuiDir', edtFPGuiDir.Directory); + gINI.WriteString(cEnvironment, 'FPGuiLibDir', edtFPGuiLibDir.Directory); + gINI.WriteString(cEnvironment, 'SyntaxDefDir', edtSyntaxDefDir.Directory); + gINI.WriteString(cEnvironment, 'TemplateDir', edtTempateDir.Directory); + gINI.WriteString(cEnvironment, 'Compiler', edtCompiler.Filename); + gINI.WriteString(cEnvironment, 'Debugger', edtDebugger.Filename); + gINI.WriteString(cEnvironment, 'ExeExt', edtExeExt.Text); + gINI.WriteString(cEnvironment, 'Target', edtTarget.Text); + gINI.WriteString(cEditor, 'Font', edtEditorFont.FontDesc); + gINI.WriteInteger(cEditor, 'TabPosition', cbTabPosition.FocusItem); + gINI.WriteBool(cEditor, 'SyntaxHighlighting', cbSyntaxHighlighting.Checked); + + SaveToMacroList(GMacroList); +end; + +procedure TConfigureIDEForm.SaveToMacroList(AList: TIDEMacroList); +begin + AList.SetValue(cMacro_FPCSrcDir, edtFPCSrcDir.Directory); + AList.SetValue(cMacro_FPGuiDir, edtFPGuiDir.Directory); + AList.SetValue(cMacro_FPGuiLibDir, edtFPGuiLibDir.Directory); + AList.SetValue(cMacro_SyntaxDefDir, edtSyntaxDefDir.Directory); + AList.SetValue(cMacro_TemplateDir, edtTempateDir.Directory); + AList.SetValue(cMacro_Compiler, edtCompiler.FileName); + AList.SetValue(cMacro_Debugger, edtDebugger.FileName); + AList.SetValue(cMacro_ExeExt, edtExeExt.Text); + AList.SetValue(cMacro_Target, edtTarget.Text); +end; + +constructor TConfigureIDEForm.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + FInternalMacroList := TIDEMacroList.Create; +end; + +destructor TConfigureIDEForm.Destroy; +begin + FInternalMacroList.Free; + inherited Destroy; +end; + +procedure TConfigureIDEForm.AfterCreate; +begin + {%region 'Auto-generated GUI code' -fold} + {@VFD_BODY_BEGIN: ConfigureIDEForm} + Name := 'ConfigureIDEForm'; + SetPosition(332, 190, 578, 480); + WindowTitle := 'Configure IDE'; + Hint := ''; + ShowHint := True; + WindowPosition := wpOneThirdDown; + + btnCancel := TfpgButton.Create(self); + with btnCancel do + begin + Name := 'btnCancel'; + SetPosition(494, 450, 80, 24); + Anchors := [anRight,anBottom]; + Text := 'Cancel'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + ModalResult := mrCancel; + TabOrder := 1; + end; + + btnOK := TfpgButton.Create(self); + with btnOK do + begin + Name := 'btnOK'; + SetPosition(410, 450, 80, 24); + Anchors := [anRight,anBottom]; + Text := 'OK'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + ModalResult := mrOK; + TabOrder := 2; + end; + + pcSettings := TfpgPageControl.Create(self); + with pcSettings do + begin + Name := 'pcSettings'; + SetPosition(4, 4, 570, 430); + Anchors := [anLeft,anRight,anTop,anBottom]; + ActivePageIndex := 0; + Hint := ''; + TabOrder := 3; + TabPosition := tpRight; + end; + + tsEnvironment := TfpgTabSheet.Create(pcSettings); + with tsEnvironment do + begin + Name := 'tsEnvironment'; + SetPosition(125, 3, 442, 424); + Text := 'Environment'; + end; + + tsEditor := TfpgTabSheet.Create(pcSettings); + with tsEditor do + begin + Name := 'tsEditor'; + SetPosition(125, 3, 442, 424); + Text := 'Editor'; + end; + + tsShortcuts := TfpgTabSheet.Create(pcSettings); + with tsShortcuts do + begin + Name := 'tsShortcuts'; + SetPosition(125, 3, 442, 424); + Text := 'Shortcuts'; + end; + + Label1 := TfpgLabel.Create(tsEnvironment); + with Label1 do + begin + Name := 'Label1'; + SetPosition(8, 4, 340, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'FPC Source Directory ${FPCSRCDIR}'; + end; + + edtFPCSrcDir := TfpgDirectoryEdit.Create(tsEnvironment); + with edtFPCSrcDir do + begin + Name := 'edtFPCSrcDir'; + SetPosition(8, 22, 424, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + Directory := ''; + RootDirectory := ''; + TabOrder := 3; + Hint := '*'; + OnShowHint := @BeforeShowHint; + end; + + edtFPGuiDir := TfpgDirectoryEdit.Create(tsEnvironment); + with edtFPGuiDir do + begin + Name := 'edtFPGuiDir'; + SetPosition(8, 74, 424, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + Directory := ''; + RootDirectory := ''; + TabOrder := 6; + Hint := '*'; + OnShowHint := @BeforeShowHint; + end; + + edtFPGuiLibDir := TfpgDirectoryEdit.Create(tsEnvironment); + with edtFPGuiLibDir do + begin + Name := 'edtFPGuiLibDir'; + SetPosition(8, 122, 424, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + Directory := '${FPGUIDIR}lib/'; + RootDirectory := ''; + TabOrder := 7; + Hint := '*'; + OnShowHint := @BeforeShowHint; + end; + + edtSyntaxDefDir := TfpgDirectoryEdit.Create(tsEnvironment); + with edtSyntaxDefDir do + begin + Name := 'edtSyntaxDefDir'; + SetPosition(8, 170, 424, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + Directory := '${FPGUIDIR}apps/ide/syntaxdefs/'; + RootDirectory := ''; + TabOrder := 8; + Hint := '*'; + OnShowHint := @BeforeShowHint; + end; + + edtTempateDir := TfpgDirectoryEdit.Create(tsEnvironment); + with edtTempateDir do + begin + Name := 'edtTempateDir'; + SetPosition(8, 218, 424, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + Directory := '{FPGUIDIR}apps/ide/templates/'; + RootDirectory := ''; + TabOrder := 9; + Hint := '*'; + OnShowHint := @BeforeShowHint; + end; + + edtCompiler := TfpgFileNameEdit.Create(tsEnvironment); + with edtCompiler do + begin + Name := 'edtCompiler'; + SetPosition(8, 266, 424, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + FileName := '/opt/fpc_2.4.1/${TARGET}/bin/fpc'; + InitialDir := ''; + Filter := ''; + TabOrder := 10; + Hint := '*'; + OnShowHint := @BeforeShowHint; + end; + + edtDebugger := TfpgFileNameEdit.Create(tsEnvironment); + with edtDebugger do + begin + Name := 'edtDebugger'; + SetPosition(8, 314, 426, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + FileName := 'gdb'; + InitialDir := ''; + Filter := ''; + TabOrder := 11; + Hint := '*'; + OnShowHint := @BeforeShowHint; + end; + + Label2 := TfpgLabel.Create(tsEnvironment); + with Label2 do + begin + Name := 'Label2'; + SetPosition(8, 56, 340, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'fpGUI Root Directory ${FPGUIDIR}'; + end; + + Label3 := TfpgLabel.Create(tsEnvironment); + with Label3 do + begin + Name := 'Label3'; + SetPosition(8, 104, 340, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := '${FPGUILIBDIR}'; + end; + + Label4 := TfpgLabel.Create(tsEnvironment); + with Label4 do + begin + Name := 'Label4'; + SetPosition(8, 152, 344, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := '${SYNTAXDEFDIR}'; + end; + + Label5 := TfpgLabel.Create(tsEnvironment); + with Label5 do + begin + Name := 'Label5'; + SetPosition(8, 200, 340, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := '${TEMPLATEDIR}'; + end; + + Label6 := TfpgLabel.Create(tsEnvironment); + with Label6 do + begin + Name := 'Label6'; + SetPosition(8, 248, 340, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := '${COMPILER}'; + end; + + Label7 := TfpgLabel.Create(tsEnvironment); + with Label7 do + begin + Name := 'Label7'; + SetPosition(8, 296, 340, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := '${DEBUGGER}'; + end; + + Label8 := TfpgLabel.Create(tsEnvironment); + with Label8 do + begin + Name := 'Label8'; + SetPosition(8, 344, 144, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := '${EXEEXT}'; + end; + + Label9 := TfpgLabel.Create(tsEnvironment); + with Label9 do + begin + Name := 'Label9'; + SetPosition(164, 344, 188, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := '${TARGET}'; + end; + + Label11 := TfpgLabel.Create(tsEditor); + with Label11 do + begin + Name := 'Label11'; + SetPosition(8, 4, 224, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Font'; + end; + + edtEditorFont := TfpgFontEdit.Create(tsEditor); + with edtEditorFont do + begin + Name := 'edtEditorFont'; + SetPosition(8, 22, 424, 24); + Anchors := [anLeft,anRight,anTop]; + FontDesc := '#Edit1'; + TabOrder := 2; + end; + + edtExeExt := TfpgEdit.Create(tsEnvironment); + with edtExeExt do + begin + Name := 'edtExeExt'; + SetPosition(8, 362, 144, 24); + ExtraHint := ''; + Hint := '*'; + TabOrder := 21; + Text := ''; + FontDesc := '#Edit1'; + OnShowHint := @BeforeShowHint; + end; + + edtTarget := TfpgEdit.Create(tsEnvironment); + with edtTarget do + begin + Name := 'edtTarget'; + SetPosition(164, 362, 192, 24); + ExtraHint := ''; + Hint := '*'; + TabOrder := 22; + Text := 'i386-linux'; + FontDesc := '#Edit1'; + OnShowHint := @BeforeShowHint; + end; + + grdShortcuts := TfpgStringGrid.Create(tsShortcuts); + with grdShortcuts do + begin + Name := 'grdShortcuts'; + SetPosition(8, 8, 428, 408); + Anchors := [anLeft,anRight,anTop,anBottom]; + AddColumn('Action', 180, taLeftJustify); + AddColumn('Shortcut', 110, taLeftJustify); + AddColumn('Alternative', 110, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 10; + RowSelect := False; + TabOrder := 1; + end; + + tsSyntaxDefs := TfpgTabSheet.Create(pcSettings); + with tsSyntaxDefs do + begin + Name := 'tsSyntaxDefs'; + SetPosition(125, 3, 442, 424); + Text := 'Syntax Highlighting'; + end; + + tsFileFilters := TfpgTabSheet.Create(pcSettings); + with tsFileFilters do + begin + Name := 'tsFileFilters'; + SetPosition(125, 3, 442, 424); + Text := 'File Filters'; + end; + + grdSyntaxDefs := TfpgStringGrid.Create(tsSyntaxDefs); + with grdSyntaxDefs do + begin + Name := 'grdSyntaxDefs'; + SetPosition(8, 8, 428, 408); + Anchors := [anLeft,anRight,anTop,anBottom]; + AddColumn('Syntax Definition File', 200, taLeftJustify); + AddColumn('File Mask', 200, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 5; + RowSelect := False; + TabOrder := 1; + end; + + grdFileFilters := TfpgStringGrid.Create(tsFileFilters); + with grdFileFilters do + begin + Name := 'grdFileFilters'; + SetPosition(8, 8, 428, 408); + Anchors := [anLeft,anRight,anTop,anBottom]; + AddColumn('Name', 150, taLeftJustify); + AddColumn('File Mask', 200, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 5; + RowSelect := False; + TabOrder := 1; + end; + + tsExtTools := TfpgTabSheet.Create(pcSettings); + with tsExtTools do + begin + Name := 'tsExtTools'; + SetPosition(125, 3, 442, 424); + Text := 'External Tools'; + end; + + Label10 := TfpgLabel.Create(tsExtTools); + with Label10 do + begin + Name := 'Label10'; + SetPosition(8, 234, 212, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Menu Caption'; + end; + + edtExtToolMenu := TfpgEdit.Create(tsExtTools); + with edtExtToolMenu do + begin + Name := 'edtExtToolMenu'; + SetPosition(8, 252, 428, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + Hint := ''; + TabOrder := 3; + Text := ''; + FontDesc := '#Edit1'; + end; + + Label12 := TfpgLabel.Create(tsExtTools); + with Label12 do + begin + Name := 'Label12'; + SetPosition(8, 282, 428, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Program'; + end; + + edtExtToolFile := TfpgFileNameEdit.Create(tsExtTools); + with edtExtToolFile do + begin + Name := 'edtExtToolFile'; + SetPosition(8, 300, 428, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + FileName := ''; + InitialDir := ''; + Filter := ''; + TabOrder := 5; + end; + + Label13 := TfpgLabel.Create(tsExtTools); + with Label13 do + begin + Name := 'Label13'; + SetPosition(8, 330, 420, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Parameters'; + end; + + edtExtToolParams := TfpgEdit.Create(tsExtTools); + with edtExtToolParams do + begin + Name := 'edtExtToolParams'; + SetPosition(8, 348, 428, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + Hint := ''; + TabOrder := 7; + Text := ''; + FontDesc := '#Edit1'; + end; + + btnExtToolAdd := TfpgButton.Create(tsExtTools); + with btnExtToolAdd do + begin + Name := 'btnExtToolAdd'; + SetPosition(8, 8, 24, 24); + Text := ''; + FontDesc := '#Label1'; + Hint := ''; + ImageMargin := -1; + ImageName := 'stdimg.add'; + ImageSpacing := 0; + TabOrder := 8; + end; + + btnExtToolDel := TfpgButton.Create(tsExtTools); + with btnExtToolDel do + begin + Name := 'btnExtToolDel'; + SetPosition(34, 8, 24, 24); + Text := ''; + FontDesc := '#Label1'; + Hint := ''; + ImageMargin := -1; + ImageName := 'stdimg.remove'; + ImageSpacing := 0; + TabOrder := 9; + end; + + lbExtTools := TfpgListBox.Create(tsExtTools); + with lbExtTools do + begin + Name := 'lbExtTools'; + SetPosition(8, 33, 428, 192); + Anchors := [anLeft,anRight,anTop]; + FontDesc := '#List'; + Hint := ''; + HotTrack := False; + PopupFrame := False; + TabOrder := 10; + end; + + cbTabPosition := TfpgComboBox.Create(tsEditor); + with cbTabPosition do + begin + Name := 'cbTabPosition'; + SetPosition(8, 68, 144, 22); + FontDesc := '#List'; + Hint := ''; + Items.Add('top'); + Items.Add('bottom'); + Items.Add('left'); + Items.Add('right'); + TabOrder := 3; + end; + + Label14 := TfpgLabel.Create(tsEditor); + with Label14 do + begin + Name := 'Label14'; + SetPosition(8, 50, 404, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Tab position'; + end; + + cbSyntaxHighlighting := TfpgCheckBox.Create(tsEditor); + with cbSyntaxHighlighting do + begin + Name := 'cbSyntaxHighlighting'; + SetPosition(4, 100, 404, 20); + Anchors := [anLeft,anRight,anTop]; + FontDesc := '#Label1'; + Hint := ''; + TabOrder := 5; + Text := 'Syntax Highlighting'; + end; + + {@VFD_BODY_END: ConfigureIDEForm} + {%endregion} +end; + + +end. diff --git a/examples/apps/ide/src/frm_debug.pas b/examples/apps/ide/src/frm_debug.pas new file mode 100644 index 00000000..267871c2 --- /dev/null +++ b/examples/apps/ide/src/frm_debug.pas @@ -0,0 +1,170 @@ +unit frm_debug; + +{$mode objfpc}{$H+} + +interface + +uses + SysUtils, Classes, fpg_base, fpg_main, fpg_form, fpg_tab, fpg_grid; + +type + + TDebugForm = class(TfpgForm) + private + {@VFD_HEAD_BEGIN: DebugForm} + PageControl1: TfpgPageControl; + TabSheet1: TfpgTabSheet; + TabSheet2: TfpgTabSheet; + TabSheet3: TfpgTabSheet; + TabSheet4: TfpgTabSheet; + TabSheet5: TfpgTabSheet; + TabSheet6: TfpgTabSheet; + TabSheet7: TfpgTabSheet; + Grid1: TfpgStringGrid; + Grid2: TfpgStringGrid; + Grid3: TfpgStringGrid; + {@VFD_HEAD_END: DebugForm} + public + procedure AfterCreate; override; + end; + +{@VFD_NEWFORM_DECL} + +var + DebugForm: TDebugForm; + +implementation + +{@VFD_NEWFORM_IMPL} + +procedure TDebugForm.AfterCreate; +begin + {%region 'Auto-generated GUI code' -fold} + {@VFD_BODY_BEGIN: DebugForm} + Name := 'DebugForm'; + SetPosition(690, 193, 512, 247); + WindowTitle := 'Debug Window'; + Hint := ''; + + PageControl1 := TfpgPageControl.Create(self); + with PageControl1 do + begin + Name := 'PageControl1'; + SetPosition(4, 4, 506, 240); + ActivePageIndex := 0; + Hint := ''; + TabOrder := 0; + end; + + TabSheet1 := TfpgTabSheet.Create(PageControl1); + with TabSheet1 do + begin + Name := 'TabSheet1'; + SetPosition(3, 24, 500, 213); + Text := 'Watches'; + end; + + TabSheet2 := TfpgTabSheet.Create(PageControl1); + with TabSheet2 do + begin + Name := 'TabSheet2'; + SetPosition(3, 24, 500, 213); + Text := 'BreakPoints'; + end; + + TabSheet3 := TfpgTabSheet.Create(PageControl1); + with TabSheet3 do + begin + Name := 'TabSheet3'; + SetPosition(3, 24, 500, 213); + Text := 'Local Vars'; + end; + + TabSheet4 := TfpgTabSheet.Create(PageControl1); + with TabSheet4 do + begin + Name := 'TabSheet4'; + SetPosition(3, 24, 500, 213); + Text := 'Call Stack'; + end; + + TabSheet5 := TfpgTabSheet.Create(PageControl1); + with TabSheet5 do + begin + Name := 'TabSheet5'; + SetPosition(3, 24, 500, 213); + Text := 'Registers'; + end; + + TabSheet6 := TfpgTabSheet.Create(PageControl1); + with TabSheet6 do + begin + Name := 'TabSheet6'; + SetPosition(3, 24, 500, 213); + Text := 'Asm'; + end; + + TabSheet7 := TfpgTabSheet.Create(PageControl1); + with TabSheet7 do + begin + Name := 'TabSheet7'; + SetPosition(3, 24, 500, 213); + Text := 'GDB output'; + end; + + Grid1 := TfpgStringGrid.Create(TabSheet1); + with Grid1 do + begin + Name := 'Grid1'; + SetPosition(0, 4, 496, 204); + AddColumn('Expression', 100, taLeftJustify); + AddColumn('Value', 350, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 5; + RowSelect := False; + TabOrder := 0; + end; + + Grid2 := TfpgStringGrid.Create(TabSheet2); + with Grid2 do + begin + Name := 'Grid2'; + SetPosition(0, 4, 496, 204); + AddColumn('State', 50, taLeftJustify); + AddColumn('Filename/Addres', 120, taLeftJustify); + AddColumn('Line/Length', 85, taLeftJustify); + AddColumn('Condition', 70, taLeftJustify); + AddColumn('Action', 50, taLeftJustify); + AddColumn('Count', 50, taLeftJustify); + AddColumn('Group', 80, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 5; + RowSelect := False; + TabOrder := 0; + end; + + Grid3 := TfpgStringGrid.Create(TabSheet3); + with Grid3 do + begin + Name := 'Grid3'; + SetPosition(0, 4, 496, 204); + AddColumn('Name', 150, taLeftJustify); + AddColumn('Value', 250, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 5; + RowSelect := False; + TabOrder := 0; + end; + + {@VFD_BODY_END: DebugForm} + {%endregion} +end; + + +end. diff --git a/examples/apps/ide/src/frm_main.pas b/examples/apps/ide/src/frm_main.pas new file mode 100644 index 00000000..85b1258b --- /dev/null +++ b/examples/apps/ide/src/frm_main.pas @@ -0,0 +1,1277 @@ +unit frm_main; + +{$mode objfpc}{$H+} + +interface + +uses + SysUtils, Classes, fpg_base, fpg_main, fpg_form, fpg_menu, fpg_panel, + fpg_button, fpg_splitter, fpg_tab, fpg_memo, fpg_label, fpg_grid, + fpg_tree, fpg_textedit, fpg_mru, synregexpr; + +type + + TMainForm = class(TfpgForm) + private + {@VFD_HEAD_BEGIN: MainForm} + pnlMenu: TfpgBevel; + mainmenu: TfpgMenuBar; + Toolbar: TfpgBevel; + btnQuit: TfpgButton; + btnOpen: TfpgButton; + btnSave: TfpgButton; + btnSaveAll: TfpgButton; + btnTest: TfpgButton; + pnlStatusBar: TfpgBevel; + lblStatus: TfpgLabel; + pnlClientArea: TfpgBevel; + pnlWindow: TfpgPageControl; + tsMessages: TfpgTabSheet; + grdMessages: TfpgStringGrid; + tsScribble: TfpgTabSheet; + memScribble: TfpgMemo; + tsTerminal: TfpgTabSheet; + Splitter1: TfpgSplitter; + pnlTool: TfpgPageControl; + tsProject: TfpgTabSheet; + tvProject: TfpgTreeView; + tsFiles: TfpgTabSheet; + grdFiles: TfpgFileGrid; + Splitter2: TfpgSplitter; + grdOpenFiles: TfpgStringGrid; + Splitter3: TfpgSplitter; + pcEditor: TfpgPageControl; + tseditor: TfpgTabSheet; + TextEditor: TfpgTextEdit; + mnuFile: TfpgPopupMenu; + mnuEdit: TfpgPopupMenu; + mnuSearch: TfpgPopupMenu; + mnuView: TfpgPopupMenu; + mnuProject: TfpgPopupMenu; + mnuRun: TfpgPopupMenu; + mnuTools: TfpgPopupMenu; + mnuSettings: TfpgPopupMenu; + mnuHelp: TfpgPopupMenu; + {@VFD_HEAD_END: MainForm} + pmOpenRecentMenu: TfpgPopupMenu; + miRecentProjects: TfpgMenuItem; + FRecentFiles: TfpgMRU; + FRegex: TRegExpr; + procedure FormShow(Sender: TObject); + procedure FormClose(Sender: TObject; var CloseAction: TCloseAction); + procedure btnQuitClicked(Sender: TObject); + procedure btnOpenFileClicked(Sender: TObject); + procedure miFileSave(Sender: TObject); + procedure miFileSaveAs(Sender: TObject); + procedure miSearchProcedureList(Sender: TObject); + procedure miAboutFPGuiClicked(Sender: TObject); + procedure miAboutIDE(Sender: TObject); + procedure miRunMake(Sender: TObject); + procedure miRunBuild(Sender: TObject); + procedure miRunMake1(Sender: TObject); + procedure miRunMake2(Sender: TObject); + procedure miRunMake3(Sender: TObject); + procedure miRunMake4(Sender: TObject); + procedure miConfigureIDE(Sender: TObject); + procedure miViewDebug(Sender: TObject); + procedure miProjectNew(Sender: TObject); + procedure miProjectNewFromTemplate(Sender: TObject); + procedure miProjectOptions(Sender: TObject); + procedure miProjectOpen(Sender: TObject); + procedure miRecentProjectsClick(Sender: TObject; const FileName: String); + procedure miProjectSave(Sender: TObject); + procedure miProjectSaveAs(Sender: TObject); + procedure miProjectAddUnitToProject(Sender: TObject); + procedure tvProjectDoubleClick(Sender: TObject; AButton: TMouseButton; AShift: TShiftState; const AMousePos: TPoint); + procedure TabSheetClosing(Sender: TObject; ATabSheet: TfpgTabSheet); + procedure BuildTerminated(Sender: TObject); + procedure BuildOutput(Sender: TObject; const ALine: string); + procedure UpdateStatus(const AText: TfpgString); + procedure SetupProjectTree; + procedure PopuplateProjectTree; + procedure SetupFilesGrid; + procedure AddMessage(const AMsg: TfpgString); + procedure ClearMessagesWindow; + procedure CloseAllTabs; + procedure LoadProject(const AFilename: TfpgString); + function OpenEditorPage(const AFilename: TfpgString): TfpgTabSheet; + procedure miTest(Sender: TObject); + function GetUnitsNode: TfpgTreeNode; + procedure UpdateWindowTitle; + procedure TextEditDrawLine(Sender: TObject; ALineText: TfpgString; ALineIndex: Integer; ACanvas: TfpgCanvas; ATextRect: TfpgRect; var AllowSelfDraw: Boolean); + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + procedure AfterCreate; override; + end; + +{@VFD_NEWFORM_DECL} + +implementation + +uses + process + ,fpg_iniutils + ,fpg_dialogs + ,fpg_utils + ,fpg_stringutils + ,fpg_constants + ,fpg_widget + ,frm_configureide + ,frm_projectoptions + ,frm_debug + ,frm_procedurelist + ,fpg_basegrid + ,ideconst + ,idemacros + ,Project + ,UnitList + ,BuilderThread + ,dbugintf + ,ideutils + ; + + +const + cTitle = 'fpGUI IDE - %s'; + cFileFilterTemplate = '%s (%s)|%s'; + cSourceFiles = '*.pas;*.pp;*.lpr;*.dpr;*.inc'; + cProjectFiles = '*.project'; + + { nicely working so far } + cKeywords = '\s*(begin|end|read|write|try|finally|except|uses|interface' + + '|implementation|procedure|function|constructor|destructor|property' + + '|private|public|published|type|class|unit|program|if|then|for|downto|to' + + '|do|else|while|and|inherited|const|var|initialization|finalization)[^0-9a-zA-Z:=;\)\( ]*'; + + cComments1 = '\s*\/\/.*$'; + +{@VFD_NEWFORM_IMPL} + +procedure TMainForm.btnQuitClicked(Sender: TObject); +begin + Close; +end; + +procedure TMainForm.btnOpenFileClicked(Sender: TObject); +var + s: TfpgString; +begin + s := SelectFileDialog(sfdOpen, Format(cFileFilterTemplate, ['Source Files', cSourceFiles, cSourceFiles])); + if s <> '' then + begin + OpenEditorPage(s); + end; +end; + +procedure TMainForm.miFileSave(Sender: TObject); +var + s: TfpgString; +begin + s := pcEditor.ActivePage.Hint; + if s <> '' then + TfpgTextEdit(pcEditor.ActivePage.Components[0]).SaveToFile(s); +end; + +procedure TMainForm.miFileSaveAs(Sender: TObject); +var + s: TfpgString; +begin + s := SelectFileDialog(sfdSave); + if s <> '' then + TfpgTextEdit(pcEditor.ActivePage.Components[0]).SaveToFile(s); +end; + +procedure TMainForm.miSearchProcedureList(Sender: TObject); +var + s: TfpgString; + edt: TfpgTextEdit; +begin + s := pcEditor.ActivePage.Hint; + if s <> '' then + begin + edt := TfpgTextEdit(pcEditor.ActivePage.Components[0]); + DisplayProcedureList(s, edt); + end; +end; + +procedure TMainForm.miAboutFPGuiClicked(Sender: TObject); +begin + TfpgMessageDialog.AboutFPGui; +end; + +procedure TMainForm.miAboutIDE(Sender: TObject); +begin + TfpgMessageDialog.Information('About fpGUI IDE', + 'fpGUI IDE version ' + FPGUI_VERSION + LineEnding + LineEnding + + 'Created by Graeme Geldenhuys' + LineEnding + + 'Compiled with FPC ' + FPCVersion); +end; + +procedure TMainForm.miRunMake(Sender: TObject); +var + thd: TBuilderThread; +begin + ClearMessagesWindow; + thd := TBuilderThread.Create(True); + thd.OnTerminate := @BuildTerminated; + thd.OnAvailableOutput := @BuildOutput; + thd.Resume; +end; + +procedure TMainForm.miRunBuild(Sender: TObject); +var + thd: TBuilderThread; +begin + ClearMessagesWindow; + thd := TBuilderThread.Create(True); + thd.BuildMode := 1; + thd.OnTerminate := @BuildTerminated; + thd.OnAvailableOutput := @BuildOutput; + thd.Resume; +end; + +procedure TMainForm.miRunMake1(Sender: TObject); +var + thd: TBuilderThread; +begin + ClearMessagesWindow; + thd := TBuilderThread.Create(True); + thd.BuildMode := 2; + thd.OnTerminate := @BuildTerminated; + thd.OnAvailableOutput := @BuildOutput; + thd.Resume; +end; + +procedure TMainForm.miRunMake2(Sender: TObject); +var + thd: TBuilderThread; +begin + ClearMessagesWindow; + thd := TBuilderThread.Create(True); + thd.BuildMode := 3; + thd.OnTerminate := @BuildTerminated; + thd.OnAvailableOutput := @BuildOutput; + thd.Resume; +end; + +procedure TMainForm.miRunMake3(Sender: TObject); +var + thd: TBuilderThread; +begin + ClearMessagesWindow; + thd := TBuilderThread.Create(True); + thd.BuildMode := 4; + thd.OnTerminate := @BuildTerminated; + thd.OnAvailableOutput := @BuildOutput; + thd.Resume; +end; + +procedure TMainForm.miRunMake4(Sender: TObject); +var + thd: TBuilderThread; +begin + ClearMessagesWindow; + thd := TBuilderThread.Create(True); + thd.BuildMode := 5; + thd.OnTerminate := @BuildTerminated; + thd.OnAvailableOutput := @BuildOutput; + thd.Resume; +end; + +procedure TMainForm.miConfigureIDE(Sender: TObject); +begin + DisplayConfigureIDE; + pcEditor.TabPosition := TfpgTabPosition(gINI.ReadInteger(cEditor, 'TabPosition', 0)); +end; + +procedure TMainForm.miViewDebug(Sender: TObject); +begin + if not Assigned(DebugForm) then + fpgApplication.CreateForm(TDebugForm, TfpgWindowBase(DebugForm)); + DebugForm.Show; +end; + +procedure TMainForm.miProjectNew(Sender: TObject); +begin + CloseAllTabs; + FreeProject; + GProject.ProjectName := 'empty.project'; + GProject.MainUnit := 'empty.pas'; + OpenEditorPage(GProject.MainUnit); + miProjectSaveAs(nil); +end; + +procedure TMainForm.miProjectNewFromTemplate(Sender: TObject); +var + dlg: TfpgFileDialog; + lFilename: TfpgString; +begin + CloseAllTabs; + FreeProject; + dlg := TfpgFileDialog.Create(nil); + try + dlg.InitialDir := GMacroList.ExpandMacro(cMacro_TemplateDir); + dlg.Filter := Format(cFileFilterTemplate, ['Project Files', cProjectFiles, cProjectFiles]) + + '|' + rsAllFiles+' ('+AllFilesMask+')'+'|'+AllFilesMask; + if dlg.RunOpenFile then + begin + lFilename := dlg.FileName; + GProject.Load(lFilename); + end; + finally + dlg.Free; + end; +end; + +procedure TMainForm.miProjectOptions(Sender: TObject); +begin + DisplayProjectOptions; +end; + +procedure TMainForm.miProjectOpen(Sender: TObject); +var + s: TfpgString; +begin + s := SelectFileDialog(sfdOpen, Format(cFileFilterTemplate, ['Project Files', cProjectFiles, cProjectFiles])); + if s <> '' then + begin + LoadProject(s); + end; +end; + +procedure TMainForm.miRecentProjectsClick(Sender: TObject; const FileName: String); +begin + LoadProject(Filename); +end; + +procedure TMainForm.miProjectSave(Sender: TObject); +begin + try + GProject.Save; + except + on E: Exception do + begin + TfpgMessageDialog.Critical('', E.Message); + end; + end; + AddMessage('Project saved.'); +end; + +procedure TMainForm.miProjectSaveAs(Sender: TObject); +var + s: TfpgString; +begin + s := SelectFileDialog(sfdSave, Format(cFileFilterTemplate, ['Project Files', cProjectFiles, cProjectFiles])); + if s <> '' then + begin + if fpgExtractFileExt(s) = '' then + s := s + cProjectExt; + try + GProject.Save(s); + except + on E: Exception do + begin + TfpgMessageDialog.Critical('', E.Message); + end; + end; + UpdateWindowTitle; + AddMessage(Format('Project saved as <%s>.', [s])); + end; +end; + +procedure TMainForm.miProjectAddUnitToProject(Sender: TObject); +var + u: TUnit; + s: TfpgString; + r: TfpgTreeNode; + n: TfpgTreeNode; +begin + s := pcEditor.ActivePage.Hint; +// writeln('adding unit: ', s); + if s = '' then + Exit; + if GProject.UnitList.FileExists(s) then + Exit; + u := TUnit.Create; + u.FileName := s; + u.Opened := True; + GProject.UnitList.Add(u); + // add reference to tabsheet + pcEditor.ActivePage.TagPointer := u; + s := ExtractRelativepath(GProject.ProjectDir, u.FileName); + r := GetUnitsNode; + n := r.AppendText(s); + // add reference to treenode + n.Data := u; + tvProject.Invalidate; +end; + +procedure TMainForm.tvProjectDoubleClick(Sender: TObject; AButton: TMouseButton; AShift: TShiftState; const AMousePos: TPoint); +var + r: TfpgTreeNode; + n: TfpgTreeNode; + ts: TfpgTabSheet; + u: TUnit; +begin + r := GetUnitsNode; + n := tvProject.Selection; + if n.Data <> nil then + u := TUnit(n.Data); + if u <> nil then + begin + ts := OpenEditorPage(u.FileName); + u.Opened := True; + ts.TagPointer := u; // add reference to tabsheet + end; +end; + +procedure TMainForm.TabSheetClosing(Sender: TObject; ATabSheet: TfpgTabSheet); +var + u: TUnit; +begin + u := TUnit(ATabSheet.TagPointer); + if Assigned(u) then + u.Opened := False; +end; + +procedure TMainForm.BuildTerminated(Sender: TObject); +begin + AddMessage('Compilation complete'); +end; + +procedure TMainForm.BuildOutput(Sender: TObject; const ALine: string); +begin + AddMessage(ALine); +end; + +procedure TMainForm.UpdateStatus(const AText: TfpgString); +begin + lblStatus.Text := AText; +end; + +procedure TMainForm.SetupProjectTree; +begin + tvProject.RootNode.Clear; + tvProject.RootNode.AppendText('Units'); + tvProject.RootNode.AppendText('Images'); + tvProject.RootNode.AppendText('Help Files'); + tvProject.RootNode.AppendText('Text'); + tvProject.RootNode.AppendText('Other'); +end; + +procedure TMainForm.PopuplateProjectTree; +var + r: TfpgTreeNode; + n: TfpgTreeNode; + i: integer; + s: TfpgString; +begin + r := GetUnitsNode; + tvProject.Selection := r; + if Assigned(r) then // just to be safe, but 'Units' should always exist + begin + for i := 0 to GProject.UnitList.Count-1 do + begin + {$Note ExtractRelativePath still needs a fpGUI wrapper } + s := ExtractRelativepath(GProject.ProjectDir, GProject.UnitList[i].FileName); + n := r.AppendText(s); + n.Data := GProject.UnitList[i]; + end; + end; + r.Expand; + tvProject.Invalidate; +end; + +procedure TMainForm.SetupFilesGrid; +begin + grdFiles.FileList.FileMask := AllFilesMask; + grdFiles.FileList.ShowHidden := False; + grdFiles.FileList.ReadDirectory; + grdFiles.FileList.Sort(soFileName); + grdFiles.Invalidate; +end; + +procedure TMainForm.AddMessage(const AMsg: TfpgString); +begin + grdMessages.BeginUpdate; + grdMessages.RowCount := grdMessages.RowCount + 1; + grdMessages.Cells[0,grdMessages.RowCount-1] := AMsg; + grdMessages.FocusRow := grdMessages.RowCount; + grdMessages.EndUpdate; +// fpgApplication.ProcessMessages; +end; + +procedure TMainForm.ClearMessagesWindow; +begin + grdMessages.RowCount := 0; +end; + +procedure TMainForm.CloseAllTabs; +var + ts: TfpgTabSheet; + i: integer; +begin + for i := 0 to pcEditor.PageCount-1 do + begin + ts := pcEditor.Pages[0]; + pcEditor.RemoveTabSheet(ts); + ts.Free; + end; +end; + +procedure TMainForm.LoadProject(const AFilename: TfpgString); +var + i: integer; + ts: TfpgTabSheet; +begin + // remove all project info + CloseAllTabs; + SetupProjectTree; + FreeProject; + // now load new project info + GProject.Load(AFilename); + FRecentFiles.AddItem(AFilename); + for i := 0 to GProject.UnitList.Count-1 do + begin + if GProject.UnitList[i].Opened then + begin + ts := OpenEditorPage(GProject.UnitList[i].FileName); + ts.TagPointer := GProject.UnitList[i]; + end; + end; + PopuplateProjectTree; + UpdateWindowTitle; + AddMessage('Project loaded'); +end; + +function TMainForm.OpenEditorPage(const AFilename: TfpgString): TfpgTabSheet; +var + s: TfpgString; + f: TfpgString; + i: integer; + found: Boolean; + ts: TfpgTabSheet; + m: TfpgTextEdit; + ext: TfpgString; +begin + s := AFilename; + f := fpgExtractFileName(s); + found := False; + for i := 0 to pcEditor.PageCount-1 do + begin + if pcEditor.Pages[i].Text = f then + found := True; + if found then + break; + end; + if found then + begin + // reuse existing tab + TfpgTextEdit(pcEditor.Pages[i].Components[0]).Lines.BeginUpdate; + TfpgTextEdit(pcEditor.Pages[i].Components[0]).LoadFromFile(s); + TfpgTextEdit(pcEditor.Pages[i].Components[0]).Lines.EndUpdate; + pcEditor.ActivePageIndex := i; + ts := pcEditor.ActivePage; + end + else + begin + // we need a new tabsheet + ts := pcEditor.AppendTabSheet(f); + m := TfpgTextEdit.Create(ts); + m.SetPosition(1, 1, 200, 20); + m.Align := alClient; + m.FontDesc := gINI.ReadString(cEditor, 'Font', '#Edit2'); + m.GutterVisible := True; + m.GutterShowLineNumbers := True; + m.RightEdge := True; + TfpgTextEdit(ts.Components[0]).Lines.BeginUpdate; + TfpgTextEdit(ts.Components[0]).Lines.LoadFromFile(s); + TfpgTextEdit(ts.Components[0]).Lines.EndUpdate; + if gINI.ReadBool(cEditor, 'SyntaxHighlighting', True) then + begin + ext := fpgExtractFileExt(AFilename); + if (ext = '.pas') or (ext = '.pp') or (ext = '.inc') then + TfpgTextEdit(ts.Components[0]).OnDrawLine := @TextEditDrawLine; + end; + ts.Realign; + pcEditor.ActivePage := ts; + end; + ts.Hint := s; + Result := ts; + UpdateStatus(s); +end; + +procedure TMainForm.miTest(Sender: TObject); +var + s: TfpgString; + r: TfpgString; +begin + TempHourGlassCursor(TfpgWidget(self)); + s := cMacro_Compiler + ' -FU' +cMacro_Target+' -Fu' + cMacro_FPGuiLibDir; +// writeln('source string = ', s); + r := GMacroList.ExpandMacro(s); +// writeln('expanded string = ', r); + sleep(5000); +end; + +function TMainForm.GetUnitsNode: TfpgTreeNode; +begin + Result := tvProject.RootNode.FindSubNode('Units', True); +end; + +procedure TMainForm.UpdateWindowTitle; +begin + WindowTitle := Format(cTitle, [GProject.ProjectName]); +end; + +const + cReservedWords: array[1..50] of string = + ('begin', 'end', 'program', 'procedure', 'var', + 'uses', 'type', 'const', 'if', 'then', + 'for', 'do', 'unit', 'interface', 'implementation', + 'initialization', 'finalization', 'with', 'case', 'private', + 'protected', 'public', 'published', 'override', 'virtual', + 'class', 'record', 'function', 'property', 'to', + 'else', 'finally', 'while', 'except', 'try', + 'constructor', 'destructor', 'read', 'write', 'out', + 'default', 'not', 'and', 'in', 'raise', + 'of', 'resourcestring', 'operator', 'inherited', 'array'); + +procedure TMainForm.TextEditDrawLine(Sender: TObject; ALineText: TfpgString; + ALineIndex: Integer; ACanvas: TfpgCanvas; ATextRect: TfpgRect; + var AllowSelfDraw: Boolean); +var + oldfont, newfont: TfpgFont; + s: TfpgString; // copy of ALineText we work with + i, j, c: integer; // i = position of reserved word; c = last character pos + iLength: integer; // length of reserved word + w: integer; // reserved word loop variable + r: TfpgRect; // string rectangle to draw in + edt: TfpgTextEdit; + lMatchPos, lOffset: integer; // user for regex + + procedure TestFurther(var AIndex: integer); + begin + if AIndex = 0 then + begin + AIndex := UTF8Pos(cReservedWords[w], s); + if (AIndex > 0) then + begin + if (AIndex+iLength-1 <> Length(s)) and not (s[AIndex+iLength] in [';', '.', '(', #10, #13]) then + AIndex := 0; + end; + end; + end; + +begin +// writeln('syntax highlight line: ', ALineIndex); + edt := TfpgTextEdit(Sender); + AllowSelfDraw := False; + + oldfont := TfpgFont(ACanvas.Font); + ACanvas.Color := clWhite; + + { draw the plain text first } + ACanvas.TextColor := clBlack; + ACanvas.DrawText(ATextRect, ALineText); + + lMatchPos := 0; + lOffset := 0; + + { syntax highlighting for: keywords } + newfont := fpgGetFont(edt.FontDesc + ':bold'); + ACanvas.Font := newfont; + FRegex.Expression := cKeywords; + if FRegex.Exec(ALineText) then + begin + for i := 1 to FRegex.SubExprMatchCount do + begin + lMatchPos := FRegex.MatchPos[i]; + lOffset := FRegex.MatchLen[i]; + s := FRegex.Match[i]; + j := Length(s); + r.SetRect(ATextRect.Left + (edt.FontWidth * (lMatchPos-1)), ATextRect.Top, + (edt.FontWidth * j), ATextRect.Height); + ACanvas.FillRectangle(r); + ACanvas.DrawText(r, s); + end; + end; + + { syntax highlighting for: comments } + ACanvas.Font := oldfont; + ACanvas.TextColor := clDarkCyan; + FRegex.Expression := cComments1; + if FRegex.Exec(ALineText) then + begin + if FRegex.SubExprMatchCount = 0 then + begin + i := 0; + lMatchPos := FRegex.MatchPos[i]; + lOffset := FRegex.MatchLen[i]; + s := FRegex.Match[i]; + j := Length(s); + r.SetRect(ATextRect.Left + (edt.FontWidth * (lMatchPos-1)), ATextRect.Top, + (edt.FontWidth * j), ATextRect.Height); + ACanvas.FillRectangle(r); + ACanvas.DrawText(r, s); + end + else + begin + for i := 1 to FRegex.SubExprMatchCount do + begin + lMatchPos := FRegex.MatchPos[i]; + lOffset := FRegex.MatchLen[i]; + s := FRegex.Match[i]; + j := Length(s); + r.SetRect(ATextRect.Left + (edt.FontWidth * (lMatchPos-1)), ATextRect.Top, + (edt.FontWidth * j), ATextRect.Height); + ACanvas.FillRectangle(r); + ACanvas.DrawText(r, s); + end; + end; + end; + + ACanvas.Font := oldfont; + newfont.Free; +// writeln('------'); +end; + +procedure TMainForm.FormShow(Sender: TObject); +var + lErrPos: integer; +begin + Left := gINI.ReadInteger(Name + 'State', 'Left', Left); + Top := gINI.ReadInteger(Name + 'State', 'Top', Top); + Width := gINI.ReadInteger(Name + 'State', 'Width', Width); + Height := gINI.ReadInteger(Name + 'State', 'Height', Height); + UpdateWindowPosition; + + SetupProjectTree; + SetupFilesGrid; + + // apply editor settings + pcEditor.TabPosition := TfpgTabPosition(gINI.ReadInteger(cEditor, 'TabPosition', 0)); + FRegex := TRegExpr.Create; + FRegex.Expression := cKeywords; +end; + +procedure TMainForm.FormClose(Sender: TObject; var CloseAction: TCloseAction); +begin + CloseAction := caFree; + gINI.WriteInteger(Name + 'State', 'Left', Left); + gINI.WriteInteger(Name + 'State', 'Top', Top); + gINI.WriteInteger(Name + 'State', 'Width', Width); + gINI.WriteInteger(Name + 'State', 'Height', Height); +end; + +constructor TMainForm.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + OnShow := @FormShow; + OnClose := @FormClose; + SendDebug('TMainForm.Create'); +end; + +destructor TMainForm.Destroy; +begin + FRegex.Free; + inherited Destroy; +end; + +procedure TMainForm.AfterCreate; +begin + SendMethodEnter('TMainForm.AfterCreate'); + {%region 'Auto-generated GUI code' -fold} + {@VFD_BODY_BEGIN: MainForm} + Name := 'MainForm'; + SetPosition(310, 206, 638, 428); + WindowTitle := 'fpGUI IDE - %s'; + Hint := ''; + WindowPosition := wpOneThirdDown; + + pnlMenu := TfpgBevel.Create(self); + with pnlMenu do + begin + Name := 'pnlMenu'; + SetPosition(0, 0, 638, 54); + Align := alTop; + Hint := ''; + Shape := bsSpacer; + end; + + mainmenu := TfpgMenuBar.Create(pnlMenu); + with mainmenu do + begin + Name := 'mainmenu'; + SetPosition(0, 0, 638, 24); + Anchors := [anLeft,anRight,anTop]; + Align := alTop; + end; + + Toolbar := TfpgBevel.Create(pnlMenu); + with Toolbar do + begin + Name := 'Toolbar'; + SetPosition(2, 2, 634, 50); + Anchors := [anLeft,anRight,anTop]; + Align := alClient; + Hint := ''; + Shape := bsSpacer; + end; + + btnQuit := TfpgButton.Create(Toolbar); + with btnQuit do + begin + Name := 'btnQuit'; + SetPosition(4, 2, 24, 24); + Text := ''; + Down := False; + Embedded := True; + FontDesc := '#Label1'; + Hint := ''; + ImageMargin := 0; + ImageName := 'stdimg.quit'; + TabOrder := 3; + OnClick := @btnQuitClicked; + end; + + btnOpen := TfpgButton.Create(Toolbar); + with btnOpen do + begin + Name := 'btnOpen'; + SetPosition(28, 2, 24, 24); + Text := ''; + Down := False; + Embedded := True; + FontDesc := '#Label1'; + Hint := ''; + ImageMargin := 0; + ImageName := 'stdimg.open'; + TabOrder := 4; + OnClick := @btnOpenFileClicked; + end; + + btnSave := TfpgButton.Create(Toolbar); + with btnSave do + begin + Name := 'btnSave'; + SetPosition(56, 2, 24, 24); + Text := ''; + Down := False; + Embedded := True; + FontDesc := '#Label1'; + Hint := ''; + ImageMargin := 0; + ImageName := 'stdimg.save'; + TabOrder := 5; + OnClick := @miFileSave; + end; + + btnSaveAll := TfpgButton.Create(Toolbar); + with btnSaveAll do + begin + Name := 'btnSaveAll'; + SetPosition(80, 2, 24, 24); + Text := ''; + Down := False; + Embedded := True; + Enabled := False; + FontDesc := '#Label1'; + Hint := ''; + ImageMargin := 0; + ImageName := 'stdimg.saveall'; + TabOrder := 6; + end; + + btnTest := TfpgButton.Create(Toolbar); + with btnTest do + begin + Name := 'btnTest'; + SetPosition(168, 2, 80, 24); + Text := 'test'; + Down := False; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 7; + OnClick := @miTest; + end; + + pnlStatusBar := TfpgBevel.Create(self); + with pnlStatusBar do + begin + Name := 'pnlStatusBar'; + SetPosition(0, 408, 636, 20); + Anchors := [anLeft,anRight,anBottom]; + Hint := ''; + Style := bsLowered; + end; + + lblStatus := TfpgLabel.Create(pnlStatusBar); + with lblStatus do + begin + Name := 'lblStatus'; + SetPosition(2, 2, 632, 16); + Anchors := [anLeft,anRight,anTop]; + Align := alBottom; + FontDesc := '#Label1'; + Hint := ''; + Text := ''; + end; + + pnlClientArea := TfpgBevel.Create(self); + with pnlClientArea do + begin + Name := 'pnlClientArea'; + SetPosition(0, 54, 638, 374); + Anchors := [anLeft,anRight,anTop,anBottom]; + Align := alClient; + Hint := ''; + Shape := bsSpacer; + end; + + pnlWindow := TfpgPageControl.Create(pnlClientArea); + with pnlWindow do + begin + Name := 'pnlWindow'; + SetPosition(2, 288, 634, 84); + ActivePageIndex := 0; + Align := alBottom; + Hint := ''; + TabOrder := 11; + TabPosition := tpRight; + end; + + tsMessages := TfpgTabSheet.Create(pnlWindow); + with tsMessages do + begin + Name := 'tsMessages'; + SetPosition(73, 3, 558, 78); + Text := 'Messages'; + end; + + grdMessages := TfpgStringGrid.Create(tsMessages); + with grdMessages do + begin + Name := 'grdMessages'; + SetPosition(0, 4, 558, 73); + Anchors := [anLeft,anRight,anTop,anBottom]; + BackgroundColor := TfpgColor($80000002); + AddColumn('New', 800, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 0; + RowSelect := True; + ShowHeader := False; + TabOrder := 13; + end; + + tsScribble := TfpgTabSheet.Create(pnlWindow); + with tsScribble do + begin + Name := 'tsScribble'; + SetPosition(73, 3, 188, 78); + Text := 'Scribble'; + end; + + memScribble := TfpgMemo.Create(tsScribble); + with memScribble do + begin + Name := 'memScribble'; + SetPosition(0, 4, 187, 73); + Anchors := [anLeft,anRight,anTop,anBottom]; + FontDesc := '#Edit2'; + Hint := ''; + Lines.Add('Make notes, use it as a clipboard'); + Lines.Add('or type whatever you want...'); + TabOrder := 15; + end; + + tsTerminal := TfpgTabSheet.Create(pnlWindow); + with tsTerminal do + begin + Name := 'tsTerminal'; + SetPosition(73, 3, 188, 78); + Text := 'Terminal'; + end; + + Splitter1 := TfpgSplitter.Create(pnlClientArea); + with Splitter1 do + begin + Name := 'Splitter1'; + SetPosition(2, 281, 634, 7); + Align := alBottom; + end; + + pnlTool := TfpgPageControl.Create(pnlClientArea); + with pnlTool do + begin + Name := 'pnlTool'; + SetPosition(2, 2, 140, 279); + ActivePageIndex := 0; + Align := alLeft; + Hint := ''; + TabOrder := 18; + end; + + tsProject := TfpgTabSheet.Create(pnlTool); + with tsProject do + begin + Name := 'tsProject'; + SetPosition(3, 24, 134, 252); + Text := 'Project'; + end; + + tvProject := TfpgTreeView.Create(tsProject); + with tvProject do + begin + Name := 'tvProject'; + SetPosition(1, 1, 132, 250); + Anchors := [anLeft,anRight,anTop,anBottom]; + FontDesc := '#Label1'; + Hint := ''; + TabOrder := 20; + OnDoubleClick := @tvProjectDoubleClick; + end; + + tsFiles := TfpgTabSheet.Create(pnlTool); + with tsFiles do + begin + Name := 'tsFiles'; + SetPosition(3, 24, 134, 193); + Text := 'Files'; + end; + + grdFiles := TfpgFileGrid.Create(tsFiles); + with grdFiles do + begin + Name := 'grdFiles'; + SetPosition(1, 1, 131, 190); + Anchors := [anLeft,anRight,anTop,anBottom]; + Options := Options + [go_SmoothScroll]; + end; + + Splitter2 := TfpgSplitter.Create(pnlClientArea); + with Splitter2 do + begin + Name := 'Splitter2'; + SetPosition(142, 2, 8, 279); + Align := alLeft; + end; + + grdOpenFiles := TfpgStringGrid.Create(pnlClientArea); + with grdOpenFiles do + begin + Name := 'grdOpenFiles'; + SetPosition(516, 2, 120, 279); + Align := alRight; + BackgroundColor := TfpgColor($80000002); + AddColumn('File', 100, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 0; + RowSelect := True; + ShowHeader := False; + TabOrder := 24; + end; + + Splitter3 := TfpgSplitter.Create(pnlClientArea); + with Splitter3 do + begin + Name := 'Splitter3'; + SetPosition(508, 2, 8, 279); + Align := alRight; + end; + + pcEditor := TfpgPageControl.Create(pnlClientArea); + with pcEditor do + begin + Name := 'pcEditor'; + SetPosition(150, 2, 358, 279); + ActivePageIndex := 0; + Align := alClient; + Hint := ''; + TabOrder := 18; + TabPosition := tpRight; + Options := Options + [to_PMenuClose]; + OnClosingTabSheet := @TabSheetClosing; + end; + + tseditor := TfpgTabSheet.Create(pcEditor); + with tseditor do + begin + Name := 'tseditor'; + SetPosition(3, 3, 282, 273); + Text := 'Tabsheet1'; + end; + + TextEditor := TfpgTextEdit.Create(tseditor); + with TextEditor do + begin + Name := 'TextEditor'; + SetPosition(0, 0, 130, 200); + Align := alClient; + end; + + mnuFile := TfpgPopupMenu.Create(self); + with mnuFile do + begin + Name := 'mnuFile'; + SetPosition(476, 61, 172, 20); + AddMenuItem('New...', '', nil).Enabled := False; + AddMenuItem('Open...', '', nil).Enabled := False; + AddMenuItem('Open Recent', '', nil).Enabled := False; + AddMenuItem('Save', '', @miFileSave); + AddMenuItem('Save As...', '', @miFileSaveAs); + AddMenuItem('-', '', nil); + AddMenuItem('Quit', '', @btnQuitClicked); + end; + + mnuEdit := TfpgPopupMenu.Create(self); + with mnuEdit do + begin + Name := 'mnuEdit'; + SetPosition(476, 80, 172, 20); + AddMenuItem('Cut', '', nil).Enabled := False; + AddMenuItem('Copy', '', nil).Enabled := False; + AddMenuItem('Paste', '', nil).Enabled := False; + end; + + mnuSearch := TfpgPopupMenu.Create(self); + with mnuSearch do + begin + Name := 'mnuSearch'; + SetPosition(476, 98, 172, 20); + AddMenuItem('Find...', '', nil).Enabled := False; + AddMenuItem('Find in Files...', '', nil).Enabled := False; + AddMenuItem('Procedure List...', '', @miSearchProcedureList); + end; + + mnuView := TfpgPopupMenu.Create(self); + with mnuView do + begin + Name := 'mnuView'; + SetPosition(476, 119, 172, 20); + AddMenuItem('Todo List...', '', nil).Enabled := False; + AddMenuItem('Debug Windows', '', @miViewDebug); + end; + + mnuProject := TfpgPopupMenu.Create(self); + with mnuProject do + begin + Name := 'mnuProject'; + SetPosition(476, 140, 172, 20); + AddMenuItem('Options...', '', @miProjectOptions); + AddMenuItem('-', '', nil); + AddMenuItem('New (empty)...', '', @miProjectNew); + AddMenuItem('New from Template...', '', @miProjectNewFromTemplate); + AddMenuItem('Open...', '', @miProjectOpen); + miRecentProjects := AddMenuItem('Open Recent', '', nil); + AddMenuItem('Save', '', @miProjectSave); + AddMenuItem('Save As...', '', @miProjectSaveAs); + AddMenuItem('-', '', nil); + AddMenuItem('View Source', '', nil); + AddMenuItem('Add editor file to Project', '', @miProjectAddUnitToProject); + end; + + mnuRun := TfpgPopupMenu.Create(self); + with mnuRun do + begin + Name := 'mnuRun'; + SetPosition(476, 161, 172, 20); + AddMenuItem('Make', 'Ctrl+F9', @miRunMake); + AddMenuItem('Build All', 'Ctrl+Shift+F9', @miRunBuild); + AddMenuItem('Make 1', 'Ctrl+Alt+1', @miRunMake1); + AddMenuItem('Make 2', 'Ctrl+Alt+2', @miRunMake2); + AddMenuItem('Make 3', 'Ctrl+Alt+3', @miRunMake3); + AddMenuItem('Make 4', 'Ctrl+Alt+4', @miRunMake4); + AddMenuItem('-', '', nil); + AddMenuItem('Run', 'F9', nil); + AddMenuItem('Run Parameters...', '', nil); + end; + + mnuTools := TfpgPopupMenu.Create(self); + with mnuTools do + begin + Name := 'mnuTools'; + SetPosition(476, 182, 172, 20); + AddMenuItem('fpGUI UI Designer...', '', nil); + AddMenuItem('fpGUI DocView...', '', nil); + end; + + mnuSettings := TfpgPopupMenu.Create(self); + with mnuSettings do + begin + Name := 'mnuSettings'; + SetPosition(476, 203, 172, 20); + AddMenuItem('Configure IDE...', '', @miConfigureIDE); + end; + + mnuHelp := TfpgPopupMenu.Create(self); + with mnuHelp do + begin + Name := 'mnuHelp'; + SetPosition(476, 224, 172, 20); + AddMenuItem('Contents...', '', nil); + AddMenuItem('-', '', nil); + AddMenuItem('About fpGUI Toolkit...', '', @miAboutFPGuiClicked); + AddMenuItem('About fpGUI IDE...', '', @miAboutIDE); + end; + + {@VFD_BODY_END: MainForm} + {%endregion} + +{ + pcEditor.AppendTabSheet('Five'); + pcEditor.AppendTabSheet('Six'); + pcEditor.AppendTabSheet('Seven'); + pcEditor.AppendTabSheet('Eight'); + pcEditor.AppendTabSheet('Nine'); + pcEditor.AppendTabSheet('Ten'); + pcEditor.AppendTabSheet('11'); + pcEditor.AppendTabSheet('12'); + pcEditor.AppendTabSheet('13'); + pcEditor.AppendTabSheet('14'); + pcEditor.AppendTabSheet('15'); + pcEditor.AppendTabSheet('16'); + pcEditor.AppendTabSheet('17'); + pcEditor.AppendTabSheet('18'); + pcEditor.AppendTabSheet('19'); + pcEditor.AppendTabSheet('20'); +} + + mainmenu.AddMenuItem('&File', nil).SubMenu := mnuFile; + mainmenu.AddMenuItem('&Edit', nil).SubMenu := mnuEdit; + mainmenu.AddMenuItem('&Search', nil).SubMenu := mnuSearch; + mainmenu.AddMenuItem('&View', nil).SubMenu := mnuView; + mainmenu.AddMenuItem('&Project', nil).SubMenu := mnuProject; + mainmenu.AddMenuItem('&Run', nil).SubMenu := mnuRun; + mainmenu.AddMenuItem('&Tools', nil).SubMenu := mnuTools; + mainmenu.AddMenuItem('&Settings', nil).SubMenu := mnuSettings; + mainmenu.AddMenuItem('&Help', nil).SubMenu := mnuHelp; + + pmOpenRecentMenu := TfpgPopupMenu.Create(self); + with pmOpenRecentMenu do + begin + Name := 'pmOpenRecentMenu'; + SetPosition(336, 68, 128, 20); + end; + + miRecentProjects.SubMenu := pmOpenRecentMenu; + + FRecentFiles := TfpgMRU.Create(self); + FRecentFiles.ParentMenuItem := pmOpenRecentMenu; + FRecentFiles.OnClick :=@miRecentProjectsClick; + FRecentFiles.MaxItems := gINI.ReadInteger('Options', 'MRUProjectCount', 10); + FRecentFiles.ShowFullPath := gINI.ReadBool('Options', 'ShowFullPath', True); + FRecentFiles.LoadMRU; + + + SendMethodExit('TMainForm.AfterCreate'); +end; + + +end. diff --git a/examples/apps/ide/src/frm_procedurelist.pas b/examples/apps/ide/src/frm_procedurelist.pas new file mode 100644 index 00000000..4b5b7548 --- /dev/null +++ b/examples/apps/ide/src/frm_procedurelist.pas @@ -0,0 +1,1333 @@ +unit frm_procedurelist; + +{$mode objfpc}{$H+} + +interface + +uses + SysUtils, Classes, fpg_base, fpg_main, fpg_form, fpg_panel, fpg_label, + fpg_edit, fpg_combobox, fpg_basegrid, fpg_grid, fpg_imagelist, + pparser, pastree, fpg_textedit; + +type + + TSimpleEngine = class(TPasTreeContainer) + public + function CreateElement(AClass: TPTreeElement; const AName: String; + AParent: TPasElement; AVisibility: TPasMemberVisibility; + const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement; override; + function FindElement(const AName: String): TPasElement; override; + end; + + TSourceLanguage = (ltPas, ltCpp); + + TProcInfo = class(TObject) + private + FLineNo: Integer; + FName: string; + FDisplayName: string; + FProcedureType: string; + FProcArgs: string; + FProcClass: string; + FProcReturnType: string; + FProcName: string; + FProcIndex: Integer; + public + property LineNo: Integer read FLineNo write FLineNo; + property Name: string read FName write FName; + property DisplayName: string read FDisplayName write FDisplayName; + property ProcedureType: string read FProcedureType write FProcedureType; + property ProcArgs: string read FProcArgs write FProcArgs; + property ProcName: string read FProcName write FProcName; + property ProcClass: string read FProcClass write FProcClass; + property ProcReturnType: string read FProcReturnType write FProcReturnType; + property ProcIndex: Integer read FProcIndex write FProcIndex; + end; + + + TProcedureListForm = class(TfpgForm) + private + procedure SearchTextChanged(Sender: TObject); + procedure SearchEditKeyPressed(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure SetFilename(const AValue: string); + private + {@VFD_HEAD_BEGIN: ProcedureListForm} + Bevel1: TfpgBevel; + Bevel2: TfpgBevel; + lblSearch: TfpgLabel; + edtSearch: TfpgEdit; + cbObjects: TfpgComboBox; + lblObjects: TfpgLabel; + grdProcedures: TfpgStringGrid; + StatusBar: TfpgPanel; + {@VFD_HEAD_END: ProcedureListForm} + FFilename: TfpgString; + FLanguage: TSourceLanguage; + FSortOnColumn: Integer; + FSearchAll: Boolean; + FProcList: TStringList; + FObjectStrings: TStringList; + FEditor: TfpgTextEdit; +// FImageList: TfpgImageList; + procedure FormShow(Sender: TObject); + procedure LoadProcs; + procedure AddProcedure(ProcedureInfo: TProcInfo); + procedure ClearObjectStrings; + procedure LoadObjectCombobox; + procedure QuickSort(L, R: Integer); + procedure InitializeForm; + procedure FillListBox; + function GetMethodName(const ProcName: string): string; + function GetImageIndex(const ProcName, ProcClass: string): Integer; + procedure GridDrawCell(Sender: TObject; const ARow, ACol: Integer; const ARect: TfpgRect; const AFlags: TfpgGridDrawState; var ADefaultDrawing: boolean); + property Filename: string read FFilename write SetFilename; + property Editor: TfpgTextEdit read FEditor write FEditor; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + procedure AfterCreate; override; + property Language: TSourceLanguage read FLanguage write FLanguage default ltPas; + end; + +{@VFD_NEWFORM_DECL} + +function DisplayProcedureList(const AFilename: TfpgString; var AEditor: TfpgTextEdit): boolean; + + +implementation + +uses + ideconst + ,mPasLex + ,ideutils + ,dbugintf + ,fpg_utils + ,fpg_imgfmt_bmp + ; + +const + SAllString = '<All>'; + SNoneString = '<None>'; + SUnknown = 'Unknown'; + SImplementationNotFound = 'Implementation section not found (parser error?)'; + SInvalidIndex = 'Invalid index number'; + SParseStatistics = 'Procedures processed in %g seconds'; + + +{$I proclistimages.inc} + + +function DisplayProcedureList(const AFilename: TfpgString; var AEditor: TfpgTextEdit): boolean; +var + frm: TProcedureListForm; +begin + try + frm := TProcedureListForm.Create(nil); + frm.Filename := AFilename; + frm.Editor := AEditor; + frm.ShowModal; + finally + frm.Free; + AEditor.SetFocus; + end; +end; + +{@VFD_NEWFORM_IMPL} + +procedure TProcedureListForm.SearchTextChanged(Sender: TObject); +begin + FillListBox; +end; + +procedure TProcedureListForm.SearchEditKeyPressed(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); +begin + case KeyCode of + KeyUp: + begin + grdProcedures.FocusRow := grdProcedures.FocusRow-1; + consumed := True; + end; + KeyDown: + begin + grdProcedures.FocusRow := grdProcedures.FocusRow+1; + consumed := True; + end; + KeyEnter: + begin + Editor.GotoLine(StrToInt(grdProcedures.Cells[3, grdProcedures.FocusRow])); + consumed := True; + Close; + end; + KeyEscape: + begin + consumed := True; + Close; + end; + end; +end; + +procedure TProcedureListForm.SetFilename(const AValue: string); +var + LoadTime: DWord; +begin + if FFilename=AValue then exit; + FFilename:=AValue; +// if IsCpp(FileName) or IsC(FileName) or IsH(FileName) then +// Language := ltCpp +// else + Language := ltPas; + + LoadTime := fpgGetTickCount; + InitializeForm; + LoadTime := fpgGetTickCount - LoadTime; + StatusBar.Text := Format(SParseStatistics, [LoadTime / 1000]); +end; + +procedure TProcedureListForm.FormShow(Sender: TObject); +var + M: TPasModule; + E: TPasTreeContainer; + I: Integer; + Decls: TList; + p: TPasElement; +begin +{ + E := TSimpleEngine.Create; + try + writeln(Format('Parsing file <%s> for OS <%s> and CPU <%s>', [FFilename, OSTarget, CPUTarget])); + M := ParseSource(E, FFilename, OSTarget, CPUTarget); + + { Cool, we successfully parsed the unit. + Now output some info about it. } + Decls := M.InterfaceSection.Declarations; + for I := 0 to Decls.Count - 1 do + begin + p := TObject(Decls[I]) as TPasElement; + Writeln('Interface item ', I, ': ' + p.Name + ' [line ' + IntToStr(p.SourceLinenumber) + ']'); + + end; + FreeAndNil(M); + finally + FreeAndNil(E) + end; +} +end; + +procedure TProcedureListForm.InitializeForm; +begin + FObjectStrings := TStringList.Create; + FObjectStrings.Sorted := True; + FObjectStrings.Duplicates := dupIgnore; + ClearObjectStrings; + + FSortOnColumn := 1; + + FProcList := TStringList.Create; + +// LoadSettings; + LoadProcs; + + FillListBox; + edtSearch.SetFocus; +end; + +procedure TProcedureListForm.FillListBox; +var + i: Integer; + ProcName: string; + IsObject: Boolean; + ProcInfo: TProcInfo; + + procedure AddListItem(ProcInfo: TProcInfo); + var + r: integer; + begin + r := grdProcedures.RowCount; + grdProcedures.RowCount := grdProcedures.RowCount + 1; + grdProcedures.Objects[0, r] := ProcInfo; +// case Language of +// ltPas: ListItem.ImageIndex := GetPasImageIndex(ProcInfo.Name); +// ltCpp: ListItem.ImageIndex := ProcInfo.ProcIndex; +// end; + grdProcedures.Cells[1, r] := ProcInfo.DisplayName; + grdProcedures.Cells[2, r] := ProcInfo.ProcedureType; + grdProcedures.Cells[3, r] := IntToStr(ProcInfo.LineNo); + end; + + procedure FocusAndSelectFirstItem; + begin + if grdProcedures.RowCount > 0 then + begin + grdProcedures.FocusRow := 0; +// lvProcs.ItemFocused := lvProcs.Selected; + end; + end; + +begin + grdProcedures.BeginUpdate; + try + grdProcedures.RowCount := 0; + if (Length(edtSearch.Text) = 0) and (cbObjects.Text = SAllString) then + begin + for i := 0 to FProcList.Count - 1 do + AddListItem(TProcInfo(FProcList.Objects[i])); + FocusAndSelectFirstItem; + Exit; + end; + + for i := 0 to FProcList.Count - 1 do + begin + ProcInfo := TProcInfo(FProcList.Objects[i]); + case Language of + ltPas: ProcName := ProcInfo.Name; + ltCpp: ProcName := ProcInfo.ProcClass; + end; + IsObject := Length(ProcInfo.ProcClass) > 0; + + // Is it the object we want? + if cbObjects.Text <> SAllString then + begin + if cbObjects.Text = SNoneString then + begin + if IsObject then // Does it have an object? + Continue; + if Length(edtSearch.Text) = 0 then // If no filter is active, add + begin + AddListItem(ProcInfo); + Continue; + end; + end // if/then + else if not SameText(cbObjects.Text, ProcInfo.ProcClass) then + Continue; + end; + + case Language of + ltPas: ProcName := GetMethodName(ProcName); + ltCpp: ProcName := ProcInfo.ProcName; + end; + + if Length(edtSearch.Text) = 0 then + AddListItem(ProcInfo) + else if not FSearchAll and SameText(edtSearch.Text, Copy(ProcName, 1, Length(edtSearch.Text))) then + AddListItem(ProcInfo) + else if FSearchAll and StrContains(edtSearch.Text, ProcName, False) then + AddListItem(ProcInfo); + end; + FocusAndSelectFirstItem; + finally + grdProcedures.EndUpdate; + end; +// ResizeCols; +end; + +function TProcedureListForm.GetMethodName(const ProcName: string): string; +var + CharPos: Integer; +begin + Result := ProcName; + Delete(Result, 1, 1); + + CharPos := Pos(#9, Result); + if CharPos <> 0 then + Delete(Result, CharPos, Length(Result)); + + CharPos := Pos(' ', Result); + Result := Copy(Result, CharPos + 1, Length(Result)); + + CharPos := Pos('(', Result); + if CharPos > 0 then + Result := Copy(Result, 1, CharPos - 1); + + CharPos := Pos('.', Result); + if CharPos > 0 then + Result := Copy(Result, CharPos + 1, Length(Result)); + + Result := Trim(Result); +end; + +function TProcedureListForm.GetImageIndex(const ProcName, ProcClass: string): Integer; +const + ImageIndexNew = 1; + ImageIndexTrash = 2; + ImageIndexGear = 3; + ImageIndexFunction = 4; +begin + if StrContains('constructor', ProcName, False) then // Do not localize. + Result := ImageIndexNew + else if StrContains('destructor', ProcName, False) then // Do not localize. + Result := ImageIndexTrash + else if StrBeginsWith('class proc', ProcName, False) // Do not localize. + or StrContains('class func', ProcName, False) + or (ProcClass <> '') then + Result := ImageIndexGear + else + Result := ImageIndexFunction; +end; + +procedure TProcedureListForm.GridDrawCell(Sender: TObject; const ARow, ACol: Integer; const ARect: TfpgRect; const AFlags: TfpgGridDrawState; var ADefaultDrawing: boolean); +var + img: TfpgImage; + i: integer; + ProcInfo: TProcInfo; +begin + ADefaultDrawing := True; + if ACol = 0 then + begin + ProcInfo := grdProcedures.Objects[ACol, ARow] as TProcInfo; + i := GetImageIndex(ProcInfo.ProcedureType, ProcInfo.ProcClass); +{ + ImageIndexNew = 1; + ImageIndexTrash = 2; + ImageIndexGear = 3; + ImageIndexFunction = 4; +} + case i of + 1: img := fpgImages.GetImage('ide.grid.constr'); + 2: img := fpgImages.GetImage('ide.grid.destr'); + 3: img := fpgImages.GetImage('ide.grid.gears'); + 4: img := fpgImages.GetImage('ide.grid.func'); + end; + if Assigned(img) then + grdProcedures.Canvas.DrawImage((ARect.Width-16) div 2, ARect.Top, img); + end; +end; + +procedure TProcedureListForm.LoadProcs; +var + Parser: TmwPasLex; +// CParser: TBCBTokenList; + BeginBracePosition: Longint; + BraceCount, PreviousBraceCount: Integer; + + function MoveToImplementation: Boolean; + begin + if IsProgram(FFileName) or (IsInc(FFileName)) then + begin + Result := True; + Exit; + end; + Result := False; + while Parser.TokenID <> tkNull do + begin + if Parser.TokenID = tkImplementation then + Result := True; + Parser.Next; + if Result then + Break; + end; + end; + + procedure FindProcs; + + function GetProperProcName(ProcType: TTokenKind; IsClass: Boolean): string; + begin + Result := SUnknown; + if IsClass then + begin + if ProcType = tkFunction then + Result := 'Class Func' // Do not localize. + else if ProcType = tkProcedure then + Result := 'Class Proc'; // Do not localize. + end + else + begin + case ProcType of + // Do not localize. + tkFunction: Result := 'Function'; + tkProcedure: Result := 'Procedure'; + tkConstructor: Result := 'Constructor'; + tkDestructor: Result := 'Destructor'; + end; + end; + end; + +(* + procedure FindBeginningBrace; + begin + repeat + CParser.NextNonJunk; + case CParser.RunID of + ctkbraceopen: Inc(BraceCount); + ctkbraceclose: Dec(BraceCount); + ctknull: Exit; + end; + until (CParser.RunID = ctkbraceopen) or + (CParser.RunID = ctkbracepair) or + (CParser.RunID = ctknull); + end; + + // This procedure does two things. It looks for procedures and it + // looks for named scopes (like class/struct definitions & namespaces) + // If it finds a named scope it returns the non-blank name. If it finds + // a procedure it returns a blank name. + procedure FindBeginningProcedureBrace(var Name: string); // Used for CPP + var + InitialPosition: Integer; + RestorePosition: Integer; + FoundClass: Boolean; + begin + BeginBracePosition := 0; + InitialPosition := CParser.RunPosition; + // Skip these: enum {a, b, c}; or int a[] = {0, 3, 5}; and find foo () { + FindBeginningBrace; + if CParser.RunID = ctknull then + Exit; + CParser.PreviousNonJunk; + // Check for a namespace or a class name + if CParser.RunID = ctkidentifier then + begin + Name := CParser.RunToken; // The name + // This might be a derived class so search backward + // no further than InitialPosition to see + RestorePosition := CParser.RunPosition; + FoundClass := False; + while CParser.RunPosition >= InitialPosition do begin + if CParser.RunID in [ctkclass, ctkstruct, ctknamespace] then + begin + FoundClass := True; + Break; + end; + if CParser.RunPosition = InitialPosition then + Break; + CParser.PreviousNonJunk; + end; + // The class name is the last token before a : or { + if FoundClass then + begin + while not (CParser.RunID in [ctkcolon, ctkbraceopen, ctknull]) do begin + Name := CParser.RunToken; + CParser.NextNonJunk; + end; + // Back up a bit if we are on a brace open so empty enums don't get treated as namespaces + if CParser.RunID = ctkbraceopen then + CParser.PreviousNonJunk; + end; + // Now get back to where you belong + while CParser.RunPosition < RestorePosition do + CParser.NextNonJunk; + CParser.NextNonJunk; + BeginBracePosition := CParser.RunPosition; + end + else + begin + if CParser.RunID in [ctkroundclose, ctkroundpair, ctkconst, ctkvolatile, ctknull] then + begin + // Return an empty name to indicate that a procedure was found + Name := ''; + CParser.NextNonJunk; + BeginBracePosition := CParser.RunPosition; + end + else + begin + while not (CParser.RunID in [ctkroundclose, ctkroundpair, ctkconst, ctkvolatile, ctknull]) do + begin + CParser.NextNonJunk; + if CParser.RunID = ctknull then + Exit; + // Recurse + FindBeginningProcedureBrace(Name); + CParser.PreviousNonJunk; + if Name <> '' then Break; + end; + CParser.NextNonJunk; + end; + end; + end; + + // This function searches backward from the current parser position + // trying to find the procedure name - it returns all of the text + // between the starting position and the position where it thinks a + // procedure name has been found. + function SearchForProcedureName: string; + var + ParenCount: Integer; + begin + ParenCount := 0; + Result := ''; + repeat + CParser.Previous; + if CParser.RunID <> ctkcrlf then + if (CParser.RunID = ctkspace) and (CParser.RunToken = #9) then + Result := #32 + Result + else + Result := CParser.RunToken + Result; + case CParser.RunID of + ctkroundclose: Inc(ParenCount); + ctkroundopen: Dec(ParenCount); + ctknull: Exit; + end; + until ((ParenCount = 0) and ((CParser.RunID = ctkroundopen) or (CParser.RunID = ctkroundpair))); + CParser.PreviousNonJunk; // This is the procedure name + end; + + function SearchForTemplateArgs: string; + var + AngleCount: Integer; + begin + Result := ''; + if CParser.RunID <> ctkGreater then + Exit; // only use if we are on a '>' + AngleCount := 1; + Result := CParser.RunToken; + repeat + CParser.Previous; + if CParser.RunID <> ctkcrlf then + if (CParser.RunID = ctkspace) and (CParser.RunToken = #9) then + Result := #32 + Result + else + Result := CParser.RunToken + Result; + case CParser.RunID of + ctkgreater: Inc(AngleCount); + ctklower: Dec(AngleCount); + ctknull: Exit; + end; + until (((AngleCount = 0) and (CParser.RunID = ctklower)) or (CParser.RunIndex = 0)); + CParser.PreviousNonJunk; // This is the token before the template args + end; +*) + + var + ProcLine: string; + ProcType: TTokenKind; + Line: Integer; + ClassLast: Boolean; + InParenthesis: Boolean; + InTypeDeclaration: Boolean; + FoundNonEmptyType: Boolean; + IdentifierNeeded: Boolean; + ProcedureInfo: TProcInfo; + BeginProcHeaderPosition: Longint; + i, j: Integer; + LineNo: Integer; + ProcName, ProcReturnType: string; + ProcedureType, ProcClass, ProcArgs: string; + ProcIndex: Integer; + NameList: TStringList; + NewName, TmpName, ProcClassAdd, ClassName: string; + BraceCountDelta: Integer; + TemplateArgs: string; + + procedure EraseName(Index: Integer); + var + NameIndex: Integer; + begin + NameIndex := NameList.IndexOfName(IntToStr(Index)); + if NameIndex <> -1 then + NameList.Delete(NameIndex); + end; + + begin + FProcList.Capacity := 200; + FProcList.BeginUpdate; + try + case Language of + ltPas: + begin + if not MoveToImplementation then + raise Exception.Create(SImplementationNotFound); + ClassLast := False; + InParenthesis := False; + InTypeDeclaration := False; + FoundNonEmptyType := False; + + while Parser.TokenID <> tkNull do + begin + if not InTypeDeclaration and + (Parser.TokenID in [tkFunction, tkProcedure, tkConstructor, tkDestructor]) then + begin + IdentifierNeeded := True; + ProcType := Parser.TokenID; + Line := Parser.LineNumber + 1; + ProcLine := ''; + while not (Parser.TokenId in [tkNull]) do + begin + //{$IFOPT D+} SendDebug('Found Inner Token: '+ Parser.Token+ ' '+BTS(ClassLast)); {$ENDIF} + case Parser.TokenID of + tkIdentifier, tkRegister: + IdentifierNeeded := False; + + tkRoundOpen: + begin + // Did we run into an identifier already? + // This prevents + // AProcedure = procedure() of object + // from being recognised as a procedure + if IdentifierNeeded then + Break; + InParenthesis := True; + end; + + tkRoundClose: + InParenthesis := False; + + else + // nothing + end; // case + + if (not InParenthesis) and (Parser.TokenID = tkSemiColon) then + Break; + + if not (Parser.TokenID in [tkCRLF, tkCRLFCo]) then + ProcLine := ProcLine + Parser.Token; + Parser.Next; + end; // while + if Parser.TokenID = tkSemicolon then + ProcLine := ProcLine + ';'; + if ClassLast then + ProcLine := 'class ' + ProcLine; // Do not localize. + //{$IFOPT D+} SendDebug('FoundProc: ' + ProcLine); {$ENDIF} + if not IdentifierNeeded then + begin + ProcedureInfo := TProcInfo.Create; + ProcedureInfo.Name := ProcLine; + ProcedureInfo.ProcedureType := GetProperProcName(ProcType, ClassLast); + ProcedureInfo.LineNo := Line; + AddProcedure(ProcedureInfo); + end; + end; + if (Parser.TokenID = tkClass) and Parser.IsClass then + begin + InTypeDeclaration := True; + FoundNonEmptyType := False; + end + else if InTypeDeclaration and + (Parser.TokenID in [tkProcedure, tkFunction, tkProperty, + tkPrivate, tkProtected, tkPublic, tkPublished]) then + begin + FoundNonEmptyType := True; + end + else if InTypeDeclaration and + ((Parser.TokenID = tkEnd) or + ((Parser.TokenID = tkSemiColon) and not FoundNonEmptyType)) then + begin + InTypeDeclaration := False; + end; + //{$IFOPT D+} SendDebug('Found Token: '+ Parser.Token+ ' '+BTS(ClassLast)); {$ENDIF} + ClassLast := (Parser.TokenID = tkClass); + if ClassLast then + begin + Parser.NextNoJunk; + //{$IFOPT D+} SendDebug('Found Class Token'+ ' '+BTS(ClassLast)); {$ENDIF} + end + else + Parser.Next; + end; + end; //ltPas + +(* + ltCpp: + begin + NameList := TStringList.Create; + try + BraceCount := 0; + NameList.Add('0='); // empty enclosure name + j := CParser.TokenPositionsList[CParser.TokenPositionsList.Count - 1]; + PreviousBraceCount := BraceCount; + FindBeginningProcedureBrace(NewName); + + while (CParser.RunPosition <= j - 1) or (CParser.RunID <> ctknull) do + begin + // If NewName = '' then we are looking at a real procedure - otherwise + // we've just found a new enclosure name to add to our list + if NewName = '' then + begin + // If we found a brace pair then special handling is necessary + // for the bracecounting stuff (it is off by one) + if CParser.RunID = ctkbracepair then + BraceCountDelta := 0 + else + BraceCountDelta := 1; + if (BraceCountDelta > 0) and (PreviousBraceCount >= BraceCount) then + EraseName(PreviousBraceCount); + // Back up a tiny bit so that we are "in front of" the + // ctkbraceopen or ctkbracepair we just found + CParser.Previous; + + while not ((CParser.RunID in [ctksemicolon, ctkbraceclose, ctkbraceopen, ctkbracepair]) or + (CParser.RunID in IdentDirect) or + (CParser.RunIndex = 0)) do + begin + CParser.PreviousNonJunk; + // Handle the case where a colon is part of a valid procedure definition + if CParser.RunID = ctkcolon then + begin + // A colon is valid in a procedure definition only if it is immediately + // following a close parenthesis (possibly separated by "junk") + CParser.PreviousNonJunk; + if CParser.RunID in [ctkroundclose, ctkroundpair] then + CParser.NextNonJunk + else + begin + // Restore position and stop backtracking + CParser.NextNonJunk; + Break; + end; + end; + end; + + if CParser.RunID in [ctkcolon, ctksemicolon, ctkbraceclose, ctkbraceopen, ctkbracepair] then + CParser.NextNonComment + else if CParser.RunIndex = 0 then + begin + if CParser.IsJunk then + CParser.NextNonJunk; + end + else // IdentDirect + begin + while CParser.RunID <> ctkcrlf do + begin + if (CParser.RunID = ctknull) then + Exit; + CParser.Next; + end; + CParser.NextNonJunk; + end; + // We are at the beginning of procedure header + BeginProcHeaderPosition := CParser.RunPosition; + + ProcLine := ''; + while (CParser.RunPosition < BeginBracePosition) and (CParser.RunID <> ctkcolon) do + begin + if (CParser.RunID = ctknull) then + Exit + else if (CParser.RunID <> ctkcrlf) then + if (CParser.RunID = ctkspace) and (CParser.RunToken = #9) then + ProcLine := ProcLine + #32 + else + ProcLine := ProcLine + CParser.RunToken; + CParser.NextNonComment; + end; + // We are at the end of a procedure header + // Go back and skip parenthesis to find the procedure name + ProcName := ''; + ProcClass := ''; + ProcReturnType := ''; + ProcArgs := SearchForProcedureName; + // We have to check for ctknull and exit since we moved the + // code to a nested procedure (if we exit SearchForProcedureName + // early due to RunID = ctknull we exit this procedure early as well) + if CParser.RunID = ctknull then + Exit; + if CParser.RunID = ctkthrow then + begin + ProcArgs := CParser.RunToken + ProcArgs; + ProcArgs := SearchForProcedureName + ProcArgs; + end; + // Since we've enabled nested procedures it is now possible + // that we think we've found a procedure but what we've really found + // is a standard C or C++ construct (like if or for, etc...) + // To guard against this we require that our procedures be of type + // ctkidentifier. If not, then skip this step. + if (CParser.RunID = ctkidentifier) and not InProcedureBlacklist(CParser.RunToken) then + begin + ProcName := CParser.RunToken; + LineNo := CParser.PositionAtLine(CParser.RunPosition); + CParser.PreviousNonJunk; + if CParser.RunID = ctkcoloncolon then // The object/method delimiter + begin + // There may be multiple name::name::name:: sets here + // so loop until no more are found + ClassName := ''; + while CParser.RunID = ctkcoloncolon do begin + CParser.PreviousNonJunk; // The object name? + // It is possible that we are looking at a templatized class and + // what we have in front of the :: is the end of a specialization: + // ClassName<x, y, z>::Function + if CParser.RunID = ctkgreater then + TemplateArgs := SearchForTemplateArgs; + ProcClass := CParser.RunToken + ProcClass; + if ClassName = '' then + ClassName := CParser.RunToken; + CParser.PreviousNonJunk; // look for another :: + if CParser.RunID = ctkcoloncolon then + ProcClass := CParser.RunToken + ProcClass; + end; + // We went back one step too far so go ahead one + CParser.NextNonJunk; + ProcIndex := ImageIndexFunction; + if ProcName = ClassName then // A constructor + ProcIndex := ImageIndexNew; + if ProcName = '~' + ClassName then // A destructor + ProcIndex := ImageIndexTrash; + end + else + begin + ProcIndex := ImageIndexFunction; + // If ProcIndex is 1 then we have backed up too far already + // so restore our previous position in order to correctly + // get the return type information for non-class methods + CParser.NextNonJunk; + end; + + while CParser.RunPosition > BeginProcHeaderPosition do // Find the return type of the procedure + begin + CParser.PreviousNonComment; + // Handle the possibility of template specifications and + // do not include them in the return type + if CParser.RunID = ctkGreater then + TemplateArgs := SearchForTemplateArgs; + if CParser.RunID = ctktemplate then + Continue; + if CParser.RunID in [ctkcrlf, ctkspace] then + ProcReturnType := ' ' + ProcReturnType + else + ProcReturnType := CParser.RunToken + ProcReturnType + end; + + // If the return type is an empty string then it must be a constructor + // or a destructor (depending on the presence of a ~ in the name + if (Trim(ProcReturnType) = '') or (Trim(ProcReturnType) = 'virtual') then + begin + if StrBeginsWith('~', ProcName) then + ProcIndex := ImageIndexTrash // a destructor + else + ProcIndex := ImageIndexNew; // a constructor + end; + + ProcLine := Trim(ProcReturnType) + ' '; + + // This code sticks enclosure names in front of + // methods (namespaces & classes with in-line definitions) + ProcClassAdd := ''; + for i := 0 to BraceCount - BraceCountDelta do begin + if i < NameList.Count then + begin + TmpName := NameList.Values[IntToStr(i)]; + if TmpName <> '' then + begin + if ProcClassAdd <> '' then + ProcClassAdd := ProcClassAdd + '::'; + ProcClassAdd := ProcClassAdd + TmpName; + end; + end; + end; + + if Length(ProcClassAdd) > 0 then + begin + if Length(ProcClass) > 0 then + ProcClassAdd := ProcClassAdd + '::'; + ProcClass := ProcClassAdd + ProcClass; + end; + if Length(ProcClass) > 0 then + ProcLine := ProcLine + ' ' + ProcClass + '::'; + ProcLine := ProcLine + ProcName + ' ' + ProcArgs; + + // We need to double check the ProcIndex if it is = 0 + // if it isn't a "static" method it should be 1 + if (ProcIndex in [ImageIndexFunction, ImageIndexGear]) then + if StrBeginsWith('static ', Trim(ProcReturnType)) and + (Length(ProcClass) > 0) then + ProcIndex := ImageIndexGear + else + ProcIndex := ImageIndexFunction; + + case ProcIndex of + ImageIndexFunction: if StrContains('void', ProcReturnType) then + ProcedureType := 'Procedure' + else + ProcedureType := 'Function'; + ImageIndexGear: if StrContains('void', ProcReturnType) then + ProcedureType := 'Class Proc' + else + ProcedureType := 'Class Func'; + ImageIndexNew: ProcedureType := 'Constructor'; + ImageIndexTrash: ProcedureType := 'Destructor'; + end; + + ProcedureInfo := TProcInfo.Create; + ProcedureInfo.Name := ProcLine; + ProcedureInfo.ProcedureType := ProcedureType; + ProcedureInfo.LineNo := LineNo; + ProcedureInfo.ProcClass := ProcClass; + ProcedureInfo.ProcArgs := ProcArgs; + ProcedureInfo.ProcReturnType := ProcReturnType; + ProcedureInfo.ProcIndex := ProcIndex; + ProcedureInfo.ProcName := ProcName; + AddProcedure(ProcedureInfo); + end; + while (CParser.RunPosition < BeginBracePosition) do + CParser.Next; + end + else begin + // Insert enclosure name into our list (delete the old one if found) + EraseName(BraceCount); + NameList.Add(IntToStr(BraceCount) + '=' + NewName); + end; + PreviousBraceCount := BraceCount; + FindBeginningProcedureBrace(NewName); + end; //while (RunPosition <= j-1) ... + finally + NameList.Free; + end; + end; //Cpp +*) + end; //case Language + finally + FProcList.EndUpdate; + end; + end; + +var + SFile: TFileStream; + MemStream: TMemoryStream; + Pos: Integer; + Size: Integer; +const + TheEnd: Char = #0; // Leave typed constant as is - needed for streaming code +begin + case Language of + ltPas: Parser := TmwPasLex.Create; +// ltCpp: CParser := TBCBTokenList.Create; + end; + try + MemStream := TMemoryStream.Create; + try + // Read from file on disk and store in a memory stream + SFile := TFileStream.Create(FFilename, fmOpenRead or fmShareDenyWrite); + try + SFile.Position := 0; + MemStream.CopyFrom(SFile, SFile.Size); + MemStream.Write(TheEnd, 1); + finally + SFile.Free; + end; + SendDebug('Procedure List: Starting Parse'); + case Language of + ltPas: Parser.Origin := MemStream.Memory; +// ltCpp: CParser.SetOrigin(MemStream.Memory, MemStream.Size); + end; + WindowTitle := WindowTitle + ' - ' + fpgExtractFileName(FFileName); + + ClearObjectStrings; + try + FindProcs; + finally + LoadObjectCombobox; + end; + SendDebug('Procedure List: QuickSorting procedures'); + QuickSort(0, FProcList.Count - 1); +// StatusBar.Panels[1].Text := Trim(IntToStr(lvProcs.Items.Count)); + finally + MemStream.Free; + end; + finally + case Language of + ltPas: Parser.Free; +// ltCpp: CParser.Free; + end; + end; +end; + +procedure TProcedureListForm.AddProcedure(ProcedureInfo: TProcInfo); +var + TempStr: string; + i: Integer; +begin + ProcedureInfo.Name := CompressWhiteSpace(ProcedureInfo.Name); + case Language of + ltPas: + begin + TempStr := ProcedureInfo.Name; + // Remove the class reserved word + if StrBeginsWith('CLASS ', TempStr, False) then // Do not localize. + Delete(TempStr, 1, 6); // Do not localize. + // Remove 'function' or 'procedure' + i := Pos(' ', TempStr); + if i > 0 then + TempStr := Copy(TempStr, i + 1, Length(TempStr)); + // Remove the paramater list + i := Pos('(', TempStr); + if i > 0 then + TempStr := Copy(TempStr, 1, i - 1); + // Remove the function return type + i := Pos(':', TempStr); + if i > 0 then + TempStr := Copy(TempStr, 1, i - 1); + // Check for an implementation procedural type + if Length(TempStr) = 0 then + begin + ProcedureInfo.Free; + Exit; + end; + // Remove any trailing ';' + if TempStr[Length(TempStr)] = ';' then + Delete(TempStr, Length(TempStr), 1); + TempStr := Trim(TempStr); + ProcedureInfo.DisplayName := TempStr; + // Add to the object combobox and set the object name in ProcedureInfo + i := Pos('.', TempStr); + if i = 0 then + FObjectStrings.Add(SNoneString) + else + begin + ProcedureInfo.ProcClass := Copy(TempStr, 1, i - 1); + FObjectStrings.Add(ProcedureInfo.ProcClass); + end; + FProcList.AddObject(#9 + TempStr + #9 + ProcedureInfo.ProcedureType + #9 + IntToStr(ProcedureInfo.LineNo), ProcedureInfo); + end; //ltPas + +(* + ltCpp: + begin + if Length(ProcedureInfo.ProcClass) > 0 then + ProcedureInfo.DisplayName := ProcedureInfo.ProcClass + '::'; + // Should be the return type and args displayed, they are now in the status bar ? + ProcedureInfo.DisplayName := ProcedureInfo.DisplayName + ProcedureInfo.ProcName; + FProcList.AddObject(#9 + ProcedureInfo.DisplayName + #9 + ProcedureInfo.ProcedureType + #9 + IntToStr(ProcedureInfo.LineNo), ProcedureInfo); + if Length(ProcedureInfo.ProcClass) = 0 then + FObjectStrings.Add(SNoneString) + else + FObjectStrings.Add(ProcedureInfo.ProcClass); + end; //ltCpp +*) + end; //case Language +end; + +procedure TProcedureListForm.ClearObjectStrings; +begin + FObjectStrings.Clear; + FObjectStrings.Add(SAllString); +end; + +procedure TProcedureListForm.LoadObjectCombobox; +begin + cbObjects.Items.Assign(FObjectStrings); + cbObjects.FocusItem := cbObjects.Items.IndexOf(SAllString); +end; + +procedure TProcedureListForm.QuickSort(L, R: Integer); + + function GetValue(idx: Integer): string; + var + i: Integer; + TabPos: Integer; + begin + if idx >= FProcList.Count then + raise Exception.Create(SInvalidIndex); + Result := FProcList.Strings[idx]; + for i := 0 to FSortOnColumn - 1 do + begin + TabPos := Pos(#9, Result); + if TabPos > 0 then + Delete(Result, 1, TabPos) + else + Exit; + end; + if FSortOnColumn = 3 then + begin + for i := Length(Result) to 5 do + Result := ' ' + Result; + end; + end; + +var + I, J: Integer; + P: string; +begin + if FProcList.Count = 0 then + Exit; + repeat + I := L; + J := R; + P := GetValue((L + R) shr 1); + repeat + while AnsiCompareText(GetValue(I), P) < 0 do + Inc(I); + while AnsiCompareText(GetValue(J), P) > 0 do + Dec(J); + if I <= J then + begin + FProcList.Exchange(I, J); + Inc(I); + Dec(J); + end; + until I > J; + if L < J then + QuickSort(L, J); + L := I; + until I >= R; +end; + +constructor TProcedureListForm.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + OnShow := @FormShow; + FLanguage := ltPas; + FSearchAll := True; // search anywhere in a method name +// FImageList := TfpgImageList.Create; + +// CreateImage_BMP(@grdimg_destructor_16, SizeOf(grdimg_destructor_16)); + + fpgImages.AddMaskedBMP( // 16x16 image + 'ide.grid.destr', + @grdimg_destructor_16, + sizeof(grdimg_destructor_16), 0, 0); + + fpgImages.AddMaskedBMP( // 16x16 image + 'ide.grid.constr', + @grdimg_constructor_16, + sizeof(grdimg_constructor_16), 0, 0); + + fpgImages.AddMaskedBMP( // 16x16 image + 'ide.grid.func', + @grdimg_function_16, + sizeof(grdimg_function_16), 0, 0); + + fpgImages.AddMaskedBMP( // 16x16 image + 'ide.grid.gears', + @grdimg_gears_16, + sizeof(grdimg_gears_16), 0, 0); +end; + +destructor TProcedureListForm.Destroy; +var + i: Integer; +begin + FreeAndNil(FObjectStrings); + + if FProcList <> nil then + begin + for i := 0 to FProcList.Count - 1 do + FProcList.Objects[i].Free; + FreeAndNil(FProcList); + end; + inherited Destroy; +end; + +procedure TProcedureListForm.AfterCreate; +begin + {%region 'Auto-generated GUI code' -fold} + {@VFD_BODY_BEGIN: ProcedureListForm} + Name := 'ProcedureListForm'; + SetPosition(332, 253, 564, 310); + WindowTitle := 'Procedure List'; + Hint := ''; + ShowHint := True; + WindowPosition := wpOneThirdDown; + + Bevel1 := TfpgBevel.Create(self); + with Bevel1 do + begin + Name := 'Bevel1'; + SetPosition(0, 0, 564, 32); + Anchors := [anLeft,anRight,anTop]; + Hint := ''; + Shape := bsSpacer; + end; + + Bevel2 := TfpgBevel.Create(self); + with Bevel2 do + begin + Name := 'Bevel2'; + SetPosition(0, 33, 564, 32); + Anchors := [anLeft,anRight,anTop]; + Hint := ''; + Shape := bsSpacer; + end; + + lblSearch := TfpgLabel.Create(Bevel2); + with lblSearch do + begin + Name := 'lblSearch'; + SetPosition(4, 8, 47, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Search'; + end; + + edtSearch := TfpgEdit.Create(Bevel2); + with edtSearch do + begin + Name := 'edtSearch'; + SetPosition(52, 4, 264, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + FontDesc := '#Edit1'; + Hint := ''; + TabOrder := 2; + Text := ''; + OnChange := @SearchTextChanged; + OnKeyPress := @SearchEditKeyPressed; + end; + + cbObjects := TfpgComboBox.Create(Bevel2); + with cbObjects do + begin + Name := 'cbObjects'; + SetPosition(376, 4, 184, 22); + Anchors := [anRight,anTop]; + FontDesc := '#List'; + Hint := ''; + TabOrder := 3; + OnChange := @SearchTextChanged; + end; + + lblObjects := TfpgLabel.Create(Bevel2); + with lblObjects do + begin + Name := 'lblObjects'; + SetPosition(324, 8, 51, 16); + Anchors := [anRight,anTop]; + FontDesc := '#Label1'; + Hint := ''; + Text := 'Objects'; + end; + + grdProcedures := TfpgStringGrid.Create(self); + with grdProcedures do + begin + Name := 'grdProcedures'; + SetPosition(4, 68, 556, 216); + Anchors := [anLeft,anRight,anTop,anBottom]; + BackgroundColor := TfpgColor($80000002); + AddColumn('', 30, taLeftJustify); + AddColumn('Procedure', 300, taLeftJustify); + AddColumn('Type', 130, taLeftJustify); + AddColumn('Line', 70, taRightJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 0; + RowSelect := True; + TabOrder := 3; + HeaderStyle := ghsFlat; + OnDrawCell := @GridDrawCell; + end; + + StatusBar := TfpgPanel.Create(self); + with StatusBar do + begin + Name := 'StatusBar'; + SetPosition(0, 290, 564, 20); + Align := alBottom; + Alignment := taLeftJustify; + FontDesc := '#Label1'; + Hint := ''; + Style := bsLowered; + Text := 'Panel'; + end; + + {@VFD_BODY_END: ProcedureListForm} + {%endregion} +end; + + +{ TSimpleEngine } + +function TSimpleEngine.CreateElement(AClass: TPTreeElement; + const AName: String; AParent: TPasElement; AVisibility: TPasMemberVisibility; + const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement; +begin + Result := AClass.Create(AName, AParent); + Result.Visibility := AVisibility; + Result.SourceFilename := ASourceFilename; + Result.SourceLinenumber := ASourceLinenumber; +end; + +function TSimpleEngine.FindElement(const AName: String): TPasElement; +begin + { dummy implementation, see TFPDocEngine.FindElement for a real example } + Result := nil; +end; + +end. diff --git a/examples/apps/ide/src/frm_projectoptions.pas b/examples/apps/ide/src/frm_projectoptions.pas new file mode 100644 index 00000000..7ece019d --- /dev/null +++ b/examples/apps/ide/src/frm_projectoptions.pas @@ -0,0 +1,1164 @@ +unit frm_projectoptions; + +{$mode objfpc}{$H+} + +interface + +uses + SysUtils, Classes, fpg_base, fpg_main, fpg_form, fpg_button, fpg_label, + fpg_tab, fpg_editbtn, fpg_checkbox, fpg_grid, fpg_basegrid, + fpg_combobox, fpg_edit, idemacros; + +type + + TProjectOptionsForm = class(TfpgForm) + private + {@VFD_HEAD_BEGIN: ProjectOptionsForm} + btnCancel: TfpgButton; + btnOK: TfpgButton; + pcOptions: TfpgPageControl; + tsCompiler: TfpgTabSheet; + tsDebugger: TfpgTabSheet; + tsMacros: TfpgTabSheet; + tsOther: TfpgTabSheet; + Label1: TfpgLabel; + edtMainFile: TfpgFileNameEdit; + Label2: TfpgLabel; + edtTargetFile: TfpgFileNameEdit; + edtMakeCommand: TfpgFileNameEdit; + Label3: TfpgLabel; + edtMakeDir: TfpgDirectoryEdit; + Label4: TfpgLabel; + Label5: TfpgLabel; + FilenameEdit4: TfpgFileNameEdit; + CheckBox1: TfpgCheckBox; + Label6: TfpgLabel; + cbDefaultMakeCol: TfpgComboBox; + Button1: TfpgButton; + pcCompiler: TfpgPageControl; + TabSheet1: TfpgTabSheet; + TabSheet2: TfpgTabSheet; + grdCompilerMakeOptions: TfpgStringGrid; + grdCompilerDirs: TfpgStringGrid; + Label11: TfpgLabel; + Label7: TfpgLabel; + FilenameEdit5: TfpgFileNameEdit; + Label8: TfpgLabel; + Edit1: TfpgEdit; + CheckBox2: TfpgCheckBox; + CheckBox3: TfpgCheckBox; + CheckBox4: TfpgCheckBox; + PageControl1: TfpgPageControl; + TabSheet3: TfpgTabSheet; + TabSheet4: TfpgTabSheet; + TabSheet5: TfpgTabSheet; + TabSheet6: TfpgTabSheet; + TabSheet7: TfpgTabSheet; + grdDebugSrcDirs: TfpgStringGrid; + btnShowCmdLine: TfpgButton; + edtUnitOutputDir: TfpgDirectoryEdit; + Label9: TfpgLabel; + Label10: TfpgLabel; + grdMacroGroup: TfpgStringGrid; + Label12: TfpgLabel; + grdUserMacros: TfpgStringGrid; + {@VFD_HEAD_END: ProjectOptionsForm} + FCellEdit: TfpgEdit; + FFocusRect: TfpgRect; + FLastGrid: TfpgStringGrid; // reference only + // so we can get correct hints, but still undo with the Cancel button + FInternalMacroList: TIDEMacroList; + procedure btnShowCmdLineClicked(Sender: TObject); + procedure CellEditExit(Sender: TObject); + procedure CellEditKeypressed(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure grdCompilerDirsDrawCell(Sender: TObject; const ARow, ACol: Integer; const ARect: TfpgRect; const AFlags: TfpgGridDrawState; var ADefaultDrawing: boolean); + procedure grdCompilerDirsKeyPressed(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure grdCompilerMakeOptionsKeyPressed(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure grdCompilerMakeOptionsDrawCell(Sender: TObject; const ARow, ACol: Integer; const ARect: TfpgRect; const AFlags: TfpgGridDrawState; var ADefaultDrawing: boolean); + procedure grdCompilerMakeOptionsClicked(Sender: TObject); + procedure grdCompilerDirsClicked(Sender: TObject); + procedure grdUserMacrosClicked(Sender: TObject); + procedure grdUserMacrosDrawCell(Sender: TObject; const ARow, ACol: Integer; const ARect: TfpgRect; const AFlags: TfpgGridDrawState; var ADefaultDrawing: boolean); + procedure grdMacroGroupCanSelectCell(Sender: TObject; const ARow, ACol: Integer; var ACanSelect: boolean); + procedure BeforeShowHint(Sender: TObject; var AHint: TfpgString); + procedure LoadSettings; + procedure SaveSettings; + procedure SetupCellEdit(AGrid: TfpgStringGrid); + procedure CleanupCompilerMakeOptionsGrid; + procedure CleanupCompilerDirs; + procedure SaveToMacroList(AList: TIDEMacroList); + + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + procedure AfterCreate; override; + end; + +{@VFD_NEWFORM_DECL} + +procedure DisplayProjectOptions; + + +implementation + +uses + fpg_iniutils + ,fpg_dialogs + ,fpg_widget + ,Project + ,ideconst + ,ideutils + ; + +type + // Used to get access to the Protected properties + TDirectoryEditFriend = class(TfpgDirectoryEdit); + + +procedure DisplayProjectOptions; +var + frm: TProjectOptionsForm; + Result: Boolean; +begin + frm := TProjectOptionsForm.Create(nil); + try + frm.LoadSettings; + Result := frm.ShowModal = mrOK; + if Result then + begin + frm.SaveSettings; + end; + finally + frm.Free; + end; +end; + +{@VFD_NEWFORM_IMPL} + +procedure TProjectOptionsForm.btnShowCmdLineClicked(Sender: TObject); +var + c: TfpgString; + b: integer; +begin + // build compilation string + c := gINI.ReadString(cEnvironment, 'Compiler', '') + LineEnding; + b := cbDefaultMakeCol.FocusItem; + + c := c + GProject.GenerateCmdLine(True, b); + try + c := GMacroList.ExpandMacro(c); + except + on E: Exception do + begin + TfpgMessageDialog.Critical('', E.Message); + Exit; + end; + end; + ShowString(c, 'Compile command'); +end; + +procedure TProjectOptionsForm.CellEditExit(Sender: TObject); +begin + FCellEdit.Visible := False; +end; + +procedure TProjectOptionsForm.CellEditKeypressed(Sender: TObject; + var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); +begin + if KeyCode = keyReturn then + begin + FLastGrid.Cells[FLastGrid.FocusCol, FLastGrid.FocusRow] := FCellEdit.Text; + FCellEdit.Visible := False; + FLastGrid.SetFocus; + end; +end; + +procedure TProjectOptionsForm.grdCompilerDirsDrawCell(Sender: TObject; + const ARow, ACol: Integer; const ARect: TfpgRect; + const AFlags: TfpgGridDrawState; var ADefaultDrawing: boolean); +var + img: TfpgImage; +begin + if ACol = 5 then + begin + grdCompilerDirs.Canvas.Color := clMedGray; + grdCompilerDirs.Canvas.DrawLine(ARect.Right-1, ARect.Top, ARect.Right-1, ARect.Bottom); + end + else if ACol = 6 then + begin + grdCompilerDirs.Canvas.Color := clMedGray; + grdCompilerDirs.Canvas.DrawLine(ARect.Left, ARect.Top, ARect.Left, ARect.Bottom); + end; + + if (gdSelected in AFlags) and (ACol = 10) then + begin + FFocusRect := ARect; + end; + + if ACol < 10 then + begin + if grdCompilerDirs.Cells[ACol, ARow] = cCheck then + begin + img := fpgImages.GetImage('stdimg.check'); + if (gdSelected in AFlags) and (gdFocused in AFlags) then + img.Invert; + grdCompilerDirs.Canvas.DrawImage(ARect.Left, ARect.Top, img); + if (gdSelected in AFlags) and (gdFocused in AFlags) then + img.Invert; // restore image to original state + ADefaultDrawing := False; + end; + end + else + grdCompilerDirs.Canvas.Setfont(grdCompilerDirs.Font); +end; + +procedure TProjectOptionsForm.grdCompilerDirsKeyPressed(Sender: TObject; + var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); +begin + if (KeyCode = keyInsert) and (ssCtrl in ShiftState) then + begin + TfpgStringGrid(Sender).RowCount := TfpgStringGrid(Sender).RowCount + 1; + Consumed := True; + Exit; + end; + + if TfpgStringGrid(Sender).FocusCol < 10 then + begin + if (KeyCode = keySpace) then + begin + grdCompilerDirsClicked(Sender); + Consumed := True; + end; + end + else if TfpgStringGrid(Sender).FocusCol = 10 then + begin + if (KeyCode = keyF2) or (KeyCode = keyReturn) then + begin + // we need to edit the cell contents + SetupCellEdit(TfpgStringGrid(Sender)); + Consumed := True; + end; + end; +end; + +procedure TProjectOptionsForm.grdCompilerMakeOptionsKeyPressed(Sender: TObject; + var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); +begin + if (KeyCode = keyInsert) and (ssCtrl in ShiftState) then + begin + TfpgStringGrid(Sender).RowCount := TfpgStringGrid(Sender).RowCount + 1; + Consumed := True; + Exit; + end; + + if TfpgStringGrid(Sender).FocusCol < 6 then + begin + if (KeyCode = keySpace) then + begin + grdCompilerMakeOptionsClicked(Sender); + Consumed := True; + end; + end + else if TfpgStringGrid(Sender).FocusCol = 6 then + begin + if (KeyCode = keyF2) or (KeyCode = keyReturn) then + begin + // we need to edit the cell contents + SetupCellEdit(TfpgStringGrid(Sender)); + Consumed := True; + end; + end; +end; + +procedure TProjectOptionsForm.grdCompilerMakeOptionsDrawCell(Sender: TObject; + const ARow, ACol: Integer; const ARect: TfpgRect; + const AFlags: TfpgGridDrawState; var ADefaultDrawing: boolean); +var + img: TfpgImage; +begin + if (gdSelected in AFlags) and (ACol = 6) then + begin + FFocusRect := ARect; + end; + + if ACol < 6 then + begin + if grdCompilerMakeOptions.Cells[ACol, ARow] = cCheck then + begin + img := fpgImages.GetImage('stdimg.check'); + if (gdSelected in AFlags) and (gdFocused in AFlags) then + img.Invert; + grdCompilerMakeOptions.Canvas.DrawImage(ARect.Left, ARect.Top, img); + if (gdSelected in AFlags) and (gdFocused in AFlags) then + img.Invert; // restore image to original state + ADefaultDrawing := False; + end; + end + else + grdCompilerMakeOptions.Canvas.Setfont(grdCompilerMakeOptions.Font); +end; + +procedure TProjectOptionsForm.grdCompilerMakeOptionsClicked(Sender: TObject); +var + r, c: integer; +begin + if TfpgStringGrid(Sender).RowCount = 0 then + TfpgStringGrid(Sender).RowCount := 1; + r := TfpgStringGrid(Sender).FocusRow; + c := TfpgStringGrid(Sender).FocusCol; + if c < 6 then // checkbox area + begin + if TfpgStringGrid(Sender).Cells[c, r] = '' then + TfpgStringGrid(Sender).Cells[c, r] := cCheck + else + TfpgStringGrid(Sender).Cells[c, r] := ''; + end; +end; + +procedure TProjectOptionsForm.grdCompilerDirsClicked(Sender: TObject); +var + r, c: integer; +begin + if TfpgStringGrid(Sender).RowCount = 0 then + TfpgStringGrid(Sender).RowCount := 1; + r := TfpgStringGrid(Sender).FocusRow; + c := TfpgStringGrid(Sender).FocusCol; + if c < 10 then // checkbox area + begin + if TfpgStringGrid(Sender).Cells[c, r] = '' then + TfpgStringGrid(Sender).Cells[c, r] := cCheck + else + TfpgStringGrid(Sender).Cells[c, r] := ''; + end; +end; + +procedure TProjectOptionsForm.grdUserMacrosClicked(Sender: TObject); +var + r, c: integer; +begin + if TfpgStringGrid(Sender).RowCount = 0 then + TfpgStringGrid(Sender).RowCount := 1; + r := TfpgStringGrid(Sender).FocusRow; + c := TfpgStringGrid(Sender).FocusCol; + if c < 6 then // checkbox area + begin + if TfpgStringGrid(Sender).Cells[c, r] = '' then + TfpgStringGrid(Sender).Cells[c, r] := cCheck + else + TfpgStringGrid(Sender).Cells[c, r] := ''; + end; +end; + +procedure TProjectOptionsForm.grdUserMacrosDrawCell(Sender: TObject; + const ARow, ACol: Integer; const ARect: TfpgRect; + const AFlags: TfpgGridDrawState; var ADefaultDrawing: boolean); +var + img: TfpgImage; +begin + if (gdSelected in AFlags) and (ACol = 6) then + begin + FFocusRect := ARect; + end; + + if ACol < 6 then + begin + if grdUserMacros.Cells[ACol, ARow] = cCheck then + begin + img := fpgImages.GetImage('stdimg.check'); + if (gdSelected in AFlags) and (gdFocused in AFlags) then + img.Invert; + grdUserMacros.Canvas.DrawImage(ARect.Left, ARect.Top, img); + if (gdSelected in AFlags) and (gdFocused in AFlags) then + img.Invert; // restore image to original state + ADefaultDrawing := False; + end; + end + else + grdUserMacros.Canvas.Setfont(grdUserMacros.Font); +end; + +procedure TProjectOptionsForm.grdMacroGroupCanSelectCell(Sender: TObject; + const ARow, ACol: Integer; var ACanSelect: boolean); +begin + ACanSelect := ACol > 0; +end; + +procedure TProjectOptionsForm.BeforeShowHint(Sender: TObject; var AHint: TfpgString); +var + s: TfpgString; + c: TfpgWidget; +begin + if Sender is TfpgWidget then + c := TfpgWidget(Sender) + else + Exit; // should never occur, but lets just be safe + + if (c.Name = 'FEdit') and ((c.Parent is TfpgDirectoryEdit) or (c.Parent is TfpgFileNameEdit)) then + begin + if c.Parent <> nil then + c := c.Parent + else + Exit; // lets just be safe again + end; + + // controls that may contain macros + if c is TfpgDirectoryEdit then + s := TfpgDirectoryEdit(c).Directory + else if c is TfpgFileNameEdit then + s := TfpgFileNameEdit(c).FileName + else if c is TfpgEdit then + s := TfpgEdit(c).Text + else if c is TfpgStringGrid then + begin + s := TfpgStringGrid(c).Cells[TfpgStringGrid(c).FocusCol, TfpgStringGrid(c).FocusRow]; + if s = cCheck then + s := ''; + end; + + AHint := s; + + if FInternalMacroList.StrHasMacros(s) then + begin + SaveToMacroList(FInternalMacroList); + AHint := FInternalMacroList.ExpandMacro(s); + end; +end; + +procedure TProjectOptionsForm.LoadSettings; +var + i, j: integer; +begin + edtMainFile.FileName := GProject.MainUnit; + edtTargetFile.FileName := GProject.TargetFile; + edtMakeDir.Directory := GProject.ProjectDir; + edtUnitOutputDir.Directory := GProject.UnitOutputDir; + cbDefaultMakeCol.FocusItem := GProject.DefaultMake; + grdCompilerMakeOptions.RowCount := GProject.MakeOptions.Count; + + for i := 0 to GProject.MakeOptions.Count-1 do + begin + grdCompilerMakeOptions.Cells[6, i] := GProject.MakeOptions[i]; + for j := 0 to 5 do // we know there is only 6 boolean columns + begin + if GProject.MakeOptionsGrid[j, i] then + grdCompilerMakeOptions.Cells[j, i] := cCheck; + end; + end; + + grdCompilerDirs.RowCount := GProject.UnitDirs.Count; + for i := 0 to GProject.UnitDirs.Count-1 do + begin + grdCompilerDirs.Cells[10, i] := GProject.UnitDirs[i]; + for j := 0 to 9 do // we know there is only 10 boolean columns + begin + if GProject.UnitDirsGrid[j, i] then + grdCompilerDirs.Cells[j, i] := cCheck; + end; + end; +end; + +procedure TProjectOptionsForm.SaveSettings; +var + i, j: integer; +begin + GProject.MainUnit := edtMainFile.FileName; + GProject.TargetFile := edtTargetFile.FileName; + GProject.ProjectDir := edtMakeDir.Directory; + GProject.DefaultMake := cbDefaultMakeCol.FocusItem; + GProject.UnitOutputDir := edtUnitOutputDir.Directory; + + CleanupCompilerMakeOptionsGrid; + GProject.ClearAndInitMakeOptions(grdCompilerMakeOptions.RowCount); + for i := 0 to grdCompilerMakeOptions.RowCount-1 do + begin + if grdCompilerMakeOptions.Cells[6, i] = '' then + Continue; + GProject.MakeOptions.Add(grdCompilerMakeOptions.Cells[6, i]); + for j := 0 to 5 do // we know there is only 6 boolean columns + begin + if grdCompilerMakeOptions.Cells[j, i] = cCheck then + GProject.MakeOptionsGrid[j, i] := True; + end; + end; + + CleanupCompilerDirs; + GProject.ClearAndInitUnitDirsGrid(grdCompilerDirs.RowCount); + for i := 0 to grdCompilerDirs.RowCount-1 do + begin + GProject.UnitDirs.Add(grdCompilerDirs.Cells[10, i]); + for j := 0 to 9 do // we know there is only 10 boolean columns + begin + if grdCompilerDirs.Cells[j, i] = cCheck then + GProject.UnitDirsGrid[j, i] := True; + end; + end; +end; + +procedure TProjectOptionsForm.SetupCellEdit(AGrid: TfpgStringGrid); +var + pt: TPoint; +begin + if Assigned(FCellEdit) then + FCellEdit.Free; + + FLastGrid := AGrid; + FCellEdit := TfpgEdit.Create(FLastGrid.Parent); + pt.X := FLastGrid.Left + FFocusRect.Left; + pt.Y := FLastGrid.Top + FFocusRect.Top; + with FCellEdit do + begin + Name := 'FCellEdit'; + SetPosition(pt.X, pt.Y, FFocusRect.Width, FFocusRect.Height); + BorderStyle := ebsSingle; + FontDesc := '#Grid'; + Text := AGrid.Cells[AGrid.FocusCol, AGrid.FocusRow]; + OnKeyPress := @CellEditKeypressed; + OnExit := @CellEditExit; + SetFocus; + end; +end; + +// Remove all rows that have empty grid options (text) +procedure TProjectOptionsForm.CleanupCompilerMakeOptionsGrid; +var + i: integer; +begin + for i := grdCompilerMakeOptions.RowCount-1 downto 0 do + begin + if Trim(grdCompilerMakeOptions.Cells[6, i]) = '' then + grdCompilerMakeOptions.DeleteRow(i); + end; +end; + +// Remove all rows that have empty grid options (text) +procedure TProjectOptionsForm.CleanupCompilerDirs; +var + i: integer; +begin + for i := grdCompilerDirs.RowCount-1 downto 0 do + begin + if Trim(grdCompilerDirs.Cells[10, i]) = '' then + grdCompilerDirs.DeleteRow(i); + end; +end; + +procedure TProjectOptionsForm.SaveToMacroList(AList: TIDEMacroList); +begin +// AList.SetValue(cMacro_FPCSrcDir, edtFPCSrcDir.Directory); +end; + +constructor TProjectOptionsForm.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + FInternalMacroList := TIDEMacroList.Create; +end; + +destructor TProjectOptionsForm.Destroy; +begin + FInternalMacroList.Free; + inherited Destroy; +end; + +procedure TProjectOptionsForm.AfterCreate; +begin + {%region 'Auto-generated GUI code' -fold} + {@VFD_BODY_BEGIN: ProjectOptionsForm} + Name := 'ProjectOptionsForm'; + SetPosition(317, 177, 609, 570); + WindowTitle := 'Project Options'; + Hint := ''; + ShowHint := True; + + btnCancel := TfpgButton.Create(self); + with btnCancel do + begin + Name := 'btnCancel'; + SetPosition(514, 540, 88, 24); + Anchors := [anRight,anBottom]; + Text := 'Cancel'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + ModalResult := mrCancel; + TabOrder := 1; + end; + + btnOK := TfpgButton.Create(self); + with btnOK do + begin + Name := 'btnOK'; + SetPosition(422, 540, 88, 24); + Anchors := [anRight,anBottom]; + Text := 'OK'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + ModalResult := mrOK; + TabOrder := 2; + end; + + pcOptions := TfpgPageControl.Create(self); + with pcOptions do + begin + Name := 'pcOptions'; + SetPosition(4, 4, 600, 524); + Anchors := [anLeft,anRight,anTop,anBottom]; + ActivePageIndex := 0; + Hint := ''; + TabOrder := 3; + end; + + tsCompiler := TfpgTabSheet.Create(pcOptions); + with tsCompiler do + begin + Name := 'tsCompiler'; + SetPosition(3, 24, 594, 497); + Text := 'Compiler'; + end; + + tsDebugger := TfpgTabSheet.Create(pcOptions); + with tsDebugger do + begin + Name := 'tsDebugger'; + SetPosition(3, 24, 594, 497); + Text := 'Debugger'; + end; + + tsMacros := TfpgTabSheet.Create(pcOptions); + with tsMacros do + begin + Name := 'tsMacros'; + SetPosition(3, 24, 594, 497); + Text := 'Macros'; + end; + + tsOther := TfpgTabSheet.Create(pcOptions); + with tsOther do + begin + Name := 'tsOther'; + SetPosition(3, 24, 456, 217); + Text := 'Other'; + end; + + Label1 := TfpgLabel.Create(tsCompiler); + with Label1 do + begin + Name := 'Label1'; + SetPosition(4, 10, 284, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Main file'; + end; + + edtMainFile := TfpgFileNameEdit.Create(tsCompiler); + with edtMainFile do + begin + Name := 'edtMainFile'; + SetPosition(4, 28, 288, 24); + ExtraHint := ''; + FileName := '${PROJECTNAME}.pas'; + InitialDir := ''; + Filter := ''; + TabOrder := 0; + Hint := ' '; + OnShowHint := @BeforeShowHint; + end; + + Label2 := TfpgLabel.Create(tsCompiler); + with Label2 do + begin + Name := 'Label2'; + SetPosition(300, 10, 288, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Target file (-o)'; + end; + + edtTargetFile := TfpgFileNameEdit.Create(tsCompiler); + with edtTargetFile do + begin + Name := 'edtTargetFile'; + SetPosition(300, 28, 288, 24); + ExtraHint := ''; + FileName := '${PROJECTNAME}${EXEEXT}'; + InitialDir := ''; + Filter := ''; + TabOrder := 1; + Hint := ' '; + OnShowHint := @BeforeShowHint; + end; + + edtMakeCommand := TfpgFileNameEdit.Create(tsCompiler); + with edtMakeCommand do + begin + Name := 'edtMakeCommand'; + SetPosition(4, 76, 288, 24); + ExtraHint := ''; + FileName := '${COMPILER}'; + InitialDir := ''; + Filter := ''; + TabOrder := 2; + Hint := ' '; + OnShowHint := @BeforeShowHint; + end; + + Label3 := TfpgLabel.Create(tsCompiler); + with Label3 do + begin + Name := 'Label3'; + SetPosition(4, 58, 284, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Make command'; + end; + + edtMakeDir := TfpgDirectoryEdit.Create(tsCompiler); + with edtMakeDir do + begin + Name := 'edtMakeDir'; + SetPosition(300, 76, 288, 24); + ExtraHint := ''; + Directory := ''; + RootDirectory := ''; + TabOrder := 3; + Hint := ' '; + OnShowHint := @BeforeShowHint; + end; + + Label4 := TfpgLabel.Create(tsCompiler); + with Label4 do + begin + Name := 'Label4'; + SetPosition(300, 58, 284, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Make directory'; + end; + + Label5 := TfpgLabel.Create(tsCompiler); + with Label5 do + begin + Name := 'Label5'; + SetPosition(4, 106, 284, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Message output file'; + Enabled := False; + end; + + FilenameEdit4 := TfpgFileNameEdit.Create(tsCompiler); + with FilenameEdit4 do + begin + Name := 'FilenameEdit4'; + SetPosition(4, 124, 288, 24); + ExtraHint := ''; + FileName := ''; + InitialDir := ''; + Filter := ''; + TabOrder := 4; + Enabled := False; + end; + + CheckBox1 := TfpgCheckBox.Create(tsCompiler); + with CheckBox1 do + begin + Name := 'CheckBox1'; + SetPosition(300, 128, 280, 20); + FontDesc := '#Label1'; + Hint := ''; + TabOrder := 5; + Text := 'Copy messages to file'; + end; + + Label6 := TfpgLabel.Create(tsCompiler); + with Label6 do + begin + Name := 'Label6'; + SetPosition(4, 154, 144, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Default make column on Run'; + end; + + cbDefaultMakeCol := TfpgComboBox.Create(tsCompiler); + with cbDefaultMakeCol do + begin + Name := 'cbDefaultMakeCol'; + SetPosition(4, 172, 132, 24); + FontDesc := '#List'; + Hint := ''; + Items.Add('M (Make)'); + Items.Add('B (Build)'); + Items.Add('1 (Make 1)'); + Items.Add('2 (Make 2)'); + Items.Add('3 (Make 3)'); + Items.Add('4 (Make 4)'); + TabOrder := 6; + FocusItem := 0; + end; + + Button1 := TfpgButton.Create(tsCompiler); + with Button1 do + begin + Name := 'Button1'; + SetPosition(148, 290, 144, 24); + Anchors := [anLeft,anBottom]; + Text := 'Show command line'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 14; + end; + + pcCompiler := TfpgPageControl.Create(tsCompiler); + with pcCompiler do + begin + Name := 'pcCompiler'; + SetPosition(4, 209, 584, 282); + Anchors := [anLeft,anRight,anTop,anBottom]; + ActivePageIndex := 0; + Hint := ''; + TabOrder := 9; + end; + + TabSheet1 := TfpgTabSheet.Create(pcCompiler); + with TabSheet1 do + begin + Name := 'TabSheet1'; + SetPosition(3, 24, 578, 255); + Text := 'Make options'; + end; + + TabSheet2 := TfpgTabSheet.Create(pcCompiler); + with TabSheet2 do + begin + Name := 'TabSheet2'; + SetPosition(3, 24, 578, 255); + Text := 'Directories'; + end; + + grdCompilerMakeOptions := TfpgStringGrid.Create(TabSheet1); + with grdCompilerMakeOptions do + begin + Name := 'grdCompilerMakeOptions'; + SetPosition(2, 2, 574, 251); + Anchors := [anLeft,anRight,anTop,anBottom]; + AddColumn('M', 20, taCenter); + AddColumn('B', 20, taCenter); + AddColumn('1', 20, taCenter); + AddColumn('2', 20, taCenter); + AddColumn('3', 20, taCenter); + AddColumn('4', 20, taCenter); + AddColumn('Command line options', 430, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ' '; + RowCount := 0; + RowSelect := False; + TabOrder := 1; + OnClick := @grdCompilerMakeOptionsClicked; + OnDrawCell := @grdCompilerMakeOptionsDrawCell; + OnKeyPress := @grdCompilerMakeOptionsKeyPressed; + OnShowHint := @BeforeShowHint; + end; + + grdCompilerDirs := TfpgStringGrid.Create(TabSheet2); + with grdCompilerDirs do + begin + Name := 'grdCompilerDirs'; + SetPosition(2, 22, 574, 231); + Anchors := [anLeft,anRight,anTop,anBottom]; + AddColumn('M', 20, taCenter); + AddColumn('B', 20, taCenter); + AddColumn('1', 20, taCenter); + AddColumn('2', 20, taCenter); + AddColumn('3', 20, taCenter); + AddColumn('4', 20, taCenter); + AddColumn('U', 20, taCenter); + AddColumn('I', 20, taCenter); + AddColumn('L', 20, taCenter); + AddColumn('O', 20, taCenter); + AddColumn('Directories', 350, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ' '; + ParentShowHint := False; + RowCount := 0; + RowSelect := False; + ShowHint := True; + TabOrder := 1; + OnClick := @grdCompilerDirsClicked; + OnDrawCell := @grdCompilerDirsDrawCell; + OnKeyPress := @grdCompilerDirsKeyPressed; + OnShowHint := @BeforeShowHint; + end; + + Label11 := TfpgLabel.Create(TabSheet2); + with Label11 do + begin + Name := 'Label11'; + SetPosition(4, 4, 560, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Unit (-Fu), Include (-Fi), Library (-Fl) and Object (-Fo) directories'; + end; + + Label7 := TfpgLabel.Create(tsDebugger); + with Label7 do + begin + Name := 'Label7'; + SetPosition(4, 10, 296, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Debug command'; + end; + + FilenameEdit5 := TfpgFileNameEdit.Create(tsDebugger); + with FilenameEdit5 do + begin + Name := 'FilenameEdit5'; + SetPosition(4, 28, 584, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + FileName := ''; + InitialDir := ''; + Filter := ''; + TabOrder := 2; + end; + + Label8 := TfpgLabel.Create(tsDebugger); + with Label8 do + begin + Name := 'Label8'; + SetPosition(4, 58, 420, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Debug options'; + end; + + Edit1 := TfpgEdit.Create(tsDebugger); + with Edit1 do + begin + Name := 'Edit1'; + SetPosition(4, 76, 584, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + Hint := ''; + TabOrder := 4; + Text := ''; + FontDesc := '#Edit1'; + end; + + CheckBox2 := TfpgCheckBox.Create(tsDebugger); + with CheckBox2 do + begin + Name := 'CheckBox2'; + SetPosition(4, 124, 152, 20); + FontDesc := '#Label1'; + Hint := ''; + TabOrder := 5; + Text := 'Activate on Break'; + end; + + CheckBox3 := TfpgCheckBox.Create(tsDebugger); + with CheckBox3 do + begin + Name := 'CheckBox3'; + SetPosition(160, 124, 152, 20); + FontDesc := '#Label1'; + Hint := ''; + TabOrder := 6; + Text := 'Stop on Exception'; + end; + + CheckBox4 := TfpgCheckBox.Create(tsDebugger); + with CheckBox4 do + begin + Name := 'CheckBox4'; + SetPosition(320, 124, 160, 20); + FontDesc := '#Label1'; + Hint := ''; + TabOrder := 7; + Text := 'Show Console on Run'; + end; + + PageControl1 := TfpgPageControl.Create(tsDebugger); + with PageControl1 do + begin + Name := 'PageControl1'; + SetPosition(4, 153, 584, 338); + ActivePageIndex := 0; + Hint := ''; + TabOrder := 8; + end; + + TabSheet3 := TfpgTabSheet.Create(PageControl1); + with TabSheet3 do + begin + Name := 'TabSheet3'; + SetPosition(3, 24, 578, 311); + Text := 'Source directories'; + end; + + TabSheet4 := TfpgTabSheet.Create(PageControl1); + with TabSheet4 do + begin + Name := 'TabSheet4'; + SetPosition(3, 24, 578, 311); + Text := 'Defines'; + end; + + TabSheet5 := TfpgTabSheet.Create(PageControl1); + with TabSheet5 do + begin + Name := 'TabSheet5'; + SetPosition(3, 24, 578, 311); + Text := 'Signals'; + end; + + TabSheet6 := TfpgTabSheet.Create(PageControl1); + with TabSheet6 do + begin + Name := 'TabSheet6'; + SetPosition(3, 24, 578, 311); + Text := 'Exceptions'; + end; + + TabSheet7 := TfpgTabSheet.Create(PageControl1); + with TabSheet7 do + begin + Name := 'TabSheet7'; + SetPosition(3, 24, 578, 311); + Text := 'Target'; + end; + + grdDebugSrcDirs := TfpgStringGrid.Create(TabSheet3); + with grdDebugSrcDirs do + begin + Name := 'grdDebugSrcDirs'; + SetPosition(2, 2, 574, 307); + AddColumn('New', 550, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 5; + RowSelect := False; + ShowHeader := False; + TabOrder := 1; + end; + + btnShowCmdLine := TfpgButton.Create(tsCompiler); + with btnShowCmdLine do + begin + Name := 'btnShowCmdLine'; + SetPosition(160, 172, 132, 24); + Text := 'Show command line'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 7; + OnClick :=@btnShowCmdLineClicked; + end; + + edtUnitOutputDir := TfpgDirectoryEdit.Create(tsCompiler); + with edtUnitOutputDir do + begin + Name := 'edtUnitOutputDir'; + SetPosition(300, 172, 288, 24); + ExtraHint := ''; + Directory := ''; + RootDirectory := ''; + TabOrder := 8; + Hint := ' '; + OnShowHint := @BeforeShowHint; + end; + + Label9 := TfpgLabel.Create(tsCompiler); + with Label9 do + begin + Name := 'Label9'; + SetPosition(300, 154, 280, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Unit output directory (-FU)'; + end; + + Label10 := TfpgLabel.Create(tsMacros); + with Label10 do + begin + Name := 'Label10'; + SetPosition(4, 10, 580, 16); + Anchors := [anLeft,anRight,anTop]; + FontDesc := '#Label1'; + Hint := ''; + Text := 'Active Group'; + end; + + grdMacroGroup := TfpgStringGrid.Create(tsMacros); + with grdMacroGroup do + begin + Name := 'grdMacroGroup'; + SetPosition(4, 28, 584, 104); + Anchors := [anLeft,anRight,anTop]; + AddColumn('#', 20, taCenter); + AddColumn('', 20, taCenter); + AddColumn('Name', 520, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 6; + RowSelect := False; + ShowHeader := False; + TabOrder := 0; + Columns[0].BackgroundColor := clLightGray; + Cells[0,0] := '1'; + Cells[0,1] := '2'; + Cells[0,2] := '3'; + Cells[0,3] := '4'; + Cells[0,4] := '5'; + Cells[0,5] := '6'; + FocusCol := 2; + OnCanSelectCell := @grdMacroGroupCanSelectCell; + end; + + Label12 := TfpgLabel.Create(tsMacros); + with Label12 do + begin + Name := 'Label12'; + SetPosition(4, 144, 572, 16); + Anchors := [anLeft,anRight,anTop]; + FontDesc := '#Label1'; + Hint := ''; + Text := 'User defined macros'; + end; + + grdUserMacros := TfpgStringGrid.Create(tsMacros); + with grdUserMacros do + begin + Name := 'grdUserMacros'; + SetPosition(4, 162, 584, 328); + Anchors := [anLeft,anRight,anTop,anBottom]; + AddColumn('1', 20, taCenter); + AddColumn('2', 20, taCenter); + AddColumn('3', 20, taCenter); + AddColumn('4', 20, taCenter); + AddColumn('5', 20, taCenter); + AddColumn('6', 20, taCenter); + AddColumn('Name', 150, taLeftJustify); + AddColumn('Value', 290, taLeftJustify); + FontDesc := '#Grid'; + HeaderFontDesc := '#GridHeader'; + Hint := ''; + RowCount := 0; + RowSelect := False; + TabOrder := 1; + OnClick := @grdUserMacrosClicked; + OnDrawCell := @grdUserMacrosDrawCell; + end; + + {@VFD_BODY_END: ProjectOptionsForm} + {%endregion} +end; + + +end. diff --git a/examples/apps/ide/src/ideconst.pas b/examples/apps/ide/src/ideconst.pas new file mode 100644 index 00000000..3570bcbf --- /dev/null +++ b/examples/apps/ide/src/ideconst.pas @@ -0,0 +1,83 @@ +unit ideconst; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils; + +const + cProjectExt = '.project'; + + // Project Options + cProjectOptions = 'ProjectOptions'; + cOpenedUnits = 'OpenedUnits'; + cUnits = 'Units'; + + // INI Environment + cEnvironment = 'Environment'; + cEditor = 'Editor'; + cShortcuts = 'Shortcuts'; + cINIMakeOption = 'MakeOption'; + cINIMakeOptionGrid = 'MakeOptionEnabled'; + cINIUnitDir = 'UnitDir'; + cINIUnitDirGrid = 'UnitDirEnabled'; + + + // Predefined Macros + cMacro_FPCSrcDir = '${FPCSRCDIR}'; + cMacro_FPGuiDir = '${FPGUIDIR}'; + cMacro_FPGuiLibDir = '${FPGUILIBDIR}'; + cMacro_SyntaxDefDir = '${SYNTAXDEFDIR}'; + cMacro_TemplateDir = '${TEMPLATEDIR}'; + cMacro_Compiler = '${COMPILER}'; + cMacro_Debugger = '${DEBUGGER}'; + cMacro_ExeExt = '${EXEEXT}'; + cMacro_Target = '${TARGET}'; + cMacro_ProjectDir = '${PROJDIR}'; + + + OSTarget: String = {$I %FPCTARGETOS%}; + CPUTarget: String = {$I %FPCTARGETCPU%}; + FPCVersion: String = {$I %FPCVERSION%}; + FPCDate: String = {$I %FPCDATE%}; + + + // Unicode character used as grid check mark + cMultiplicationX = #$E2#$9C#$95; + cNormCheck = #$E2#$9C#$93; + cHeavyCheck = #$E2#$9C#$94; + cHeavyX = #$E2#$9C#$96; + cMedCircle = #$E2#$9A#$AB; + cCheck = cHeavyCheck; +{ + U+2715 MULTIPLICATION X + UTF-8: 0xE2 0x9C 0x95 + UTF-16: 0x2715 + + U+2713 CHECK MARK + UTF-8: 0xE2 0x9C 0x93 + UTF-16: 0x2713 + + U+2714 HEAVY CHECK MARK + UTF-8: 0xE2 0x9C 0x94 + UTF-16: 0x2714 + + U+2716 HEAVY MULTIPLICATION X + UTF-8: 0xE2 0x9C 0x96 + UTF-16: 0x2716 + + U+26AB MEDIUM BLACK CIRCLE + UTF-8: 0xE2 0x9A 0xAB + UTF-16: 0x26AB +} + + +implementation + +initialization + OSTarget := Lowercase(OSTarget); + +end. + diff --git a/examples/apps/ide/src/ideimages.pas b/examples/apps/ide/src/ideimages.pas new file mode 100644 index 00000000..6963e6e9 --- /dev/null +++ b/examples/apps/ide/src/ideimages.pas @@ -0,0 +1,64 @@ +unit ideimages; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils; + +procedure RegisterIDEImages; + + +implementation + +uses + fpg_main; + +const + ide_gutter_vertical : Array[0..437] of byte = ( + 66, 77,182, 1, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, + 0, 15, 0, 0, 0, 8, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 128, 1, 0, 0,196, 14, 0, 0,196, 14, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,187,160,131,243,220,191,247,227,200,243,221,194,239,215, + 187,235,210,183,231,206,179,226,198,167,224,196,162,226,197,166,224, + 199,168,224,199,168,226,204,175,203,175,144,255,255,255, 0, 0, 0, + 187,160,131,243,220,191,247,227,200,243,221,194,239,215,187,235,210, + 183,231,206,179,226,198,167,224,196,162,226,197,166,224,199,168,224, + 199,168,226,204,175,203,175,144,255,255,255, 0, 0, 0,187,160,131, + 243,220,191,247,227,200,243,221,194,239,215,187,235,210,183,231,206, + 179,226,198,167,224,196,162,226,197,166,224,199,168,224,199,168,226, + 204,175,203,175,144,255,255,255, 0, 0, 0,187,160,131,243,220,191, + 247,227,200,243,221,194,239,215,187,235,210,183,231,206,179,226,198, + 167,224,196,162,226,197,166,224,199,168,224,199,168,226,204,175,203, + 175,144,255,255,255, 0, 0, 0,187,160,131,243,220,191,247,227,200, + 243,221,194,239,215,187,235,210,183,231,206,179,226,198,167,224,196, + 162,226,197,166,224,199,168,224,199,168,226,204,175,203,175,144,255, + 255,255, 0, 0, 0,187,160,131,243,220,191,247,227,200,243,221,194, + 239,215,187,235,210,183,231,206,179,226,198,167,224,196,162,226,197, + 166,224,199,168,224,199,168,226,204,175,203,175,144,255,255,255, 0, + 0, 0,187,160,131,243,220,191,247,227,200,243,221,194,239,215,187, + 235,210,183,231,206,179,226,198,167,224,196,162,226,197,166,224,199, + 168,224,199,168,226,204,175,203,175,144,255,255,255, 0, 0, 0,187, + 160,131,243,220,191,247,227,200,243,221,194,239,215,187,235,210,183, + 231,206,179,226,198,167,224,196,162,226,197,166,224,199,168,224,199, + 168,226,204,175,203,175,144,255,255,255, 0, 0, 0); + + +procedure RegisterIDEImages; +begin + // system images. Change these to the composite arrow bmp that includes + // disabled state + //fpgImages.AddBMP( + // 'sys.sb.up', + // @stdimg_arrow_up, + // sizeof(stdimg_arrow_up)); + + fpgImages.AddMaskedBMP( // 60x12 in total. 5 images of 12x12 each. + 'ide.guttervertical', + @ide_gutter_vertical, + sizeof(ide_gutter_vertical), 0,0); +end; + +end. + diff --git a/examples/apps/ide/src/idemacros.pas b/examples/apps/ide/src/idemacros.pas new file mode 100644 index 00000000..deee6507 --- /dev/null +++ b/examples/apps/ide/src/idemacros.pas @@ -0,0 +1,318 @@ +unit idemacros; + +{$mode objfpc}{$H+} + +interface + +uses + Classes + ,SysUtils + ,fpg_base + ,fpg_main + ; + +type + TIDEMacro = class(TObject) + public + Name: string; + Value: string; + Description: string; + constructor Create(AName, AValue, ADescription: TfpgString); + end; + + + TIDEMacroList = class(TObject) + private + FItems: TList; + function GetItems(AIndex: integer): TIDEMacro; + procedure SetItems(AIndex: integer; const AValue: TIDEMacro); + procedure AddDefaults; + procedure LoadSavedValues; + public + constructor Create; + destructor Destroy; override; + function Count: integer; + function FindByName(const MacroName: TfpgString): TIDEMacro; + function StrHasMacros(const s: TfpgString): boolean; + function ExpandMacro(const s: TfpgString): TfpgString; + procedure Add(NewMacro: TIDEMacro); + procedure Clear; + procedure Delete(AIndex: integer); + procedure SetValue(const MacroName, NewValue: TfpgString); + procedure ResetToDefaults; + property Items[AIndex: integer]: TIDEMacro read GetItems write SetItems; default; + end; + + +// lazy-man singleton of IDE Macros +function GMacroList: TIDEMacroList; + + +implementation + +uses + ideconst + ,fpg_iniutils; + +var + uIDEMacroList: TIDEMacroList; + + +function GMacroList: TIDEMacroList; +begin + if not Assigned(uIDEMacroList) then + uIDEMacroList := TIDEMacroList.Create; + Result := uIDEMacroList; +end; + + +{ TIDEMacro } + +constructor TIDEMacro.Create(AName, AValue, ADescription: TfpgString); +begin + Name := AName; + Value := AValue; + Description := ADescription; +end; + + +{ TIDEMacroList } + +function TIDEMacroList.GetItems(AIndex: integer): TIDEMacro; +begin + Result := TIDEMacro(FItems[AIndex]); +end; + +procedure TIDEMacroList.SetItems(AIndex: integer; const AValue: TIDEMacro); +begin + FItems[AIndex] := AValue; +end; + +procedure TIDEMacroList.AddDefaults; +var + o: TIDEMacro; +begin + o := TIDEMacro.Create(cMacro_FPCSrcDir, '', 'FPC source directory'); + Add(o); + o := TIDEMacro.Create(cMacro_FPGuiDir, '', 'fpGUI root directory'); + Add(o); + o := TIDEMacro.Create(cMacro_FPGuiLibDir, cMacro_FPGuiDir+'lib/'+cMacro_Target+'/', 'fpGUI compiled library directory'); + Add(o); + o := TIDEMacro.Create(cMacro_SyntaxDefDir, cMacro_FPGuiDir+'examples/apps/fpgide/syntaxdefs/', 'Editor syntax highlighter definitions'); + Add(o); + o := TIDEMacro.Create(cMacro_TemplateDir, cMacro_FPGuiDir+'examples/apps/fpgide/templates/', 'Project template directory'); + Add(o); + o := TIDEMacro.Create(cMacro_Compiler, '', 'FPC Compiler to use'); + Add(o); + o := TIDEMacro.Create(cMacro_Debugger, 'gdb', 'Location of GDB debugger'); + Add(o); + o := TIDEMacro.Create(cMacro_ExeExt, {$IFDEF MSWINDOWS} '.exe' {$ENDIF} {$IFDEF UNIX} '' {$ENDIF}, 'Default executable extension'); + Add(o); + o := TIDEMacro.Create(cMacro_Target, CPUTarget+'-'+OSTarget, 'Default target'); + Add(o); +end; + +procedure TIDEMacroList.LoadSavedValues; +var + s: TfpgString; +begin + // we don't unnecessarily override the defaults setup in AddDefaults() + SetValue(cMacro_FPCSrcDir, gINI.ReadString(cEnvironment, 'FPCSrcDir', '')); + SetValue(cMacro_FPGuiDir, gINI.ReadString(cEnvironment, 'FPGuiDir', '')); + s := gINI.ReadString(cEnvironment, 'FPGuiLibDir', ''); + if s <> '' then + SetValue(cMacro_FPGuiLibDir, s); + s := gINI.ReadString(cEnvironment, 'SyntaxDefDir', ''); + if s <> '' then + SetValue(cMacro_SyntaxDefDir, s); + s := gINI.ReadString(cEnvironment, 'TemplateDir', ''); + if s <> '' then + SetValue(cMacro_TemplateDir, s); + SetValue(cMacro_Compiler, gINI.ReadString(cEnvironment, 'Compiler', '')); + s := gINI.ReadString(cEnvironment, 'Debugger', ''); + if s <> '' then + SetValue(cMacro_Debugger, s); + s := gINI.ReadString(cEnvironment, 'ExeExt', ''); + if s <> '' then + SetValue(cMacro_ExeExt, s); + s := gINI.ReadString(cEnvironment, 'Target', ''); + if s <> '' then + SetValue(cMacro_Target, s); +end; + +constructor TIDEMacroList.Create; +begin + inherited Create; + FItems := TList.Create; + AddDefaults; + LoadSavedValues; +end; + +destructor TIDEMacroList.Destroy; +begin + Clear; + FItems.Free; + inherited Destroy; +end; + +function TIDEMacroList.Count: integer; +begin + Result := FItems.Count; +end; + +function TIDEMacroList.FindByName(const MacroName: TfpgString): TIDEMacro; +var + l: Integer; + r: Integer; + m: Integer; + cmp: Integer; +begin + l := 0; + r := FItems.Count-1; + m := 0; + while l <= r do + begin + m := (l+r) shr 1; + Result := Items[m]; + cmp := AnsiCompareText(MacroName,Result.Name); + if cmp < 0 then + r := m-1 + else if cmp > 0 then + l := m+1 + else + exit; + end; + Result := nil; +end; + +function TIDEMacroList.StrHasMacros(const s: TfpgString): boolean; +// search for ${ +var + p: Integer; + Len: Integer; +begin + Result := false; + p := 1; + Len := length(s); + while (p < Len) do + begin + if s[p] = '$' then + begin + inc(p); + if (p<Len) and (s[p]<>'$') then + begin + // skip macro function name + while (p<Len) and (s[p]<>'{') do inc(p); + if (p<Len) then + begin + Result:=true; + exit; + end; + end + else + begin + // $$ is not a macro + inc(p); + end; + end else + inc(p); + end; +end; + +function TIDEMacroList.ExpandMacro(const s: TfpgString): TfpgString; +var + sub: TfpgString; + pstart: integer; + pend: integer; + len: integer; + m: TIDEMacro; + r: TfpgString; +begin + r := s; + pstart := Pos('${', r); + while (pstart > 0) do + begin + len := Length(r); + pend := pstart + 2; + while pend < len do + begin + if r[pend] = '}' then + break + else + inc(pend); + end; + sub := Copy(r, pstart, (pend-pstart)+1); + m := FindByName(sub); + if not Assigned(m) then + raise Exception.CreateFmt('The macro <%s> is not defined.', [sub]); + r := StringReplace(r, sub, m.Value, [rfReplaceAll, rfIgnoreCase]); + pstart := Pos('${', r); + end; + Result := r; +end; + +procedure TIDEMacroList.Add(NewMacro: TIDEMacro); +var + l: Integer; + r: Integer; + m: Integer; + cmp: Integer; +begin + l := 0; + r := FItems.Count-1; + m := 0; + while l <= r do + begin + m := (l+r) shr 1; + cmp := AnsiCompareText(NewMacro.Name, Items[m].Name); + if cmp < 0 then + r := m-1 + else if cmp > 0 then + l := m + 1 + else + break; + end; + if (m < FItems.Count) and (AnsiCompareText(NewMacro.Name, Items[m].Name) > 0) then + inc(m); + FItems.Insert(m, NewMacro); +end; + +procedure TIDEMacroList.Clear; +var + i: integer; +begin + for i := 0 to FItems.Count-1 do Items[i].Free; + FItems.Clear; +end; + +procedure TIDEMacroList.Delete(AIndex: integer); +begin + Items[AIndex].Free; + FItems.Delete(AIndex); +end; + +procedure TIDEMacroList.SetValue(const MacroName, NewValue: TfpgString); +var + lMacro: TIDEMacro; +begin + lMacro := FindByName(MacroName); + if lMacro <> nil then + lMacro.Value := NewValue; +end; + +procedure TIDEMacroList.ResetToDefaults; +begin + Clear; + AddDefaults; + LoadSavedValues; +end; + + +initialization + uIDEMacroList := nil; + +finalization + uIDEMacroList.Free; + +end. + diff --git a/examples/apps/ide/src/ideutils.pas b/examples/apps/ide/src/ideutils.pas new file mode 100644 index 00000000..f0b54648 --- /dev/null +++ b/examples/apps/ide/src/ideutils.pas @@ -0,0 +1,395 @@ +unit ideutils; + +{$mode objfpc}{$H+} + +interface + +uses + Classes + ,SysUtils + ,fpg_base + ,fpg_widget + ; + + + +// Set the Screen.Cursor to crHourGlass until the interface goes out of scope +function TempHourGlassCursor(var AWidget: TfpgWidget): IInterface; + + +function tiNumToken(const AValue, AToken: string): integer; +function tiToken(const AValue, AToken: string; const APos: integer): string; +procedure ShowString(const AString: TfpgString; const AHeading: TfpgString); + +// Extract the file extension from FileName and return it in all UPPERCASE. +function ExtractUpperFileExt(const FileName: string): string; + +// Transforms all consecutive sequences of #10, #13, #32, and #9 in Str +// into a single space, and strips off whitespace at the beginning and +// end of the string +function CompressWhiteSpace(const Str: string): string; + +// See if a string begins/ends with a specific substring +function StrBeginsWith(const SubStr, Str: string; CaseSensitive: Boolean = True): Boolean; +function StrEndsWith(const SubStr, Str: string; CaseSensitive: Boolean = True): Boolean; +// See is a string contains another substring +function StrContains(const SubStr, Str: string; CaseSensitive: Boolean = True): Boolean; + +// Find SubString in S; do not consider case; +// this works exactly the same as the Pos function, +// except for case-INsensitivity. +function CaseInsensitivePos(Pat, Text: PChar): Integer; overload; +function CaseInsensitivePos(const Pat, Text: string): Integer; overload; +function AnsiCaseInsensitivePos(const SubString, S: string): Integer; + +procedure MakeASCIICharTable; +procedure Initialize; + +function IsCharAlpha(ch: Char): Boolean; +function IsCharUpper(ch: Char): Boolean; +function IsCharLower(ch: Char): Boolean; +function IsCharAlphaNumeric(ch: Char): Boolean; + +// Emulates the VB $Right function to obtain up to n of the +// rightmost characters in a string. +function RightString(const Value: string; NumChars: Integer): string; + +function IsPas(const FileName: string): Boolean; +function IsInc(const FileName: string): Boolean; +function IsProgram(const FileName: string): Boolean; + + + + +implementation + +uses + fpg_form + ,fpg_memo + ,fpg_main + ,fpg_utils + ; + +var + LocaleIdentifierChars: set of Char; + ASCIICharTable: array [#0..#255] of Byte; + +const + EmptyString = ''; + GxIdentChars = ['A'..'Z', 'a'..'z', '0'..'9', '_']; + GxIdentStartChars = ['A'..'Z', 'a'..'z', '0'..'9']; + GxAlphaChars = ['A'..'Z', 'a'..'z']; + GxUpperAlphaChars = ['A'..'Z']; + GxLowerAlphaChars = ['a'..'z']; + GxSentenceEndChars = ['.', '!', '?']; + + SAllAlphaNumericChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'; + + +type + { once it goes out of scope, it restores the mouse cursor } + TTempHourClassCursor = class(TInterfacedObject, IInterface) + private + FOldCursor: TMouseCursor; + FWidget: TfpgWidget; + public + constructor Create(var AWidget: TfpgWidget); + destructor Destroy; override; + end; + +constructor TTempHourClassCursor.Create(var AWidget: TfpgWidget); +begin + inherited Create; + FOldCursor := AWidget.MouseCursor; + FWidget := AWidget; + AWidget.MouseCursor := mcHourGlass; +end; + +destructor TTempHourClassCursor.Destroy; +begin + FWidget.MouseCursor := FOldCursor; + inherited Destroy; +end; + + + +function TempHourGlassCursor(var AWidget: TfpgWidget): IInterface; +begin + Result := TTempHourClassCursor.Create(AWidget) as IInterface; +end; + +function tiNumToken(const AValue, AToken : string): integer; +var + i, iCount : integer; + lsValue : string; +begin + Result := 0; + if AValue = '' then + Exit; //==> + + iCount := 0; + lsValue := AValue; + i := pos(AToken, lsValue); + while i <> 0 do begin + delete(lsValue, i, length(AToken)); + inc(iCount); + i := pos(AToken, lsValue); + end; + Result := iCount + 1; +end; + + +function tiToken(const AValue, AToken : string; const APos : integer): string; +var + i, iCount, iNumToken : integer; + lsValue : string; +begin + result := ''; + + iNumToken := tiNumToken(AValue, AToken); + if APos = 1 then + begin + if pos(AToken, AValue) = 0 then + result := AValue + else + result := copy(AValue, 1, pos(AToken, AValue)-1); + end + else if (iNumToken < APos-1) or (APos<1) then + begin + result := ''; + end + else + begin + { Remove leading blocks } + iCount := 1; + lsValue := AValue; + i := pos(AToken, lsValue); + while (i<>0) and (iCount<APos) do + begin + delete(lsValue, 1, i + length(AToken) - 1); + inc(iCount); + i := pos(AToken, lsValue); + end; + + if (i=0) and (iCount=APos) then + result := lsValue + else if (i=0) and (iCount<>APos) then + result := '' + else + result := copy(lsValue, 1, i-1); + end; +end; + +procedure ShowString(const AString: TfpgString; const AHeading: TfpgString); +var + lForm: TfpgForm; + lMemo: TfpgMemo; +begin + lForm := TfpgForm.Create(nil); + lMemo := TfpgMemo.Create(lForm); + try + lForm.WindowTitle := AHeading; + lForm.Width := 450; + lForm.Height := 250; + lForm.WindowPosition := wpOneThirdDown; + lForm.Name := 'FormShowStrings'; + lMemo.Lines.Text := AString; + lMemo.FontDesc := '#Edit2'; + lMemo.SetPosition(0, 0, lForm.Width, lForm.Height); + lMemo.Align := alClient; + lForm.ShowModal; + finally + lForm.free; + end; +end; + +function ExtractUpperFileExt(const FileName: string): string; +begin + Result := UpperCase(fpgExtractFileExt(FileName)); +end; + +function CompressWhiteSpace(const Str: string): string; +var + i: Integer; + Len: Integer; + NextResultChar: Integer; + CheckChar: Char; + NextChar: Char; +begin + Len := Length(Str); + NextResultChar := 1; + SetLength(Result, Len); + + for i := 1 to Len do + begin + CheckChar := Str[i]; + {$RANGECHECKS OFF} + NextChar := Str[i + 1]; + {$RANGECHECKS ON} + case CheckChar of + #9, #10, #13, #32: + begin + if (NextChar in [#0, #9, #10, #13, #32]) or (NextResultChar = 1) then + Continue + else + begin + Result[NextResultChar] := #32; + Inc(NextResultChar); + end; + end; + else + begin + Result[NextResultChar] := Str[i]; + Inc(NextResultChar); + end; + end; + end; + if Len = 0 then + Exit; + SetLength(Result, NextResultChar - 1); +end; + +function StrBeginsWith(const SubStr, Str: string; CaseSensitive: Boolean): Boolean; +begin + if CaseSensitive then + Result := Pos(SubStr, Str) = 1 + else + Result := CaseInsensitivePos(SubStr, Str) = 1; +end; + +function StrEndsWith(const SubStr, Str: string; CaseSensitive: Boolean): Boolean; +begin + if CaseSensitive then + Result := RightString(Str, Length(SubStr)) = SubStr + else + Result := SameText(RightString(Str, Length(SubStr)), SubStr); +end; + +function StrContains(const SubStr, Str: string; CaseSensitive: Boolean): Boolean; +begin + if CaseSensitive then + Result := Pos(SubStr, Str) > 0 + else + Result := CaseInsensitivePos(SubStr, Str) > 0; +end; + +function CaseInsensitivePos(Pat, Text: PChar): Integer; +var + RunPat, RunText, PosPtr: PChar; +begin + Result := 0; + RunPat := Pat; + RunText := Text; + while RunText^ <> #0 do + begin + if (ASCIICharTable[RunPat^] = ASCIICharTable[RunText^]) then + begin + PosPtr := RunText; + while RunPat^ <> #0 do + begin + if ASCIICharTable[RunPat^] <> ASCIICharTable[RunText^] then + Break; + Inc(RunPat); + Inc(RunText); + end; + if RunPat^ = #0 then + begin + Result := PosPtr - Text + 1; + Break; + end; + end + else + Inc(RunText); + RunPat := Pat; + end; +end; + +function CaseInsensitivePos(const Pat, Text: string): Integer; overload; +begin + Result := CaseInsensitivePos(PChar(Pat), PChar(Text)); +end; + +function AnsiCaseInsensitivePos(const SubString, S: string): Integer; +begin + Result := AnsiPos(AnsiUpperCase(SubString), AnsiUpperCase(S)); +end; + +procedure MakeASCIICharTable; +var + i: Integer; +begin + for i := 0 to 255 do + begin + If (I > 64) and (I < 91) then + ASCIICharTable[Char(I)] := i + 32 + else + ASCIICharTable[Char(I)] := i; + end; +end; + +procedure Initialize; +var + i: Char; +begin + for i := Low(Char) to High(Char) do + if IsCharAlphaNumeric(i) then + Include(LocaleIdentifierChars, i); + Include(LocaleIdentifierChars, '_'); + MakeASCIICharTable; +end; + +function IsCharAlpha(ch: Char): Boolean; +begin + Result := (ch in GxAlphaChars); +end; + +function IsCharUpper(ch: Char): Boolean; +begin + Result := (ch in GxUpperAlphaChars); +end; + +function IsCharLower(ch: Char): Boolean; +begin + Result := (ch in GxLowerAlphaChars); +end; + +function IsCharAlphaNumeric(ch: Char): Boolean; +begin + Result := (ch in GxIdentStartChars); +end; + +function RightString(const Value: string; NumChars: Integer): string; +begin + Result := Copy(Value, (Length(Value) - NumChars) + 1, NumChars); +end; + +function IsPas(const FileName: string): Boolean; +var + FileExt: string; +begin + FileExt := ExtractUpperFileExt(FileName); + Result := (FileExt = '.PAS'); +end; + +function IsInc(const FileName: string): Boolean; +var + FileExt: string; +begin + FileExt := ExtractUpperFileExt(FileName); + Result := (FileExt = '.INC'); +end; + +function IsProgram(const FileName: string): Boolean; +var + FileExt: string; +begin + FileExt := ExtractUpperFileExt(FileName); + Result := (FileExt = '.LPR') or (FileExt = '.DPR'); +end; + + +initialization + Initialize; + +end. + diff --git a/examples/apps/ide/src/mPasLex.pas b/examples/apps/ide/src/mPasLex.pas new file mode 100644 index 00000000..a9f452c9 --- /dev/null +++ b/examples/apps/ide/src/mPasLex.pas @@ -0,0 +1,1442 @@ +{+--------------------------------------------------------------------------+ + | Class: TmwPasLex + | Created: 07.98 - 10.98 + | Author: Martin Waldenburg + | Description: A very fast Pascal tokenizer. + | Version: 1.32 + | Copyright (c) 1998, 1999 Martin Waldenburg + | All rights reserved. + | + | LICENCE CONDITIONS + | + | USE OF THE ENCLOSED SOFTWARE + | INDICATES YOUR ASSENT TO THE + | FOLLOWING LICENCE CONDITIONS. + | + | + | + | These Licence Conditions are exlusively + | governed by the Law and Rules of the + | Federal Republic of Germany. + | + | Redistribution and use in source and binary form, with or without + | modification, are permitted provided that the following conditions + | are met: + | + | 1. Redistributions of source code must retain the above copyright + | notice, this list of conditions and the following disclaimer. + | If the source is modified, the complete original and unmodified + | source code has to distributed with the modified version. + | + | 2. Redistributions in binary form must reproduce the above + | copyright notice, these licence conditions and the disclaimer + | found at the end of this licence agreement in the documentation + | and/or other materials provided with the distribution. + | + | 3. Software using this code must contain a visible line of credit. + | + | 4. If my code is used in a "for profit" product, you have to donate + | to a registered charity in an amount that you feel is fair. + | You may use it in as many of your products as you like. + | Proof of this donation must be provided to the author of + | this software. + | + | 5. If you for some reasons don't want to give public credit to the + | author, you have to donate three times the price of your software + | product, or any other product including this component in any way, + | but no more than $500 US and not less than $200 US, or the + | equivalent thereof in other currency, to a registered charity. + | You have to do this for every of your products, which uses this + | code separately. + | Proof of this donations must be provided to the author of + | this software. + | + | + | DISCLAIMER: + | + | THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS'. + | + | ALL EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + | PARTICULAR PURPOSE ARE DISCLAIMED. + | + | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + | + | Martin.Waldenburg@T-Online.de + +--------------------------------------------------------------------------+} + +unit mPasLex; + +{$mode delphi}{$H+} + +interface + +uses + SysUtils; + +var + Identifiers: array[#0..#255]of ByteBool; + mHashTable: array[#0..#255]of Integer; + +type + TTokenKind=(tkAbsolute, tkAbstract, tkAddressOp, tkAnd, tkAnsiComment, + tkArray, tkAs, tkAt, tkAsciiChar, tkAsm, tkAssembler, tkAssign, tkAutomated, + tkBegin, tkBadString, tkBorComment, tkCase, tkCdecl, tkClass, tkColon, + tkComma, tkCompDirect, tkConst, tkConstructor, tkCRLF, tkCRLFCo, tkDefault, + tkDestructor, tkDispid, tkDispinterface, tkDiv, tkDo, tkDoubleAddressOp, + tkDotDot, tkDownto, tkDynamic, tkElse, tkEnd, tkEqual, tkError, tkExcept, + tkExport, tkExports, tkExternal, tkFar, tkFile, tkFinalization, tkFinally, + tkFloat, tkFor, tkForward, tkFunction, tkGoto, tkGreater, tkGreaterEqual, + tkIdentifier, tkIf, tkImplementation, tkImplements, tkIn, tkIndex, + tkInherited, tkInitialization, tkInline, tkInteger, tkInterface, tkIs, + tkKeyString, tkLabel, tkLibrary, tkLower, tkLowerEqual, tkMessage, tkMinus, + tkMod, tkName, tkNear, tkNil, tkNodefault, tkNone, tkNot, tkNotEqual, tkNull, + tkNumber, tkObject, tkOf, tkOn, tkOr, tkOut, tkOverload, tkOverride, + tkPacked, tkPascal, tkPlus, tkPoint, tkPointerSymbol, tkPrivate, tkProcedure, + tkProgram, tkProperty, tkProtected, tkPublic, tkPublished, tkRaise, tkRead, + tkReadonly, tkRecord, tkRegister, tkReintroduce, tkRepeat, tkResident, + tkResourcestring, tkRoundClose, tkRoundOpen, tkSafecall, tkSemiColon, tkSet, + tkShl, tkShr, tkSlash, tkSlashesComment, tkSquareClose, tkSquareOpen, + tkSpace, tkStar, tkStdcall, tkStored, tkString, tkStringresource, tkSymbol, + tkThen, tkThreadvar, tkTo, tkTry, tkType, tkUnit, tkUnknown, tkUntil, tkUses, + tkVar, tkVirtual, tkWhile, tkWith, tkWrite, tkWriteonly, tkXor); + + TCommentState=(csAnsi, csBor, csNo); + + TmwPasLex=class(TObject) + private + fComment: TCommentState; + fOrigin: PChar; + fProcTable: array[#0..#255]of procedure of Object; + Run: Longint; + Temp: PChar; + FRoundCount: Integer; + FSquareCount: Integer; + fStringLen: Integer; + fToIdent: PChar; + fIdentFuncTable: array[0..191]of function: TTokenKind of Object; + fTokenPos: Integer; + fLineNumber: Integer; + FTokenID: TTokenKind; + fLastIdentPos: Integer; + fLastNoSpace: TTokenKind; + fLastNoSpacePos: Integer; + fLinePos: Integer; + fIsInterface: Boolean; + fIsClass: Boolean; + function KeyHash(ToHash: PChar): Integer; + function KeyComp(const aKey: string): Boolean; + function Func15: TTokenKind; + function Func19: TTokenKind; + function Func20: TTokenKind; + function Func21: TTokenKind; + function Func23: TTokenKind; + function Func25: TTokenKind; + function Func27: TTokenKind; + function Func28: TTokenKind; + function Func29: TTokenKind; + function Func32: TTokenKind; + function Func33: TTokenKind; + function Func35: TTokenKind; + function Func37: TTokenKind; + function Func38: TTokenKind; + function Func39: TTokenKind; + function Func40: TTokenKind; + function Func41: TTokenKind; + function Func44: TTokenKind; + function Func45: TTokenKind; + function Func47: TTokenKind; + function Func49: TTokenKind; + function Func52: TTokenKind; + function Func54: TTokenKind; + function Func55: TTokenKind; + function Func56: TTokenKind; + function Func57: TTokenKind; + function Func59: TTokenKind; + function Func60: TTokenKind; + function Func61: TTokenKind; + function Func63: TTokenKind; + function Func64: TTokenKind; + function Func65: TTokenKind; + function Func66: TTokenKind; + function Func69: TTokenKind; + function Func71: TTokenKind; + function Func73: TTokenKind; + function Func75: TTokenKind; + function Func76: TTokenKind; + function Func79: TTokenKind; + function Func81: TTokenKind; + function Func84: TTokenKind; + function Func85: TTokenKind; + function Func87: TTokenKind; + function Func88: TTokenKind; + function Func91: TTokenKind; + function Func92: TTokenKind; + function Func94: TTokenKind; + function Func95: TTokenKind; + function Func96: TTokenKind; + function Func97: TTokenKind; + function Func98: TTokenKind; + function Func99: TTokenKind; + function Func100: TTokenKind; + function Func101: TTokenKind; + function Func102: TTokenKind; + function Func103: TTokenKind; + function Func105: TTokenKind; + function Func106: TTokenKind; + function Func117: TTokenKind; + function Func126: TTokenKind; + function Func129: TTokenKind; + function Func132: TTokenKind; + function Func133: TTokenKind; + function Func136: TTokenKind; + function Func141: TTokenKind; + function Func143: TTokenKind; + function Func166: TTokenKind; + function Func168: TTokenKind; + function Func191: TTokenKind; + function AltFunc: TTokenKind; + procedure InitIdent; + function IdentKind(MayBe: PChar): TTokenKind; + procedure SetOrigin(NewValue: PChar); + procedure SetRunPos(Value: Integer); + procedure MakeMethodTables; + procedure AddressOpProc; + procedure AsciiCharProc; + procedure AnsiProc; + procedure BorProc; + procedure BraceCloseProc; + procedure BraceOpenProc; + procedure ColonProc; + procedure CommaProc; + procedure CRProc; + procedure EqualProc; + procedure GreaterProc; + procedure IdentProc; + procedure IntegerProc; + procedure LFProc; + procedure LowerProc; + procedure MinusProc; + procedure NullProc; + procedure NumberProc; + procedure PlusProc; + procedure PointerSymbolProc; + procedure PointProc; + procedure RoundCloseProc; + procedure RoundOpenProc; + procedure SemiColonProc; + procedure SlashProc; + procedure SpaceProc; + procedure SquareCloseProc; + procedure SquareOpenProc; + procedure StarProc; + procedure StringProc; + procedure SymbolProc; + procedure UnknownProc; + function GetToken: string; + function InSymbols(aChar: Char): Boolean; + protected + public + constructor Create; + destructor Destroy; override; + function CharAhead(Count: Integer): Char; + function NextChar: Char; + procedure Next; + procedure NextID(ID: TTokenKind); + procedure NextNoJunk; + procedure NextClass; + property IsClass: Boolean read fIsClass; + property IsInterface: Boolean read fIsInterface; + property LastIdentPos: Integer read fLastIdentPos; + property LastNoSpace: TTokenKind read fLastNoSpace; + property LastNoSpacePos: Integer read fLastNoSpacePos; + property LineNumber: Integer read fLineNumber; + property LinePos: Integer read fLinePos; + property Origin: PChar read fOrigin write SetOrigin; + property RunPos: Integer read Run write SetRunPos; + property TokenPos: Integer read fTokenPos; + property Token: string read GetToken; + property TokenID: TTokenKind read FTokenID; + published + end; + +implementation + +procedure MakeIdentTable; +var + I, J: Char; +begin + for I:=#0 to #255 do + begin + Case I of + '_', '0'..'9', 'a'..'z', 'A'..'Z': Identifiers[I]:=True; + else Identifiers[I]:=False; + end; + J:=UpperCase(I)[1]; + Case I of + 'a'..'z', 'A'..'Z', '_': mHashTable[I]:=Ord(J)-64; + else mHashTable[Char(I)]:=0; + end; + end; +end; + +procedure TmwPasLex.InitIdent; +var + I: Integer; +begin + for I:=0 to 191 do + Case I of + 15: fIdentFuncTable[I]:=Func15; + 19: fIdentFuncTable[I]:=Func19; + 20: fIdentFuncTable[I]:=Func20; + 21: fIdentFuncTable[I]:=Func21; + 23: fIdentFuncTable[I]:=Func23; + 25: fIdentFuncTable[I]:=Func25; + 27: fIdentFuncTable[I]:=Func27; + 28: fIdentFuncTable[I]:=Func28; + 29: fIdentFuncTable[I]:=Func29; + 32: fIdentFuncTable[I]:=Func32; + 33: fIdentFuncTable[I]:=Func33; + 35: fIdentFuncTable[I]:=Func35; + 37: fIdentFuncTable[I]:=Func37; + 38: fIdentFuncTable[I]:=Func38; + 39: fIdentFuncTable[I]:=Func39; + 40: fIdentFuncTable[I]:=Func40; + 41: fIdentFuncTable[I]:=Func41; + 44: fIdentFuncTable[I]:=Func44; + 45: fIdentFuncTable[I]:=Func45; + 47: fIdentFuncTable[I]:=Func47; + 49: fIdentFuncTable[I]:=Func49; + 52: fIdentFuncTable[I]:=Func52; + 54: fIdentFuncTable[I]:=Func54; + 55: fIdentFuncTable[I]:=Func55; + 56: fIdentFuncTable[I]:=Func56; + 57: fIdentFuncTable[I]:=Func57; + 59: fIdentFuncTable[I]:=Func59; + 60: fIdentFuncTable[I]:=Func60; + 61: fIdentFuncTable[I]:=Func61; + 63: fIdentFuncTable[I]:=Func63; + 64: fIdentFuncTable[I]:=Func64; + 65: fIdentFuncTable[I]:=Func65; + 66: fIdentFuncTable[I]:=Func66; + 69: fIdentFuncTable[I]:=Func69; + 71: fIdentFuncTable[I]:=Func71; + 73: fIdentFuncTable[I]:=Func73; + 75: fIdentFuncTable[I]:=Func75; + 76: fIdentFuncTable[I]:=Func76; + 79: fIdentFuncTable[I]:=Func79; + 81: fIdentFuncTable[I]:=Func81; + 84: fIdentFuncTable[I]:=Func84; + 85: fIdentFuncTable[I]:=Func85; + 87: fIdentFuncTable[I]:=Func87; + 88: fIdentFuncTable[I]:=Func88; + 91: fIdentFuncTable[I]:=Func91; + 92: fIdentFuncTable[I]:=Func92; + 94: fIdentFuncTable[I]:=Func94; + 95: fIdentFuncTable[I]:=Func95; + 96: fIdentFuncTable[I]:=Func96; + 97: fIdentFuncTable[I]:=Func97; + 98: fIdentFuncTable[I]:=Func98; + 99: fIdentFuncTable[I]:=Func99; + 100: fIdentFuncTable[I]:=Func100; + 101: fIdentFuncTable[I]:=Func101; + 102: fIdentFuncTable[I]:=Func102; + 103: fIdentFuncTable[I]:=Func103; + 105: fIdentFuncTable[I]:=Func105; + 106: fIdentFuncTable[I]:=Func106; + 117: fIdentFuncTable[I]:=Func117; + 126: fIdentFuncTable[I]:=Func126; + 129: fIdentFuncTable[I]:=Func129; + 132: fIdentFuncTable[I]:=Func132; + 133: fIdentFuncTable[I]:=Func133; + 136: fIdentFuncTable[I]:=Func136; + 141: fIdentFuncTable[I]:=Func141; + 143: fIdentFuncTable[I]:=Func143; + 166: fIdentFuncTable[I]:=Func166; + 168: fIdentFuncTable[I]:=Func168; + 191: fIdentFuncTable[I]:=Func191; + else fIdentFuncTable[I]:=AltFunc; + end; +end; + +function TmwPasLex.KeyHash(ToHash: PChar): Integer; +begin + Result:=0; + while ToHash^in ['a'..'z', 'A'..'Z']do + begin + Inc(Result, mHashTable[ToHash^]); + Inc(ToHash); + end; + if ToHash^in ['_', '0'..'9']then Inc(ToHash); + fStringLen:=ToHash-fToIdent; +end; { KeyHash } + +function TmwPasLex.KeyComp(const aKey: string): Boolean; +var + I: Integer; +begin + Temp:=fToIdent; + if Length(aKey)=fStringLen then + begin + Result:=True; + for i:=1 to fStringLen do + begin + if mHashTable[Temp^]<>mHashTable[aKey[i]]then + begin + Result:=False; + Break; + end; + Inc(Temp); + end; + end else Result:=False; +end; { KeyComp } + +function TmwPasLex.Func15: TTokenKind; +begin + if KeyComp('If')then Result:=tkIf else Result:=tkIdentifier; +end; + +function TmwPasLex.Func19: TTokenKind; +begin + if KeyComp('Do')then Result:=tkDo else + if KeyComp('And')then Result:=tkAnd else Result:=tkIdentifier; +end; + +function TmwPasLex.Func20: TTokenKind; +begin + if KeyComp('As')then Result:=tkAs else Result:=tkIdentifier; +end; + +function TmwPasLex.Func21: TTokenKind; +begin + if KeyComp('Of')then Result:=tkOf else + if KeyComp('At')then Result:=tkAt else Result:=tkIdentifier; +end; + +function TmwPasLex.Func23: TTokenKind; +begin + if KeyComp('End')then Result:=tkEnd else + if KeyComp('In')then Result:=tkIn else Result:=tkIdentifier; +end; + +function TmwPasLex.Func25: TTokenKind; +begin + if KeyComp('Far')then Result:=tkFar else Result:=tkIdentifier; +end; + +function TmwPasLex.Func27: TTokenKind; +begin + if KeyComp('Cdecl')then Result:=tkCdecl else Result:=tkIdentifier; +end; + +function TmwPasLex.Func28: TTokenKind; +begin + if KeyComp('Read')then + begin + if inSymbols(CharAhead(fStringLen))then Result:=tkIdentifier else + Result:=tkRead + end else + if KeyComp('Case')then Result:=tkCase else + if KeyComp('Is')then Result:=tkIs else Result:=tkIdentifier; +end; + +function TmwPasLex.Func29: TTokenKind; +begin + if KeyComp('On')then Result:=tkOn else Result:=tkIdentifier; +end; + +function TmwPasLex.Func32: TTokenKind; +begin + if KeyComp('File')then Result:=tkFile else + if KeyComp('Label')then Result:=tkLabel else + if KeyComp('Mod')then Result:=tkMod else Result:=tkIdentifier; +end; + +function TmwPasLex.Func33: TTokenKind; +begin + if KeyComp('Or')then Result:=tkOr else + if KeyComp('Name')then + begin + if inSymbols(CharAhead(fStringLen))then Result:=tkIdentifier else + Result:=tkName + end else + if KeyComp('Asm')then Result:=tkAsm else Result:=tkIdentifier; +end; + +function TmwPasLex.Func35: TTokenKind; +begin + if KeyComp('To')then Result:=tkTo else + if KeyComp('Nil')then Result:=tkNil else + if KeyComp('Div')then Result:=tkDiv else Result:=tkIdentifier; +end; + +function TmwPasLex.Func37: TTokenKind; +begin + if KeyComp('Begin')then Result:=tkBegin else Result:=tkIdentifier; +end; + +function TmwPasLex.Func38: TTokenKind; +begin + if KeyComp('Near')then Result:=tkNear else Result:=tkIdentifier; +end; + +function TmwPasLex.Func39: TTokenKind; +begin + if KeyComp('For')then Result:=tkFor else + if KeyComp('Shl')then Result:=tkShl else Result:=tkIdentifier; +end; + +function TmwPasLex.Func40: TTokenKind; +begin + if KeyComp('Packed')then Result:=tkPacked else Result:=tkIdentifier; +end; + +function TmwPasLex.Func41: TTokenKind; +begin + if KeyComp('Else')then Result:=tkElse else + if KeyComp('Var')then Result:=tkVar else Result:=tkIdentifier; +end; + +function TmwPasLex.Func44: TTokenKind; +begin + if KeyComp('Set')then Result:=tkSet else Result:=tkIdentifier; +end; + +function TmwPasLex.Func45: TTokenKind; +begin + if KeyComp('Shr')then Result:=tkShr else Result:=tkIdentifier; +end; + +function TmwPasLex.Func47: TTokenKind; +begin + if KeyComp('Then')then Result:=tkThen else Result:=tkIdentifier; +end; + +function TmwPasLex.Func49: TTokenKind; +begin + if KeyComp('Not')then Result:=tkNot else Result:=tkIdentifier; +end; + +function TmwPasLex.Func52: TTokenKind; +begin + if KeyComp('Raise')then Result:=tkRaise else + if KeyComp('Pascal')then Result:=tkPascal else Result:=tkIdentifier; +end; + +function TmwPasLex.Func54: TTokenKind; +begin + if KeyComp('Class')then + begin + Result:=tkClass; + if fLastNoSpace=tkEqual then + begin + fIsClass:=True; + if Identifiers[CharAhead(fStringLen)]then fIsClass:=False; + end else fIsClass:=False; + end else Result:=tkIdentifier; +end; + +function TmwPasLex.Func55: TTokenKind; +begin + if KeyComp('Object')then Result:=tkObject else Result:=tkIdentifier; +end; + +function TmwPasLex.Func56: TTokenKind; +begin + if KeyComp('Index')then + begin + if inSymbols(CharAhead(fStringLen))then Result:=tkIdentifier else + Result:=tkIndex + end else + if KeyComp('Out')then Result:=tkOut else Result:=tkIdentifier; +end; + +function TmwPasLex.Func57: TTokenKind; +begin + if KeyComp('While')then Result:=tkWhile else + if KeyComp('Goto')then Result:=tkGoto else + if KeyComp('Xor')then Result:=tkXor else Result:=tkIdentifier; +end; + +function TmwPasLex.Func59: TTokenKind; +begin + if KeyComp('Safecall')then Result:=tkSafecall else Result:=tkIdentifier; +end; + +function TmwPasLex.Func60: TTokenKind; +begin + if KeyComp('With')then Result:=tkWith else Result:=tkIdentifier; +end; + +function TmwPasLex.Func61: TTokenKind; +begin + if KeyComp('Dispid')then Result:=tkDispid else Result:=tkIdentifier; +end; + +function TmwPasLex.Func63: TTokenKind; +begin + if KeyComp('Public')then + begin + if inSymbols(CharAhead(fStringLen))then Result:=tkIdentifier else + Result:=tkPublic + end else + if KeyComp('Record')then Result:=tkRecord else + if KeyComp('Try')then Result:=tkTry else + if KeyComp('Array')then Result:=tkArray else + if KeyComp('Inline')then Result:=tkInline else Result:=tkIdentifier; +end; + +function TmwPasLex.Func64: TTokenKind; +begin + if KeyComp('Uses')then Result:=tkUses else + if KeyComp('Unit')then Result:=tkUnit else Result:=tkIdentifier; +end; + +function TmwPasLex.Func65: TTokenKind; +begin + if KeyComp('Repeat')then Result:=tkRepeat else Result:=tkIdentifier; +end; + +function TmwPasLex.Func66: TTokenKind; +begin + if KeyComp('Type')then Result:=tkType else Result:=tkIdentifier; +end; + +function TmwPasLex.Func69: TTokenKind; +begin + if KeyComp('Dynamic')then Result:=tkDynamic else + if KeyComp('Default')then Result:=tkDefault else + if KeyComp('Message')then Result:=tkMessage else Result:=tkIdentifier; +end; + +function TmwPasLex.Func71: TTokenKind; +begin + if KeyComp('Stdcall')then Result:=tkStdcall else + if KeyComp('Const')then Result:=tkConst else Result:=tkIdentifier; +end; + +function TmwPasLex.Func73: TTokenKind; +begin + if KeyComp('Except')then Result:=tkExcept else Result:=tkIdentifier; +end; + +function TmwPasLex.Func75: TTokenKind; +begin + if KeyComp('Write')then + begin + if inSymbols(CharAhead(fStringLen))then Result:=tkIdentifier else + Result:=tkWrite + end else Result:=tkIdentifier; +end; + +function TmwPasLex.Func76: TTokenKind; +begin + if KeyComp('Until')then Result:=tkUntil else Result:=tkIdentifier; +end; + +function TmwPasLex.Func79: TTokenKind; +begin + if KeyComp('Finally')then Result:=tkFinally else Result:=tkIdentifier; +end; + +function TmwPasLex.Func81: TTokenKind; +begin + if KeyComp('Interface')then + begin + Result:=tkInterface; + if fLastNoSpace=tkEqual then + fIsInterface:=True else fIsInterface:=False; + end else + if KeyComp('Stored')then Result:=tkStored else Result:=tkIdentifier; +end; + +function TmwPasLex.Func84: TTokenKind; +begin + if KeyComp('Abstract')then Result:=tkAbstract else Result:=tkIdentifier; +end; + +function TmwPasLex.Func85: TTokenKind; +begin + if KeyComp('Library')then Result:=tkLibrary else + if KeyComp('Forward')then Result:=tkForward else Result:=tkIdentifier; +end; + +function TmwPasLex.Func87: TTokenKind; +begin + if KeyComp('String')then Result:=tkString else Result:=tkIdentifier; +end; + +function TmwPasLex.Func88: TTokenKind; +begin + if KeyComp('Program')then Result:=tkProgram else Result:=tkIdentifier; +end; + +function TmwPasLex.Func91: TTokenKind; +begin + if KeyComp('Private')then + begin + if inSymbols(CharAhead(fStringLen))then Result:=tkIdentifier else + Result:=tkPrivate + end else + if KeyComp('Downto')then Result:=tkDownto else Result:=tkIdentifier; +end; + +function TmwPasLex.Func92: TTokenKind; +begin + if KeyComp('overload') then + Result:=tkOverload + else + if KeyComp('Inherited') then + Result:=tkInherited + else + Result:=tkIdentifier; +end; + +function TmwPasLex.Func94: TTokenKind; +begin + if KeyComp('Resident')then Result:=tkResident else + if KeyComp('Readonly')then Result:=tkReadonly else + if KeyComp('Assembler')then Result:=tkAssembler else Result:=tkIdentifier; +end; + +function TmwPasLex.Func95: TTokenKind; +begin + if KeyComp('Absolute')then Result:=tkAbsolute else Result:=tkIdentifier; +end; + +function TmwPasLex.Func96: TTokenKind; +begin + if KeyComp('Published')then + begin + if inSymbols(CharAhead(fStringLen))then Result:=tkIdentifier else + Result:=tkPublished + end else + if KeyComp('Override')then Result:=tkOverride else Result:=tkIdentifier; +end; + +function TmwPasLex.Func97: TTokenKind; +begin + if KeyComp('Threadvar')then Result:=tkThreadvar else Result:=tkIdentifier; +end; + +function TmwPasLex.Func98: TTokenKind; +begin + if KeyComp('Export')then Result:=tkExport else + if KeyComp('Nodefault')then Result:=tkNodefault else Result:=tkIdentifier; +end; + +function TmwPasLex.Func99: TTokenKind; +begin + if KeyComp('External')then Result:=tkExternal else Result:=tkIdentifier; +end; + +function TmwPasLex.Func100: TTokenKind; +begin + if KeyComp('Automated')then + begin + if inSymbols(CharAhead(fStringLen))then Result:=tkIdentifier else + Result:=tkAutomated + end else Result:=tkIdentifier; +end; + +function TmwPasLex.Func101: TTokenKind; +begin + if KeyComp('Register')then Result:=tkRegister else Result:=tkIdentifier; +end; + +function TmwPasLex.Func102: TTokenKind; +begin + if KeyComp('Function')then Result:=tkFunction else Result:=tkIdentifier; +end; + +function TmwPasLex.Func103: TTokenKind; +begin + if KeyComp('Virtual')then Result:=tkVirtual else Result:=tkIdentifier; +end; + +function TmwPasLex.Func105: TTokenKind; +begin + if KeyComp('Procedure')then Result:=tkProcedure else Result:=tkIdentifier; +end; + +function TmwPasLex.Func106: TTokenKind; +begin + if KeyComp('Protected')then + begin + if inSymbols(CharAhead(fStringLen))then Result:=tkIdentifier else + Result:=tkProtected + end else Result:=tkIdentifier; +end; + +function TmwPasLex.Func117: TTokenKind; +begin + if KeyComp('Exports')then Result:=tkExports else Result:=tkIdentifier; +end; + +function TmwPasLex.Func126: TTokenKind; +begin + if KeyComp('Implements') then + Result:=tkImplements + else + Result:=tkIdentifier; +end; + +function TmwPasLex.Func129: TTokenKind; +begin + if KeyComp('Dispinterface')then Result:=tkDispinterface else Result:=tkIdentifier; +end; + +function TmwPasLex.Func132: TTokenKind; +begin + if KeyComp('Reintroduce') then + Result:=tkReintroduce + else + Result:=tkIdentifier; +end; + +function TmwPasLex.Func133: TTokenKind; +begin + if KeyComp('Property')then Result:=tkProperty else Result:=tkIdentifier; +end; + +function TmwPasLex.Func136: TTokenKind; +begin + if KeyComp('Finalization')then Result:=tkFinalization else Result:=tkIdentifier; +end; + +function TmwPasLex.Func141: TTokenKind; +begin + if KeyComp('Writeonly')then Result:=tkWriteonly else Result:=tkIdentifier; +end; + +function TmwPasLex.Func143: TTokenKind; +begin + if KeyComp('Destructor')then Result:=tkDestructor else Result:=tkIdentifier; +end; + +function TmwPasLex.Func166: TTokenKind; +begin + if KeyComp('Constructor')then Result:=tkConstructor else + if KeyComp('Implementation')then Result:=tkImplementation else Result:=tkIdentifier; +end; + +function TmwPasLex.Func168: TTokenKind; +begin + if KeyComp('Initialization')then Result:=tkInitialization else Result:=tkIdentifier; +end; + +function TmwPasLex.Func191: TTokenKind; +begin + if KeyComp('Resourcestring')then Result:=tkResourcestring else + if KeyComp('Stringresource')then Result:=tkStringresource else Result:=tkIdentifier; +end; + +function TmwPasLex.AltFunc: TTokenKind; +begin + Result:=tkIdentifier +end; + +function TmwPasLex.IdentKind(MayBe: PChar): TTokenKind; +var + HashKey: Integer; +begin + fToIdent:=MayBe; + HashKey:=KeyHash(MayBe); + if HashKey<192 then Result:=fIdentFuncTable[HashKey]else Result:=tkIdentifier; +end; + +procedure TmwPasLex.MakeMethodTables; +var + I: Char; +begin + for I:=#0 to #255 do + case I of + #0: fProcTable[I]:=NullProc; + #10: fProcTable[I]:=LFProc; + #13: fProcTable[I]:=CRProc; + #1..#9, #11, #12, #14..#32: + fProcTable[I]:=SpaceProc; + '#': fProcTable[I]:=AsciiCharProc; + '$': fProcTable[I]:=IntegerProc; + #39: fProcTable[I]:=StringProc; + '0'..'9': fProcTable[I]:=NumberProc; + 'A'..'Z', 'a'..'z', '_': + fProcTable[I]:=IdentProc; + '{': fProcTable[I]:=BraceOpenProc; + '}': fProcTable[I]:=BraceCloseProc; + '!', '"', '%', '&', '('..'/', ':'..'@', '['..'^', '`', '~': + begin + case I of + '(': fProcTable[I]:=RoundOpenProc; + ')': fProcTable[I]:=RoundCloseProc; + '*': fProcTable[I]:=StarProc; + '+': fProcTable[I]:=PlusProc; + ',': fProcTable[I]:=CommaProc; + '-': fProcTable[I]:=MinusProc; + '.': fProcTable[I]:=PointProc; + '/': fProcTable[I]:=SlashProc; + ':': fProcTable[I]:=ColonProc; + ';': fProcTable[I]:=SemiColonProc; + '<': fProcTable[I]:=LowerProc; + '=': fProcTable[I]:=EqualProc; + '>': fProcTable[I]:=GreaterProc; + '@': fProcTable[I]:=AddressOpProc; + '[': fProcTable[I]:=SquareOpenProc; + ']': fProcTable[I]:=SquareCloseProc; + '^': fProcTable[I]:=PointerSymbolProc; + else fProcTable[I]:=SymbolProc; + end; + end; + else fProcTable[I]:=UnknownProc; + end; +end; + +constructor TmwPasLex.Create; +begin + inherited Create; + InitIdent; + MakeMethodTables; +end; { Create } + +destructor TmwPasLex.Destroy; +begin + inherited Destroy; +end; { Destroy } + +procedure TmwPasLex.SetOrigin(NewValue: PChar); +begin + fOrigin:=NewValue; + fComment:=csNo; + fLineNumber:=0; + fLinePos:=0; + Run:=0; + Next; +end; { SetOrigin } + +procedure TmwPasLex.SetRunPos(Value: Integer); +begin + Run:=Value; + Next; +end; + +procedure TmwPasLex.AddressOpProc; +begin + Case FOrigin[Run+1]of + '@': + begin + fTokenID:=tkDoubleAddressOp; + Inc(Run, 2); + end; + else + begin + fTokenID:=tkAddressOp; + Inc(Run); + end; + end; +end; + +procedure TmwPasLex.AsciiCharProc; +begin + fTokenID:=tkAsciiChar; + Inc(Run); + while FOrigin[Run]in ['0'..'9']do Inc(Run); +end; + +procedure TmwPasLex.BraceCloseProc; +begin + Inc(Run); + fTokenId:=tkError; +end; + +procedure TmwPasLex.BorProc; +begin + fTokenID:=tkBorComment; + case FOrigin[Run]of + #0: + begin + NullProc; + Exit; + end; + + #10: + begin + LFProc; + Exit; + end; + + #13: + begin + CRProc; + Exit; + end; + end; + + while FOrigin[Run]<>#0 do + case FOrigin[Run]of + '}': + begin + fComment:=csNo; + Inc(Run); + Break; + end; + #10: Break; + + #13: Break; + else Inc(Run); + end; +end; + +procedure TmwPasLex.BraceOpenProc; +begin + Case FOrigin[Run+1]of + '$': fTokenID:=tkCompDirect; + else + begin + fTokenID:=tkBorComment; + fComment:=csBor; + end; + end; + Inc(Run); + while FOrigin[Run]<>#0 do + case FOrigin[Run]of + '}': + begin + fComment:=csNo; + Inc(Run); + Break; + end; + #10: Break; + + #13: Break; + else Inc(Run); + end; +end; + +procedure TmwPasLex.ColonProc; +begin + Case FOrigin[Run+1]of + '=': + begin + Inc(Run, 2); + fTokenID:=tkAssign; + end; + else + begin + Inc(Run); + fTokenID:=tkColon; + end; + end; +end; + +procedure TmwPasLex.CommaProc; +begin + Inc(Run); + fTokenID:=tkComma; +end; + +procedure TmwPasLex.CRProc; +begin + Case fComment of + csBor: fTokenID:=tkCRLFCo; + csAnsi: fTokenID:=tkCRLFCo; + else fTokenID:=tkCRLF; + end; + + Case FOrigin[Run+1]of + #10: Inc(Run, 2); + else Inc(Run); + end; + Inc(fLineNumber); + fLinePos:=Run; +end; + +procedure TmwPasLex.EqualProc; +begin + Inc(Run); + fTokenID:=tkEqual; +end; + +procedure TmwPasLex.GreaterProc; +begin + Case FOrigin[Run+1]of + '=': + begin + Inc(Run, 2); + fTokenID:=tkGreaterEqual; + end; + else + begin + Inc(Run); + fTokenID:=tkGreater; + end; + end; +end; + +function TmwPasLex.InSymbols(aChar: Char): Boolean; +begin + if aChar in ['#', '$', '&', #39, '(', ')', '*', '+', ',', #150, '.', '/', ':', + ';', '<', '=', '>', '@', '[', ']', '^']then Result:=True else Result:=False; +end; + +function TmwPasLex.CharAhead(Count: Integer): Char; +begin + Temp:=fOrigin+Run+Count; + while Temp^in [#1..#9, #11, #12, #14..#32]do Inc(Temp); + Result:=Temp^; +end; + +function TmwPasLex.NextChar: Char; +begin + Temp:=fOrigin+Run; + Result:=Temp^; +end; + +procedure TmwPasLex.IdentProc; +begin + fTokenID:=IdentKind((fOrigin+Run)); + Inc(Run, fStringLen); + while Identifiers[fOrigin[Run]]do Inc(Run); +end; + +procedure TmwPasLex.IntegerProc; +begin + Inc(Run); + fTokenID:=tkInteger; + while FOrigin[Run]in ['0'..'9', 'A'..'F', 'a'..'f']do Inc(Run); +end; + +procedure TmwPasLex.LFProc; +begin + Case fComment of + csBor: fTokenID:=tkCRLFCo; + csAnsi: fTokenID:=tkCRLFCo; + else fTokenID:=tkCRLF; + end; + Inc(Run); + Inc(fLineNumber); + fLinePos:=Run; +end; + +procedure TmwPasLex.LowerProc; +begin + case FOrigin[Run+1]of + '=': + begin + Inc(Run, 2); + fTokenID:=tkLowerEqual; + end; + '>': + begin + Inc(Run, 2); + fTokenID:=tkNotEqual; + end + else + begin + Inc(Run); + fTokenID:=tkLower; + end; + end; +end; + +procedure TmwPasLex.MinusProc; +begin + Inc(Run); + fTokenID:=tkMinus; +end; + +procedure TmwPasLex.NullProc; +begin + fTokenID:=tkNull; +end; + +procedure TmwPasLex.NumberProc; +begin + Inc(Run); + fTokenID:=tkNumber; + while FOrigin[Run]in ['0'..'9', '.', 'e', 'E']do + begin + case FOrigin[Run]of + '.': + if FOrigin[Run+1]='.' then Break else fTokenID:=tkFloat + end; + Inc(Run); + end; +end; + +procedure TmwPasLex.PlusProc; +begin + Inc(Run); + fTokenID:=tkPlus; +end; + +procedure TmwPasLex.PointerSymbolProc; +begin + Inc(Run); + fTokenID:=tkPointerSymbol; +end; + +procedure TmwPasLex.PointProc; +begin + case FOrigin[Run+1]of + '.': + begin + Inc(Run, 2); + fTokenID:=tkDotDot; + end; + ')': + begin + Inc(Run, 2); + fTokenID:=tkSquareClose; + Dec(FSquareCount); + end; + else + begin + Inc(Run); + fTokenID:=tkPoint; + end; + end; +end; + +procedure TmwPasLex.RoundCloseProc; +begin + Inc(Run); + fTokenID:=tkRoundClose; + Dec(FRoundCount); +end; + +procedure TmwPasLex.AnsiProc; +begin + fTokenID:=tkAnsiComment; + case FOrigin[Run]of + #0: + begin + NullProc; + Exit; + end; + + #10: + begin + LFProc; + Exit; + end; + + #13: + begin + CRProc; + Exit; + end; + end; + + while fOrigin[Run]<>#0 do + case fOrigin[Run]of + '*': + if fOrigin[Run+1]=')' then + begin + fComment:=csNo; + Inc(Run, 2); + Break; + end else Inc(Run); + #10: Break; + + #13: Break; + else Inc(Run); + end; +end; + +procedure TmwPasLex.RoundOpenProc; +begin + Inc(Run); + case fOrigin[Run]of + '*': + begin + fTokenID:=tkAnsiComment; + if FOrigin[Run+1]='$' then fTokenID:=tkCompDirect else fComment:=csAnsi; + Inc(Run); + while fOrigin[Run]<>#0 do + case fOrigin[Run]of + '*': + if fOrigin[Run+1]=')' then + begin + fComment:=csNo; + Inc(Run, 2); + Break; + end else Inc(Run); + #10: Break; + #13: Break; + else Inc(Run); + end; + end; + '.': + begin + Inc(Run); + fTokenID:=tkSquareOpen; + Inc(FSquareCount); + end; + else + begin + FTokenID:=tkRoundOpen; + Inc(FRoundCount); + end; + end; +end; + +procedure TmwPasLex.SemiColonProc; +begin + Inc(Run); + fTokenID:=tkSemiColon; +end; + +procedure TmwPasLex.SlashProc; +begin + case FOrigin[Run+1]of + '/': + begin + Inc(Run, 2); + fTokenID:=tkSlashesComment; + while FOrigin[Run]<>#0 do + begin + case FOrigin[Run]of + #10, #13: Break; + end; + Inc(Run); + end; + end; + else + begin + Inc(Run); + fTokenID:=tkSlash; + end; + end; +end; + +procedure TmwPasLex.SpaceProc; +begin + Inc(Run); + fTokenID:=tkSpace; + while FOrigin[Run]in [#1..#9, #11, #12, #14..#32]do Inc(Run); +end; + +procedure TmwPasLex.SquareCloseProc; +begin + Inc(Run); + fTokenID:=tkSquareClose; + Dec(FSquareCount); +end; + +procedure TmwPasLex.SquareOpenProc; +begin + Inc(Run); + fTokenID:=tkSquareOpen; + Inc(FSquareCount); +end; + +procedure TmwPasLex.StarProc; +begin + Inc(Run); + fTokenID:=tkStar; +end; + +procedure TmwPasLex.StringProc; +begin + fTokenID:=tkString; + if(FOrigin[Run+1]=#39)and(FOrigin[Run+2]=#39)then Inc(Run, 2); + repeat + case FOrigin[Run]of + #0, #10, #13: Break; + end; + Inc(Run); + until FOrigin[Run]=#39; + if FOrigin[Run]<>#0 then Inc(Run); +end; + +procedure TmwPasLex.SymbolProc; +begin + Inc(Run); + fTokenID:=tkSymbol; +end; + +procedure TmwPasLex.UnknownProc; +begin + Inc(Run); + fTokenID:=tkUnknown; +end; + +procedure TmwPasLex.Next; +begin + Case fTokenID of + tkIdentifier: + begin + fLastIdentPos:=fTokenPos; + fLastNoSpace:=fTokenID; + fLastNoSpacePos:=fTokenPos; + end; + tkSpace: ; + else + begin + fLastNoSpace:=fTokenID; + fLastNoSpacePos:=fTokenPos; + end; + end; + fTokenPos:=Run; + Case fComment of + csNo: fProcTable[fOrigin[Run]]; + else + Case fComment of + csBor: BorProc; + csAnsi: AnsiProc; + end; + end; +end; + +function TmwPasLex.GetToken: string; +var + Len: Longint; +begin + Len:=Run-fTokenPos; + SetString(Result, (FOrigin+fTokenPos), Len); +end; + +procedure TmwPasLex.NextID(ID: TTokenKind); +begin + repeat + Case fTokenID of + tkNull: Break; + else Next; + end; + until fTokenID=ID; +end; + +procedure TmwPasLex.NextNoJunk; +begin + repeat + Next; + until not(fTokenID in [tkSlashesComment, tkAnsiComment, tkBorComment, tkCRLF, tkCRLFCo, tkSpace]); +end; + +procedure TmwPasLex.NextClass; +begin + if fTokenID<>tkNull then next; + repeat + Case fTokenID of + tkNull: Break; + else Next; + end; + until(fTokenID=tkClass)and(IsClass); +end; + +initialization + MakeIdentTable; + +end. + + + + + diff --git a/examples/apps/ide/src/proclistimages.inc b/examples/apps/ide/src/proclistimages.inc new file mode 100644 index 00000000..0b6454db --- /dev/null +++ b/examples/apps/ide/src/proclistimages.inc @@ -0,0 +1,208 @@ + +Const + grdimg_destructor_16 : Array[0..821] of byte = ( + 66, 77, 54, 3, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, + 0, 16, 0, 0, 0, 16, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 19, 11, 0, 0, 19, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255, 0, 0, 0, 0, 0, + 0,132,132,132,132,132,132,132,132,132,132,132,132, 0, 0, 0, 0, + 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255, 0, 0, 0,255, 0,255,132,132,132, 0, 0, + 0,132,132,132, 0, 0, 0, 0, 0, 0,132,132,132,255, 0,255, 0, + 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255, 0, 0, 0, 0, 0, 0,255, 0,255,255, 0,255, 0, 0, + 0,255, 0,255,255, 0,255,255, 0,255, 0, 0, 0, 0, 0, 0,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 0, 0, 0,255, 0,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0,255, 0,255, 0, 0, 0,255, 0,255, 0, 0, 0,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255, 0, 0, 0,255, 0,255, + 0, 0, 0,255,255,255,255,255,255,255,255,255,255, 0,255, 0, 0, + 0,255, 0,255,132,132,132,255, 0,255, 0, 0, 0,255, 0,255,255, + 0,255,255, 0,255,255, 0,255, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255, 0,255, 0, 0, + 0,255, 0,255, 0, 0, 0, 0, 0, 0,255, 0,255,255, 0,255,255, + 0,255,255, 0,255, 0, 0, 0,255,255,255,255,255,255,255,255,255, + 0, 0, 0,255,255,255,255,255,255,132,132,132,255,255,255, 0, 0, + 0,255, 0,255, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,132, + 132,132, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255, 0,255, 0, 0, + 0, 0, 0, 0,132,132,132,255, 0,255,255, 0,255, 0, 0, 0,132, + 132,132,255,255,255, 0, 0, 0,255,255,255,255,255,255,255,255,255, + 0, 0, 0,255,255,255,255,255,255, 0, 0, 0,255,255,255,132,132, + 132, 0, 0, 0,255, 0,255,255, 0,255, 0, 0, 0,255, 0,255,132, + 132,132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,132,132,132,255,255,255, 0, 0, + 0,255, 0,255,255, 0,255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 0,255,132,132,132,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, 0, + 255,255, 0,255, 0, 0, 0,255, 0,255,255, 0,255,132,132,132,255, + 0,255,132,132,132,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,198,198,198, 0, 0, 0, 0, 0, 0,255, 0,255,255, 0, + 255,255, 0,255, 0, 0, 0, 0, 0, 0,132,132,132,255, 0,255,132, + 132,132,255,255,255,255,255,255,255,255,255,255,255,255,198,198,198, + 0, 0, 0, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255, 0, 0, 0, 0, 0, 0, 0, 0, 0,132, + 132,132,255,255,255,255,255,255,198,198,198, 0, 0, 0,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,132, + 132,132,198,198,198, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255); + +Const + grdimg_function_16 : Array[0..821] of byte = ( + 66, 77, 54, 3, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, + 0, 16, 0, 0, 0, 16, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 19, 11, 0, 0, 19, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,132, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,132, 0, 0,255, 0,255,255, 0,255,255, 0,255, + 132, 0, 0,132, 0, 0,255, 0,255,255, 0,255,132, 0, 0,255, 0, + 255,255, 0,255,132, 0, 0,255, 0,255,255, 0,255,132, 0, 0,255, + 0,255,255, 0,255,132, 0, 0,255, 0,255,255, 0,255,132, 0, 0, + 132, 0, 0,255, 0,255,255, 0,255,132, 0, 0,255, 0,255,255, 0, + 255,132, 0, 0,255, 0,255,255, 0,255,132, 0, 0,255, 0,255,255, + 0,255,132, 0, 0,255, 0,255,255, 0,255,132, 0, 0,132, 0, 0, + 255, 0,255,255, 0,255,132, 0, 0,255, 0,255,255, 0,255,132, 0, + 0,255, 0,255,255, 0,255,132, 0, 0,255, 0,255,255, 0,255,132, + 0, 0,255, 0,255,255, 0,255,132, 0, 0,132, 0, 0,255, 0,255, + 255, 0,255,132, 0, 0,255, 0,255,255, 0,255,132, 0, 0,132, 0, + 0,132, 0, 0,255, 0,255,255, 0,255,255, 0,255,132, 0, 0,255, + 0,255,255, 0,255,132, 0, 0,132, 0, 0,255, 0,255,255, 0,255, + 132, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,132, 0, 0,255, 0,255,132, + 0, 0,132, 0, 0,132, 0, 0,132, 0, 0,255, 0,255,255, 0,255, + 132, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,132, 0, 0,255, 0,255,255, 0,255,255, 0,255,132, + 0, 0,132, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,132, 0, 0,132, + 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,132, 0, 0,132, 0, 0,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,132, 0, 0,132, 0, 0,255, 0,255,132, + 0, 0,132, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,132, 0, 0,132, 0, 0,132, 0, 0,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255); + +Const + grdimg_constructor_16 : Array[0..821] of byte = ( + 66, 77, 54, 3, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, + 0, 16, 0, 0, 0, 16, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 19, 11, 0, 0, 19, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,132,132, + 132,198,198,198,198,198,198,198,198,198,198,198,198,198,198,198,198, + 198,198,198,198,198,198,198,198, 0, 0, 0,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,132,132,132,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,198,198,198, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,132,132,132,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,198, + 198,198, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,132,132,132,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,198,198,198, 0, + 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,132,132,132,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,198,198,198, 0, 0, 0,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 132,132,132,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,198,198,198, 0, 0, 0,255, 0,255,255, + 0,255,255, 0,255,255,255,255,255, 0,255,255, 0,255,132,132,132, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,198,198,198, 0, 0, 0,255, 0,255,255, 0,255,255, + 0,255,132,132,132,255,255,255,132,132,132,132,132,132,255,255,255, + 255,255,255,132,132,132,255,255,255,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,132,132,132, 0,255,255,132,132,132,255,255,255,132,132,132, + 255,255,255,255,255,255,255,255,255,198,198,198,255,255,255,132,132, + 132,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255,255,255,255, + 255,255,132,132,132,255,255,255, 0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,198,198,198,132,132,132,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,132,132,132,132, + 132,132, 0,255,255,255,255,255,255,255,255,132,132,132,132,132,132, + 132,132,132,132,132,132,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,132,132,132, 0,255,255,132, + 132,132, 0,255,255,132,132,132,255,255,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,132,132,132,255,255,255,255, 0,255,132,132,132,255, + 255,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255,255,255,255, 0,255,255, 0,255,132,132,132,255,255,255,255, + 0,255,255, 0,255,132,132,132,255,255,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,132,132,132,255,255,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255); + +Const + grdimg_gears_16 : Array[0..821] of byte = ( + 66, 77, 54, 3, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, + 0, 16, 0, 0, 0, 16, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 19, 11, 0, 0, 19, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 0, 0, 0, 0,132,132, 0, 0, 0, 0,255,255, 0, + 0, 0, 0,132,132, 0, 0, 0, 0, 0, 0,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0,132,132, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,132,132, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255, 0,132,132, 0,132, + 132, 0,255,255, 0,132,132, 0, 0, 0, 0,132,132, 0,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255, 0,132,132, 0,255,255, 0,255, + 255, 0, 0, 0,132,132,132, 0, 0, 0, 0,255,255, 0,255,255, 0, + 132,132, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255, 0,132,132, 0,132,132, 0,255,255, 0,132, + 132, 0, 0, 0, 0,132,132, 0,255,255, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255, 0,132,132, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,132,132, 0, 0, 0,255, 0,255,255, + 0,255,255, 0,255,255, 0,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0,132,132, 0,132,132, 0, 0, 0, 0,255,255, 0, 0, + 0, 0,132,132, 0, 0, 0, 0, 0, 0,255, 0,255,255, 0,255, 0, + 0, 0, 0,132,132, 0, 0, 0, 0,255,255, 0, 0, 0, 0,132,132, + 0, 0, 0, 0, 0, 0, 0,132,132, 0,132,132, 0,132,132, 0,132, + 132,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 0,132,132, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,132,132, + 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255, 0,132,132, 0,132,132, 0,255,255, 0, + 132,132, 0, 0, 0, 0,132,132, 0,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 0,132,132, 0,255,255, 0,255,255, 0, 0, 0,132, + 132,132, 0, 0, 0, 0,255,255, 0,255,255, 0,132,132, 0, 0, 0, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0,132,132, 0,132,132, 0,255,255, 0,132,132, 0, 0, 0, 0, + 132,132, 0,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0,132,132, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,132,132, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 0,132, + 132, 0,132,132, 0, 0, 0, 0,255,255, 0, 0, 0, 0,132,132, 0, + 0, 0, 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0,132,132, 0,132,132, 0,132,132, 0,132,132,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255); diff --git a/examples/apps/ide/src/project.pas b/examples/apps/ide/src/project.pas new file mode 100644 index 00000000..699c0957 --- /dev/null +++ b/examples/apps/ide/src/project.pas @@ -0,0 +1,368 @@ +unit Project; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, UnitList, fpg_base, fpg_iniutils; + +type + TBooleanGrid = array of array of Boolean; + + TProject = class(TObject) + private + FMakeOptionsGrid: TBooleanGrid; + FProjectName: TfpgString; + FMainUnit: TfpgString; + FUnitDirs: TStringList; + FUnitDirsGrid: TBooleanGrid; + FUnitList: TUnitList; + FIniFile: TfpgINIFile; + FProjectDir: TfpgString; + FTargetFile: TfpgString; + FDefaultMake: integer; + FMakeOptions: TStringList; + FMacroNames: TStringList; + FUnitOutputDir: TfpgString; + public + constructor Create; + destructor Destroy; override; + function Save(const AFile: TfpgString = ''): Boolean; + function Load(AProjectFile: TfpgString): Boolean; + function GenerateCmdLine(const AShowOnly: Boolean = False; const ABuildMode: integer = -1): TfpgString; + procedure ClearAndInitMakeOptions(const ASize: integer); + procedure ClearAndInitUnitDirsGrid(const ASize: integer); + property ProjectDir: TfpgString read FProjectDir write FProjectDir; + property ProjectName: TfpgString read FProjectName write FProjectName; + property MainUnit: TfpgString read FMainUnit write FMainUnit; + property TargetFile: TfpgString read FTargetFile write FTargetFile; + property UnitList: TUnitList read FUnitList; + property DefaultMake: integer read FDefaultMake write FDefaultMake; + property MakeOptions: TStringList read FMakeOptions; + property MakeOptionsGrid: TBooleanGrid read FMakeOptionsGrid write FMakeOptionsGrid; + property MacroNames: TStringList read FMacroNames; + property UnitDirs: TStringList read FUnitDirs; + property UnitOutputDir: TfpgString read FUnitOutputDir write FUnitOutputDir; + property UnitDirsGrid: TBooleanGrid read FUnitDirsGrid write FUnitDirsGrid; + end; + + +// lazy-mans singleton +function GProject: TProject; + +procedure FreeProject; + + +implementation + +uses + ideconst + ,ideutils + ,fpg_utils + ; + + +var + uProject: TProject; + +function GProject: TProject; +begin + if not Assigned(uProject) then + uProject := TProject.Create; + Result := uProject; +end; + +procedure FreeProject; +begin + uProject.Free; + uProject := nil; +end; + + +{ TProject } + +constructor TProject.Create; +begin + inherited Create; + FUnitList := TUnitList.Create; + FMakeOptions := TStringList.Create; + FMacroNames := TStringList.Create; + FUnitDirs := TStringList.Create; +end; + +destructor TProject.Destroy; +begin + FUnitDirs.Free; + FMacroNames.Free; + FMakeOptions.Free; + FUnitList.Free; + FIniFile.Free; + inherited Destroy; +end; + +function TProject.Save(const AFile: TfpgString = ''): Boolean; +var + c, j: integer; + s: TfpgString; + lDelim: TfpgString; + + procedure SaveList(AList: TStringList; const CName, IName: TfpgString); + var + i: integer; + begin + FIniFile.WriteInteger(cProjectOptions, CName, AList.Count); + for i := 0 to AList.Count-1 do + FIniFile.WriteString(cProjectOptions, IName + IntToStr(i+1), AList[i]); + end; + +begin + Result := False; + if (AFile = '') and (ProjectName = '') then + raise Exception.Create('Project name has not been specified yet'); + + if not Assigned(FIniFile) then + begin + if AFile = '' then + FIniFile := TfpgINIFile.CreateExt(ProjectDir + ProjectName + cProjectExt) + else + FIniFile := TfpgINIFile.CreateExt(AFile); + end + else + begin + if AFile <> '' then + begin + FIniFile.Free; + FIniFile := TfpgINIFile.CreateExt(AFile); + end; + end; + + if AFile <> '' then + ProjectName := fpgExtractFileName(AFile); + + FIniFile.WriteString(cProjectOptions, 'ProjectName', ProjectName); + FIniFile.WriteString(cProjectOptions, 'MainUnit', MainUnit); + FIniFile.WriteString(cProjectOptions, 'TargetFile', TargetFile); + FIniFile.WriteInteger(cProjectOptions, 'DefaultMake', DefaultMake); + FIniFile.WriteString(cProjectOptions, 'UnitOutputDir', UnitOutputDir); + + // Process the Make (compiler param) options + { first delete old items in ini file } + c := FIniFile.ReadInteger(cProjectOptions, 'MakeOptionsCount', 0); + for j := 1 to c do + FIniFile.DeleteKey(cProjectOptions, cINIMakeOption + IntToStr(j)); + { no lets save new info } + SaveList(MakeOptions, 'MakeOptionsCount', cINIMakeOption); + for j := 0 to MakeOptions.Count-1 do + begin + s := ''; + lDelim := ''; + for c := 0 to 5 do + begin + if MakeOptionsGrid[c, j] then // True = 1, False = 0 + s := s + lDelim + '1' + else + s := s + lDelim + '0'; + lDelim := ','; + end; + FIniFile.WriteString(cProjectOptions, cINIMakeOptionGrid + IntToStr(j+1), s); + end; + + // macros definitions + SaveList(MacroNames, 'MacroCount', 'Macro'); + + // unit search directories + { first delete old items in ini file } + c := FIniFile.ReadInteger(cProjectOptions, 'UnitDirsCount', 0); + for j := 1 to c do + FIniFile.DeleteKey(cProjectOptions, cINIUnitDir + IntToStr(j)); + SaveList(UnitDirs, 'UnitDirsCount', cINIUnitDir); + for j := 0 to UnitDirs.Count-1 do + begin + s := ''; + lDelim := ''; + for c := 0 to 9 do + begin + if UnitDirsGrid[c, j] then // True = 1, False = 0 + s := s + lDelim + '1' + else + s := s + lDelim + '0'; + lDelim := ','; + end; + FIniFile.WriteString(cProjectOptions, cINIUnitDirGrid + IntToStr(j+1), s); + end; + + // Unit file list + FIniFile.WriteInteger(cUnits, 'UnitCount', UnitList.Count); + for j := 0 to UnitList.Count-1 do + begin + s := UnitList[j].FileName; + FIniFile.WriteString(cUnits, 'Unit' + IntToStr(j+1), + Format('%s,%s', [ExtractRelativepath(ProjectDir, s), BoolToStr(UnitList[j].Opened, False)])); + end; + + Result := True; +end; + +function TProject.Load(AProjectFile: TfpgString): Boolean; +var + a: string; + s: TfpgString; + j: integer; + l: integer; + sl: TStringList; + u: TUnit; + + // CName = xxxCount & IName is the Item name + procedure LoadList(ASection: TfpgString; AList: TStringList; const CName, IName: TfpgString); + var + c: integer; + i: integer; + begin + c := FIniFile.ReadInteger(ASection, CName, 0); + for i := 0 to c-1 do + begin + s := FIniFile.ReadString(ASection, IName + IntToStr(i+1), ''); + if s <> '' then + AList.Add(s); + end; + end; + +begin + Result := False; + if AProjectFile = '' then + raise Exception.Create('You need to specify a Project filename'); + + if not Assigned(FIniFile) then + FIniFile := TfpgINIFile.CreateExt(AProjectFile); + + ProjectDir := fpgExtractFilePath(AProjectFile); + ProjectName := FIniFile.ReadString(cProjectOptions, 'ProjectName', ChangeFileExt(fpgExtractFileName(AProjectFile), '')); + MainUnit := FIniFile.ReadString(cProjectOptions, 'MainUnit', ''); + TargetFile := FIniFile.ReadString(cProjectOptions, 'TargetFile', ''); + DefaultMake := FIniFile.ReadInteger(cProjectOptions, 'DefaultMake', 0); + UnitOutputDir := FIniFile.ReadString(cProjectOptions, 'UnitOutputDir', 'units/'+cMacro_Target+'/'); + + // Load make options + LoadList(cProjectOptions, MakeOptions, 'MakeOptionsCount', 'MakeOption'); + sl := TStringList.Create; + try + LoadList(cProjectOptions, sl, 'MakeOptionsCount', cINIMakeOptionGrid); + SetLength(FMakeOptionsGrid, 6, MakeOptions.Count); // 6 columns by X rows + for j := 0 to sl.Count-1 do + begin + s := sl[j]; + for l := 0 to 5 do // we know we only have 6 columns + begin + a := tiToken(s, ',', l+1); + MakeOptionsGrid[l, j] := Boolean(StrToInt(a)); // 1 = True, 0 = False + end; + end; + finally + sl.Free; + end; + + // Load Macro definitions + LoadList(cProjectOptions, MacroNames, 'MacroCount', 'Macro'); + + // Load Unit search dirs + LoadList(cProjectOptions, UnitDirs, 'UnitDirsCount', 'UnitDir'); + sl := TStringList.Create; + try + LoadList(cProjectOptions, sl, 'UnitDirsCount', 'UnitDirEnabled'); + SetLength(FUnitDirsGrid, 10, UnitDirs.Count); // 10 columns by X rows + for j := 0 to sl.Count-1 do + begin + s := sl[j]; + for l := 0 to 9 do // we know we only have 10 columns + begin + a := tiToken(s, ',', l+1); + UnitDirsGrid[l, j] := Boolean(StrToInt(a)); // 1 = True, 0 = False + end; + end; + finally + sl.Free; + end; + + // Load Unit file list + sl := TStringList.Create; + try + LoadList(cUnits, sl, 'UnitCount', 'Unit'); + for j := 0 to sl.Count-1 do + begin + u := TUnit.Create; + s := tiToken(sl[j], ',', 1); + u.FileName := fpgExpandFileName(ProjectDir + s); + u.Opened := Boolean(StrToInt(tiToken(sl[j], ',', 2))); + UnitList.Add(u); + end; + finally + sl.Free; + end; + + Result := True; +end; + +function TProject.GenerateCmdLine(const AShowOnly: Boolean; const ABuildMode: integer): TfpgString; +var + c: TfpgString; + b: integer; + eol: TfpgString; + i: integer; +begin + if AShowOnly then + eol := LineEnding + else + eol := ''; + if ABuildMode = -1 then + b := DefaultMake + else + b := ABuildMode; + + // include dirs + for i := 0 to UnitDirs.Count-1 do + if UnitDirsGrid[b, i] and UnitDirsGrid[7, i] then + c := c + ' -Fi' + UnitDirs[i] + eol; + // unit dirs + for i := 0 to UnitDirs.Count-1 do + if UnitDirsGrid[b, i] and UnitDirsGrid[6, i] then + c := c + ' -Fu' + UnitDirs[i] + eol; + // unit output dir + if UnitOutputDir <> '' then + c := c + ' -FU' + UnitOutputDir + eol; + // make option - compiler flags + for i := 0 to MakeOptions.Count-1 do + if MakeOptionsGrid[b, i] then + c := c + ' ' + MakeOptions[i]; + // target output file + if TargetFile <> '' then + c := c + ' -o' + TargetFile; + // unit to start compilation + c := c + ' ' + MainUnit; + + Result := c; +end; + +procedure TProject.ClearAndInitMakeOptions(const ASize: integer); +begin + FMakeOptions.Clear; + SetLength(FMakeOptionsGrid, 0, 0); // free items + SetLength(FMakeOptionsGrid, 6, ASize); // 6 columns by X rows +end; + +procedure TProject.ClearAndInitUnitDirsGrid(const ASize: integer); +begin + FUnitDirs.Clear; + SetLength(FUnitDirsGrid, 0, 0); // free items + SetLength(FUnitDirsGrid, 10, ASize); // 10 columns by X rows +end; + + +initialization + uProject := nil; + +finalization + FreeProject; + +end. + diff --git a/examples/apps/ide/src/stringhelpers.pas b/examples/apps/ide/src/stringhelpers.pas new file mode 100644 index 00000000..35fb9060 --- /dev/null +++ b/examples/apps/ide/src/stringhelpers.pas @@ -0,0 +1,390 @@ +unit stringhelpers; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils; + +function psubstr(const start,stop: pchar): string; +function cstringtostringvar(var inp: pchar): string; +function trystrtooct(const inp: string; out value: longword): boolean; +function strtooct(const inp: string): longword; +function trystrtobin(const inp: string; out value: longword): boolean; +function strtobin(const inp: string): longword; +function trystrtodec(const inp: string; out value: longword): boolean; +function strtodec(const inp: string): longword; +function trystrtohex(const inp: string; out value: longword): boolean; +function strtohex(const inp: string): longword; +function trystrtooct64(const inp: string; out value: qword): boolean; + +function trystrtointvalue(const inp: string; out value: longword): boolean; overload; +function trystrtointvalue64(const inp: string; out value: qword): boolean; overload; + + +implementation + + +procedure formaterror(const value: string); +begin + raise exception.Create('Invalid number '''+value+'''.'); +end; + +function strtooct(const inp: string): longword; + //wandelt 1..0-string in longword) +begin + if not trystrtooct(inp,result) then begin + formaterror(inp); + end; +end; + +function strtooct64(const inp: string): qword; + //wandelt 1..0-string in longword) +begin + if not trystrtooct64(inp,result) then begin + formaterror(inp); + end; +end; + +function strtodec1(const inp: string; out value: longword): boolean; +begin + result:= trystrtoint(inp,integer(value)); +end; + +function strtodec164(const inp: string; out value: qword): boolean; +begin + result:= trystrtoint64(inp,int64(value)); +end; + +function strtohex1(const inp: string; out value: longword): boolean; +begin + result:= trystrtoint('$'+inp,integer(value)); +end; + +function strtohex164(const inp: string; out value: qword): boolean; +begin + result:= trystrtoint64('$'+inp,int64(value)); +end; + + +function strtooct1(const inp: string; out value: longword): boolean; +var + int1: integer; + ca1: longword; + ch1: char; +begin + result:= false; + if inp <> '' then begin + value:= 0; + ca1:= 0; + for int1:= length(inp) downto 1 do begin + ch1:= inp[int1]; + if (ch1 < '0') or (ch1 > '7') then begin + exit; + end; + value:= value + longword(((ord(ch1) - ord('0'))) shl ca1); + inc(ca1,3); + end; + result:= true; + end; +end; + +function strtobin1(const inp: string; out value: longword): boolean; + //wandelt 1..0-string in longword) +var + int1: integer; + lwo1: longword; +begin + result:= false; + if inp <> '' then begin + value:= 0; + lwo1:= 1; + for int1:= length(inp) downto 1 do begin + if inp[int1] = '1' then begin + value:= value + lwo1; + end + else begin + if inp[int1] <> '0' then begin + exit; + end; + end; + lwo1:= lwo1 shl 1; + end; + result:= true; + end; +end; + +function strtobin164(const inp: string; out value: qword): boolean; + //wandelt 1..0-string in longword) +var + int1: integer; + lwo1: qword; +begin + result:= false; + if inp <> '' then begin + value:= 0; + lwo1:= 1; + for int1:= length(inp) downto 1 do begin + if inp[int1] = '1' then begin + value:= value + lwo1; + end + else begin + if inp[int1] <> '0' then begin + exit; + end; + end; + lwo1:= lwo1 shl 1; + end; + result:= true; + end; +end; + + + +function psubstr(const start,stop: pchar): string; +var + int1: integer; +begin + if (start = nil) or (stop = nil) then begin + result:= ''; + end + else begin + int1:= stop-start; + setlength(result,int1); + move(start^,result[1],int1); + end; +end; + +function cstringtostringvar(var inp: pchar): string; + +const + quotechar = '"'; + escapechar = '\'; + +var + po1,po2: pchar; + int1,int2: integer; + ch1: char; + +begin + result:= ''; + if inp <> nil then begin + po1:= inp; + while true do begin + while (po1^ = ' ') do begin //first quote + inc(po1); + end; + if (po1^ <> quotechar) then begin + break; //end or no start quote + end; + inc(po1); + po2:= po1; //text + while true do begin + while (po1^ <> quotechar) and (po1^ <> escapechar) do begin + if (po1^ = #0) then begin + result:= ''; + inp:= nil; + exit; //error: no end quote + end; + inc(po1); + end; + int1:= po1-po2; //text length + int2:= length(result)+1; + setlength(result,length(result) + int1); + move(po2^,result[int2],int1); //add text + if po1^ = escapechar then begin + inc(po1); + case po1^ of + 'a': ch1:= #$07; + 'b': ch1:= #$08; + 'f': ch1:= #$0c; + 'n': ch1:= #$0a; + 'r': ch1:= #$0d; + 't': ch1:= #$09; + 'v': ch1:= #$0b; + '\': ch1:= '\'; + '''': ch1:= ''''; + '"': ch1:= '"'; + '?': ch1:= '?'; + '0'..'7': begin + po2:= po1; + for int1:= 0 to 2 do begin + if (po1^ < '0') or (po1^ > '7') then begin + break; + end; + inc(po1); + end; + ch1:= char(strtooct(psubstr(po2,po1))); + dec(po1); + end; + 'x','X': begin + inc(po1); + po2:= po1; + while (po1^ >= '0') and (po1^ <= '9') or + (po1^ >= 'a') and (po1^ <= 'f') or + (po1^ >= 'A') and (po1^ <= 'F') do begin + inc(po1); + end; + ch1:= char(strtohex(psubstr(po2,po1))); + dec(po1); + end; + else begin + ch1:= ' '; + end; + end; + result:= result + ch1; + inc(po1); + end + else begin + inc(po1); + break; + end; + po2:= po1; //past quote + end; + end; + inp:= po1; + end; +end; + +function strtooct164(const inp: string; out value: qword): boolean; +var + int1: integer; + ca1: longword; + ch1: char; +begin + result:= false; + if inp <> '' then begin + value:= 0; + ca1:= 0; + for int1:= length(inp) downto 1 do begin + ch1:= inp[int1]; + if (ch1 < '0') or (ch1 > '7') then begin + exit; + end; + value:= value + qword(((ord(ch1) - ord('0'))) shl ca1); + inc(ca1,3); + end; + result:= true; + end; +end; + +function trystrtooct(const inp: string; out value: longword): boolean; +begin + result:= strtooct1(inp,value); + if not result then begin + result:= trystrtointvalue(inp,value); + end; +end; + +function trystrtobin(const inp: string; out value: longword): boolean; +begin + result:= strtobin1(inp,value); + if not result then begin + result:= trystrtointvalue(inp,value); + end; +end; + +function strtobin(const inp: string): longword; + //wandelt 0..1-string in longword) +begin + if not trystrtobin(inp,result) then begin + formaterror(inp); + end; +end; + +function trystrtodec(const inp: string; out value: longword): boolean; +begin + result:= strtodec1(inp,value); + if not result then begin + result:= trystrtointvalue(inp,value); + end; +end; + +function strtodec(const inp: string): longword; + //wandelt 0..9-string in longword) +begin + if not trystrtodec(inp,result) then begin + formaterror(inp); + end; +end; + +function trystrtohex(const inp: string; out value: longword): boolean; +begin + result:= strtohex1(inp,value); + if not result then begin + result:= trystrtointvalue(inp,value); + end; +end; + +function strtohex(const inp: string): longword; +begin + if not trystrtohex(inp,result) then begin + formaterror(inp); + end; +end; + +function trystrtooct64(const inp: string; out value: qword): boolean; +begin + result:= strtooct164(inp,value); + if not result then begin + result:= trystrtointvalue64(inp,value); + end; +end; + +function trystrtointvalue(const inp: string; out value: longword): boolean; +var + lint1: int64; +begin + result:= false; + if length(inp) > 0 then begin + case inp[1] of + '%': result:= strtobin1(copy(inp,2,length(inp)-1),value); + '&': result:= strtooct1(copy(inp,2,length(inp)-1),value); + '#': result:= strtodec1(copy(inp,2,length(inp)-1),value); + '$': result:= strtohex1(copy(inp,2,length(inp)-1),value); + else begin + if (length(inp) > 2) and + ((inp[2] = 'x') or (inp[2] = 'X')) and (inp[1] = '0') then begin + result:= strtohex1(copy(inp,3,length(inp)-2),value); + end + else begin + result:= trystrtoint64(inp,lint1); + if result then begin + value:= lint1; + end; + end; + end; + end; + end; +end; + +function trystrtointvalue64(const inp: string; out value: qword): boolean; overload; +var + lint1: int64; +begin + result:= false; + if length(inp) > 0 then begin + case inp[1] of + '%': result:= strtobin164(copy(inp,2,length(inp)-1),value); + '&': result:= strtooct164(copy(inp,2,length(inp)-1),value); + '#': result:= strtodec164(copy(inp,2,length(inp)-1),value); + '$': result:= strtohex164(copy(inp,2,length(inp)-1),value); + else begin + if (length(inp) > 2) and + ((inp[2] = 'x') or (inp[2] = 'X')) and (inp[1] = '0') then begin + result:= strtohex164(copy(inp,3,length(inp)-2),value); + end + else begin + result:= trystrtoint64(inp,lint1); + if result then begin + value:= lint1; + end; + end; + end; + end; + end; +end; + +end. + diff --git a/examples/apps/ide/src/synregexpr.pas b/examples/apps/ide/src/synregexpr.pas new file mode 100644 index 00000000..b88d9c2d --- /dev/null +++ b/examples/apps/ide/src/synregexpr.pas @@ -0,0 +1,4141 @@ +{$IFNDEF QSYNREGEXPR} +unit SynRegExpr; +{$ENDIF} + +{ + TRegExpr class library + Delphi Regular Expressions + + Copyright (c) 1999-2004 Andrey V. Sorokin, St.Petersburg, Russia + + You may use this software in any kind of development, + including comercial, redistribute, and modify it freely, + under the following restrictions : + 1. This software is provided as it is, without any kind of + warranty given. Use it at Your own risk.The author is not + responsible for any consequences of use of this software. + 2. The origin of this software may not be mispresented, You + must not claim that You wrote the original software. If + You use this software in any kind of product, it would be + appreciated that there in a information box, or in the + documentation would be an acknowledgement like + + Partial Copyright (c) 2004 Andrey V. Sorokin + http://RegExpStudio.com + mailto:anso@mail.ru + + 3. You may not have any income from distributing this source + (or altered version of it) to other developers. When You + use this product in a comercial package, the source may + not be charged seperatly. + 4. Altered versions must be plainly marked as such, and must + not be misrepresented as being the original software. + 5. RegExp Studio application and all the visual components as + well as documentation is not part of the TRegExpr library + and is not free for usage. + + mailto:anso@mail.ru + http://RegExpStudio.com + http://anso.da.ru/ +} + +interface + +{$IFDEF FPC} + {$MODE Delphi} + {$DEFINE SYN_COMPILER_1_UP} + {$DEFINE SYN_COMPILER_2_UP} + {$DEFINE SYN_COMPILER_3_UP} + {$DEFINE SYN_COMPILER_4_UP} + {$DEFINE SYN_DELPHI_2_UP} + {$DEFINE SYN_DELPHI_3_UP} + {$DEFINE SYN_DELPHI_4_UP} + {$DEFINE SYN_DELPHI_5_UP} + {$DEFINE SYN_LAZARUS} +{$ENDIF} + +// ======== Determine compiler +{$IFDEF VER80} Sorry, TRegExpr is for 32-bits Delphi only. Delphi 1 is not supported (and whos really care today?!). {$ENDIF} +{$IFDEF VER90} {$DEFINE D2} {$ENDIF} // D2 +{$IFDEF VER93} {$DEFINE D2} {$ENDIF} // CPPB 1 +{$IFDEF VER100} {$DEFINE D3} {$DEFINE D2} {$ENDIF} // D3 +{$IFDEF VER110} {$DEFINE D4} {$DEFINE D3} {$DEFINE D2} {$ENDIF} // CPPB 3 +{$IFDEF VER120} {$DEFINE D4} {$DEFINE D3} {$DEFINE D2} {$ENDIF} // D4 +{$IFDEF VER130} {$DEFINE D5} {$DEFINE D4} {$DEFINE D3} {$DEFINE D2} {$ENDIF} // D5 +{$IFDEF VER140} {$DEFINE D6} {$DEFINE D5} {$DEFINE D4} {$DEFINE D3} {$DEFINE D2} {$ENDIF} // D6 +{$IFDEF VER150} {$DEFINE D7} {$DEFINE D6} {$DEFINE D5} {$DEFINE D4} {$DEFINE D3} {$DEFINE D2} {$ENDIF} // D7 + +// ======== Define base compiler options +{$BOOLEVAL OFF} +{$EXTENDEDSYNTAX ON} +{$LONGSTRINGS ON} +{$IFNDEF SYN_LAZARUS} +{$OPTIMIZATION ON} +{$ENDIF} +{$IFDEF D6} + {$WARN SYMBOL_PLATFORM OFF} // Suppress .Net warnings +{$ENDIF} +{$IFDEF D7} + {$WARN UNSAFE_CAST OFF} // Suppress .Net warnings + {$WARN UNSAFE_TYPE OFF} // Suppress .Net warnings + {$WARN UNSAFE_CODE OFF} // Suppress .Net warnings +{$ENDIF} +{$IFDEF FPC} + {$IFNDEF SYN_LAZARUS} + {$MODE DELPHI} // Delphi-compatible mode in FreePascal + {$ENDIF} +{$ENDIF} + +// ======== Define options for TRegExpr engine +{.$DEFINE UniCode} // Unicode support +{$DEFINE RegExpPCodeDump} // p-code dumping (see Dump method) +{$IFNDEF FPC} // the option is not supported in FreePascal + {$DEFINE reRealExceptionAddr} // exceptions will point to appropriate source line, not to Error procedure +{$ENDIF} +{$DEFINE ComplexBraces} // support braces in complex cases +{$IFNDEF UniCode} // the option applicable only for non-UniCode mode + {$DEFINE UseSetOfChar} // Significant optimization by using set of char +{$ENDIF} +{$IFDEF UseSetOfChar} + {$DEFINE UseFirstCharSet} // Fast skip between matches for r.e. that starts with determined set of chars +{$ENDIF} + +// ======== Define Pascal-language options +// Define 'UseAsserts' option (do not edit this definitions). +// Asserts used to catch 'strange bugs' in TRegExpr implementation (when something goes +// completely wrong). You can swith asserts on/off with help of {$C+}/{$C-} compiler options. +{$IFDEF D3} {$DEFINE UseAsserts} {$ENDIF} +{$IFDEF FPC} {$DEFINE UseAsserts} {$ENDIF} + +// Define 'use subroutine parameters default values' option (do not edit this definition). +{$IFDEF D4} {$DEFINE DefParam} {$ENDIF} + +// Define 'OverMeth' options, to use method overloading (do not edit this definitions). +{$IFDEF D5} {$DEFINE OverMeth} {$ENDIF} +{$IFDEF FPC} {$DEFINE OverMeth} {$ENDIF} + +uses + Classes, // TStrings in Split method + SysUtils; // Exception + +type + {$IFDEF UniCode} + PRegExprChar = PWideChar; + RegExprString = WideString; + REChar = WideChar; + {$ELSE} + PRegExprChar = PChar; + RegExprString = AnsiString; //###0.952 was string + REChar = Char; + {$ENDIF} + TREOp = REChar; // internal p-code type //###0.933 + PREOp = ^TREOp; + TRENextOff = integer; // internal Next "pointer" (offset to current p-code) //###0.933 + PRENextOff = ^TRENextOff; // used for extracting Next "pointers" from compiled r.e. //###0.933 + TREBracesArg = integer; // type of {m,n} arguments + PREBracesArg = ^TREBracesArg; + +const + REOpSz = SizeOf (TREOp) div SizeOf (REChar); // size of p-code in RegExprString units + RENextOffSz = SizeOf (TRENextOff) div SizeOf (REChar); // size of Next 'pointer' -"- + REBracesArgSz = SizeOf (TREBracesArg) div SizeOf (REChar); // size of BRACES arguments -"- + +type + TRegExprInvertCaseFunction = function (const Ch : REChar) : REChar + of object; + +const + EscChar = '\'; // 'Escape'-char ('\' in common r.e.) used for escaping metachars (\w, \d etc). + RegExprModifierI : boolean = False; // default value for ModifierI + RegExprModifierR : boolean = True; // default value for ModifierR + RegExprModifierS : boolean = True; // default value for ModifierS + RegExprModifierG : boolean = True; // default value for ModifierG + RegExprModifierM : boolean = False; // default value for ModifierM + RegExprModifierX : boolean = False; // default value for ModifierX + RegExprSpaceChars : RegExprString = // default value for SpaceChars + ' '#$9#$A#$D#$C; + RegExprWordChars : RegExprString = // default value for WordChars + '0123456789' //###0.940 + + 'abcdefghijklmnopqrstuvwxyz' + + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'; + RegExprLineSeparators : RegExprString =// default value for LineSeparators + #$d#$a{$IFDEF UniCode}+#$b#$c#$2028#$2029#$85{$ENDIF}; //###0.947 + RegExprLinePairedSeparator : RegExprString =// default value for LinePairedSeparator + #$d#$a; + { if You need Unix-styled line separators (only \n), then use: + RegExprLineSeparators = #$a; + RegExprLinePairedSeparator = ''; + } + + +const + NSUBEXP = 15; // max number of subexpression //###0.929 + // Cannot be more than NSUBEXPMAX + // Be carefull - don't use values which overflow CLOSE opcode + // (in this case you'll get compiler erorr). + // Big NSUBEXP will cause more slow work and more stack required + NSUBEXPMAX = 255; // Max possible value for NSUBEXP. //###0.945 + // Don't change it! It's defined by internal TRegExpr design. + + MaxBracesArg = $7FFFFFFF - 1; // max value for {n,m} arguments //###0.933 + + {$IFDEF ComplexBraces} + LoopStackMax = 10; // max depth of loops stack //###0.925 + {$ENDIF} + + TinySetLen = 3; + // if range includes more then TinySetLen chars, //###0.934 + // then use full (32 bytes) ANYOFFULL instead of ANYOF[BUT]TINYSET + // !!! Attension ! If you change TinySetLen, you must + // change code marked as "//!!!TinySet" + + +type + +{$IFDEF UseSetOfChar} + PSetOfREChar = ^TSetOfREChar; + TSetOfREChar = set of REChar; +{$ENDIF} + + TRegExpr = class; + + TRegExprReplaceFunction = function (ARegExpr : TRegExpr): string + of object; + + TRegExpr = class + private + startp : array [0 .. NSUBEXP - 1] of PRegExprChar; // founded expr starting points + endp : array [0 .. NSUBEXP - 1] of PRegExprChar; // founded expr end points + + {$IFDEF ComplexBraces} + LoopStack : array [1 .. LoopStackMax] of integer; // state before entering loop + LoopStackIdx : integer; // 0 - out of all loops + {$ENDIF} + + // The "internal use only" fields to pass info from compile + // to execute that permits the execute phase to run lots faster on + // simple cases. + regstart : REChar; // char that must begin a match; '\0' if none obvious + reganch : REChar; // is the match anchored (at beginning-of-line only)? + regmust : PRegExprChar; // string (pointer into program) that match must include, or nil + regmlen : integer; // length of regmust string + // Regstart and reganch permit very fast decisions on suitable starting points + // for a match, cutting down the work a lot. Regmust permits fast rejection + // of lines that cannot possibly match. The regmust tests are costly enough + // that regcomp() supplies a regmust only if the r.e. contains something + // potentially expensive (at present, the only such thing detected is * or + + // at the start of the r.e., which can involve a lot of backup). Regmlen is + // supplied because the test in regexec() needs it and regcomp() is computing + // it anyway. + {$IFDEF UseFirstCharSet} //###0.929 + FirstCharSet : TSetOfREChar; + {$ENDIF} + + // work variables for Exec's routins - save stack in recursion} + reginput : PRegExprChar; // String-input pointer. + fInputStart : PRegExprChar; // Pointer to first char of input string. + fInputEnd : PRegExprChar; // Pointer to char AFTER last char of input string + + // work variables for compiler's routines + regparse : PRegExprChar; // Input-scan pointer. + regnpar : integer; // count. + regdummy : char; + regcode : PRegExprChar; // Code-emit pointer; @regdummy = don't. + regsize : integer; // Code size. + + regexpbeg : PRegExprChar; // only for error handling. Contains + // pointer to beginning of r.e. while compiling + fExprIsCompiled : boolean; // true if r.e. successfully compiled + + // programm is essentially a linear encoding + // of a nondeterministic finite-state machine (aka syntax charts or + // "railroad normal form" in parsing technology). Each node is an opcode + // plus a "next" pointer, possibly plus an operand. "Next" pointers of + // all nodes except BRANCH implement concatenation; a "next" pointer with + // a BRANCH on both ends of it is connecting two alternatives. (Here we + // have one of the subtle syntax dependencies: an individual BRANCH (as + // opposed to a collection of them) is never concatenated with anything + // because of operator precedence.) The operand of some types of node is + // a literal string; for others, it is a node leading into a sub-FSM. In + // particular, the operand of a BRANCH node is the first node of the branch. + // (NB this is *not* a tree structure: the tail of the branch connects + // to the thing following the set of BRANCHes.) The opcodes are: + programm : PRegExprChar; // Unwarranted chumminess with compiler. + + fExpression : PRegExprChar; // source of compiled r.e. + fInputString : PRegExprChar; // input string + + fLastError : integer; // see Error, LastError + + fModifiers : integer; // modifiers + fCompModifiers : integer; // compiler's copy of modifiers + fProgModifiers : integer; // modifiers values from last programm compilation + + fSpaceChars : RegExprString; //###0.927 + fWordChars : RegExprString; //###0.929 + fInvertCase : TRegExprInvertCaseFunction; //###0.927 + + fLineSeparators : RegExprString; //###0.941 + fLinePairedSeparatorAssigned : boolean; + fLinePairedSeparatorHead, + fLinePairedSeparatorTail : REChar; + {$IFNDEF UniCode} + fLineSeparatorsSet : set of REChar; + {$ENDIF} + + procedure InvalidateProgramm; + // Mark programm as have to be [re]compiled + + function IsProgrammOk : boolean; //###0.941 + // Check if we can use precompiled r.e. or + // [re]compile it if something changed + + function GetExpression : RegExprString; + procedure SetExpression (const s : RegExprString); + + function GetModifierStr : RegExprString; + class function ParseModifiersStr (const AModifiers : RegExprString; + var AModifiersInt : integer) : boolean; //###0.941 class function now + // Parse AModifiers string and return true and set AModifiersInt + // if it's in format 'ismxrg-ismxrg'. + procedure SetModifierStr (const AModifiers : RegExprString); + + function GetModifier (AIndex : integer) : boolean; + procedure SetModifier (AIndex : integer; ASet : boolean); + + procedure Error (AErrorID : integer); virtual; // error handler. + // Default handler raise exception ERegExpr with + // Message = ErrorMsg (AErrorID), ErrorCode = AErrorID + // and CompilerErrorPos = value of property CompilerErrorPos. + + + {==================== Compiler section ===================} + function CompileRegExpr (exp : PRegExprChar) : boolean; + // compile a regular expression into internal code + + procedure Tail (p : PRegExprChar; val : PRegExprChar); + // set the next-pointer at the end of a node chain + + procedure OpTail (p : PRegExprChar; val : PRegExprChar); + // regoptail - regtail on operand of first argument; nop if operandless + + function EmitNode (op : TREOp) : PRegExprChar; + // regnode - emit a node, return location + + procedure EmitC (b : REChar); + // emit (if appropriate) a byte of code + + procedure InsertOperator (op : TREOp; opnd : PRegExprChar; sz : integer); //###0.90 + // insert an operator in front of already-emitted operand + // Means relocating the operand. + + function ParseReg (paren : integer; var flagp : integer) : PRegExprChar; + // regular expression, i.e. main body or parenthesized thing + + function ParseBranch (var flagp : integer) : PRegExprChar; + // one alternative of an | operator + + function ParsePiece (var flagp : integer) : PRegExprChar; + // something followed by possible [*+?] + + function ParseAtom (var flagp : integer) : PRegExprChar; + // the lowest level + + function GetCompilerErrorPos : integer; + // current pos in r.e. - for error hanling + + {$IFDEF UseFirstCharSet} //###0.929 + procedure FillFirstCharSet (prog : PRegExprChar); + {$ENDIF} + + {===================== Mathing section ===================} + function regrepeat (p : PRegExprChar; AMax : integer) : integer; + // repeatedly match something simple, report how many + + function regnext (p : PRegExprChar) : PRegExprChar; + // dig the "next" pointer out of a node + + function MatchPrim (prog : PRegExprChar) : boolean; + // recursively matching routine + + function ExecPrim (AOffset: integer) : boolean; + // Exec for stored InputString + + {$IFDEF RegExpPCodeDump} + function DumpOp (op : REChar) : RegExprString; + {$ENDIF} + + function GetSubExprMatchCount : integer; + function GetMatchPos (Idx : integer) : integer; + function GetMatchLen (Idx : integer) : integer; + function GetMatch (Idx : integer) : RegExprString; + + function GetInputString : RegExprString; + procedure SetInputString (const AInputString : RegExprString); + + {$IFNDEF UseSetOfChar} + function StrScanCI (s : PRegExprChar; ch : REChar) : PRegExprChar; //###0.928 + {$ENDIF} + + procedure SetLineSeparators (const AStr : RegExprString); + procedure SetLinePairedSeparator (const AStr : RegExprString); + function GetLinePairedSeparator : RegExprString; + + public + constructor Create; + destructor Destroy; override; + + class function VersionMajor : integer; //###0.944 + class function VersionMinor : integer; //###0.944 + + property Expression : RegExprString read GetExpression write SetExpression; + // Regular expression. + // For optimization, TRegExpr will automatically compiles it into 'P-code' + // (You can see it with help of Dump method) and stores in internal + // structures. Real [re]compilation occures only when it really needed - + // while calling Exec[Next], Substitute, Dump, etc + // and only if Expression or other P-code affected properties was changed + // after last [re]compilation. + // If any errors while [re]compilation occures, Error method is called + // (by default Error raises exception - see below) + + property ModifierStr : RegExprString read GetModifierStr write SetModifierStr; + // Set/get default values of r.e.syntax modifiers. Modifiers in + // r.e. (?ismx-ismx) will replace this default values. + // If you try to set unsupported modifier, Error will be called + // (by defaul Error raises exception ERegExpr). + + property ModifierI : boolean index 1 read GetModifier write SetModifier; + // Modifier /i - caseinsensitive, initialized from RegExprModifierI + + property ModifierR : boolean index 2 read GetModifier write SetModifier; + // Modifier /r - use r.e.syntax extended for russian, + // (was property ExtSyntaxEnabled in previous versions) + // If true, then а-я additional include russian letter 'ё', + // А-Я additional include 'Ё', and а-Я include all russian symbols. + // You have to turn it off if it may interfere with you national alphabet. + // , initialized from RegExprModifierR + + property ModifierS : boolean index 3 read GetModifier write SetModifier; + // Modifier /s - '.' works as any char (else as [^\n]), + // , initialized from RegExprModifierS + + property ModifierG : boolean index 4 read GetModifier write SetModifier; + // Switching off modifier /g switchs all operators in + // non-greedy style, so if ModifierG = False, then + // all '*' works as '*?', all '+' as '+?' and so on. + // , initialized from RegExprModifierG + + property ModifierM : boolean index 5 read GetModifier write SetModifier; + // Treat string as multiple lines. That is, change `^' and `$' from + // matching at only the very start or end of the string to the start + // or end of any line anywhere within the string. + // , initialized from RegExprModifierM + + property ModifierX : boolean index 6 read GetModifier write SetModifier; + // Modifier /x - eXtended syntax, allow r.e. text formatting, + // see description in the help. Initialized from RegExprModifierX + + function Exec (const AInputString : RegExprString) : boolean; {$IFDEF OverMeth} overload; + {$IFNDEF FPC} // I do not know why FreePascal cannot overload methods with empty param list + function Exec : boolean; overload; //###0.949 + {$ENDIF} + function Exec (AOffset: integer) : boolean; overload; //###0.949 + {$ENDIF} + // match a programm against a string AInputString + // !!! Exec store AInputString into InputString property + // For Delphi 5 and higher available overloaded versions - first without + // parameter (uses already assigned to InputString property value) + // and second that has integer parameter and is same as ExecPos + + function ExecNext : boolean; + // find next match: + // ExecNext; + // works same as + // if MatchLen [0] = 0 then ExecPos (MatchPos [0] + 1) + // else ExecPos (MatchPos [0] + MatchLen [0]); + // but it's more simpler ! + // Raises exception if used without preceeding SUCCESSFUL call to + // Exec* (Exec, ExecPos, ExecNext). So You always must use something like + // if Exec (InputString) then repeat { proceed results} until not ExecNext; + + function ExecPos (AOffset: integer {$IFDEF DefParam}= 1{$ENDIF}) : boolean; + // find match for InputString starting from AOffset position + // (AOffset=1 - first char of InputString) + + property InputString : RegExprString read GetInputString write SetInputString; + // returns current input string (from last Exec call or last assign + // to this property). + // Any assignment to this property clear Match* properties ! + + function Substitute (const ATemplate : RegExprString) : RegExprString; + // Returns ATemplate with '$&' or '$0' replaced by whole r.e. + // occurence and '$n' replaced by occurence of subexpression #n. + // Since v.0.929 '$' used instead of '\' (for future extensions + // and for more Perl-compatibility) and accept more then one digit. + // If you want place into template raw '$' or '\', use prefix '\' + // Example: '1\$ is $2\\rub\\' -> '1$ is <Match[2]>\rub\' + // If you want to place raw digit after '$n' you must delimit + // n with curly braces '{}'. + // Example: 'a$12bc' -> 'a<Match[12]>bc' + // 'a${1}2bc' -> 'a<Match[1]>2bc'. + + procedure Split (AInputStr : RegExprString; APieces : TStrings); + // Split AInputStr into APieces by r.e. occurencies + // Internally calls Exec[Next] + + function Replace (AInputStr : RegExprString; + const AReplaceStr : RegExprString; + AUseSubstitution : boolean{$IFDEF DefParam}= False{$ENDIF}) //###0.946 + : RegExprString; {$IFDEF OverMeth} overload; + function Replace (AInputStr : RegExprString; + AReplaceFunc : TRegExprReplaceFunction) + : RegExprString; overload; + {$ENDIF} + function ReplaceEx (AInputStr : RegExprString; + AReplaceFunc : TRegExprReplaceFunction) + : RegExprString; + // Returns AInputStr with r.e. occurencies replaced by AReplaceStr + // If AUseSubstitution is true, then AReplaceStr will be used + // as template for Substitution methods. + // For example: + // Expression := '({-i}block|var)\s*\(\s*([^ ]*)\s*\)\s*'; + // Replace ('BLOCK( test1)', 'def "$1" value "$2"', True); + // will return: def 'BLOCK' value 'test1' + // Replace ('BLOCK( test1)', 'def "$1" value "$2"') + // will return: def "$1" value "$2" + // Internally calls Exec[Next] + // Overloaded version and ReplaceEx operate with call-back function, + // so You can implement really complex functionality. + + property SubExprMatchCount : integer read GetSubExprMatchCount; + // Number of subexpressions has been found in last Exec* call. + // If there are no subexpr. but whole expr was found (Exec* returned True), + // then SubExprMatchCount=0, if no subexpressions nor whole + // r.e. found (Exec* returned false) then SubExprMatchCount=-1. + // Note, that some subexpr. may be not found and for such + // subexpr. MathPos=MatchLen=-1 and Match=''. + // For example: Expression := '(1)?2(3)?'; + // Exec ('123'): SubExprMatchCount=2, Match[0]='123', [1]='1', [2]='3' + // Exec ('12'): SubExprMatchCount=1, Match[0]='12', [1]='1' + // Exec ('23'): SubExprMatchCount=2, Match[0]='23', [1]='', [2]='3' + // Exec ('2'): SubExprMatchCount=0, Match[0]='2' + // Exec ('7') - return False: SubExprMatchCount=-1 + + property MatchPos [Idx : integer] : integer read GetMatchPos; + // pos of entrance subexpr. #Idx into tested in last Exec* + // string. First subexpr. have Idx=1, last - MatchCount, + // whole r.e. have Idx=0. + // Returns -1 if in r.e. no such subexpr. or this subexpr. + // not found in input string. + + property MatchLen [Idx : integer] : integer read GetMatchLen; + // len of entrance subexpr. #Idx r.e. into tested in last Exec* + // string. First subexpr. have Idx=1, last - MatchCount, + // whole r.e. have Idx=0. + // Returns -1 if in r.e. no such subexpr. or this subexpr. + // not found in input string. + // Remember - MatchLen may be 0 (if r.e. match empty string) ! + + property Match [Idx : integer] : RegExprString read GetMatch; + // == copy (InputString, MatchPos [Idx], MatchLen [Idx]) + // Returns '' if in r.e. no such subexpr. or this subexpr. + // not found in input string. + + function LastError : integer; + // Returns ID of last error, 0 if no errors (unusable if + // Error method raises exception) and clear internal status + // into 0 (no errors). + + function ErrorMsg (AErrorID : integer) : RegExprString; virtual; + // Returns Error message for error with ID = AErrorID. + + property CompilerErrorPos : integer read GetCompilerErrorPos; + // Returns pos in r.e. there compiler stopped. + // Useful for error diagnostics + + property SpaceChars : RegExprString read fSpaceChars write fSpaceChars; //###0.927 + // Contains chars, treated as /s (initially filled with RegExprSpaceChars + // global constant) + + property WordChars : RegExprString read fWordChars write fWordChars; //###0.929 + // Contains chars, treated as /w (initially filled with RegExprWordChars + // global constant) + + property LineSeparators : RegExprString read fLineSeparators write SetLineSeparators; //###0.941 + // line separators (like \n in Unix) + + property LinePairedSeparator : RegExprString read GetLinePairedSeparator write SetLinePairedSeparator; //###0.941 + // paired line separator (like \r\n in DOS and Windows). + // must contain exactly two chars or no chars at all + + class function InvertCaseFunction (const Ch : REChar) : REChar; + // Converts Ch into upper case if it in lower case or in lower + // if it in upper (uses current system local setings) + + property InvertCase : TRegExprInvertCaseFunction read fInvertCase write fInvertCase; //##0.935 + // Set this property if you want to override case-insensitive functionality. + // Create set it to RegExprInvertCaseFunction (InvertCaseFunction by default) + + procedure Compile; //###0.941 + // [Re]compile r.e. Useful for example for GUI r.e. editors (to check + // all properties validity). + + {$IFDEF RegExpPCodeDump} + function Dump : RegExprString; + // dump a compiled regexp in vaguely comprehensible form + {$ENDIF} + end; + + ERegExpr = class (Exception) + public + ErrorCode : integer; + CompilerErrorPos : integer; + end; + +const + RegExprInvertCaseFunction : TRegExprInvertCaseFunction = {$IFDEF FPC} nil {$ELSE} TRegExpr.InvertCaseFunction{$ENDIF}; + // defaul for InvertCase property + +function ExecRegExpr (const ARegExpr, AInputStr : RegExprString) : boolean; +// true if string AInputString match regular expression ARegExpr +// ! will raise exeption if syntax errors in ARegExpr + +procedure SplitRegExpr (const ARegExpr, AInputStr : RegExprString; APieces : TStrings); +// Split AInputStr into APieces by r.e. ARegExpr occurencies + +function ReplaceRegExpr (const ARegExpr, AInputStr, AReplaceStr : RegExprString; + AUseSubstitution : boolean{$IFDEF DefParam}= False{$ENDIF}) : RegExprString; //###0.947 +// Returns AInputStr with r.e. occurencies replaced by AReplaceStr +// If AUseSubstitution is true, then AReplaceStr will be used +// as template for Substitution methods. +// For example: +// ReplaceRegExpr ('({-i}block|var)\s*\(\s*([^ ]*)\s*\)\s*', +// 'BLOCK( test1)', 'def "$1" value "$2"', True) +// will return: def 'BLOCK' value 'test1' +// ReplaceRegExpr ('({-i}block|var)\s*\(\s*([^ ]*)\s*\)\s*', +// 'BLOCK( test1)', 'def "$1" value "$2"') +// will return: def "$1" value "$2" + +function QuoteRegExprMetaChars (const AStr : RegExprString) : RegExprString; +// Replace all metachars with its safe representation, +// for example 'abc$cd.(' converts into 'abc\$cd\.\(' +// This function useful for r.e. autogeneration from +// user input + +function RegExprSubExpressions (const ARegExpr : string; + ASubExprs : TStrings; AExtendedSyntax : boolean{$IFDEF DefParam}= False{$ENDIF}) : integer; +// Makes list of subexpressions found in ARegExpr r.e. +// In ASubExps every item represent subexpression, +// from first to last, in format: +// String - subexpression text (without '()') +// low word of Object - starting position in ARegExpr, including '(' +// if exists! (first position is 1) +// high word of Object - length, including starting '(' and ending ')' +// if exist! +// AExtendedSyntax - must be True if modifier /m will be On while +// using the r.e. +// Useful for GUI editors of r.e. etc (You can find example of using +// in TestRExp.dpr project) +// Returns +// 0 Success. No unbalanced brackets was found; +// -1 There are not enough closing brackets ')'; +// -(n+1) At position n was found opening '[' without //###0.942 +// corresponding closing ']'; +// n At position n was found closing bracket ')' without +// corresponding opening '('. +// If Result <> 0, then ASubExpr can contain empty items or illegal ones + + +implementation + +{$IFDEF SYN_LAZARUS} +{$ELSE} +uses +{$IFDEF SYN_WIN32} + Windows; // CharUpper/Lower +{$ELSE} + Libc; //Qt.pas from Borland does not expose char handling functions +{$ENDIF} +{$ENDIF} + +const + TRegExprVersionMajor : integer = 0; + TRegExprVersionMinor : integer = 952; + // TRegExpr.VersionMajor/Minor return values of this constants + + MaskModI = 1; // modifier /i bit in fModifiers + MaskModR = 2; // -"- /r + MaskModS = 4; // -"- /s + MaskModG = 8; // -"- /g + MaskModM = 16; // -"- /m + MaskModX = 32; // -"- /x + + {$IFDEF UniCode} + XIgnoredChars = ' '#9#$d#$a; + {$ELSE} + XIgnoredChars = [' ', #9, #$d, #$a]; + {$ENDIF} + +{=============================================================} +{=================== WideString functions ====================} +{=============================================================} + +{$IFDEF UniCode} + +function StrPCopy (Dest: PRegExprChar; const Source: RegExprString): PRegExprChar; + var + i, Len : Integer; + begin + Len := length (Source); //###0.932 + for i := 1 to Len do + Dest [i - 1] := Source [i]; + Dest [Len] := #0; + Result := Dest; + end; { of function StrPCopy +--------------------------------------------------------------} + +function StrLCopy (Dest, Source: PRegExprChar; MaxLen: Cardinal): PRegExprChar; + var i: Integer; + begin + for i := 0 to MaxLen - 1 do + Dest [i] := Source [i]; + Result := Dest; + end; { of function StrLCopy +--------------------------------------------------------------} + +function StrLen (Str: PRegExprChar): Cardinal; + begin + Result:=0; + while Str [result] <> #0 + do Inc (Result); + end; { of function StrLen +--------------------------------------------------------------} + +function StrPos (Str1, Str2: PRegExprChar): PRegExprChar; + var n: Integer; + begin + Result := nil; + n := Pos (RegExprString (Str2), RegExprString (Str1)); + if n = 0 + then EXIT; + Result := Str1 + n - 1; + end; { of function StrPos +--------------------------------------------------------------} + +function StrLComp (Str1, Str2: PRegExprChar; MaxLen: Cardinal): Integer; + var S1, S2: RegExprString; + begin + S1 := Str1; + S2 := Str2; + if Copy (S1, 1, MaxLen) > Copy (S2, 1, MaxLen) + then Result := 1 + else + if Copy (S1, 1, MaxLen) < Copy (S2, 1, MaxLen) + then Result := -1 + else Result := 0; + end; { function StrLComp +--------------------------------------------------------------} + +function StrScan (Str: PRegExprChar; Chr: WideChar): PRegExprChar; + begin + Result := nil; + while (Str^ <> #0) and (Str^ <> Chr) + do Inc (Str); + if (Str^ <> #0) + then Result := Str; + end; { of function StrScan +--------------------------------------------------------------} + +{$ENDIF} + + +{=============================================================} +{===================== Global functions ======================} +{=============================================================} + +function ExecRegExpr (const ARegExpr, AInputStr : RegExprString) : boolean; + var r : TRegExpr; + begin + r := TRegExpr.Create; + try + r.Expression := ARegExpr; + Result := r.Exec (AInputStr); + finally r.Free; + end; + end; { of function ExecRegExpr +--------------------------------------------------------------} + +procedure SplitRegExpr (const ARegExpr, AInputStr : RegExprString; APieces : TStrings); + var r : TRegExpr; + begin + APieces.Clear; + r := TRegExpr.Create; + try + r.Expression := ARegExpr; + r.Split (AInputStr, APieces); + finally r.Free; + end; + end; { of procedure SplitRegExpr +--------------------------------------------------------------} + +function ReplaceRegExpr (const ARegExpr, AInputStr, AReplaceStr : RegExprString; + AUseSubstitution : boolean{$IFDEF DefParam}= False{$ENDIF}) : RegExprString; + begin + with TRegExpr.Create do try + Expression := ARegExpr; + Result := Replace (AInputStr, AReplaceStr, AUseSubstitution); + finally Free; + end; + end; { of function ReplaceRegExpr +--------------------------------------------------------------} + +function QuoteRegExprMetaChars (const AStr : RegExprString) : RegExprString; + const + RegExprMetaSet : RegExprString = '^$.[()|?+*'+EscChar+'{' + + ']}'; // - this last are additional to META. + // Very similar to META array, but slighly changed. + // !Any changes in META array must be synchronized with this set. + var + i, i0, Len : integer; + begin + Result := ''; + Len := length (AStr); + i := 1; + i0 := i; + while i <= Len do begin + if Pos (AStr [i], RegExprMetaSet) > 0 then begin + Result := Result + System.Copy (AStr, i0, i - i0) + + EscChar + AStr [i]; + i0 := i + 1; + end; + inc (i); + end; + Result := Result + System.Copy (AStr, i0, MaxInt); // Tail + end; { of function QuoteRegExprMetaChars +--------------------------------------------------------------} + +function RegExprSubExpressions (const ARegExpr : string; + ASubExprs : TStrings; AExtendedSyntax : boolean{$IFDEF DefParam}= False{$ENDIF}) : integer; + type + TStackItemRec = record //###0.945 + SubExprIdx : integer; + StartPos : integer; + end; + TStackArray = packed array [0 .. NSUBEXPMAX - 1] of TStackItemRec; + var + Len, SubExprLen : integer; + i, i0 : integer; + Modif : integer; + Stack : ^TStackArray; //###0.945 + StackIdx, StackSz : integer; + begin + Result := 0; // no unbalanced brackets found at this very moment + + ASubExprs.Clear; // I don't think that adding to non empty list + // can be useful, so I simplified algorithm to work only with empty list + + Len := length (ARegExpr); // some optimization tricks + + // first we have to calculate number of subexpression to reserve + // space in Stack array (may be we'll reserve more then need, but + // it's faster then memory reallocation during parsing) + StackSz := 1; // add 1 for entire r.e. + for i := 1 to Len do + if ARegExpr [i] = '(' + then inc (StackSz); +// SetLength (Stack, StackSz); //###0.945 + GetMem (Stack, SizeOf (TStackItemRec) * StackSz); + try + + StackIdx := 0; + i := 1; + while (i <= Len) do begin + case ARegExpr [i] of + '(': begin + if (i < Len) and (ARegExpr [i + 1] = '?') then begin + // this is not subexpression, but comment or other + // Perl extension. We must check is it (?ismxrg-ismxrg) + // and change AExtendedSyntax if /x is changed. + inc (i, 2); // skip '(?' + i0 := i; + while (i <= Len) and (ARegExpr [i] <> ')') + do inc (i); + if i > Len + then Result := -1 // unbalansed '(' + else + if TRegExpr.ParseModifiersStr (System.Copy (ARegExpr, i, i - i0), Modif) + then AExtendedSyntax := (Modif and MaskModX) <> 0; + end + else begin // subexpression starts + ASubExprs.Add (''); // just reserve space + with Stack [StackIdx] do begin + SubExprIdx := ASubExprs.Count - 1; + StartPos := i; + end; + inc (StackIdx); + end; + end; + ')': begin + if StackIdx = 0 + then Result := i // unbalanced ')' + else begin + dec (StackIdx); + with Stack [StackIdx] do begin + SubExprLen := i - StartPos + 1; + ASubExprs.Objects [SubExprIdx] := + TObject (StartPos or (SubExprLen ShL 16)); + ASubExprs [SubExprIdx] := System.Copy ( + ARegExpr, StartPos + 1, SubExprLen - 2); // add without brackets + end; + end; + end; + EscChar: inc (i); // skip quoted symbol + '[': begin + // we have to skip character ranges at once, because they can + // contain '#', and '#' in it must NOT be recognized as eXtended + // comment beginning! + i0 := i; + inc (i); + if ARegExpr [i] = ']' // cannot be 'emty' ranges - this interpretes + then inc (i); // as ']' by itself + while (i <= Len) and (ARegExpr [i] <> ']') do + if ARegExpr [i] = EscChar //###0.942 + then inc (i, 2) // skip 'escaped' char to prevent stopping at '\]' + else inc (i); + if (i > Len) or (ARegExpr [i] <> ']') //###0.942 + then Result := - (i0 + 1); // unbalansed '[' //###0.942 + end; + '#': if AExtendedSyntax then begin + // skip eXtended comments + while (i <= Len) and (ARegExpr [i] <> #$d) and (ARegExpr [i] <> #$a) + // do not use [#$d, #$a] due to UniCode compatibility + do inc (i); + while (i + 1 <= Len) and ((ARegExpr [i + 1] = #$d) or (ARegExpr [i + 1] = #$a)) + do inc (i); // attempt to work with different kinds of line separators + // now we are at the line separator that must be skipped. + end; + // here is no 'else' clause - we simply skip ordinary chars + end; // of case + inc (i); // skip scanned char + // ! can move after Len due to skipping quoted symbol + end; + + // check brackets balance + if StackIdx <> 0 + then Result := -1; // unbalansed '(' + + // check if entire r.e. added + if (ASubExprs.Count = 0) + or ((PtrInt (ASubExprs.Objects [0]) and $FFFF) <> 1) + or (((PtrInt (ASubExprs.Objects [0]) ShR 16) and $FFFF) <> Len) + // whole r.e. wasn't added because it isn't bracketed + // well, we add it now: + then ASubExprs.InsertObject (0, ARegExpr, TObject ((Len ShL 16) or 1)); + + finally FreeMem (Stack); + end; + end; { of function RegExprSubExpressions +--------------------------------------------------------------} + + + +const + MAGIC = TREOp (216);// programm signature + +// name opcode opnd? meaning + EEND = TREOp (0); // - End of program + BOL = TREOp (1); // - Match "" at beginning of line + EOL = TREOp (2); // - Match "" at end of line + ANY = TREOp (3); // - Match any one character + ANYOF = TREOp (4); // Str Match any character in string Str + ANYBUT = TREOp (5); // Str Match any char. not in string Str + BRANCH = TREOp (6); // Node Match this alternative, or the next + BACK = TREOp (7); // - Jump backward (Next < 0) + EXACTLY = TREOp (8); // Str Match string Str + NOTHING = TREOp (9); // - Match empty string + STAR = TREOp (10); // Node Match this (simple) thing 0 or more times + PLUS = TREOp (11); // Node Match this (simple) thing 1 or more times + ANYDIGIT = TREOp (12); // - Match any digit (equiv [0-9]) + NOTDIGIT = TREOp (13); // - Match not digit (equiv [0-9]) + ANYLETTER = TREOp (14); // - Match any letter from property WordChars + NOTLETTER = TREOp (15); // - Match not letter from property WordChars + ANYSPACE = TREOp (16); // - Match any space char (see property SpaceChars) + NOTSPACE = TREOp (17); // - Match not space char (see property SpaceChars) + BRACES = TREOp (18); // Node,Min,Max Match this (simple) thing from Min to Max times. + // Min and Max are TREBracesArg + COMMENT = TREOp (19); // - Comment ;) + EXACTLYCI = TREOp (20); // Str Match string Str case insensitive + ANYOFCI = TREOp (21); // Str Match any character in string Str, case insensitive + ANYBUTCI = TREOp (22); // Str Match any char. not in string Str, case insensitive + LOOPENTRY = TREOp (23); // Node Start of loop (Node - LOOP for this loop) + LOOP = TREOp (24); // Node,Min,Max,LoopEntryJmp - back jump for LOOPENTRY. + // Min and Max are TREBracesArg + // Node - next node in sequence, + // LoopEntryJmp - associated LOOPENTRY node addr + ANYOFTINYSET= TREOp (25); // Chrs Match any one char from Chrs (exactly TinySetLen chars) + ANYBUTTINYSET=TREOp (26); // Chrs Match any one char not in Chrs (exactly TinySetLen chars) + ANYOFFULLSET= TREOp (27); // Set Match any one char from set of char + // - very fast (one CPU instruction !) but takes 32 bytes of p-code + BSUBEXP = TREOp (28); // Idx Match previously matched subexpression #Idx (stored as REChar) //###0.936 + BSUBEXPCI = TREOp (29); // Idx -"- in case-insensitive mode + + // Non-Greedy Style Ops //###0.940 + STARNG = TREOp (30); // Same as START but in non-greedy mode + PLUSNG = TREOp (31); // Same as PLUS but in non-greedy mode + BRACESNG = TREOp (32); // Same as BRACES but in non-greedy mode + LOOPNG = TREOp (33); // Same as LOOP but in non-greedy mode + + // Multiline mode \m + BOLML = TREOp (34); // - Match "" at beginning of line + EOLML = TREOp (35); // - Match "" at end of line + ANYML = TREOp (36); // - Match any one character + + // Word boundary + BOUND = TREOp (37); // Match "" between words //###0.943 + NOTBOUND = TREOp (38); // Match "" not between words //###0.943 + + // !!! Change OPEN value if you add new opcodes !!! + + OPEN = TREOp (39); // - Mark this point in input as start of \n + // OPEN + 1 is \1, etc. + CLOSE = TREOp (ord (OPEN) + NSUBEXP); + // - Analogous to OPEN. + + // !!! Don't add new OpCodes after CLOSE !!! + +// We work with p-code thru pointers, compatible with PRegExprChar. +// Note: all code components (TRENextOff, TREOp, TREBracesArg, etc) +// must have lengths that can be divided by SizeOf (REChar) ! +// A node is TREOp of opcode followed Next "pointer" of TRENextOff type. +// The Next is a offset from the opcode of the node containing it. +// An operand, if any, simply follows the node. (Note that much of +// the code generation knows about this implicit relationship!) +// Using TRENextOff=integer speed up p-code processing. + +// Opcodes description: +// +// BRANCH The set of branches constituting a single choice are hooked +// together with their "next" pointers, since precedence prevents +// anything being concatenated to any individual branch. The +// "next" pointer of the last BRANCH in a choice points to the +// thing following the whole choice. This is also where the +// final "next" pointer of each individual branch points; each +// branch starts with the operand node of a BRANCH node. +// BACK Normal "next" pointers all implicitly point forward; BACK +// exists to make loop structures possible. +// STAR,PLUS,BRACES '?', and complex '*' and '+', are implemented as +// circular BRANCH structures using BACK. Complex '{min,max}' +// - as pair LOOPENTRY-LOOP (see below). Simple cases (one +// character per match) are implemented with STAR, PLUS and +// BRACES for speed and to minimize recursive plunges. +// LOOPENTRY,LOOP {min,max} are implemented as special pair +// LOOPENTRY-LOOP. Each LOOPENTRY initialize loopstack for +// current level. +// OPEN,CLOSE are numbered at compile time. + + +{=============================================================} +{================== Error handling section ===================} +{=============================================================} + +const + reeOk = 0; + reeCompNullArgument = 100; + reeCompRegexpTooBig = 101; + reeCompParseRegTooManyBrackets = 102; + reeCompParseRegUnmatchedBrackets = 103; + reeCompParseRegUnmatchedBrackets2 = 104; + reeCompParseRegJunkOnEnd = 105; + reePlusStarOperandCouldBeEmpty = 106; + reeNestedSQP = 107; + reeBadHexDigit = 108; + reeInvalidRange = 109; + reeParseAtomTrailingBackSlash = 110; + reeNoHexCodeAfterBSlashX = 111; + reeHexCodeAfterBSlashXTooBig = 112; + reeUnmatchedSqBrackets = 113; + reeInternalUrp = 114; + reeQPSBFollowsNothing = 115; + reeTrailingBackSlash = 116; + reeRarseAtomInternalDisaster = 119; + reeBRACESArgTooBig = 122; + reeBracesMinParamGreaterMax = 124; + reeUnclosedComment = 125; + reeComplexBracesNotImplemented = 126; + reeUrecognizedModifier = 127; + reeBadLinePairedSeparator = 128; + reeRegRepeatCalledInappropriately = 1000; + reeMatchPrimMemoryCorruption = 1001; + reeMatchPrimCorruptedPointers = 1002; + reeNoExpression = 1003; + reeCorruptedProgram = 1004; + reeNoInpitStringSpecified = 1005; + reeOffsetMustBeGreaterThen0 = 1006; + reeExecNextWithoutExec = 1007; + reeGetInputStringWithoutInputString = 1008; + reeDumpCorruptedOpcode = 1011; + reeModifierUnsupported = 1013; + reeLoopStackExceeded = 1014; + reeLoopWithoutEntry = 1015; + reeBadPCodeImported = 2000; + +function TRegExpr.ErrorMsg (AErrorID : integer) : RegExprString; + begin + case AErrorID of + reeOk: Result := 'No errors'; + reeCompNullArgument: Result := 'TRegExpr(comp): Null Argument'; + reeCompRegexpTooBig: Result := 'TRegExpr(comp): Regexp Too Big'; + reeCompParseRegTooManyBrackets: Result := 'TRegExpr(comp): ParseReg Too Many ()'; + reeCompParseRegUnmatchedBrackets: Result := 'TRegExpr(comp): ParseReg Unmatched ()'; + reeCompParseRegUnmatchedBrackets2: Result := 'TRegExpr(comp): ParseReg Unmatched ()'; + reeCompParseRegJunkOnEnd: Result := 'TRegExpr(comp): ParseReg Junk On End'; + reePlusStarOperandCouldBeEmpty: Result := 'TRegExpr(comp): *+ Operand Could Be Empty'; + reeNestedSQP: Result := 'TRegExpr(comp): Nested *?+'; + reeBadHexDigit: Result := 'TRegExpr(comp): Bad Hex Digit'; + reeInvalidRange: Result := 'TRegExpr(comp): Invalid [] Range'; + reeParseAtomTrailingBackSlash: Result := 'TRegExpr(comp): Parse Atom Trailing \'; + reeNoHexCodeAfterBSlashX: Result := 'TRegExpr(comp): No Hex Code After \x'; + reeHexCodeAfterBSlashXTooBig: Result := 'TRegExpr(comp): Hex Code After \x Is Too Big'; + reeUnmatchedSqBrackets: Result := 'TRegExpr(comp): Unmatched []'; + reeInternalUrp: Result := 'TRegExpr(comp): Internal Urp'; + reeQPSBFollowsNothing: Result := 'TRegExpr(comp): ?+*{ Follows Nothing'; + reeTrailingBackSlash: Result := 'TRegExpr(comp): Trailing \'; + reeRarseAtomInternalDisaster: Result := 'TRegExpr(comp): RarseAtom Internal Disaster'; + reeBRACESArgTooBig: Result := 'TRegExpr(comp): BRACES Argument Too Big'; + reeBracesMinParamGreaterMax: Result := 'TRegExpr(comp): BRACE Min Param Greater then Max'; + reeUnclosedComment: Result := 'TRegExpr(comp): Unclosed (?#Comment)'; + reeComplexBracesNotImplemented: Result := 'TRegExpr(comp): If you want take part in beta-testing BRACES ''{min,max}'' and non-greedy ops ''*?'', ''+?'', ''??'' for complex cases - remove ''.'' from {.$DEFINE ComplexBraces}'; + reeUrecognizedModifier: Result := 'TRegExpr(comp): Urecognized Modifier'; + reeBadLinePairedSeparator: Result := 'TRegExpr(comp): LinePairedSeparator must countain two different chars or no chars at all'; + + reeRegRepeatCalledInappropriately: Result := 'TRegExpr(exec): RegRepeat Called Inappropriately'; + reeMatchPrimMemoryCorruption: Result := 'TRegExpr(exec): MatchPrim Memory Corruption'; + reeMatchPrimCorruptedPointers: Result := 'TRegExpr(exec): MatchPrim Corrupted Pointers'; + reeNoExpression: Result := 'TRegExpr(exec): Not Assigned Expression Property'; + reeCorruptedProgram: Result := 'TRegExpr(exec): Corrupted Program'; + reeNoInpitStringSpecified: Result := 'TRegExpr(exec): No Input String Specified'; + reeOffsetMustBeGreaterThen0: Result := 'TRegExpr(exec): Offset Must Be Greater Then 0'; + reeExecNextWithoutExec: Result := 'TRegExpr(exec): ExecNext Without Exec[Pos]'; + reeGetInputStringWithoutInputString: Result := 'TRegExpr(exec): GetInputString Without InputString'; + reeDumpCorruptedOpcode: Result := 'TRegExpr(dump): Corrupted Opcode'; + reeLoopStackExceeded: Result := 'TRegExpr(exec): Loop Stack Exceeded'; + reeLoopWithoutEntry: Result := 'TRegExpr(exec): Loop Without LoopEntry !'; + + reeBadPCodeImported: Result := 'TRegExpr(misc): Bad p-code imported'; + else Result := 'Unknown error'; + end; + end; { of procedure TRegExpr.Error +--------------------------------------------------------------} + +function TRegExpr.LastError : integer; + begin + Result := fLastError; + fLastError := reeOk; + end; { of function TRegExpr.LastError +--------------------------------------------------------------} + + +{=============================================================} +{===================== Common section ========================} +{=============================================================} + +class function TRegExpr.VersionMajor : integer; //###0.944 + begin + Result := TRegExprVersionMajor; + end; { of class function TRegExpr.VersionMajor +--------------------------------------------------------------} + +class function TRegExpr.VersionMinor : integer; //###0.944 + begin + Result := TRegExprVersionMinor; + end; { of class function TRegExpr.VersionMinor +--------------------------------------------------------------} + +constructor TRegExpr.Create; + begin + inherited; + programm := nil; + fExpression := nil; + fInputString := nil; + + regexpbeg := nil; + fExprIsCompiled := false; + + ModifierI := RegExprModifierI; + ModifierR := RegExprModifierR; + ModifierS := RegExprModifierS; + ModifierG := RegExprModifierG; + ModifierM := RegExprModifierM; //###0.940 + + SpaceChars := RegExprSpaceChars; //###0.927 + WordChars := RegExprWordChars; //###0.929 + fInvertCase := RegExprInvertCaseFunction; //###0.927 + + fLineSeparators := RegExprLineSeparators; //###0.941 + LinePairedSeparator := RegExprLinePairedSeparator; //###0.941 + end; { of constructor TRegExpr.Create +--------------------------------------------------------------} + +destructor TRegExpr.Destroy; + begin + if programm <> nil + then FreeMem (programm); + if fExpression <> nil + then FreeMem (fExpression); + if fInputString <> nil + then FreeMem (fInputString); + end; { of destructor TRegExpr.Destroy +--------------------------------------------------------------} + +class function TRegExpr.InvertCaseFunction (const Ch : REChar) : REChar; + begin + {$IFDEF UniCode} + if Ch >= #128 + then Result := Ch + else + {$ENDIF} + begin + Result := {$IFDEF FPC}AnsiUpperCase (Ch) [1]{$ELSE} {$IFDEF SYN_WIN32}REChar (CharUpper (PChar (Ch))){$ELSE}REChar (toupper (integer (Ch))){$ENDIF} {$ENDIF}; + if Result = Ch + then Result := {$IFDEF FPC}AnsiLowerCase (Ch) [1]{$ELSE} {$IFDEF SYN_WIN32}REChar (CharLower (PChar (Ch))){$ELSE}REChar(tolower (integer (Ch))){$ENDIF} {$ENDIF}; + end; + end; { of function TRegExpr.InvertCaseFunction +--------------------------------------------------------------} + +function TRegExpr.GetExpression : RegExprString; + begin + if fExpression <> nil + then Result := fExpression + else Result := ''; + end; { of function TRegExpr.GetExpression +--------------------------------------------------------------} + +procedure TRegExpr.SetExpression (const s : RegExprString); + var + Len : integer; //###0.950 + begin + if (s <> fExpression) or not fExprIsCompiled then begin + fExprIsCompiled := false; + if fExpression <> nil then begin + FreeMem (fExpression); + fExpression := nil; + end; + if s <> '' then begin + Len := length (s); //###0.950 + GetMem (fExpression, (Len + 1) * SizeOf (REChar)); +// StrPCopy (fExpression, s); //###0.950 replaced due to StrPCopy limitation of 255 chars + {$IFDEF UniCode} + StrPCopy (fExpression, Copy (s, 1, Len)); //###0.950 + {$ELSE} + StrLCopy (fExpression, PRegExprChar (s), Len); //###0.950 + {$ENDIF UniCode} + + InvalidateProgramm; //###0.941 + end; + end; + end; { of procedure TRegExpr.SetExpression +--------------------------------------------------------------} + +function TRegExpr.GetSubExprMatchCount : integer; + begin + if Assigned (fInputString) then begin + Result := NSUBEXP - 1; + while (Result > 0) and ((startp [Result] = nil) + or (endp [Result] = nil)) + do dec (Result); + end + else Result := -1; + end; { of function TRegExpr.GetSubExprMatchCount +--------------------------------------------------------------} + +function TRegExpr.GetMatchPos (Idx : integer) : integer; + begin + if (Idx >= 0) and (Idx < NSUBEXP) and Assigned (fInputString) + and Assigned (startp [Idx]) and Assigned (endp [Idx]) then begin + Result := (startp [Idx] - fInputString) + 1; + end + else Result := -1; + end; { of function TRegExpr.GetMatchPos +--------------------------------------------------------------} + +function TRegExpr.GetMatchLen (Idx : integer) : integer; + begin + if (Idx >= 0) and (Idx < NSUBEXP) and Assigned (fInputString) + and Assigned (startp [Idx]) and Assigned (endp [Idx]) then begin + Result := endp [Idx] - startp [Idx]; + end + else Result := -1; + end; { of function TRegExpr.GetMatchLen +--------------------------------------------------------------} + +function TRegExpr.GetMatch (Idx : integer) : RegExprString; + begin + if (Idx >= 0) and (Idx < NSUBEXP) and Assigned (fInputString) + and Assigned (startp [Idx]) and Assigned (endp [Idx]) + //then Result := copy (fInputString, MatchPos [Idx], MatchLen [Idx]) //###0.929 + then begin + {$IFDEF SYN_LAZARUS} + Result:=''; + {$ENDIF} + SetString (Result, startp [idx], endp [idx] - startp [idx]) + end + else Result := ''; + end; { of function TRegExpr.GetMatch +--------------------------------------------------------------} + +function TRegExpr.GetModifierStr : RegExprString; + begin + Result := '-'; + + if ModifierI + then Result := 'i' + Result + else Result := Result + 'i'; + if ModifierR + then Result := 'r' + Result + else Result := Result + 'r'; + if ModifierS + then Result := 's' + Result + else Result := Result + 's'; + if ModifierG + then Result := 'g' + Result + else Result := Result + 'g'; + if ModifierM + then Result := 'm' + Result + else Result := Result + 'm'; + if ModifierX + then Result := 'x' + Result + else Result := Result + 'x'; + + if Result [length (Result)] = '-' // remove '-' if all modifiers are 'On' + then System.Delete (Result, length (Result), 1); + end; { of function TRegExpr.GetModifierStr +--------------------------------------------------------------} + +class function TRegExpr.ParseModifiersStr (const AModifiers : RegExprString; +var AModifiersInt : integer) : boolean; +// !!! Be carefull - this is class function and must not use object instance fields + var + i : integer; + IsOn : boolean; + Mask : integer; + begin + Result := true; + IsOn := true; + Mask := 0; // prevent compiler warning + for i := 1 to length (AModifiers) do + if AModifiers [i] = '-' + then IsOn := false + else begin + if Pos (AModifiers [i], 'iI') > 0 + then Mask := MaskModI + else if Pos (AModifiers [i], 'rR') > 0 + then Mask := MaskModR + else if Pos (AModifiers [i], 'sS') > 0 + then Mask := MaskModS + else if Pos (AModifiers [i], 'gG') > 0 + then Mask := MaskModG + else if Pos (AModifiers [i], 'mM') > 0 + then Mask := MaskModM + else if Pos (AModifiers [i], 'xX') > 0 + then Mask := MaskModX + else begin + Result := false; + EXIT; + end; + if IsOn + then AModifiersInt := AModifiersInt or Mask + else AModifiersInt := AModifiersInt and not Mask; + end; + end; { of function TRegExpr.ParseModifiersStr +--------------------------------------------------------------} + +procedure TRegExpr.SetModifierStr (const AModifiers : RegExprString); + begin + if not ParseModifiersStr (AModifiers, fModifiers) + then Error (reeModifierUnsupported); + end; { of procedure TRegExpr.SetModifierStr +--------------------------------------------------------------} + +function TRegExpr.GetModifier (AIndex : integer) : boolean; + var + Mask : integer; + begin + Result := false; + case AIndex of + 1: Mask := MaskModI; + 2: Mask := MaskModR; + 3: Mask := MaskModS; + 4: Mask := MaskModG; + 5: Mask := MaskModM; + 6: Mask := MaskModX; + else begin + Error (reeModifierUnsupported); + EXIT; + end; + end; + Result := (fModifiers and Mask) <> 0; + end; { of function TRegExpr.GetModifier +--------------------------------------------------------------} + +procedure TRegExpr.SetModifier (AIndex : integer; ASet : boolean); + var + Mask : integer; + begin + case AIndex of + 1: Mask := MaskModI; + 2: Mask := MaskModR; + 3: Mask := MaskModS; + 4: Mask := MaskModG; + 5: Mask := MaskModM; + 6: Mask := MaskModX; + else begin + Error (reeModifierUnsupported); + EXIT; + end; + end; + if ASet + then fModifiers := fModifiers or Mask + else fModifiers := fModifiers and not Mask; + end; { of procedure TRegExpr.SetModifier +--------------------------------------------------------------} + + +{=============================================================} +{==================== Compiler section =======================} +{=============================================================} + +procedure TRegExpr.InvalidateProgramm; + begin + if programm <> nil then begin + FreeMem (programm); + programm := nil; + end; + end; { of procedure TRegExpr.InvalidateProgramm +--------------------------------------------------------------} + +procedure TRegExpr.Compile; //###0.941 + begin + if fExpression = nil then begin // No Expression assigned + Error (reeNoExpression); + EXIT; + end; + CompileRegExpr (fExpression); + end; { of procedure TRegExpr.Compile +--------------------------------------------------------------} + +function TRegExpr.IsProgrammOk : boolean; + {$IFNDEF UniCode} + var + i : integer; + {$ENDIF} + begin + Result := false; + + // check modifiers + if fModifiers <> fProgModifiers //###0.941 + then InvalidateProgramm; + + // can we optimize line separators by using sets? + {$IFNDEF UniCode} + fLineSeparatorsSet := []; + for i := 1 to length (fLineSeparators) + do System.Include (fLineSeparatorsSet, fLineSeparators [i]); + {$ENDIF} + + // [Re]compile if needed + if programm = nil + then Compile; //###0.941 + + // check [re]compiled programm + if programm = nil + then EXIT // error was set/raised by Compile (was reeExecAfterCompErr) + else if programm [0] <> MAGIC // Program corrupted. + then Error (reeCorruptedProgram) + else Result := true; + end; { of function TRegExpr.IsProgrammOk +--------------------------------------------------------------} + +procedure TRegExpr.Tail (p : PRegExprChar; val : PRegExprChar); +// set the next-pointer at the end of a node chain + var + scan : PRegExprChar; + temp : PRegExprChar; +// i : int64; + begin + if p = @regdummy + then EXIT; + // Find last node. + scan := p; + REPEAT + temp := regnext (scan); + if temp = nil + then BREAK; + scan := temp; + UNTIL false; + // Set Next 'pointer' + if val < scan + then PRENextOff (scan + REOpSz)^ := - (scan - val) //###0.948 + // work around PWideChar subtraction bug (Delphi uses + // shr after subtraction to calculate widechar distance %-( ) + // so, if difference is negative we have .. the "feature" :( + // I could wrap it in $IFDEF UniCode, but I didn't because + // "P – Q computes the difference between the address given + // by P (the higher address) and the address given by Q (the + // lower address)" - Delphi help quotation. + else PRENextOff (scan + REOpSz)^ := val - scan; //###0.933 + end; { of procedure TRegExpr.Tail +--------------------------------------------------------------} + +procedure TRegExpr.OpTail (p : PRegExprChar; val : PRegExprChar); +// regtail on operand of first argument; nop if operandless + begin + // "Operandless" and "op != BRANCH" are synonymous in practice. + if (p = nil) or (p = @regdummy) or (PREOp (p)^ <> BRANCH) + then EXIT; + Tail (p + REOpSz + RENextOffSz, val); //###0.933 + end; { of procedure TRegExpr.OpTail +--------------------------------------------------------------} + +function TRegExpr.EmitNode (op : TREOp) : PRegExprChar; //###0.933 +// emit a node, return location + begin + Result := regcode; + if Result <> @regdummy then begin + PREOp (regcode)^ := op; + inc (regcode, REOpSz); + PRENextOff (regcode)^ := 0; // Next "pointer" := nil + inc (regcode, RENextOffSz); + end + else inc (regsize, REOpSz + RENextOffSz); // compute code size without code generation + end; { of function TRegExpr.EmitNode +--------------------------------------------------------------} + +procedure TRegExpr.EmitC (b : REChar); +// emit a byte to code + begin + if regcode <> @regdummy then begin + regcode^ := b; + inc (regcode); + end + else inc (regsize); // Type of p-code pointer always is ^REChar + end; { of procedure TRegExpr.EmitC +--------------------------------------------------------------} + +procedure TRegExpr.InsertOperator (op : TREOp; opnd : PRegExprChar; sz : integer); +// insert an operator in front of already-emitted operand +// Means relocating the operand. + var + src, dst, place : PRegExprChar; + i : integer; + begin + if regcode = @regdummy then begin + inc (regsize, sz); + EXIT; + end; + src := regcode; + inc (regcode, sz); + dst := regcode; + while src > opnd do begin + dec (dst); + dec (src); + dst^ := src^; + end; + place := opnd; // Op node, where operand used to be. + PREOp (place)^ := op; + inc (place, REOpSz); + for i := 1 + REOpSz to sz do begin + place^ := #0; + inc (place); + end; + end; { of procedure TRegExpr.InsertOperator +--------------------------------------------------------------} + +function strcspn (s1 : PRegExprChar; s2 : PRegExprChar) : integer; +// find length of initial segment of s1 consisting +// entirely of characters not from s2 + var scan1, scan2 : PRegExprChar; + begin + Result := 0; + scan1 := s1; + while scan1^ <> #0 do begin + scan2 := s2; + while scan2^ <> #0 do + if scan1^ = scan2^ + then EXIT + else inc (scan2); + inc (Result); + inc (scan1) + end; + end; { of function strcspn +--------------------------------------------------------------} + +const +// Flags to be passed up and down. + HASWIDTH = 01; // Known never to match nil string. + SIMPLE = 02; // Simple enough to be STAR/PLUS/BRACES operand. + SPSTART = 04; // Starts with * or +. + WORST = 0; // Worst case. + META : array [0 .. 12] of REChar = ( + '^', '$', '.', '[', '(', ')', '|', '?', '+', '*', EscChar, '{', #0); + // Any modification must be synchronized with QuoteRegExprMetaChars !!! + +{$IFDEF UniCode} + RusRangeLo : array [0 .. 33] of REChar = + (#$430,#$431,#$432,#$433,#$434,#$435,#$451,#$436,#$437, + #$438,#$439,#$43A,#$43B,#$43C,#$43D,#$43E,#$43F, + #$440,#$441,#$442,#$443,#$444,#$445,#$446,#$447, + #$448,#$449,#$44A,#$44B,#$44C,#$44D,#$44E,#$44F,#0); + RusRangeHi : array [0 .. 33] of REChar = + (#$410,#$411,#$412,#$413,#$414,#$415,#$401,#$416,#$417, + #$418,#$419,#$41A,#$41B,#$41C,#$41D,#$41E,#$41F, + #$420,#$421,#$422,#$423,#$424,#$425,#$426,#$427, + #$428,#$429,#$42A,#$42B,#$42C,#$42D,#$42E,#$42F,#0); + RusRangeLoLow = #$430{'а'}; + RusRangeLoHigh = #$44F{'я'}; + RusRangeHiLow = #$410{'А'}; + RusRangeHiHigh = #$42F{'Я'}; +{$ELSE} + RusRangeLo = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя'; + RusRangeHi = 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'; + RusRangeLoLow = 'а'; + RusRangeLoHigh = 'я'; + RusRangeHiLow = 'А'; + RusRangeHiHigh = 'Я'; +{$ENDIF} + +function TRegExpr.CompileRegExpr (exp : PRegExprChar) : boolean; +// compile a regular expression into internal code +// We can't allocate space until we know how big the compiled form will be, +// but we can't compile it (and thus know how big it is) until we've got a +// place to put the code. So we cheat: we compile it twice, once with code +// generation turned off and size counting turned on, and once "for real". +// This also means that we don't allocate space until we are sure that the +// thing really will compile successfully, and we never have to move the +// code and thus invalidate pointers into it. (Note that it has to be in +// one piece because free() must be able to free it all.) +// Beware that the optimization-preparation code in here knows about some +// of the structure of the compiled regexp. + var + scan, longest : PRegExprChar; + len : cardinal; + flags : integer; + begin + Result := false; // life too dark + + regparse := nil; // for correct error handling + regexpbeg := exp; + try + + if programm <> nil then begin + FreeMem (programm); + programm := nil; + end; + + if exp = nil then begin + Error (reeCompNullArgument); + EXIT; + end; + + fProgModifiers := fModifiers; + // well, may it's paranoia. I'll check it later... !!!!!!!! + + // First pass: determine size, legality. + fCompModifiers := fModifiers; + regparse := exp; + regnpar := 1; + regsize := 0; + regcode := @regdummy; + EmitC (MAGIC); + if ParseReg (0, flags) = nil + then EXIT; + + // Small enough for 2-bytes programm pointers ? + // ###0.933 no real p-code length limits now :))) +// if regsize >= 64 * 1024 then begin +// Error (reeCompRegexpTooBig); +// EXIT; +// end; + + // Allocate space. + GetMem (programm, regsize * SizeOf (REChar)); + + // Second pass: emit code. + fCompModifiers := fModifiers; + regparse := exp; + regnpar := 1; + regcode := programm; + EmitC (MAGIC); + if ParseReg (0, flags) = nil + then EXIT; + + // Dig out information for optimizations. + {$IFDEF UseFirstCharSet} //###0.929 + FirstCharSet := []; + FillFirstCharSet (programm + REOpSz); + {$ENDIF} + regstart := #0; // Worst-case defaults. + reganch := #0; + regmust := nil; + regmlen := 0; + scan := programm + REOpSz; // First BRANCH. + if PREOp (regnext (scan))^ = EEND then begin // Only one top-level choice. + scan := scan + REOpSz + RENextOffSz; + + // Starting-point info. + if PREOp (scan)^ = EXACTLY + then regstart := (scan + REOpSz + RENextOffSz)^ + else if PREOp (scan)^ = BOL + then inc (reganch); + + // If there's something expensive in the r.e., find the longest + // literal string that must appear and make it the regmust. Resolve + // ties in favor of later strings, since the regstart check works + // with the beginning of the r.e. and avoiding duplication + // strengthens checking. Not a strong reason, but sufficient in the + // absence of others. + if (flags and SPSTART) <> 0 then begin + longest := nil; + len := 0; + while scan <> nil do begin + if (PREOp (scan)^ = EXACTLY) + and (strlen (scan + REOpSz + RENextOffSz) >= integer(len)) then begin + longest := scan + REOpSz + RENextOffSz; + len := strlen (longest); + end; + scan := regnext (scan); + end; + regmust := longest; + regmlen := len; + end; + end; + + Result := true; + + finally begin + if not Result + then InvalidateProgramm; + regexpbeg := nil; + fExprIsCompiled := Result; //###0.944 + end; + end; + + end; { of function TRegExpr.CompileRegExpr +--------------------------------------------------------------} + +function TRegExpr.ParseReg (paren : integer; var flagp : integer) : PRegExprChar; +// regular expression, i.e. main body or parenthesized thing +// Caller must absorb opening parenthesis. +// Combining parenthesis handling with the base level of regular expression +// is a trifle forced, but the need to tie the tails of the branches to what +// follows makes it hard to avoid. + var + ret, br, ender : PRegExprChar; + parno : integer; + flags : integer; + SavedModifiers : integer; + begin + Result := nil; + flagp := HASWIDTH; // Tentatively. + parno := 0; // eliminate compiler stupid warning + SavedModifiers := fCompModifiers; + + // Make an OPEN node, if parenthesized. + if paren <> 0 then begin + if regnpar >= NSUBEXP then begin + Error (reeCompParseRegTooManyBrackets); + EXIT; + end; + parno := regnpar; + inc (regnpar); + ret := EmitNode (TREOp (ord (OPEN) + parno)); + end + else ret := nil; + + // Pick up the branches, linking them together. + br := ParseBranch (flags); + if br = nil then begin + Result := nil; + EXIT; + end; + if ret <> nil + then Tail (ret, br) // OPEN -> first. + else ret := br; + if (flags and HASWIDTH) = 0 + then flagp := flagp and not HASWIDTH; + flagp := flagp or flags and SPSTART; + while (regparse^ = '|') do begin + inc (regparse); + br := ParseBranch (flags); + if br = nil then begin + Result := nil; + EXIT; + end; + Tail (ret, br); // BRANCH -> BRANCH. + if (flags and HASWIDTH) = 0 + then flagp := flagp and not HASWIDTH; + flagp := flagp or flags and SPSTART; + end; + + // Make a closing node, and hook it on the end. + if paren <> 0 + then ender := EmitNode (TREOp (ord (CLOSE) + parno)) + else ender := EmitNode (EEND); + Tail (ret, ender); + + // Hook the tails of the branches to the closing node. + br := ret; + while br <> nil do begin + OpTail (br, ender); + br := regnext (br); + end; + + // Check for proper termination. + if paren <> 0 then + if regparse^ <> ')' then begin + Error (reeCompParseRegUnmatchedBrackets); + EXIT; + end + else inc (regparse); // skip trailing ')' + if (paren = 0) and (regparse^ <> #0) then begin + if regparse^ = ')' + then Error (reeCompParseRegUnmatchedBrackets2) + else Error (reeCompParseRegJunkOnEnd); + EXIT; + end; + fCompModifiers := SavedModifiers; // restore modifiers of parent + Result := ret; + end; { of function TRegExpr.ParseReg +--------------------------------------------------------------} + +function TRegExpr.ParseBranch (var flagp : integer) : PRegExprChar; +// one alternative of an | operator +// Implements the concatenation operator. + var + ret, chain, latest : PRegExprChar; + flags : integer; + begin + flagp := WORST; // Tentatively. + + ret := EmitNode (BRANCH); + chain := nil; + while (regparse^ <> #0) and (regparse^ <> '|') + and (regparse^ <> ')') do begin + latest := ParsePiece (flags); + if latest = nil then begin + Result := nil; + EXIT; + end; + flagp := flagp or flags and HASWIDTH; + if chain = nil // First piece. + then flagp := flagp or flags and SPSTART + else Tail (chain, latest); + chain := latest; + end; + if chain = nil // Loop ran zero times. + then EmitNode (NOTHING); + Result := ret; + end; { of function TRegExpr.ParseBranch +--------------------------------------------------------------} + +function TRegExpr.ParsePiece (var flagp : integer) : PRegExprChar; +// something followed by possible [*+?{] +// Note that the branching code sequences used for ? and the general cases +// of * and + and { are somewhat optimized: they use the same NOTHING node as +// both the endmarker for their branch list and the body of the last branch. +// It might seem that this node could be dispensed with entirely, but the +// endmarker role is not redundant. + function parsenum (AStart, AEnd : PRegExprChar) : TREBracesArg; + begin + Result := 0; + if AEnd - AStart + 1 > 8 then begin // prevent stupid scanning + Error (reeBRACESArgTooBig); + EXIT; + end; + while AStart <= AEnd do begin + Result := Result * 10 + (ord (AStart^) - ord ('0')); + inc (AStart); + end; + if (Result > MaxBracesArg) or (Result < 0) then begin + Error (reeBRACESArgTooBig); + EXIT; + end; + end; + + var + op : REChar; + NonGreedyOp, NonGreedyCh : boolean; //###0.940 + TheOp : TREOp; //###0.940 + NextNode : PRegExprChar; + flags : integer; + BracesMin, Bracesmax : TREBracesArg; + p, savedparse : PRegExprChar; + + procedure EmitComplexBraces (ABracesMin, ABracesMax : TREBracesArg; + ANonGreedyOp : boolean); //###0.940 + {$IFDEF ComplexBraces} + var + off : integer; + {$ENDIF} + begin + {$IFNDEF ComplexBraces} + Error (reeComplexBracesNotImplemented); + {$ELSE} + if ANonGreedyOp + then TheOp := LOOPNG + else TheOp := LOOP; + InsertOperator (LOOPENTRY, Result, REOpSz + RENextOffSz); + NextNode := EmitNode (TheOp); + if regcode <> @regdummy then begin + off := (Result + REOpSz + RENextOffSz) + - (regcode - REOpSz - RENextOffSz); // back to Atom after LOOPENTRY + PREBracesArg (regcode)^ := ABracesMin; + inc (regcode, REBracesArgSz); + PREBracesArg (regcode)^ := ABracesMax; + inc (regcode, REBracesArgSz); + PRENextOff (regcode)^ := off; + inc (regcode, RENextOffSz); + end + else inc (regsize, REBracesArgSz * 2 + RENextOffSz); + Tail (Result, NextNode); // LOOPENTRY -> LOOP + if regcode <> @regdummy then + Tail (Result + REOpSz + RENextOffSz, NextNode); // Atom -> LOOP + {$ENDIF} + end; + + procedure EmitSimpleBraces (ABracesMin, ABracesMax : TREBracesArg; + ANonGreedyOp : boolean); //###0.940 + begin + if ANonGreedyOp //###0.940 + then TheOp := BRACESNG + else TheOp := BRACES; + InsertOperator (TheOp, Result, REOpSz + RENextOffSz + REBracesArgSz * 2); + if regcode <> @regdummy then begin + PREBracesArg (Result + REOpSz + RENextOffSz)^ := ABracesMin; + PREBracesArg (Result + REOpSz + RENextOffSz + REBracesArgSz)^ := ABracesMax; + end; + end; + + begin + Result := ParseAtom (flags); + if Result = nil + then EXIT; + + op := regparse^; + if not ((op = '*') or (op = '+') or (op = '?') or (op = '{')) then begin + flagp := flags; + EXIT; + end; + if ((flags and HASWIDTH) = 0) and (op <> '?') then begin + Error (reePlusStarOperandCouldBeEmpty); + EXIT; + end; + + case op of + '*': begin + flagp := WORST or SPSTART; + NonGreedyCh := (regparse + 1)^ = '?'; //###0.940 + NonGreedyOp := NonGreedyCh or ((fCompModifiers and MaskModG) = 0); //###0.940 + if (flags and SIMPLE) = 0 then begin + if NonGreedyOp //###0.940 + then EmitComplexBraces (0, MaxBracesArg, NonGreedyOp) + else begin // Emit x* as (x&|), where & means "self". + InsertOperator (BRANCH, Result, REOpSz + RENextOffSz); // Either x + OpTail (Result, EmitNode (BACK)); // and loop + OpTail (Result, Result); // back + Tail (Result, EmitNode (BRANCH)); // or + Tail (Result, EmitNode (NOTHING)); // nil. + end + end + else begin // Simple + if NonGreedyOp //###0.940 + then TheOp := STARNG + else TheOp := STAR; + InsertOperator (TheOp, Result, REOpSz + RENextOffSz); + end; + if NonGreedyCh //###0.940 + then inc (regparse); // Skip extra char ('?') + end; { of case '*'} + '+': begin + flagp := WORST or SPSTART or HASWIDTH; + NonGreedyCh := (regparse + 1)^ = '?'; //###0.940 + NonGreedyOp := NonGreedyCh or ((fCompModifiers and MaskModG) = 0); //###0.940 + if (flags and SIMPLE) = 0 then begin + if NonGreedyOp //###0.940 + then EmitComplexBraces (1, MaxBracesArg, NonGreedyOp) + else begin // Emit x+ as x(&|), where & means "self". + NextNode := EmitNode (BRANCH); // Either + Tail (Result, NextNode); + Tail (EmitNode (BACK), Result); // loop back + Tail (NextNode, EmitNode (BRANCH)); // or + Tail (Result, EmitNode (NOTHING)); // nil. + end + end + else begin // Simple + if NonGreedyOp //###0.940 + then TheOp := PLUSNG + else TheOp := PLUS; + InsertOperator (TheOp, Result, REOpSz + RENextOffSz); + end; + if NonGreedyCh //###0.940 + then inc (regparse); // Skip extra char ('?') + end; { of case '+'} + '?': begin + flagp := WORST; + NonGreedyCh := (regparse + 1)^ = '?'; //###0.940 + NonGreedyOp := NonGreedyCh or ((fCompModifiers and MaskModG) = 0); //###0.940 + if NonGreedyOp then begin //###0.940 // We emit x?? as x{0,1}? + if (flags and SIMPLE) = 0 + then EmitComplexBraces (0, 1, NonGreedyOp) + else EmitSimpleBraces (0, 1, NonGreedyOp); + end + else begin // greedy '?' + InsertOperator (BRANCH, Result, REOpSz + RENextOffSz); // Either x + Tail (Result, EmitNode (BRANCH)); // or + NextNode := EmitNode (NOTHING); // nil. + Tail (Result, NextNode); + OpTail (Result, NextNode); + end; + if NonGreedyCh //###0.940 + then inc (regparse); // Skip extra char ('?') + end; { of case '?'} + '{': begin + savedparse := regparse; + // !!!!!!!!!!!! + // Filip Jirsak's note - what will happen, when we are at the end of regparse? + inc (regparse); + p := regparse; + while Pos (regparse^, '0123456789') > 0 // <min> MUST appear + do inc (regparse); + if (regparse^ <> '}') and (regparse^ <> ',') or (p = regparse) then begin + regparse := savedparse; + flagp := flags; + EXIT; + end; + BracesMin := parsenum (p, regparse - 1); + if regparse^ = ',' then begin + inc (regparse); + p := regparse; + while Pos (regparse^, '0123456789') > 0 + do inc (regparse); + if regparse^ <> '}' then begin + regparse := savedparse; + EXIT; + end; + if p = regparse + then BracesMax := MaxBracesArg + else BracesMax := parsenum (p, regparse - 1); + end + else BracesMax := BracesMin; // {n} == {n,n} + if BracesMin > BracesMax then begin + Error (reeBracesMinParamGreaterMax); + EXIT; + end; + if BracesMin > 0 + then flagp := WORST; + if BracesMax > 0 + then flagp := flagp or HASWIDTH or SPSTART; + + NonGreedyCh := (regparse + 1)^ = '?'; //###0.940 + NonGreedyOp := NonGreedyCh or ((fCompModifiers and MaskModG) = 0); //###0.940 + if (flags and SIMPLE) <> 0 + then EmitSimpleBraces (BracesMin, BracesMax, NonGreedyOp) + else EmitComplexBraces (BracesMin, BracesMax, NonGreedyOp); + if NonGreedyCh //###0.940 + then inc (regparse); // Skip extra char '?' + end; // of case '{' +// else // here we can't be + end; { of case op} + + inc (regparse); + if (regparse^ = '*') or (regparse^ = '+') or (regparse^ = '?') or (regparse^ = '{') then begin + Error (reeNestedSQP); + EXIT; + end; + end; { of function TRegExpr.ParsePiece +--------------------------------------------------------------} + +function TRegExpr.ParseAtom (var flagp : integer) : PRegExprChar; +// the lowest level +// Optimization: gobbles an entire sequence of ordinary characters so that +// it can turn them into a single node, which is smaller to store and +// faster to run. Backslashed characters are exceptions, each becoming a +// separate node; the code is simpler that way and it's not worth fixing. + var + ret : PRegExprChar; + flags : integer; + RangeBeg, RangeEnd : REChar; + CanBeRange : boolean; + len : integer; + ender : REChar; + begmodfs : PRegExprChar; + + {$IFDEF UseSetOfChar} //###0.930 + RangePCodeBeg : PRegExprChar; + RangePCodeIdx : integer; + RangeIsCI : boolean; + RangeSet : TSetOfREChar; + RangeLen : integer; + RangeChMin, RangeChMax : REChar; + {$ENDIF} + + procedure EmitExactly (ch : REChar); + begin + if (fCompModifiers and MaskModI) <> 0 + then ret := EmitNode (EXACTLYCI) + else ret := EmitNode (EXACTLY); + EmitC (ch); + EmitC (#0); + flagp := flagp or HASWIDTH or SIMPLE; + end; + + procedure EmitStr (const s : RegExprString); + var i : integer; + begin + for i := 1 to length (s) + do EmitC (s [i]); + end; + + function HexDig (ch : REChar) : integer; + begin + Result := 0; + if (ch >= 'a') and (ch <= 'f') + then ch := REChar (ord (ch) - (ord ('a') - ord ('A'))); + if (ch < '0') or (ch > 'F') or ((ch > '9') and (ch < 'A')) then begin + Error (reeBadHexDigit); + EXIT; + end; + Result := ord (ch) - ord ('0'); + if ch >= 'A' + then Result := Result - (ord ('A') - ord ('9') - 1); + end; + + function EmitRange (AOpCode : REChar) : PRegExprChar; + begin + {$IFDEF UseSetOfChar} + case AOpCode of + ANYBUTCI, ANYBUT: + Result := EmitNode (ANYBUTTINYSET); + else // ANYOFCI, ANYOF + Result := EmitNode (ANYOFTINYSET); + end; + case AOpCode of + ANYBUTCI, ANYOFCI: + RangeIsCI := True; + else // ANYBUT, ANYOF + RangeIsCI := False; + end; + RangePCodeBeg := regcode; + RangePCodeIdx := regsize; + RangeLen := 0; + RangeSet := []; + RangeChMin := #255; + RangeChMax := #0; + {$ELSE} + Result := EmitNode (AOpCode); + // ToDo: + // !!!!!!!!!!!!! Implement ANYOF[BUT]TINYSET generation for UniCode !!!!!!!!!! + {$ENDIF} + end; + +{$IFDEF UseSetOfChar} + procedure EmitRangeCPrim (b : REChar); //###0.930 + begin + if b in RangeSet + then EXIT; + inc (RangeLen); + if b < RangeChMin + then RangeChMin := b; + if b > RangeChMax + then RangeChMax := b; + Include (RangeSet, b); + end; + {$ENDIF} + + procedure EmitRangeC (b : REChar); + {$IFDEF UseSetOfChar} + var + Ch : REChar; + {$ENDIF} + begin + CanBeRange := false; + {$IFDEF UseSetOfChar} + if b <> #0 then begin + EmitRangeCPrim (b); //###0.930 + if RangeIsCI + then EmitRangeCPrim (InvertCase (b)); //###0.930 + end + else begin + {$IFDEF UseAsserts} + Assert (RangeLen > 0, 'TRegExpr.ParseAtom(subroutine EmitRangeC): empty range'); // impossible, but who knows.. + Assert (RangeChMin <= RangeChMax, 'TRegExpr.ParseAtom(subroutine EmitRangeC): RangeChMin > RangeChMax'); // impossible, but who knows.. + {$ENDIF} + if RangeLen <= TinySetLen then begin // emit "tiny set" + if regcode = @regdummy then begin + regsize := RangePCodeIdx + TinySetLen; // RangeChMin/Max !!! + EXIT; + end; + regcode := RangePCodeBeg; + for Ch := RangeChMin to RangeChMax do //###0.930 + if Ch in RangeSet then begin + regcode^ := Ch; + inc (regcode); + end; + // fill rest: + while regcode < RangePCodeBeg + TinySetLen do begin + regcode^ := RangeChMax; + inc (regcode); + end; + end + else begin + if regcode = @regdummy then begin + regsize := RangePCodeIdx + SizeOf (TSetOfREChar); + EXIT; + end; + if (RangePCodeBeg - REOpSz - RENextOffSz)^ = ANYBUTTINYSET + then RangeSet := [#0 .. #255] - RangeSet; + PREOp (RangePCodeBeg - REOpSz - RENextOffSz)^ := ANYOFFULLSET; + regcode := RangePCodeBeg; + Move (RangeSet, regcode^, SizeOf (TSetOfREChar)); + inc (regcode, SizeOf (TSetOfREChar)); + end; + end; + {$ELSE} + EmitC (b); + {$ENDIF} + end; + + procedure EmitSimpleRangeC (b : REChar); + begin + RangeBeg := b; + EmitRangeC (b); + CanBeRange := true; + end; + + procedure EmitRangeStr (const s : RegExprString); + var i : integer; + begin + for i := 1 to length (s) + do EmitRangeC (s [i]); + end; + + function UnQuoteChar (var APtr : PRegExprChar) : REChar; //###0.934 + begin + case APtr^ of + 't': Result := #$9; // tab (HT/TAB) + 'n': Result := #$a; // newline (NL) + 'r': Result := #$d; // car.return (CR) + 'f': Result := #$c; // form feed (FF) + 'a': Result := #$7; // alarm (bell) (BEL) + 'e': Result := #$1b; // escape (ESC) + 'x': begin // hex char + Result := #0; + inc (APtr); + if APtr^ = #0 then begin + Error (reeNoHexCodeAfterBSlashX); + EXIT; + end; + if APtr^ = '{' then begin // \x{nnnn} //###0.936 + REPEAT + inc (APtr); + if APtr^ = #0 then begin + Error (reeNoHexCodeAfterBSlashX); + EXIT; + end; + if APtr^ <> '}' then begin + if (Ord (Result) + ShR (SizeOf (REChar) * 8 - 4)) and $F <> 0 then begin + Error (reeHexCodeAfterBSlashXTooBig); + EXIT; + end; + Result := REChar ((Ord (Result) ShL 4) or HexDig (APtr^)); + // HexDig will cause Error if bad hex digit found + end + else BREAK; + UNTIL False; + end + else begin + Result := REChar (HexDig (APtr^)); + // HexDig will cause Error if bad hex digit found + inc (APtr); + if APtr^ = #0 then begin + Error (reeNoHexCodeAfterBSlashX); + EXIT; + end; + Result := REChar ((Ord (Result) ShL 4) or HexDig (APtr^)); + // HexDig will cause Error if bad hex digit found + end; + end; + else Result := APtr^; + end; + end; + + begin + Result := nil; + flagp := WORST; // Tentatively. + + inc (regparse); + case (regparse - 1)^ of + '^': if ((fCompModifiers and MaskModM) = 0) + or ((fLineSeparators = '') and not fLinePairedSeparatorAssigned) + then ret := EmitNode (BOL) + else ret := EmitNode (BOLML); + '$': if ((fCompModifiers and MaskModM) = 0) + or ((fLineSeparators = '') and not fLinePairedSeparatorAssigned) + then ret := EmitNode (EOL) + else ret := EmitNode (EOLML); + '.': + if (fCompModifiers and MaskModS) <> 0 then begin + ret := EmitNode (ANY); + flagp := flagp or HASWIDTH or SIMPLE; + end + else begin // not /s, so emit [^:LineSeparators:] + ret := EmitNode (ANYML); + flagp := flagp or HASWIDTH; // not so simple ;) +// ret := EmitRange (ANYBUT); +// EmitRangeStr (LineSeparators); //###0.941 +// EmitRangeStr (LinePairedSeparator); // !!! isn't correct if have to accept only paired +// EmitRangeC (#0); +// flagp := flagp or HASWIDTH or SIMPLE; + end; + '[': begin + if regparse^ = '^' then begin // Complement of range. + if (fCompModifiers and MaskModI) <> 0 + then ret := EmitRange (ANYBUTCI) + else ret := EmitRange (ANYBUT); + inc (regparse); + end + else + if (fCompModifiers and MaskModI) <> 0 + then ret := EmitRange (ANYOFCI) + else ret := EmitRange (ANYOF); + + CanBeRange := false; + + if (regparse^ = ']') then begin + EmitSimpleRangeC (regparse^); // []-a] -> ']' .. 'a' + inc (regparse); + end; + + while (regparse^ <> #0) and (regparse^ <> ']') do begin + if (regparse^ = '-') + and ((regparse + 1)^ <> #0) and ((regparse + 1)^ <> ']') + and CanBeRange then begin + inc (regparse); + RangeEnd := regparse^; + if RangeEnd = EscChar then begin + {$IFDEF UniCode} //###0.935 + if (ord ((regparse + 1)^) < 256) + and (char ((regparse + 1)^) + in ['d', 'D', 's', 'S', 'w', 'W']) then begin + {$ELSE} + if (regparse + 1)^ in ['d', 'D', 's', 'S', 'w', 'W'] then begin + {$ENDIF} + EmitRangeC ('-'); // or treat as error ?!! + CONTINUE; + end; + inc (regparse); + RangeEnd := UnQuoteChar (regparse); + end; + + // r.e.ranges extension for russian + if ((fCompModifiers and MaskModR) <> 0) + and (RangeBeg = RusRangeLoLow) and (RangeEnd = RusRangeLoHigh) then begin + EmitRangeStr (RusRangeLo); + end + else if ((fCompModifiers and MaskModR) <> 0) + and (RangeBeg = RusRangeHiLow) and (RangeEnd = RusRangeHiHigh) then begin + EmitRangeStr (RusRangeHi); + end + else if ((fCompModifiers and MaskModR) <> 0) + and (RangeBeg = RusRangeLoLow) and (RangeEnd = RusRangeHiHigh) then begin + EmitRangeStr (RusRangeLo); + EmitRangeStr (RusRangeHi); + end + else begin // standard r.e. handling + if RangeBeg > RangeEnd then begin + Error (reeInvalidRange); + EXIT; + end; + inc (RangeBeg); + EmitRangeC (RangeEnd); // prevent infinite loop if RangeEnd=$ff + while RangeBeg < RangeEnd do begin //###0.929 + EmitRangeC (RangeBeg); + inc (RangeBeg); + end; + end; + inc (regparse); + end + else begin + if regparse^ = EscChar then begin + inc (regparse); + if regparse^ = #0 then begin + Error (reeParseAtomTrailingBackSlash); + EXIT; + end; + case regparse^ of // r.e.extensions + 'd': EmitRangeStr ('0123456789'); + 'w': EmitRangeStr (WordChars); + 's': EmitRangeStr (SpaceChars); + else EmitSimpleRangeC (UnQuoteChar (regparse)); + end; { of case} + end + else EmitSimpleRangeC (regparse^); + inc (regparse); + end; + end; { of while} + EmitRangeC (#0); + if regparse^ <> ']' then begin + Error (reeUnmatchedSqBrackets); + EXIT; + end; + inc (regparse); + flagp := flagp or HASWIDTH or SIMPLE; + end; + '(': begin + if regparse^ = '?' then begin + // check for extended Perl syntax : (?..) + if (regparse + 1)^ = '#' then begin // (?#comment) + inc (regparse, 2); // find closing ')' + while (regparse^ <> #0) and (regparse^ <> ')') + do inc (regparse); + if regparse^ <> ')' then begin + Error (reeUnclosedComment); + EXIT; + end; + inc (regparse); // skip ')' + ret := EmitNode (COMMENT); // comment + end + else begin // modifiers ? + inc (regparse); // skip '?' + begmodfs := regparse; + while (regparse^ <> #0) and (regparse^ <> ')') + do inc (regparse); + if (regparse^ <> ')') + or not ParseModifiersStr (copy (begmodfs, 1, (regparse - begmodfs)), fCompModifiers) then begin + Error (reeUrecognizedModifier); + EXIT; + end; + inc (regparse); // skip ')' + ret := EmitNode (COMMENT); // comment +// Error (reeQPSBFollowsNothing); +// EXIT; + end; + end + else begin + ret := ParseReg (1, flags); + if ret = nil then begin + Result := nil; + EXIT; + end; + flagp := flagp or flags and (HASWIDTH or SPSTART); + end; + end; + #0, '|', ')': begin // Supposed to be caught earlier. + Error (reeInternalUrp); + EXIT; + end; + '?', '+', '*': begin + Error (reeQPSBFollowsNothing); + EXIT; + end; + EscChar: begin + if regparse^ = #0 then begin + Error (reeTrailingBackSlash); + EXIT; + end; + case regparse^ of // r.e.extensions + 'b': ret := EmitNode (BOUND); //###0.943 + 'B': ret := EmitNode (NOTBOUND); //###0.943 + 'A': ret := EmitNode (BOL); //###0.941 + 'Z': ret := EmitNode (EOL); //###0.941 + 'd': begin // r.e.extension - any digit ('0' .. '9') + ret := EmitNode (ANYDIGIT); + flagp := flagp or HASWIDTH or SIMPLE; + end; + 'D': begin // r.e.extension - not digit ('0' .. '9') + ret := EmitNode (NOTDIGIT); + flagp := flagp or HASWIDTH or SIMPLE; + end; + 's': begin // r.e.extension - any space char + {$IFDEF UseSetOfChar} + ret := EmitRange (ANYOF); + EmitRangeStr (SpaceChars); + EmitRangeC (#0); + {$ELSE} + ret := EmitNode (ANYSPACE); + {$ENDIF} + flagp := flagp or HASWIDTH or SIMPLE; + end; + 'S': begin // r.e.extension - not space char + {$IFDEF UseSetOfChar} + ret := EmitRange (ANYBUT); + EmitRangeStr (SpaceChars); + EmitRangeC (#0); + {$ELSE} + ret := EmitNode (NOTSPACE); + {$ENDIF} + flagp := flagp or HASWIDTH or SIMPLE; + end; + 'w': begin // r.e.extension - any english char / digit / '_' + {$IFDEF UseSetOfChar} + ret := EmitRange (ANYOF); + EmitRangeStr (WordChars); + EmitRangeC (#0); + {$ELSE} + ret := EmitNode (ANYLETTER); + {$ENDIF} + flagp := flagp or HASWIDTH or SIMPLE; + end; + 'W': begin // r.e.extension - not english char / digit / '_' + {$IFDEF UseSetOfChar} + ret := EmitRange (ANYBUT); + EmitRangeStr (WordChars); + EmitRangeC (#0); + {$ELSE} + ret := EmitNode (NOTLETTER); + {$ENDIF} + flagp := flagp or HASWIDTH or SIMPLE; + end; + '1' .. '9': begin //###0.936 + if (fCompModifiers and MaskModI) <> 0 + then ret := EmitNode (BSUBEXPCI) + else ret := EmitNode (BSUBEXP); + EmitC (REChar (ord (regparse^) - ord ('0'))); + flagp := flagp or HASWIDTH or SIMPLE; + end; + else EmitExactly (UnQuoteChar (regparse)); + end; { of case} + inc (regparse); + end; + else begin + dec (regparse); + if ((fCompModifiers and MaskModX) <> 0) and // check for eXtended syntax + ((regparse^ = '#') + or ({$IFDEF UniCode}StrScan (XIgnoredChars, regparse^) <> nil //###0.947 + {$ELSE}regparse^ in XIgnoredChars{$ENDIF})) then begin //###0.941 \x + if regparse^ = '#' then begin // Skip eXtended comment + // find comment terminator (group of \n and/or \r) + while (regparse^ <> #0) and (regparse^ <> #$d) and (regparse^ <> #$a) + do inc (regparse); + while (regparse^ = #$d) or (regparse^ = #$a) // skip comment terminator + do inc (regparse); // attempt to support different type of line separators + end + else begin // Skip the blanks! + while {$IFDEF UniCode}StrScan (XIgnoredChars, regparse^) <> nil //###0.947 + {$ELSE}regparse^ in XIgnoredChars{$ENDIF} + do inc (regparse); + end; + ret := EmitNode (COMMENT); // comment + end + else begin + len := strcspn (regparse, META); + if len <= 0 then + if regparse^ <> '{' then begin + Error (reeRarseAtomInternalDisaster); + EXIT; + end + else len := strcspn (regparse + 1, META) + 1; // bad {n,m} - compile as EXATLY + ender := (regparse + len)^; + if (len > 1) + and ((ender = '*') or (ender = '+') or (ender = '?') or (ender = '{')) + then dec (len); // Back off clear of ?+*{ operand. + flagp := flagp or HASWIDTH; + if len = 1 + then flagp := flagp or SIMPLE; + if (fCompModifiers and MaskModI) <> 0 + then ret := EmitNode (EXACTLYCI) + else ret := EmitNode (EXACTLY); + while (len > 0) + and (((fCompModifiers and MaskModX) = 0) or (regparse^ <> '#')) do begin + if ((fCompModifiers and MaskModX) = 0) or not ( //###0.941 + {$IFDEF UniCode}StrScan (XIgnoredChars, regparse^) <> nil //###0.947 + {$ELSE}regparse^ in XIgnoredChars{$ENDIF} ) + then EmitC (regparse^); + inc (regparse); + dec (len); + end; + EmitC (#0); + end; { of if not comment} + end; { of case else} + end; { of case} + + Result := ret; + end; { of function TRegExpr.ParseAtom +--------------------------------------------------------------} + +function TRegExpr.GetCompilerErrorPos : integer; + begin + Result := 0; + if (regexpbeg = nil) or (regparse = nil) + then EXIT; // not in compiling mode ? + Result := regparse - regexpbeg; + end; { of function TRegExpr.GetCompilerErrorPos +--------------------------------------------------------------} + + +{=============================================================} +{===================== Matching section ======================} +{=============================================================} + +{$IFNDEF UseSetOfChar} +function TRegExpr.StrScanCI (s : PRegExprChar; ch : REChar) : PRegExprChar; //###0.928 - now method of TRegExpr + begin + while (s^ <> #0) and (s^ <> ch) and (s^ <> InvertCase (ch)) + do inc (s); + if s^ <> #0 + then Result := s + else Result := nil; + end; { of function TRegExpr.StrScanCI +--------------------------------------------------------------} +{$ENDIF} + +function TRegExpr.regrepeat (p : PRegExprChar; AMax : integer) : integer; +// repeatedly match something simple, report how many + var + scan : PRegExprChar; + opnd : PRegExprChar; + TheMax : integer; + {Ch,} InvCh : REChar; //###0.931 + sestart, seend : PRegExprChar; //###0.936 + begin + Result := 0; + scan := reginput; + opnd := p + REOpSz + RENextOffSz; //OPERAND + TheMax := fInputEnd - scan; + if TheMax > AMax + then TheMax := AMax; + case PREOp (p)^ of + ANY: begin + // note - ANYML cannot be proceeded in regrepeat because can skip + // more than one char at once + Result := TheMax; + inc (scan, Result); + end; + EXACTLY: begin // in opnd can be only ONE char !!! +// Ch := opnd^; // store in register //###0.931 + while (Result < TheMax) and (opnd^ = scan^) do begin + inc (Result); + inc (scan); + end; + end; + EXACTLYCI: begin // in opnd can be only ONE char !!! +// Ch := opnd^; // store in register //###0.931 + while (Result < TheMax) and (opnd^ = scan^) do begin // prevent unneeded InvertCase //###0.931 + inc (Result); + inc (scan); + end; + if Result < TheMax then begin //###0.931 + InvCh := InvertCase (opnd^); // store in register + while (Result < TheMax) and + ((opnd^ = scan^) or (InvCh = scan^)) do begin + inc (Result); + inc (scan); + end; + end; + end; + BSUBEXP: begin //###0.936 + sestart := startp [ord (opnd^)]; + if sestart = nil + then EXIT; + seend := endp [ord (opnd^)]; + if seend = nil + then EXIT; + REPEAT + opnd := sestart; + while opnd < seend do begin + if (scan >= fInputEnd) or (scan^ <> opnd^) + then EXIT; + inc (scan); + inc (opnd); + end; + inc (Result); + reginput := scan; + UNTIL Result >= AMax; + end; + BSUBEXPCI: begin //###0.936 + sestart := startp [ord (opnd^)]; + if sestart = nil + then EXIT; + seend := endp [ord (opnd^)]; + if seend = nil + then EXIT; + REPEAT + opnd := sestart; + while opnd < seend do begin + if (scan >= fInputEnd) or + ((scan^ <> opnd^) and (scan^ <> InvertCase (opnd^))) + then EXIT; + inc (scan); + inc (opnd); + end; + inc (Result); + reginput := scan; + UNTIL Result >= AMax; + end; + ANYDIGIT: + while (Result < TheMax) and + (scan^ >= '0') and (scan^ <= '9') do begin + inc (Result); + inc (scan); + end; + NOTDIGIT: + while (Result < TheMax) and + ((scan^ < '0') or (scan^ > '9')) do begin + inc (Result); + inc (scan); + end; + {$IFNDEF UseSetOfChar} //###0.929 + ANYLETTER: + while (Result < TheMax) and + (Pos (scan^, fWordChars) > 0) //###0.940 + { ((scan^ >= 'a') and (scan^ <= 'z') !! I've forgotten (>='0') and (<='9') + or (scan^ >= 'A') and (scan^ <= 'Z') or (scan^ = '_'))} do begin + inc (Result); + inc (scan); + end; + NOTLETTER: + while (Result < TheMax) and + (Pos (scan^, fWordChars) <= 0) //###0.940 + { not ((scan^ >= 'a') and (scan^ <= 'z') !! I've forgotten (>='0') and (<='9') + or (scan^ >= 'A') and (scan^ <= 'Z') + or (scan^ = '_'))} do begin + inc (Result); + inc (scan); + end; + ANYSPACE: + while (Result < TheMax) and + (Pos (scan^, fSpaceChars) > 0) do begin + inc (Result); + inc (scan); + end; + NOTSPACE: + while (Result < TheMax) and + (Pos (scan^, fSpaceChars) <= 0) do begin + inc (Result); + inc (scan); + end; + {$ENDIF} + ANYOFTINYSET: begin + while (Result < TheMax) and //!!!TinySet + ((scan^ = opnd^) or (scan^ = (opnd + 1)^) + or (scan^ = (opnd + 2)^)) do begin + inc (Result); + inc (scan); + end; + end; + ANYBUTTINYSET: begin + while (Result < TheMax) and //!!!TinySet + (scan^ <> opnd^) and (scan^ <> (opnd + 1)^) + and (scan^ <> (opnd + 2)^) do begin + inc (Result); + inc (scan); + end; + end; + {$IFDEF UseSetOfChar} //###0.929 + ANYOFFULLSET: begin + while (Result < TheMax) and + (scan^ in PSetOfREChar (opnd)^) do begin + inc (Result); + inc (scan); + end; + end; + {$ELSE} + ANYOF: + while (Result < TheMax) and + (StrScan (opnd, scan^) <> nil) do begin + inc (Result); + inc (scan); + end; + ANYBUT: + while (Result < TheMax) and + (StrScan (opnd, scan^) = nil) do begin + inc (Result); + inc (scan); + end; + ANYOFCI: + while (Result < TheMax) and (StrScanCI (opnd, scan^) <> nil) do begin + inc (Result); + inc (scan); + end; + ANYBUTCI: + while (Result < TheMax) and (StrScanCI (opnd, scan^) = nil) do begin + inc (Result); + inc (scan); + end; + {$ENDIF} + else begin // Oh dear. Called inappropriately. + Result := 0; // Best compromise. + Error (reeRegRepeatCalledInappropriately); + EXIT; + end; + end; { of case} + reginput := scan; + end; { of function TRegExpr.regrepeat +--------------------------------------------------------------} + +function TRegExpr.regnext (p : PRegExprChar) : PRegExprChar; +// dig the "next" pointer out of a node + var offset : TRENextOff; + begin + if p = @regdummy then begin + Result := nil; + EXIT; + end; + offset := PRENextOff (p + REOpSz)^; //###0.933 inlined NEXT + if offset = 0 + then Result := nil + else Result := p + offset; + end; { of function TRegExpr.regnext +--------------------------------------------------------------} + +function TRegExpr.MatchPrim (prog : PRegExprChar) : boolean; +// recursively matching routine +// Conceptually the strategy is simple: check to see whether the current +// node matches, call self recursively to see whether the rest matches, +// and then act accordingly. In practice we make some effort to avoid +// recursion, in particular by going through "ordinary" nodes (that don't +// need to know whether the rest of the match failed) by a loop instead of +// by recursion. + var + scan : PRegExprChar; // Current node. + next : PRegExprChar; // Next node. + len : integer; + opnd : PRegExprChar; + no : integer; + save : PRegExprChar; + nextch : REChar; + BracesMin, BracesMax : integer; // we use integer instead of TREBracesArg for better support */+ + {$IFDEF ComplexBraces} + SavedLoopStack : array [1 .. LoopStackMax] of integer; // :(( very bad for recursion + SavedLoopStackIdx : integer; //###0.925 + {$ENDIF} + begin + Result := false; + scan := prog; + + while scan <> nil do begin + len := PRENextOff (scan + 1)^; //###0.932 inlined regnext + if len = 0 + then next := nil + else next := scan + len; + + case scan^ of + NOTBOUND, //###0.943 //!!! think about UseSetOfChar !!! + BOUND: + if (scan^ = BOUND) + xor ( + ((reginput = fInputStart) or (Pos ((reginput - 1)^, fWordChars) <= 0)) + and (reginput^ <> #0) and (Pos (reginput^, fWordChars) > 0) + or + (reginput <> fInputStart) and (Pos ((reginput - 1)^, fWordChars) > 0) + and ((reginput^ = #0) or (Pos (reginput^, fWordChars) <= 0))) + then EXIT; + + BOL: if reginput <> fInputStart + then EXIT; + EOL: if reginput^ <> #0 + then EXIT; + BOLML: if reginput > fInputStart then begin + nextch := (reginput - 1)^; + if (nextch <> fLinePairedSeparatorTail) + or ((reginput - 1) <= fInputStart) + or ((reginput - 2)^ <> fLinePairedSeparatorHead) + then begin + if (nextch = fLinePairedSeparatorHead) + and (reginput^ = fLinePairedSeparatorTail) + then EXIT; // don't stop between paired separator + if + {$IFNDEF UniCode} + not (nextch in fLineSeparatorsSet) + {$ELSE} + (pos (nextch, fLineSeparators) <= 0) + {$ENDIF} + then EXIT; + end; + end; + EOLML: if reginput^ <> #0 then begin + nextch := reginput^; + if (nextch <> fLinePairedSeparatorHead) + or ((reginput + 1)^ <> fLinePairedSeparatorTail) + then begin + if (nextch = fLinePairedSeparatorTail) + and (reginput > fInputStart) + and ((reginput - 1)^ = fLinePairedSeparatorHead) + then EXIT; // don't stop between paired separator + if + {$IFNDEF UniCode} + not (nextch in fLineSeparatorsSet) + {$ELSE} + (pos (nextch, fLineSeparators) <= 0) + {$ENDIF} + then EXIT; + end; + end; + ANY: begin + if reginput^ = #0 + then EXIT; + inc (reginput); + end; + ANYML: begin //###0.941 + if (reginput^ = #0) + or ((reginput^ = fLinePairedSeparatorHead) + and ((reginput + 1)^ = fLinePairedSeparatorTail)) + or {$IFNDEF UniCode} (reginput^ in fLineSeparatorsSet) + {$ELSE} (pos (reginput^, fLineSeparators) > 0) {$ENDIF} + then EXIT; + inc (reginput); + end; + ANYDIGIT: begin + if (reginput^ = #0) or (reginput^ < '0') or (reginput^ > '9') + then EXIT; + inc (reginput); + end; + NOTDIGIT: begin + if (reginput^ = #0) or ((reginput^ >= '0') and (reginput^ <= '9')) + then EXIT; + inc (reginput); + end; + {$IFNDEF UseSetOfChar} //###0.929 + ANYLETTER: begin + if (reginput^ = #0) or (Pos (reginput^, fWordChars) <= 0) //###0.943 + then EXIT; + inc (reginput); + end; + NOTLETTER: begin + if (reginput^ = #0) or (Pos (reginput^, fWordChars) > 0) //###0.943 + then EXIT; + inc (reginput); + end; + ANYSPACE: begin + if (reginput^ = #0) or not (Pos (reginput^, fSpaceChars) > 0) //###0.943 + then EXIT; + inc (reginput); + end; + NOTSPACE: begin + if (reginput^ = #0) or (Pos (reginput^, fSpaceChars) > 0) //###0.943 + then EXIT; + inc (reginput); + end; + {$ENDIF} + EXACTLYCI: begin + opnd := scan + REOpSz + RENextOffSz; // OPERAND + // Inline the first character, for speed. + if (opnd^ <> reginput^) + and (InvertCase (opnd^) <> reginput^) + then EXIT; + len := strlen (opnd); + //###0.929 begin + no := len; + save := reginput; + while no > 1 do begin + inc (save); + inc (opnd); + if (opnd^ <> save^) + and (InvertCase (opnd^) <> save^) + then EXIT; + dec (no); + end; + //###0.929 end + inc (reginput, len); + end; + EXACTLY: begin + opnd := scan + REOpSz + RENextOffSz; // OPERAND + // Inline the first character, for speed. + if opnd^ <> reginput^ + then EXIT; + len := strlen (opnd); + //###0.929 begin + no := len; + save := reginput; + while no > 1 do begin + inc (save); + inc (opnd); + if opnd^ <> save^ + then EXIT; + dec (no); + end; + //###0.929 end + inc (reginput, len); + end; + BSUBEXP: begin //###0.936 + no := ord ((scan + REOpSz + RENextOffSz)^); + if startp [no] = nil + then EXIT; + if endp [no] = nil + then EXIT; + save := reginput; + opnd := startp [no]; + while opnd < endp [no] do begin + if (save >= fInputEnd) or (save^ <> opnd^) + then EXIT; + inc (save); + inc (opnd); + end; + reginput := save; + end; + BSUBEXPCI: begin //###0.936 + no := ord ((scan + REOpSz + RENextOffSz)^); + if startp [no] = nil + then EXIT; + if endp [no] = nil + then EXIT; + save := reginput; + opnd := startp [no]; + while opnd < endp [no] do begin + if (save >= fInputEnd) or + ((save^ <> opnd^) and (save^ <> InvertCase (opnd^))) + then EXIT; + inc (save); + inc (opnd); + end; + reginput := save; + end; + ANYOFTINYSET: begin + if (reginput^ = #0) or //!!!TinySet + ((reginput^ <> (scan + REOpSz + RENextOffSz)^) + and (reginput^ <> (scan + REOpSz + RENextOffSz + 1)^) + and (reginput^ <> (scan + REOpSz + RENextOffSz + 2)^)) + then EXIT; + inc (reginput); + end; + ANYBUTTINYSET: begin + if (reginput^ = #0) or //!!!TinySet + (reginput^ = (scan + REOpSz + RENextOffSz)^) + or (reginput^ = (scan + REOpSz + RENextOffSz + 1)^) + or (reginput^ = (scan + REOpSz + RENextOffSz + 2)^) + then EXIT; + inc (reginput); + end; + {$IFDEF UseSetOfChar} //###0.929 + ANYOFFULLSET: begin + if (reginput^ = #0) + or not (reginput^ in PSetOfREChar (scan + REOpSz + RENextOffSz)^) + then EXIT; + inc (reginput); + end; + {$ELSE} + ANYOF: begin + if (reginput^ = #0) or (StrScan (scan + REOpSz + RENextOffSz, reginput^) = nil) + then EXIT; + inc (reginput); + end; + ANYBUT: begin + if (reginput^ = #0) or (StrScan (scan + REOpSz + RENextOffSz, reginput^) <> nil) + then EXIT; + inc (reginput); + end; + ANYOFCI: begin + if (reginput^ = #0) or (StrScanCI (scan + REOpSz + RENextOffSz, reginput^) = nil) + then EXIT; + inc (reginput); + end; + ANYBUTCI: begin + if (reginput^ = #0) or (StrScanCI (scan + REOpSz + RENextOffSz, reginput^) <> nil) + then EXIT; + inc (reginput); + end; + {$ENDIF} + NOTHING: ; + COMMENT: ; + BACK: ; + Succ (OPEN) .. TREOp (Ord (OPEN) + NSUBEXP - 1) : begin //###0.929 + no := ord (scan^) - ord (OPEN); +// save := reginput; + save := startp [no]; //###0.936 + startp [no] := reginput; //###0.936 + Result := MatchPrim (next); + if not Result //###0.936 + then startp [no] := save; +// if Result and (startp [no] = nil) +// then startp [no] := save; + // Don't set startp if some later invocation of the same + // parentheses already has. + EXIT; + end; + Succ (CLOSE) .. TREOp (Ord (CLOSE) + NSUBEXP - 1): begin //###0.929 + no := ord (scan^) - ord (CLOSE); +// save := reginput; + save := endp [no]; //###0.936 + endp [no] := reginput; //###0.936 + Result := MatchPrim (next); + if not Result //###0.936 + then endp [no] := save; +// if Result and (endp [no] = nil) +// then endp [no] := save; + // Don't set endp if some later invocation of the same + // parentheses already has. + EXIT; + end; + BRANCH: begin + if (next^ <> BRANCH) // No choice. + then next := scan + REOpSz + RENextOffSz // Avoid recursion + else begin + REPEAT + save := reginput; + Result := MatchPrim (scan + REOpSz + RENextOffSz); + if Result + then EXIT; + reginput := save; + scan := regnext (scan); + UNTIL (scan = nil) or (scan^ <> BRANCH); + EXIT; + end; + end; + {$IFDEF ComplexBraces} + LOOPENTRY: begin //###0.925 + no := LoopStackIdx; + inc (LoopStackIdx); + if LoopStackIdx > LoopStackMax then begin + Error (reeLoopStackExceeded); + EXIT; + end; + save := reginput; + LoopStack [LoopStackIdx] := 0; // init loop counter + Result := MatchPrim (next); // execute LOOP + LoopStackIdx := no; // cleanup + if Result + then EXIT; + reginput := save; + EXIT; + end; + LOOP, LOOPNG: begin //###0.940 + if LoopStackIdx <= 0 then begin + Error (reeLoopWithoutEntry); + EXIT; + end; + opnd := scan + PRENextOff (scan + REOpSz + RENextOffSz + 2 * REBracesArgSz)^; + BracesMin := PREBracesArg (scan + REOpSz + RENextOffSz)^; + BracesMax := PREBracesArg (scan + REOpSz + RENextOffSz + REBracesArgSz)^; + save := reginput; + if LoopStack [LoopStackIdx] >= BracesMin then begin // Min alredy matched - we can work + if scan^ = LOOP then begin + // greedy way - first try to max deep of greed ;) + if LoopStack [LoopStackIdx] < BracesMax then begin + inc (LoopStack [LoopStackIdx]); + no := LoopStackIdx; + Result := MatchPrim (opnd); + LoopStackIdx := no; + if Result + then EXIT; + reginput := save; + end; + dec (LoopStackIdx); // Fail. May be we are too greedy? ;) + Result := MatchPrim (next); + if not Result + then reginput := save; + EXIT; + end + else begin + // non-greedy - try just now + Result := MatchPrim (next); + if Result + then EXIT + else reginput := save; // failed - move next and try again + if LoopStack [LoopStackIdx] < BracesMax then begin + inc (LoopStack [LoopStackIdx]); + no := LoopStackIdx; + Result := MatchPrim (opnd); + LoopStackIdx := no; + if Result + then EXIT; + reginput := save; + end; + dec (LoopStackIdx); // Failed - back up + EXIT; + end + end + else begin // first match a min_cnt times + inc (LoopStack [LoopStackIdx]); + no := LoopStackIdx; + Result := MatchPrim (opnd); + LoopStackIdx := no; + if Result + then EXIT; + dec (LoopStack [LoopStackIdx]); + reginput := save; + EXIT; + end; + end; + {$ENDIF} + STAR, PLUS, BRACES, STARNG, PLUSNG, BRACESNG: begin + // Lookahead to avoid useless match attempts when we know + // what character comes next. + nextch := #0; + if next^ = EXACTLY + then nextch := (next + REOpSz + RENextOffSz)^; + BracesMax := MaxInt; // infinite loop for * and + //###0.92 + if (scan^ = STAR) or (scan^ = STARNG) + then BracesMin := 0 // STAR + else if (scan^ = PLUS) or (scan^ = PLUSNG) + then BracesMin := 1 // PLUS + else begin // BRACES + BracesMin := PREBracesArg (scan + REOpSz + RENextOffSz)^; + BracesMax := PREBracesArg (scan + REOpSz + RENextOffSz + REBracesArgSz)^; + end; + save := reginput; + opnd := scan + REOpSz + RENextOffSz; + if (scan^ = BRACES) or (scan^ = BRACESNG) + then inc (opnd, 2 * REBracesArgSz); + + if (scan^ = PLUSNG) or (scan^ = STARNG) or (scan^ = BRACESNG) then begin + // non-greedy mode + BracesMax := regrepeat (opnd, BracesMax); // don't repeat more than BracesMax + // Now we know real Max limit to move forward (for recursion 'back up') + // In some cases it can be faster to check only Min positions first, + // but after that we have to check every position separtely instead + // of fast scannig in loop. + no := BracesMin; + while no <= BracesMax do begin + reginput := save + no; + // If it could work, try it. + if (nextch = #0) or (reginput^ = nextch) then begin + {$IFDEF ComplexBraces} + System.Move (LoopStack, SavedLoopStack, SizeOf (LoopStack)); //###0.925 + SavedLoopStackIdx := LoopStackIdx; + {$ENDIF} + if MatchPrim (next) then begin + Result := true; + EXIT; + end; + {$IFDEF ComplexBraces} + System.Move (SavedLoopStack, LoopStack, SizeOf (LoopStack)); + LoopStackIdx := SavedLoopStackIdx; + {$ENDIF} + end; + inc (no); // Couldn't or didn't - move forward. + end; { of while} + EXIT; + end + else begin // greedy mode + no := regrepeat (opnd, BracesMax); // don't repeat more than max_cnt + while no >= BracesMin do begin + // If it could work, try it. + if (nextch = #0) or (reginput^ = nextch) then begin + {$IFDEF ComplexBraces} + System.Move (LoopStack, SavedLoopStack, SizeOf (LoopStack)); //###0.925 + SavedLoopStackIdx := LoopStackIdx; + {$ENDIF} + if MatchPrim (next) then begin + Result := true; + EXIT; + end; + {$IFDEF ComplexBraces} + System.Move (SavedLoopStack, LoopStack, SizeOf (LoopStack)); + LoopStackIdx := SavedLoopStackIdx; + {$ENDIF} + end; + dec (no); // Couldn't or didn't - back up. + reginput := save + no; + end; { of while} + EXIT; + end; + end; + EEND: begin + Result := true; // Success! + EXIT; + end; + else begin + Error (reeMatchPrimMemoryCorruption); + EXIT; + end; + end; { of case scan^} + scan := next; + end; { of while scan <> nil} + + // We get here only if there's trouble -- normally "case EEND" is the + // terminating point. + Error (reeMatchPrimCorruptedPointers); + end; { of function TRegExpr.MatchPrim +--------------------------------------------------------------} + +{$IFDEF UseFirstCharSet} //###0.929 +procedure TRegExpr.FillFirstCharSet (prog : PRegExprChar); + var + scan : PRegExprChar; // Current node. + next : PRegExprChar; // Next node. + opnd : PRegExprChar; + min_cnt : integer; + begin + scan := prog; + while scan <> nil do begin + next := regnext (scan); + case PREOp (scan)^ of + BSUBEXP, BSUBEXPCI: begin //###0.938 + FirstCharSet := [#0 .. #255]; // :((( we cannot + // optimize r.e. if it starts with back reference + EXIT; + end; + BOL, BOLML: ; // EXIT; //###0.937 + EOL, EOLML: begin //###0.948 was empty in 0.947, was EXIT in 0.937 + Include (FirstCharSet, #0); + if ModifierM + then begin + opnd := PRegExprChar (LineSeparators); + while opnd^ <> #0 do begin + Include (FirstCharSet, opnd^); + inc (opnd); + end; + end; + EXIT; + end; + BOUND, NOTBOUND: ; //###0.943 ?!! + ANY, ANYML: begin // we can better define ANYML !!! + FirstCharSet := [#0 .. #255]; //###0.930 + EXIT; + end; + ANYDIGIT: begin + FirstCharSet := FirstCharSet + ['0' .. '9']; + EXIT; + end; + NOTDIGIT: begin + FirstCharSet := FirstCharSet + ([#0 .. #255] - ['0' .. '9']); //###0.948 FirstCharSet was forgotten + EXIT; + end; + EXACTLYCI: begin + Include (FirstCharSet, (scan + REOpSz + RENextOffSz)^); + Include (FirstCharSet, InvertCase ((scan + REOpSz + RENextOffSz)^)); + EXIT; + end; + EXACTLY: begin + Include (FirstCharSet, (scan + REOpSz + RENextOffSz)^); + EXIT; + end; + ANYOFFULLSET: begin + FirstCharSet := FirstCharSet + PSetOfREChar (scan + REOpSz + RENextOffSz)^; + EXIT; + end; + ANYOFTINYSET: begin + //!!!TinySet + Include (FirstCharSet, (scan + REOpSz + RENextOffSz)^); + Include (FirstCharSet, (scan + REOpSz + RENextOffSz + 1)^); + Include (FirstCharSet, (scan + REOpSz + RENextOffSz + 2)^); + // ... // up to TinySetLen + EXIT; + end; + ANYBUTTINYSET: begin + //!!!TinySet + FirstCharSet := FirstCharSet + ([#0 .. #255] - [ //###0.948 FirstCharSet was forgotten + (scan + REOpSz + RENextOffSz)^, + (scan + REOpSz + RENextOffSz + 1)^, + (scan + REOpSz + RENextOffSz + 2)^]); + // ... // up to TinySetLen + EXIT; + end; + NOTHING: ; + COMMENT: ; + BACK: ; + Succ (OPEN) .. TREOp (Ord (OPEN) + NSUBEXP - 1) : begin //###0.929 + FillFirstCharSet (next); + EXIT; + end; + Succ (CLOSE) .. TREOp (Ord (CLOSE) + NSUBEXP - 1): begin //###0.929 + FillFirstCharSet (next); + EXIT; + end; + BRANCH: begin + if (PREOp (next)^ <> BRANCH) // No choice. + then next := scan + REOpSz + RENextOffSz // Avoid recursion. + else begin + REPEAT + FillFirstCharSet (scan + REOpSz + RENextOffSz); + scan := regnext (scan); + UNTIL (scan = nil) or (PREOp (scan)^ <> BRANCH); + EXIT; + end; + end; + {$IFDEF ComplexBraces} + LOOPENTRY: begin //###0.925 +// LoopStack [LoopStackIdx] := 0; //###0.940 line removed + FillFirstCharSet (next); // execute LOOP + EXIT; + end; + LOOP, LOOPNG: begin //###0.940 + opnd := scan + PRENextOff (scan + REOpSz + RENextOffSz + REBracesArgSz * 2)^; + min_cnt := PREBracesArg (scan + REOpSz + RENextOffSz)^; + FillFirstCharSet (opnd); + if min_cnt = 0 + then FillFirstCharSet (next); + EXIT; + end; + {$ENDIF} + STAR, STARNG: //###0.940 + FillFirstCharSet (scan + REOpSz + RENextOffSz); + PLUS, PLUSNG: begin //###0.940 + FillFirstCharSet (scan + REOpSz + RENextOffSz); + EXIT; + end; + BRACES, BRACESNG: begin //###0.940 + opnd := scan + REOpSz + RENextOffSz + REBracesArgSz * 2; + min_cnt := PREBracesArg (scan + REOpSz + RENextOffSz)^; // BRACES + FillFirstCharSet (opnd); + if min_cnt > 0 + then EXIT; + end; + EEND: begin + FirstCharSet := [#0 .. #255]; //###0.948 + EXIT; + end; + else begin + Error (reeMatchPrimMemoryCorruption); + EXIT; + end; + end; { of case scan^} + scan := next; + end; { of while scan <> nil} + end; { of procedure FillFirstCharSet +--------------------------------------------------------------} +{$ENDIF} + +function TRegExpr.Exec (const AInputString : RegExprString) : boolean; + begin + InputString := AInputString; + Result := ExecPrim (1); + end; { of function TRegExpr.Exec +--------------------------------------------------------------} + +{$IFDEF OverMeth} +{$IFNDEF FPC} +function TRegExpr.Exec : boolean; + begin + Result := ExecPrim (1); + end; { of function TRegExpr.Exec +--------------------------------------------------------------} +{$ENDIF} +function TRegExpr.Exec (AOffset: integer) : boolean; + begin + Result := ExecPrim (AOffset); + end; { of function TRegExpr.Exec +--------------------------------------------------------------} +{$ENDIF} + +function TRegExpr.ExecPos (AOffset: integer {$IFDEF DefParam}= 1{$ENDIF}) : boolean; + begin + Result := ExecPrim (AOffset); + end; { of function TRegExpr.ExecPos +--------------------------------------------------------------} + +function TRegExpr.ExecPrim (AOffset: integer) : boolean; + procedure ClearMatchs; + // Clears matchs array + var i : integer; + begin + for i := 0 to NSUBEXP - 1 do begin + startp [i] := nil; + endp [i] := nil; + end; + end; { of procedure ClearMatchs; +..............................................................} + function RegMatch (str : PRegExprChar) : boolean; + // try match at specific point + begin + //###0.949 removed clearing of start\endp + reginput := str; + Result := MatchPrim (programm + REOpSz); + if Result then begin + startp [0] := str; + endp [0] := reginput; + end; + end; { of function RegMatch +..............................................................} + var + s : PRegExprChar; + StartPtr: PRegExprChar; + InputLen : integer; + begin + Result := false; // Be paranoid... + + ClearMatchs; //###0.949 + // ensure that Match cleared either if optimization tricks or some error + // will lead to leaving ExecPrim without actual search. That is + // importent for ExecNext logic and so on. + + if not IsProgrammOk //###0.929 + then EXIT; + + // Check InputString presence + if not Assigned (fInputString) then begin + Error (reeNoInpitStringSpecified); + EXIT; + end; + + InputLen := length (fInputString); + + //Check that the start position is not negative + if AOffset < 1 then begin + Error (reeOffsetMustBeGreaterThen0); + EXIT; + end; + // Check that the start position is not longer than the line + // If so then exit with nothing found + if AOffset > (InputLen + 1) // for matching empty string after last char. + then EXIT; + + StartPtr := fInputString + AOffset - 1; + + // If there is a "must appear" string, look for it. + if regmust <> nil then begin + s := StartPtr; + REPEAT + s := StrScan (s, regmust [0]); + if s <> nil then begin + if StrLComp (s, regmust, regmlen) = 0 + then BREAK; // Found it. + inc (s); + end; + UNTIL s = nil; + if s = nil // Not present. + then EXIT; + end; + + // Mark beginning of line for ^ . + fInputStart := fInputString; + + // Pointer to end of input stream - for + // pascal-style string processing (may include #0) + fInputEnd := fInputString + InputLen; + + {$IFDEF ComplexBraces} + // no loops started + LoopStackIdx := 0; //###0.925 + {$ENDIF} + + // Simplest case: anchored match need be tried only once. + if reganch <> #0 then begin + Result := RegMatch (StartPtr); + EXIT; + end; + + // Messy cases: unanchored match. + s := StartPtr; + if regstart <> #0 then // We know what char it must start with. + REPEAT + s := StrScan (s, regstart); + if s <> nil then begin + Result := RegMatch (s); + if Result + then EXIT + else ClearMatchs; //###0.949 + inc (s); + end; + UNTIL s = nil + else begin // We don't - general case. + repeat //###0.948 + {$IFDEF UseFirstCharSet} + if s^ in FirstCharSet + then Result := RegMatch (s); + {$ELSE} + Result := RegMatch (s); + {$ENDIF} + if Result or (s^ = #0) // Exit on a match or after testing the end-of-string. + then EXIT + else ClearMatchs; //###0.949 + inc (s); + until false; +(* optimized and fixed by Martin Fuller - empty strings + were not allowed to pass thru in UseFirstCharSet mode + {$IFDEF UseFirstCharSet} //###0.929 + while s^ <> #0 do begin + if s^ in FirstCharSet + then Result := RegMatch (s); + if Result + then EXIT; + inc (s); + end; + {$ELSE} + REPEAT + Result := RegMatch (s); + if Result + then EXIT; + inc (s); + UNTIL s^ = #0; + {$ENDIF} +*) + end; + // Failure + end; { of function TRegExpr.ExecPrim +--------------------------------------------------------------} + +function TRegExpr.ExecNext : boolean; + var offset : integer; + begin + Result := false; + if not Assigned (startp[0]) or not Assigned (endp[0]) then begin + Error (reeExecNextWithoutExec); + EXIT; + end; +// Offset := MatchPos [0] + MatchLen [0]; +// if MatchLen [0] = 0 + Offset := endp [0] - fInputString + 1; //###0.929 + if endp [0] = startp [0] //###0.929 + then inc (Offset); // prevent infinite looping if empty string match r.e. + Result := ExecPrim (Offset); + end; { of function TRegExpr.ExecNext +--------------------------------------------------------------} + +function TRegExpr.GetInputString : RegExprString; + begin + if not Assigned (fInputString) then begin + Error (reeGetInputStringWithoutInputString); + EXIT; + end; + Result := fInputString; + end; { of function TRegExpr.GetInputString +--------------------------------------------------------------} + +procedure TRegExpr.SetInputString (const AInputString : RegExprString); + var + Len : integer; + i : integer; + begin + // clear Match* - before next Exec* call it's undefined + for i := 0 to NSUBEXP - 1 do begin + startp [i] := nil; + endp [i] := nil; + end; + + // need reallocation of input string buffer ? + Len := length (AInputString); + if Assigned (fInputString) and (Length (fInputString) <> Len) then begin + FreeMem (fInputString); + fInputString := nil; + end; + // buffer [re]allocation + if not Assigned (fInputString) + then GetMem (fInputString, (Len + 1) * SizeOf (REChar)); + + // copy input string into buffer + {$IFDEF UniCode} + StrPCopy (fInputString, Copy (AInputString, 1, Len)); //###0.927 + {$ELSE} + StrLCopy (fInputString, PRegExprChar (AInputString), Len); + {$ENDIF} + + { + fInputString : string; + fInputStart, fInputEnd : PRegExprChar; + + SetInputString: + fInputString := AInputString; + UniqueString (fInputString); + fInputStart := PChar (fInputString); + Len := length (fInputString); + fInputEnd := PRegExprChar (integer (fInputStart) + Len); ?? + !! startp/endp все равно будет опасно использовать ? + } + end; { of procedure TRegExpr.SetInputString +--------------------------------------------------------------} + +procedure TRegExpr.SetLineSeparators (const AStr : RegExprString); + begin + if AStr <> fLineSeparators then begin + fLineSeparators := AStr; + InvalidateProgramm; + end; + end; { of procedure TRegExpr.SetLineSeparators +--------------------------------------------------------------} + +procedure TRegExpr.SetLinePairedSeparator (const AStr : RegExprString); + begin + if length (AStr) = 2 then begin + if AStr [1] = AStr [2] then begin + // it's impossible for our 'one-point' checking to support + // two chars separator for identical chars + Error (reeBadLinePairedSeparator); + EXIT; + end; + if not fLinePairedSeparatorAssigned + or (AStr [1] <> fLinePairedSeparatorHead) + or (AStr [2] <> fLinePairedSeparatorTail) then begin + fLinePairedSeparatorAssigned := true; + fLinePairedSeparatorHead := AStr [1]; + fLinePairedSeparatorTail := AStr [2]; + InvalidateProgramm; + end; + end + else if length (AStr) = 0 then begin + if fLinePairedSeparatorAssigned then begin + fLinePairedSeparatorAssigned := false; + InvalidateProgramm; + end; + end + else Error (reeBadLinePairedSeparator); + end; { of procedure TRegExpr.SetLinePairedSeparator +--------------------------------------------------------------} + +function TRegExpr.GetLinePairedSeparator : RegExprString; + begin + if fLinePairedSeparatorAssigned then begin + {$IFDEF UniCode} + // Here is some UniCode 'magic' + // If You do know better decision to concatenate + // two WideChars, please, let me know! + Result := fLinePairedSeparatorHead; //###0.947 + Result := Result + fLinePairedSeparatorTail; + {$ELSE} + Result := fLinePairedSeparatorHead + fLinePairedSeparatorTail; + {$ENDIF} + end + else Result := ''; + end; { of function TRegExpr.GetLinePairedSeparator +--------------------------------------------------------------} + +function TRegExpr.Substitute (const ATemplate : RegExprString) : RegExprString; +// perform substitutions after a regexp match +// completely rewritten in 0.929 +type + TSubstMode = (smodeNormal, smodeOneUpper, smodeOneLower, smodeAllUpper, + smodeAllLower); +var + TemplateLen : integer; + TemplateBeg, TemplateEnd : PRegExprChar; + p, p0, p1, ResultPtr : PRegExprChar; + ResultLen : integer; + n : integer; + Ch : REChar; + Mode: TSubstMode; + LineEnd: String = LineEnding; + + function ParseVarName (var APtr : PRegExprChar) : integer; + // extract name of variable (digits, may be enclosed with + // curly braces) from APtr^, uses TemplateEnd !!! + const + Digits = ['0' .. '9']; + var + p : PRegExprChar; + Delimited : boolean; + begin + Result := 0; + p := APtr; + Delimited := (p < TemplateEnd) and (p^ = '{'); + if Delimited + then inc (p); // skip left curly brace + if (p < TemplateEnd) and (p^ = '&') + then inc (p) // this is '$&' or '${&}' + else + while (p < TemplateEnd) and + {$IFDEF UniCode} //###0.935 + (ord (p^) < 256) and (char (p^) in Digits) + {$ELSE} + (p^ in Digits) + {$ENDIF} + do begin + Result := Result * 10 + (ord (p^) - ord ('0')); //###0.939 + inc (p); + end; + if Delimited then + if (p < TemplateEnd) and (p^ = '}') + then inc (p) // skip right curly brace + else p := APtr; // isn't properly terminated + if p = APtr + then Result := -1; // no valid digits found or no right curly brace + APtr := p; + end; + +begin + // Check programm and input string + if not IsProgrammOk + then EXIT; + if not Assigned (fInputString) then begin + Error (reeNoInpitStringSpecified); + EXIT; + end; + // Prepare for working + TemplateLen := length (ATemplate); + if TemplateLen = 0 then begin // prevent nil pointers + Result := ''; + EXIT; + end; + TemplateBeg := pointer (ATemplate); + TemplateEnd := TemplateBeg + TemplateLen; + // Count result length for speed optimization. + ResultLen := 0; + p := TemplateBeg; + while p < TemplateEnd do begin + Ch := p^; + inc (p); + if Ch = '$' + then n := ParseVarName (p) + else n := -1; + if n >= 0 then begin + if (n < NSUBEXP) and Assigned (startp [n]) and Assigned (endp [n]) + then inc (ResultLen, endp [n] - startp [n]); + end + else begin + if (Ch = EscChar) and (p < TemplateEnd) then begin // quoted or special char followed + Ch := p^; + inc (p); + case Ch of + 'n' : inc(ResultLen, Length(LineEnding)); + 'u', 'l', 'U', 'L': {nothing}; + else inc(ResultLen); + end; + end + else + inc(ResultLen); + end; + end; + // Get memory. We do it once and it significant speed up work ! + if ResultLen = 0 then begin + Result := ''; + EXIT; + end; + SetString (Result, nil, ResultLen); + // Fill Result + ResultPtr := pointer (Result); + p := TemplateBeg; + Mode := smodeNormal; + while p < TemplateEnd do begin + Ch := p^; + p0 := p; + inc (p); + p1 := p; + if Ch = '$' + then n := ParseVarName (p) + else n := -1; + if (n >= 0) then begin + p0 := startp[n]; + p1 := endp[n]; + if (n >= NSUBEXP) or not Assigned (p0) or not Assigned (endp [n]) then + p1 := p0; // empty + end + else begin + if (Ch = EscChar) and (p < TemplateEnd) then begin // quoted or special char followed + Ch := p^; + inc (p); + case Ch of + 'n' : begin + p0 := @LineEnd[1]; + p1 := p0 + Length(LineEnding); + end; + 'l' : begin + Mode := smodeOneLower; + p1 := p0; + end; + 'L' : begin + Mode := smodeAllLower; + p1 := p0; + end; + 'u' : begin + Mode := smodeOneUpper; + p1 := p0; + end; + 'U' : begin + Mode := smodeAllUpper; + p1 := p0; + end; + else + begin + inc(p0); + inc(p1); + end; + end; + end + end; + if p0 < p1 then begin + while p0 < p1 do begin + case Mode of + smodeOneLower, smodeAllLower: + begin + Ch := p0^; + if Ch < #128 then + Ch := AnsiLowerCase(Ch)[1]; + ResultPtr^ := Ch; + if Mode = smodeOneLower then + Mode := smodeNormal; + end; + smodeOneUpper, smodeAllUpper: + begin + Ch := p0^; + if Ch < #128 then + Ch := AnsiUpperCase(Ch)[1]; + ResultPtr^ := Ch; + if Mode = smodeOneUpper then + Mode := smodeNormal; + end; + else + ResultPtr^ := p0^; + end; + inc (ResultPtr); + inc (p0); + end; + Mode := smodeNormal; + end; + end; +end; { of function TRegExpr.Substitute +--------------------------------------------------------------} + +procedure TRegExpr.Split (AInputStr : RegExprString; APieces : TStrings); + var PrevPos : integer; + begin + PrevPos := 1; + if Exec (AInputStr) then + REPEAT + APieces.Add (System.Copy (AInputStr, PrevPos, MatchPos [0] - PrevPos)); + PrevPos := MatchPos [0] + MatchLen [0]; + UNTIL not ExecNext; + APieces.Add (System.Copy (AInputStr, PrevPos, MaxInt)); // Tail + end; { of procedure TRegExpr.Split +--------------------------------------------------------------} + +function TRegExpr.Replace (AInputStr : RegExprString; const AReplaceStr : RegExprString; + AUseSubstitution : boolean{$IFDEF DefParam}= False{$ENDIF}) : RegExprString; + var + PrevPos : integer; + begin + Result := ''; + PrevPos := 1; + if Exec (AInputStr) then + REPEAT + Result := Result + System.Copy (AInputStr, PrevPos, + MatchPos [0] - PrevPos); + if AUseSubstitution //###0.946 + then Result := Result + Substitute (AReplaceStr) + else Result := Result + AReplaceStr; + PrevPos := MatchPos [0] + MatchLen [0]; + UNTIL not ExecNext; + Result := Result + System.Copy (AInputStr, PrevPos, MaxInt); // Tail + end; { of function TRegExpr.Replace +--------------------------------------------------------------} + +function TRegExpr.ReplaceEx (AInputStr : RegExprString; + AReplaceFunc : TRegExprReplaceFunction) + : RegExprString; + var + PrevPos : integer; + begin + Result := ''; + PrevPos := 1; + if Exec (AInputStr) then + REPEAT + Result := Result + System.Copy (AInputStr, PrevPos, + MatchPos [0] - PrevPos) + + AReplaceFunc (Self); + PrevPos := MatchPos [0] + MatchLen [0]; + UNTIL not ExecNext; + Result := Result + System.Copy (AInputStr, PrevPos, MaxInt); // Tail + end; { of function TRegExpr.ReplaceEx +--------------------------------------------------------------} + + +{$IFDEF OverMeth} +function TRegExpr.Replace (AInputStr : RegExprString; + AReplaceFunc : TRegExprReplaceFunction) + : RegExprString; + begin + {$IFDEF SYN_LAZARUS}Result:={$ENDIF}ReplaceEx (AInputStr, AReplaceFunc); + end; { of function TRegExpr.Replace +--------------------------------------------------------------} +{$ENDIF} + +{=============================================================} +{====================== Debug section ========================} +{=============================================================} + +{$IFDEF RegExpPCodeDump} +function TRegExpr.DumpOp (op : TREOp) : RegExprString; +// printable representation of opcode + begin + case op of + BOL: Result := 'BOL'; + EOL: Result := 'EOL'; + BOLML: Result := 'BOLML'; + EOLML: Result := 'EOLML'; + BOUND: Result := 'BOUND'; //###0.943 + NOTBOUND: Result := 'NOTBOUND'; //###0.943 + ANY: Result := 'ANY'; + ANYML: Result := 'ANYML'; //###0.941 + ANYLETTER: Result := 'ANYLETTER'; + NOTLETTER: Result := 'NOTLETTER'; + ANYDIGIT: Result := 'ANYDIGIT'; + NOTDIGIT: Result := 'NOTDIGIT'; + ANYSPACE: Result := 'ANYSPACE'; + NOTSPACE: Result := 'NOTSPACE'; + ANYOF: Result := 'ANYOF'; + ANYBUT: Result := 'ANYBUT'; + ANYOFCI: Result := 'ANYOF/CI'; + ANYBUTCI: Result := 'ANYBUT/CI'; + BRANCH: Result := 'BRANCH'; + EXACTLY: Result := 'EXACTLY'; + EXACTLYCI: Result := 'EXACTLY/CI'; + NOTHING: Result := 'NOTHING'; + COMMENT: Result := 'COMMENT'; + BACK: Result := 'BACK'; + EEND: Result := 'END'; + BSUBEXP: Result := 'BSUBEXP'; + BSUBEXPCI: Result := 'BSUBEXP/CI'; + Succ (OPEN) .. TREOp (Ord (OPEN) + NSUBEXP - 1): //###0.929 + Result := Format ('OPEN[%d]', [ord (op) - ord (OPEN)]); + Succ (CLOSE) .. TREOp (Ord (CLOSE) + NSUBEXP - 1): //###0.929 + Result := Format ('CLOSE[%d]', [ord (op) - ord (CLOSE)]); + STAR: Result := 'STAR'; + PLUS: Result := 'PLUS'; + BRACES: Result := 'BRACES'; + {$IFDEF ComplexBraces} + LOOPENTRY: Result := 'LOOPENTRY'; //###0.925 + LOOP: Result := 'LOOP'; //###0.925 + LOOPNG: Result := 'LOOPNG'; //###0.940 + {$ENDIF} + ANYOFTINYSET: Result:= 'ANYOFTINYSET'; + ANYBUTTINYSET:Result:= 'ANYBUTTINYSET'; + {$IFDEF UseSetOfChar} //###0.929 + ANYOFFULLSET: Result:= 'ANYOFFULLSET'; + {$ENDIF} + STARNG: Result := 'STARNG'; //###0.940 + PLUSNG: Result := 'PLUSNG'; //###0.940 + BRACESNG: Result := 'BRACESNG'; //###0.940 + else Error (reeDumpCorruptedOpcode); + end; {of case op} + Result := ':' + Result; + end; { of function TRegExpr.DumpOp +--------------------------------------------------------------} + +function TRegExpr.Dump : RegExprString; +// dump a regexp in vaguely comprehensible form + var + s : PRegExprChar; + op : TREOp; // Arbitrary non-END op. + next : PRegExprChar; + i : integer; + Diff : integer; +{$IFDEF UseSetOfChar} //###0.929 + Ch : REChar; +{$ENDIF} + begin + if not IsProgrammOk //###0.929 + then EXIT; + + op := EXACTLY; + Result := ''; + s := programm + REOpSz; + while op <> EEND do begin // While that wasn't END last time... + op := s^; + Result := Result + Format ('%2d%s', [s - programm, DumpOp (s^)]); // Where, what. + next := regnext (s); + if next = nil // Next ptr. + then Result := Result + ' (0)' + else begin + if next > s //###0.948 PWideChar subtraction workaround (see comments in Tail method for details) + then Diff := next - s + else Diff := - (s - next); + Result := Result + Format (' (%d) ', [(s - programm) + Diff]); + end; + inc (s, REOpSz + RENextOffSz); + if (op = ANYOF) or (op = ANYOFCI) or (op = ANYBUT) or (op = ANYBUTCI) + or (op = EXACTLY) or (op = EXACTLYCI) then begin + // Literal string, where present. + while s^ <> #0 do begin + Result := Result + s^; + inc (s); + end; + inc (s); + end; + if (op = ANYOFTINYSET) or (op = ANYBUTTINYSET) then begin + for i := 1 to TinySetLen do begin + Result := Result + s^; + inc (s); + end; + end; + if (op = BSUBEXP) or (op = BSUBEXPCI) then begin + Result := Result + ' \' + IntToStr (Ord (s^)); + inc (s); + end; + {$IFDEF UseSetOfChar} //###0.929 + if op = ANYOFFULLSET then begin + for Ch := #0 to #255 do + if Ch in PSetOfREChar (s)^ then + if Ch < ' ' + then Result := Result + '#' + IntToStr (Ord (Ch)) //###0.936 + else Result := Result + Ch; + inc (s, SizeOf (TSetOfREChar)); + end; + {$ENDIF} + if (op = BRACES) or (op = BRACESNG) then begin //###0.941 + // show min/max argument of BRACES operator + Result := Result + Format ('{%d,%d}', [PREBracesArg (s)^, PREBracesArg (s + REBracesArgSz)^]); + inc (s, REBracesArgSz * 2); + end; + {$IFDEF ComplexBraces} + if (op = LOOP) or (op = LOOPNG) then begin //###0.940 + Result := Result + Format (' -> (%d) {%d,%d}', [ + (s - programm - (REOpSz + RENextOffSz)) + PRENextOff (s + 2 * REBracesArgSz)^, + PREBracesArg (s)^, PREBracesArg (s + REBracesArgSz)^]); + inc (s, 2 * REBracesArgSz + RENextOffSz); + end; + {$ENDIF} + Result := Result + #$d#$a; + end; { of while} + + // Header fields of interest. + + if regstart <> #0 + then Result := Result + 'start ' + regstart; + if reganch <> #0 + then Result := Result + 'anchored '; + if regmust <> nil + then Result := Result + 'must have ' + regmust; + {$IFDEF UseFirstCharSet} //###0.929 + Result := Result + #$d#$a'FirstCharSet:'; + for Ch := #0 to #255 do + if Ch in FirstCharSet + then begin + if Ch < ' ' + then Result := Result + '#' + IntToStr(Ord(Ch)) //###0.948 + else Result := Result + Ch; + end; + {$ENDIF} + Result := Result + #$d#$a; + end; { of function TRegExpr.Dump +--------------------------------------------------------------} +{$ENDIF} + +{$IFDEF reRealExceptionAddr} +{$OPTIMIZATION ON} +// ReturnAddr works correctly only if compiler optimization is ON +// I placed this method at very end of unit because there are no +// way to restore compiler optimization flag ... +{$ENDIF} +procedure TRegExpr.Error (AErrorID : integer); +{$IFDEF reRealExceptionAddr} + function ReturnAddr : pointer; //###0.938 + asm + mov eax,[ebp+4] + end; +{$ENDIF} + var + e : ERegExpr; + begin + fLastError := AErrorID; // dummy stub - useless because will raise exception + if AErrorID < 1000 // compilation error ? + then e := ERegExpr.Create (ErrorMsg (AErrorID) // yes - show error pos + + ' (pos ' + IntToStr (CompilerErrorPos) + ')') + else e := ERegExpr.Create (ErrorMsg (AErrorID)); + e.ErrorCode := AErrorID; + e.CompilerErrorPos := CompilerErrorPos; + raise e + {$IFDEF reRealExceptionAddr} + At ReturnAddr; //###0.938 + {$ENDIF} + end; { of procedure TRegExpr.Error +--------------------------------------------------------------} + +(* + PCode persistence: + FirstCharSet + programm, regsize + regstart // -> programm + reganch // -> programm + regmust, regmlen // -> programm + fExprIsCompiled +*) + +// be carefull - placed here code will be always compiled with +// compiler optimization flag + +{$IFDEF FPC} +initialization + RegExprInvertCaseFunction := TRegExpr.InvertCaseFunction; + +{$ENDIF} +end. + diff --git a/examples/apps/ide/src/templates/default.project b/examples/apps/ide/src/templates/default.project new file mode 100644 index 00000000..bbab8e40 --- /dev/null +++ b/examples/apps/ide/src/templates/default.project @@ -0,0 +1,39 @@ +[ProjectOptions] +ProjectDir=/media/flash16gig/programming/fpgide/src/ +ProjectName=default.project +MainUnit=default.pas +TargetFile=default${EXEEXT} +DefaultMake=0 +UnitOutputDir=units/${TARGET}/ +MakeOptionsCount=4 +MakeOptionEnabled1=1,1,1,1,1,1 +MakeOptionEnabled2=1,1,1,1,1,0 +MakeOptionEnabled3=0,1,0,0,0,1 +MakeOptionEnabled4=0,0,0,0,0,1 +MacroCount=6 +Macro1=TargetCPU=x86_64 +Macro2=TargetOS=linux +Macro3=TargetCPU=i386 +Macro4=TargetOS=win32 +Macro5=FPGUI_DIR=/home/graemeg/programming/fpgui/ +Macro6=tiOPF_fpGUI_Dir=/home/graemeg/programming/3rdParty/tiOPF2/src/ +UnitDirsCount=6 +UnitDirEnabled1=1,1,1,1,0,0,0,1,0,0 +UnitDirEnabled2=1,1,1,1,0,0,1,1,0,0 +UnitDirEnabled3=1,1,1,0,0,0,1,1,0,0 +UnitDirEnabled4=0,0,0,1,0,0,1,1,0,0 +UnitDirEnabled5=1,1,1,1,0,0,1,0,0,0 +UnitDirEnabled6=1,1,1,1,0,0,1,0,0,0 +MakeOption1=-l -Mobjfpc -Sch +MakeOption2=-gl -O- +MakeOption3=-B +MakeOption4=-O2 -XX -Xs -CX +UnitDir1=${FPGUIDIR}src/ +UnitDir2=${FPGUIDIR}src/corelib/ +UnitDir3=${FPGUIDIR}src/corelib/x11/ +UnitDir4=${FPGUIDIR}src/corelib/gdi/ +UnitDir5=${FPGUIDIR}src/gui/ +UnitDir6=${FPGUIDIR}src/gui/db/ + +[Units] +UnitCount=0 diff --git a/examples/apps/ide/src/templates/empty.project b/examples/apps/ide/src/templates/empty.project new file mode 100644 index 00000000..b775eb1b --- /dev/null +++ b/examples/apps/ide/src/templates/empty.project @@ -0,0 +1,29 @@ +[ProjectOptions] +ProjectDir=/media/flash16gig/programming/fpgide/src/ +ProjectName=fpgide +MainUnit=empty.lpr +TargetFile=empty +DefaultMake=0 +UnitOutputDir=units/i386-linux/ +MakeOptionsCount=4 +MakeOption1=-l -Mobjfpc -Sch +MakeOption2=-gl -O- +MakeOption3=-B +MakeOption4=-O2 -XX -Xs -CX +MakeOptionEnabled1=1,1,1,1,1,1 +MakeOptionEnabled2=1,1,1,1,1,0 +MakeOptionEnabled3=0,1,0,0,0,1 +MakeOptionEnabled4=0,0,0,0,0,1 +MacroCount=6 +Macro1=TargetCPU=x86_64 +Macro2=TargetOS=linux +Macro3=TargetCPU=i386 +Macro4=TargetOS=win32 +Macro5=FPGUI_DIR=/home/graemeg/programming/fpgui/ +Macro6=tiOPF_fpGUI_Dir=/home/graemeg/programming/3rdParty/tiOPF2/src/ +UnitDirsCount=1 +UnitDir1=./ +UnitDirEnabled1=1,1,1,1,1,1,1,0,0,0 + +[Units] +UnitCount=0 diff --git a/examples/apps/ide/src/unitlist.pas b/examples/apps/ide/src/unitlist.pas new file mode 100644 index 00000000..b8ca59ad --- /dev/null +++ b/examples/apps/ide/src/unitlist.pas @@ -0,0 +1,170 @@ +unit UnitList; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, fpg_base; + +type + TUnit = class(TObject) + private + FFilename: TfpgString; + FOpened: Boolean; + function GetUnitName: TfpgString; + public + constructor Create; + property FileName: TfpgString read FFilename write FFilename; + property UnitName: TfpgString read GetUnitName; + property Opened: Boolean read FOpened write FOpened; + end; + + + TUnitList = class(TObject) + private + FList: TList; + function GetItems(AIndex: integer): TUnit; + procedure SetItems(AIndex: integer; const AValue: TUnit); + public + constructor Create; + destructor Destroy; override; + function Count: integer; + function FindByName(const AUnitName: TfpgString): TUnit; + function FileExists(const AFilename: TfpgString): Boolean; + procedure Add(NewUnit: TUnit); + procedure Clear; + procedure Delete(AIndex: integer); + property Items[AIndex: integer]: TUnit read GetItems write SetItems; default; + end; + + +implementation + +uses + fpg_utils; + +{ TUnitList } + +function TUnitList.GetItems(AIndex: integer): TUnit; +begin + Result := TUnit(FList[AIndex]); +end; + +procedure TUnitList.SetItems(AIndex: integer; const AValue: TUnit); +begin + FList[AIndex] := AValue; +end; + +constructor TUnitList.Create; +begin + FList := TList.Create; + inherited Create; +end; + +destructor TUnitList.Destroy; +begin + Clear; + FList.Free; + inherited Destroy; +end; + +function TUnitList.Count: integer; +begin + Result := FList.Count; +end; + +function TUnitList.FindByName(const AUnitName: TfpgString): TUnit; +var + l: Integer; + r: Integer; + m: Integer; + cmp: Integer; +begin + l := 0; + r := Count-1; + m := 0; + while l <= r do + begin + m := (l+r) shr 1; + Result := Items[m]; + cmp := AnsiCompareText(AUnitName, Result.UnitName); + if cmp < 0 then + r := m-1 + else if cmp > 0 then + l := m+1 + else + exit; + end; + Result := nil; +end; + +function TUnitList.FileExists(const AFilename: TfpgString): Boolean; +var + i: integer; +begin + Result := False; + for i := 0 to Count-1 do + begin + Result := Items[i].FileName = AFilename; + if Result then + Exit; + end; +end; + +procedure TUnitList.Add(NewUnit: TUnit); +var + l: Integer; + r: Integer; + m: Integer; + cmp: Integer; +begin + l := 0; + r := Count-1; + m := 0; + while l <= r do + begin + m := (l+r) shr 1; + cmp := AnsiCompareText(NewUnit.UnitName, Items[m].UnitName); + if cmp < 0 then + r := m-1 + else if cmp > 0 then + l := m + 1 + else + break; + end; + if (m < Count) and (AnsiCompareText(NewUnit.UnitName, Items[m].UnitName) > 0) then + inc(m); + FList.Insert(m, NewUnit); +end; + +procedure TUnitList.Clear; +var + i: integer; +begin + for i := Count-1 downto 0 do + TUnit(FList[i]).Free; + FList.Clear; +end; + +procedure TUnitList.Delete(AIndex: integer); +begin + TUnit(FList[AIndex]).Free; + FList.Delete(AIndex); +end; + +{ TUnit } + +function TUnit.GetUnitName: TfpgString; +begin + Result := fpgExtractFileName(Filename); +end; + +constructor TUnit.Create; +begin + inherited Create; + FOpened := False; +end; + +end. + |