mirror of
				https://gitlab.com/freepascal.org/lazarus/lazarus.git
				synced 2025-10-31 08:21:39 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			656 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			656 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
| {
 | |
|  ***************************************************************************
 | |
|  *                                                                         *
 | |
|  *   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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 | |
|  *                                                                         *
 | |
|  ***************************************************************************
 | |
| 
 | |
|   Author: Mattias Gaertner
 | |
| 
 | |
|   Abstract:
 | |
|     Common functions.
 | |
| }
 | |
| unit CodyUtils;
 | |
| 
 | |
| {$mode objfpc}{$H+}
 | |
| 
 | |
| interface
 | |
| 
 | |
| uses
 | |
|   Classes, SysUtils, Dialogs, Controls, LCLIntf, Clipbrd, LCLType, LResources,
 | |
|   // IDEIntf
 | |
|   IDEDialogs, LazIDEIntf, SrcEditorIntf,
 | |
|   FileProcs, LazFileUtils, CodeToolManager, CodeCache, SourceLog, BasicCodeTools,
 | |
|   EventCodeTool, LinkScanner, PascalParserTool, CodeTree, SourceChanger,
 | |
|   CodeBeautifier,
 | |
|   CodyStrConsts;
 | |
| 
 | |
| type
 | |
| 
 | |
|   { TCodyClipboardData }
 | |
| 
 | |
|   TCodyClipboardData = class
 | |
|   public
 | |
|     AsText: string;
 | |
|     procedure WriteString(MemStream: TMemoryStream; const s: string);
 | |
|     function ReadString(MemStream: TMemoryStream): string;
 | |
|     procedure WriteToStream(MemStream: TMemoryStream); virtual; abstract;
 | |
|     procedure ReadFromStream(MemStream: TMemoryStream); virtual; abstract;
 | |
|     procedure Execute({%H-}SrcEdit: TSourceEditorInterface; {%H-}LogXY: TPoint); virtual;
 | |
|   end;
 | |
|   TCodyClipboardFormat = class of TCodyClipboardData;
 | |
| 
 | |
|   { TCodyClipboardSrcData }
 | |
| 
 | |
|   TCodyClipboardSrcData = class(TCodyClipboardData)
 | |
|   public
 | |
|     SourceFilename: string;
 | |
|     SourceX: integer;
 | |
|     SourceY: integer;
 | |
|     procedure SetSourcePos(const SrcPos: TCodeXYPosition);
 | |
|     procedure WriteToStream(MemStream: TMemoryStream); override;
 | |
|     procedure ReadFromStream(MemStream: TMemoryStream); override;
 | |
|   end;
 | |
| 
 | |
|   { TCody }
 | |
| 
 | |
|   TCody = class
 | |
|   private
 | |
|     FClipboardFormats: TFPList;
 | |
|     function GetClipboardFormats(Index: integer): TCodyClipboardFormat;
 | |
|   public
 | |
|     constructor Create;
 | |
|     destructor Destroy; override;
 | |
| 
 | |
|     procedure DecodeLoaded(Sender: TSourceLog; const Filename: string;
 | |
|                            var Source, DiskEncoding, MemEncoding: string);
 | |
| 
 | |
|     // clipboard
 | |
|     class function ClipboardFormatId: TClipboardFormat;
 | |
|     function CanReadFromClipboard(AClipboard: TClipboard): Boolean;
 | |
|     function ReadFromClipboard(AClipboard: TClipboard;
 | |
|         SrcEdit: TSourceEditorInterface; LogXY: TPoint; AText: string): boolean;
 | |
|     function WriteToClipboard(Data: TCodyClipboardData;
 | |
|                               AClipboard: TClipboard = nil): Boolean;
 | |
|     procedure RegisterClipboardFormat(ccFormat: TCodyClipboardFormat);
 | |
|     function FindClipboardFormat(aName: string): TCodyClipboardFormat;
 | |
|     function ClipboardFormatCount: integer;
 | |
|     property ClipboardFormats[Index: integer]: TCodyClipboardFormat
 | |
