Debugger: Allow to expand/unfold locals values

This commit is contained in:
Martin 2023-03-19 13:55:40 +01:00
parent 26f4160df9
commit ad9eaf0474
9 changed files with 739 additions and 102 deletions

View File

@ -1805,6 +1805,8 @@ type
class function GetDebuggerClass(const AIndex: Integer): TDebuggerClass;static;
class function GetDebuggerClassByName(const AIndex: String): TDebuggerClass; static;
function FindDebuggerClass(const Astring: String): TDebuggerClass;
public
procedure RequestWatchData(AWatchValue: TWatchValueIntf); virtual; abstract;
public
class function DebuggerCount: Integer;

View File

@ -24,10 +24,10 @@ type
FOwner: TDbgEntityValuesList;
FFlags: set of (devImmutable);
function GetImmutable: Boolean;
function GetStackFrame: Integer;
function GetThreadId: Integer;
procedure SetImmutable(AValue: Boolean);
protected
function GetStackFrame: Integer;
function GetThreadId: Integer;
procedure DoAssign({%H-}AnOther: TDbgEntityValue); virtual;
property Owner: TDbgEntityValuesList read FOwner;
public
@ -63,7 +63,7 @@ type
constructor Create(AThreadId, AStackFrame: Integer);
destructor Destroy; override;
procedure Assign(AnOther: TDbgEntityValuesList); // assert other has same thread/stack
procedure Add(AnEntry: TDbgEntityValue);
procedure Add(AnEntry: TDbgEntityValue); virtual;
procedure Clear;
function Count: Integer;
property Entries[AnIndex: Integer]: TDbgEntityValue read GetEntry;

View File

@ -198,6 +198,7 @@ type
function ShowBreakPointProperties(const ABreakpoint: TIDEBreakPoint): TModalresult; virtual; abstract;
function ShowWatchProperties(const AWatch: TCurrentWatch; AWatchExpression: String = ''): TModalresult; virtual; abstract;
procedure RequestWatchData(AWatchValue: TWatchValueIntf); override;
// Dialog routines
procedure CreateDebugDialog(Sender: TObject; aFormName: string;
var AForm: TCustomForm; DoDisableAutoSizing: boolean); virtual; abstract;
@ -247,6 +248,15 @@ var
implementation
{ TBaseDebugManager }
procedure TBaseDebugManager.RequestWatchData(AWatchValue: TWatchValueIntf);
begin
if (Watches <> nil) and (Watches.Supplier <> nil)
then
Watches.Supplier.RequestData(AWatchValue);
end;
initialization
RegisterIDEOptionsGroup(GroupDebugger, TDebuggerOptions);
DebugBoss := nil;

View File

@ -43,7 +43,7 @@ type
constructor Create(ATreeView: TDbgTreeView);
//destructor Destroy; override;
procedure AddWatchData(AWatchAble: TObject; AWatchAbleResult: TWatchAbleResultIntf = nil);
function AddWatchData(AWatchAble: TObject; AWatchAbleResult: TWatchAbleResultIntf = nil; AVNode: PVirtualNode = nil): PVirtualNode;
procedure UpdateWatchData(AWatchAble: TObject; AVNode: PVirtualNode; AWatchAbleResult: TWatchAbleResultIntf = nil);
property CancelUpdate: Boolean read FCancelUpdate write FCancelUpdate;
@ -365,19 +365,26 @@ begin
FTreeView.OnInitChildren := @TreeViewInitChildren;
end;
procedure TDbgTreeViewWatchDataMgr.AddWatchData(AWatchAble: TObject;
AWatchAbleResult: TWatchAbleResultIntf);
var
AVNode: PVirtualNode;
function TDbgTreeViewWatchDataMgr.AddWatchData(AWatchAble: TObject;
AWatchAbleResult: TWatchAbleResultIntf; AVNode: PVirtualNode): PVirtualNode;
begin
if AWatchAble = nil then
exit;
AVNode := FTreeView.FindNodeForItem(AWatchAble);
if AVNode = nil then begin
AVNode := FTreeView.AddChild(nil, AWatchAble);
if (AVNode <> nil) then begin
FTreeView.NodeItem[AVNode] := AWatchAble;
FTreeView.SelectNode(AVNode);
(AWatchAble as TFreeNotifyingIntf).AddFreeNotification(@DoWatchAbleFreed);
end
else begin
AVNode := FTreeView.FindNodeForItem(AWatchAble);
if AVNode = nil then begin
AVNode := FTreeView.AddChild(nil, AWatchAble);
FTreeView.SelectNode(AVNode);
(AWatchAble as TFreeNotifyingIntf).AddFreeNotification(@DoWatchAbleFreed);
end;
end;
Result := AVNode;
UpdateWatchData(AWatchAble, AVNode, AWatchAbleResult);
end;

View File

