Debugger: Update locals to use VirtualTree / FpDebug: return locals as new WatchResultData

This commit is contained in:
Martin 2023-03-14 15:25:46 +01:00
parent 40a5975659
commit 26f4160df9
7 changed files with 334 additions and 219 deletions

View File

@ -115,9 +115,6 @@ type
{ TFpThreadWorkerLocalsUpdate }
TFpThreadWorkerLocalsUpdate = class(TFpThreadWorkerLocals)
private
FLocals: TLocalsListIntf;
procedure DoLocalsFreed_DecRef(Sender: TObject);
protected
procedure UpdateLocals_DecRef(Data: PtrInt = 0); override;
procedure DoRemovedFromLinkedList; override; // _DecRef
@ -985,41 +982,15 @@ end;
{ TFpThreadWorkerLocalsUpdate }
procedure TFpThreadWorkerLocalsUpdate.DoLocalsFreed_DecRef(Sender: TObject);
begin
assert(system.ThreadID = classes.MainThreadID, 'TFpThreadWorkerLocals.DoLocalsFreed_DecRef: system.ThreadID = classes.MainThreadID');
FLocals := nil;
RequestStop;
UnQueue_DecRef;
end;
procedure TFpThreadWorkerLocalsUpdate.UpdateLocals_DecRef(Data: PtrInt);
var
i: Integer;
r: TResultEntry;
dbg: TFpDebugDebugger;
rv: TLzDbgWatchDataIntf;
begin
assert(system.ThreadID = classes.MainThreadID, 'TFpThreadWorkerLocals.UpdateLocals_DecRef: system.ThreadID = classes.MainThreadID');
if FLocals <> nil then begin
FLocals.RemoveFreeNotification(@DoLocalsFreed_DecRef);
if FResults = nil then begin
FLocals.Validity := ddsInvalid;
FLocals := nil;
UnQueue_DecRef;
exit;
end;
FLocals.BeginUpdate;
for i := 0 to FResults.Count - 1 do begin
r := FResults[i];
rv := FLocals.Add(r.Name);
rv.CreatePrePrinted(r.Value);
end;
FLocals.Validity := ddsValid;
FLocals.EndUpdate;
FLocals := nil;
end;
@ -1036,8 +1007,8 @@ begin
exit;
end
else begin
FLocals.RemoveFreeNotification(@DoLocalsFreed_DecRef);
FLocals.Validity := ddsInvalid;
FLocals.EndUpdate;
end;
FLocals := nil;
end;
@ -1050,7 +1021,7 @@ begin
// Runs in IDE thread (TThread.Queue)
assert(system.ThreadID = classes.MainThreadID, 'TFpThreadWorkerLocals.Create: system.ThreadID = classes.MainThreadID');
FLocals := ALocals;
FLocals.AddFreeNotification(@DoLocalsFreed_DecRef);
FLocals.BeginUpdate;
FThreadId := ALocals.ThreadId;
FStackFrame := ALocals.StackFrame;
inherited Create(ADebugger, twpLocal);

View File