|                                                        read GetClipboardFormats;
 | |
|     procedure SrcEditCopyPaste(SrcEdit: TSourceEditorInterface;
 | |
|       var AText: String; var {%H-}AMode: TSemSelectionMode; ALogStartPos: TPoint;
 | |
|       var AnAction: TSemCopyPasteAction);
 | |
|   end;
 | |
| 
 | |
| var
 | |
|   Cody: TCody;
 | |
| 
 | |
| type
 | |
|   TCUParseError = (
 | |
|     cupeNoSrcEditor,
 | |
|     cupeMainCodeNotFound, // the file of the unit start was not found
 | |
|     cupeParseError,
 | |
|     cupeCursorNotInCode, // e.g. in front of the keyword 'unit'
 | |
|     cupeSuccess
 | |
|     );
 | |
| 
 | |
| procedure ExplodeAWithBlockCmd(Sender: TObject);
 | |
| procedure InsertFileAtCursor(Sender: TObject);
 | |
| procedure InsertCallInherited(Sender: TObject);
 | |
| procedure InsertInt64ID(Sender: TObject);
 | |
| 
 | |
| function ParseTilCursor(out Tool: TCodeTool; out CleanPos: integer;
 | |
|    out Node: TCodeTreeNode; out ErrorHandled: boolean;
 | |
|    JumpToError: boolean; CodePos: PCodeXYPosition = nil): TCUParseError;
 | |
| function ParseUnit(out Tool: TCodeTool; out CleanPos: integer;
 | |
|    out Node: TCodeTreeNode; out ErrorHandled: boolean;
 | |
|    JumpToError: boolean; CodePos: PCodeXYPosition = nil;
 | |
|    TilCursor: boolean = false): TCUParseError;
 | |
| procedure OpenCodyHelp(Path: string);
 | |
| 
 | |
| function GetPatternValue1(const Pattern, PlaceHolder, Src: string; out Value1: string): boolean;
 | |
| 
 | |
| implementation
 | |
| 
 | |
| procedure ExplodeAWithBlockCmd(Sender: TObject);
 | |
| 
 | |
|   procedure ErrorNotInWithVar;
 | |
|   begin
 | |
|     IDEMessageDialog(crsCWError,
 | |
|       crsCWPleasePlaceTheCursorOfTheSourceEditorOnAWithVariab,
 | |
|       mtError,[mbCancel]);
 | |
|   end;
 | |
| 
 | |
| var
 | |
|   SrcEdit: TSourceEditorInterface;
 | |
| begin
 | |
|   // commit changes form source editor to codetools
 | |
|   if not LazarusIDE.BeginCodeTools then exit;
 | |
|   // check context at cursor
 | |
|   SrcEdit:=SourceEditorManagerIntf.ActiveEditor;
 | |
|   if SrcEdit=nil then begin
 | |
|     ErrorNotInWithVar;
 | |
|     exit;
 | |
|   end;
 | |
|   if not CodeToolBoss.RemoveWithBlock(SrcEdit.CodeToolsBuffer as TCodeBuffer,
 | |
|     SrcEdit.CursorTextXY.X,SrcEdit.CursorTextXY.Y)
 | |
|   then begin
 | |
|     // syntax error or not in a class
 | |
|     if CodeToolBoss.ErrorMessage<>'' then
 | |
|       LazarusIDE.DoJumpToCodeToolBossError
 | |
|     else
 | |
|       ErrorNotInWithVar;
 | |
|     exit;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure InsertFileAtCursor(Sender: TObject);
 | |
| var
 | |
|   OpenDialog: TOpenDialog;
 | |
|   Filter: String;
 | |
|   Filename: String;
 | |
|   Code: TCodeBuffer;
 | |
|   SrcEdit: TSourceEditorInterface;
 | |
| begin
 | |
|   SrcEdit:=SourceEditorManagerIntf.ActiveEditor;
 | |
|   if SrcEdit=nil then exit;
 | |
| 
 | |
