mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-08 01:45:32 +00:00
1110 lines
28 KiB
ObjectPascal
1110 lines
28 KiB
ObjectPascal
unit UframeTextEditor; // I3323 // I4797
|
|
|
|
interface
|
|
|
|
uses
|
|
System.Classes,
|
|
System.ImageList,
|
|
System.JSON,
|
|
System.SysUtils,
|
|
System.Types,
|
|
System.Variants,
|
|
Vcl.Controls,
|
|
Vcl.Dialogs,
|
|
Vcl.ExtCtrls,
|
|
Vcl.Forms,
|
|
Vcl.Graphics,
|
|
Vcl.ImgList,
|
|
Vcl.Menus,
|
|
Vcl.StdCtrls,
|
|
Winapi.Messages,
|
|
Winapi.Windows,
|
|
|
|
uCEFConstants,
|
|
uCEFInterfaces,
|
|
uCEFTypes,
|
|
|
|
Keyman.Developer.UI.UframeCEFHost,
|
|
KMDActionInterfaces,
|
|
MenuImgList,
|
|
TextFileFormat,
|
|
UfrmTike,
|
|
UserMessages;
|
|
|
|
type
|
|
TEditorBreakpointClickedEvent = procedure(Sender: TObject; ALine: Integer) of object;
|
|
|
|
TframeTextEditor = class(TTIKEForm, IKMDEditActions, IKMDSearchActions, IKMDTextEditorActions)
|
|
lstImages: TMenuImgList;
|
|
dlgFonts: TFontDialog;
|
|
dlgPrintSetup: TPrinterSetupDialog;
|
|
lstImagesDisabled: TImageList;
|
|
procedure FormCreate(Sender: TObject);
|
|
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
procedure TntFormDestroy(Sender: TObject);
|
|
private
|
|
class var FInitialFilenameIndex: Integer;
|
|
private
|
|
FCharFont, FCodeFont: TFont;
|
|
FSelectedRange: TRect;
|
|
FSelectedRangeIsBackwards: Boolean;
|
|
FHasBeenLoaded: Boolean;
|
|
FCanUndo: Boolean;
|
|
FCanRedo: Boolean;
|
|
FHasSelection: Boolean;
|
|
FLoading: Boolean;
|
|
FEditorFormat: TEditorFormat;
|
|
FOnChanged: TNotifyEvent;
|
|
FTextFileFormat: TTextFileFormat;
|
|
|
|
cef: TframeCEFHost;
|
|
FFilename: string;
|
|
FOnBreakpointClicked: TEditorBreakpointClickedEvent;
|
|
|
|
procedure RefreshOptions;
|
|
function GetText: WideString;
|
|
procedure SetText(Value: WideString);
|
|
procedure SetEditorFormat(const Value: TEditorFormat);
|
|
procedure Changed;
|
|
procedure UpdateState(ALocation: string);
|
|
procedure ClearError;
|
|
procedure SetCharFont(const Value: TFont);
|
|
procedure SetCodeFont(const Value: TFont);
|
|
function GetCharFont: TFont;
|
|
function GetCodeFont: TFont;
|
|
procedure SetTextFileFormat(const Value: TTextFileFormat);
|
|
|
|
procedure cefBeforeBrowse(Sender: TObject; const Url: string; out Result: Boolean);
|
|
procedure cefBeforeContextMenu(Sender: TObject;
|
|
const browser: ICefBrowser; const frame: ICefFrame;
|
|
const params: ICefContextMenuParams; const model: ICefMenuModel);
|
|
procedure cefContextMenuCommand(Sender: TObject;
|
|
const browser: ICefBrowser; const frame: ICefFrame;
|
|
const params: ICefContextMenuParams; commandId: Integer;
|
|
eventFlags: Cardinal; out Result: Boolean);
|
|
procedure cefPreKeyEvent(Sender: TObject; const browser: ICefBrowser;
|
|
const event: PCefKeyEvent; osEvent: PMsg; out isKeyboardShortcut,
|
|
Result: Boolean);
|
|
|
|
procedure WMUser_TextEditor_Command(var Message: TMessage); message WM_USER_TextEditor_Command;
|
|
procedure WMUser_FireCommand(var Message: TMessage); message WM_USER_FireCommand;
|
|
procedure WMUser_SyntaxColourChange(var Message: TMessage); message WM_USER_SYNTAXCOLOURCHANGE;
|
|
procedure FireCommand(const commands: TStringList);
|
|
procedure LoadFileInBrowser(const AData: string);
|
|
procedure UpdateInsertState(const AMode: string);
|
|
procedure ExecuteCommand(const command: string; const parameters: TJSONValue = nil);
|
|
procedure ExecuteLineCommand(ALine: Integer; const command: string);
|
|
procedure UpdateToken(command: string);
|
|
procedure SetCursorPosition(AColumn, ARow: Integer);
|
|
procedure cefLoadEnd(Sender: TObject);
|
|
procedure DoBreakpointClicked(const line: string);
|
|
procedure UpdateEditorFonts;
|
|
procedure CharMapDragDrop(Sender, Source: TObject; X, Y: Integer);
|
|
procedure CharMapDragOver(Sender, Source: TObject; X, Y: Integer;
|
|
State: TDragState; var Accept: Boolean);
|
|
|
|
protected
|
|
function GetHelpTopic: string; override;
|
|
|
|
procedure SaveToStream(AStream: TStream);
|
|
|
|
{ IKMDSearchActions }
|
|
procedure EditFind;
|
|
procedure EditFindNext;
|
|
procedure EditReplace;
|
|
function CanEditFind: Boolean;
|
|
function CanEditFindNext: Boolean;
|
|
|
|
{ IKMDEditActions }
|
|
function IKMDTextEditorActions_GetText: string;
|
|
procedure IKMDTextEditorActions_SetText(const Value: string);
|
|
function IKMDTextEditorActions.GetText = IKMDTextEditorActions_GetText;
|
|
procedure IKMDTextEditorActions.SetText = IKMDTextEditorActions_SetText;
|
|
procedure CutToClipboard;
|
|
procedure CopyToClipboard;
|
|
procedure PasteFromClipboard;
|
|
procedure Undo;
|
|
procedure Redo; // I4032
|
|
procedure SelectAll;
|
|
procedure ClearSelection;
|
|
function CanCut: Boolean;
|
|
function CanCopy: Boolean;
|
|
function CanPaste: Boolean;
|
|
function CanUndo: Boolean;
|
|
function CanRedo: Boolean; // I4032
|
|
function CanSelectAll: Boolean;
|
|
function CanClearSelection: Boolean;
|
|
|
|
{ IKMDTextEditorActions }
|
|
function GetEditorFormat: TEditorFormat;
|
|
function GetSelectedRow: Integer;
|
|
function GetSelectedCol: Integer;
|
|
function GetSelectedRange: TRect;
|
|
procedure ReplaceSelection(ARange: TRect; const ANewText: string);
|
|
|
|
public
|
|
{ Public declarations }
|
|
procedure DebugSetBreakpoint(ALine: Integer);
|
|
procedure DebugClearBreakpoint(ALine: Integer);
|
|
procedure DebugUpdateExecutionPoint(ALine: Integer);
|
|
property OnBreakpointClicked: TEditorBreakpointClickedEvent read FOnBreakpointClicked write FOnBreakpointClicked;
|
|
|
|
procedure SetFocus; override;
|
|
procedure SetupCharMapDrop;
|
|
|
|
procedure FindError(ln: Integer);
|
|
procedure FindErrorByOffset(offset: Integer); // I4083
|
|
function OffsetToLine(Offset: Integer): Integer; // I4083
|
|
|
|
procedure SyntaxColourChange;
|
|
|
|
function PrintFile(Header: WideString = ''): Boolean;
|
|
function PrintPreview(Header: WideString = ''): Boolean;
|
|
|
|
procedure LoadFromFile(AFileName: WideString); overload; // I4034
|
|
procedure LoadFromStream(AStream: TStream); overload; // I2964
|
|
procedure SaveToFile(AFileName: WideString);
|
|
procedure LoadFromFile(AFileName: WideString; ATextFileFormat: TTextFileFormat); overload; // I4034
|
|
procedure LoadFromStream(AStream: TStream; ATextFileFormat: TTextFileFormat); overload; // I2964
|
|
|
|
procedure SetSelectedRow(ARow: Integer);
|
|
|
|
property EditorText: WideString read GetText write SetText;
|
|
property EditorFormat: TEditorFormat read GetEditorFormat write SetEditorFormat;
|
|
property OnChanged: TNotifyEvent read FOnChanged write FonChanged;
|
|
|
|
property CodeFont: TFont read GetCodeFont write SetCodeFont;
|
|
property CharFont: TFont read GetCharFont write SetCharFont;
|
|
property TextFileFormat: TTextFileFormat read FTextFileFormat write SetTextFileFormat;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.TypInfo,
|
|
Vcl.Clipbrd,
|
|
|
|
Keyman.Developer.System.HelpTopics,
|
|
Keyman.UI.FontUtils,
|
|
|
|
dmActionsMain,
|
|
dmActionsTextEditor,
|
|
CharacterDragObject,
|
|
CharacterInfo,
|
|
CharMapDropTool,
|
|
CharMapInsertMode,
|
|
keyboardparser,
|
|
KeymanDeveloperOptions,
|
|
kwhelp,
|
|
ErrorControlledRegistry,
|
|
RegistryKeys,
|
|
UfrmHelp,
|
|
UfrmMain,
|
|
UmodWebHTTPServer,
|
|
Unicode,
|
|
utilhttp,
|
|
utilstr;
|
|
|
|
{$R *.dfm}
|
|
|
|
{ TframeTextEditor }
|
|
|
|
procedure TframeTextEditor.RefreshOptions;
|
|
begin
|
|
//TODO: memo.TabStops := FKeymanDeveloperOptions.IndentSize;
|
|
end;
|
|
|
|
procedure TframeTextEditor.ReplaceSelection(ARange: TRect;
|
|
const ANewText: string);
|
|
var
|
|
j: TJSONObject;
|
|
begin
|
|
j := TJSONObject.Create;
|
|
try
|
|
j.AddPair('top', TJSONNumber.Create(ARange.Top));
|
|
j.AddPair('left', TJSONNumber.Create(ARange.Left));
|
|
j.AddPair('bottom', TJSONNumber.Create(ARange.Bottom));
|
|
j.AddPair('right', TJSONNumber.Create(ARange.Right));
|
|
j.AddPair('newText', ANewText);
|
|
ExecuteCommand('replaceSelection', j);
|
|
finally
|
|
j.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.cefBeforeBrowse(Sender: TObject; const Url: string; out Result: Boolean);
|
|
var
|
|
params: TStringList;
|
|
begin
|
|
Result := False;
|
|
|
|
if csDestroying in ComponentState then // I3983
|
|
begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
|
|
if GetParamsFromURL(URL, params) then
|
|
begin
|
|
PostMessage(Handle, WM_USER_FireCommand, 0, Integer(params));
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
const
|
|
TEXTEDITOR_CONTEXTMENU_SHOWCHARACTER = MENU_ID_USER_FIRST + 1; //actTextEditor_ShowCharacter
|
|
TEXTEDITOR_CONTEXTMENU_CONVERTTOCHARACTERS = MENU_ID_USER_FIRST + 2; //actTextEditor_ConvertToCharacters
|
|
|
|
procedure TframeTextEditor.cefBeforeContextMenu(Sender: TObject;
|
|
const browser: ICefBrowser; const frame: ICefFrame;
|
|
const params: ICefContextMenuParams; const model: ICefMenuModel);
|
|
begin
|
|
if not cef.cef.IsSameBrowser(browser) then exit;
|
|
|
|
model.AddSeparator;
|
|
model.AddItem(TEXTEDITOR_CONTEXTMENU_SHOWCHARACTER, 'S&how Character');
|
|
model.SetEnabled(TEXTEDITOR_CONTEXTMENU_SHOWCHARACTER, modActionsTextEditor.actTextEditor_ShowCharacter.Enabled);
|
|
model.AddItem(TEXTEDITOR_CONTEXTMENU_CONVERTTOCHARACTERS, 'C&onvert to Characters');
|
|
model.SetAccelerator(TEXTEDITOR_CONTEXTMENU_CONVERTTOCHARACTERS, Ord('U'), True, True, False);
|
|
model.SetEnabled(TEXTEDITOR_CONTEXTMENU_CONVERTTOCHARACTERS, modActionsTextEditor.actTextEditor_ConvertToCharacters.Enabled);
|
|
end;
|
|
|
|
procedure TframeTextEditor.cefContextMenuCommand(Sender: TObject;
|
|
const browser: ICefBrowser; const frame: ICefFrame;
|
|
const params: ICefContextMenuParams; commandId: Integer; eventFlags: Cardinal;
|
|
out Result: Boolean);
|
|
begin
|
|
Result := False;
|
|
|
|
if not cef.cef.IsSameBrowser(browser) then exit;
|
|
|
|
case commandId of
|
|
TEXTEDITOR_CONTEXTMENU_SHOWCHARACTER,
|
|
TEXTEDITOR_CONTEXTMENU_CONVERTTOCHARACTERS:
|
|
PostMessage(Handle, WM_USER_TextEditor_Command, commandId, 0);
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.cefPreKeyEvent(Sender: TObject;
|
|
const browser: ICefBrowser; const event: PCefKeyEvent; osEvent: PMsg;
|
|
out isKeyboardShortcut, Result: Boolean);
|
|
begin
|
|
if event.windows_key_code = VK_ESCAPE then
|
|
begin
|
|
ClearError;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.Changed;
|
|
begin
|
|
if Assigned(FOnChanged) then FOnChanged(Self);
|
|
end;
|
|
|
|
procedure TframeTextEditor.FormCreate(Sender: TObject);
|
|
begin
|
|
inherited;
|
|
|
|
FCharFont := TFont.Create;
|
|
FCodeFont := TFont.Create;
|
|
|
|
cef := TframeCEFHost.Create(Self);
|
|
cef.ShouldShowContextMenu := True;
|
|
cef.Parent := Self;
|
|
cef.Visible := True;
|
|
cef.OnBeforeBrowse := cefBeforeBrowse;
|
|
cef.OnLoadEnd := cefLoadEnd;
|
|
cef.cef.OnBeforeContextMenu := cefBeforeContextMenu;
|
|
cef.cef.OnContextMenuCommand := cefContextMenuCommand;
|
|
cef.OnPreKeyEvent := cefPreKeyEvent;
|
|
SetupCharMapDrop;
|
|
end;
|
|
|
|
procedure TframeTextEditor.SetupCharMapDrop;
|
|
begin
|
|
GetCharMapDropTool.Handle(cef.cefwp, cmimDefault, CharMapDragOver, CharMapDragDrop);
|
|
end;
|
|
|
|
procedure TframeTextEditor.cefLoadEnd(Sender: TObject);
|
|
begin
|
|
FHasBeenLoaded := True;
|
|
end;
|
|
|
|
type
|
|
TAccessWinControl = class(TWinControl);
|
|
|
|
procedure TframeTextEditor.FormKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
function ShiftStateToLParam(Shift: TShiftState): Integer;
|
|
const
|
|
AltMask = $20000000;
|
|
begin
|
|
if ssAlt in Shift then
|
|
Result := AltMask
|
|
else
|
|
Result := 0;
|
|
end;
|
|
var
|
|
msg: TWMKey;
|
|
begin
|
|
if (Owner is TCustomForm) and (Owner as TCustomForm).KeyPreview then
|
|
begin
|
|
msg.Msg := WM_KEYDOWN;
|
|
msg.CharCode := Key;
|
|
msg.KeyData := ShiftStateToLParam(Shift);
|
|
if TAccessWinControl(Owner).DoKeyDown(msg) then Key := 0;
|
|
end;
|
|
end;
|
|
|
|
function TframeTextEditor.GetCharFont: TFont;
|
|
begin
|
|
Result := FCharFont;
|
|
end;
|
|
|
|
function TframeTextEditor.GetCodeFont: TFont;
|
|
begin
|
|
Result := FCodeFont;
|
|
end;
|
|
|
|
function TframeTextEditor.GetEditorFormat: TEditorFormat;
|
|
begin
|
|
Result := FEditorFormat;
|
|
end;
|
|
|
|
function TframeTextEditor.GetText: WideString;
|
|
begin
|
|
Result := modWebHttpServer.AppSource.GetSource(FFileName);
|
|
end;
|
|
|
|
function TframeTextEditor.IKMDTextEditorActions_GetText: string;
|
|
begin
|
|
Result := GetText;
|
|
end;
|
|
|
|
procedure TframeTextEditor.IKMDTextEditorActions_SetText(const Value: string);
|
|
begin
|
|
SetText(Value);
|
|
end;
|
|
|
|
procedure TframeTextEditor.LoadFromFile(AFileName: WideString);
|
|
var
|
|
fs: TFileStream;
|
|
begin
|
|
FLoading := True;
|
|
try
|
|
FFileName := AFileName;
|
|
if FileExists(AFileName) then
|
|
begin
|
|
fs := TFileStream.Create(AFileName, fmOpenRead);
|
|
try
|
|
LoadFromStream(fs);
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
TextFileFormat := tffUTF8;
|
|
LoadFileInBrowser('');
|
|
end;
|
|
finally
|
|
FLoading := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.LoadFromStream(AStream: TStream; ATextFileFormat: TTextFileFormat); // I2964
|
|
var
|
|
s: TStringList;
|
|
begin
|
|
FLoading := True;
|
|
try
|
|
TextFileFormat := ATextFileFormat;
|
|
|
|
s := TStringList.Create;
|
|
try
|
|
s.LoadFromStream(AStream, TextFileFormatToEncoding(TextFileFormat)); // I3637
|
|
LoadFileInBrowser(s.Text);
|
|
finally
|
|
s.Free;
|
|
end;
|
|
|
|
finally
|
|
FLoading := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.LoadFileInBrowser(const AData: string);
|
|
function GenerateNewFilename: string;
|
|
begin
|
|
Inc(FInitialFilenameIndex);
|
|
Result := '*texteditor*'+IntToStr(FInitialFilenameIndex);
|
|
end;
|
|
function EncodeFont(const prefix: string; f: TFont): string;
|
|
begin
|
|
Result := Format('&%sName=%s&%sSize=%d', [prefix, URLEncode(f.Name), prefix, TFontUtils.FontSizeInPixels(f)]);
|
|
end;
|
|
const
|
|
mode: array[TEditorFormat] of string = (
|
|
'keyman', 'xml', 'text', 'html', 'json', 'javascript', 'css'
|
|
);
|
|
begin
|
|
if FFilename = '' then
|
|
FFilename := GenerateNewFilename;
|
|
modWebHTTPServer.AppSource.RegisterSource(FFilename, AData, True);
|
|
cef.Navigate(
|
|
modWebHttpServer.GetLocalhostURL + '/app/editor/'+
|
|
'?mode='+mode[FEditorFormat]+
|
|
'&filename='+URLEncode(FFilename)+
|
|
EncodeFont('charFont', FCharFont)+
|
|
EncodeFont('codeFont', FCodeFont));
|
|
end;
|
|
|
|
procedure TframeTextEditor.LoadFromFile(AFileName: WideString;
|
|
ATextFileFormat: TTextFileFormat); // I4034
|
|
var
|
|
fs: TFileStream;
|
|
begin
|
|
FLoading := True;
|
|
try
|
|
FFileName := AFileName;
|
|
if FileExists(AFileName) then
|
|
begin
|
|
try
|
|
fs := TFileStream.Create(AFileName, fmOpenRead);
|
|
try
|
|
LoadFromStream(fs, ATextFileFormat);
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
except
|
|
on E:EFOpenError do // I4616 // I4721
|
|
begin
|
|
ShowMessage(E.Message);
|
|
Exit;
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
TextFileFormat := ATextFileFormat;
|
|
LoadFileInBrowser('');
|
|
end;
|
|
finally
|
|
FLoading := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.LoadFromStream(AStream: TStream);
|
|
var
|
|
s: TStringList;
|
|
begin
|
|
FLoading := True;
|
|
try
|
|
s := TStringList.Create;
|
|
try
|
|
s.LoadFromStream(AStream); // prolog determines encoding // I3337
|
|
if s.Encoding = TEncoding.UTF8 then // I3337 // I3636
|
|
TextFileFormat := tffUTF8
|
|
else if s.Encoding = TEncoding.Unicode then // I3337 // I3636
|
|
TextFileFormat := tffUTF16
|
|
else
|
|
TextFileFormat := tffANSI;
|
|
LoadFileInBrowser(s.Text);
|
|
finally
|
|
s.Free;
|
|
end;
|
|
|
|
finally
|
|
FLoading := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.SaveToFile(AFileName: WideString);
|
|
var
|
|
fs: TFileStream;
|
|
begin
|
|
fs := TFileStream.Create(AFileName, fmCreate);
|
|
with fs do
|
|
try
|
|
Self.SaveToStream(fs);
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TframeTextEditor.SaveToStream(AStream: TStream);
|
|
var
|
|
FSource: string;
|
|
ss: TStringStream;
|
|
begin
|
|
FSource := modWebHttpServer.AppSource.GetSource(FFileName);
|
|
case FTextFileFormat of
|
|
tffANSI: ss := TStringStream.Create(FSource, TEncoding.Default);
|
|
tffUTF8: ss := TStringStream.Create(FSource, TEncoding.UTF8);
|
|
tffUTF16: ss := TStringStream.Create(FSource, TEncoding.Unicode);
|
|
else raise EAssertionFailed.Create('Unsupported file format');
|
|
end;
|
|
try
|
|
AStream.CopyFrom(ss, 0);
|
|
finally
|
|
ss.Free;
|
|
end;
|
|
end;
|
|
|
|
(*
|
|
with Source as TCharacterDragObject do
|
|
s := Text[cmimText];
|
|
{ if IsUnicode
|
|
then s := Format('U+%4.4X', [CharCode])
|
|
else s := Format('d%d', [CharCode]);}
|
|
|
|
memo.SelText := s;
|
|
|
|
Windows.SetFocus(memo.Handle);
|
|
|
|
Accept := (Source is TCharacterDragObject) and (X > 16) and (Y > 16);
|
|
if Accept then
|
|
begin
|
|
memo.SetCaretFromMouse(X - 16, Y - 16);
|
|
end;
|
|
*)
|
|
|
|
procedure TframeTextEditor.UpdateInsertState(const AMode: string);
|
|
begin
|
|
frmKeymanDeveloper.barStatus.Panels[1].Text := AMode;
|
|
end;
|
|
|
|
function TframeTextEditor.PrintFile(Header: WideString): Boolean;
|
|
begin
|
|
ExecuteCommand('print');
|
|
Result := True;
|
|
end;
|
|
|
|
function TframeTextEditor.PrintPreview(Header: WideString): Boolean;
|
|
begin
|
|
// TODO: print preview
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TframeTextEditor.SetCharFont(const Value: TFont);
|
|
begin
|
|
FCharFont.Assign(Value);
|
|
UpdateEditorFonts;
|
|
end;
|
|
|
|
procedure TframeTextEditor.SetCodeFont(const Value: TFont);
|
|
begin
|
|
FCodeFont.Assign(Value);
|
|
UpdateEditorFonts;
|
|
end;
|
|
|
|
procedure TframeTextEditor.UpdateEditorFonts;
|
|
function FontToJSON(f: TFont): TJSONObject;
|
|
begin
|
|
Result := TJSONObject.Create;
|
|
Result.AddPair('name', f.Name);
|
|
Result.AddPair('size', TJSONNumber.Create(TFontUtils.FontSizeInPixels(f)));
|
|
end;
|
|
var
|
|
j: TJSONObject;
|
|
begin
|
|
if not FHasBeenLoaded then
|
|
Exit;
|
|
|
|
j := TJSONObject.Create;
|
|
try
|
|
j.AddPair('codeFont', FontToJSON(FCodeFont));
|
|
j.AddPair('charFont', FontToJSON(FCharFont));
|
|
ExecuteCommand('setFonts', j);
|
|
finally
|
|
j.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.SetEditorFormat(const Value: TEditorFormat);
|
|
begin
|
|
FEditorFormat := Value;
|
|
end;
|
|
|
|
procedure TframeTextEditor.SetFocus;
|
|
begin
|
|
inherited;
|
|
cef.SetFocus;
|
|
end;
|
|
|
|
procedure TframeTextEditor.SetSelectedRow(ARow: Integer);
|
|
begin
|
|
SetCursorPosition(0, ARow);
|
|
end;
|
|
|
|
procedure TframeTextEditor.SetCursorPosition(AColumn, ARow: Integer);
|
|
var
|
|
j: TJSONObject;
|
|
begin
|
|
j := TJSONObject.Create;
|
|
try
|
|
j.AddPair('row', TJSONNumber.Create(ARow));
|
|
j.AddPair('column', TJSONNumber.Create(AColumn));
|
|
ExecuteCommand('moveCursor', j);
|
|
finally
|
|
j.Free;
|
|
end;
|
|
end;
|
|
|
|
function TframeTextEditor.GetSelectedCol: Integer;
|
|
begin
|
|
if FSelectedRangeIsBackwards
|
|
then Result := FSelectedRange.Left
|
|
else Result := FSelectedRange.Right;
|
|
end;
|
|
|
|
function TframeTextEditor.GetSelectedRange: TRect;
|
|
begin
|
|
Result := FSelectedRange;
|
|
end;
|
|
|
|
function TframeTextEditor.GetSelectedRow: Integer;
|
|
begin
|
|
if FSelectedRangeIsBackwards
|
|
then Result := FSelectedRange.Top
|
|
else Result := FSelectedRange.Bottom;
|
|
end;
|
|
|
|
procedure TframeTextEditor.SetText(Value: WideString);
|
|
var
|
|
v: TJSONString;
|
|
begin
|
|
RefreshOptions;
|
|
|
|
if FHasBeenLoaded then
|
|
begin
|
|
v := TJSONString.Create(Value);
|
|
try
|
|
ExecuteCommand('setText', v);
|
|
finally
|
|
v.Free;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
FLoading := True;
|
|
try
|
|
LoadFileInBrowser(Value);
|
|
finally
|
|
FLoading := False;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.SetTextFileFormat(const Value: TTextFileFormat);
|
|
var
|
|
FSource: string;
|
|
begin
|
|
if FTextFileFormat <> Value then
|
|
begin
|
|
FTextFileFormat := Value;
|
|
if FTextFileFormat = tffANSI then
|
|
begin
|
|
// Recode to ANSI
|
|
FSource := string(AnsiString(modWebHttpServer.AppSource.GetSource(FFileName)));
|
|
SetText(FSource);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.SyntaxColourChange;
|
|
begin
|
|
// TODO: syntax colouring
|
|
end;
|
|
|
|
procedure TframeTextEditor.UpdateState(ALocation: string);
|
|
begin
|
|
if cef.HasFocus then
|
|
begin
|
|
if ALocation <> '' then
|
|
begin
|
|
FSelectedRange.Top := StrToIntDef(StrToken(ALocation, ','),0);
|
|
FSelectedRange.Left := StrToIntDef(StrToken(ALocation, ','),0);
|
|
FSelectedRange.Bottom := StrToIntDef(StrToken(ALocation, ','),0);
|
|
FSelectedRange.Right := StrToIntDef(StrToken(ALocation, ','),0);
|
|
FSelectedRangeIsBackwards := StrToIntDef(StrToken(ALocation, ','),0) > 0;
|
|
frmKeymanDeveloper.barStatus.Panels[0].Text := Format('Line %d, Col %d', [FSelectedRange.Top+1,FSelectedRange.Left+1]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.WMUser_FireCommand(var Message: TMessage);
|
|
var
|
|
params: TStringList;
|
|
begin
|
|
params := TStringList(Message.LParam);
|
|
if (params.Count > 0) and (params[0] = 'command') then
|
|
begin
|
|
params.Delete(0);
|
|
FireCommand(params);
|
|
end;
|
|
params.Free;
|
|
end;
|
|
|
|
procedure TframeTextEditor.WMUser_SyntaxColourChange(var Message: TMessage);
|
|
begin
|
|
ExecuteCommand('reloadSettings');
|
|
end;
|
|
|
|
procedure TframeTextEditor.WMUser_TextEditor_Command(var Message: TMessage);
|
|
begin
|
|
case Message.wParam of
|
|
TEXTEDITOR_CONTEXTMENU_SHOWCHARACTER:
|
|
modActionsTextEditor.actTextEditor_ShowCharacter.Execute;
|
|
TEXTEDITOR_CONTEXTMENU_CONVERTTOCHARACTERS:
|
|
modActionsTextEditor.actTextEditor_ConvertToCharacters.Execute;
|
|
end;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
- Paragraph colour management and errors -
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TframeTextEditor.ClearError;
|
|
begin
|
|
ExecuteCommand('highlightError');
|
|
end;
|
|
|
|
{ IKMDEditActions }
|
|
|
|
function TframeTextEditor.CanClearSelection: Boolean;
|
|
begin
|
|
Result := FHasSelection;
|
|
end;
|
|
|
|
function TframeTextEditor.CanCopy: Boolean;
|
|
begin
|
|
Result := FHasSelection;
|
|
end;
|
|
|
|
function TframeTextEditor.CanCut: Boolean;
|
|
begin
|
|
Result := FHasSelection;
|
|
end;
|
|
|
|
function TframeTextEditor.CanPaste: Boolean;
|
|
begin
|
|
Result := Clipboard.HasFormat(CF_TEXT);
|
|
end;
|
|
|
|
function TframeTextEditor.CanRedo: Boolean;
|
|
begin
|
|
Result := FCanRedo;
|
|
end;
|
|
|
|
function TframeTextEditor.CanSelectAll: Boolean;
|
|
begin
|
|
Result := True; // TODO: Only if we have text?
|
|
end;
|
|
|
|
function TframeTextEditor.CanUndo: Boolean;
|
|
begin
|
|
Result := FCanUndo;
|
|
end;
|
|
|
|
procedure TframeTextEditor.Undo;
|
|
begin
|
|
ExecuteCommand('editUndo');
|
|
end;
|
|
|
|
procedure TframeTextEditor.Redo;
|
|
begin
|
|
ExecuteCommand('editRedo');
|
|
end;
|
|
|
|
procedure TframeTextEditor.SelectAll;
|
|
begin
|
|
ExecuteCommand('editSelectAll');
|
|
end;
|
|
|
|
procedure TframeTextEditor.ClearSelection;
|
|
begin
|
|
cef.cef.ClipboardDel;
|
|
end;
|
|
|
|
procedure TframeTextEditor.CopyToClipboard;
|
|
begin
|
|
cef.cef.ClipboardCopy;
|
|
end;
|
|
|
|
procedure TframeTextEditor.CutToClipboard;
|
|
begin
|
|
cef.cef.ClipboardCut;
|
|
end;
|
|
|
|
procedure TframeTextEditor.DebugClearBreakpoint(ALine: Integer);
|
|
begin
|
|
ExecuteLineCommand(ALine, 'clearBreakpoint');
|
|
end;
|
|
|
|
procedure TframeTextEditor.DebugSetBreakpoint(ALine: Integer);
|
|
begin
|
|
ExecuteLineCommand(ALine, 'setBreakpoint');
|
|
end;
|
|
|
|
procedure TframeTextEditor.DebugUpdateExecutionPoint(ALine: Integer);
|
|
begin
|
|
ExecuteLineCommand(ALine, 'updateExecutionPoint');
|
|
end;
|
|
|
|
procedure TframeTextEditor.PasteFromClipboard;
|
|
begin
|
|
cef.cef.ClipboardPaste;
|
|
end;
|
|
|
|
{ IKMDSearchActions }
|
|
|
|
function TframeTextEditor.CanEditFind: Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
function TframeTextEditor.CanEditFindNext: Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TframeTextEditor.EditFind;
|
|
begin
|
|
if not cef.HasFocus then Exit;
|
|
ExecuteCommand('searchFind');
|
|
end;
|
|
|
|
procedure TframeTextEditor.EditFindNext;
|
|
begin
|
|
if not cef.HasFocus then Exit;
|
|
ExecuteCommand('searchFindNext');
|
|
end;
|
|
|
|
procedure TframeTextEditor.EditReplace;
|
|
begin
|
|
if not cef.HasFocus then Exit;
|
|
ExecuteCommand('searchReplace');
|
|
end;
|
|
|
|
procedure TframeTextEditor.ExecuteCommand(const command: string; const parameters: TJSONValue);
|
|
begin
|
|
if Assigned(parameters) then
|
|
begin
|
|
cef.cef.ExecuteJavaScript('window.editorGlobalContext.'+command+'('+parameters.ToJSON+')', '');
|
|
end
|
|
else
|
|
begin
|
|
cef.cef.ExecuteJavaScript('window.editorGlobalContext.'+command+'()', '');
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.ExecuteLineCommand(ALine: Integer;
|
|
const command: string);
|
|
var
|
|
v: TJSONNumber;
|
|
begin
|
|
v := TJSONNumber.Create(ALine);
|
|
try
|
|
ExecuteCommand(command, v);
|
|
finally
|
|
v.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.FindError(ln: Integer);
|
|
begin
|
|
ClearError;
|
|
if (ln <= 0) then Exit;
|
|
|
|
ExecuteLineCommand(ln, 'highlightError');
|
|
SetSelectedRow(ln);
|
|
end;
|
|
|
|
function TframeTextEditor.OffsetToLine(Offset: Integer): Integer; // I4083
|
|
var
|
|
line: Integer;
|
|
begin
|
|
with TStringList.Create do
|
|
try
|
|
Text := Self.GetText;
|
|
line := 0;
|
|
while offset > 0 do
|
|
begin
|
|
Dec(offset, Strings[line].Length + 2);
|
|
Inc(line);
|
|
end;
|
|
finally
|
|
Free;
|
|
end;
|
|
|
|
Result := line;
|
|
end;
|
|
|
|
procedure TframeTextEditor.FindErrorByOffset(offset: Integer); // I4083
|
|
begin
|
|
ClearError;
|
|
FindError(OffsetToLine(offset));
|
|
end;
|
|
|
|
procedure TframeTextEditor.FireCommand(const commands: TStringList);
|
|
var
|
|
i: Integer;
|
|
command0, command: string;
|
|
commandParams: TArray<string>;
|
|
begin
|
|
i := 0;
|
|
while i < commands.Count do
|
|
begin
|
|
command0 := commands[i];
|
|
commandParams := command0.Split([',']);
|
|
command := commandParams[0];
|
|
if command = 'modified' then Changed // I3948
|
|
else if command = 'undo-disable' then FCanUndo := False
|
|
else if command = 'undo-enable' then FCanUndo := True
|
|
else if command = 'redo-disable' then FCanRedo := False
|
|
else if command = 'redo-enable' then FCanRedo := True
|
|
else if command = 'has-selection' then FHasSelection := True
|
|
else if command = 'no-selection' then FHasSelection := False
|
|
else if command = 'breakpoint-clicked' then DoBreakpointClicked(commandParams[1])
|
|
else if command0.StartsWith('insert-mode,') then
|
|
begin
|
|
UpdateInsertState(command0.Substring('insert-mode,'.Length));
|
|
end
|
|
else if command0.StartsWith('location,') then
|
|
begin
|
|
UpdateState(command0.Substring('location,'.Length));
|
|
end
|
|
else if command0.StartsWith('token,') then
|
|
begin
|
|
UpdateToken(command0.Substring('token,'.Length));
|
|
end
|
|
else ShowMessage('keyman:'+commands.Text);
|
|
Inc(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.DoBreakpointClicked(const line: string);
|
|
begin
|
|
if Assigned(FOnBreakpointClicked) then
|
|
FOnBreakpointClicked(Self, StrToInt(line));
|
|
end;
|
|
|
|
procedure TframeTextEditor.UpdateToken(command: string);
|
|
var
|
|
col: Integer;
|
|
line: string;
|
|
x, tx: Integer;
|
|
token, prevtoken: WideString;
|
|
FHelpTopic, FPrevHelpTopic: WideString;
|
|
begin
|
|
token := StrToken(command, ',');
|
|
if token = 'null' then
|
|
col := 0
|
|
else if not TryStrToInt(token, col) then
|
|
Exit;
|
|
|
|
line := command;
|
|
|
|
x := col+1;
|
|
|
|
token := GetTokenAtCursor(line, x, tx, prevtoken);
|
|
if (FHasSelection) and (token <> '') then
|
|
begin
|
|
prevtoken := line;
|
|
if (x > tx) and CharInSet(token[1], ['"', '''']) then
|
|
prevtoken := token[1] + prevtoken + token[1];
|
|
|
|
if not TKeyboardParser_Line.GetXStr(prevtoken, token) then
|
|
token := line;
|
|
|
|
x := 1;
|
|
tx := 1;
|
|
end
|
|
else
|
|
token := GetTokenAtCursor(line, x, tx, prevtoken);
|
|
|
|
UpdateCharacterMap(False, token, x, tx, FHasSelection);
|
|
|
|
FHelpTopic := token;
|
|
FPrevHelpTopic := prevtoken;
|
|
|
|
if EditorFormat = efKMN then
|
|
begin
|
|
if not IsValidHelpToken(FHelpTopic, False) then
|
|
begin
|
|
if not IsValidHelpToken(FPrevHelpTopic, False)
|
|
then FHelpTopic := ''
|
|
else FHelpTopic := FPrevHelpTopic;
|
|
end;
|
|
|
|
if FHelpTopic <> '' then
|
|
begin
|
|
HelpKeyword := FHelpTopic;
|
|
|
|
if Assigned(frmHelp) and frmHelp.Showing then
|
|
frmHelp.QueueRefresh;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TframeTextEditor.GetHelpTopic: string;
|
|
begin
|
|
if FEditorFormat <> efKMN then
|
|
Exit(SHelpTopic_Context_TextEditor);
|
|
|
|
Result := HelpKeyword;
|
|
end;
|
|
|
|
procedure TframeTextEditor.TntFormDestroy(Sender: TObject);
|
|
begin
|
|
inherited;
|
|
if FFileName <> '' then
|
|
modWebHttpServer.AppSource.UnregisterSource(FFileName);
|
|
|
|
FreeAndNil(FCharFont);
|
|
FreeAndNil(FCodeFont);
|
|
end;
|
|
|
|
procedure TframeTextEditor.CharMapDragOver(Sender, Source: TObject; X, Y: Integer;
|
|
State: TDragState; var Accept: Boolean);
|
|
var
|
|
j: TJSONObject;
|
|
begin
|
|
cef.cefwp.SetFocus;
|
|
j := TJSONObject.Create;
|
|
try
|
|
j.AddPair('x', TJSONNumber.Create(X));
|
|
j.AddPair('y', TJSONNumber.Create(Y));
|
|
j.AddPair('state', GetEnumName(TypeInfo(TDragState), Ord(State)));
|
|
ExecuteCommand('charmapDragOver', j);
|
|
finally
|
|
j.Free;
|
|
end;
|
|
|
|
// We cannot test acceptance via event because it is asynchronous
|
|
// So we will just assume we can accept and throw it away if it is outside bounds
|
|
// during drop.
|
|
Accept := True;
|
|
end;
|
|
|
|
procedure TframeTextEditor.CharMapDragDrop(Sender, Source: TObject; X, Y: Integer);
|
|
var
|
|
j: TJSONObject;
|
|
cdo: TCharacterDragObject;
|
|
begin
|
|
cef.cefwp.SetFocus;
|
|
cdo := Source as TCharacterDragObject;
|
|
j := TJSONObject.Create;
|
|
try
|
|
j.AddPair('x', TJSONNumber.Create(X));
|
|
j.AddPair('y', TJSONNumber.Create(Y));
|
|
j.AddPair('text', cdo.Text[cdo.InsertType]);
|
|
ExecuteCommand('charmapDragDrop', j);
|
|
finally
|
|
j.Free;
|
|
end;
|
|
end;
|
|
|
|
end.
|