@ -191,18 +191,11 @@ type
TFpThreadWorkerLocals = class(TFpDbgDebggerThreadWorkerLinkedItem)
protected type
TResultEntry = record
Name, Value: String;
class operator = (a, b: TResultEntry): Boolean;
end;
TResultList = specialize TFPGList<TResultEntry>;
protected
FLocals: TLocalsListIntf;
FThreadId, FStackFrame: Integer;
FResults: TResultList;
procedure UpdateLocals_DecRef(Data: PtrInt = 0); virtual; abstract;
procedure DoExecute; override;
public
destructor Destroy; override;
end;
{ TFpThreadWorkerModify }
@ -657,24 +650,15 @@ begin
inherited Create(ADebugger, 1, twpThread);
end;
{ TFpThreadWorkerLocals.TResultEntry }
class operator TFpThreadWorkerLocals.TResultEntry. = (a, b: TResultEntry
): Boolean;
begin
Result := False;
assert(False, 'TFpThreadWorkerLocals.TResultEntry.=: False');
end;
{ TFpThreadWorkerLocals }
procedure TFpThreadWorkerLocals.DoExecute;
var
LocalScope: TFpDbgSymbolScope;
ProcVal, m: TFpValue;
PrettyPrinter: TFpPascalPrettyPrinter;
i: Integer;
r: TResultEntry;
WatchResConv: TFpLazDbgWatchResultConvertor;
ResData: TLzDbgWatchDataIntf;
begin
LocalScope := FDebugger.DbgController.CurrentProcess.FindSymbolScope(FThreadId, FStackFrame);
if (LocalScope = nil) or (LocalScope.SymbolAtAddress = nil) then begin
@ -688,39 +672,33 @@ begin
exit;
end;
PrettyPrinter := TFpPascalPrettyPrinter.Create(LocalScope.SizeOfAddress);
PrettyPrinter.Context := LocalScope.LocationContext;
// PrettyPrinter.MemManager.DefaultContext := LocalScope.LocationContext;
WatchResConv := TFpLazDbgWatchResultConvertor.Create(LocalScope.LocationContext);
WatchResConv.MaxArrayConv := TFpDebugDebuggerProperties(FDebugger.GetProperties).MemLimits.MaxArrayConversionCnt;
WatchResConv.MaxTotalConv := TFpDebugDebuggerProperties(FDebugger.GetProperties).MemLimits.MaxTotalConversionCnt;
WatchResConv.Debugger := FDebugger;
WatchResConv.ExpressionScope := LocalScope;
FResults := TResultList.Create;
for i := 0 to ProcVal.MemberCount - 1 do begin
m := ProcVal.Member[i];
if m <> nil then begin
if m.DbgSymbol <> nil then
r.Name := m.DbgSymbol.Name
else
r.Name := '';
//if not StopRequested then // finish getting all names?
PrettyPrinter.PrintValue(r.Value, m);
ResData := FLocals.Add(m.DbgSymbol.Name);
if not WatchResConv.WriteWatchResultData(m, ResData)
then begin
ResData.CreateError('Unknown Error');
end;
m.ReleaseReference;
FResults.Add(r);
end;
if StopRequested then
Break;
end;
PrettyPrinter.Free;
WatchResConv.Free;
ProcVal.ReleaseReference;
LocalScope.ReleaseReference;
Queue(@UpdateLocals_DecRef);
end;
destructor TFpThreadWorkerLocals.Destroy;
begin
FResults.Free;
inherited Destroy;
end;
{ TFpThreadWorkerModify }
procedure TFpThreadWorkerModify.DoExecute;

View File

@ -7072,7 +7072,9 @@ begin
APath := APath + 'Entry';
for i := 0 to c - 1 do begin
Add(AConfig.GetValue(APath + IntToStr(i) + '/Expression', ''),
AConfig.GetValue(APath + IntToStr(i) + '/Value', ''));
TWatchResultData.CreateFromXMLConfig(AConfig, APath + IntToStr(i) + '/')
);
end;
end;
@ -7086,7 +7088,7 @@ begin
APath := APath + 'Entry';
for i := 0 to Count - 1 do begin
AConfig.SetValue(APath + IntToStr(i) + '/Expression', Names[i]);
AConfig.SetValue(APath + IntToStr(i) + '/Value', Values[i]);
Values[i].SaveDataToXMLConfig(AConfig, APath + IntToStr(i) + '/');
end;
end;
@ -7177,14 +7179,17 @@ var
begin
FCurrentResData := FCurrentResData.RootResultData;
// TODO: maybe create an error entry, if only FNewResultData is missing
if (FCurrentResData = nil) or (FCurrentResData.FNewResultData = nil) then
if (FCurrentResData = nil) then
exit;
if (FCurrentResData.FNewResultData = nil) then begin
FreeAndNil(FCurrentResData);
exit;
end;
FCurrentResData.Done;
v := TLocalsValue(CreateEntry);
v.Init(FCurrentResName, FCurrentResData.FNewResultData);
FCurrentResData.FNewResultData := nil;
if IsUpdating then
FCurrentResList.Add(v)

View File