|   OpenDialog:=TOpenDialog.Create(nil);
 | |
|   Code:=nil;
 | |
|   try
 | |
|     InitIDEFileDialog(OpenDialog);
 | |
|     OpenDialog.Title:=crsCUSelectFileToInsertAtCursor;
 | |
|     OpenDialog.Options:=OpenDialog.Options+[ofFileMustExist];
 | |
|     Filter:=crsCUPascalPasPpPasPp;
 | |
|     Filter:=Format(crsCUAllFiles, [Filter, FileMask, FileMask]);
 | |
|     OpenDialog.Filter:=Filter;
 | |
|     if not OpenDialog.Execute then exit;
 | |
|     Filename:=OpenDialog.FileName;
 | |
|     if not FileIsText(Filename) then begin
 | |
|       if IDEMessageDialog(crsCUWarning, crsCUTheFileSeemsToBeABinaryProceed,
 | |
|         mtConfirmation,[mbOk,mbCancel])<>mrOK then exit;
 | |
|     end;
 | |
|     Code:=TCodeBuffer.Create;
 | |
|     Code.Filename:=Filename;
 | |
|     Code.OnDecodeLoaded:=@Cody.DecodeLoaded;
 | |
|     if not Code.LoadFromFile(Filename) then begin
 | |
|       IDEMessageDialog(crsCWError, Format(crsCUUnableToLoadFile, [Filename, LineEnding
 | |
|         , Code.LastError]),
 | |
|         mtError,[mbCancel]);
 | |
|       exit;
 | |
|     end;
 | |
| 
 | |
|     SrcEdit.Selection:=Code.Source;
 | |
|   finally
 | |
|     OpenDialog.Free;
 | |
|     Code.Free;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure InsertCallInherited(Sender: TObject);
 | |
| 
 | |
|   procedure ErrorNotInMethod;
 | |
|   begin
 | |
|     IDEMessageDialog(crsCWError,
 | |
|       crsCUPleasePlaceTheCursorOfTheSourceEditorInAnImplement,
 | |
|       mtError,[mbCancel]);
 | |
|   end;
 | |
| 
 | |
| var
 | |
|   Handled: boolean;
 | |
|   Tool: TEventsCodeTool;
 | |
|   CleanPos: integer;
 | |
|   CursorNode: TCodeTreeNode;
 | |
|   ProcNode: TCodeTreeNode;
 | |
|   DeclNode: TCodeTreeNode;
 | |
|   NewCode: String;
 | |
|   SrcEdit: TSourceEditorInterface;
 | |
|   Indent: LongInt;
 | |
|   IndentContextSensitive: Boolean;
 | |
|   NewIndent: TFABIndentationPolicy;
 | |
|   NewLine: Boolean;
 | |
|   Gap: TGapTyp;
 | |
|   FromPos: Integer;
 | |
|   ToPos: Integer;
 | |
|   NewXY: TPoint;
 | |
| begin
 | |
|   if (ParseTilCursor(Tool,CleanPos,CursorNode,Handled,true)<>cupeSuccess)
 | |
|   and not Handled then begin
 | |
|     ErrorNotInMethod;
 | |
|     exit;
 | |
|   end;
 | |
|   SrcEdit:=SourceEditorManagerIntf.ActiveEditor;
 | |
|   try
 | |
|     try
 | |
|       ProcNode:=CursorNode.GetNodeOfType(ctnProcedure);
 | |
|       if not Tool.NodeIsMethodBody(ProcNode) then begin
 | |
|         debugln(['InsertCallInherited not in a method body']);
 | |
|         exit;
 | |
|       end;
 | |
|       // search the declaration (the header of the body may be incomplete)
 | |
|       DeclNode:=Tool.FindCorrespondingProcNode(ProcNode);
 | |
|       if DeclNode=nil then
 | |
|         DeclNode:=ProcNode;
 | |
|       Handled:=true;
 | |
|       NewCode:='inherited '+Tool.ExtractProcHead(DeclNode,
 | |
|         [phpWithoutClassName,phpWithParameterNames,phpWithoutParamTypes,
 | |
|          phpWithoutSemicolon]);
 | |