@ -590,7 +590,7 @@ type
{ TIdeWatch }
TIdeWatch = class(TWatch, TWatchAbleDataIntf)
TIdeWatch = class(TWatch, TWatchAbleDataIntf, TFreeNotifyingIntf)
private
FChildWatches: TIdeWatches;
FDisplayName: String;
@ -922,6 +922,40 @@ type
property OnChange;
end;
TSubLocals = class;
{ TIdeLocalsValue }
TIdeLocalsValue = class(TLocalsValue, TWatchAbleResultIntf, TWatchAbleDataIntf, TFreeNotifyingIntf)
private
FSubLocals: TSubLocals;
FDisplayName: String;
private
// TWatchAble
procedure LimitChildWatchCount(AMaxCnt: Integer; AKeepIndexEntriesBelow: Int64 = low(Int64)); virtual;
procedure ClearDisplayData; // Clear any cached display-data / keep only what's needed for the snapshot
function GetDisplayName: String;
function GetExpression: String;
function GetEnabled: Boolean;
function GetValidity: TDebuggerDataState; virtual;
function GetDisplayFormat: TWatchDisplayFormat;
function GetTypeInfo: TDBGType; deprecated;
function GetChildrenByNameAsArrayEntry(AName: Int64): TObject;
function GetChildrenByNameAsField(AName, AClassName: String): TObject;
private
procedure CreateSubLocals; virtual;
function GetSubLocal(ADispName, AnExpr: String): TIdeLocalsValue;
protected
procedure SetDisplayName(AValue: String); virtual;
procedure DoAssign(AnOther: TDbgEntityValue); override;
public
destructor Destroy; override;
property DisplayName: String read GetDisplayName write SetDisplayName;
end;
{ TIDELocals }
TIDELocals = class(TLocals)
@ -930,19 +964,84 @@ type
APath: string);
procedure SaveDataToXMLConfig(const AConfig: TXMLConfig;
APath: string);
function CreateEntry: TDbgEntityValue; override;
public
constructor CreateFromXMLConfig(const AConfig: TXMLConfig; APath: string);
procedure SetDataValidity({%H-}AValidity: TDebuggerDataState); override;
end;
{ THistoryLocalValue }
THistoryLocalValue = class(TIdeLocalsValue)
protected
FSnapShot: TIdeLocalsValue;
procedure SetSnapShot(const AValue: TIdeLocalsValue); virtual;
procedure CreateSubLocals; override;
procedure SetDisplayName(AValue: String); override;
end;
{ THistoryLocals }
THistoryLocals = class(TIDELocals)
protected
FSnapShot: TIDELocals;
procedure SetSnapShot(const AValue: TIDELocals); virtual;
end;
{ TSubLocalsValue }
TSubLocalsValue = class(specialize TDbgDataRequestTemplateBase<THistoryLocalValue, TWatchValueIntf>, TWatchValueIntf)
private
FValidity: TDebuggerDataState;
FCurrentResData: TCurrentResData;
private
FOnChange: TNotifyEvent;
// TWatchValueIntf
function GetEvaluateFlags: TWatcheEvaluateFlags;
function GetDbgValConverter: TLazDbgValueConvertSelectorIntf;
function GetFirstIndexOffs: Int64;
function GetRepeatCount: Integer;
function GetValidity: TDebuggerDataState; override;
procedure SetTypeInfo(AValue: TDBGTypeBase);
procedure SetValidity(AValue: TDebuggerDataState);
procedure SetValue(AValue: String);
function ResData: TLzDbgWatchDataIntf; // new ResData for debugger to fill in
procedure DoBeginUpdating; override;
procedure DoEndUpdating; override;
procedure RequestData;
protected
function GetResultData: TWatchResultData; override;
function GetValue: String; override;
public
destructor Destroy; override;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;
{ TSubLocals }
TSubLocals = class(THistoryLocals)
private
FOwnerLocals: TIDELocals;
protected
function CreateEntry: TDbgEntityValue; override;
function Add(const AName: String): TIdeLocalsValue; overload;
procedure SetSnapShot(const AValue: TIDELocals); override;
function TopOwner: TIDELocals;
public
constructor Create(AThreadId, AStackFrame: Integer; AOwnerLocals: TIDELocals);
end;
TCurrentLocalValue = class(THistoryLocalValue)
end;
{ TCurrentLocals }
TCurrentLocals = class(specialize TDbgDataRequestTemplateBase<TIDELocals, TLocalsListIntf>, TLocalsListIntf)
TCurrentLocals = class(specialize TDbgDataRequestTemplateBase<THistoryLocals, TLocalsListIntf>, TLocalsListIntf)
private
FMonitor: TIdeLocalsMonitor;
FSnapShot: TIDELocals;
FDataValidity: TDebuggerDataState;
procedure SetSnapShot(const AValue: TIDELocals);
private
(* TLocalsListIntf *)
FCurrentResName: String;
@ -955,14 +1054,19 @@ type
procedure DoEndUpdating; override;
procedure SetValidity(AValue: TDebuggerDataState);
function Add(AName: String): TLzDbgWatchDataIntf; overload;
procedure FinishCurrentRes;
protected
procedure FinishCurrentRes(AnInUpdate: Boolean = False);
function CreateEntry: TDbgEntityValue; override;
procedure SetSnapShot(const AValue: TIDELocals); override;
property SnapShot: TIDELocals read FSnapShot write SetSnapShot;
public
constructor Create(AMonitor: TIdeLocalsMonitor; AThreadId, AStackFrame: Integer);
destructor Destroy; override;
function Count: Integer; override;
procedure Add(AnEntry: TDbgEntityValue); override;
function Add(const AName: String; AValue: TWatchResultData): TLocalsValue; override; overload;
procedure SetDataValidity(AValidity: TDebuggerDataState); override;
property Validity: TDebuggerDataState read FDataValidity;
end;
{ TLocalsList }
@ -7055,6 +7159,115 @@ end;
(******************************************************************************)
(******************************************************************************)
{ TIdeLocalsValue }
procedure TIdeLocalsValue.LimitChildWatchCount(AMaxCnt: Integer;
AKeepIndexEntriesBelow: Int64);
begin
//
end;
procedure TIdeLocalsValue.ClearDisplayData;
begin
//
end;
function TIdeLocalsValue.GetDisplayName: String;
begin
Result := FDisplayName;
if Result = '' then
Result := Name;
end;
function TIdeLocalsValue.GetExpression: String;
begin
Result := Name;
end;
function TIdeLocalsValue.GetEnabled: Boolean;
begin
Result := True;
end;
function TIdeLocalsValue.GetValidity: TDebuggerDataState;
begin
if ResultData <> nil then
Result := ddsValid
else
Result := ddsRequested;
end;
function TIdeLocalsValue.GetDisplayFormat: TWatchDisplayFormat;
begin
Result := wdfDefault;
end;
function TIdeLocalsValue.GetTypeInfo: TDBGType;
begin
Result := nil;
end;
function TIdeLocalsValue.GetChildrenByNameAsArrayEntry(AName: Int64): TObject;
begin
Result := GetSubLocal(IntToStr(AName),
GetExpressionForArrayElement(Name, AName)
);
end;
function TIdeLocalsValue.GetChildrenByNameAsField(AName, AClassName: String
): TObject;
var
Expr: String;
begin
Expr := Name;
if AClassName <> '' then
Expr := AClassName + '(' + Expr + ')';
Expr := Expr + '.' + AName;
Result := GetSubLocal(AName, Expr);
end;
procedure TIdeLocalsValue.CreateSubLocals;
begin
if FSubLocals = nil then
FSubLocals := TSubLocals.Create(ThreadId, StackFrame, TIDELocals(Owner));
end;
function TIdeLocalsValue.GetSubLocal(ADispName, AnExpr: String
): TIdeLocalsValue;
begin
if FSubLocals = nil then begin
CreateSubLocals;
end
else begin
Result := TIdeLocalsValue(FSubLocals.Find(AnExpr));
if Result <> nil then
exit;
end;
Result := FSubLocals.Add(AnExpr);
Result.DisplayName := ADispName;
end;
procedure TIdeLocalsValue.SetDisplayName(AValue: String);
begin
FDisplayName := AValue;
end;
procedure TIdeLocalsValue.DoAssign(AnOther: TDbgEntityValue);
begin
inherited DoAssign(AnOther);
if AnOther is TIdeLocalsValue then begin
FDisplayName := TIdeLocalsValue(AnOther).FDisplayName;
// skip SubLocals
end;
end;
destructor TIdeLocalsValue.Destroy;
begin
inherited Destroy;
FSubLocals.Free;
end;
{ =========================================================================== }
{ TLocals }
{ =========================================================================== }
@ -7092,6 +7305,11 @@ begin
end;
end;
function TIDELocals.CreateEntry: TDbgEntityValue;
begin
Result := TIdeLocalsValue.Create;
end;
constructor TIDELocals.CreateFromXMLConfig(const AConfig: TXMLConfig; APath: string);
var
LoadThreadId, LoadStackFrame: Integer;
@ -7102,17 +7320,247 @@ begin
LoadDataFromXMLConfig(AConfig, APath);
end;
{ THistoryLocalValue }
procedure THistoryLocalValue.SetSnapShot(const AValue: TIdeLocalsValue);
begin
FSnapShot := AValue;
FSnapShot.Assign(Self);
if FSubLocals <> nil then begin
FSnapShot.CreateSubLocals;
FSubLocals.SetSnapShot(FSnapShot.FSubLocals);
end;
end;
procedure THistoryLocalValue.CreateSubLocals;
begin
inherited CreateSubLocals;
if FSnapShot <> nil then begin
FSnapShot.CreateSubLocals;
FSubLocals.SetSnapShot(FSnapShot.FSubLocals);
end;
end;
procedure THistoryLocalValue.SetDisplayName(AValue: String);
begin
inherited SetDisplayName(AValue);
if FSnapShot <> nil then
FSnapShot.DisplayName := AValue;
end;
{ THistoryLocals }
procedure THistoryLocals.SetSnapShot(const AValue: TIDELocals);
begin
FSnapShot := AValue;
end;
{ TSubLocalsValue }
function TSubLocalsValue.ResData: TLzDbgWatchDataIntf;
begin
if FCurrentResData = nil then
FCurrentResData := TCurrentResData.Create;
Result := FCurrentResData;
end;
procedure TSubLocalsValue.DoBeginUpdating;
begin
AddReference;
FValidity := ddsEvaluating;
end;
procedure TSubLocalsValue.DoEndUpdating;
begin
FCurrentResData := FCurrentResData.RootResultData;
// TODO: maybe create an error entry, if only FNewResultData is missing
if (FCurrentResData <> nil) then begin
if (FCurrentResData.FNewResultData = nil) then begin
FreeAndNil(FCurrentResData);
FValidity := ddsInvalid;
ReleaseReference;
exit;
end;
FCurrentResData.Done;
FValue := FCurrentResData.FNewResultData;
FreeAndNil(FCurrentResData);
if FValidity = ddsEvaluating then
FValidity := ddsValid;
end
else
FValidity := ddsInvalid;
if FSnapShot <> nil then begin
TSubLocalsValue(FSnapShot).FValidity := FValidity;
TSubLocalsValue(FSnapShot).FValue := FValue.CreateCopy;
end;
if FOnChange <> nil then
FOnChange(Self);
ReleaseReference;
end;
procedure TSubLocalsValue.RequestData;
begin
if(DebugBossManager <> nil) and
(FValidity = ddsUnknown) and
(TSubLocals(Owner).TopOwner is TCurrentLocals) and
(inherited GetResultData = nil)
then begin
FValidity := ddsRequested;
DebugBossManager.RequestWatchData(Self);
end;
end;
function TSubLocalsValue.GetResultData: TWatchResultData;
begin
RequestData;
Result := inherited GetResultData;
end;
function TSubLocalsValue.GetValue: String;
begin
RequestData;
case FValidity of
ddsRequested, ddsEvaluating: Result := '<evaluating>';
ddsValid: Result := inherited GetValue;
ddsInvalid: Result := '<invalid>';
ddsError: Result := '<Error: '+ (inherited GetValue) +'>';
else Result := '<not evaluated>';
end;
end;
destructor TSubLocalsValue.Destroy;
begin
inherited Destroy;
DoDestroy;
end;
function TSubLocalsValue.GetEvaluateFlags: TWatcheEvaluateFlags;
begin
Result := [];
end;
function TSubLocalsValue.GetDbgValConverter: TLazDbgValueConvertSelectorIntf;
begin
Result := nil;
end;
function TSubLocalsValue.GetFirstIndexOffs: Int64;
begin
Result := 0;
end;
function TSubLocalsValue.GetRepeatCount: Integer;
begin
Result := 0;
end;
function TSubLocalsValue.GetValidity: TDebuggerDataState;
begin
RequestData;
Result := FValidity;
end;
procedure TSubLocalsValue.SetTypeInfo(AValue: TDBGTypeBase);
begin
//assert(False, 'TSubLocalsValue.SetTypeInfo: False');
end;
procedure TSubLocalsValue.SetValidity(AValue: TDebuggerDataState);
begin
FValidity := AValue;
if not IsUpdating then begin
AddReference;
DoEndUpdating;
end;
end;
procedure TSubLocalsValue.SetValue(AValue: String);
begin
FValue.Free;
FValue := TWatchResultDataPrePrinted.Create(AValue);
end;
{ TSubLocals }
function TSubLocals.CreateEntry: TDbgEntityValue;
begin
Result := TSubLocalsValue.Create;
end;
function TSubLocals.Add(const AName: String): TIdeLocalsValue;
var
V: TSubLocalsValue;
begin
Result := TIdeLocalsValue(CreateEntry);
Result.FName := AName;
Add(Result);
if FSnapShot <> nil then begin
V := TSubLocalsValue(FSnapShot.Add('', nil));
assert(V is TSubLocalsValue, 'TSubLocals.Add: V is TSubLocalsValue');
assert(Result is TSubLocalsValue, 'TSubLocals.Add: Result is TSubLocalsValue');
TSubLocalsValue(Result).SetSnapShot(V);
end;
end;
procedure TSubLocals.SetSnapShot(const AValue: TIDELocals);
var
V: TSubLocalsValue;
i: Integer;
begin
assert((FSnapShot=nil) or (AValue=nil), 'TSubLocals.SetSnapShot: (FSnapShot=nil) or (AValue=nil)');
inherited SetSnapShot(AValue);
if FSnapShot <> nil then begin
assert(FSnapShot is TSubLocals, 'TSubLocals.SetSnapShot: FSnapShot is TSubLocals');
FSnapShot.Clear;
for i := 0 to Count - 1 do begin
V := TSubLocalsValue(FSnapShot.Add('', nil));
TSubLocalsValue(Entries[i]).SetSnapShot(V);
end;
end;
end;
function TSubLocals.TopOwner: TIDELocals;
begin
Result := FOwnerLocals;
while (Result <> nil) and (Result is TSubLocals) do
Result := TSubLocals(Result).FOwnerLocals;
end;
constructor TSubLocals.Create(AThreadId, AStackFrame: Integer;
AOwnerLocals: TIDELocals);
begin
inherited Create(AThreadId, AStackFrame);
FOwnerLocals := AOwnerLocals;
end;
{ =========================================================================== }
{ TCurrentLocals }
{ =========================================================================== }
procedure TCurrentLocals.SetSnapShot(const AValue: TIDELocals);
var
V: TIdeLocalsValue;
i: Integer;
begin
assert((FSnapShot=nil) or (AValue=nil), 'TCurrentLocals already have snapshot');
if FSnapShot = AValue then exit;
FSnapShot := AValue;
if FSnapShot <> nil
then FSnapShot.Assign(Self);
inherited SetSnapShot(AValue);
if FSnapShot <> nil then begin
FSnapShot.Clear;
for i := 0 to Count - 1 do begin
V := TIdeLocalsValue(FSnapShot.Add('', nil));
TCurrentLocalValue(Entries[i]).SetSnapShot(V);
end;
end;
end;
function TCurrentLocals.GetStackFrame: Integer;
@ -7140,7 +7588,7 @@ procedure TCurrentLocals.DoEndUpdating;
var
i: Integer;
begin
FinishCurrentRes;
FinishCurrentRes(True);
for i := 0 to FCurrentResList.Count - 1 do
Add(TLocalsValue(FCurrentResList[i]));
@ -7173,7 +7621,7 @@ begin
Result := FCurrentResData;
end;
procedure TCurrentLocals.FinishCurrentRes;
procedure TCurrentLocals.FinishCurrentRes(AnInUpdate: Boolean);
var
v: TLocalsValue;
begin
@ -7191,7 +7639,7 @@ begin
v := TLocalsValue(CreateEntry);
v.Init(FCurrentResName, FCurrentResData.FNewResultData);
if IsUpdating then
if IsUpdating or AnInUpdate then
FCurrentResList.Add(v)
else
Add(v);
@ -7199,6 +7647,11 @@ begin
FreeAndNil(FCurrentResData);
end;
function TCurrentLocals.CreateEntry: TDbgEntityValue;
begin
Result := TCurrentLocalValue.Create;
end;
constructor TCurrentLocals.Create(AMonitor: TIdeLocalsMonitor; AThreadId, AStackFrame: Integer);
begin
FMonitor := AMonitor;
@ -7209,6 +7662,7 @@ end;
destructor TCurrentLocals.Destroy;
begin
inherited Destroy;
DoDestroy;
FCurrentResData := FCurrentResData.RootResultData;
if (FCurrentResData <> nil) {and (FResultData = nil)} then
@ -7240,6 +7694,30 @@ begin
end;
end;
procedure TCurrentLocals.Add(AnEntry: TDbgEntityValue);
var
V: TIdeLocalsValue;
begin
inherited Add(AnEntry);
assert(AnEntry is TCurrentLocalValue, 'TCurrentLocals.Add: AnEntry is TCurrentLocalValue');
if FSnapShot <> nil then begin
V := TIdeLocalsValue(FSnapShot.Add('', nil));
TCurrentLocalValue(AnEntry).SetSnapShot(V);
end;
end;
function TCurrentLocals.Add(const AName: String; AValue: TWatchResultData
): TLocalsValue;
var
V: TIdeLocalsValue;
begin
Result := inherited Add(AName, AValue);
if FSnapShot <> nil then begin
V := TIdeLocalsValue(FSnapShot.Add('', nil));
TCurrentLocalValue(Result).SetSnapShot(V);
end;
end;
procedure TCurrentLocals.SetDataValidity(AValidity: TDebuggerDataState);
begin
if FDataValidity = AValidity then exit;