@ -56,6 +56,7 @@ type
procedure SelectNode(Node: PVirtualNode; ASetFocus: boolean = True);
function FindNodeForItem(AnItem: TObject): PVirtualNode;
function FindNodeForText(AText: String; AColumn: integer): PVirtualNode;
procedure DeleteNodeEx(Node: PVirtualNode; FreeItem: Boolean; Reindex: Boolean = True);
property NodeItem[Node: PVirtualNode]: TObject read GetNodeItem write SetNodeItem;
@ -350,6 +351,18 @@ begin
Result := nil;
end;
function TDbgTreeView.FindNodeForText(AText: String; AColumn: integer
): PVirtualNode;
var
VNode: PVirtualNode;
begin
for VNode in NoInitNodes do begin
if GetNodeText(VNode, AColumn) = AText then
exit(VNode);
end;
Result := nil;
end;
procedure TDbgTreeView.DeleteNodeEx(Node: PVirtualNode; FreeItem: Boolean;
Reindex: Boolean);
var

View File

@ -238,13 +238,14 @@ type
TLocalsValue = class(TDbgEntityValue)
private
FName: String;
FValue: 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: String read FValue;
property Value: TWatchResultData read FValue;
end;
{ TLocals }
@ -253,17 +254,17 @@ type
private
function GetEntry(AnIndex: Integer): TLocalsValue;
function GetName(const AnIndex: Integer): String;
function GetValue(const AnIndex: Integer): String;
function GetValue(const AnIndex: Integer): TWatchResultData;
protected
function CreateEntry: TDbgEntityValue; override;
public
procedure Add(const AName, AValue: String); overload; deprecated;
procedure Add(const AName: String; AValue: TWatchResultData); overload; deprecated;
procedure SetDataValidity({%H-}AValidity: TDebuggerDataState); virtual;
public
function Count: Integer;reintroduce; virtual;
property Entries[AnIndex: Integer]: TLocalsValue read GetEntry;
property Names[const AnIndex: Integer]: String read GetName;
property Values[const AnIndex: Integer]: String read GetValue;
property Values[const AnIndex: Integer]: TWatchResultData read GetValue;
end;
{ TLocalsList }
@ -975,14 +976,20 @@ procedure TLocalsValue.DoAssign(AnOther: TDbgEntityValue);
begin
inherited DoAssign(AnOther);
FName := TLocalsValue(AnOther).FName;
FValue := TLocalsValue(AnOther).FValue;
FValue.Free;
FValue := TLocalsValue(AnOther).FValue.CreateCopy();
end;
destructor TLocalsValue.Destroy;
begin
inherited Destroy;
FreeAndNil(FValue);
end;
procedure TLocalsValue.Init(AName: String; AValue: TWatchResultData);
begin
FName := AName;
FValue := AValue.AsString;
AValue.Free;
FValue := AValue;
end;
{ TLocalsList }
@ -1009,7 +1016,7 @@ begin
Result := Entries[AnIndex].Name;
end;
function TLocals.GetValue(const AnIndex: Integer): String;
function TLocals.GetValue(const AnIndex: Integer): TWatchResultData;
begin
Result := Entries[AnIndex].Value;
end;
@ -1019,7 +1026,7 @@ begin
Result := TLocalsValue.Create;
end;
procedure TLocals.Add(const AName, AValue: String);
procedure TLocals.Add(const AName: String; AValue: TWatchResultData);
var
v: TLocalsValue;
begin

View File