|       NewCode:=StringReplace(NewCode,';',',',[rfReplaceAll])+';';
 | |
|       //debugln(['InsertCallInherited NewCode="',NewCode,'"']);
 | |
|       NewLine:=true;
 | |
|       Gap:=gtNone;
 | |
|       if Tool.NodeIsFunction(DeclNode) then begin
 | |
|         if FindFirstNonSpaceCharInLine(Tool.Src,CleanPos)<CleanPos then begin
 | |
|           // insert function behind some code
 | |
|           // e.g. InheritedValue:=|
 | |
|           Indent:=0;
 | |
|           NewLine:=false;
 | |
|         end else begin
 | |
|           // store the old result value
 | |
|           NewCode:='Result:='+NewCode;
 | |
|         end;
 | |
|       end else
 | |
|         NewLine:=true; // procedures always on a separate line
 | |
|       FromPos:=CleanPos;
 | |
|       ToPos:=CleanPos;
 | |
| 
 | |
|       if NewLine then begin
 | |
|         // auto indent
 | |
|         Gap:=gtNewLine;
 | |
|         Indent:=SrcEdit.CursorScreenXY.X-1;
 | |
|         IndentContextSensitive:=true;
 | |
|         if CodeToolBoss.Indenter.GetIndent(Tool.Src,CleanPos,
 | |
|           Tool.Scanner.NestedComments,
 | |
|           true,NewIndent,IndentContextSensitive,NewCode)
 | |
|         and NewIndent.IndentValid then begin
 | |
|           Indent:=NewIndent.Indent;
 | |
|         end;
 | |
