unit framefunctionlist; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, LResources, MufasaBase,Forms, ComCtrls, StdCtrls, Controls, ExtCtrls, Buttons,mmisc,v_ideCodeInsight; type { TFillThread } TFillThread = class(TThread) public Analyzer : TCodeInsight; MS : TMemoryStream; FunctionList : ^TTreeView; IncludesNode,ScriptNode : TTreeNode; procedure execute; override; end; { TFunctionListFrame } TFunctionListFrame = class(TFrame) editSearchList: TEdit; FunctionList: TTreeView; FunctionListLabel: TLabel; CloseButton: TSpeedButton; procedure editSearchListChange(Sender: TObject); procedure FillThreadTerminate(Sender: TObject); procedure FrameEndDock(Sender, Target: TObject; X, Y: Integer); procedure FunctionListDblClick(Sender: TObject); procedure FunctionListDeletion(Sender: TObject; Node: TTreeNode); procedure FunctionListLabelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure FunctionListMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure DockFormOnClose(Sender: TObject; var CloseAction: TCloseAction); procedure CloseButtonClick(Sender: TObject); procedure FunctionListMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private FFilterTree : TTreeView; FLastScript : string; Filtering : boolean; FillThread : TFillThread; procedure FilterTreeVis(Vis : boolean); function GetFilterTree: TTreeView; { private declarations } public DraggingNode : TTreeNode; ScriptNode : TTreeNode; IncludesNode : TTreeNode; InCodeCompletion : boolean; CompletionCaret : TPoint; StartWordCompletion : TPoint; CompletionLine : string; CompletionStart : string; property FilterTree : TTreeView read GetFilterTree; procedure LoadScriptTree( Script : String); function Find(Next : boolean; backwards : boolean = false) : boolean; { public declarations } end; TMethodInfo = packed record MethodStr,Filename : PChar; BeginPos,endpos : integer; end; PMethodInfo = ^TMethodInfo; implementation uses SimbaUnit, Graphics, stringutil, simpleanalyzer,v_ideCodeParser,lclintf; { TFunctionListFrame } procedure TFunctionListFrame.editSearchListChange(Sender: TObject); begin Find(false); end; procedure TFunctionListFrame.FillThreadTerminate(Sender: TObject); begin FillThread.Analyzer.Free; ScriptNode.Expand(true); FunctionList.EndUpdate; if Filtering then begin FilterTreeVis(True); Find(false,false); end; FillThread := nil; end; procedure TFunctionListFrame.FrameEndDock(Sender, Target: TObject; X, Y: Integer); begin if (Target is TPanel) then begin SimbaForm.SplitterFunctionList.Visible := true; CloseButton.Visible:= true; end else if (Target is TCustomDockForm) then begin TCustomDockForm(Target).Caption := 'Functionlist'; TCustomDockForm(Target).OnClose := @DockFormOnClose; SimbaForm.SplitterFunctionList.Visible:= false; CloseButton.Visible:= false; end; end; procedure TFunctionListFrame.FunctionListDblClick(Sender: TObject); var Node : TTreeNode; MethodInfo : TMethodInfo; begin if FilterTree.Visible then Node := FilterTree.Selected else node := FunctionList.Selected; if (node<> nil) and (node.Level > 0) and (node.Data <> nil) then if InCodeCompletion then begin SimbaForm.CurrScript.SynEdit.InsertTextAtCaret( GetMethodName(PMethodInfo(node.Data)^.MethodStr,true)); SimbaForm.RefreshTab; end else begin MethodInfo := PMethodInfo(node.Data)^; if (DraggingNode = node) and (MethodInfo.BeginPos > 0) then if (MethodInfo.Filename = nil) or ((MethodInfo.Filename = '') xor FileExistsUTF8(MethodInfo.Filename)) then begin if (MethodInfo.Filename <> nil) and (MethodInfo.Filename <> '') then SimbaForm.LoadScriptFile(MethodInfo.Filename,true,true); SimbaForm.CurrScript.SynEdit.SelStart := MethodInfo.BeginPos + 1; SimbaForm.CurrScript.SynEdit.SelEnd := MethodInfo.EndPos + 1; end; end; end; procedure TFunctionListFrame.FunctionListDeletion(Sender: TObject; Node: TTreeNode); var MethodInfo : PMethodInfo; begin if node.data <> nil then begin MethodInfo := PMethodInfo(Node.data); if MethodInfo^.MethodStr <> nil then StrDispose(MethodInfo^.MethodStr); if MethodInfo^.FileName <> nil then StrDispose(MethodInfo^.filename); Freemem(node.data,sizeof(TMethodInfo)); end; end; procedure TFunctionListFrame.FunctionListLabelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin Self.DragKind := dkDock; Self.BeginDrag(false, 40); end; procedure TFunctionListFrame.DockFormOnClose(Sender: TObject; var CloseAction: TCloseAction); begin CloseAction := caHide; SimbaForm.MenuItemFunctionList.Checked := False; end; procedure TFunctionListFrame.CloseButtonClick(Sender: TObject); begin self.Hide; SimbaForm.MenuItemFunctionList.Checked := False; end; procedure TFunctionListFrame.FunctionListMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var N: TTreeNode; MethodInfo : TMethodInfo; begin if InCodeCompletion then begin; mDebugLn('Not yet implemented'); exit; end; if not (Sender is TTreeView) then exit; N := TTreeView(Sender).GetNodeAt(x, y); if(N = nil)then exit; end; procedure TFunctionListFrame.FilterTreeVis(Vis: boolean); begin FunctionList.Visible:= not Vis; FilterTree.Visible := Vis; end; function TFunctionListFrame.GetFilterTree: TTreeView; begin Result := FFilterTree; if Assigned(Result) then exit; FFilterTree := TTreeView.Create(Self); FFilterTree.Parent := Self; FFilterTree.Visible := false; FFilterTree.SetBounds(FunctionList.Left,FunctionList.Top,FunctionList.Width,FunctionList.Height); FFilterTree.Align := alClient; FFilterTree.ReadOnly:= True; FFilterTree.ScrollBars:= ssAutoBoth; FFilterTree.OnMouseDown:= FunctionList.OnMouseDown; FFilterTree.OnMouseUp:= FunctionList.OnMouseUp; FFilterTree.OnChange:= FunctionList.OnChange; FFilterTree.OnExit := FunctionList.OnExit; FFilterTree.OnDblClick:= FunctionList.OnDblClick; Result := FFilterTree; //We do not want to delete the data from the FilterTree // FilterTree.OnDeletion:= FunctionList.OnDeletion; end; procedure TFunctionListFrame.LoadScriptTree(Script: String); begin if script = '' then exit; if ScriptNode = nil then exit; if FillThread <> nil then {Already busy filling!} exit; if FLastScript = Script then exit; if SimbaForm.CurrScript = nil then exit; FLastScript:= Script; Filtering := FilterTree.Visible; if FilterTree.Visible then FilterTreeVis(false); FunctionList.BeginUpdate; ScriptNode.DeleteChildren; FillThread := TFillThread.Create(true); FillThread.FunctionList := @Self.FunctionList; FillThread.Analyzer := TCodeInsight.Create; with FillThread,FillThread.Analyzer do begin OnFindInclude := @SimbaForm.OnCCFindInclude; OnLoadLibrary := @SimbaForm.OnCCLoadLibrary; FileName := SimbaForm.CurrScript.ScriptFile; MS := TMemoryStream.Create; MS.Write(Script[1],length(script)); OnTerminate:=@FillThreadTerminate; FreeOnTerminate := True; FillThread.ScriptNode := self.ScriptNode; FillThread.IncludesNode := self.IncludesNode; end; FillThread.resume; //See FillThreadTerminate for the rest of this procedure end; function TFunctionListFrame.Find(Next : boolean; backwards : boolean = false) : boolean; var Start,Len,i,ii,index,posi,c: Integer; FoundFunction : boolean; LastSection : Array[1..2] of String; str : string; RootNode : TTreeNode; NormalNode,tmpNode : TTreeNode; Node : TTreeNode; InsertStr : string; begin if(editSearchList.Text = '')then begin editSearchList.Color := clWhite; FunctionList.FullCollapse; if InCodeCompletion then begin; SimbaForm.CurrScript.SynEdit.Lines[CompletionCaret.y - 1] := CompletionStart; SimbaForm.CurrScript.SynEdit.LogicalCaretXY:= point(CompletionCaret.x,CompletionCaret.y); SimbaForm.CurrScript.SynEdit.SelEnd:= SimbaForm.CurrScript.SynEdit.SelStart; end; FilterTreeVis(False); ScriptNode.Expand(true); exit; end; //We only have to search the next item in our filter tree.. Fu-king easy! if next then begin; if FilterTree.Visible = false then begin; mDebugLn('ERROR: You cannot search next, since the Tree isnt generated yet'); Find(false); exit; end; if FilterTree.Selected <> nil then begin; if backwards then start := FilterTree.Selected.AbsoluteIndex - 1 else Start := FilterTree.Selected.AbsoluteIndex + 1; end else begin if backwards then Start := FilterTree.Items.Count - 1 else Start := 0; end; Len := FilterTree.Items.Count; i := start + len; //This is for the backwards compatibily, we do mod anways.. it just makes sure -1 isn't negative. c := 0; while c < (len ) do begin; if (FilterTree.Items[i mod len].HasChildren = false) then begin FilterTree.Items[i mod len].Selected:= true; InsertStr := FilterTree.Items[i mod len].Text; Result := true; break; end; if backwards then dec(i) else inc(i); inc(c); end; end else begin FilterTree.BeginUpdate; FilterTree.Items.Clear; FoundFunction := False; if FunctionList.Selected <> nil then Start := FunctionList.Selected.AbsoluteIndex else Start := 0; Len := FunctionList.Items.Count; LastSection[1] := ''; LastSection[2] := ''; for i := start to start + FunctionList.Items.Count - 1 do begin; Node := FunctionList.Items[i mod FunctionList.Items.Count]; if(Node.Level >= 1) and (node.HasChildren = false) then if(pos(lowercase(editSearchList.Text), lowercase(Node.Text)) > 0)then begin if not FoundFunction then begin FoundFunction := True; index := i mod FunctionList.Items.Count; InsertStr:= node.Text; end; if node.level = 2 then begin; if node.Parent.text <> lastsection[2] then begin if node.parent.parent.text <> lastsection[1] then begin; rootnode := FilterTree.Items.AddChild(nil,node.parent.parent.text); lastsection[1] := rootnode.text; rootnode := FilterTree.Items.AddChild(Rootnode,node.parent.text); lastsection[2] := rootnode.text; end else begin rootnode := FilterTree.Items.AddChild(rootnode.parent,node.parent.text); lastsection[2] := rootnode.text; end; end; end else begin if node.parent.text <> lastsection[1] then begin rootnode := FilterTree.Items.AddChild(nil,node.parent.text); lastsection[1] := Rootnode.text; end; end; FilterTree.Items.AddChild(RootNode,Node.Text).Data := Node.Data; // break; end; end; Result := FoundFunction; if Result then begin; FilterTreeVis(True); FilterTree.FullExpand; c := 0; while FilterTree.Items[c].HasChildren do inc(c); FilterTree.Items[c].Selected:= True; mDebugLn(FunctionList.Items[Index].Text); FunctionList.FullCollapse; FunctionList.Items[Index].Selected := true; FunctionList.Items[index].ExpandParents; editSearchList.Color := clWhite; end else begin FilterTreeVis(false); editSearchList.Color := 6711039; if InCodeCompletion then SimbaForm.CurrScript.SynEdit.Lines[CompletionCaret.y - 1] := CompletionStart; end; FilterTree.EndUpdate; end; if result and InCodeCompletion then begin; str := format(CompletionLine, [InsertStr]); with SimbaForm.CurrScript.SynEdit do begin; Lines[CompletionCaret.y - 1] := str; LogicalCaretXY:= StartWordCompletion; i := SelStart; posi := pos(lowercase(editSearchList.text), lowercase(InsertStr)) + length(editSearchList.text) - 1; //underline the rest of the word if Posi = Length(InsertStr) then //Special occasions begin; if Length(editSearchList.Text) <> Posi then //We found the last part of the text -> for exmaple when you Search for bitmap, you can find LoadBitmap -> We underline 'Load' begin; SelStart := i; SelEnd := i + pos(lowercase(editSearchList.text), lowercase(InsertStr)) -1; Exit; end; //We searched for the whole text -> for example LoadBitmap, and we found LoadBitmap -> Underline the whole text Posi := 0; end; //Underline the rest of the word SelStart := i + posi; SelEnd := SelStart + Length(InsertStr) - posi; end; end; end; procedure TFunctionListFrame.FunctionListMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var N: TTreeNode; begin if button = mbRight then exit; if InCodeCompletion then begin; mDebugLn('Not yet implemented'); exit; end; if not (Sender is TTreeView) then exit; N := TTreeView(Sender).GetNodeAt(x, y); if(N = nil)then begin Self.DragKind := dkDock; Self.BeginDrag(false, 40); exit; end; Self.DragKind := dkDrag; if(Button = mbLeft) and (N.Level > 0)then Self.BeginDrag(False, 10); DraggingNode := N; end; { TFillThread } procedure TFillThread.execute; procedure AddProcsTree(Node : TTreeNode; Procs : TDeclarationList; Path : string); var i : integer; tmpNode : TTreeNode; begin; if procs = nil then exit; for i := 0 to Procs.Count - 1 do if (Procs[i] <> nil) and (Procs[i] is TciProcedureDeclaration) then with Procs[i] as TciProcedureDeclaration do if name <> nil then begin tmpNode := FunctionList^.Items.AddChild(Node,name.ShortText); tmpNode.Data := GetMem(SizeOf(TMethodInfo)); FillChar(PMethodInfo(tmpNode.Data)^,SizeOf(TMethodInfo),0); with PMethodInfo(tmpNode.Data)^ do begin MethodStr := strnew(Pchar(CleanDeclaration)); Filename:= strnew(pchar(path)); BeginPos:= name.StartPos ; EndPos := name.StartPos + Length(TrimRight(name.RawText)); end; end; end; procedure AddIncludes(ParentNode : TTreeNode; Include : TCodeInsight); var i : integer; begin; parentNode := FunctionList^.Items.AddChild( IncludesNode,ExtractFileNameOnly( Include.FileName)); AddProcsTree(parentNode,Include.Items,Include.FileName); for i := 0 to high(Include.Includes) do AddIncludes(ParentNode,Include.Includes[i]) end; var i : integer; begin Analyzer.Run(MS,nil,-1,true); AddProcsTree(ScriptNode,Analyzer.Items,Analyzer.FileName); //Add the procedures of the script to the script tree //Lame condition.. We must check if nothing new has been included since //last generation of the tree.. However, this will do fine for now ;) if IncludesNode.Count <> length(Analyzer.Includes) then begin; IncludesNode.DeleteChildren; for i := 0 to high(Analyzer.Includes) do AddIncludes(IncludesNode, Analyzer.Includes[i]); end; end; initialization {$R *.lfm} end.