mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-16 08:29:47 +02:00
424 lines
13 KiB
ObjectPascal
424 lines
13 KiB
ObjectPascal
(*
|
|
/***************************************************************************
|
|
todolist.pp
|
|
--------------------
|
|
|
|
***************************************************************************/
|
|
|
|
***************************************************************************
|
|
* *
|
|
* This source is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This code is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* General Public License for more details. *
|
|
* *
|
|
* A copy of the GNU General Public License is available on the World *
|
|
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
|
|
* obtain it by writing to the Free Software Foundation, *
|
|
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
* *
|
|
***************************************************************************
|
|
|
|
|
|
Author: Olivier GUILBAUD <golivier@free.fr>
|
|
|
|
Abstract:
|
|
List all to do comments of current project.
|
|
|
|
the todo comments has this syntax
|
|
{#todoxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}
|
|
or
|
|
//#todoxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
|
or
|
|
(.*#todoxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy *.) (delete the .)
|
|
|
|
where xxx is the priority
|
|
yyy..yy is the text of todo
|
|
|
|
you can create an file naming projectname.todo and add list of todo
|
|
*)
|
|
|
|
{#todo goto line of selected unit}
|
|
{#todo options }
|
|
{#todo print an todo report }
|
|
|
|
unit TodoList;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, LResources,
|
|
ExtCtrls, ComCtrls, Menus, Buttons, GraphType,
|
|
StdCtrls, mPasLex, LCLIntf, LCLType,
|
|
CodeCache, CodeToolManager, LazarusIDEStrConsts;
|
|
|
|
Const
|
|
cTodoFlag = '#todo';
|
|
cAltTodoFLag = 'TODO';
|
|
|
|
type
|
|
TOnOpenFile = procedure(Sender: TObject; const Filename: string;
|
|
const LineNumber: integer) of object;
|
|
|
|
{ TfrmTodo }
|
|
|
|
TfrmTodo = class(TForm)
|
|
ImageList1: TImageList;
|
|
lvTodo: TListView;
|
|
StatusBar: TStatusBar;
|
|
ToolBar: TToolBar;
|
|
tbGoto: TToolButton;
|
|
tbOptions: TToolButton;
|
|
tbPrint: TToolButton;
|
|
tbRefresh: TToolButton;
|
|
procedure tbGotoClick(Sender: TObject);
|
|
procedure tbRefreshClick(Sender: TObject);
|
|
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift:TShiftState);
|
|
private
|
|
{ private declarations }
|
|
fBuild : Boolean;
|
|
fFileName : String;
|
|
FOnOpenFile : TOnOpenFile;
|
|
fRootCBuffer : TCodeBuffer;
|
|
fScannedFile : TStringList;
|
|
|
|
procedure SetFileName(const AValue: String);
|
|
|
|
procedure ParseComment(const aFileName: string; const SComment, EComment: string;
|
|
const TokenString: string; LineNumber: Integer);
|
|
procedure ParseDirective(aDirective : String);
|
|
|
|
procedure LoadFile(const aFileName : string);
|
|
public
|
|
{ public declarations }
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
property FileName : String read fFileName write SetFileName;
|
|
property OnOpenFile: TOnOpenFile read FOnOpenFile write FOnOpenFile;
|
|
end;
|
|
|
|
var
|
|
frmTodo: TfrmTodo;
|
|
|
|
implementation
|
|
|
|
uses
|
|
StrUtils,
|
|
FileUtil;
|
|
|
|
{ TfrmTodo }
|
|
|
|
constructor TfrmTodo.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
fBuild:=False;
|
|
fScannedFile:=TStringList.Create;
|
|
|
|
Caption := lisTodoListCaption;
|
|
|
|
tbRefresh.Hint := lisTodolistRefresh;
|
|
tbGoto.Hint := listodoListGotoLine;
|
|
tbPrint.Hint :=listodoListPrintList;
|
|
tbOptions.Hint := lisToDoListOptions;
|
|
|
|
with lvTodo do
|
|
begin
|
|
Column[0].Caption := ' !';
|
|
Column[0].Width := 25;
|
|
Column[1].Caption := lisToDoLDescription;
|
|
Column[1].Width := 250;
|
|
Column[2].Caption := lisToDoLFile;
|
|
Column[2].Width := 150;
|
|
Column[3].Caption := lisToDoLLine;
|
|
Column[3].Width := 50;
|
|
end;
|
|
end;
|
|
|
|
destructor TfrmTodo.Destroy;
|
|
begin
|
|
fScannedFile.Free;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
//Load project main file and scan all files for find the syntax todo
|
|
procedure TfrmTodo.tbRefreshClick(Sender: TObject);
|
|
var
|
|
UsedInterfaceFilenames,
|
|
UsedImplementationFilenames: TStrings;
|
|
i: integer;
|
|
St : String;
|
|
begin
|
|
if fBuild then Exit;
|
|
|
|
Screen.Cursor:=crHourGlass;
|
|
Try
|
|
fBuild:=True;
|
|
lvTodo.Items.Clear;
|
|
fScannedFile.Clear;
|
|
//Find an '.todo' filename
|
|
St:=ChangeFileExt(fFileName,'.todo');
|
|
If FileExists(St) then
|
|
LoadFile(St);
|
|
|
|
//Load project file
|
|
LoadFile(fFileName);
|
|
|
|
if Assigned(CodeToolBoss) then
|
|
begin
|
|
fRootCBuffer:=CodeToolBoss.LoadFile(fFileName,false,false);
|
|
if not Assigned(fRootCBuffer) then Exit;
|
|
|
|
if CodeToolBoss.FindUsedUnitFiles(fRootCBuffer,UsedInterfaceFilenames,
|
|
UsedImplementationFilenames) then
|
|
begin
|
|
try
|
|
for i:=0 to UsedInterfaceFilenames.Count-1 do
|
|
LoadFile(UsedInterfaceFilenames[i]);
|
|
for i:=0 to UsedImplementationFilenames.Count-1 do
|
|
LoadFile(UsedImplementationFilenames[i]);
|
|
finally
|
|
UsedImplementationFilenames.Free;
|
|
UsedInterfaceFilenames.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
Screen.Cursor:=crDefault;
|
|
fBuild:=False;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmTodo.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
begin
|
|
if Key=VK_ESCAPE then
|
|
Close;
|
|
end;
|
|
|
|
//Initialise then todo project and find them
|
|
procedure TfrmTodo.SetFileName(const AValue: String);
|
|
begin
|
|
if fFileName=AValue then exit;
|
|
fFileName:=AValue;
|
|
tbRefreshClick(nil);
|
|
end;
|
|
|
|
procedure TfrmTodo.tbGotoClick(Sender: TObject);
|
|
var
|
|
CurFilename: String;
|
|
TheItem: TListItem;
|
|
TheLine: integer;
|
|
UsedInterfaceFilenames: TStrings;
|
|
UsedImplementationFilenames: TStrings;
|
|
i: integer;
|
|
Found: boolean;
|
|
begin
|
|
CurFilename:='';
|
|
TheItem:= lvtodo.Selected;
|
|
Found:= false;
|
|
if Assigned(TheItem) then
|
|
begin
|
|
CurFileName:= TheItem.SubItems[1];
|
|
TheLine:= StrToInt(TheItem.SubItems[2]);
|
|
if not FileNameIsAbsolute(CurFileName) then
|
|
begin
|
|
if Assigned(CodeToolBoss) then
|
|
begin
|
|
fRootCBuffer:=CodeToolBoss.LoadFile(fFileName,false,false);
|
|
if not Assigned(fRootCBuffer) then Exit;
|
|
if CodeToolBoss.FindUsedUnitFiles(fRootCBuffer,UsedInterfaceFilenames,
|
|
UsedImplementationFilenames) then
|
|
begin
|
|
try
|
|
for i:=0 to UsedInterfaceFilenames.Count-1 do
|
|
begin
|
|
if AnsiCompareStr(ExtractFileName(UsedInterfaceFileNames[i]),
|
|
CurFileName) = 0 then
|
|
begin
|
|
CurFileName:= UsedInterFaceFileNames[i];
|
|
Found:= true;
|
|
break;
|
|
end;
|
|
end;
|
|
if not Found then
|
|
begin
|
|
for i:=0 to UsedImplementationFilenames.Count-1 do
|
|
begin
|
|
if AnsiCompareStr(ExtractFileName
|
|
(UsedImplementationFilenames[i]), CurFileName) = 0 then
|
|
begin
|
|
CurFileName:= UsedImplementationFilenames[i];
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
UsedImplementationFilenames.Free;
|
|
UsedInterfaceFilenames.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
if Assigned(OnOpenFile) then OnOpenFile(Self,CurFilename,TheLine);
|
|
end;
|
|
end;
|
|
|
|
//Find the {$I filename} directive. If exists, call LoadFile()
|
|
procedure TfrmTodo.ParseDirective(aDirective : String);
|
|
Var N : Integer;
|
|
ParsingString : string;
|
|
begin
|
|
N:=Pos('{$I ',UpperCase(aDirective));
|
|
if N<>0 then
|
|
begin
|
|
//we found a token that looks like an include directive. now extract
|
|
//the file name
|
|
ParsingString:=Trim(Copy(aDirective,N+Length('{$I '),MaxInt));
|
|
ParsingString:=Trim(Copy(ParsingString,1,Pos('}',ParsingString)-1));
|
|
{#ToDo: search in include path}
|
|
LoadFile(ParsingString);
|
|
end;
|
|
end;
|
|
|
|
//Find in comment the ToDo message
|
|
procedure TfrmTodo.ParseComment(const aFileName: string; const SComment, EComment: string;
|
|
const TokenString: string; LineNumber: Integer);
|
|
Var
|
|
N,J : Integer;
|
|
ParsingString : string;
|
|
CListItem : TListItem;
|
|
TodoFlag : string;
|
|
function IsTodoFlag(const Flag: string): boolean;
|
|
begin
|
|
TodoFLag := Flag;
|
|
Result := Pos(UpperCase(Flag),UpperCase(TokenString)) > 1;
|
|
end;
|
|
begin
|
|
if IsTodoFlag(cTodoFlag) or IsTodoFlag(cAltTodoFlag) then
|
|
begin
|
|
// We found a token that looks like a TODO comment. Now
|
|
// verify that it *is* one: either a white-space or the
|
|
// comment token need to be right in front of the TODO item
|
|
|
|
// Remove comment characters
|
|
ParsingString := TokenString;
|
|
Delete(ParsingString, 1, Length(SComment));
|
|
|
|
// Remove white-space left and right
|
|
ParsingString := Trim(ParsingString);
|
|
|
|
// The TODO token should be at the beginning of the comment
|
|
N:=Pos(UpperCase(TodoFlag),UpperCase(ParsingString));
|
|
if N=1 then
|
|
begin
|
|
// Remove token from string
|
|
Delete(ParsingString, 1, Length(TodoFlag));
|
|
ParsingString := TrimRight(ParsingString);
|
|
|
|
if EComment<>'' then
|
|
begin
|
|
N:=Pos(EComment,ParsingString);
|
|
// Remove end comment from string
|
|
Delete(ParsingString, N, Length(EComment));
|
|
ParsingString := TrimRight(ParsingString);
|
|
end;
|
|
|
|
CListItem := lvToDo.Items.Add;
|
|
|
|
// Identify numeric priority (if there is one)
|
|
j := 0;
|
|
while j < Length(ParsingString) do
|
|
begin
|
|
if not (ParsingString[j + 1] in ['0'..'9']) then
|
|
Break;
|
|
Inc(j);
|
|
end;
|
|
N:=StrToIntDef(Copy(ParsingString, 1, j), 0);
|
|
ClistItem.Caption:=IntToStr(N);
|
|
Delete(ParsingString, 1, j);
|
|
ParsingString := TrimLeft(ParsingString);
|
|
if (j=0) and AnsiStartsText(':', ParsingString) then begin
|
|
{ Remove Leading : from comment }
|
|
Delete(ParsingString, 1, 1);
|
|
ParsingString := TrimLeft(ParsingString);
|
|
end;
|
|
|
|
ClistItem.SubItems.Add(ParsingString);
|
|
ClistItem.SubItems.Add(ExtractFileName(aFileName));
|
|
ClistItem.SubItems.Add(IntToStr(LineNumber));
|
|
// CListItem.ImageIndex := Ord(Info.Priority);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//Load an FileName and find {#todox yyyyyy} where
|
|
// x is the priority (0 by default)
|
|
// yyyy it's the message one line only
|
|
procedure TfrmTodo.LoadFile(const aFileName: string);
|
|
var
|
|
Parser : TmwPasLex;
|
|
EStream : TMemoryStream;
|
|
ST : String;
|
|
begin
|
|
St:=ExtractFileName(aFileName);
|
|
//Quit this method if we have already scan this file
|
|
if fScannedFile.IndexOf(St)<>-1 then Exit;
|
|
StatusBar.SimpleText :=aFileName;// St;
|
|
StatusBar.Repaint;
|
|
fScannedFile.Add(St);
|
|
|
|
EStream := TMemoryStream.Create;
|
|
try
|
|
//Echange of stream
|
|
Try
|
|
EStream.LoadFromFile(aFileName);
|
|
EStream.Position := EStream.Size;
|
|
EStream.WriteByte(0); // Terminate string for TmwPasLex
|
|
Except
|
|
FreeAndNil(EStream);
|
|
End;
|
|
if not Assigned(EStream) then Exit; // Silently ignore failed reads
|
|
|
|
EStream.Position := 0;
|
|
|
|
Parser := TmwPasLex.Create;
|
|
try
|
|
Parser.Origin := EStream.Memory;
|
|
while Parser.TokenID <> tkNull do
|
|
begin
|
|
{ TODO 3 -oStefan -cIssue: This needs to be fixed for multiline comments;
|
|
the strategy ought to be to read the complete comment and only then
|
|
start parsing. Also it would be better to move deleting of the comment
|
|
tokens out of the parser }
|
|
case Parser.TokenID of
|
|
tkBorComment: ParseComment(aFileName, '{', '}', Parser.Token, Parser.LineNumber + 1);
|
|
tkAnsiComment: ParseComment(aFileName, '(*', '*)', Parser.Token, Parser.LineNumber + 1);
|
|
// Slash comments in CPP files should work if they are not in a {}
|
|
tkSlashesComment: ParseComment(aFileName, '//', '', Parser.Token, Parser.LineNumber + 1);
|
|
tkCompDirect : ParseDirective(Parser.Token);
|
|
end;
|
|
Parser.Next;
|
|
end;
|
|
finally
|
|
Parser.Free;
|
|
end;
|
|
finally
|
|
EStream.Free;
|
|
Self.Update;
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
{$i todolist.lrs}
|
|
|
|
end.
|
|
|