|         while (FromPos>1) and (Tool.Src[FromPos-1] in [' ',#9]) do
 | |
|           dec(FromPos);
 | |
|         NewCode:=Tool.Beautifier.GetIndentStr(Indent)+NewCode;
 | |
|         //debugln(['InsertCallInherited Indent=',Indent,' Line="',GetLineInSrc(Tool.Src,CleanPos),'"']);
 | |
|       end;
 | |
| 
 | |
|       NewCode:=CodeToolBoss.SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
 | |
|         NewCode,Indent,[bcfDoNotIndentFirstLine],GetPosInLine(Tool.Src,FromPos));
 | |
|       CodeToolBoss.SourceChangeCache.MainScanner:=Tool.Scanner;
 | |
|       // move editor cursor in front of insert position
 | |
|       NewXY:=Point(GetPosInLine(Tool.Src,FromPos)+1,SrcEdit.CursorTextXY.Y);
 | |
|       //debugln(['InsertCallInherited NewXY=',dbgs(NewXY),' FromPos=',Tool.CleanPosToStr(FromPos),' ToPos=',Tool.CleanPosToStr(ToPos)]);
 | |
|       if not CodeToolBoss.SourceChangeCache.Replace(Gap,Gap,FromPos,ToPos,NewCode)
 | |
|       then begin
 | |
|         debugln(['InsertCallInherited CodeToolBoss.SourceChangeCache.Replace failed']);
 | |
|         exit;
 | |
|       end;
 | |
|       SrcEdit.BeginUndoBlock{$IFDEF SynUndoDebugBeginEnd}('InsertCallInherited'){$ENDIF};
 | |
|       try
 | |
|         SrcEdit.CursorTextXY:=NewXY;
 | |
|         if not CodeToolBoss.SourceChangeCache.Apply then begin
 | |
|           debugln(['InsertCallInherited CodeToolBoss.SourceChangeCache.Apply failed']);
 | |
|           exit;
 | |
|         end;
 | |
|       finally
 | |
|         SrcEdit.EndUndoBlock{$IFDEF SynUndoDebugBeginEnd}('InsertCallInherited'){$ENDIF};
 | |
|       end;
 | |
|     except
 | |
|       on e: Exception do CodeToolBoss.HandleException(e);
 | |
|     end;
 | |
|   finally
 | |
|     // syntax error or not in a method
 | |
|     if not Handled then begin
 | |
|       if CodeToolBoss.ErrorMessage<>'' then
 | |
|         LazarusIDE.DoJumpToCodeToolBossError
 | |
|       else
 | |
|         ErrorNotInMethod;
 | |
|     end;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure InsertInt64ID(Sender: TObject);
 | |
| var
 | |
|   SrcEdit: TSourceEditorInterface;
 | |
| begin
 | |
|   SrcEdit:=SourceEditorManagerIntf.ActiveEditor;
 | |
|   if SrcEdit=nil then exit;
 | |
| 
 | |
|   SrcEdit.Selection:=FormatDateTime('YYYYMMDDhhnnss',Now);
 | |
| end;
 | |
| 
 | |
| function ParseTilCursor(out Tool: TCodeTool; out CleanPos: integer;
 | |
|   out Node: TCodeTreeNode; out ErrorHandled: boolean;
 | |
|   JumpToError: boolean; CodePos: PCodeXYPosition): TCUParseError;
 | |
| begin
 | |
|   Result:=ParseUnit(Tool,CleanPos,Node,ErrorHandled,JumpToError,CodePos,true);
 | |
| end;
 | |
| 
 | |
| function ParseUnit(out Tool: TCodeTool; out CleanPos: integer;
 | |
|   out Node: TCodeTreeNode; out ErrorHandled: boolean; JumpToError: boolean;
 | |
|   CodePos: PCodeXYPosition; TilCursor: boolean): TCUParseError;
 | |
| var
 | |
|   SrcEdit: TSourceEditorInterface;
 | |
|   CursorPos: TCodeXYPosition;
 | |
| begin
 | |
|   Tool:=nil;
 | |
|   CleanPos:=0;
 | |
|   Node:=nil;
 | |
|   ErrorHandled:=false;
 | |
|   if CodePos<>nil then CodePos^:=CleanCodeXYPosition;
 | |
|   SrcEdit:=SourceEditorManagerIntf.ActiveEditor;
 | |
|   if SrcEdit=nil then begin
 | |
|     if JumpToError then
 | |
|       IDEMessageDialog('No source editor','This function needs a Pascal source in the source editor.',mtError,[mbOk]);
 | |
|     debugln(['CodyUtils.ParseTilCursor: no source editor']);
 | |
|     exit(cupeNoSrcEditor);
 | |
|   end;
 | |
|   if not LazarusIDE.BeginCodeTools then exit;
 | |
| 
 | |
|   CursorPos.Code:=SrcEdit.CodeToolsBuffer as TCodeBuffer;
 | |
|   CursorPos.X:=SrcEdit.CursorTextXY.X;
 | |
|   CursorPos.Y:=SrcEdit.CursorTextXY.Y;
 | |
|   if CodePos<>nil then
 | |
|     CodePos^:=CursorPos;
 | |
|   try
 | |
|     if not CodeToolBoss.InitCurCodeTool(CursorPos.Code) then
 | |
|       exit(cupeMainCodeNotFound);
 | |
|     try
 | |
|       Tool:=CodeToolBoss.CurCodeTool;
 | |
|       Result:=cupeParseError;
 | |
|       //Range:=trTillRange;
 | |
|       if TilCursor then
 | |
|         Tool.BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanPos,
 | |
|                                     [btSetIgnoreErrorPos])
 | |
|       else
 | |
|         Tool.BuildTreeAndGetCleanPos(trTillRange,lsrEnd,CursorPos,CleanPos,[]);
 | |
|       Node:=Tool.FindDeepestNodeAtPos(CleanPos,false);
 | |
|       if Node=nil then
 | |
|         exit(cupeCursorNotInCode);
 | |
|       Result:=cupeSuccess;
 | |
|     except
 | |
|       on e: Exception do CodeToolBoss.HandleException(e);
 | |
|     end;
 | |
|   finally
 | |
|     if (CodeToolBoss.ErrorMessage<>'') and JumpToError then begin
 | |