View File

@ -56,8 +56,10 @@ type
procedure SelectNode(Node: PVirtualNode; ASetFocus: boolean = True);
function FindNodeForItem(AnItem: TObject): PVirtualNode;
function FindNodeForText(AText: String; AColumn: integer): PVirtualNode;
function FindNodeForText(AText: String; AColumn: integer; ATopLvlOnly: Boolean = False): PVirtualNode;
procedure DeleteNodeEx(Node: PVirtualNode; FreeItem: Boolean; Reindex: Boolean = True);
// LazMoveTo: Don't mess with children
procedure LazMoveTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode);
property NodeItem[Node: PVirtualNode]: TObject read GetNodeItem write SetNodeItem;
property NodeText[Node: PVirtualNode; AColumn: integer]: String read GetNodeText write SetNodeText;
@ -351,14 +353,19 @@ begin
Result := nil;
end;
function TDbgTreeView.FindNodeForText(AText: String; AColumn: integer
): PVirtualNode;
function TDbgTreeView.FindNodeForText(AText: String; AColumn: integer;
ATopLvlOnly: Boolean): PVirtualNode;
var
VNode: PVirtualNode;
begin
for VNode in NoInitNodes do begin
VNode := GetFirstNoInit;
while VNode <> nil do begin
if GetNodeText(VNode, AColumn) = AText then
exit(VNode);
if ATopLvlOnly then
VNode := GetNextSiblingNoInit(VNode)
else
VNode := GetNextNoInit(VNode);
end;
Result := nil;
end;
@ -377,6 +384,46 @@ begin
Item.Free;
end;
procedure TDbgTreeView.LazMoveTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode);
var
NewNode: PVirtualNode;
begin
if Target = nil then
begin
Target := RootNode;
Mode := amAddChildFirst;
end;
if Target = RootNode then
begin
case Mode of
amInsertBefore:
Mode := amAddChildFirst;
amInsertAfter:
Mode := amAddChildLast;
end;
end;
if (Source <> Target) and HasAsParent(Target, Source) then
exit;
// Disconnect from old location.
InternalDisconnectNode(Source, True);
// Connect to new location.
InternalConnectNode(Source, Target, Self, Mode);
DoNodeMoved(Source);
InvalidateCache;
if (UpdateCount = 0) then
begin
ValidateCache;
UpdateScrollBars(True);
Invalidate;
end;
StructureChange(Source, crNodeMoved);
end;
initialization
RegisterClass(TDbgTreeView);

