lazarus/ide/wordcompletion.pp
ondrej 2a34ee3ad9 IDE: identifier completion:
- handle "Include words" option
- don't scan a unit several times if it is open in several source editor windows

git-svn-id: trunk@58308 -
2018-06-18 13:16:58 +00:00

370 lines
11 KiB
ObjectPascal

{
Author: Mattias Gaertner
*****************************************************************************
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
Abstract:
A wordcompletion stores words and can createse a list of words gathered
from the recently added words and provided source texts.
}
unit WordCompletion;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, SynEdit;
type
TWordCompletionGetSource =
procedure(var Source:TStrings; var TopLine, BottomLine: Integer;
var IgnoreWordPos: TPoint; SourceIndex:integer) of object;
TWordCompletion = class
private
FWordBuffer:TStringList;// the recent used words list. the newest are at the end
FWordBufferCapacity:integer;
FOnGetSource:TWordCompletionGetSource;
function GetWordBufferCapacity:integer;
procedure SetWordBufferCapacity(NewCapacity: integer);
function CaseInsensitiveIndexOf(const AWord: string):integer;
function CaseSensitiveIndexOf(const AWord: string):integer;
protected
procedure DoGetSource(var Source:TStrings; var TopLine, BottomLine: Integer;
var IgnoreWordPos: TPoint; SourceIndex:integer); virtual;
public
constructor Create;
destructor Destroy; override;
procedure AddWord(const AWord:string);
property WordBufferCapacity:integer
read GetWordBufferCapacity write SetWordBufferCapacity;
procedure GetWordList(AWordList:TStrings; const Filter: String;
ContainsFilter, CaseSensitive:boolean; MaxResults:integer);
procedure CompletePrefix(const Prefix: string; var CompletedPrefix: string;
CaseSensitive:boolean);
public
property OnGetSource:TWordCompletionGetSource
read FOnGetSource write FOnGetSource;
end;
implementation
type
TCharType = (ctNone,ctWordBegin,ctWord);
var
CharTable: array[char] of TCharType;
procedure InitCharTable;
var c:char;
begin
for c:=low(char) to high(char) do
case c of
'a'..'z','A'..'Z','_':CharTable[c]:=ctWordBegin;
'0'..'9':CharTable[c]:=ctWord;
else CharTable[c]:=ctNone;
end;
end;
{ TWordCompletion }
// fast pos functions used in GetWordList
function MyPos(const SubStr, S: string; Offset, LastPos: SizeInt): SizeInt;
var
i,MaxLen, SubLen : SizeInt;
SubFirst: Char;
pc: pchar;
begin
Result:=0;
SubLen := Length(SubStr);
if (SubLen > 0) and (Offset > 0) and (Offset <= LastPos) then
begin
MaxLen := LastPos- SubLen;
SubFirst := SubStr[1];
i := IndexByte(S[Offset],LastPos - Offset + 1, Byte(SubFirst));
while (i >= 0) and ((i + sizeint(Offset) - 1) <= MaxLen) do
begin
pc := @S[i+SizeInt(Offset)];
//we know now that pc^ = SubFirst, because indexbyte returned a value > -1
if (CompareByte(Substr[1],pc^,SubLen) = 0) then
Exit(i + SizeInt(Offset));
//point Offset to next char in S
Offset := sizeint(i) + Offset + 1;
i := IndexByte(S[Offset],LastPos - Offset + 1, Byte(SubFirst));
end;
end;
end;
procedure TWordCompletion.GetWordList(AWordList: TStrings;
const Filter: String; ContainsFilter, CaseSensitive: boolean;
MaxResults: integer);
var i, Line, x, FilterLen, MaxHash, LineLen: integer;
UpFilter, LineText, UpLineText, UpWordBuffer: string;
SourceText: TStrings;
HashList: ^integer;// index list. Every entry points to a word in the AWordList
SourceTextIndex, SourceTopLine, SourceBottomLine:integer;
LastCharType:TCharType;
IgnoreWordPos: TPoint;
procedure Add(const AWord:string);
// if AWord is not already in list then add it to AWordList
var a,Hash,HashTry:integer;
ALowWord:string;
begin
ALowWord:=lowercase(AWord);
Hash:=0;
a:=1;
while (a<=length(ALowWord)) and (a<20) do begin
inc(Hash,ord(ALowWord[a]) and $7f);
inc(a);
end;
Hash:=(Hash*137) mod MaxHash;
HashTry:=0;
while (HashTry<MaxHash) do begin
a:=HashList[(Hash+HashTry) mod MaxHash];
if a>=0 then begin
if (AWordList[a]=AWord) then
// word already in list -> do not add
exit;
end else begin
// word not in list -> add
HashList[(Hash+HashTry) mod MaxHash]:=AWordList.Add(AWord);
exit;
end;
inc(HashTry);
end;
end;
procedure AddIfMatch(const ALine, ALineUp:string; const AFirstPos, ALength: Integer);
var
AAdd: Boolean;
begin
if FilterLen=0 then
AAdd := True
else
begin
AAdd := False;
if CaseSensitive then begin
if ContainsFilter then
AAdd := MyPos(Filter, ALine, AFirstPos, AFirstPos+ALength-1)>0
else
AAdd := strlcomp(PChar(@ALine[AFirstPos]),PChar(Filter),FilterLen)=0;
end else
begin
if ContainsFilter then
AAdd := MyPos(UpFilter, ALineUp, AFirstPos, AFirstPos+ALength-1)>0
else
AAdd := strlcomp(PChar(@ALineUp[AFirstPos]),PChar(UpFilter),FilterLen)=0;
end;
end;
if AAdd then
Add(Copy(ALine, AFirstPos, ALength));
end;
// TWordCompletion.GetWordList
begin
AWordList.Clear;
if MaxResults<1 then MaxResults:=1;
MaxHash:=MaxResults*3;
GetMem(HashList,MaxHash*SizeOf(Integer));
try
for i:=0 to MaxHash-1 do HashList[i]:=-1;
FilterLen:=length(Filter);
AWordList.Capacity:=MaxResults;
UpFilter:=uppercase(Filter);
// first add all recently used words
i:=FWordBuffer.Count-1;
UpWordBuffer:='';
while (i>=0) and (AWordList.Count<MaxResults) do begin
if not CaseSensitive then
UpWordBuffer := UpperCase(FWordBuffer[i]);
AddIfMatch(FWordBuffer[i], UpWordBuffer, 1, Length(FWordBuffer[i]));
dec(i);
end;
if AWordList.Count>=MaxResults then exit;
// then search in all sources for more words that could fit
SourceTextIndex:=0;
SourceText:=nil;
SourceTopLine:=0;
SourceBottomLine:=-1;
IgnoreWordPos:=Point(-1,-1);
DoGetSource(SourceText,SourceTopLine,SourceBottomLine,IgnoreWordPos,SourceTextIndex);
UpLineText:='';
repeat
if SourceText<>nil then begin
Line:=SourceTopLine;
if SourceBottomLine<0 then
SourceBottomLine := SourceText.Count-1;
while (Line<=SourceBottomLine) do begin
LineText:=SourceText[line];
LineLen:=length(LineText);
if not CaseSensitive then
UpLineText:=uppercase(LineText);
x:=1;
LastCharType:=ctNone;
while (x<=LineLen) do begin
if (LastCharType=ctNone) and (CharTable[LineText[x]]=ctWordBegin)
then begin
// word found
i:=x;
repeat
inc(i);
until (i>LineLen) or (CharTable[LineText[i]]=ctNone);
if (i-x>=FilterLen) and not ((Line=IgnoreWordPos.Y) and (x<=IgnoreWordPos.X) and (IgnoreWordPos.X<=i)) then begin
AddIfMatch(LineText,UpLineText,x,i-x);
if AWordList.Count>=MaxResults then exit;
end;
x:=i;
end else
inc(x);
LastCharType:=CharTable[LineText[x-1]];
end;
inc(line);
end;
end;
inc(SourceTextIndex);
SourceText:=nil;
SourceTopLine:=0;
SourceBottomLine:=-1;
IgnoreWordPos:=Point(-1,-1);
DoGetSource(SourceText,SourceTopLine,SourceBottomLine,IgnoreWordPos,SourceTextIndex);
until SourceText=nil;
finally
FreeMem(HashList);
end;
end;
procedure TWordCompletion.CompletePrefix(const Prefix: string;
var CompletedPrefix: string; CaseSensitive:boolean);
var
WordList: TStringList;
s: string;
SamePos: Integer;
MaxPos: Integer;
i: Integer;
begin
CompletedPrefix:=Prefix;
WordList:=TStringList.Create;
try
// fetch all words with Prefix
GetWordList(WordList,Prefix,False,CaseSensitive,10000);
if WordList.Count=0 then exit;
// find the biggest prefix of all available words
CompletedPrefix:=WordList[0];
for i:=1 to WordList.Count-1 do begin
// stop, when it can't get shorter
if CompletedPrefix=Prefix then exit;
s:=WordList[i];
if length(s)<length(Prefix) then continue;
// count same
SamePos:=0;
MaxPos:=length(s);
if MaxPos>length(CompletedPrefix) then MaxPos:=length(CompletedPrefix);
while (SamePos<MaxPos) do begin
if CaseSensitive then begin
if s[SamePos+1]<>CompletedPrefix[SamePos+1] then
break;
end else begin
if upcase(s[SamePos+1])<>upcase(CompletedPrefix[SamePos+1]) then
break;
end;
inc(SamePos);
end;
if SamePos<length(Prefix) then continue;
if SamePos<length(CompletedPrefix) then
CompletedPrefix:=copy(CompletedPrefix,1,SamePos);
end;
finally
WordList.Free;
end;
end;
constructor TWordCompletion.Create;
begin
inherited Create;
FWordBuffer:=TStringList.Create;
FWordBufferCapacity:=100;
end;
destructor TWordCompletion.Destroy;
begin
FWordBuffer.Free;
inherited Destroy;
end;
procedure TWordCompletion.DoGetSource(var Source: TStrings; var TopLine,
BottomLine: Integer; var IgnoreWordPos: TPoint; SourceIndex: integer);
begin
if Assigned(FOnGetSource) then
FOnGetSource(Source,TopLine,BottomLine,IgnoreWordPos,SourceIndex);
end;
function TWordCompletion.GetWordBufferCapacity:integer;
begin
Result:=FWordBufferCapacity;
end;
procedure TWordCompletion.SetWordBufferCapacity(NewCapacity: integer);
var TempWordBuffer:TStringList;
i:integer;
begin
if NewCapacity<5 then NewCapacity:=5;
if NewCapacity<>FWordBufferCapacity then begin
FWordBufferCapacity:=NewCapacity;
if FWordBuffer.Count>NewCapacity then begin
TempWordBuffer:=TStringList.Create;
TempWordBuffer.Capacity:=NewCapacity;
i:=FWordBuffer.Count-NewCapacity;
while i<FWordBuffer.Count do begin
TempWordBuffer.Add(FWordBuffer[i]);
inc(i);
end;
FWordBuffer.Free;
FWordBuffer:=TempWordBuffer;
end;
end;
end;
procedure TWordCompletion.AddWord(const AWord:string);
var OldIndex:integer;
begin
OldIndex:=CaseSensitiveIndexOf(AWord);
if OldIndex>=0 then begin
// move word to the top
FWordBuffer.Move(OldIndex,FWordBuffer.Count-1);
end else begin
// add new word
if FWordBuffer.Count=FWordBufferCapacity then
FWordBuffer.Delete(0);
FWordBuffer.Add(AWord);
end;
end;
function TWordCompletion.CaseInsensitiveIndexOf(const AWord:string):integer;
begin
Result:=FWordBuffer.Count-1;
while (Result>=0) and (CompareText(FWordBuffer[Result],AWord)<>0) do
dec(Result);
end;
function TWordCompletion.CaseSensitiveIndexOf(const AWord: string): integer;
begin
Result:=FWordBuffer.Count-1;
while (Result>=0) and (FWordBuffer[Result]<>AWord) do
dec(Result);
end;
initialization
InitCharTable;
end.