mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-08-11 22:56:06 +02:00
MG: added guess unclosed block
git-svn-id: trunk@587 -
This commit is contained in:
parent
1f09b8a479
commit
a91d8446f3
@ -144,6 +144,10 @@ type
|
|||||||
function FindBlockCounterPart(Code: TCodeBuffer; X,Y: integer;
|
function FindBlockCounterPart(Code: TCodeBuffer; X,Y: integer;
|
||||||
var NewCode: TCodeBuffer;
|
var NewCode: TCodeBuffer;
|
||||||
var NewX, NewY, NewTopLine: integer): boolean;
|
var NewX, NewY, NewTopLine: integer): boolean;
|
||||||
|
function GuessUnclosedBlock(Code: TCodeBuffer; X,Y: integer;
|
||||||
|
var NewCode: TCodeBuffer;
|
||||||
|
var NewX, NewY, NewTopLine: integer): boolean;
|
||||||
|
|
||||||
|
|
||||||
// find declaration
|
// find declaration
|
||||||
function FindDeclaration(Code: TCodeBuffer; X,Y: integer;
|
function FindDeclaration(Code: TCodeBuffer; X,Y: integer;
|
||||||
@ -593,6 +597,38 @@ writeln('TCodeToolManager.FindBlockCounterPart END ');
|
|||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TCodeToolManager.GuessUnclosedBlock(Code: TCodeBuffer; X, Y: integer;
|
||||||
|
var NewCode: TCodeBuffer; var NewX, NewY, NewTopLine: integer): boolean;
|
||||||
|
var
|
||||||
|
CursorPos: TCodeXYPosition;
|
||||||
|
NewPos: TCodeXYPosition;
|
||||||
|
begin
|
||||||
|
Result:=false;
|
||||||
|
{$IFDEF CTDEBUG}
|
||||||
|
writeln('TCodeToolManager.GuessUnclosedBlock A ',Code.Filename,' x=',x,' y=',y);
|
||||||
|
{$ENDIF}
|
||||||
|
if not InitCodeTool(Code) then exit;
|
||||||
|
CursorPos.X:=X;
|
||||||
|
CursorPos.Y:=Y;
|
||||||
|
CursorPos.Code:=Code;
|
||||||
|
{$IFDEF CTDEBUG}
|
||||||
|
writeln('TCodeToolManager.GuessUnclosedBlock B ',FCodeTool.Scanner<>nil);
|
||||||
|
{$ENDIF}
|
||||||
|
try
|
||||||
|
Result:=FCodeTool.GuessUnclosedBlock(CursorPos,NewPos,NewTopLine);
|
||||||
|
if Result then begin
|
||||||
|
NewX:=NewPos.X;
|
||||||
|
NewY:=NewPos.Y;
|
||||||
|
NewCode:=NewPos.Code;
|
||||||
|
end;
|
||||||
|
except
|
||||||
|
on e: Exception do Result:=HandleException(e);
|
||||||
|
end;
|
||||||
|
{$IFDEF CTDEBUG}
|
||||||
|
writeln('TCodeToolManager.GuessUnclosedBlock END ');
|
||||||
|
{$ENDIF}
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TCodeToolManager.GetCompatibleMethods(Code: TCodeBuffer;
|
procedure TCodeToolManager.GetCompatibleMethods(Code: TCodeBuffer;
|
||||||
const AClassName: string; TypeData: PTypeData; Proc: TGetStringProc);
|
const AClassName: string; TypeData: PTypeData; Proc: TGetStringProc);
|
||||||
begin
|
begin
|
||||||
|
@ -95,6 +95,7 @@ type
|
|||||||
procedure MoveCursorToCleanPos(ACleanPos: integer); virtual;
|
procedure MoveCursorToCleanPos(ACleanPos: integer); virtual;
|
||||||
function ReadTilSection(SectionType: TCodeTreeNodeDesc): boolean;
|
function ReadTilSection(SectionType: TCodeTreeNodeDesc): boolean;
|
||||||
function ReadTilBracketClose(ExceptionOnNotFound: boolean): boolean;
|
function ReadTilBracketClose(ExceptionOnNotFound: boolean): boolean;
|
||||||
|
function ReadBackTilBracketClose(ExceptionOnNotFound: boolean): boolean;
|
||||||
function DoAtom: boolean; virtual;
|
function DoAtom: boolean; virtual;
|
||||||
procedure ReadNextAtom; virtual;
|
procedure ReadNextAtom; virtual;
|
||||||
procedure UndoReadNextAtom; virtual;
|
procedure UndoReadNextAtom; virtual;
|
||||||
@ -916,6 +917,7 @@ end;
|
|||||||
|
|
||||||
function TCustomCodeTool.ReadTilBracketClose(
|
function TCustomCodeTool.ReadTilBracketClose(
|
||||||
ExceptionOnNotFound: boolean): boolean;
|
ExceptionOnNotFound: boolean): boolean;
|
||||||
|
// reads code brackets (not comment brackets)
|
||||||
var CloseBracket, AntiCloseBracket: char;
|
var CloseBracket, AntiCloseBracket: char;
|
||||||
Start: TAtomPosition;
|
Start: TAtomPosition;
|
||||||
begin
|
begin
|
||||||
@ -951,6 +953,44 @@ begin
|
|||||||
Result:=true;
|
Result:=true;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TCustomCodeTool.ReadBackTilBracketClose(
|
||||||
|
ExceptionOnNotFound: boolean): boolean;
|
||||||
|
// reads code brackets (not comment brackets)
|
||||||
|
var CloseBracket, AntiCloseBracket: char;
|
||||||
|
Start: TAtomPosition;
|
||||||
|
begin
|
||||||
|
Result:=false;
|
||||||
|
if AtomIsChar(')') then begin
|
||||||
|
CloseBracket:='(';
|
||||||
|
AntiCloseBracket:='[';
|
||||||
|
end else if AtomIsChar(']') then begin
|
||||||
|
CloseBracket:='[';
|
||||||
|
AntiCloseBracket:='(';
|
||||||
|
end else begin
|
||||||
|
if ExceptionOnNotFound then
|
||||||
|
RaiseException(
|
||||||
|
'syntax error: bracket close expected, but '+GetAtom+' found');
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
Start:=CurPos;
|
||||||
|
repeat
|
||||||
|
ReadPriorAtom;
|
||||||
|
if (AtomIsChar(CloseBracket)) then break;
|
||||||
|
if (CurPos.StartPos<1) or AtomIsChar(AntiCloseBracket)
|
||||||
|
or UpAtomIs('END') or UpAtomIs('BEGIN') then begin
|
||||||
|
CurPos:=Start;
|
||||||
|
if ExceptionOnNotFound then
|
||||||
|
RaiseException(
|
||||||
|
'syntax error: bracket '+CloseBracket+' not found');
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
if (AtomIsChar(')')) or (AtomIsChar(']')) then begin
|
||||||
|
if not ReadBackTilBracketClose(ExceptionOnNotFound) then exit;
|
||||||
|
end;
|
||||||
|
until false;
|
||||||
|
Result:=true;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TCustomCodeTool.BeginParsing(DeleteNodes,
|
procedure TCustomCodeTool.BeginParsing(DeleteNodes,
|
||||||
OnlyInterfaceNeeded: boolean);
|
OnlyInterfaceNeeded: boolean);
|
||||||
begin
|
begin
|
||||||
|
@ -181,8 +181,10 @@ writeln('TMethodJumpingCodeTool.FindJumpPoint B');
|
|||||||
if CleanCursorPos>=LastAtomEnd then CleanCursorPos:=LastAtomEnd-1;
|
if CleanCursorPos>=LastAtomEnd then CleanCursorPos:=LastAtomEnd-1;
|
||||||
// find CodeTreeNode at cursor
|
// find CodeTreeNode at cursor
|
||||||
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos);
|
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos);
|
||||||
if CursorNode=nil then
|
if CursorNode=nil then begin
|
||||||
|
WriteDebugTreeReport;
|
||||||
RaiseException('no node found at cursor');
|
RaiseException('no node found at cursor');
|
||||||
|
end;
|
||||||
{$IFDEF CTDEBUG}
|
{$IFDEF CTDEBUG}
|
||||||
writeln('TMethodJumpingCodeTool.FindJumpPoint C ',NodeDescriptionAsString(CursorNode.Desc));
|
writeln('TMethodJumpingCodeTool.FindJumpPoint C ',NodeDescriptionAsString(CursorNode.Desc));
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
@ -213,15 +215,6 @@ writeln('TMethodJumpingCodeTool.FindJumpPoint D ',CleanCursorPos,', |',copy(Src,
|
|||||||
or (not (CursorNode.Desc in [ctnProcedureHead,ctnProcedure])) then
|
or (not (CursorNode.Desc in [ctnProcedureHead,ctnProcedure])) then
|
||||||
exit;
|
exit;
|
||||||
// build the method name + parameter list (without default values)
|
// build the method name + parameter list (without default values)
|
||||||
//SearchedProc:=ExtractProcHead(CursorNode,
|
|
||||||
// [phpWithParameterNames,phpAddClassname]);
|
|
||||||
{$IFDEF CTDEBUG}
|
|
||||||
//writeln('TMethodJumpingCodeTool.FindJumpPoint E SearchedProc="',SearchedProc,'"');
|
|
||||||
{$ENDIF}
|
|
||||||
//if SearchedProc='' then exit;
|
|
||||||
// search the method
|
|
||||||
//ProcNode:=FindProcNode(TypeSectionNode,SearchedProc,
|
|
||||||
// [phpWithParameterNames,phpIgnoreForwards]);
|
|
||||||
Result:=FindBestProcNode(CursorNode,[phpAddClassName,phpInUpperCase],
|
Result:=FindBestProcNode(CursorNode,[phpAddClassName,phpInUpperCase],
|
||||||
TypeSectionNode,[phpIgnoreForwards,phpInUpperCase]);
|
TypeSectionNode,[phpIgnoreForwards,phpInUpperCase]);
|
||||||
{$IFDEF CTDEBUG}
|
{$IFDEF CTDEBUG}
|
||||||
@ -309,17 +302,8 @@ writeln('TMethodJumpingCodeTool.FindJumpPoint 2B ');
|
|||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
|
|
||||||
// build the method name + parameter list (without default values)
|
// build the method name + parameter list (without default values)
|
||||||
//SearchedProc:=ExtractProcHead(ProcNode,[phpInUpperCase]);
|
|
||||||
{$IFDEF CTDEBUG}
|
|
||||||
//writeln('TMethodJumpingCodeTool.FindJumpPoint 2C SearchedProc="',SearchedProc,'"');
|
|
||||||
{$ENDIF}
|
|
||||||
//if SearchedProc='' then exit;
|
|
||||||
// search the method
|
|
||||||
//ProcNode:=FindProcNode(ProcNode,SearchedProc,
|
|
||||||
// [phpInUpperCase,phpIgnoreForwards]);
|
|
||||||
Result:=FindBestProcNode(ProcNode,[phpInUpperCase],
|
Result:=FindBestProcNode(ProcNode,[phpInUpperCase],
|
||||||
ProcNode,[phpInUpperCase,phpIgnoreForwards]);
|
ProcNode,[phpInUpperCase,phpIgnoreForwards]);
|
||||||
//if ProcNode=nil then exit;
|
|
||||||
// find good position in procedure body
|
// find good position in procedure body
|
||||||
{$IFDEF CTDEBUG}
|
{$IFDEF CTDEBUG}
|
||||||
writeln('TMethodJumpingCodeTool.FindJumpPoint 2D');
|
writeln('TMethodJumpingCodeTool.FindJumpPoint 2D');
|
||||||
@ -365,18 +349,6 @@ writeln('TMethodJumpingCodeTool.FindJumpPoint 4D ',StartNode<>nil);
|
|||||||
{$IFDEF CTDEBUG}
|
{$IFDEF CTDEBUG}
|
||||||
writeln('TMethodJumpingCodeTool.FindJumpPoint 4E ',Result);
|
writeln('TMethodJumpingCodeTool.FindJumpPoint 4E ',Result);
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
//if ProcNode=nil then begin
|
|
||||||
// there is no exact corresponding proc
|
|
||||||
// -> search for a proc with the same name but different param list
|
|
||||||
// SearchedProc:=ExtractProcHead(ProcNode,
|
|
||||||
// [phpWithoutClassName,phpInUpperCase,phpWithoutBrackets,
|
|
||||||
// phpWithoutParamList]);
|
|
||||||
// ProcNode:=FindProcNode(StartNode,SearchedProc,[phpInUpperCase,
|
|
||||||
// phpWithoutBrackets,phpWithoutParamList]);
|
|
||||||
{$IFDEF CTDEBUG}
|
|
||||||
//writeln('TMethodJumpingCodeTool.FindJumpPoint 4E2 ',ProcNode<>nil,' ',SearchedProc);
|
|
||||||
{$ENDIF}
|
|
||||||
//end;
|
|
||||||
if not Result then begin
|
if not Result then begin
|
||||||
// search first undefined proc node with body
|
// search first undefined proc node with body
|
||||||
SearchForNodes:=GatherProcNodes(StartNode,
|
SearchForNodes:=GatherProcNodes(StartNode,
|
||||||
@ -427,18 +399,8 @@ writeln('TMethodJumpingCodeTool.FindJumpPoint 4G ',DiffNode<>nil);
|
|||||||
end;
|
end;
|
||||||
end else begin
|
end else begin
|
||||||
// search forward procedure
|
// search forward procedure
|
||||||
//SearchedProc:=ExtractProcHead(ProcNode,[phpWithParameterNames]);
|
|
||||||
//ProcNode:=FindProcNode(StartNode,SearchedProc,
|
|
||||||
// [phpWithParameterNames,phpIgnoreProcsWithBody]);
|
|
||||||
//if ProcNode=nil then exit;
|
|
||||||
Result:=FindBestProcNode(ProcNode,[phpInUpperCase],
|
Result:=FindBestProcNode(ProcNode,[phpInUpperCase],
|
||||||
StartNode,[phpInUpperCase,phpIgnoreProcsWithBody]);
|
StartNode,[phpInUpperCase,phpIgnoreProcsWithBody]);
|
||||||
// find good position in forward procedure
|
|
||||||
{$IFDEF CTDEBUG}
|
|
||||||
//writeln('TMethodJumpingCodeTool.FindJumpPoint 4B');
|
|
||||||
{$ENDIF}
|
|
||||||
//ProcNode:=ProcNode.FirstChild;
|
|
||||||
//Result:=JumpToNode(ProcNode,NewPos,NewTopLine);
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
@ -31,7 +31,9 @@
|
|||||||
|
|
||||||
|
|
||||||
ToDo:
|
ToDo:
|
||||||
|
- ReadBackTilBlockEnd: case could also be in a record, then it should not
|
||||||
|
close the block
|
||||||
|
|
||||||
}
|
}
|
||||||
unit PascalParserTool;
|
unit PascalParserTool;
|
||||||
|
|
||||||
@ -198,13 +200,16 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
TEndBlockType = (ebtBegin, ebtAsm, ebtTry, ebtCase, ebtRepeat, ebtRecord);
|
TEndBlockType = (ebtBegin, ebtAsm, ebtTry, ebtCase, ebtRepeat, ebtRecord,
|
||||||
|
ebtClass, ebtObject);
|
||||||
TTryType = (ttNone, ttFinally, ttExcept);
|
TTryType = (ttNone, ttFinally, ttExcept);
|
||||||
|
|
||||||
|
|
||||||
{ TMultiKeyWordListCodeTool }
|
{ TMultiKeyWordListCodeTool }
|
||||||
|
|
||||||
@ -1519,7 +1524,8 @@ begin
|
|||||||
begin
|
begin
|
||||||
if BlockType=ebtAsm then
|
if BlockType=ebtAsm then
|
||||||
RaiseException('syntax error: unexpected keyword "'+GetAtom+'" found');
|
RaiseException('syntax error: unexpected keyword "'+GetAtom+'" found');
|
||||||
ReadTilBlockEnd(false);
|
if (BlockType<>ebtRecord) or (not UpAtomIs('CASE')) then
|
||||||
|
ReadTilBlockEnd(false);
|
||||||
end else if UpAtomIs('UNTIL') then begin
|
end else if UpAtomIs('UNTIL') then begin
|
||||||
if BlockType=ebtRepeat then
|
if BlockType=ebtRepeat then
|
||||||
break;
|
break;
|
||||||
@ -1586,6 +1592,7 @@ begin
|
|||||||
end else if UpAtomIs('BEGIN') or UpAtomIs('CASE') or UpAtomIs('ASM')
|
end else if UpAtomIs('BEGIN') or UpAtomIs('CASE') or UpAtomIs('ASM')
|
||||||
or UpAtomIs('RECORD') then
|
or UpAtomIs('RECORD') then
|
||||||
begin
|
begin
|
||||||
|
// Todo: case could also be in a record, then it should not close the block
|
||||||
if BlockType=ebtBegin then
|
if BlockType=ebtBegin then
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
|
@ -51,6 +51,13 @@ uses
|
|||||||
|
|
||||||
type
|
type
|
||||||
TStandardCodeTool = class(TFindDeclarationTool)
|
TStandardCodeTool = class(TFindDeclarationTool)
|
||||||
|
private
|
||||||
|
BlockKeywordFuncList: TKeyWordFunctionList;
|
||||||
|
procedure BuildBlockKeyWordFuncList;
|
||||||
|
function ReadTilGuessedUnclosedBlock(MinCleanPos: integer;
|
||||||
|
ReadOnlyOneBlock: boolean): boolean;
|
||||||
|
function ReadForwardTilAnyBracketClose: boolean;
|
||||||
|
function ReadBackwardTilAnyBracketClose: boolean;
|
||||||
public
|
public
|
||||||
// source name e.g. 'unit UnitName;'
|
// source name e.g. 'unit UnitName;'
|
||||||
function GetSourceNamePos(var NamePos: TAtomPosition): boolean;
|
function GetSourceNamePos(var NamePos: TAtomPosition): boolean;
|
||||||
@ -119,12 +126,27 @@ type
|
|||||||
// blocks (e.g. begin..end)
|
// blocks (e.g. begin..end)
|
||||||
function FindBlockCounterPart(CursorPos: TCodeXYPosition;
|
function FindBlockCounterPart(CursorPos: TCodeXYPosition;
|
||||||
var NewPos: TCodeXYPosition; var NewTopLine: integer): boolean;
|
var NewPos: TCodeXYPosition; var NewTopLine: integer): boolean;
|
||||||
//function GuessBrokenBlock(StartPos: integer): boolean;
|
function GuessUnclosedBlock(CursorPos: TCodeXYPosition;
|
||||||
|
var NewPos: TCodeXYPosition; var NewTopLine: integer): boolean;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
|
type
|
||||||
|
TBlockKeyword = (bkwNone, bkwBegin, bkwAsm, bkwTry, bkwCase, bkwRepeat,
|
||||||
|
bkwRecord, bkwClass, bkwObject, bkwInterface,
|
||||||
|
bkwDispInterface, bkwEnd, bkwUntil, bkwFinally,
|
||||||
|
bkwExcept);
|
||||||
|
|
||||||
|
const
|
||||||
|
BlockKeywords: array[TBlockKeyword] of string = (
|
||||||
|
'(unknown)', 'BEGIN', 'ASM', 'TRY', 'CASE', 'REPEAT', 'RECORD', 'CLASS',
|
||||||
|
'OBJECT', 'INTERFACE', 'DISPINTERFACE', 'END', 'UNTIL', 'FINALLY',
|
||||||
|
'EXCEPT'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{ TStandardCodeTool }
|
{ TStandardCodeTool }
|
||||||
|
|
||||||
@ -956,40 +978,334 @@ begin
|
|||||||
{$IFDEF CTDEBUG}
|
{$IFDEF CTDEBUG}
|
||||||
writeln('TStandardCodeTool.FindBlockCounterPart A CursorPos=',CursorPos.X,',',CursorPos.Y);
|
writeln('TStandardCodeTool.FindBlockCounterPart A CursorPos=',CursorPos.X,',',CursorPos.Y);
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
BeginParsing(true,false);
|
if UpdateNeeded(false) then BeginParsing(true,false);
|
||||||
// find the CursorPos in cleaned source
|
// find the CursorPos in cleaned source
|
||||||
Dummy:=CaretToCleanPos(CursorPos, CleanCursorPos);
|
Dummy:=CaretToCleanPos(CursorPos, CleanCursorPos);
|
||||||
if (Dummy<>0) and (Dummy<>-1) then
|
if (Dummy<>0) and (Dummy<>-1) then
|
||||||
RaiseException('cursor pos outside of code');
|
RaiseException('cursor pos outside of code');
|
||||||
// read word at cursor
|
// read word at cursor
|
||||||
MoveCursorToCleanPos(CleanCursorPos);
|
MoveCursorToCleanPos(CleanCursorPos);
|
||||||
if Src[CurPos.StartPos] in [';','.'] then dec(CurPos.StartPos);
|
if Src[CurPos.StartPos] in ['(','[','{'] then begin
|
||||||
CurPos.EndPos:=CurPos.StartPos;
|
// jump forward to matching bracket
|
||||||
while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
|
CurPos.EndPos:=CurPos.StartPos+1;
|
||||||
dec(CurPos.StartPos);
|
if not ReadForwardTilAnyBracketClose then exit;
|
||||||
while (CurPos.EndPos<SrcLen) and (IsWordChar[Src[CurPos.EndPos]]) do
|
end else if Src[CurPos.StartPos] in [')',']','}'] then begin
|
||||||
inc(CurPos.EndPos);
|
// jump backward to matching bracket
|
||||||
if CurPos.EndPos=CurPos.StartPos then exit;
|
CurPos.EndPos:=CurPos.StartPos+1;
|
||||||
|
if not ReadBackwardTilAnyBracketClose then exit;
|
||||||
|
end else begin;
|
||||||
|
if Src[CurPos.StartPos] in [';','.'] then dec(CurPos.StartPos);
|
||||||
|
CurPos.EndPos:=CurPos.StartPos;
|
||||||
|
while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
|
||||||
|
dec(CurPos.StartPos);
|
||||||
|
while (CurPos.EndPos<SrcLen) and (IsWordChar[Src[CurPos.EndPos]]) do
|
||||||
|
inc(CurPos.EndPos);
|
||||||
|
if CurPos.EndPos=CurPos.StartPos then exit;
|
||||||
{$IFDEF CTDEBUG}
|
{$IFDEF CTDEBUG}
|
||||||
writeln('TStandardCodeTool.FindBlockCounterPart C Word=',GetAtom);
|
writeln('TStandardCodeTool.FindBlockCounterPart C Word=',GetAtom);
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
// read till block keyword counterpart
|
// read till block keyword counterpart
|
||||||
if UpAtomIs('BEGIN') or UpAtomIs('CASE') or UpAtomIs('ASM')
|
if UpAtomIs('BEGIN') or UpAtomIs('CASE') or UpAtomIs('ASM')
|
||||||
or UpAtomIs('RECORD') or UpAtomIs('CLASS') or UpAtomIs('OBJECT')
|
or UpAtomIs('RECORD') or UpAtomIs('TRY') or UpAtomIs('REPEAT') then begin
|
||||||
or UpAtomIs('TRY') then begin
|
// read forward till END, FINALLY, EXCEPT
|
||||||
// read forward till END, FINALLY, EXCEPT
|
ReadTilBlockEnd(true);
|
||||||
ReadTilBlockEnd(true);
|
end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
|
||||||
end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT') then
|
or UpAtomIs('UNTIL') then
|
||||||
begin
|
begin
|
||||||
// read backward till BEGIN, CASE, ASM, RECORD, CLASS, OBJECT
|
// read backward till BEGIN, CASE, ASM, RECORD, REPEAT
|
||||||
ReadBackTilBlockEnd(true);
|
ReadBackTilBlockEnd(true);
|
||||||
end else
|
end else
|
||||||
exit;
|
exit;
|
||||||
|
end;
|
||||||
// CursorPos now contains the counter block keyword
|
// CursorPos now contains the counter block keyword
|
||||||
Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
|
Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TStandardCodeTool.GuessUnclosedBlock(CursorPos: TCodeXYPosition;
|
||||||
|
var NewPos: TCodeXYPosition; var NewTopLine: integer): boolean;
|
||||||
|
{ search a block (e.g. begin..end) that looks unclosed, i.e. 'begin'
|
||||||
|
without 'end' or 'begin' with 'end' in a different column.
|
||||||
|
This function can be used as GuessNextUnclosedBlock, because it ignores blocks
|
||||||
|
in front of CursorPos.
|
||||||
|
|
||||||
|
Examples for good blocks:
|
||||||
|
|
||||||
|
repeat
|
||||||
|
until
|
||||||
|
|
||||||
|
begin end // start and end of block in the same line
|
||||||
|
|
||||||
|
if expr then begin // first char in line is relevant, not the block keyword
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Examples for bad blocks:
|
||||||
|
|
||||||
|
begin // block start and end has different indenting
|
||||||
|
end
|
||||||
|
|
||||||
|
asm // 'end.' is source end, never asm end
|
||||||
|
end.
|
||||||
|
|
||||||
|
try // different indenting
|
||||||
|
finally
|
||||||
|
|
||||||
|
repeat // keywords do not match
|
||||||
|
end
|
||||||
|
|
||||||
|
}
|
||||||
|
var Dummy, CleanCursorPos: integer;
|
||||||
|
begin
|
||||||
|
Result:=false;
|
||||||
|
// scan code
|
||||||
|
{$IFDEF CTDEBUG}
|
||||||
|
writeln('TStandardCodeTool.GuessUnclosedBlock A CursorPos=',CursorPos.X,',',CursorPos.Y);
|
||||||
|
{$ENDIF}
|
||||||
|
if UpdateNeeded(false) then BeginParsing(true,false);
|
||||||
|
// find the CursorPos in cleaned source
|
||||||
|
Dummy:=CaretToCleanPos(CursorPos, CleanCursorPos);
|
||||||
|
if (Dummy<>0) and (Dummy<>-1) then
|
||||||
|
RaiseException('cursor pos outside of code');
|
||||||
|
// start reading at beginning of code
|
||||||
|
MoveCursorToCleanPos(1);
|
||||||
|
BuildBlockKeyWordFuncList;
|
||||||
|
if ReadTilGuessedUnclosedBlock(CleanCursorPos,false) then
|
||||||
|
Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TStandardCodeTool.ReadTilGuessedUnclosedBlock(
|
||||||
|
MinCleanPos: integer; ReadOnlyOneBlock: boolean): boolean;
|
||||||
|
// returns true if unclosed block found
|
||||||
|
var BlockType, CurBlockWord: TBlockKeyword;
|
||||||
|
BlockStart: integer;
|
||||||
|
begin
|
||||||
|
Result:=false;
|
||||||
|
BlockType:=bkwNone;
|
||||||
|
BlockStart:=-1;
|
||||||
|
// read til this block is closed
|
||||||
|
while (CurPos.StartPos<=SrcLen) do begin
|
||||||
|
if BlockKeywordFuncList.DoItUppercase(UpperSrc,
|
||||||
|
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos) then
|
||||||
|
begin
|
||||||
|
for CurBlockWord:=Low(TBlockKeyword) to High(TBlockKeyword) do
|
||||||
|
if UpAtomIs(BlockKeywords[CurBlockWord]) then
|
||||||
|
break;
|
||||||
|
if (CurBlockWord=bkwInterface) and (not LastAtomIs(0,'=')) then
|
||||||
|
CurBlockWord:=bkwNone;
|
||||||
|
|
||||||
|
if (CurBlockWord=bkwEnd) then begin
|
||||||
|
ReadNextAtom;
|
||||||
|
if AtomIsChar('.') then begin
|
||||||
|
// source end found
|
||||||
|
if BlockType in [bkwBegin,bkwNone] then begin
|
||||||
|
CurPos.StartPos:=SrcLen+1;
|
||||||
|
exit;
|
||||||
|
end else begin
|
||||||
|
MoveCursorToCleanPos(BlockStart);
|
||||||
|
Result:=true;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
end else
|
||||||
|
UndoReadNextAtom;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if BlockType=bkwNone then begin
|
||||||
|
case CurBlockWord of
|
||||||
|
|
||||||
|
bkwBegin,bkwRepeat,bkwCase,bkwTry,bkwRecord,bkwClass,bkwObject,
|
||||||
|
bkwInterface,bkwDispInterface:
|
||||||
|
begin
|
||||||
|
BlockType:=CurBlockWord;
|
||||||
|
BlockStart:=CurPos.StartPos;
|
||||||
|
end;
|
||||||
|
|
||||||
|
bkwEnd,bkwUntil:
|
||||||
|
begin
|
||||||
|
// close block keywords found, but no block was opened
|
||||||
|
// -> unclosed block found
|
||||||
|
Result:=true;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if ((BlockType in [bkwBegin, bkwAsm, bkwCase, bkwRecord, bkwClass,
|
||||||
|
bkwObject, bkwFinally, bkwExcept, bkwInterface, bkwDispInterface])
|
||||||
|
and (CurBlockWord=bkwEnd))
|
||||||
|
or ((BlockType=bkwRepeat) and (CurBlockWord=bkwUntil)) then begin
|
||||||
|
// block end found
|
||||||
|
if (MinCleanPos<=CurPos.StartPos)
|
||||||
|
and (GetLineIndent(Src,CurPos.StartPos)<>GetLineIndent(Src,BlockStart))
|
||||||
|
then begin
|
||||||
|
// different indent -> unclosed block found
|
||||||
|
if GetLineIndent(Src,BlockStart)>=GetLineIndent(Src,CurPos.StartPos)
|
||||||
|
then begin
|
||||||
|
// the current block is more or equal indented than the next block
|
||||||
|
// -> probably the current block misses a block end
|
||||||
|
MoveCursorToCleanPos(BlockStart);
|
||||||
|
end;
|
||||||
|
Result:=true;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
// end block
|
||||||
|
BlockType:=bkwNone;
|
||||||
|
if ReadOnlyOneBlock then break;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if (BlockType=bkwTry) and (CurBlockWord in [bkwFinally,bkwExcept]) then
|
||||||
|
begin
|
||||||
|
// try..finally, try..except found
|
||||||
|
if (MinCleanPos<=CurPos.StartPos)
|
||||||
|
and (GetLineIndent(Src,CurPos.StartPos)<>GetLineIndent(Src,BlockStart))
|
||||||
|
then begin
|
||||||
|
// different indent -> unclosed block found
|
||||||
|
// probably a block start is missing, so the error position is
|
||||||
|
// here at block end
|
||||||
|
Result:=true;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
// change blocktype
|
||||||
|
BlockType:=CurBlockWord;
|
||||||
|
BlockStart:=CurPos.StartPos;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if ((BlockType in [bkwBegin,bkwRepeat,bkwTry,bkwFinally,bkwExcept,
|
||||||
|
bkwCase])
|
||||||
|
and (CurBlockWord in [bkwBegin,bkwRepeat,bkwTry,bkwCase]))
|
||||||
|
or ((BlockType in [bkwClass,bkwInterface,bkwDispInterface,bkwObject,
|
||||||
|
bkwRecord])
|
||||||
|
and (CurBlockWord in [bkwRecord])) then
|
||||||
|
begin
|
||||||
|
// sub blockstart found -> read recursively
|
||||||
|
Result:=ReadTilGuessedUnclosedBlock(MinCleanPos,true);
|
||||||
|
if Result then exit;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if (BlockType=bkwRecord) and (CurBlockWord=bkwCase) then begin
|
||||||
|
// variant record
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// unexpected keyword found
|
||||||
|
if GetLineIndent(Src,BlockStart)>=GetLineIndent(Src,CurPos.StartPos)
|
||||||
|
then begin
|
||||||
|
// the current block is more or equal indented than the next block
|
||||||
|
// -> probably the current block misses a block end
|
||||||
|
MoveCursorToCleanPos(BlockStart);
|
||||||
|
end;
|
||||||
|
Result:=true;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
ReadNextAtom;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TStandardCodeTool.BuildBlockKeyWordFuncList;
|
||||||
|
var BlockWord: TBlockKeyword;
|
||||||
|
begin
|
||||||
|
if BlockKeywordFuncList=nil then begin
|
||||||
|
BlockKeywordFuncList:=TKeyWordFunctionList.Create;
|
||||||
|
for BlockWord:=Low(TBlockKeyword) to High(TBlockKeyword) do
|
||||||
|
with BlockKeywordFuncList do
|
||||||
|
Add(BlockKeywords[BlockWord],{$ifdef FPC}@{$endif}AllwaysTrue);
|
||||||
|
AddKeyWordFuncList(BlockKeywordFuncList);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TStandardCodeTool.ReadForwardTilAnyBracketClose: boolean;
|
||||||
|
// this function reads any bracket
|
||||||
|
// (the ReadTilBracketClose function reads only brackets in code, not comments)
|
||||||
|
var OpenBracket: char;
|
||||||
|
CommentLvl: integer;
|
||||||
|
begin
|
||||||
|
Result:=false;
|
||||||
|
OpenBracket:=Src[CurPos.StartPos];
|
||||||
|
if OpenBracket='{' then begin
|
||||||
|
// read til end of comment
|
||||||
|
CommentLvl:=1;
|
||||||
|
inc(CurPos.StartPos);
|
||||||
|
while (CurPos.StartPos<=SrcLen) and (CommentLvl>0) do begin
|
||||||
|
case Src[CurPos.StartPos] of
|
||||||
|
'{': if Scanner.NestedComments then inc(CommentLvl);
|
||||||
|
'}':
|
||||||
|
if CommentLvl=1 then begin
|
||||||
|
Result:=true;
|
||||||
|
break;
|
||||||
|
end else
|
||||||
|
dec(CommentLvl);
|
||||||
|
end;
|
||||||
|
inc(CurPos.StartPos);
|
||||||
|
end;
|
||||||
|
end else if OpenBracket='(' then begin
|
||||||
|
if (CurPos.StartPos<SrcLen) and (Src[CurPos.StartPos+1]='*') then begin
|
||||||
|
// read til end of comment
|
||||||
|
inc(CurPos.StartPos,3);
|
||||||
|
while true do begin
|
||||||
|
if (CurPos.StartPos<=SrcLen)
|
||||||
|
and ((Src[CurPos.StartPos-1]='*') and (Src[CurPos.StartPos]=')')) then
|
||||||
|
begin
|
||||||
|
Result:=true;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
inc(CurPos.StartPos);
|
||||||
|
end;
|
||||||
|
end else begin
|
||||||
|
Result:=ReadTilBracketClose(false);
|
||||||
|
end;
|
||||||
|
end else if OpenBracket='[' then begin
|
||||||
|
Result:=ReadTilBracketClose(false);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TStandardCodeTool.ReadBackwardTilAnyBracketClose: boolean;
|
||||||
|
// this function reads any bracket
|
||||||
|
// (the ReadBackTilBracketClose function reads only brackets in code,
|
||||||
|
// not comments)
|
||||||
|
var OpenBracket: char;
|
||||||
|
CommentLvl: integer;
|
||||||
|
begin
|
||||||
|
Result:=false;
|
||||||
|
OpenBracket:=Src[CurPos.StartPos];
|
||||||
|
if OpenBracket='}' then begin
|
||||||
|
// read backwards til end of comment
|
||||||
|
CommentLvl:=1;
|
||||||
|
dec(CurPos.StartPos);
|
||||||
|
while (CurPos.StartPos>=1) and (CommentLvl>0) do begin
|
||||||
|
case Src[CurPos.StartPos] of
|
||||||
|
'}': if Scanner.NestedComments then inc(CommentLvl);
|
||||||
|
'{':
|
||||||
|
if CommentLvl=1 then begin
|
||||||
|
Result:=true;
|
||||||
|
break;
|
||||||
|
end else
|
||||||
|
dec(CommentLvl);
|
||||||
|
end;
|
||||||
|
dec(CurPos.StartPos);
|
||||||
|
end;
|
||||||
|
end else if OpenBracket=')' then begin
|
||||||
|
if (CurPos.StartPos>1) and (Src[CurPos.StartPos-1]='*') then begin
|
||||||
|
// read til end of comment
|
||||||
|
dec(CurPos.StartPos,3);
|
||||||
|
while true do begin
|
||||||
|
if (CurPos.StartPos>=1)
|
||||||
|
and ((Src[CurPos.StartPos+11]='*') and (Src[CurPos.StartPos]='(')) then
|
||||||
|
begin
|
||||||
|
Result:=true;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
dec(CurPos.StartPos);
|
||||||
|
end;
|
||||||
|
end else begin
|
||||||
|
Result:=ReadBackTilBracketClose(false);
|
||||||
|
end;
|
||||||
|
end else if OpenBracket=']' then begin
|
||||||
|
Result:=ReadBackTilBracketClose(false);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
@ -843,7 +843,6 @@ var
|
|||||||
begin
|
begin
|
||||||
if (not SelAvail)
|
if (not SelAvail)
|
||||||
or (PrimarySelection.OnRequest=@PrimarySelectionRequest) then exit;
|
or (PrimarySelection.OnRequest=@PrimarySelectionRequest) then exit;
|
||||||
writeln('>>> TCustomSynEdit.AquirePrimarySelection <<<');
|
|
||||||
FormatList:=CF_TEXT;
|
FormatList:=CF_TEXT;
|
||||||
try
|
try
|
||||||
PrimarySelection.SetSupportedFormats(1,@FormatList);
|
PrimarySelection.SetSupportedFormats(1,@FormatList);
|
||||||
|
52
ide/main.pp
52
ide/main.pp
@ -147,6 +147,7 @@ type
|
|||||||
|
|
||||||
itmToolConfigure: TMenuItem;
|
itmToolConfigure: TMenuItem;
|
||||||
itmToolSyntaxCheck: TMenuItem;
|
itmToolSyntaxCheck: TMenuItem;
|
||||||
|
itmToolGuessUnclosedBlockCheck: TMenuItem;
|
||||||
|
|
||||||
itmEnvGeneralOptions: TMenuItem;
|
itmEnvGeneralOptions: TMenuItem;
|
||||||
itmEnvEditorOptions: TMenuItem;
|
itmEnvEditorOptions: TMenuItem;
|
||||||
@ -216,6 +217,7 @@ type
|
|||||||
// tools menu
|
// tools menu
|
||||||
procedure mnuToolConfigureClicked(Sender : TObject);
|
procedure mnuToolConfigureClicked(Sender : TObject);
|
||||||
procedure mnuToolSyntaxCheckClicked(Sender : TObject);
|
procedure mnuToolSyntaxCheckClicked(Sender : TObject);
|
||||||
|
procedure mnuToolGuessUnclosedBlockClicked(Sender : TObject);
|
||||||
|
|
||||||
// enironment menu
|
// enironment menu
|
||||||
procedure mnuEnvGeneralOptionsClicked(Sender : TObject);
|
procedure mnuEnvGeneralOptionsClicked(Sender : TObject);
|
||||||
@ -385,6 +387,7 @@ type
|
|||||||
procedure DoJumpToCodeToolBossError;
|
procedure DoJumpToCodeToolBossError;
|
||||||
function DoCheckSyntax: TModalResult;
|
function DoCheckSyntax: TModalResult;
|
||||||
procedure DoGoToPascalBlockEnd;
|
procedure DoGoToPascalBlockEnd;
|
||||||
|
procedure DoJumpToGuessedUnclosedBlock(FindNext: boolean);
|
||||||
|
|
||||||
// methods for debugging, compiling and external tools
|
// methods for debugging, compiling and external tools
|
||||||
function DoJumpToCompilerMessage(Index:integer;
|
function DoJumpToCompilerMessage(Index:integer;
|
||||||
@ -1403,6 +1406,12 @@ begin
|
|||||||
itmToolSyntaxCheck.OnClick := @mnuToolSyntaxCheckClicked;
|
itmToolSyntaxCheck.OnClick := @mnuToolSyntaxCheckClicked;
|
||||||
mnuTools.Add(itmToolSyntaxCheck);
|
mnuTools.Add(itmToolSyntaxCheck);
|
||||||
|
|
||||||
|
itmToolGuessUnclosedBlockCheck := TMenuItem.Create(Self);
|
||||||
|
itmToolGuessUnclosedBlockCheck.Name:='itmToolGuessUnclosedBlockCheck';
|
||||||
|
itmToolGuessUnclosedBlockCheck.Caption := 'Guess unclosed block';
|
||||||
|
itmToolGuessUnclosedBlockCheck.OnClick := @mnuToolGuessUnclosedBlockClicked;
|
||||||
|
mnuTools.Add(itmToolGuessUnclosedBlockCheck);
|
||||||
|
|
||||||
|
|
||||||
//--------------
|
//--------------
|
||||||
// Environment
|
// Environment
|
||||||
@ -1775,6 +1784,9 @@ begin
|
|||||||
|
|
||||||
ecSyntaxCheck:
|
ecSyntaxCheck:
|
||||||
DoCheckSyntax;
|
DoCheckSyntax;
|
||||||
|
|
||||||
|
ecGuessUnclosedBlock:
|
||||||
|
DoJumpToGuessedUnclosedBlock(true);
|
||||||
|
|
||||||
else
|
else
|
||||||
Handled:=false;
|
Handled:=false;
|
||||||
@ -2105,6 +2117,11 @@ begin
|
|||||||
DoCheckSyntax;
|
DoCheckSyntax;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainIDE.mnuToolGuessUnclosedBlockClicked(Sender : TObject);
|
||||||
|
begin
|
||||||
|
DoJumpToGuessedUnclosedBlock(true);
|
||||||
|
end;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
procedure TMainIDE.SaveDesktopSettings(
|
procedure TMainIDE.SaveDesktopSettings(
|
||||||
@ -5176,6 +5193,33 @@ writeln('[TMainIDE.DoGoToPascalBlockEnd] ************');
|
|||||||
DoJumpToCodeToolBossError;
|
DoJumpToCodeToolBossError;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainIDE.DoJumpToGuessedUnclosedBlock(FindNext: boolean);
|
||||||
|
var ActiveSrcEdit: TSourceEditor;
|
||||||
|
ActiveUnitInfo: TUnitInfo;
|
||||||
|
NewSource: TCodeBuffer;
|
||||||
|
StartX, StartY, NewX, NewY, NewTopLine: integer;
|
||||||
|
begin
|
||||||
|
if not BeginCodeTool(ActiveSrcEdit,ActiveUnitInfo) then exit;
|
||||||
|
{$IFDEF IDE_DEBUG}
|
||||||
|
writeln('');
|
||||||
|
writeln('[TMainIDE.DoGoToPascalBlockEnd] ************');
|
||||||
|
{$ENDIF}
|
||||||
|
if FindNext then begin
|
||||||
|
StartX:=ActiveSrcEdit.EditorComponent.CaretX;
|
||||||
|
StartY:=ActiveSrcEdit.EditorComponent.CaretY;
|
||||||
|
end else begin
|
||||||
|
StartX:=1;
|
||||||
|
StartY:=1;
|
||||||
|
end;
|
||||||
|
if CodeToolBoss.GuessUnclosedBlock(ActiveUnitInfo.Source,
|
||||||
|
StartX,StartY,NewSource,NewX,NewY,NewTopLine) then
|
||||||
|
begin
|
||||||
|
DoJumpToCodePos(ActiveSrcEdit, ActiveUnitInfo,
|
||||||
|
NewSource, NewX, NewY, NewTopLine);
|
||||||
|
end else
|
||||||
|
DoJumpToCodeToolBossError;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainIDE.DoCompleteCodeAtCursor;
|
procedure TMainIDE.DoCompleteCodeAtCursor;
|
||||||
var ActiveSrcEdit: TSourceEditor;
|
var ActiveSrcEdit: TSourceEditor;
|
||||||
ActiveUnitInfo: TUnitInfo;
|
ActiveUnitInfo: TUnitInfo;
|
||||||
@ -5534,8 +5578,8 @@ end.
|
|||||||
=======
|
=======
|
||||||
|
|
||||||
$Log$
|
$Log$
|
||||||
Revision 1.198 2002/01/11 15:57:49 lazarus
|
Revision 1.199 2002/01/11 20:41:52 lazarus
|
||||||
MG: added find block end
|
MG: added guess unclosed block
|
||||||
|
|
||||||
Revision 1.197 2002/01/02 13:32:52 lazarus
|
Revision 1.197 2002/01/02 13:32:52 lazarus
|
||||||
MG: fixed clean abort of project loading
|
MG: fixed clean abort of project loading
|
||||||
@ -5565,8 +5609,8 @@ end.
|
|||||||
|
|
||||||
<<<<<<< main.pp
|
<<<<<<< main.pp
|
||||||
$Log$
|
$Log$
|
||||||
Revision 1.198 2002/01/11 15:57:49 lazarus
|
Revision 1.199 2002/01/11 20:41:52 lazarus
|
||||||
MG: added find block end
|
MG: added guess unclosed block
|
||||||
|
|
||||||
Revision 1.197 2002/01/02 13:32:52 lazarus
|
Revision 1.197 2002/01/02 13:32:52 lazarus
|
||||||
MG: fixed clean abort of project loading
|
MG: fixed clean abort of project loading
|
||||||
|
Loading…
Reference in New Issue
Block a user