View File

@ -235,18 +235,20 @@ type
{ TLocalsValue }
TLocalsValue = class(TDbgEntityValue)
private
FName: String;
FValue: TWatchResultData;
protected
procedure DoAssign(AnOther: TDbgEntityValue); override;
public
destructor Destroy; override;
procedure Init(AName: String; AValue: TWatchResultData);
property Name: String read FName;
property Value: TWatchResultData read FValue;
end;
TLocalsValue = class(TDbgEntityValue)
protected
FName: String;
FValue: TWatchResultData;
function GetValue: String; virtual;
function GetResultData: TWatchResultData; virtual;
procedure DoAssign(AnOther: TDbgEntityValue); override;
public
destructor Destroy; override;
procedure Init(AName: String; AValue: TWatchResultData);
property Name: String read FName;
property Value: String read GetValue;
property ResultData: TWatchResultData read GetResultData;
end;
{ TLocals }
@ -254,17 +256,18 @@ type
private
function GetEntry(AnIndex: Integer): TLocalsValue;
function GetName(const AnIndex: Integer): String;
function GetValue(const AnIndex: Integer): TWatchResultData;
function GetResultData(const AnIndex: Integer): TWatchResultData;
protected
function CreateEntry: TDbgEntityValue; override;
public
procedure Add(const AName: String; AValue: TWatchResultData); overload; deprecated;
function Add(const AName: String; AValue: TWatchResultData): TLocalsValue; virtual; overload;
procedure SetDataValidity({%H-}AValidity: TDebuggerDataState); virtual;
public
function Count: Integer;reintroduce; virtual;
function Find(AName: String): TLocalsValue;
property Entries[AnIndex: Integer]: TLocalsValue read GetEntry;
property Names[const AnIndex: Integer]: String read GetName;
property Values[const AnIndex: Integer]: TWatchResultData read GetValue;
property Values[const AnIndex: Integer]: TWatchResultData read GetResultData;
end;
{ TLocalsList }
@ -972,6 +975,18 @@ end;
{ TLocalsValue }
function TLocalsValue.GetValue: String;
begin
Result := '';
if FValue <> nil then
Result := FValue.AsString;
end;
function TLocalsValue.GetResultData: TWatchResultData;
begin
Result := FValue;
end;
procedure TLocalsValue.DoAssign(AnOther: TDbgEntityValue);
begin
inherited DoAssign(AnOther);
@ -1016,9 +1031,9 @@ begin
Result := Entries[AnIndex].Name;
end;
function TLocals.GetValue(const AnIndex: Integer): TWatchResultData;
function TLocals.GetResultData(const AnIndex: Integer): TWatchResultData;
begin
Result := Entries[AnIndex].Value;
Result := Entries[AnIndex].ResultData;
end;
function TLocals.CreateEntry: TDbgEntityValue;
@ -1026,15 +1041,14 @@ begin
Result := TLocalsValue.Create;
end;
procedure TLocals.Add(const AName: String; AValue: TWatchResultData);
var
v: TLocalsValue;
function TLocals.Add(const AName: String; AValue: TWatchResultData
): TLocalsValue;
begin
assert(not Immutable, 'TLocalsBase.Add Immutable');
v := TLocalsValue(CreateEntry);
v.FName := AName;
v.FValue := AValue;
inherited Add(v);
Result := TLocalsValue(CreateEntry);
Result.FName := AName;
Result.FValue := AValue;
inherited Add(Result);
end;
procedure TLocals.SetDataValidity(AValidity: TDebuggerDataState);
@ -1047,6 +1061,20 @@ begin
Result := inherited Count;
end;
function TLocals.Find(AName: String): TLocalsValue;
var
i: Integer;
begin
i := Count - 1;
while i >= 0 do begin
Result := Entries[i];
if Result.Name = AName then
exit;
dec(i);
end;
Result := nil;
end;
{ TLocalsMonitor }
procedure TLocalsMonitor.DoStateChange(const AOldState, ANewState: TDBGState);