|       ErrorHandled:=true;
 | |
|       LazarusIDE.DoJumpToCodeToolBossError;
 | |
|     end;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure OpenCodyHelp(Path: string);
 | |
| var
 | |
|   BasePath: String;
 | |
| begin
 | |
|   BasePath:='http://wiki.lazarus.freepascal.org/Cody';
 | |
|   OpenURL(BasePath+Path);
 | |
| end;
 | |
| 
 | |
| function GetPatternValue1(const Pattern, PlaceHolder, Src: string; out
 | |
|   Value1: string): boolean;
 | |
| { Pattern: 'Descendant of %1'
 | |
|   PlaceHolder: '%1'
 | |
|   Src:     'Descendant of TWinControl'
 | |
|   Value1:  'TWinControl'
 | |
| }
 | |
| var
 | |
|   PatLen, SrcLen, PHLen, l: Integer;
 | |
|   p: SizeInt;
 | |
| begin
 | |
|   Value1:='';
 | |
|   Result:=false;
 | |
|   PatLen:=length(Pattern);
 | |
|   PHLen:=length(PlaceHolder);
 | |
|   SrcLen:=length(Src);
 | |
|   if SrcLen<PatLen-PHLen then exit;
 | |
|   p:=Pos(PlaceHolder,Pattern);
 | |
|   if p<1 then exit;
 | |
|   // check start pattern
 | |
|   if (p>1) and (not CompareMem(Pointer(Src),Pointer(Pattern),p-1)) then exit;
 | |
|   // check end pattern
 | |
|   l:=PatLen-p-PHLen;
 | |
|   if (l>0)
 | |
|   and (not CompareMem(Pointer(Src)+SrcLen-l,Pointer(Pattern)+p+PHLen,l)) then exit;
 | |
|   Value1:=copy(Src,p,SrcLen-PatLen+PHLen);
 | |
|   Result:=true;
 | |
| end;
 | |
| 
 | |
| { TCodyClipboardSrcData }
 | |
| 
 | |
| procedure TCodyClipboardSrcData.SetSourcePos(const SrcPos: TCodeXYPosition);
 | |
| begin
 | |
|   SourceFilename:=SrcPos.Code.Filename;
 | |
|   SourceX:=SrcPos.X;
 | |
|   SourceY:=SrcPos.Y;
 | |
| end;
 | |
| 
 | |
| procedure TCodyClipboardSrcData.WriteToStream(MemStream: TMemoryStream);
 | |
| begin
 | |
|   WriteString(MemStream,SourceFilename);
 | |
|   WriteLRSInteger(MemStream,SourceY);
 | |
|   WriteLRSInteger(MemStream,SourceX);
 | |
| end;
 | |
| 
 | |
| procedure TCodyClipboardSrcData.ReadFromStream(MemStream: TMemoryStream);
 | |
| begin
 | |
|   SourceFilename:=ReadString(MemStream);
 | |
|   SourceY:=ReadLRSInteger(MemStream);
 | |
|   SourceX:=ReadLRSInteger(MemStream);
 | |
| end;
 | |
| 
 | |
| { TCodyClipboardData }
 | |
| 
 | |
| procedure TCodyClipboardData.WriteString(MemStream: TMemoryStream;
 | |
|   const s: string);
 | |
| var
 | |
|   b: byte;
 | |
|   l: Integer;
 | |
| begin
 | |
|   if length(s)<255 then begin
 | |
|     b:=length(s);
 | |
|     MemStream.Write(b,1);
 | |
|     if b>0 then
 | |
|       MemStream.Write(s[1],b);
 | |
|   end else begin
 | |
|     b:=255;
 | |
|     MemStream.Write(b,1);
 | |
|     l:=length(s);
 | |
|     WriteLRSInteger(MemStream,l);
 | |
|     MemStream.Write(s[1],l);
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TCodyClipboardData.ReadString(MemStream: TMemoryStream): string;
 | |
| var
 | |
|   b: byte;
 | |
|   l: integer;
 | |