@ -5,33 +5,42 @@ object LocalsDlg: TLocalsDlg
Width = 500
HorzScrollBar.Page = 499
VertScrollBar.Page = 199
ActiveControl = lvLocals
BorderStyle = bsSizeToolWin
Caption = 'Locals'
ClientHeight = 200
ClientWidth = 500
LCLVersion = '2.1.0.0'
object lvLocals: TListView
LCLVersion = '2.3.0.0'
object vtLocals: TDbgTreeView
Left = 0
Height = 200
Top = 0
Width = 500
Align = alClient
Columns = <
Header.AutoSizeIndex = 0
Header.Columns = <
item
Caption = 'Name'
Width = 150
Position = 0
end
item
Caption = 'Value'
Position = 2
end
item
MaxWidth = 300
Options = [coAllowClick, coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coAllowFocus, coEditable]
Position = 1
end>
MultiSelect = True
Header.Options = [hoColumnResize, hoDrag, hoShowSortGlyphs, hoVisible]
PopupMenu = PopupMenu1
ReadOnly = True
RowSelect = True
SortType = stText
TabOrder = 0
ViewStyle = vsReport
TreeOptions.AutoOptions = [toAutoScrollOnExpand, toAutoSort, toAutoTristateTracking, toAutoDeleteMovedNodes, toAutoChangeScale]
TreeOptions.MiscOptions = [toFullRepaintOnResize, toInitOnSave, toWheelPanning]
TreeOptions.PaintOptions = [toShowButtons, toShowDropmark, toShowRoot, toShowTreeLines, toThemeAware, toUseBlendedImages, toUseExplorerTheme]
TreeOptions.SelectionOptions = [toFullRowSelect, toMultiSelect, toRightClickSelect]
OnChange = vtLocalsChange
OnDragOver = vtLocalsDragOver
OnDragDrop = vtLocalsDragDrop
OnFocusChanged = vtLocalsFocusChanged
OnNodeDblClick = vtLocalsNodeDblClick
end
object ActionList1: TActionList
Left = 152
@ -83,13 +92,6 @@ object LocalsDlg: TLocalsDlg
OnUpdate = actInspectUpdate
ShortCut = 16451
end
object actEvaluateAll: TAction
Category = 'main'
Caption = 'actEvaluateAll'
OnExecute = actEvaluateAllExecute
OnUpdate = actCopyAllUpdate
ShortCut = 16466
end
end
object PopupMenu1: TPopupMenu
Left = 38
@ -103,9 +105,6 @@ object LocalsDlg: TLocalsDlg
object MenuItem3: TMenuItem
Action = actEvaluate
end
object MenuItem9: TMenuItem
Action = actEvaluateAll
end
object MenuItem4: TMenuItem
Caption = '-'
end

View File