View File

@ -26,7 +26,7 @@ function ClearMultiline(const AValue: ansistring): ansistring;
The Index "11" returns Foo[11]
*)
function GetExpressionForArrayElement(AnArrayExpression: AnsiString; AnIndex: String): AnsiString; overload;
function GetExpressionForArrayElement(AnArrayExpression: AnsiString; AnIndex: Integer): AnsiString; overload;
function GetExpressionForArrayElement(AnArrayExpression: AnsiString; AnIndex: Int64): AnsiString; overload;
implementation
@ -349,7 +349,7 @@ begin
end;
function GetExpressionForArrayElement(AnArrayExpression: AnsiString;
AnIndex: Integer): AnsiString;
AnIndex: Int64): AnsiString;
begin
Result := GetExpressionForArrayElement(AnArrayExpression, IntToStr(AnIndex));
end;

View File

@ -45,13 +45,17 @@ uses
IDEWindowIntf,
// DebuggerIntf
DbgIntfDebuggerBase, laz.VirtualTrees, LazDebuggerIntf,
LazDebuggerIntfBaseTypes,
// IDE Debugger
IdeDebuggerStringConstants, BaseDebugManager, EnvironmentOpts, Debugger,
DebuggerDlg, IdeDebuggerWatchResPrinter, IdeDebuggerUtils, DebuggerTreeView,
IdeDebuggerWatchResult, IdeDebuggerBase, Controls, ActiveX;
IdeDebuggerWatchResult, IdeDebuggerBase, DbgTreeViewWatchData, Controls,
ActiveX;
type
TDbgTreeViewLocalsValueMgr = class;
{ TLocalsDlg }
TLocalsDlg = class(TDebuggerDlg)
@ -95,16 +99,18 @@ type
const HitInfo: THitInfo);
private
FWatchPrinter: TWatchResultPrinter;
FLocolsTreeMgr: TDbgTreeViewLocalsValueMgr;
FUpdateFlags: set of (ufNeedUpdating);
function GetSelected: TLocalsValue; // The focused Selected Node
procedure CopyRAWValueEvaluateCallback(Sender: TObject; ASuccess: Boolean;
ResultText: String; ResultDBGType: TDBGType);
procedure CopyValueEvaluateCallback(Sender: TObject; ASuccess: Boolean;
ResultText: String; ResultDBGType: TDBGType);
procedure DoLocalValueFreed(Sender: TObject);
procedure ClearTree(OnlyClearNodeData: boolean = False);
procedure LocalsChanged(Sender: TObject);
procedure SubLocalChanged(Sender: TObject);
function GetThreadId: Integer;
function GetSelectedThreads(Snap: TSnapshot): TIdeThreads;
function GetStackframe: Integer;
@ -123,6 +129,23 @@ type
property SnapshotManager;
end;
{ TDbgTreeViewLocalsValueMgr }
TDbgTreeViewLocalsValueMgr = class(TDbgTreeViewWatchDataMgr)
private
FLocalsDlg: TLocalsDlg;
protected
function WatchAbleResultFromNode(AVNode: PVirtualNode): TWatchAbleResultIntf; override;
function WatchAbleResultFromObject(AWatchAble: TObject): TWatchAbleResultIntf; override;
procedure UpdateColumnsText(AWatchAble: TObject; AWatchAbleResult: TWatchAbleResultIntf; AVNode: PVirtualNode); override;
procedure ConfigureNewSubItem(AWatchAble: TObject); override;
//procedure UpdateSubItems(AWatchAble: TObject; AWatchAbleResult: TWatchAbleResultIntf;
// AVNode: PVirtualNode; out ChildCount: LongWord); override;
//procedure UpdateSubItemsLocked(AWatchAble: TObject; AWatchAbleResult: TWatchAbleResultIntf;
// AVNode: PVirtualNode; out ChildCount: LongWord); override;
end;
function ValueToRAW(const AValue: string): string;
implementation
@ -289,6 +312,8 @@ begin
CallstackNotification.OnCurrent := @LocalsChanged;
SnapshotNotification.OnCurrent := @LocalsChanged;
FWatchPrinter := TWatchResultPrinter.Create;
FLocolsTreeMgr := TDbgTreeViewLocalsValueMgr.Create(vtLocals);
FLocolsTreeMgr.FLocalsDlg := Self;
Caption:= lisLocals;
vtLocals.Header.Columns[0].Text:= lisName;
@ -310,6 +335,7 @@ begin
ClearTree;
inherited Destroy;
FWatchPrinter.free;
FLocolsTreeMgr.Free;
end;
procedure TLocalsDlg.actInspectUpdate(Sender: TObject);
@ -326,7 +352,7 @@ begin
if LVal = nil then
exit;
ResVal := LVal.Value;
ResVal := LVal.ResultData;
if ResVal = nil then
exit;
@ -347,7 +373,7 @@ begin
end
else begin
Clipboard.Open;
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(ResVal, wdfDefault));
Clipboard.Close;
end;
@ -357,7 +383,7 @@ begin
if not DebugBoss.Evaluate(LVal.Name, @CopyRAWValueEvaluateCallback, []) then
begin
Clipboard.Open;
Clipboard.AsText := ValueToRAW(ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault)));
Clipboard.AsText := ValueToRAW(ClearMultiline(FWatchPrinter.PrintWatchValue(ResVal, wdfDefault)));
Clipboard.Close;
end;
end;
@ -459,7 +485,7 @@ begin
for VNode in vtLocals.NoInitNodes do begin
LVal := TLocalsValue((vtLocals.NodeItem[VNode]));
if LVal <> nil then
AStringList.Values[LVal.Name] := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
AStringList.Values[LVal.Name] := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.ResultData, wdfDefault));
end;
Clipboard.Open;
@ -482,13 +508,13 @@ begin
if LVal = nil then
exit;
ResVal := LVal.Value;
ResVal := LVal.ResultData;
if ResVal = nil then
exit;
if (ResVal.ValueKind <> rdkPrePrinted) then begin
Clipboard.Open;
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(ResVal, wdfDefault));
Clipboard.Close;
exit;
end;
@ -496,7 +522,7 @@ begin
if not DebugBoss.Evaluate(LVal.Name, @CopyValueEvaluateCallback, []) then
begin
Clipboard.Open;
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(ResVal, wdfDefault));
Clipboard.Close;
end
end;
@ -507,10 +533,10 @@ var
Locals: TIDELocals;
Snap: TSnapshot;
s: String;
LVal: TLocalsValue;
LVal: TIdeLocalsValue;
VNode, VN2: PVirtualNode;
begin
if (ThreadsMonitor = nil) or (CallStackMonitor = nil) or (LocalsMonitor=nil) then begin
if (DebugBoss = nil) or (ThreadsMonitor = nil) or (CallStackMonitor = nil) or (LocalsMonitor=nil) then begin
ClearTree;
exit;
end;
@ -529,6 +555,19 @@ begin
end;
Snap := GetSelectedSnapshot;
if (Snap = nil) then begin
if (DebugBoss.State in [dsInit, dsIdle, dsStop]) then begin
ClearTree;
exit;
end;
if not (DebugBoss.State in [dsPause, dsInternalPause]) then begin
ClearTree(True);
exit;
end;
end;
if (Snap <> nil)
then begin
Locals := LocalsMonitor.Snapshots[Snap][GetThreadId, GetStackframe];
@ -539,32 +578,29 @@ begin
Caption:= lisLocals;
end;
if Locals = nil
then begin
if (Locals = nil) then begin
ClearTree;
VNode:= vtLocals.AddChild(nil, nil);
vtLocals.NodeText[VNode, 1] := lisLocalsNotEvaluated;
Exit;
end;
if (Locals is TCurrentLocals) and (TCurrentLocals(Locals).Validity in [ddsUnknown, ddsRequested, ddsEvaluating])
then begin
Locals.Count; // trigger
ClearTree(True);
Exit;
end;
BeginUpdate;
try
ClearTree(True);
VN2 := nil;
for n := 0 to Locals.Count - 1 do begin
LVal := Locals.Entries[n];
LVal.AddFreeNotification(@DoLocalValueFreed);
VNode := vtLocals.FindNodeForText(LVal.Name, 0);
if (VNode = nil) then begin
VNode := vtLocals.AddChild(nil, LVal);
LVal.AddFreeNotification(@DoLocalValueFreed);
end
else
vtLocals.NodeItem[VNode] := LVal;
s := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
vtLocals.NodeText[VNode, 0] := LVal.Name;
vtLocals.NodeText[VNode, 1] := s;
LVal := TIdeLocalsValue(Locals.Entries[n]);
VNode := vtLocals.FindNodeForText(LVal.DisplayName, 0, True);
if (VNode <> nil) and (VNode^.PrevSibling <> VN2) then
vtLocals.LazMoveTo(VNode, VN2, amInsertAfter);
VNode := FLocolsTreeMgr.AddWatchData(LVal, LVal, VNode);
VN2 := VNode;
end;
@ -578,12 +614,21 @@ begin
else
VNode := vtLocals.GetNextVisibleNoInit(VNode);
end;
finally
EndUpdate;
vtLocals.Invalidate;
end;
end;
procedure TLocalsDlg.SubLocalChanged(Sender: TObject);
var
VNode: PVirtualNode;
begin
VNode := vtLocals.FindNodeForItem(Sender);
if VNode <> nil then
FLocolsTreeMgr.UpdateWatchData(TSubLocalsValue(Sender), VNode);
end;
function TLocalsDlg.GetThreadId: Integer;
var
Threads: TIdeThreads;
@ -682,9 +727,9 @@ begin
else
begin
LVal := GetSelected;
if (LVal <> nil) and (LVal.Value <> nil) then begin
if (LVal <> nil) and (LVal.ResultData <> nil) then begin
Clipboard.Open;
Clipboard.AsText := ValueToRAW(ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault)));
Clipboard.AsText := ValueToRAW(ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.ResultData, wdfDefault)));
Clipboard.Close;
end;
end;
@ -702,9 +747,9 @@ begin
Clipboard.AsText := ExtractValue(ResultText, ResultDBGType.TypeName)
else begin
LVal := GetSelected;
if (LVal <> nil) and (LVal.Value <> nil) then begin
if (LVal <> nil) and (LVal.ResultData <> nil) then begin
Clipboard.Open;
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.ResultData, wdfDefault));
Clipboard.Close;
end;
end;
@ -712,35 +757,55 @@ begin
FreeAndNil(ResultDBGType);
end;
procedure TLocalsDlg.DoLocalValueFreed(Sender: TObject);
var
nd: PVirtualNode;
begin
nd := vtLocals.FindNodeForItem(Sender);
if nd = nil then
exit;
//vtLocals.OnItemRemoved := nil;
vtLocals.NodeItem[nd] := nil;
//vtLocals.OnItemRemoved := @DoItemRemovedFromView;
end;
procedure TLocalsDlg.ClearTree(OnlyClearNodeData: boolean);
var
LVal: TLocalsValue;
VNode: PVirtualNode;
begin
for VNode in vtLocals.NoInitNodes do begin
LVal := TLocalsValue(vtLocals.NodeItem[VNode]);
if LVal <> nil then
LVal.RemoveFreeNotification(@DoLocalValueFreed);
vtLocals.NodeItem[VNode] := nil;
if OnlyClearNodeData then
vtLocals.NodeText[VNode, 1] := '<not evaluated>';
end;
if not OnlyClearNodeData then
vtLocals.Clear;
end;
{ TDbgTreeViewLocalsValueMgr }
function TDbgTreeViewLocalsValueMgr.WatchAbleResultFromNode(AVNode: PVirtualNode
): TWatchAbleResultIntf;
begin
Result := TIdeLocalsValue(TreeView.NodeItem[AVNode]);
end;
function TDbgTreeViewLocalsValueMgr.WatchAbleResultFromObject(
AWatchAble: TObject): TWatchAbleResultIntf;
begin
Result := TIdeLocalsValue(AWatchAble);
end;
procedure TDbgTreeViewLocalsValueMgr.UpdateColumnsText(AWatchAble: TObject;
AWatchAbleResult: TWatchAbleResultIntf; AVNode: PVirtualNode);
var
s: String;
ResData: TWatchResultData;
begin
ResData := AWatchAbleResult.ResultData;
if ResData = nil then
s := AWatchAbleResult.Value
else
s := ClearMultiline(FLocalsDlg.FWatchPrinter.PrintWatchValue(ResData, wdfDefault));
TreeView.NodeText[AVNode, 0] := TIdeLocalsValue(AWatchAble).DisplayName;
TreeView.NodeText[AVNode, 1] := s;
end;
procedure TDbgTreeViewLocalsValueMgr.ConfigureNewSubItem(AWatchAble: TObject);
begin
TSubLocalsValue(AWatchAble).OnChange := @FLocalsDlg.SubLocalChanged;
end;
initialization
LocalsDlgWindowCreator := IDEWindowCreators.Add(DebugDialogNames[ddtLocals]);