| begin
 | |
|   Result:='';
 | |
|   b:=0;
 | |
|   if MemStream.Read(b,1)<>1 then exit;
 | |
|   if b<255 then begin
 | |
|     SetLength(Result,b);
 | |
|     if Result<>'' then
 | |
|       MemStream.Read(Result[1],b);
 | |
|   end else begin
 | |
|     l:=ReadLRSInteger(MemStream);
 | |
|     if l<=0 then exit;
 | |
|     SetLength(Result,l);
 | |
|     MemStream.Read(Result[1],l);
 | |
|   end;
 | |
|   //debugln(['TCodyClipboardData.ReadString Result="',Result,'"']);
 | |
| end;
 | |
| 
 | |
| procedure TCodyClipboardData.Execute(SrcEdit: TSourceEditorInterface;
 | |
|   LogXY: TPoint);
 | |
| begin
 | |
|   raise Exception.Create('not implemented yet: '+ClassName+'.Execute');
 | |
| end;
 | |
| 
 | |
| { TCody }
 | |
| 
 | |
| function TCody.GetClipboardFormats(Index: integer): TCodyClipboardFormat;
 | |
| begin
 | |
|   Result:=TCodyClipboardFormat(FClipboardFormats[Index]);
 | |
| end;
 | |
| 
 | |
| constructor TCody.Create;
 | |
| begin
 | |
|   FClipboardFormats:=TFPList.Create;
 | |
| end;
 | |
| 
 | |
| destructor TCody.Destroy;
 | |
| begin
 | |
|   FreeAndNil(FClipboardFormats);
 | |
|   inherited Destroy;
 | |
| end;
 | |
| 
 | |
| procedure TCody.DecodeLoaded(Sender: TSourceLog; const Filename: string;
 | |
|   var Source, DiskEncoding, MemEncoding: string);
 | |
| begin
 | |
|   //debugln(['TCody.DecodeLoaded ',Filename]);
 | |
|   if (Sender is TCodeBuffer)
 | |
|   and Assigned(CodeToolBoss.SourceCache.OnDecodeLoaded) then
 | |
|     CodeToolBoss.SourceCache.OnDecodeLoaded(TCodeBuffer(Sender),Filename,
 | |
|       Source,DiskEncoding,MemEncoding);
 | |
| end;
 | |
| 
 | |
| class function TCody.ClipboardFormatId: TClipboardFormat;
 | |
| const
 | |
|   CodyClipboardMimeType = 'Application/X-Laz-Cody';
 | |
| var
 | |
|   ID: TClipboardFormat = 0;
 | |
| begin
 | |
|   if ID = 0 then
 | |
|     ID := ClipboardRegisterFormat(CodyClipboardMimeType);
 | |
|   Result := ID;
 | |
| end;
 | |
| 
 | |
| function TCody.CanReadFromClipboard(AClipboard: TClipboard): Boolean;
 | |
| begin
 | |
|   Result := AClipboard.HasFormat(ClipboardFormatId);
 | |
| end;
 | |
| 
 | |
| function TCody.ReadFromClipboard(AClipboard: TClipboard;
 | |
|   SrcEdit: TSourceEditorInterface; LogXY: TPoint; AText: string): boolean;
 | |
| 
 | |
|   procedure InvalidStream;
 | |
|   begin
 | |
|     raise Exception.Create('The Cody clipboard data is invalid');
 | |
|   end;
 | |
| 
 | |
| var
 | |
|   MemStream: TMemoryStream;
 | |
|   ID: ShortString;
 | |
|   aFormat: TCodyClipboardFormat;
 | |
|   Data: TCodyClipboardData;
 | |
| begin
 | |
|   Result:=false;
 | |
|   if not AClipboard.HasFormat(ClipboardFormatId) then exit;
 | |
|   Result:=true;
 | |
|   MemStream:=TMemoryStream.Create;
 | |
|   Data:=nil;
 | |
|   try
 | |
|     Result:=AClipboard.GetFormat(ClipboardFormatId,MemStream);
 | |
