mirror of https://gitlab.com/basile.b/dexed.git
553 lines
16 KiB
Plaintext
553 lines
16 KiB
Plaintext
unit ce_staticexplorer;
|
|
|
|
{$I ce_defines.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, TreeFilterEdit, Forms, Controls, Graphics, ExtCtrls, Menus,
|
|
ComCtrls, ce_widget, jsonparser, fpjson, process, actnlist, Buttons, Clipbrd,
|
|
ce_common, ce_project, ce_observer, ce_synmemo, ce_interfaces;
|
|
|
|
type
|
|
|
|
{ TCEStaticExplorerWidget }
|
|
TCEStaticExplorerWidget = class(TCEWidget, ICEProjectObserver, ICEMultiDocObserver)
|
|
btnRefresh: TBitBtn;
|
|
imgList: TImageList;
|
|
Panel1: TPanel;
|
|
Tree: TTreeView;
|
|
TreeFilterEdit1: TTreeFilterEdit;
|
|
procedure btnRefreshClick(Sender: TObject);
|
|
procedure TreeDeletion(Sender: TObject; Node: TTreeNode);
|
|
procedure TreeFilterEdit1AfterFilter(Sender: TObject);
|
|
procedure TreeKeyPress(Sender: TObject; var Key: char);
|
|
private
|
|
fDmdProc: TCheckedAsyncProcess;
|
|
fLogMessager: TCELogMessageSubject;
|
|
fActCopyIdent: TAction;
|
|
fActRefresh: TAction;
|
|
fActRefreshOnChange: TAction;
|
|
fActRefreshOnFocus: TAction;
|
|
fActAutoRefresh: TAction;
|
|
fActSelectInSource: TAction;
|
|
fDoc: TCESynMemo;
|
|
fProj: TCEProject;
|
|
fAutoRefresh: boolean;
|
|
fRefreshOnChange: boolean;
|
|
fRefreshOnFocus: boolean;
|
|
fJsonFname: string;
|
|
ndAlias, ndClass, ndEnum, ndFunc: TTreeNode;
|
|
ndImp, ndIntf, ndMix, ndStruct, ndTmp, ndVar: TTreeNode;
|
|
procedure TreeDblClick(Sender: TObject);
|
|
procedure actRefreshExecute(Sender: TObject);
|
|
procedure actAutoRefreshExecute(Sender: TObject);
|
|
procedure actRefreshOnChangeExecute(Sender: TObject);
|
|
procedure actRefreshOnFocusExecute(Sender: TObject);
|
|
procedure actCopyIdentExecute(Sender: TObject);
|
|
procedure updateVisibleCat;
|
|
procedure clearTree;
|
|
//
|
|
procedure produceJsonInfo;
|
|
procedure jsonInfoProduced(sender: TObject);
|
|
//
|
|
procedure optget_AutoRefresh(aWriter: TWriter);
|
|
procedure optset_AutoRefresh(aReader: TReader);
|
|
procedure optget_RefreshOnChange(aWriter: TWriter);
|
|
procedure optset_RefreshOnChange(aReader: TReader);
|
|
procedure optget_RefreshOnFocus(aWriter: TWriter);
|
|
procedure optset_RefreshOnFocus(aReader: TReader);
|
|
protected
|
|
procedure UpdateByDelay; override;
|
|
published
|
|
property autoRefresh: boolean read fAutoRefresh write fAutoRefresh;
|
|
property refreshOnChange: boolean read fRefreshOnChange write fRefreshOnChange;
|
|
property refreshOnFocus: boolean read fRefreshOnFocus write fRefreshOnFocus;
|
|
public
|
|
constructor create(aOwner: TComponent); override;
|
|
destructor destroy; override;
|
|
//
|
|
procedure docNew(aDoc: TCESynMemo);
|
|
procedure docClosing(aDoc: TCESynMemo);
|
|
procedure docFocused(aDoc: TCESynMemo);
|
|
procedure docChanged(aDoc: TCESynMemo);
|
|
//
|
|
function contextName: string; override;
|
|
function contextActionCount: integer; override;
|
|
function contextAction(index: integer): TAction; override;
|
|
//
|
|
procedure projNew(aProject: TCEProject);
|
|
procedure projClosing(aProject: TCEProject);
|
|
procedure projFocused(aProject: TCEProject);
|
|
procedure projChanged(aProject: TCEProject);
|
|
//
|
|
procedure sesoptDeclareProperties(aFiler: TFiler); override;
|
|
end;
|
|
|
|
implementation
|
|
{$R *.lfm}
|
|
|
|
uses LCLProc, ce_libman, ce_symstring;
|
|
|
|
{$REGION Standard Comp/Obj------------------------------------------------------}
|
|
constructor TCEStaticExplorerWidget.create(aOwner: TComponent);
|
|
begin
|
|
fLogMessager := TCELogMessageSubject.create;
|
|
fAutoRefresh := false;
|
|
fRefreshOnFocus := true;
|
|
fRefreshOnChange := false;
|
|
|
|
fActCopyIdent := TAction.Create(self);
|
|
fActCopyIdent.OnExecute:=@actCopyIdentExecute;
|
|
fActCopyIdent.Caption := 'Copy identifier';
|
|
fActRefresh := TAction.Create(self);
|
|
fActRefresh.OnExecute := @actRefreshExecute;
|
|
fActRefresh.Caption := 'Refresh';
|
|
fActAutoRefresh := TAction.Create(self);
|
|
fActAutoRefresh.OnExecute := @actAutoRefreshExecute;
|
|
fActAutoRefresh.Caption := 'Auto-refresh';
|
|
fActAutoRefresh.AutoCheck := true;
|
|
fActAutoRefresh.Checked := fAutoRefresh;
|
|
fActRefreshOnChange := TAction.Create(self);
|
|
fActRefreshOnChange.OnExecute := @actRefreshOnChangeExecute;
|
|
fActRefreshOnChange.Caption := 'Refresh on change';
|
|
fActRefreshOnChange.AutoCheck := true;
|
|
fActRefreshOnChange.Checked := fRefreshOnChange;
|
|
fActRefreshOnFocus := TAction.Create(self);
|
|
fActRefreshOnFocus.OnExecute := @actRefreshOnFocusExecute;
|
|
fActRefreshOnFocus.Caption := 'Refresh on focused';
|
|
fActRefreshOnFocus.AutoCheck := true;
|
|
fActRefreshOnFocus.Checked := fRefreshOnFocus;
|
|
fActSelectInSource := TAction.Create(self);
|
|
fActSelectInSource.OnExecute := @TreeDblClick;
|
|
fActSelectInSource.Caption := 'Select in source';
|
|
//
|
|
inherited;
|
|
//
|
|
ndAlias := Tree.Items[0];
|
|
ndClass := Tree.Items[1];
|
|
ndEnum := Tree.Items[2];
|
|
ndFunc := Tree.Items[3];
|
|
ndImp := Tree.Items[4];
|
|
ndIntf := Tree.Items[5];
|
|
ndMix := Tree.Items[6];
|
|
ndStruct := Tree.Items[7];
|
|
ndTmp := Tree.Items[8];
|
|
ndVar := Tree.Items[9];
|
|
//
|
|
Tree.OnDblClick := @TreeDblClick;
|
|
Tree.PopupMenu := contextMenu;
|
|
//
|
|
EntitiesConnector.addObserver(self);
|
|
end;
|
|
|
|
destructor TCEStaticExplorerWidget.destroy;
|
|
begin
|
|
EntitiesConnector.removeObserver(self);
|
|
//
|
|
killProcess(fDmdProc);
|
|
fLogMessager.Free;
|
|
inherited;
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGION ICESessionOptionsObserver ---------------------------------------------}
|
|
procedure TCEStaticExplorerWidget.optget_AutoRefresh(aWriter: TWriter);
|
|
begin
|
|
aWriter.WriteBoolean(fAutoRefresh);
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.optset_AutoRefresh(aReader: TReader);
|
|
begin
|
|
fAutoRefresh := aReader.ReadBoolean;
|
|
fActAutoRefresh.Checked := fAutoRefresh;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.optget_RefreshOnChange(aWriter: TWriter);
|
|
begin
|
|
aWriter.WriteBoolean(fRefreshOnChange);
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.optset_RefreshOnChange(aReader: TReader);
|
|
begin
|
|
fRefreshOnChange := aReader.ReadBoolean;
|
|
fActRefreshOnChange.Checked := fRefreshOnChange;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.optget_RefreshOnFocus(aWriter: TWriter);
|
|
begin
|
|
aWriter.WriteBoolean(fRefreshOnFocus);
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.optset_RefreshOnFocus(aReader: TReader);
|
|
begin
|
|
fRefreshOnFocus := aReader.ReadBoolean;
|
|
fActRefreshOnFocus.Checked := fRefreshOnFocus;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.sesoptDeclareProperties(aFiler: TFiler);
|
|
begin
|
|
inherited;
|
|
aFiler.DefineProperty(Name + '_AutoRefresh', @optset_AutoRefresh, @optget_AutoRefresh, true);
|
|
aFiler.DefineProperty(Name + '_RefreshOnChange', @optset_RefreshOnChange, @optget_RefreshOnChange, true);
|
|
aFiler.DefineProperty(Name + '_RefreshOnFocus', @optset_RefreshOnFocus, @optget_RefreshOnFocus, true);
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGION ICEContextualActions---------------------------------------------------}
|
|
function TCEStaticExplorerWidget.contextName: string;
|
|
begin
|
|
result := 'Static explorer';
|
|
end;
|
|
|
|
function TCEStaticExplorerWidget.contextActionCount: integer;
|
|
begin
|
|
result := 6;
|
|
end;
|
|
|
|
function TCEStaticExplorerWidget.contextAction(index: integer): TAction;
|
|
begin
|
|
case index of
|
|
0: exit(fActSelectInSource);
|
|
1: exit(fActCopyIdent);
|
|
2: exit(fActRefresh);
|
|
3: exit(fActAutoRefresh);
|
|
4: exit(fActRefreshOnChange);
|
|
5: exit(fActRefreshOnFocus);
|
|
else result := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.actRefreshExecute(Sender: TObject);
|
|
begin
|
|
if Updating then exit;
|
|
produceJsonInfo;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.actAutoRefreshExecute(Sender: TObject);
|
|
begin
|
|
autoRefresh := not autoRefresh;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.actRefreshOnChangeExecute(Sender: TObject);
|
|
begin
|
|
refreshOnChange := not refreshOnChange;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.actRefreshOnFocusExecute(Sender: TObject);
|
|
begin
|
|
refreshOnFocus := not refreshOnFocus;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.actCopyIdentExecute(Sender: TObject);
|
|
begin
|
|
if Tree.Selected = nil then exit;
|
|
Clipboard.AsText:= Tree.Selected.Text;
|
|
end;
|
|
|
|
{$ENDREGION}
|
|
|
|
{$REGION ICEMultiDocObserver ---------------------------------------------------}
|
|
procedure TCEStaticExplorerWidget.docNew(aDoc: TCESynMemo);
|
|
begin
|
|
fDoc := aDoc;
|
|
beginUpdateByDelay;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.docClosing(aDoc: TCESynMemo);
|
|
begin
|
|
if fDoc <> aDoc then exit;
|
|
fDoc := nil;
|
|
clearTree;
|
|
updateVisibleCat;
|
|
beginUpdateByDelay;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.docFocused(aDoc: TCESynMemo);
|
|
begin
|
|
fDoc := aDoc;
|
|
if fAutoRefresh then beginUpdateByDelay
|
|
else if fRefreshOnFocus then produceJsonInfo;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.docChanged(aDoc: TCESynMemo);
|
|
begin
|
|
if fDoc <> aDoc then exit;
|
|
if fAutoRefresh then
|
|
beginUpdateByDelay
|
|
else if fRefreshOnChange then
|
|
produceJsonInfo;
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGION ICEProjectObserver ----------------------------------------------------}
|
|
procedure TCEStaticExplorerWidget.projNew(aProject: TCEProject);
|
|
begin
|
|
fProj := aProject;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.projClosing(aProject: TCEProject);
|
|
begin
|
|
if fProj <> aProject then
|
|
exit;
|
|
fProj := nil;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.projFocused(aProject: TCEProject);
|
|
begin
|
|
fProj := aProject;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.projChanged(aProject: TCEProject);
|
|
begin
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGION Symbol-tree things ----------------------------------------------------}
|
|
procedure TCEStaticExplorerWidget.UpdateByDelay;
|
|
begin
|
|
if not fAutoRefresh then exit;
|
|
produceJsonInfo;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.TreeDeletion(Sender: TObject; Node: TTreeNode);
|
|
begin
|
|
if (node.Data <> nil) then
|
|
Dispose(PInt64(node.Data));
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.btnRefreshClick(Sender: TObject);
|
|
begin
|
|
fActRefresh.Execute;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.updateVisibleCat;
|
|
begin
|
|
if (fDoc <> nil) then
|
|
begin
|
|
ndAlias.Visible := ndAlias.Count > 0;
|
|
ndClass.Visible := ndClass.Count > 0;
|
|
ndEnum.Visible := ndEnum.Count > 0;
|
|
ndFunc.Visible := ndFunc.Count > 0;
|
|
ndImp.Visible := ndImp.Count > 0;
|
|
ndIntf.Visible := ndIntf.Count > 0;
|
|
ndMix.Visible := ndMix.Count > 0;
|
|
ndStruct.Visible:= ndStruct.Count > 0;
|
|
ndTmp.Visible := ndTmp.Count > 0;
|
|
ndVar.Visible := ndVar.Count > 0;
|
|
end else
|
|
begin
|
|
ndAlias.Visible := true;
|
|
ndClass.Visible := true;
|
|
ndEnum.Visible := true;
|
|
ndFunc.Visible := true;
|
|
ndImp.Visible := true;
|
|
ndIntf.Visible := true;
|
|
ndMix.Visible := true;
|
|
ndStruct.Visible:= true;
|
|
ndTmp.Visible := true;
|
|
ndVar.Visible := true;
|
|
end;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.clearTree;
|
|
begin
|
|
ndAlias.DeleteChildren;
|
|
ndClass.DeleteChildren;
|
|
ndEnum.DeleteChildren;
|
|
ndFunc.DeleteChildren;
|
|
ndImp.DeleteChildren;
|
|
ndIntf.DeleteChildren;
|
|
ndMix.DeleteChildren;
|
|
ndStruct.DeleteChildren;
|
|
ndTmp.DeleteChildren;
|
|
ndVar.DeleteChildren;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.TreeFilterEdit1AfterFilter(Sender: TObject);
|
|
begin
|
|
if TreeFilterEdit1.Filter ='' then
|
|
updateVisibleCat;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.TreeKeyPress(Sender: TObject; var Key: char);
|
|
begin
|
|
if Key = #13 then TreeDblClick(nil);
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.TreeDblClick(Sender: TObject);
|
|
var
|
|
line: Int64;
|
|
begin
|
|
if fDoc = nil then exit;
|
|
if Tree.Selected = nil then exit;
|
|
if Tree.Selected.Data = nil then exit;
|
|
//
|
|
line := PInt64(Tree.Selected.Data)^;
|
|
fDoc.CaretY := line;
|
|
fDoc.SelectLine;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.produceJsonInfo;
|
|
var
|
|
srcFname, itm: string;
|
|
i: Integer;
|
|
begin
|
|
if fDoc = nil then exit;
|
|
if fDoc.Lines.Count = 0 then exit;
|
|
|
|
// standard process options
|
|
killProcess(fDmdProc);
|
|
fDmdProc := TCheckedAsyncProcess.Create(nil);
|
|
fDmdProc.ShowWindow := swoHIDE;
|
|
fDmdProc.Options := [];
|
|
fDmdProc.Executable := 'dmd';
|
|
fDmdProc.OnTerminate := @jsonInfoProduced;
|
|
|
|
// focused source
|
|
fJsonFname := fDoc.tempFilename + '.json';
|
|
srcFname := fDoc.fileName;
|
|
if not fileExists(srcFname) then begin
|
|
srcFname := fDoc.tempFilename;
|
|
fDoc.saveTempFile;
|
|
end;
|
|
//else fDoc.save; refreshonChange/autorefresh don't work until existing doc is saved
|
|
fDmdProc.Parameters.Add(srcFname);
|
|
|
|
// other project sources, -I, -J
|
|
if fProj <> nil then if fProj.isProjectSource(srcFname) then
|
|
begin
|
|
fDmdProc.CurrentDirectory := extractFilePath(fProj.fileName);
|
|
for i := 0 to fProj.Sources.Count-1 do begin
|
|
itm := fProj.getAbsoluteSourceName(i);
|
|
if srcFname <> itm then fDmdProc.Parameters.Add(itm);
|
|
end;
|
|
for itm in fProj.currentConfiguration.pathsOptions.Includes do
|
|
fDmdProc.Parameters.Add('-I' + symbolExpander.get(itm));
|
|
for itm in fProj.currentConfiguration.pathsOptions.Imports do
|
|
fDmdProc.Parameters.Add('-J' + symbolExpander.get(itm));
|
|
end;
|
|
|
|
//adds the libman entries
|
|
LibMan.getLibFiles(nil, fDmdProc.Parameters);
|
|
LibMan.getLibSources(nil, fDmdProc.Parameters);
|
|
|
|
// option to produce the Json file.
|
|
fDmdProc.Parameters.Add('-c');
|
|
fDmdProc.Parameters.Add('-o-');
|
|
fDmdProc.Parameters.Add('-Xf' + fJsonFname);
|
|
|
|
fDmdProc.Execute;
|
|
end;
|
|
|
|
procedure TCEStaticExplorerWidget.jsonInfoProduced(sender: TObject);
|
|
var
|
|
str: TMemoryStream;
|
|
prs: TJsonParser;
|
|
dat: TJsonData;
|
|
memb: TJsonData;
|
|
ndCat: TTreeNode;
|
|
ln: PInt64;
|
|
nme, knd: string;
|
|
i: NativeInt;
|
|
|
|
// recursively display members, without master categories.
|
|
procedure digMembers(const srcDt: TJsonData; const srcNd: TTreeNode);
|
|
var
|
|
_memb: TJsonData;
|
|
_ln: PInt64;
|
|
_nme: string;
|
|
_i: NativeInt;
|
|
_nd: TTreeNode;
|
|
begin
|
|
_memb := srcDt.FindPath('members');
|
|
if _memb <> nil then for _i := 0 to _memb.Count-1 do
|
|
begin
|
|
_ln := new(PInt64);
|
|
_ln^ := _memb.Items[_i].GetPath('line').AsInt64;
|
|
_nme := _memb.Items[_i].GetPath('name').AsString;
|
|
_nd := Tree.Items.AddChildObject(srcNd, _nme, _ln);
|
|
digMembers(_memb.Items[_i], _nd);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
if ndAlias = nil then exit;
|
|
|
|
clearTree;
|
|
updateVisibleCat;
|
|
|
|
if not FileExists(fJsonFname) then exit;
|
|
|
|
// load json
|
|
str := TMemoryStream.Create;
|
|
try
|
|
str.LoadFromFile(fJsonFname);
|
|
str.Position := 0;
|
|
prs := TJsonParser.Create(str);
|
|
try
|
|
dat := prs.Parse;
|
|
finally
|
|
prs.Free;
|
|
end;
|
|
finally
|
|
str.Free;
|
|
DeleteFile(fJsonFname);
|
|
end;
|
|
|
|
// update tree
|
|
try
|
|
memb := dat.items[0].FindPath('members');
|
|
if memb <> nil then for i := 0 to memb.Count-1 do
|
|
begin
|
|
|
|
ndcat := nil;
|
|
// categories
|
|
ln := new(PInt64);
|
|
ln^ := memb.Items[i].GetPath('line').AsInt64;
|
|
nme := memb.Items[i].GetPath('name').AsString;
|
|
|
|
knd := memb.Items[i].GetPath('kind').AsString;
|
|
case knd of
|
|
'alias' :ndCat := Tree.Items.AddChildObject(ndAlias, nme, ln);
|
|
'class' :ndCat := Tree.Items.AddChildObject(ndClass, nme, ln);
|
|
'enum', 'enum member'
|
|
:ndCat := Tree.Items.AddChildObject(ndEnum, nme, ln);
|
|
'function' :ndCat := Tree.Items.AddChildObject(ndFunc, nme, ln);
|
|
'import', 'static import'
|
|
:ndCat := Tree.Items.AddChildObject(ndImp, nme, ln);
|
|
'interface' :ndCat := Tree.Items.AddChildObject(ndIntf, nme, ln);
|
|
'mixin' :ndCat := Tree.Items.AddChildObject(ndMix, nme, ln);
|
|
'struct' :ndCat := Tree.Items.AddChildObject(ndStruct, nme, ln);
|
|
'template' :ndCat := Tree.Items.AddChildObject(ndTmp, nme, ln);
|
|
'variable' :ndCat := Tree.Items.AddChildObject(ndVar, nme, ln);
|
|
else subjLmFromString(fLogMessager, 'static explorer does not handle this kind: '
|
|
+ knd, nil, amcApp, amkWarn);
|
|
end;
|
|
|
|
if ndCat = nil then
|
|
begin
|
|
{$IFDEF DEBUG}
|
|
DebugLn(memb.Items[i].GetPath('kind').AsString);
|
|
{$ENDIF}
|
|
continue;
|
|
end;
|
|
|
|
ndCat.Parent.Visible := true;
|
|
|
|
//recursive
|
|
digMembers(memb.Items[i], ndCat);
|
|
|
|
end;
|
|
finally
|
|
if dat <> nil then
|
|
begin
|
|
dat.Clear;
|
|
dat.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
{$ENDREGION --------------------------------------------------------------------}
|
|
|
|
end.
|