lazarus/components/instantsearch/ideinstantsearch.pas
2022-12-31 18:58:51 +01:00

1161 lines
32 KiB
ObjectPascal

unit ideinstantsearch;
{$mode ObjFPC}{$H+}
{$modeswitch typehelpers}
interface
uses
Classes, SysUtils, idemcindexer, lazideintf, idemsgintf, LazConfigStorage, BaseIDEIntf, projectintf,
ExtCtrls;
Const
RTLTree = 'RTL';
FCLTree = 'FCL';
CompilerTree = 'Compiler';
LCLTree = 'LCL';
LazTree = 'Lazarus';
// Disconnect after timeout. in seconds
DefaultIdleDisconnectTimeOut = 30;
DefaultMaxStartSearchLength = 255;
DefaultMaxResultCount = 100;
// Number of seconds to wait after project was opened before start indexing in case of pmiOnOpen
DefaultOpenDelay = 5;
// Number of seconds to wait after project was opened before marking it for indexing.
DefaultTimedDelay = 5 * 60;
// Source tree ID in project custom data
SInstantSearchID = 'InstantSearchID';
// Source tree ID in project custom data
SInstantSearchProjFileName = 'InstantSearchProjFileName';
Type
TIndexProjectMoment = (
ipmOnOpen, // after open project, index project
ipmManual, // User manually starts indexing operation
ipmFirstsave // Indexing starts when first save happens.
);
TIndexProjectStrategy = (
ipsAll, // All projects are marked for indexing.
ipsTimed, // A project is marked for indexing if it is open after TimedDelay
ipsManual // a project is marked for indexing by user
);
{ TIndexProjectMomentHelper }
TIndexProjectMomentHelper = Type helper for TIndexProjectMoment
Function ToString : string;
end;
{ TIndexProjectStrategyHelper }
TIndexProjectStrategyHelper = Type helper for TIndexProjectStrategy
Function ToString : string;
end;
{ TSourceTreeDefinition }
TSourceTreeDefinition = Class(TCollectionItem)
private
FAllFiles: Boolean;
FBaseDir: String;
FEnabled: Boolean;
FExtensions: String;
FName: String;
FRecurse: Boolean;
FSystem: Boolean;
Public
procedure Assign(aSource : TPersistent); override;
Published
Property System : Boolean Read FSystem Write FSystem;
Property Name : String Read FName Write FName;
Property BaseDir : String Read FBaseDir Write FBaseDir;
Property Recurse : Boolean Read FRecurse Write FRecurse;
Property AllFiles : Boolean Read FAllFiles Write FAllFiles;
Property Extensions : String Read FExtensions Write FExtensions;
Property Enabled : Boolean Read FEnabled Write FEnabled;
end;
{ TSourceTreeDefinitionList }
TSourceTreeDefinitionList = class(TOwnedCollection)
private
function GetDef(aIndex : Integer): TSourceTreeDefinition;
procedure SetDef(aIndex : Integer; AValue: TSourceTreeDefinition);
Protected
procedure Update(Item: TCollectionItem); override;
Public
Function AddTree(const aName : String): TSourceTreeDefinition;
Function IndexOfName(const aName : String) : Integer;
Function FindByName(const aName : String): TSourceTreeDefinition;
Property Definitions[aIndex : Integer] : TSourceTreeDefinition Read GetDef Write SetDef; default;
end;
{ TIDEInstantSearchManager }
TIDEInstantSearchManager = class(TComponent)
Private
FConfigured: Boolean;
FIdleDisconnectTimeOut: Integer;
FIndexer: TManticoreSearchSources;
FIndexProjectDelay: Cardinal;
FMaxStartSearchLength: Integer;
FMinSearchLength: Integer;
FIndexProjectMoment: TIndexProjectMoment;
FIndexProjectStrategy: TIndexProjectStrategy;
FOnIndexStart: TNotifyEvent;
FOnSourceTreesChanged: TNotifyEvent;
FProjectTreeName: string;
FSearchProject: boolean;
FServerTrees: TStringList;
FSourceTrees: TSourceTreeDefinitionList;
FIndexThread : TThread;
FOnIndexDone : TNotifyEvent;
FMarkProject : TLazProject;
FMarkProjectTimer : TTimer;
FStartIndexTimer : TTimer;
class var _instance : TIDEInstantSearchManager;
procedure DoOnIndexDone(Sender: TObject);
procedure DoManticoreLog(Sender: TObject; aKind: TMCLogKind; const aMessage: String);
procedure DoRefreshTimer(Sender: TObject);
procedure GetProjectFiles(aProject: TLazProject; aList: TStrings);
procedure SetMinSearchLength(AValue: Integer);
procedure SetRefreshTimer;
procedure SetServerTrees(AValue: TStringList);
procedure SetSourceTrees(AValue: TSourceTreeDefinitionList);
procedure DoMarkProjectIndexed(Sender: TObject);
Protected
procedure CallIndexDone;
procedure DoStartIndexing;
Procedure SourceTreesChanged; virtual;
Public
Class constructor Init;
Class Destructor Done;
Class Property Instance : TIDEInstantSearchManager Read _Instance;
Constructor Create(aOwner : TComponent); override;
Destructor Destroy; override;
// Load/Save config
function GetDefaultConfigFileName: String;
Procedure LoadConfig(const aFileName : String);
Procedure SaveConfig(const aFileName : string);
Procedure Load;
Procedure Save;
// Start timer to mark project as indexable
procedure StartMarkProjectTimer(aProject: TLazProject);
// Refresh system trees
procedure RefreshSystemTrees(Force : Boolean);
// Assign a tree ID to a project
function AssignProjectTreeID(aProject: TLazProject): String;
// Index files of project
Procedure IndexProjectFiles(aProject : TLazProject);
// Rescan FPC directory trees
procedure RescanFPCDir;
// Clear mark project timer
procedure ClearMarkProjectTimer;
// Mark a project indexable
procedure MarkProjectIndexed(aProject: TLazProject);
// Do search, taking into account active trees.
function Search(const aTerm: String): TMCSearchResultArray;
// Create default trees. Only call in or after register, it needs IDE macros.
procedure CreateDefaultTrees;
// Are all properties to connect set ?
function CanConnect : Boolean;
// Test connection. If aIndexList is assigned, fill list with names of existing trees.
function TestConnect(aIndexList : TStrings) : Boolean;
// Returns empty string if we can search. Returns what needs to be configured.
Function CanSearch : string;
// Start indexing operation for tree.
function IndexTree(aTree : TSourceTreeDefinition) : Boolean;
// Start indexing operation on a list of trees.
function IndexTrees(aList: TSourceTreeDefinitionList; aFreeList : Boolean): Boolean;
// Active project tree name
Property ProjectTreeName : string Read FProjectTreeName Write FProjectTreeName;
// Search project or not. If yes, projecttreeName Will be added to the list of tree to search in.
Property SearchProject : boolean Read FSearchProject Write FSearchProject;
// When to index a project when it is marked for indexing
Property IndexProjectMoment : TIndexProjectMoment Read FIndexProjectMoment Write FIndexProjectMoment default ipmManual;
// When to mark a project for indexing
Property IndexProjectStrategy : TIndexProjectStrategy Read FIndexProjectStrategy Write FIndexProjectStrategy default ipsManual;
// when IndexProjectStrategy is ipsTimed, Time (minutes) to wait when a project is opened
Property IndexProjectDelay : Cardinal Read FIndexProjectDelay Write FIndexProjectDelay default DefaultTimedDelay;
// Check whether one of the source tree indexes must be refreshed, and start an index operation
procedure CheckRefresh(aExisting: TStrings; aForceRefresh : Boolean);
// Is an index job currently running ?
Function IsIndexing : Boolean;
// Global indexer instance, use only for searching.
Property Indexer : TManticoreSearchSources Read FIndexer;
// Minimum number of characters needed before a search can be started.
Property MinSearchLength : Integer Read FMinSearchLength Write SetMinSearchLength default DefaultMCMinInfixLen;
// When checking clipboard content, maximum length for text to be accepted as search text
Property MaxStartSearchLength : Integer Read FMaxStartSearchLength Write FMaxStartSearchLength default DefaultMaxStartSearchLength;
// Our server trees.
Property SourceTrees : TSourceTreeDefinitionList Read FSourceTrees Write SetSourceTrees;
// Source trees on server
Property ServerTrees : TStringList Read FServerTrees Write SetServerTrees;
// Get notification when index operation is done.
Property OnIndexStart : TNotifyEvent Read FOnIndexStart Write FOnIndexStart;
// Get notification when index operation is done.
Property OnIndexDone : TNotifyEvent Read FOnIndexDone Write FOnIndexDone ;
// Get notification when source trees change
Property OnSourceTreesChanged : TNotifyEvent Read FOnSourceTreesChanged Write FOnSourceTreesChanged;
// Configured.
Property Configured : Boolean Read FConfigured Write FConfigured;
// Timeout before disconnecting. In seconds
Property IdleDisconnectTimeOut : Integer Read FIdleDisconnectTimeOut Write FIdleDisconnectTimeOut;
end;
Function IDEInstantSearchManager : TIDEInstantSearchManager;
implementation
uses TypInfo, Strutils, IDEExternToolIntf, MacroIntf, IDEOptionsIntf, instantsearchstrings;
function IDEInstantSearchManager: TIDEInstantSearchManager;
begin
Result:=TIDEInstantSearchManager.Instance;
end;
Type
{ TInstantSearchIndexThread }
TInstantSearchIndexThread = class(TThread)
Private
FIndexer : TManticoreSearchSources;
Protected
procedure DoLog(aKind: TMCLogKind; const aMessage: string); overload;
procedure DoLog(aKind: TMCLogKind; const aFmt: String; const aArgs: array of const); overload;
Property Indexer : TManticoreSearchSources Read FIndexer;
Public
Constructor Create(aIndexer : TManticoreSearchSources; aOnTerminate : TNotifyEvent);
end;
TInstantSearchIndexTreeThread = class(TInstantSearchIndexThread)
private
FTrees : TSourceTreeDefinitionList;
procedure DoCheckTerminate(Sender: TObject; const aFileName: String; var aContinue: Boolean);
function IndexTree(aTree: TSourceTreeDefinition): Integer;
Public
Constructor Create(aIndexer : TManticoreSearchSources; aTrees : TSourceTreeDefinitionList; aOnTerminate : TNotifyEvent);
Destructor Destroy; override;
Procedure Execute; override;
end;
{ TIndexFilesData }
TIndexFilesData = class
private
FName : String;
FBaseDir: String;
Flist: TStrings;
FTree: string;
Public
Constructor Create(aName,aTree,aBaseDir : String; aList : TStrings);
Destructor destroy; override;
Property Name : string Read FName;
Property Tree : string Read FTree;
Property BaseDir : String Read FBaseDir;
Property List : TStrings Read Flist;
end;
{ TInstantSearchIndexFilesThread }
TInstantSearchIndexFilesThread = class(TInstantSearchIndexThread)
Private
FData : TIndexFilesData;
Protected
Property Data : TIndexFilesData Read FData;
Public
Constructor Create(aIndexer : TManticoreSearchSources; const aData : TIndexFilesdata; aOnTerminate : TNotifyEvent);
Destructor Destroy; override;
Procedure Execute; override;
end;
{ TIndexFilesData }
constructor TIndexFilesData.Create(aName, aTree, aBaseDir: String;
aList: TStrings);
begin
FName:=aName;
FTree:=aTree;
FBaseDir:=IncludeTrailingPathDelimiter(aBaseDir);
FList:=aList;
end;
destructor TIndexFilesData.destroy;
begin
FreeAndNil(Flist);
inherited destroy;
end;
{ TInstantSearchIndexFilesThread }
constructor TInstantSearchIndexFilesThread.Create(
aIndexer: TManticoreSearchSources; const aData: TIndexFilesdata;
aOnTerminate: TNotifyEvent);
begin
FData:=aData;
Inherited Create(aIndexer,aOnTerminate);
end;
destructor TInstantSearchIndexFilesThread.Destroy;
begin
FreeAndNil(FData);
inherited Destroy;
end;
procedure TInstantSearchIndexFilesThread.Execute;
var
aCount,I : Integer;
FN,RelFN,Msg : string;
begin
aCount:=0;
try
if not Indexer.Connected then
Indexer.Connect;
DoLog(mlkProgress,lrsStartIndexingProject,[Data.Name,Data.BaseDir]);
FIndexer.DeleteTree(Data.Tree);
For I:=0 to Data.List.Count-1 do
begin
inc(aCount);
FN:=Data.list[i];
if Data.BaseDir='' then
RelFN:=FN
else
RelFN:=ExtractRelativePath(Data.BaseDir,FN);
Indexer.IndexSourceFile(Data.Tree,RelFN,FN);
if Terminated then
break;
end;
if Terminated then
Msg:=lrsIndexingProjectTerminated
else
Msg:=lrsFinishedIndexingProject;
DoLog(mlkProgress,Msg,[Data.Name,aCount])
except
On E : exception do
DoLog(mlkError,'Exception %s while indexing project %s : %s',[E.ClassName,Data.Name,E.Message]);
end;
end;
{ TInstantSearchIndexThread }
constructor TInstantSearchIndexThread.Create(aIndexer: TManticoreSearchSources; aOnTerminate: TNotifyEvent);
begin
FIndexer:=aIndexer;
OnTerminate:=aOnTerminate;
Inherited Create(False);
end;
destructor TInstantSearchIndexTreeThread.Destroy;
begin
FreeAndNil(FTrees);
inherited Destroy;
end;
Procedure TInstantSearchIndexThread.DoLog(aKind : TMCLogKind; Const aMessage : string);
begin
if Assigned(FIndexer) and Assigned(FIndexer.Onlog) then
FIndexer.Onlog(Self,aKind,aMessage);
end;
Procedure TInstantSearchIndexThread.DoLog(aKind : TMCLogKind; Const aFmt : String; Const aArgs : Array of const);
begin
DoLog(aKind,Format(aFmt,aArgs));
end;
{ TInstantSearchIndexTreeThread }
Constructor TInstantSearchIndexTreeThread.Create(aIndexer : TManticoreSearchSources; aTrees : TSourceTreeDefinitionList; aOnTerminate : TNotifyEvent);
begin
FTrees:=aTrees;
Inherited Create(aIndexer,aOnTerminate);
end;
Function TInstantSearchIndexTreeThread.IndexTree(aTree : TSourceTreeDefinition) : Integer;
Var
Options : TMCIndexOptions;
begin
Findexer.Extensions:=aTree.Extensions.Split([';'],TStringSplitOptions.ExcludeEmpty);
if not FIndexer.Connected then
FIndexer.Connect;
Options:=[ioStoreRelativeNames];
if aTree.AllFiles then
Include(Options,ioAllFiles);
if aTree.Recurse then
Include(Options,ioRecurse);
FIndexer.DeleteTree(aTree.Name);
Result:=FIndexer.IndexSources(aTree.Name,aTree.BaseDir,Options,@DoCheckTerminate);
end;
procedure TInstantSearchIndexTreeThread.DoCheckTerminate(Sender: TObject;
const aFileName: String; var aContinue: Boolean);
begin
if Terminated then
aContinue:=False;
end;
procedure TInstantSearchIndexTreeThread.Execute;
var
I,aCount : Integer;
aTree : TSourceTreeDefinition;
aName : string;
begin
For I:=0 to FTrees.Count-1 do
try
aTree:=FTrees[i];
aName:=aTree.Name;
DoLog(mlkProgress,lrsStartIndexingTree,[aName,aTree.BaseDir]);
aCount:=IndexTree(aTree);
DoLog(mlkProgress,lrsFinishedIndexingTree,[aName,aCount]);
if Terminated then
Break;
except
On E : exception do
DoLog(mlkError,'Exception %s while indexing tree %s : %s',[E.ClassName,aName,E.Message]);
end;
end;
{ TIndexProjectStrategyHelper }
function TIndexProjectStrategyHelper.ToString: string;
begin
Case self of
ipsAll : Result:=lrsAllProjects;
ipsTimed : Result:=lrsTimed;
ipsManual : Result:=lrsManual;
else
Result:='?';
end;
end;
{ TIndexProjectMomentHelper }
function TIndexProjectMomentHelper.ToString: string;
begin
Case self of
ipmOnOpen : Result:=lrsOnOpen;
ipmManual : Result:=lrsManual;
ipmFirstsave : Result:=lrsOnFirstSave;
else
Result:='?';
end;
end;
{ TIDEInstantSearchManager }
procedure TIDEInstantSearchManager.SetMinSearchLength(AValue: Integer);
begin
if FMinSearchLength=AValue then Exit;
if aValue<2 then
Raise EManticoreSearch.Create(lrsErrorMinLengthIs2);
FMinSearchLength:=AValue;
FIndexer.MinInfixLen:=AValue;
end;
procedure TIDEInstantSearchManager.CallIndexDone;
begin
// Must be called in main thread
If Assigned(FonIndexDone) then
FonIndexDone(Self);
DoManticoreLog(self,mlkProgress,lrsIndexingOperationFinished);
end;
procedure TIDEInstantSearchManager.DoMarkProjectIndexed(Sender: TObject);
begin
if assigned(FMarkProject) and (FMarkProject=LazarusIDE.ActiveProject) then
begin
MarkProjectIndexed(FMarkProject);
end;
end;
procedure TIDEInstantSearchManager.SourceTreesChanged;
begin
if assigned(FOnSourceTreesChanged) then
FOnSourceTreesChanged(Self);
end;
procedure TIDEInstantSearchManager.DoOnIndexDone(Sender: TObject);
begin
if Sender=FIndexThread then
FIndexThread:=Nil;
if assigned(FonIndexDone) then
TThread.Synchronize(TThread.CurrentThread,@CallIndexDone);
end;
procedure TIDEInstantSearchManager.DoManticoreLog(Sender: TObject;
aKind: TMCLogKind; const aMessage: String);
Var
mlu : TMessageLineUrgency;
begin
// Writeln(aKind,': ',aMessage);
if (aKind in [mlkError,mlkProgress]) and Assigned(IDEMessagesWindow) then
begin
if aKind=mlkError then
mlu:=mluError
else
mlu:=mluProgress;
IDEMessagesWindow.AddCustomMessage(mlu,lrsInstantSearch+': '+aMessage,'',0,0,lrsInstantSearch);
end;
end;
procedure TIDEInstantSearchManager.SetServerTrees(AValue: TStringList);
begin
if FServerTrees=AValue then Exit;
FServerTrees.Assign(AValue);
end;
procedure TIDEInstantSearchManager.SetSourceTrees(
AValue: TSourceTreeDefinitionList);
begin
if FSourceTrees=AValue then Exit;
FSourceTrees.Assign(AValue);
end;
class constructor TIDEInstantSearchManager.Init;
begin
_Instance:=TIDEInstantSearchManager.Create(Nil);
end;
class destructor TIDEInstantSearchManager.Done;
begin
FreeAndNil(_Instance);
end;
constructor TIDEInstantSearchManager.Create(aOwner: TComponent);
begin
Inherited;
IdleDisconnectTimeOut:=DefaultIdleDisconnectTimeOut;
FMinSearchLength:=DefaultMCMinInfixLen;
FIndexer:=TManticoreSearchSources.Create(Nil);
FIndexer.OnLog:=@DoManticoreLog;
FIndexer.Extensions:=['pp','pas','lpr','inc'];
FIndexer.Limit:=DefaultMaxResultCount;
FServerTrees:=TStringList.Create;
FSourceTrees:=TSourceTreeDefinitionList.Create(Self,TSourceTreeDefinition);
FMaxStartSearchLength:=DefaultMaxStartSearchLength;
FIndexProjectMoment:=ipmManual;
FIndexProjectStrategy:=ipsManual;
FIndexProjectDelay:=DefaultTimedDelay;
end;
procedure TIDEInstantSearchManager.CreateDefaultTrees;
function GetCurrentFPCSourceDirectory: string;
begin
Result:='$(FPCSrcDir)';
if not IDEMacros.SubstituteMacros(Result) then
raise Exception.Create('unable to retrieve FPCSrcDir');
end;
Var
FPCDir : String;
begin
FPCDir:=IncludeTrailingPathDelimiter(GetCurrentFPCSourceDirectory);
With FSourceTrees.AddTree(RTLTree) do
begin
BaseDir:=FPCDir+'rtl';
Recurse:=True;
System:=true;
Extensions:='pp;pas;inc';
end;
With FSourceTrees.AddTree(FCLTree) do
begin
BaseDir:=FPCDir+'packages';
Recurse:=True;
System:=true;
Extensions:='pp;pas;inc';
end;
With FSourceTrees.AddTree(CompilerTree) do
begin
BaseDir:=FPCDir+'compiler';
Recurse:=True;
System:=true;
Extensions:='pp;pas;inc';
end;
With FSourceTrees.AddTree(LCLTree) do
begin
BaseDir:=IncludeTrailingPathDelimiter(IDEEnvironmentOptions.GetParsedLazarusDirectory)+'lcl'+PathDelim;
Recurse:=True;
System:=true;
Extensions:='pp;pas;inc';
end;
end;
procedure TIDEInstantSearchManager.LoadConfig(const aFileName: String);
var
Cfg: TConfigStorage;
List,S,aEnableds : String;
aTree : TSourceTreeDefinition;
I : integer;
begin
List:='';
Cfg:=GetIDEConfigStorage(aFilename,True);
try
Configured:=Cfg.GetValue('Search/Configured',Configured);
MaxStartSearchLength:=Cfg.GetValue('Search/MaxStartSearchLength',MaxStartSearchLength);
Findexer.Limit:=Cfg.GetValue('Indexer/Limit',Findexer.Limit);
MinSearchLength:=Cfg.GetValue('Indexer/MinSearchLength',MinSearchLength);
Findexer.Extensions:=Cfg.GetValue('Indexer/Extensions','').Split(';',TStringSplitOptions.ExcludeEmpty);
Findexer.HostName:=Cfg.GetValue('Indexer/Hostname',Findexer.HostName);
Findexer.Port:=Cfg.GetValue('Indexer/Port',Findexer.Port);
IdleDisconnectTimeOut:=Cfg.GetValue('Indexer/IdleDisconnect',IdleDisconnectTimeOut);
aEnableds:=Cfg.GetValue('Indexer/EnabledSystemTrees','');
S:=GetEnumName(TypeInfo(TMCTransport),Ord(Findexer.Transport));
S:=Cfg.GetValue('Indexer/Transport',S);
I:=GetEnumValue(TypeInfo(TMCTransport),S);
if I<>-1 then
Findexer.Transport:=TMCTransport(I);
S:=GetEnumName(TypeInfo(TMCMySQLClientVersion),Ord(Findexer.MySQLVersion));
S:=Cfg.GetValue('Indexer/MySQLVersion',S);
I:=GetEnumValue(TypeInfo(TMCMySQLClientVersion),S);
if I<>-1 then
Findexer.MySQLVersion:=TMCMySQLClientVersion(I);
Cfg.GetValue('Trees/Names',List);
For I:=SourceTrees.Count-1 downto 0 do
if Not SourceTrees[i].System then
SourceTrees.Delete(I);
For I:=SourceTrees.Count-1 downto 0 do
With SourceTrees[i] do
Enabled:=Pos(';'+Name+';',aEnableds)>0;
for S in SplitString(List,';') do
if S<>'' then
begin
ATree:=SourceTrees.AddTree(S);
Cfg.ReadObject('Trees/list/'+aTree.Name+'/',aTree);
end;
finally
Cfg.Free;
end;
end;
procedure TIDEInstantSearchManager.SaveConfig(const aFileName: string);
var
Cfg: TConfigStorage;
S,ext : String;
I : Integer;
aTree : TSourceTreeDefinition;
begin
S:='';
Cfg:=GetIDEConfigStorage(aFilename,True);
try
Cfg.SetValue('Search/Configured',Configured);
Cfg.SetValue('Search/MaxStartSearchLength',MaxStartSearchLength);
Cfg.SetValue('Indexer/MinSearchLength',MinSearchLength);
Cfg.SetValue('Indexer/Limit',Findexer.Limit);
Cfg.SetValue('Indexer/Transport',GetEnumName(TypeInfo(TMCTransport),Ord(Findexer.Transport)));
Cfg.SetValue('Indexer/IdleDisconnect',IdleDisconnectTimeOut);
S:=GetEnumName(TypeInfo(TMCMySQLClientVersion),Ord(Findexer.MySQLVersion));
Cfg.SetValue('Indexer/MySQLVersion',S);
S:='';
For ext in Findexer.Extensions do
begin
if S<>'' then
S:=S+';';
S:=S+Ext;
end;
Cfg.SetValue('Indexer/Extensions',S);
Cfg.SetValue('Indexer/Hostname',Findexer.HostName);
Cfg.SetValue('Indexer/Port',Findexer.Port);
S:='';
For I:=0 to SourceTrees.Count-1 do
With SourceTrees[i] do
if System and Enabled then
begin
S:=S+';'+SourceTrees[i].Name;
end;
if (S<>'') then
S:=S+';';
Cfg.SetValue('Indexer/EnabledSystemTrees',S);
S:='';
For I:=0 to SourceTrees.Count-1 do
if not SourceTrees[i].System then
begin
if S<>'' then
S:=S+';';
S:=S+SourceTrees[i].Name;
end;
Cfg.SetValue('Trees/Names',S);
Cfg.DeletePath('Trees/List');
For I:=0 to SourceTrees.Count-1 do
begin
aTree:=SourceTrees[i];
if not aTree.System then
Cfg.WriteObject('Trees/list/'+aTree.Name+'/',SourceTrees[I]);
end;
Cfg.WriteToDisk;
finally
Cfg.Free;
end;
end;
function TIDEInstantSearchManager.GetDefaultConfigFileName : String;
begin
Result:=IncludeTrailingPathDelimiter(LazarusIDE.GetPrimaryConfigPath)+'instantsearch.xml';
end;
procedure TIDEInstantSearchManager.CheckRefresh(aExisting : TStrings; aForceRefresh : Boolean);
Var
i : Integer;
Msg,aName : string;
aTrees : TSourceTreeDefinitionList;
begin
aTrees:=TSourceTreeDefinitionList.Create(Self,TSourceTreeDefinition);
try
for I:=0 to FSourceTrees.Count-1 do
begin
aName:=FSourceTrees[i].Name;
if aForceRefresh or (aExisting.IndexOf(aName)=-1) then
begin
if not aForceRefresh then
begin
Msg:=Format('Source tree %s has no records (searched %d trees), refreshing',[aName,aExisting.Count]);
IDEMessagesWindow.AddCustomMessage(mluHint,lrsInstantSearch+': '+Msg,'',0,0,lrsInstantSearch);
end;
aTrees.AddTree(aName).Assign(FSourceTrees[i]);
end;
end;
except
FreeAndNil(aTrees);
raise;
end;
if aTrees.Count>0 then
IndexTrees(aTrees,True)
else
FreeAndNil(aTrees);
end;
procedure TIDEInstantSearchManager.RefreshSystemTrees(Force : Boolean);
Var
L : TStrings;
begin
L:=TStringList.Create;
try
Indexer.ListTrees(L);
CheckRefresh(L,Force);
finally
L.Free;
end;
end;
procedure TIDEInstantSearchManager.DoRefreshTimer(Sender : TObject);
begin
FStartIndexTimer.Enabled:=False;
FreeAndNil(FStartIndexTimer);
RefreshSystemTrees(False);
end;
procedure TIDEInstantSearchManager.SetRefreshTimer;
begin
FStartIndexTimer:=TTimer.Create(Self);
FStartIndexTimer.Enabled:=False;
FStartIndexTimer.Interval:=DefaultOpenDelay;
FStartIndexTimer.OnTimer:=@DoRefreshTimer;
FStartIndexTimer.Enabled:=True;
end;
procedure TIDEInstantSearchManager.Load;
Var
L : TStrings;
begin
LoadConfig(GetDefaultConfigFileName);
L:=TStringList.Create;
try
if TestConnect(L) then
begin
ServerTrees.Assign(L);
if L.IndexOf(Indexer.IndexName)<>-1 then
SetRefreshTimer;
end;
finally
L.Free;
end;
end;
procedure TIDEInstantSearchManager.Save;
begin
SaveConfig(GetDefaultConfigFileName);
end;
function TIDEInstantSearchManager.AssignProjectTreeID(aProject : TLazProject) : String;
var
CurrentProjectFileName : String;
begin
Result:=TGUID.NewGuid.ToString(True);
CurrentProjectFileName:=aProject.ProjectInfoFile;
aProject.CustomSessionData[SInstantSearchID]:=Result;
aProject.CustomSessionData[SInstantSearchProjFileName]:=CurrentProjectFileName;
aProject.Modified:=True;
end;
procedure TIDEInstantSearchManager.GetProjectFiles(aProject: TLazProject; aList : TStrings);
Var
FN : String;
I : Integer;
begin
for I:=0 to aProject.FileCount-1 do
if aProject.Files[i].IsPartOfProject then
begin
FN:=aProject.Files[i].GetFullFilename;
if Indexer.AllowExtension(ExtractFileExt(FN)) then
aList.Add(FN);
end
end;
procedure TIDEInstantSearchManager.IndexProjectFiles(aProject: TLazProject);
Var
ProjName : string;
ProjTree : String;
ProjTreeFileName : String;
ProjectDir,CurrentProjectFileName : String;
lIndexer : TManticoreSearchSources;
Data : TIndexFilesData;
L : TStrings;
begin
ProjTree:=aProject.CustomSessionData[SInstantSearchID];
ProjTreeFileName:=aProject.CustomSessionData[SInstantSearchProjFileName];
CurrentProjectFileName:=aProject.ProjectInfoFile;
ProjectDir:=ExtractFilePath(CurrentProjectFileName);
if (ProjTree='') or (ProjTreeFileName<>CurrentProjectFileName) then
ProjTree:=AssignProjectTreeID(aProject);
L:=TStringList.Create;
try
GetProjectFiles(aProject,L);
ProjName:=aProject.Title;
if ProjName='' then
ProjName:=ChangeFileExt(ExtractFileName(CurrentProjectFileName),'');
Data:=TIndexFilesData.Create(ProjName,projtree,ProjectDir,L);
L:=nil;
lIndexer:=Indexer.Clone(Self);
FIndexThread:=TInstantSearchIndexFilesThread.Create(lIndexer,Data,@DoOnIndexDone);
finally
L.Free;
end;
end;
procedure TIDEInstantSearchManager.RescanFPCDir;
Var
L : TSourceTreeDefinitionList;
aTree : TSourceTreeDefinition;
begin
L:=TSourceTreeDefinitionList.Create(Self,TSourceTreeDefinition);
aTree:=SourceTrees.FindByName(FCLTree);
if assigned(aTree) then
L.AddTree(aTree.Name).Assign(aTree);
aTree:=SourceTrees.FindByName(RTLTree);
if assigned(aTree) then
L.AddTree(aTree.Name).Assign(aTree);
aTree:=SourceTrees.FindByName(CompilerTree);
if assigned(aTree) then
L.AddTree(aTree.Name).Assign(aTree);
IndexTrees(L,True);
end;
procedure TIDEInstantSearchManager.ClearMarkProjectTimer;
begin
FMarkProject:=nil;
if Assigned(FMarkProjectTimer) then
begin
FMarkProjectTimer.Enabled:=False;
FreeAndNil(FMarkProjectTimer);
end;
end;
procedure TIDEInstantSearchManager.StartMarkProjectTimer(aProject : TLazProject);
begin
FMarkProject:=aProject;
FMarkProjectTimer:=TTimer.Create(Self);
FMarkProjectTimer.Enabled:=False;
FMarkProjectTimer.Interval:=IndexProjectDelay*1000;
FMarkProjectTimer.OnTimer:=@DoMarkProjectIndexed;
FMarkProjectTimer.Enabled:=True;
end;
procedure TIDEInstantSearchManager.MarkProjectIndexed(aProject: TLazProject);
Var
FN : String;
begin
FN:=ChangeFileExt(ExtractFileName(aProject.ProjectInfoFile),'');
IDEMessagesWindow.AddCustomMessage(mluVerbose,lrsInstantSearch+Format(lrsMarkingIndexable,[FN]),aProject.ProjectInfoFile,0,0,lrsInstantSearch);
AssignProjectTreeID(aProject);
IndexProjectFiles(aProject);
end;
function TIDEInstantSearchManager.Search(const aTerm: String) : TMCSearchResultArray;
Var
I,aCount : Integer;
aTree : TSourceTreeDefinition;
aTrees : Array of string;
Procedure AddTree(const aName : String);
begin
aTrees[aCount]:=aName;
inc(aCount);
end;
begin
aTrees:=[];
SetLength(aTrees,Sourcetrees.Count+1);
aCount:=0;
if SearchProject and (ProjectTreeName<>'') then
addTree(ProjectTreeName);
for I:=0 to SourceTrees.Count-1 do
begin
aTree:=SourceTrees[i];
if aTree.Enabled then
addTree(aTree.Name);
end;
SetLength(aTrees,aCount);
Result:=Indexer.Search('*'+aTerm+'*',aTrees);
end;
destructor TIDEInstantSearchManager.Destroy;
begin
if Assigned(FIndexThread) then
begin
FIndexThread.Terminate;
While Assigned(FIndexThread) do
begin
Sleep(10);
// Can't use application.processmessages
CheckSynchronize;
end;
end;
FreeAndNil(FIndexer);
FreeAndNil(FSourceTrees);
FreeAndNil(FServerTrees);
FreeAndNil(FStartIndexTimer);
FreeAndNil(FMarkProjectTimer);
inherited Destroy;
end;
function TIDEInstantSearchManager.CanConnect: Boolean;
begin
Result:=(Indexer.IndexName<>'')
end;
function TIDEInstantSearchManager.TestConnect(aIndexList: TStrings): Boolean;
Var
aList : Tstrings;
begin
Result:=False;
aList:=aIndexList;
if aList=Nil then
aList:=TstringList.Create;
try
try
Indexer.Connect;
Indexer.ListIndexes(aList);
Result:=True;
except
On E : Exception do
DoManticoreLog(Self,mlkInfo,Format(lrsCannotConnectToManticore,[E.ClassName,E.Message]));
end;
finally
if aList<>aIndexList then
FreeAndNil(aList);
end;
end;
function TIDEInstantSearchManager.CanSearch: string;
begin
if Indexer.Transport=mctNone then
Exit(lrsNoTransport);
if (Indexer.Transport=mctMysql) and (Indexer.MySQLVersion=mcvNone) then
Exit(lrsNoMySQLVersion);
if SourceTrees.Count=0 then
Exit(lrsNoSearchTrees);
If (ServerTrees.Count=0) then
Exit(lrsNoServerTrees);
end;
procedure TIDEInstantSearchManager.DoStartIndexing;
begin
if Assigned(FOnIndexStart) then
FOnIndexStart(Self);
end;
function TIDEInstantSearchManager.IndexTrees(aList: TSourceTreeDefinitionList;
aFreeList: Boolean): Boolean;
Var
lTrees : TSourceTreeDefinitionList;
lIndexer : TManticoreSearchSources;
begin
Result:=False;
if IsIndexing then
begin
if AFreeList then
aList.Free;
exit;
end;
lTrees:=Nil;
try
if aFreeList then
lTrees:=aList
else
begin
lTrees:=TSourceTreeDefinitionList.Create(Nil,aList.ItemClass);
lTrees.Assign(aList);
end;
lIndexer:=Indexer.Clone(Self);
FIndexThread:=TInstantSearchIndexTreeThread.Create(lIndexer,lTrees,@DoOnIndexDone);
DoStartIndexing;
Result:=True;
except
On E : Exception do
begin
FreeAndNil(lTrees);
DoManticoreLog(Self,mlkInfo,Format(lrsCannotConnectToManticore,[E.ClassName,E.Message]));
end;
end;
end;
function TIDEInstantSearchManager.IndexTree(aTree: TSourceTreeDefinition
): Boolean;
Var
aList : TSourceTreeDefinitionList;
begin
aList:=TSourceTreeDefinitionList.Create(Self,TSourceTreeDefinition);
aList.AddTree(aTree.Name).Assign(aTree);
Result:=IndexTrees(aList,True);
end;
function TIDEInstantSearchManager.IsIndexing: Boolean;
begin
Result:=Assigned(FIndexThread);
end;
{ TSourceTreeDefinitionList }
function TSourceTreeDefinitionList.GetDef(aIndex : Integer
): TSourceTreeDefinition;
begin
Result:=TSourceTreeDefinition(Items[aIndex]);
end;
procedure TSourceTreeDefinitionList.SetDef(aIndex : Integer;
AValue: TSourceTreeDefinition);
begin
Items[aIndex]:=aValue;
end;
procedure TSourceTreeDefinitionList.Update(Item: TCollectionItem);
begin
if Owner is TIDEInstantSearchManager then
(Owner as TIDEInstantSearchManager).SourceTreesChanged;
end;
function TSourceTreeDefinitionList.AddTree(const aName: String
): TSourceTreeDefinition;
begin
Result:=Add as TSourceTreeDefinition;
Result.Name:=aName;
end;
function TSourceTreeDefinitionList.IndexOfName(const aName: String): Integer;
begin
Result:=Count-1;
While (Result>=0) and Not SameText(GetDef(Result).Name,aName) do
Dec(Result);
end;
function TSourceTreeDefinitionList.FindByName(const aName: String
): TSourceTreeDefinition;
Var
Idx : Integer;
begin
Result:=nil;
Idx:=IndexOfName(aName);
if Idx<>-1 then
Result:=GetDef(Idx);
end;
{ TSourceTreeDefinition }
procedure TSourceTreeDefinition.Assign(aSource: TPersistent);
Var
aSTD : TSourceTreeDefinition absolute aSource;
begin
if aSource is TSourceTreeDefinition then
begin
FBaseDir:=aStd.BaseDir;
FName:=aStd.Name;
FRecurse:=aStd.Recurse;
FExtensions:=aStd.Extensions;
FSystem:=aStd.System;
FAllFiles:=aStd.AllFiles;
FEnabled:=aStd.Enabled;
end
else
inherited Assign(aSource);
end;
end.