|     ID:='';
 | |
|     MemStream.Position:=0;
 | |
|     if MemStream.Read(ID[0],1)<>1 then
 | |
|       InvalidStream;
 | |
|     if MemStream.Read(ID[1],ord(ID[0]))<>ord(ID[0]) then
 | |
|       InvalidStream;
 | |
|     aFormat:=FindClipboardFormat(ID);
 | |
|     if aFormat=nil then
 | |
|       InvalidStream;
 | |
|     Data:=aFormat.Create;
 | |
|     Data.AsText:=AText;
 | |
|     Data.ReadFromStream(MemStream);
 | |
|     Data.Execute(SrcEdit,LogXY);
 | |
|   finally
 | |
|     Data.Free;
 | |
|     MemStream.Free;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TCody.WriteToClipboard(Data: TCodyClipboardData; AClipboard: TClipboard
 | |
|   ): Boolean;
 | |
| var
 | |
|   MemStream: TMemoryStream;
 | |
|   ID: ShortString;
 | |
| begin
 | |
|   if AClipboard=nil then AClipboard:=Clipboard;
 | |
|   AClipboard.AsText:=Data.AsText;
 | |
|   if not AClipboard.HasFormat(CF_TEXT) then
 | |
|     raise Exception.Create('Write to clipboard failed');
 | |
|   MemStream:=TMemoryStream.Create;
 | |
|   try
 | |
|     ID:=Data.ClassName;
 | |
|     MemStream.Write(ID[0],length(ID)+1);
 | |
|     Data.WriteToStream(MemStream);
 | |
|     MemStream.Position:=0;
 | |
|     Result:=AClipboard.AddFormat(ClipboardFormatId,MemStream);
 | |
|   finally
 | |
|     MemStream.Free;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TCody.RegisterClipboardFormat(ccFormat: TCodyClipboardFormat);
 | |
| begin
 | |
|   if FindClipboardFormat(ccFormat.ClassName)<>nil then
 | |
|     raise Exception.Create('cody clipboard format "'+ccFormat.ClassName+'" is already registered');
 | |
|   FClipboardFormats.Add(ccFormat);
 | |
| end;
 | |
| 
 | |
| function TCody.FindClipboardFormat(aName: string): TCodyClipboardFormat;
 | |
| var
 | |
|   i: Integer;
 | |
| begin
 | |
|   for i:=0 to ClipboardFormatCount-1 do begin
 | |
|     Result:=ClipboardFormats[i];
 | |
|     if SysUtils.CompareText(Result.ClassName,aName)=0 then exit;
 | |
|   end;
 | |
|   Result:=nil;
 | |
| end;
 | |
| 
 | |
| function TCody.ClipboardFormatCount: integer;
 | |
| begin
 | |
|   Result:=FClipboardFormats.Count;
 | |
| end;
 | |
| 
 | |
| procedure TCody.SrcEditCopyPaste(SrcEdit: TSourceEditorInterface;
 | |
|   var AText: String; var AMode: TSemSelectionMode; ALogStartPos: TPoint;
 | |
|   var AnAction: TSemCopyPasteAction);
 | |
| var
 | |
|   AClipBoard: TClipboard;
 | |
| begin
 | |
|   // ToDo: use the right clipboard
 | |
|   AClipBoard:=Clipboard;
 | |
|   try
 | |
|     if not ReadFromClipboard(AClipBoard,SrcEdit,ALogStartPos,AText) then exit;
 | |
|   except
 | |
|     on E: Exception do begin
 | |
|       IDEMessageDialog('Error','Unable to paste Cody data.'+LineEnding+E.Message,
 | |
|         mtError,[mbCancel]);
 | |
|     end;
 | |
|   end;
 | |
|   AnAction:=semcaAbort;
 | |
| end;
 | |
| 
 | |
| initialization
 | |
|   Cody:=TCody.Create;
 | |
| finalization
 | |
|   FreeAndNil(Cody);
 | |
| 
 | |
| end.
 | |
| 
 | 