@ -44,9 +44,11 @@ uses
// IdeIntf
IDEWindowIntf,
// DebuggerIntf
DbgIntfDebuggerBase, LazDebuggerIntf,
// IDE
IdeDebuggerStringConstants, BaseDebugManager, EnvironmentOpts, Debugger, DebuggerDlg;
DbgIntfDebuggerBase, laz.VirtualTrees, LazDebuggerIntf,
// IDE Debugger
IdeDebuggerStringConstants, BaseDebugManager, EnvironmentOpts, Debugger,
DebuggerDlg, IdeDebuggerWatchResPrinter, IdeDebuggerUtils, DebuggerTreeView,
IdeDebuggerWatchResult, IdeDebuggerBase, Controls, ActiveX;
type
@ -59,10 +61,9 @@ type
actCopyValue: TAction;
actCopyAll: TAction;
actCopyRAWValue: TAction;
actEvaluateAll: TAction;
actWath: TAction;
ActionList1: TActionList;
lvLocals: TListView;
vtLocals: TDbgTreeView;
MenuItem1: TMenuItem;
MenuItem2: TMenuItem;
MenuItem3: TMenuItem;
@ -71,28 +72,38 @@ type
MenuItem6: TMenuItem;
MenuItem7: TMenuItem;
MenuItem8: TMenuItem;
MenuItem9: TMenuItem;
PopupMenu1: TPopupMenu;
procedure actCopyAllExecute(Sender: TObject);
procedure actCopyAllUpdate(Sender: TObject);
procedure actCopyNameExecute(Sender: TObject);
procedure actCopyValueExecute(Sender: TObject);
procedure actEvaluateAllExecute(Sender: TObject);
procedure actEvaluateExecute(Sender: TObject);
procedure actInspectExecute(Sender: TObject);
procedure actInspectUpdate(Sender: TObject);
procedure actCopyRAWValueExecute(Sender: TObject);
procedure actWathExecute(Sender: TObject);
procedure vtLocalsChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure vtLocalsDragDrop(Sender: TBaseVirtualTree; Source: TObject;
DataObject: IDataObject; Formats: TFormatArray; Shift: TShiftState;
const Pt: TPoint; var Effect: LongWord; Mode: TDropMode);
procedure vtLocalsDragOver(Sender: TBaseVirtualTree; Source: TObject;
Shift: TShiftState; State: TDragState; const Pt: TPoint; Mode: TDropMode;
var Effect: LongWord; var Accept: Boolean);
procedure vtLocalsFocusChanged(Sender: TBaseVirtualTree;
Node: PVirtualNode; Column: TColumnIndex);
procedure vtLocalsNodeDblClick(Sender: TBaseVirtualTree;
const HitInfo: THitInfo);
private
FWatchPrinter: TWatchResultPrinter;
FUpdateFlags: set of (ufNeedUpdating);
EvaluateAllCallbackItem: TListItem;
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 EvaluateAllCallback(Sender: TObject; ASuccess: Boolean;
ResultText: String; ResultDBGType: TDBGType);
procedure DoLocalValueFreed(Sender: TObject);
procedure ClearTree(OnlyClearNodeData: boolean = False);
procedure LocalsChanged(Sender: TObject);
function GetThreadId: Integer;
function GetSelectedThreads(Snap: TSnapshot): TIdeThreads;
@ -105,6 +116,7 @@ type
procedure ColSizeSetter(AColId: Integer; ASize: Integer);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property LocalsMonitor;
property ThreadsMonitor;
property CallStackMonitor;
@ -112,7 +124,6 @@ type
end;
function ValueToRAW(const AValue: string): string;
function ExtractValue(const AValue: string; AType: string = ''): string;
implementation
@ -277,34 +288,76 @@ begin
ThreadsNotification.OnCurrent := @LocalsChanged;
CallstackNotification.OnCurrent := @LocalsChanged;
SnapshotNotification.OnCurrent := @LocalsChanged;
FWatchPrinter := TWatchResultPrinter.Create;
Caption:= lisLocals;
lvLocals.Columns[0].Caption:= lisName;
lvLocals.Columns[1].Caption:= lisValue;
vtLocals.Header.Columns[0].Text:= lisName;
vtLocals.Header.Columns[1].Text:= lisValue;
actInspect.Caption := lisInspect;
actWath.Caption := lisWatch;
actEvaluate.Caption := lisEvaluateModify;
actEvaluateAll.Caption := lisEvaluateAll;
actCopyName.Caption := lisLocalsDlgCopyName;
actCopyValue.Caption := lisLocalsDlgCopyValue;
actCopyRAWValue.Caption := lisLocalsDlgCopyRAWValue;
actCopyAll.Caption := lisCopyAll;
for i := low(COL_WIDTHS) to high(COL_WIDTHS) do
lvLocals.Column[i].Width := COL_WIDTHS[i];
vtLocals.Header.Columns[i].Width := COL_WIDTHS[i];
end;
destructor TLocalsDlg.Destroy;
begin
ClearTree;
inherited Destroy;
FWatchPrinter.free;
end;
procedure TLocalsDlg.actInspectUpdate(Sender: TObject);
begin
(Sender as TAction).Enabled := Assigned(lvLocals.Selected);
(Sender as TAction).Enabled := Assigned(GetSelected);
end;
procedure TLocalsDlg.actCopyRAWValueExecute(Sender: TObject);
var
LVal: TLocalsValue;
ResVal: TWatchResultData;
begin
if not DebugBoss.Evaluate(lvLocals.Selected.Caption, @CopyRAWValueEvaluateCallback, []) then
LVal := GetSelected;
if LVal = nil then
exit;
ResVal := LVal.Value;
if ResVal = nil then
exit;
if (ResVal.ValueKind <> rdkPrePrinted) then begin
while ResVal <> nil do begin
case ResVal.ValueKind of
rdkVariant: ResVal := ResVal.DerefData;
rdkConvertRes: ResVal := ResVal.ConvertedRes;
//rdkPCharOrString:
else break;
end;
end;
if ResVal.ValueKind in [rdkString, rdkWideString, rdkChar] then begin
Clipboard.Open;
Clipboard.AsText := ResVal.AsString;
Clipboard.Close;
end
else begin
Clipboard.Open;
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
Clipboard.Close;
end;
exit;
end;
if not DebugBoss.Evaluate(LVal.Name, @CopyRAWValueEvaluateCallback, []) then
begin
Clipboard.Open;
Clipboard.AsText := ValueToRAW(lvLocals.Selected.SubItems[0]);
Clipboard.AsText := ValueToRAW(ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault)));
Clipboard.Close;
end;
end;
@ -313,8 +366,13 @@ procedure TLocalsDlg.actWathExecute(Sender: TObject);
var
S: String;
Watch: TCurrentWatch;
LVal: TLocalsValue;
begin
S := lvLocals.Selected.Caption;
LVal := GetSelected;
if LVal = nil then
exit;
S := LVal.Name;
if s = '' then
exit;
if DebugBoss.Watches.CurrentWatches.Find(S) = nil then
@ -332,76 +390,128 @@ begin
DebugBoss.ViewDebugDialog(ddtWatches);
end;
procedure TLocalsDlg.vtLocalsChange(Sender: TBaseVirtualTree; Node: PVirtualNode
);
begin
end;
procedure TLocalsDlg.vtLocalsDragDrop(Sender: TBaseVirtualTree;
Source: TObject; DataObject: IDataObject; Formats: TFormatArray;
Shift: TShiftState; const Pt: TPoint; var Effect: LongWord; Mode: TDropMode);
begin
end;
procedure TLocalsDlg.vtLocalsDragOver(Sender: TBaseVirtualTree;
Source: TObject; Shift: TShiftState; State: TDragState; const Pt: TPoint;
Mode: TDropMode; var Effect: LongWord; var Accept: Boolean);
begin
end;
procedure TLocalsDlg.vtLocalsFocusChanged(Sender: TBaseVirtualTree;
Node: PVirtualNode; Column: TColumnIndex);
begin
end;
procedure TLocalsDlg.vtLocalsNodeDblClick(Sender: TBaseVirtualTree;
const HitInfo: THitInfo);
begin
end;
function TLocalsDlg.GetSelected: TLocalsValue;
begin
Result := TLocalsValue(vtLocals.FocusedItem(True));
end;
procedure TLocalsDlg.actInspectExecute(Sender: TObject);
begin
DebugBoss.Inspect(lvLocals.Selected.Caption);
if GetSelected <> nil then
DebugBoss.Inspect(GetSelected.Name);
end;
procedure TLocalsDlg.actEvaluateExecute(Sender: TObject);
begin
DebugBoss.EvaluateModify(lvLocals.Selected.Caption);
if GetSelected <> nil then
DebugBoss.EvaluateModify(GetSelected.Name);
end;
procedure TLocalsDlg.actCopyNameExecute(Sender: TObject);
begin
Clipboard.Open;
Clipboard.AsText := lvLocals.Selected.Caption;
if GetSelected <> nil then
Clipboard.AsText := GetSelected.Name
else
Clipboard.AsText := '';
Clipboard.Close;
end;
procedure TLocalsDlg.actCopyAllExecute(Sender: TObject);
Var
AStringList : TStringList;
I : Integer;
LVal: TLocalsValue;
VNode: PVirtualNode;
begin
if lvLocals.Items.Count > 0 then begin
AStringList := TStringList.Create;
for I := 0 to lvLocals.Items.Count - 1 do
AStringList.Values[lvLocals.Items[I].Caption] := lvLocals.Items[I].SubItems[0];
Clipboard.Open;
Clipboard.AsText := AStringList.Text;
Clipboard.Close;
FreeAndNil(AStringList);
AStringList := TStringList.Create;
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));
end;
Clipboard.Open;
Clipboard.AsText := AStringList.Text;
Clipboard.Close;
FreeAndNil(AStringList);
end;
procedure TLocalsDlg.actCopyAllUpdate(Sender: TObject);
begin
(Sender as TAction).Enabled := lvLocals.Items.Count > 0;
(Sender as TAction).Enabled := vtLocals.ChildCount[nil] > 0;
end;
procedure TLocalsDlg.actCopyValueExecute(Sender: TObject);
var
LVal: TLocalsValue;
ResVal: TWatchResultData;
begin
if not DebugBoss.Evaluate(lvLocals.Selected.Caption, @CopyValueEvaluateCallback, []) then
LVal := GetSelected;
if LVal = nil then
exit;
ResVal := LVal.Value;
if ResVal = nil then
exit;
if (ResVal.ValueKind <> rdkPrePrinted) then begin
Clipboard.Open;
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
Clipboard.Close;
exit;
end;
if not DebugBoss.Evaluate(LVal.Name, @CopyValueEvaluateCallback, []) then
begin
Clipboard.Open;
Clipboard.AsText := lvLocals.Selected.SubItems[0];
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
Clipboard.Close;
end
end;
procedure TLocalsDlg.actEvaluateAllExecute(Sender: TObject);
var
I: Integer;
begin
for I := 0 to lvLocals.Items.Count-1 do
begin
EvaluateAllCallbackItem := lvLocals.Items[I];
DebugBoss.Evaluate(EvaluateAllCallbackItem.Caption, @EvaluateAllCallback, []);
end;
EvaluateAllCallbackItem := nil;
end;
procedure TLocalsDlg.LocalsChanged(Sender: TObject);
var
n, idx: Integer;
List: TStringListUTF8Fast;
Item: TListItem;
n: Integer;
Locals: TIDELocals;
Snap: TSnapshot;
s: String;
LVal: TLocalsValue;
VNode, VN2: PVirtualNode;
begin
if (ThreadsMonitor = nil) or (CallStackMonitor = nil) or (LocalsMonitor=nil) then begin
lvLocals.Items.Clear;
ClearTree;
exit;
end;
@ -414,7 +524,7 @@ begin
DebugLn(DBG_DATA_MONITORS, ['DebugDataMonitor: TLocalsDlg.LocalsChanged']);
if GetStackframe < 0 then begin // TODO need dedicated validity property
lvLocals.Items.Clear;
ClearTree;
exit;
end;
@ -429,54 +539,48 @@ begin
Caption:= lisLocals;
end;
List := TStringListUTF8Fast.Create;
if Locals = nil
then begin
ClearTree;
VNode:= vtLocals.AddChild(nil, nil);
vtLocals.NodeText[VNode, 1] := lisLocalsNotEvaluated;
Exit;
end;
BeginUpdate;
try
BeginUpdate;
try
if Locals = nil
then begin
lvLocals.Items.Clear;
Item := lvLocals.Items.Add;
Item.Caption := '';
Item.SubItems.add(lisLocalsNotEvaluated);
Exit;
end;
ClearTree(True);
for n := 0 to Locals.Count - 1 do begin
LVal := Locals.Entries[n];
LVal.AddFreeNotification(@DoLocalValueFreed);
//Get existing items
for n := 0 to lvLocals.Items.Count - 1 do
begin
Item := lvLocals.Items[n];
List.AddObject(Item.Caption, Item);
end;
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;
// add/update entries
for n := 0 to Locals.Count - 1 do
begin
idx := List.IndexOf(Locals.Names[n]);
if idx = -1
then begin
// New entry
Item := lvLocals.Items.Add;
Item.Caption := Locals.Names[n];
Item.SubItems.Add(ExtractValue(Locals.Values[n]));
end
else begin
// Existing entry
Item := TListItem(List.Objects[idx]);
Item.SubItems[0] := ExtractValue(Locals.Values[n]);
List.Delete(idx);
end;
end;
// remove obsolete entries
for n := 0 to List.Count - 1 do
lvLocals.Items.Delete(TListItem(List.Objects[n]).Index);
finally
EndUpdate;
s := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
vtLocals.NodeText[VNode, 0] := LVal.Name;
vtLocals.NodeText[VNode, 1] := s;
end;
VNode := vtLocals.GetFirstNoInit;
while VNode <> nil do begin
if (vtLocals.NodeItem[VNode] = nil) and (vtLocals.NodeControl[VNode] = nil) then begin
VN2 := VNode;
VNode := vtLocals.GetNextVisibleSiblingNoInit(VNode);
vtLocals.DeleteNode(VN2);
end
else
VNode := vtLocals.GetNextVisibleNoInit(VNode);
end;
finally
List.Free;
EndUpdate;
end;
end;
@ -537,31 +641,23 @@ end;
procedure TLocalsDlg.DoBeginUpdate;
begin
lvLocals.BeginUpdate;
inherited DoBeginUpdate;
vtLocals.BeginUpdate;
end;
procedure TLocalsDlg.DoEndUpdate;
begin
inherited DoEndUpdate;
if ufNeedUpdating in FUpdateFlags then LocalsChanged(nil);
lvLocals.EndUpdate;
end;
procedure TLocalsDlg.EvaluateAllCallback(Sender: TObject; ASuccess: Boolean;
ResultText: String; ResultDBGType: TDBGType);
begin
if ASuccess then
begin
if Assigned(EvaluateAllCallbackItem) then
EvaluateAllCallbackItem.SubItems[0] := ExtractValue(ResultText, ResultDBGType.TypeName);
end;
FreeAndNil(ResultDBGType);
vtLocals.EndUpdate;
vtLocalsChange(nil, nil);
end;
function TLocalsDlg.ColSizeGetter(AColId: Integer; var ASize: Integer): Boolean;
begin
if (AColId - 1 >= 0) and (AColId - 1 < lvLocals.ColumnCount) then begin
ASize := lvLocals.Column[AColId - 1].Width;
Result := (ASize <> COL_WIDTHS[AColId - 1]) and (not lvLocals.Column[AColId - 1].AutoSize);
if (AColId - 1 >= 0) and (AColId - 1 < vtLocals.Header.Columns.Count) then begin
ASize := vtLocals.Header.Columns[AColId - 1].Width;
Result := (ASize <> COL_WIDTHS[AColId - 1]);
end
else
Result := False;
@ -570,35 +666,81 @@ end;
procedure TLocalsDlg.ColSizeSetter(AColId: Integer; ASize: Integer);
begin
case AColId of
COL_LOCALS_NAME: lvLocals.Column[0].Width := ASize;
COL_LOCALS_VALUE: lvLocals.Column[1].Width := ASize;
COL_LOCALS_NAME: vtLocals.Header.Columns[0].Width := ASize;
COL_LOCALS_VALUE: vtLocals.Header.Columns[1].Width := ASize;
end;
end;
procedure TLocalsDlg.CopyRAWValueEvaluateCallback(Sender: TObject;
ASuccess: Boolean; ResultText: String; ResultDBGType: TDBGType);
var
LVal: TLocalsValue;
begin
Clipboard.Open;
if ASuccess then
Clipboard.AsText := ValueToRAW(ExtractValue(ResultText, ResultDBGType.TypeName))
else
Clipboard.AsText := ValueToRAW(lvLocals.Selected.SubItems[0]);
begin
LVal := GetSelected;
if (LVal <> nil) and (LVal.Value <> nil) then begin
Clipboard.Open;
Clipboard.AsText := ValueToRAW(ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault)));
Clipboard.Close;
end;
end;
Clipboard.Close;
FreeAndNil(ResultDBGType);
end;
procedure TLocalsDlg.CopyValueEvaluateCallback(Sender: TObject;
ASuccess: Boolean; ResultText: String; ResultDBGType: TDBGType);
var
LVal: TLocalsValue;
begin
Clipboard.Open;
if ASuccess then
Clipboard.AsText := ExtractValue(ResultText, ResultDBGType.TypeName)
else
Clipboard.AsText := lvLocals.Selected.SubItems[0];
else begin
LVal := GetSelected;
if (LVal <> nil) and (LVal.Value <> nil) then begin
Clipboard.Open;
Clipboard.AsText := ClearMultiline(FWatchPrinter.PrintWatchValue(LVal.Value, wdfDefault));
Clipboard.Close;
end;
end;
Clipboard.Close;
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;
end;
if not OnlyClearNodeData then
vtLocals.Clear;
end;
initialization
LocalsDlgWindowCreator := IDEWindowCreators.Add(DebugDialogNames[ddtLocals]);