Show frmPythonII.pas syntax highlighted
{-----------------------------------------------------------------------------
Unit Name: frmPythonII
Author: Kiriakos Vlahos
Date: 20-Jan-2005
Purpose: Python Interactive Interperter using Python for Delphi and Synedit
Features: Syntax Highlighting
Brace Highlighting
Command History
- Alt-UP : previous command
- Alt-Down : next command
- Esc : clear command
Code Completion
Call Tips
History:
-----------------------------------------------------------------------------}
unit frmPythonII;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs , Menus, PythonEngine, SyncObjs, SynHighlighterPython,
SynEditHighlighter, SynEdit,
SynEditKeyCmds, SynCompletionProposal, JvComponent, JvDockControlForm,
frmIDEDockWin, ExtCtrls, TBX, TBXThemes, PythonGUIInputOutput, JvComponentBase,
SynUnicode, TB2Item, ActnList;
const
WM_APPENDTEXT = WM_USER + 1020;
type
TPythonIIForm = class(TIDEDockWindow)
SynEdit: TSynEdit;
PythonEngine: TPythonEngine;
PythonIO: TPythonInputOutput;
SynCodeCompletion: TSynCompletionProposal;
DebugIDE: TPythonModule;
SynParamCompletion: TSynCompletionProposal;
InterpreterPopUp: TTBXPopupMenu;
InterpreterActionList: TActionList;
actCleanUpNameSpace: TAction;
actCleanUpSysModules: TAction;
TBXItem1: TTBXItem;
TBXItem2: TTBXItem;
TBXSeparatorItem1: TTBXSeparatorItem;
TBXItem3: TTBXItem;
actCopyHistory: TAction;
TBXSeparatorItem2: TTBXSeparatorItem;
TBXItem4: TTBXItem;
procedure testResultAddError(Sender: TObject; PSelf, Args: PPyObject;
var Result: PPyObject);
procedure testResultAddFailure(Sender: TObject; PSelf, Args: PPyObject;
var Result: PPyObject);
procedure testResultAddSuccess(Sender: TObject; PSelf, Args: PPyObject;
var Result: PPyObject);
procedure testResultStopTestExecute(Sender: TObject; PSelf, Args: PPyObject;
var Result: PPyObject);
procedure testResultStartTestExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
procedure MaskFPUExceptionsExecute(Sender: TObject; PSelf, Args: PPyObject;
var Result: PPyObject);
procedure UnMaskFPUExceptionsExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
procedure Get8087CWExecute(Sender: TObject; PSelf, Args: PPyObject;
var Result: PPyObject);
procedure SynEditReplaceText(Sender: TObject; const ASearch,
AReplace: WideString; Line, Column: Integer; var Action: TSynReplaceAction);
procedure SynEditPaintTransient(Sender: TObject; Canvas: TCanvas;
TransientType: TTransientType);
procedure FormCreate(Sender: TObject);
procedure SynEditProcessCommand(Sender: TObject;
var Command: TSynEditorCommand; var AChar: WideChar; Data: Pointer);
procedure SynEditProcessUserCommand(Sender: TObject;
var Command: TSynEditorCommand; var AChar: WideChar; Data: Pointer);
procedure SynCodeCompletionExecute(Kind: SynCompletionType;
Sender: TObject; var CurrentInput: WideString; var x, y: Integer;
var CanExecute: Boolean);
function FormHelp(Command: Word; Data: Integer;
var CallHelp: Boolean): Boolean;
procedure InputBoxExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
procedure FormActivate(Sender: TObject);
procedure StatusWriteExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
procedure MessageWriteExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
procedure FormDestroy(Sender: TObject);
procedure SynParamCompletionExecute(Kind: SynCompletionType;
Sender: TObject; var CurrentInput: WideString; var x, y: Integer;
var CanExecute: Boolean);
procedure SynEditCommandProcessed(Sender: TObject;
var Command: TSynEditorCommand; var AChar: WideChar; Data: Pointer);
procedure actCleanUpNameSpaceExecute(Sender: TObject);
procedure InterpreterPopUpPopup(Sender: TObject);
procedure actCleanUpSysModulesExecute(Sender: TObject);
procedure actCopyHistoryExecute(Sender: TObject);
procedure SynEditDblClick(Sender: TObject);
private
{ Private declarations }
fCommandHistory : TWideStringList;
fCommandHistorySize : integer;
fCommandHistoryPointer : integer;
fCommandHistoryPrefix : WideString;
fShowOutput : Boolean;
FCriticalSection : TCriticalSection;
fOutputStream : TMemoryStream;
fCloseBracketChar: WideChar;
procedure GetBlockBoundary(LineN: integer; var StartLineN,
EndLineN: integer; var IsCode: Boolean);
function GetPromptPrefix(line: string): string;
procedure SetCommandHistorySize(const Value: integer);
procedure GetBlockCode(var Source: WideString;
var Buffer: array of WideString; EndLineN: Integer; StartLineN: Integer);
procedure LoadPythonEngine;
protected
procedure PythonIOSendData(Sender: TObject; const Data: WideString);
procedure PythonIOReceiveData(Sender: TObject; var Data: WideString);
procedure TBMThemeChange(var Message: TMessage); message TBM_THEMECHANGE;
procedure WMAPPENDTEXT(var Message: TMessage); message WM_APPENDTEXT;
public
{ Public declarations }
PS1, PS2 : WideString;
PythonHelpFile : string;
function OutputSuppressor : IInterface;
procedure AppendText(S: WideString);
procedure AppendToPrompt(const Buffer : array of WideString);
function IsEmpty : Boolean;
function CanFind: boolean;
function CanFindNext: boolean;
function CanReplace: boolean;
procedure ExecFind;
procedure ExecFindNext;
procedure ExecFindPrev;
procedure ExecReplace;
procedure RegisterHistoryCommands;
property ShowOutput : boolean read fShowOutput write fShowOutput;
property CommandHistorySize : integer read fCommandHistorySize write SetCommandHistorySize;
end;
var
PythonIIForm: TPythonIIForm;
implementation
Uses
SynEditTypes, Math, frmPyIDEMain, dmCommands, VarPyth, Registry,
frmMessages, uCommonFunctions, JclStrings, frmVariables, StringResources,
dlgConfirmReplace, frmUnitTests, JvDockGlobals, SynRegExpr, cPyBaseDebugger,
cPyDebugger;
{$R *.dfm}
{ Class TSuppressOuptput modelled after JVCL.WaitCursor}
type
TSuppressOutput = class(TInterfacedObject, IInterface)
private
fPythonIIForm : TPythonIIForm;
OldShowOutput : Boolean;
public
constructor Create(PythonIIForm : TPythonIIForm);
destructor Destroy; override;
end;
constructor TSuppressOutput.Create(PythonIIForm : TPythonIIForm);
begin
inherited Create;
fPythonIIForm := PythonIIForm;
if Assigned(fPythonIIForm) then begin
OldShowOutput := PythonIIForm.ShowOutput;
PythonIIForm.ShowOutput := False;
end;
end;
destructor TSuppressOutput.Destroy;
begin
if Assigned(fPythonIIForm) then
fPythonIIForm.ShowOutput := OldShowOutput;
inherited Destroy;
end;
{ PythonIIForm }
function TPythonIIForm.OutputSuppressor: IInterface;
begin
Result := TSuppressOutput.Create(Self);
end;
procedure TPythonIIForm.SynEditPaintTransient(Sender: TObject; Canvas: TCanvas;
TransientType: TTransientType);
begin
if (not Assigned(SynEdit.Highlighter)) then
Exit;
CommandsDataModule.PaintMatchingBrackets(Canvas, SynEdit, TransientType);
end;
procedure TPythonIIForm.PythonIOReceiveData(Sender: TObject;
var Data: WideString);
Var
S : string;
begin
S := Data;
if not InputQuery('PyScripter - Input requested', 'Input:', S) then
with GetPythonEngine do
PyErr_SetString(PyExc_KeyboardInterrupt^, 'Operation cancelled')
else
Data := S + #10;
end;
procedure TPythonIIForm.PythonIOSendData(Sender: TObject; const Data: WideString);
Var
WS : WideString;
begin
if fShowOutput then begin
fCriticalSection.Acquire;
try
fOutputStream.Write(Data[1], Length (Data) * 2);
//fOutputStream.Write(WideLineBreak[1], Length (WideLineBreak) * 2); RawOutput
if GetCurrentThreadId = MainThreadId then begin
SetLength(WS, fOutputStream.Size div 2);
fOutputStream.Position := 0;
fOutputStream.Read(WS[1], Length(WS)*2);
AppendText(WS);
fOutputStream.Size := 0;
end else begin
PostMessage(Handle, WM_APPENDTEXT, 0, 0);
end;
finally
fCriticalSection.Release;
end;
end;
end;
procedure TPythonIIForm.actCleanUpNameSpaceExecute(Sender: TObject);
begin
CommandsDataModule.PyIDEOptions.CleanupMainDict := (Sender as TAction).Checked;
InternalInterpreter.Debugger.CleanupMaindict := CommandsDataModule.PyIDEOptions.CleanupMainDict;
end;
procedure TPythonIIForm.actCleanUpSysModulesExecute(Sender: TObject);
begin
CommandsDataModule.PyIDEOptions.CleanupSysModules := (Sender as TAction).Checked;
InternalInterpreter.Debugger.CleanupSysModules := CommandsDataModule.PyIDEOptions.CleanupSysModules;
end;
procedure TPythonIIForm.actCopyHistoryExecute(Sender: TObject);
begin
SetClipboardText(fCommandHistory.Text);
end;
procedure TPythonIIForm.AppendText(S: WideString);
begin
SynEdit.ExecuteCommand(ecEditorBottom, ' ', nil);
SynEdit.SelText := S;
SynEdit.ExecuteCommand(ecEditorBottom, ' ', nil);
SynEdit.EnsureCursorPosVisible;
end;
procedure TPythonIIForm.AppendToPrompt(const Buffer: array of WideString);
Var
LineCount, i : integer;
Line : WideString;
begin
LineCount := SynEdit.Lines.Count;
Line := SynEdit.Lines[LineCount-1];
SynEdit.BeginUpdate;
try
if Line <> PS1 then begin
if Line <> '' then AppendText(WideLineBreak);
AppendText(PS1);
end;
for i := Low(Buffer) to High(Buffer) - 1 do
AppendText(Buffer[i] + WideLineBreak + PS2);
if Length(Buffer) > 0 then AppendText(Buffer[High(Buffer)]);
finally
SynEdit.EndUpdate;
end;
end;
procedure TPythonIIForm.FormCreate(Sender: TObject);
Var
S : string;
Registry : TRegistry;
RegKey : string;
II : Variant; // wrapping sys and code modules
begin
inherited;
SynEdit.ControlStyle := SynEdit.ControlStyle + [csOpaque];
SynEdit.Highlighter := TSynPythonInterpreterSyn.Create(Self);
SynEdit.Highlighter.Assign(CommandsDataModule.SynPythonSyn);
SynEdit.Assign(CommandsDataModule.InterpreterEditorOptions);
RegisterHistoryCommands;
// IO
PythonIO.OnSendUniData := PythonIOSendData;
PythonIO.OnReceiveUniData := PythonIOReceiveData;
PythonIO.UnicodeIO := True;
PythonIO.RawOutput := True;
LoadPythonEngine;
fShowOutput := True;
// For handling output from Python threads
FCriticalSection := TCriticalSection.Create;
fOutputStream := TMemoryStream.Create;
// For recalling old commands in Interactive Window;
fCommandHistory := TWideStringList.Create();
fCommandHistorySize := 20;
fCommandHistoryPointer := 0;
// Get Python vars and print banner
S := Format('*** Python %s on %s. ***'+sLineBreak,
[SysModule.version, SysModule.platform]);
AppendText(S);
PS1 := SysModule.ps1;
PS2 := SysModule.ps2;
AppendText(PS1);
// Python Help File
Registry := TRegistry.Create(KEY_READ);
try
Registry.RootKey := HKEY_LOCAL_MACHINE;
// False because we do not want to create it if it doesn't exist
RegKey := '\SOFTWARE\Python\PythonCore\'+SysModule.winver+
'\Help\Main Python Documentation';
if Registry.OpenKey(RegKey, False) then
PythonHelpFile := Registry.ReadString('')
else begin
// try Current User
Registry.RootKey := HKEY_CURRENT_USER;
if Registry.OpenKey(RegKey, False) then
PythonHelpFile := Registry.ReadString('')
end;
finally
Registry.Free;
end;
// Create internal Interpreter and Debugger
II := VarPythonEval('_II');
PythonEngine.ExecString('del _II');
InternalInterpreter := TPyInternalInterpreter.Create(II);
PyControl.ActiveInterpreter := InternalInterpreter;
PyControl.ActiveDebugger := TPyInternalDebugger.Create;
end;
procedure TPythonIIForm.FormDestroy(Sender: TObject);
begin
FreeAndNil(PyControl.ActiveDebugger);
if PyControl.ActiveInterpreter <> InternalInterpreter then
FreeAndNil(PyControl.ActiveInterpreter);
FreeAndNil(InternalInterpreter);
FreeAndNil(fCommandHistory);
FreeAndNil(FCriticalSection);
FreeAndNil(fOutputStream);
inherited;
end;
procedure TPythonIIForm.GetBlockBoundary(LineN: integer; var StartLineN,
EndLineN: integer; var IsCode: Boolean);
{-----------------------------------------------------------------------------
GetBlockBoundary takes a line number, and will return the
start and end line numbers of the block, and a flag indicating if the
block is a Python code block.
If the line specified has a Python prompt, then the lines are parsed
and forwards, and the IsCode is true.
If the line does not start with a prompt, the block is searched forward
and backward until a prompt _is_ found, and all lines in between without
prompts are returned, and the IsCode is false.
-----------------------------------------------------------------------------}
Var
Line, Prefix : string;
MaxLineNo : integer;
begin
Line := SynEdit.Lines[LineN];
MaxLineNo := SynEdit.Lines.Count - 1;
Prefix := GetPromptPrefix(line);
if Prefix = '' then begin
IsCode := False;
StartLineN := LineN;
while StartLineN > 0 do begin
if GetPromptPrefix(SynEdit.Lines[StartLineN-1]) <> '' then break;
Dec(StartLineN);
end;
EndLineN := LineN;
while EndLineN < MaxLineNo do begin
if GetPromptPrefix(SynEdit.Lines[EndLineN+1]) <> '' then break;
Inc(EndLineN);
end;
end else begin
IsCode := True;
StartLineN := LineN;
while (StartLineN > 0) and (Prefix <> PS1) do begin
Prefix := GetPromptPrefix(SynEdit.Lines[StartLineN-1]);
if Prefix = '' then break;
Dec(StartLineN);
end;
EndLineN := LineN;
while EndLineN < MaxLineNo do begin
Prefix := GetPromptPrefix(SynEdit.Lines[EndLineN+1]);
if (Prefix = PS1) or (Prefix = '') then break;
Inc(EndLineN);
end;
end;
end;
function TPythonIIForm.GetPromptPrefix(line: string): string;
begin
if Copy(line, 1, Length(PS1)) = PS1 then
Result := PS1
else if Copy(line, 1, Length(PS2)) = PS2 then
Result := PS2
else
Result := '';
end;
procedure TPythonIIForm.SynEditProcessCommand(Sender: TObject;
var Command: TSynEditorCommand; var AChar: WideChar; Data: Pointer);
Var
LineN, StartLineN, EndLineN, i, Position, Index : integer;
NeedIndent : boolean;
IsCode : Boolean;
Line, CurLine, Source, Indent : WideString;
EncodedSource : string;
Buffer : array of WideString;
NewCommand : TSynEditorCommand;
WChar : WideChar;
begin
case Command of
ecLineBreak :
begin
Command := ecNone; // do not processed it further
if SynParamCompletion.Form.Visible then
SynParamCompletion.CancelCompletion;
LineN := SynEdit.CaretY - 1; // Caret is 1 based
GetBlockBoundary(LineN, StartLineN, EndLineN, IsCode);
// If we are in a code-block, but it isnt at the end of the buffer
// then copy it to the end ready for editing and subsequent execution
if not IsCode then begin
SetLength(Buffer, 0);
AppendToPrompt(Buffer);
end else begin
SetLength(Buffer, EndLineN-StartLineN + 1);
GetBlockCode(Source, Buffer, EndLineN, StartLineN);
// If we are in a code-block, but it isnt at the end of the buffer
// then copy it to the end ready for editing and subsequent execution
if EndLineN <> SynEdit.Lines.Count - 1 then
AppendToPrompt(Buffer)
else if Trim(Source) = '' then begin
AppendText(WideLineBreak);
AppendText(PS1);
end else begin
SynEdit.ExecuteCommand(ecEditorBottom, ' ', nil);
AppendText(WideLineBreak);
//remove trailing tabs
for i := Length(Source) downto 1 do
if Source[i] = #9 then Delete(Source, i, 1)
else
break;
if CommandsDataModule.PyIDEOptions.UTF8inInterpreter then
EncodedSource := UTF8BOMString + Utf8Encode(Source)
else
EncodedSource := Source;
// RunSource
NeedIndent := False; // True denotes an incomplete statement
case PyControl.DebuggerState of
dsInactive :
NeedIndent :=
PyControl.ActiveInterpreter.RunSource(EncodedSource, '<interactive input>');
dsPaused :
NeedIndent :=
PyControl.ActiveDebugger.RunSource(EncodedSource, '<interactive input>');
else //dsRunning, dsRunningNoDebug
// it is dangerous to execute code while running scripts
// so just beep and do nothing
Beep();
end;
if not NeedIndent then begin
// The source code has been executed
// If the last line isnt empty, append a newline
SetLength(Buffer, 0);
AppendToPrompt(Buffer);
NeedIndent := False;
// Add the command executed to History
Index := fCommandHistory.IndexOf(Source);
if Index >= 0 then
fCommandHistory.Delete(Index);
FCommandHistory.Add(Source);
SetCommandHistorySize(fCommandHistorySize);
fCommandHistoryPointer := fCommandHistory.Count;
VariablesWindow.UpdateWindow;
end;
if NeedIndent then begin
// Now attempt to correct indentation
CurLine := Copy(SynEdit.Lines[lineN], Length(PS2)+1, MaxInt); //!!
Position := 1;
Indent := '';
while (Length(CurLine)>=Position) and
(CurLine[Position] in [WideChar(#09), WideChar(#32)]) do begin
Indent := Indent + CurLine[Position];
Inc(Position);
end;
if CommandsDataModule.IsBlockOpener(CurLine) then
Indent := Indent + #9
else if CommandsDataModule.IsBlockCloser(CurLine) then
Delete(Indent, Length(Indent), 1);
// use ReplaceSel to ensure it goes at the cursor rather than end of buffer.
SynEdit.SelText := PS2 + Indent;
end;
end;
end;
SynEdit.EnsureCursorPosVisible;
end;
ecDeleteLastChar :
begin
Line := SynEdit.Lines[SynEdit.CaretY - 1];
if ((Pos(PS1, Line) = 1) and (SynEdit.CaretX <= Length(PS1)+1)) or
((Pos(PS2, Line) = 1) and (SynEdit.CaretX <= Length(PS2)+1)) then
Command := ecNone; // do not processed it further
end;
ecLineStart :
begin
Line := SynEdit.Lines[SynEdit.CaretY - 1];
if Pos(PS1, Line) = 1 then begin
Command := ecNone; // do not processed it further
SynEdit.CaretX := Length(PS1) + 1;
end else if Pos(PS2, Line) = 1 then begin
Command := ecNone; // do not processed it further
SynEdit.CaretX := Length(PS2) + 1;
end;
end;
ecChar, ecDeleteChar, ecDeleteWord, ecDeleteLastWord, ecCut, ecPaste:
begin
Line := SynEdit.Lines[SynEdit.CaretY - 1];
if ((Pos(PS1, Line) = 1) and (SynEdit.CaretX <= Length(PS1))) or
((Pos(PS2, Line) = 1) and (SynEdit.CaretX <= Length(PS2)))
then
Command := ecNone; // do not processed it further
end;
ecUp, ecDown :
begin
LineN := SynEdit.CaretY - 1; // Caret is 1 based
GetBlockBoundary(LineN, StartLineN, EndLineN, IsCode);
if IsCode and (EndLineN = SynEdit.Lines.Count - 1) then begin
if Command = ecUp then
NewCommand := ecRecallCommandPrev
else
NewCommand := ecRecallCommandNext;
WChar := WideNull;
SynEditProcessUserCommand(Self, NewCommand, WChar, nil);
Command := ecNone; // do not processed it further
end;
end;
end;
end;
procedure TPythonIIForm.SynEditCommandProcessed(Sender: TObject;
var Command: TSynEditorCommand; var AChar: WideChar; Data: Pointer);
const
OpenBrackets : WideString = '([{"''';
CloseBrackets : WideString = ')]}"''';
Var
OpenBracketPos : integer;
Line: WideString;
Len, Position : Integer;
CharRight: WideChar;
CharLeft: WideChar;
begin
if (Command = ecChar) and CommandsDataModule.PyIDEOptions.AutoCompleteBrackets then
with SynEdit do begin
Line := LineText;
Len := Length(LineText);
if aChar = fCloseBracketChar then begin
if InsertMode and (CaretX <= Len) and (Line[CaretX] = fCloseBracketChar) then
ExecuteCommand(ecDeleteChar, WideChar(#0), nil);
fCloseBracketChar := #0;
end else begin
fCloseBracketChar := #0;
OpenBracketPos := Pos(aChar, OpenBrackets);
if (OpenBracketPos > 0) then begin
CharRight := WideNull;
Position := CaretX;
while (Position <= Len) and Highlighter.IsWhiteChar(LineText[Position]) do
Inc(Position);
if Position <= Len then
CharRight := Line[Position];
CharLeft := WideNull;
Position := CaretX-2;
while (Position >= 1) and Highlighter.IsWhiteChar(LineText[Position]) do
Dec(Position);
if Position >= 1 then
CharLeft := Line[Position];
if (CharRight <> aChar) and not Highlighter.IsIdentChar(CharRight) and
not ((aChar in [WideChar('"'), WideChar('''')])
and (Highlighter.IsIdentChar(CharLeft) or (CharLeft= aChar))) then
begin
SelText := CloseBrackets[OpenBracketPos];
CaretX := CaretX - 1;
fCloseBracketChar := CloseBrackets[OpenBracketPos];
end;
end;
end;
end;
end;
procedure TPythonIIForm.SynEditDblClick(Sender: TObject);
var
RegExpr : TRegExpr;
ErrLineNo : integer;
FileName : string;
begin
RegExpr := TRegExpr.Create;
try
RegExpr.Expression := STracebackFilePosExpr;
if RegExpr.Exec(Synedit.LineText) then begin
ErrLineNo := StrToIntDef(RegExpr.Match[3], 0);
FileName := GetLongFileName(ExpandFileName(RegExpr.Match[1]));
PyIDEMainForm.ShowFilePosition(FileName, ErrLineNo, 1);
end;
finally
RegExpr.Free;
end;
end;
procedure TPythonIIForm.SynEditProcessUserCommand(Sender: TObject;
var Command: TSynEditorCommand; var AChar: WideChar; Data: Pointer);
Var
LineN, StartLineN, EndLineN, i: integer;
IsCode: Boolean;
Source, BlockSource : WideString;
Buffer : array of WideString;
P1, P2 : PWideChar;
begin
if Command = ecCodeCompletion then begin
if SynCodeCompletion.Form.Visible then
SynCodeCompletion.CancelCompletion;
//SynCodeCompletion.DefaultType := ctCode;
SynCodeCompletion.ActivateCompletion;
Command := ecNone;
end else if Command = ecParamCompletion then begin
if SynParamCompletion.Form.Visible then
SynParamCompletion.CancelCompletion;
//SynCodeCompletion.DefaultType := ctParams;
SynParamCompletion.ActivateCompletion;
Command := ecNone;
end;
// The following does not work. compiler bug???
// if Command in [ecRecallCommandPrev, ecRecallCommandNext, ecRecallCommandEsc] then begin
if (Command = ecRecallCommandPrev) or (Command = ecRecallCommandNext) or
(Command = ecRecallCommandEsc) then
begin
SynCodeCompletion.CancelCompletion;
SynParamCompletion.CancelCompletion;
LineN := SynEdit.CaretY -1;
GetBlockBoundary(LineN, StartLineN, EndLineN, IsCode);
SetLength(Buffer, EndLineN-StartLineN + 1);
GetBlockCode(BlockSource, Buffer, EndLineN, StartLineN);
// Prefix
if fCommandHistoryPrefix <> '' then begin
if not (IsCode and (EndLineN = SynEdit.Lines.Count - 1) and
(SynEdit.CaretX = Length(SynEdit.Lines[SynEdit.Lines.Count - 1])+1) and
InRange(fCommandHistoryPointer, 0, fCommandHistory.Count-1) and
(BlockSource = fCommandHistory[fCommandHistoryPointer])) then
fCommandHistoryPrefix := ''
end else begin
if IsCode and (EndLineN = SynEdit.Lines.Count - 1) and
(SynEdit.CaretX = Length(SynEdit.Lines[SynEdit.Lines.Count - 1])+1) and
not (InRange(fCommandHistoryPointer, 0, fCommandHistory.Count-1) and
(BlockSource = fCommandHistory[fCommandHistoryPointer]))
then
fCommandHistoryPrefix := BlockSource;
end;
// if we have code at the end remove code block
SynEdit.BeginUpdate;
try
if IsCode and (EndLineN = SynEdit.Lines.Count - 1) then begin
for i := EndLineN downto StartLineN do
SynEdit.Lines.Delete(i);
end;
//Append new prompt if needed
SetLength(Buffer, 0);
AppendToPrompt(Buffer);
SynEdit.ExecuteCommand(ecEditorBottom, ' ', nil);
SynEdit.EnsureCursorPosVisible;
finally
SynEdit.EndUpdate;
end;
end else
Exit;
// We get here if Command is one or our defined Recall commands
Source := '';
if fCommandHistory.Count = 0 then Exit;
if Command = ecRecallCommandEsc then begin
fCommandHistoryPointer := fCommandHistory.Count;
fCommandHistoryPrefix := '';
end else
Repeat
if Command = ecRecallCommandPrev then
Dec(fCommandHistoryPointer)
else if Command = ecRecallCommandNext then
Inc(fCommandHistoryPointer);
fCommandHistoryPointer := EnsureRange(fCommandHistoryPointer, -1, fCommandHistory.Count);
Until not InRange(fCommandHistoryPointer, 0, fCommandHistory.Count-1) or
(fCommandHistoryPrefix = '') or
WideStrIsLeft(PWideChar(fCommandHistory[fCommandHistoryPointer]), PWideChar(fCommandHistoryPrefix));
if InRange(fCommandHistoryPointer, 0, fCommandHistory.Count-1) then
Source := fCommandHistory[fCommandHistoryPointer]
else begin
if Command <> ecRecallCommandEsc then
Beep();
Source := fCommandHistoryPrefix;
fCommandHistoryPrefix := '';
end;
if Source <> '' then begin
SynEdit.BeginUpdate;
try
i := 0;
P1 := PWideChar(Source);
while P1 <> nil do begin
P1 := StrScanW(P1, WideLF);
if Assigned(P1) then Inc(P1);
Inc(i);
end;
SetLength(Buffer, i);
i := 0;
P1 := PWideChar(Source);
while P1 <> nil do begin
P2 := StrScanW(P1, WideLF);
if P2 = nil then
Buffer[i] := Copy(Source, P1 - PWideChar(Source) + 1,
Length(Source) - (P1 - PWideChar(Source)))
else begin
Buffer[i] := Copy(Source, P1 - PWideChar(Source) + 1, P2 - P1);
Inc(P2);
end;
P1 := P2;
Inc(i);
end;
AppendToPrompt(Buffer);
SynEdit.ExecuteCommand(ecEditorBottom, ' ', nil);
SynEdit.EnsureCursorPosVisible;
finally
SynEdit.EndUpdate;
end;
end;
Command := ecNone; // do not processed it further
end;
procedure TPythonIIForm.SetCommandHistorySize(const Value: integer);
Var
i : integer;
begin
fCommandHistorySize := Value;
if FCommandHistory.Count > Value then begin
for i := 1 to FCommandHistory.Count - Value do
FCommandHistory.Delete(0);
end;
end;
procedure TPythonIIForm.SynCodeCompletionExecute(Kind: SynCompletionType;
Sender: TObject; var CurrentInput: WideString; var x, y: Integer;
var CanExecute: Boolean);
{-----------------------------------------------------------------------------
Based on code from Syendit Demo
-----------------------------------------------------------------------------}
var locline, lookup: String;
TmpX, Index, ImageIndex, i,
TmpLocation : Integer;
FoundMatch : Boolean;
DisplayText, InsertText: string;
NameSpaceDict, NameSpaceItem : TBaseNameSpaceItem;
begin
if PyControl.IsRunning then
// No code completion while Python is running
Exit;
with TSynCompletionProposal(Sender).Editor do
begin
locLine := LineText;
//go back from the cursor and find the first open paren
TmpX := CaretX;
if TmpX > length(locLine) then
TmpX := length(locLine)
else dec(TmpX);
TmpLocation := 0;
lookup := GetWordAtPos(LocLine, TmpX, IdentChars+['.'], True, False, True);
Index := CharLastPos(lookup, '.');
NameSpaceDict := nil;
if Index > 0 then
lookup := Copy(lookup, 1, Index-1)
else
lookup := ''; // Completion from global namespace
if (Index <= 0) or (lookup <> '') then begin
if PyControl.DebuggerState = dsInactive then
NameSpaceDict := PyControl.ActiveInterpreter.NameSpaceFromExpression(lookup)
else
NameSpaceDict := PyControl.ActiveDebugger.NameSpaceFromExpression(lookup);
end;
DisplayText := '';
InsertText := '';
if Assigned(NameSpaceDict) then
for i := 0 to NameSpaceDict.ChildCount - 1 do begin
NameSpaceItem := NameSpaceDict.ChildNode[i];
if NameSpaceItem.IsModule then
ImageIndex := 16
else if NameSpaceItem.IsMethod
{or NameSpaceItem.IsMethodDescriptor} then
ImageIndex := 14
else if NameSpaceItem.IsFunction
{or NameSpaceItem.IsBuiltin} then
ImageIndex := 17
else if NameSpaceItem.IsClass then
ImageIndex := 13
else begin
if Index > 0 then
ImageIndex := 1
else
ImageIndex := 0;
end;
DisplayText := DisplayText + Format('\Image{%d}\hspace{2}%s', [ImageIndex, NameSpaceItem.Name]);
InsertText := InsertText + NameSpaceItem.Name;
if i < NameSpaceDict.ChildCount - 1 then begin
DisplayText := DisplayText + #10;
InsertText := InsertText + #10;
end;
end;
FreeAndNil(NameSpaceDict);
FoundMatch := DisplayText <> '';
end;
CanExecute := FoundMatch;
if CanExecute then begin
TSynCompletionProposal(Sender).Form.CurrentIndex := TmpLocation;
TSynCompletionProposal(Sender).ItemList.Text := DisplayText;
TSynCompletionProposal(Sender).InsertList.Text := InsertText;
end else begin
TSynCompletionProposal(Sender).ItemList.Clear;
TSynCompletionProposal(Sender).InsertList.Clear;
end;
end;
procedure TPythonIIForm.SynParamCompletionExecute(Kind: SynCompletionType;
Sender: TObject; var CurrentInput: WideString; var x, y: Integer;
var CanExecute: Boolean);
var locline, lookup: String;
TmpX, StartX,
ParenCounter,
TmpLocation : Integer;
FoundMatch : Boolean;
DisplayText, DocString : string;
p : TPoint;
begin
if PyControl.IsRunning then Exit; // just in case
with TSynCompletionProposal(Sender).Editor do
begin
locLine := LineText;
//go back from the cursor and find the first open paren
TmpX := CaretX;
StartX := CaretX;
if TmpX > length(locLine) then
TmpX := length(locLine)
else dec(TmpX);
FoundMatch := False;
TmpLocation := 0;
while (TmpX > 0) and not(FoundMatch) do
begin
if LocLine[TmpX] = ',' then
begin
inc(TmpLocation);
dec(TmpX);
end else if LocLine[TmpX] = ')' then
begin
//We found a close, go till it's opening paren
ParenCounter := 1;
dec(TmpX);
while (TmpX > 0) and (ParenCounter > 0) do
begin
if LocLine[TmpX] = ')' then inc(ParenCounter)
else if LocLine[TmpX] = '(' then dec(ParenCounter);
dec(TmpX);
end;
end else if locLine[TmpX] = '(' then
begin
//we have a valid open paren, lets see what the word before it is
StartX := TmpX;
while (TmpX > 0) and not(locLine[TmpX] in IdentChars+['.']) do // added [.]
Dec(TmpX);
if TmpX > 0 then
begin
lookup := GetWordAtPos(LocLine, TmpX, IdentChars+['.'], True, False, True);
FoundMatch := PyControl.ActiveInterpreter.CallTipFromExpression(
lookup, DisplayText, DocString);
if not(FoundMatch) then
begin
TmpX := StartX;
dec(TmpX);
end;
end;
end else dec(TmpX)
end;
end;
CanExecute := FoundMatch;
if CanExecute then begin
with TSynCompletionProposal(Sender) do begin
FormatParams := not (DisplayText = '');
if not FormatParams then
DisplayText := '\style{~B}' + SNoParameters + '\style{~B}';
if (DocString <> '') then
DisplayText := DisplayText + sLineBreak;
Form.CurrentIndex := TmpLocation;
ItemList.Text := DisplayText + DocString;
end;
// position the hint window at and just below the opening bracket
p := SynEdit.ClientToScreen(SynEdit.RowColumnToPixels(
SynEdit.BufferToDisplayPos(BufferCoord(Succ(StartX), SynEdit.CaretY))));
Inc(p.y, SynEdit.LineHeight);
x := p.X;
y := p.Y;
end else begin
TSynCompletionProposal(Sender).ItemList.Clear;
TSynCompletionProposal(Sender).InsertList.Clear;
end;
end;
function TPythonIIForm.FormHelp(Command: Word; Data: Integer;
var CallHelp: Boolean): Boolean;
Var
KeyWord : string;
begin
Keyword := SynEdit.WordAtCursor;
if not PyIDEMainForm.PythonKeywordHelpRequested and
not PyIDEMainForm.MenuHelpRequested and (Keyword <> '') then
begin
CallHelp := not CommandsDataModule.ShowPythonKeywordHelp(KeyWord);
Result := True;
end else begin
CallHelp := True;
Result := False;
end;
end;
procedure TPythonIIForm.InputBoxExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
// InputBox function
Var
PCaption, PPrompt, PDefault : PWideChar;
WideS : WideString;
begin
with GetPythonEngine do
if PyArg_ParseTuple( args, 'uuu:InputBox', [@PCaption, @PPrompt, @PDefault] ) <> 0 then begin
WideS := PDefault;
if WideInputQuery(PCaption, PPrompt, WideS) then
Result := PyUnicode_FromWideChar(PWideChar(WideS), Length(WideS))
else
Result := ReturnNone;
end else
Result := nil;
end;
procedure TPythonIIForm.StatusWriteExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
// statusWrite
Var
Msg : PChar;
begin
with GetPythonEngine do
if PyArg_ParseTuple( args, 's:statusWrite', [@Msg] ) <> 0 then begin
PyIDEMainForm.WriteStatusMsg(Msg);
Application.ProcessMessages;
Result := ReturnNone;
end else
Result := nil;
end;
procedure TPythonIIForm.MessageWriteExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
// messageWrite
Var
Msg, FName : PChar;
LineNo, Offset : integer;
S : string;
begin
FName := nil;
LineNo := 0;
Offset := 0;
with GetPythonEngine do
if PyArg_ParseTuple( args, 's|sii:messageWrite', [@Msg, @FName, @LineNo, @Offset] ) <> 0 then begin
if Assigned(FName) then
S := FName
else
S := '';
MessagesWindow.AddMessage(Msg, S, LineNo, Offset);
Application.ProcessMessages;
Result := ReturnNone;
end else
Result := nil;
end;
procedure TPythonIIForm.Get8087CWExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
begin
Result := GetPythonEngine.PyLong_FromUnsignedLong(Get8087CW);
end;
procedure TPythonIIForm.UnMaskFPUExceptionsExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
begin
MaskFPUExceptions(False);
CommandsDataModule.PyIDEOptions.MaskFPUExceptions := False;
Result := GetPythonEngine.ReturnNone;
end;
procedure TPythonIIForm.MaskFPUExceptionsExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
begin
MaskFPUExceptions(True);
CommandsDataModule.PyIDEOptions.MaskFPUExceptions := True;
Result := GetPythonEngine.ReturnNone;
end;
procedure TPythonIIForm.testResultStartTestExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
begin
UnitTestWindow.StartTest(VarPythonCreate(Args).GetItem(0));
Result := GetPythonEngine.ReturnNone;
end;
procedure TPythonIIForm.testResultStopTestExecute(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
begin
UnitTestWindow.StopTest(VarPythonCreate(Args).GetItem(0));
Result := GetPythonEngine.ReturnNone;
end;
procedure TPythonIIForm.testResultAddSuccess(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
begin
UnitTestWindow.AddSuccess(VarPythonCreate(Args).GetItem(0));
Result := GetPythonEngine.ReturnNone;
end;
procedure TPythonIIForm.testResultAddFailure(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
begin
UnitTestWindow.AddFailure(VarPythonCreate(Args).GetItem(0),
VarPythonCreate(Args).GetItem(1));
Result := GetPythonEngine.ReturnNone;
end;
procedure TPythonIIForm.testResultAddError(Sender: TObject; PSelf,
Args: PPyObject; var Result: PPyObject);
begin
UnitTestWindow.AddError(VarPythonCreate(Args).GetItem(0),
VarPythonCreate(Args).GetItem(1));
Result := GetPythonEngine.ReturnNone;
end;
procedure TPythonIIForm.FormActivate(Sender: TObject);
begin
inherited;
if not HasFocus then begin
FGPanelEnter(Self);
PostMessage(SynEdit.Handle, WM_SETFOCUS, 0, 0);
end;
end;
procedure TPythonIIForm.TBMThemeChange(var Message: TMessage);
begin
inherited;
if Message.WParam = TSC_VIEWCHANGE then begin
// Update the gutter of the PythonII editor
PyIDEMainForm.ThemeEditorGutter(SynEdit.Gutter);
SynEdit.InvalidateGutter;
end;
end;
procedure TPythonIIForm.InterpreterPopUpPopup(Sender: TObject);
begin
actCleanUpNameSpace.Checked := CommandsDataModule.PyIDEOptions.CleanupMainDict;
actCleanUpSysModules.Checked := CommandsDataModule.PyIDEOptions.CleanupSysModules;
end;
procedure TPythonIIForm.WMAPPENDTEXT(var Message: TMessage);
Var
WS : WideString;
begin
fCriticalSection.Acquire;
try
if fOutputStream.Size > 0 then begin
SetLength(WS, fOutputStream.Size div 2);
fOutputStream.Position := 0;
fOutputStream.Read(WS[1], Length(WS)*2);
AppendText(WS);
fOutputStream.Size := 0;
end;
finally
fCriticalSection.Release;
end;
end;
function TPythonIIForm.IsEmpty : Boolean;
begin
Result := (SynEdit.Lines.Count = 0) or
((SynEdit.Lines.Count = 1) and (SynEdit.Lines[0] = ''));
end;
function TPythonIIForm.CanFind: boolean;
begin
Result := not IsEmpty;
end;
function TPythonIIForm.CanFindNext: boolean;
begin
Result := not IsEmpty and
(CommandsDataModule.EditorSearchOptions.SearchText <> '');
end;
function TPythonIIForm.CanReplace: boolean;
begin
Result := not IsEmpty;
end;
procedure TPythonIIForm.ExecFind;
begin
CommandsDataModule.ShowSearchReplaceDialog(SynEdit, FALSE);
end;
procedure TPythonIIForm.ExecFindNext;
begin
CommandsDataModule.DoSearchReplaceText(SynEdit, FALSE, FALSE);
end;
procedure TPythonIIForm.ExecFindPrev;
begin
CommandsDataModule.DoSearchReplaceText(SynEdit, FALSE, TRUE);
end;
procedure TPythonIIForm.ExecReplace;
begin
CommandsDataModule.ShowSearchReplaceDialog(SynEdit, TRUE);
end;
procedure TPythonIIForm.RegisterHistoryCommands;
begin
// Register the Recall History Command
with SynEdit.Keystrokes.Add do
begin
ShortCut := Menus.ShortCut(VK_UP, [ssAlt]);
Command := ecRecallCommandPrev;
end;
with SynEdit.Keystrokes.Add do
begin
ShortCut := Menus.ShortCut(VK_DOWN, [ssAlt]);
Command := ecRecallCommandNext;
end;
with SynEdit.Keystrokes.Add do
begin
ShortCut := Menus.ShortCut(VK_ESCAPE, []);
Command := ecRecallCommandEsc;
end;
end;
procedure TPythonIIForm.LoadPythonEngine;
function IsPythonVersionParam(const AParam : String; out AVersion : String) : Boolean;
begin
Result := (Length(AParam) = 9) and
SameText(Copy(AParam, 1, 7), '-PYTHON') and
(AParam[8] in ['0'..'9']) and
(AParam[9] in ['0'..'9']);
if Result then
AVersion := AParam[8] + '.' + AParam[9];
end;
function IndexOfKnownVersion(const AVersion : String) : Integer;
var
i : Integer;
begin
Result := -1;
for i := High(PYTHON_KNOWN_VERSIONS) downto Low(PYTHON_KNOWN_VERSIONS) do
if PYTHON_KNOWN_VERSIONS[i].RegVersion = AVersion then
begin
Result := i;
Break;
end;
end;
var
i: Integer;
idx : Integer;
versionIdx : Integer;
expectedVersion : string;
expectedVersionIdx : Integer;
begin
// first find an optional parameter specifying the expected Python version in the form of -PYTHONXY
expectedVersion := '';
expectedVersionIdx := -1;
for i := 1 to ParamCount do begin
if IsPythonVersionParam(ParamStr(i), expectedVersion) then
begin
idx := IndexOfKnownVersion(expectedVersion);
if idx >= COMPILED_FOR_PYTHON_VERSION_INDEX then
expectedVersionIdx := idx;
if expectedVersionIdx = -1 then
if idx = -1 then
MessageDlg(Format('PyScripter can''t use command line parameter %s because it doesn''t know this version of Python.',
[ParamStr(i)]), mtWarning, [mbOK], 0)
else
MessageDlg(Format('PyScripter can''t use command line parameter %s because it was compiled for Python %s or later.',
[ParamStr(i), PYTHON_KNOWN_VERSIONS[COMPILED_FOR_PYTHON_VERSION_INDEX].RegVersion]), mtWarning, [mbOK], 0);
Break;
end;
end;
// disable feature that will try to use the last version of Python because we provide our
// own behaviour. Note that this feature would not load the latest version if the python dll
// matching the compiled version of P4D was found.
PythonEngine.UseLastKnownVersion := False;
if expectedVersionIdx > -1 then
begin
// if we found a parameter requiring a specific version of Python,
// then we must immediatly fail if P4D did not find the expected dll.
versionIdx := expectedVersionIdx;
PythonEngine.FatalMsgDlg := True;
PythonEngine.FatalAbort := True;
end
else
begin
// otherwise, let's start searching a valid python dll from the latest known version
versionIdx := High(PYTHON_KNOWN_VERSIONS);
PythonEngine.FatalMsgDlg := False;
PythonEngine.FatalAbort := False;
end;
// try to find an acceptable version of Python, starting from either the specified version,
// or the latest know version, but stop when we reach the version targeted on compilation.
for i := versionIdx downto COMPILED_FOR_PYTHON_VERSION_INDEX do
begin
PythonEngine.DllName := PYTHON_KNOWN_VERSIONS[i].DllName;
PythonEngine.APIVersion := PYTHON_KNOWN_VERSIONS[i].APIVersion;
PythonEngine.RegVersion := PYTHON_KNOWN_VERSIONS[i].RegVersion;
if i = COMPILED_FOR_PYTHON_VERSION_INDEX then
begin
// last chance, so raise an error if it goes wrong
PythonEngine.FatalMsgDlg := True;
PythonEngine.FatalAbort := True;
end;
PythonEngine.LoadDll;
if PythonEngine.IsHandleValid then
// we found a valid version
Break;
end;
end;
procedure TPythonIIForm.GetBlockCode(var Source: WideString;
var Buffer: array of WideString; EndLineN: Integer; StartLineN: Integer);
var
Len: Integer;
Line: WideString;
i: Integer;
begin
Assert(Length(Buffer) = EndLineN-StartLineN + 1);
Source := '';
for i := StartLineN to EndLineN do
begin
Line := SynEdit.Lines[i];
Len := Length(GetPromptPrefix(Line));
Buffer[i - StartLineN] := Copy(Line, Len + 1, MaxInt);
Source := Source + Buffer[i - StartLineN] + WideLF;
end;
Delete(Source, Length(Source), 1);
end;
procedure TPythonIIForm.SynEditReplaceText(Sender: TObject; const ASearch,
AReplace: WideString; Line, Column: Integer; var Action: TSynReplaceAction);
var
APos: TPoint;
EditRect: TRect;
begin
if ASearch = AReplace then
Action := raSkip
else begin
APos := SynEdit.ClientToScreen(
SynEdit.RowColumnToPixels(
SynEdit.BufferToDisplayPos(
BufferCoord(Column, Line) ) ) );
EditRect := ClientRect;
EditRect.TopLeft := ClientToScreen(EditRect.TopLeft);
EditRect.BottomRight := ClientToScreen(EditRect.BottomRight);
if ConfirmReplaceDialog = nil then
ConfirmReplaceDialog := TConfirmReplaceDialog.Create(Application);
ConfirmReplaceDialog.PrepareShow(EditRect, APos.X, APos.Y,
APos.Y + SynEdit.LineHeight, ASearch);
case ConfirmReplaceDialog.ShowModal of
mrYes: Action := raReplace;
mrYesToAll: Action := raReplaceAll;
mrNo: Action := raSkip;
else Action := raCancel;
end;
end;
end;
end.
See more files for this project here