lazarus/components/macroscript/emscriptmacro.pas
2012-08-27 13:00:37 +00:00

388 lines
8.2 KiB
ObjectPascal

unit EMScriptMacro;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, SrcEditorIntf, Controls, SynEdit, SynEditKeyCmds, EMScriptClasses,
Laz2_XMLCfg, LazLoggerBase, uPSCompiler, uPSRuntime, uPSUtils, uPSC_std, uPSR_std;
type
{ TEMSEditorMacro }
TEMSEditorMacro = class(TEditorMacro)
private
FMacroName: String;
FSource: String;
FState: TEditorMacroState;
FHasError: Boolean;
FErrorMsg: String;
FKeyBinding: TEditorMacroKeyBinding;
protected
function GetMacroName: String; override;
procedure SetMacroName(AValue: string); override;
function GetState: TEditorMacroState; override;
function GetErrorMsg: String; override;
function GetKeyBinding: TEditorMacroKeyBinding; override;
procedure DoRecordMacro(aEditor: TWinControl); override;
procedure DoPlaybackMacro(aEditor: TWinControl); override;
procedure DoStop; override;
procedure DoPause; override;
procedure DoResume; override;
procedure Compile;
procedure FixBeginEnd;
public
constructor Create(aOwner: TComponent); override;
destructor Destroy; override;
procedure AssignEventsFrom(AMacroRecorder: TEditorMacro); override;
procedure Clear; override;
function GetAsSource: String; override;
procedure SetFromSource(const AText: String); override;
procedure WriteToXmlConf(AConf: TXMLConfig; const APath: String); override;
procedure ReadFromXmlConf(AConf: TXMLConfig; const APath: String); override;
function IsEmpty: Boolean; override;
function IsInvalid: Boolean; override;
function IsRecording(AnEditor: TWinControl): Boolean; override;
end;
{ TEMSTPSExec }
TEMSTPSExec = class(TPSExec)
public
SynEdit: TCustomSynEdit;
procedure AddECFuncToExecEnum(const s: String);
end;
{ TEMSPSPascalCompiler }
TEMSPSPascalCompiler = class(TPSPascalCompiler)
public
procedure AddECFuncToCompEnum(const s: String);
end;
implementation
var
TheCompiler: TEMSPSPascalCompiler;
TheExec: TEMSTPSExec;
TheCLassImp: TPSRuntimeClassImporter;
function HandleEcCommandFoo(Caller: TPSExec; p: TPSExternalProcRec; Global, Stack: TPSStack): Boolean;
var
i: integer;
pt: TPoint;
e: TEMSTPSExec;
begin
i := PtrUint(p.Ext2);
e := TEMSTPSExec(p.Ext1);
case i of
ecGotoXY, ecSelGotoXY:
begin
pt.x := Stack.GetInt(-1);
pt.y := Stack.GetInt(-2);
e.SynEdit.ExecuteCommand(i, '', @pt);
end;
ecChar:
e.SynEdit.ExecuteCommand(i, Stack.GetAnsiString(-1), nil);
else
e.SynEdit.ExecuteCommand(i, '', nil);
end;
Result := True;
end;
function HandleGetCaller(Caller: TPSExec; p: TPSExternalProcRec; Global, Stack: TPSStack): Boolean;
var
e: TEMSTPSExec;
begin
e := TEMSTPSExec(p.Ext1);
Stack.SetClass(-1, e.SynEdit);
end;
function CompilerOnUses(Sender: TPSPascalCompiler; const Name: TbtString): Boolean;
begin
if Name = 'SYSTEM' then
begin
SIRegisterTObject(Sender);
//SIRegister_Std(Sender);
if Sender is TEMSPSPascalCompiler then
GetEditorCommandValues(@TEMSPSPascalCompiler(Sender).AddECFuncToCompEnum);
CompRegisterTSynEdit(TheCompiler);
Sender.AddFunction('function Caller: TSynEdit;');
Result := True;
end else
Result := False;
end;
procedure AddECFuncToExec;
begin
GetEditorCommandValues(@TheExec.AddECFuncToExecEnum);
ExecRegisterTSynEdit(TheCLassImp);
TheExec.RegisterFunctionName('CALLER', @HandleGetCaller, TheExec, nil);
end;
{ Create global objects }
procedure CreateCompiler;
begin
TheCompiler := TEMSPSPascalCompiler.Create;
TheCompiler.OnUses := @CompilerOnUses;
end;
procedure CreateExec;
begin
TheExec := TEMSTPSExec.Create;
TheCLassImp := TPSRuntimeClassImporter.Create;
RIRegisterTObject(TheCLassImp);
// ## RIRegister_Std(CL);
AddECFuncToExec;
RegisterClassLibraryRuntime(TheExec, TheCLassImp);
end;
{ TEMSPSPascalCompiler }
procedure TEMSPSPascalCompiler.AddECFuncToCompEnum(const s: String);
begin
if (s = 'ecSynMacroPlay') or (s = 'ecSynMacroRecord') then exit;
if (s = 'ecGotoXY') or (s = 'ecSelGotoXY') then
AddFunction('procedure '+s+'(X, Y: Integer);')
else
if (s = 'ecChar') then
AddFunction('procedure '+s+'(s: string);')
// ecString
else
AddFunction('procedure '+s+';');
end;
{ TEMSTPSExec }
procedure TEMSTPSExec.AddECFuncToExecEnum(const s: String);
var
i: longint;
begin
i := 0;
if not IdentToEditorCommand(s, i) then exit;
TheExec.RegisterFunctionName(UpperCase(s), @HandleEcCommandFoo, self, Pointer(PtrUInt(i)));
end;
{ TEMSEditorMacro }
function TEMSEditorMacro.GetMacroName: String;
begin
Result := FMacroName;
end;
procedure TEMSEditorMacro.SetMacroName(AValue: string);
begin
FMacroName := AValue;
DoChanged;
end;
function TEMSEditorMacro.GetState: TEditorMacroState;
begin
Result := FState;
end;
function TEMSEditorMacro.GetErrorMsg: String;
begin
Result := FErrorMsg;
end;
function TEMSEditorMacro.GetKeyBinding: TEditorMacroKeyBinding;
begin
if FKeyBinding = nil then
FKeyBinding := GetDefaultKeyBinding;
Result := FKeyBinding;
end;
procedure TEMSEditorMacro.DoRecordMacro(aEditor: TWinControl);
begin
// Not supported
end;
procedure TEMSEditorMacro.DoPlaybackMacro(aEditor: TWinControl);
var
s: tbtString;
begin
if IsEmpty or IsInvalid then exit;
FState := emPlaying;
if Assigned(OnStateChange) then
OnStateChange(Self);
try
Compile;
if IsInvalid then exit;
TheCompiler.GetOutput(s);
if not TheExec.LoadData(s) then // Load the data from the Data string.
exit;
TheExec.SynEdit := aEditor as TCustomSynEdit;
TheExec.RunScript
finally
FState := emStopped;
if Assigned(OnStateChange) then
OnStateChange(Self);
end;
end;
procedure TEMSEditorMacro.DoStop;
begin
end;
procedure TEMSEditorMacro.DoPause;
begin
// Not supported
end;
procedure TEMSEditorMacro.DoResume;
begin
// Not supported
end;
procedure TEMSEditorMacro.Compile;
var
i: Integer;
begin
FHasError := False;
FErrorMsg := '';
TheCompiler.Clear;
if not TheCompiler.Compile(FSource) then
begin
for i := 0 to TheCompiler.MsgCount -1 do
FErrorMsg := FErrorMsg + TheCompiler.Msg[i].MessageToString + LineEnding;
FHasError := True;
end;
end;
procedure TEMSEditorMacro.FixBeginEnd;
begin
if (not IsEmpty) and (not IsInvalid) then
begin
if (pos('begin', FSource) < 1) or (pos('end.', FSource) < 1) then
FSource := 'begin' + LineEnding + FSource + 'end.'+LineEnding;
end;
end;
constructor TEMSEditorMacro.Create(aOwner: TComponent);
begin
FState := emStopped;
end;
destructor TEMSEditorMacro.Destroy;
begin
inherited Destroy;
FreeAndNil(FKeyBinding);
end;
procedure TEMSEditorMacro.AssignEventsFrom(AMacroRecorder: TEditorMacro);
begin
FHasError := False;
if AMacroRecorder = nil then
Clear
else
FSource := AMacroRecorder.GetAsSource;
if not(AMacroRecorder is TEMSEditorMacro) then
FixBeginEnd;
Compile;
DoChanged;
end;
procedure TEMSEditorMacro.Clear;
begin
FSource := '';
FHasError := False;
DoChanged;
end;
function TEMSEditorMacro.GetAsSource: String;
begin
Result := FSource;
end;
procedure TEMSEditorMacro.SetFromSource(const AText: String);
begin
FSource := AText;
Compile;
DoChanged;
end;
procedure TEMSEditorMacro.WriteToXmlConf(AConf: TXMLConfig; const APath: String);
begin
AConf.SetValue(APath + 'Name', MacroName);
AConf.SetValue(APath + 'Code/Value', GetAsSource);
if (KeyBinding <> nil) then
KeyBinding.WriteToXmlConf(AConf, APath);
end;
procedure TEMSEditorMacro.ReadFromXmlConf(AConf: TXMLConfig; const APath: String);
var
s: String;
begin
s := AConf.GetValue(APath + 'Code/Value', '');
FSource := s;
FixBeginEnd;
Compile;
s := AConf.GetValue(APath + 'Name', '');
if s <> '' then MacroName := s;
if (not FHasError) and (IsEmpty) then begin
FHasError := True;
FErrorMsg := s;
end;
if (KeyBinding <> nil) then
KeyBinding.ReadFromXmlConf(AConf, APath);
DoChanged;
end;
function TEMSEditorMacro.IsEmpty: Boolean;
begin
Result := FSource = '';
end;
function TEMSEditorMacro.IsInvalid: Boolean;
begin
Result := FHasError;
end;
function TEMSEditorMacro.IsRecording(AnEditor: TWinControl): Boolean;
begin
Result := False;
end;
initialization
CreateCompiler;
CreateExec;
finalization
FreeAndNil(TheExec);
FreeAndNil(TheCLassImp);
FreeAndNil(TheCompiler);
end.