mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-20 21:07:52 +00:00
The focus code paths for Chromium frames (CEF4Delphi) were incorrect. The return value for preventing focus on navigation events was inverted, and then other code was attempting to work around this inversion, wrongly. Fixed the root issue of the inverted test (it would help if the parameter cefSetFocus was known as `Handled` rather than `Result`!) and then the remaining code pathways became far simpler. Fixes #2314.
1126 lines
28 KiB
ObjectPascal
1126 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;
|
|
FErrorLine: Integer;
|
|
|
|
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; params: TStringList; wasHandled: 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 cefPreKeySyncEvent(Sender: TObject; e: TCEFHostKeyEventData; out isShortcut, Handled: Boolean);
|
|
procedure cefKeyEvent(Sender: TObject; e: TCEFHostKeyEventData; wasShortcut, wasHandled: Boolean);
|
|
|
|
procedure WMUser_TextEditor_Command(var Message: TMessage); message WM_USER_TextEditor_Command;
|
|
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; params: TStringList; wasHandled: Boolean);
|
|
begin
|
|
AssertVclThread;
|
|
if (params.Count > 0) and (params[0] = 'command') then
|
|
begin
|
|
params.Delete(0);
|
|
FireCommand(params);
|
|
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.cefKeyEvent(Sender: TObject; e: TCEFHostKeyEventData;
|
|
wasShortcut, wasHandled: Boolean);
|
|
begin
|
|
AssertVclThread;
|
|
if e.event.windows_key_code = VK_ESCAPE then
|
|
begin
|
|
ClearError;
|
|
end;
|
|
end;
|
|
|
|
procedure TframeTextEditor.cefPreKeySyncEvent(Sender: TObject;
|
|
e: TCEFHostKeyEventData; out isShortcut, Handled: Boolean);
|
|
begin
|
|
AssertCefThread;
|
|
Handled := False;
|
|
// While FErrorLine is from main thread, this is unlikely to cause trouble so
|
|
// will not worry about synchronisation
|
|
if (e.event.windows_key_code = VK_ESCAPE) and (FErrorLine > 0) then
|
|
Handled := True;
|
|
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.OnPreKeySyncEvent := cefPreKeySyncEvent;
|
|
cef.OnKeyEvent := cefKeyEvent;
|
|
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', 'wordlisttsv'
|
|
);
|
|
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;
|
|
buffer: TBytes;
|
|
encoding: TEncoding;
|
|
p: Int64;
|
|
BufferLength, PreambleLength: Integer;
|
|
begin
|
|
FLoading := True;
|
|
try
|
|
{ Sniff the buffer preamble }
|
|
p := AStream.Position;
|
|
SetLength(buffer, 16);
|
|
BufferLength := AStream.Read(buffer, 16);
|
|
SetLength(buffer, BufferLength);
|
|
encoding := nil;
|
|
PreambleLength := TEncoding.GetBufferEncoding(buffer, encoding, TEncoding.UTF8);
|
|
AStream.Position := p + PreambleLength;
|
|
|
|
s := TStringList.Create;
|
|
try
|
|
p := AStream.Position;
|
|
try
|
|
s.LoadFromStream(AStream, encoding); // prolog determines encoding // I3337
|
|
except
|
|
on E:EEncodingError do
|
|
begin
|
|
// Reload as ANSI, because not valid UTF-8
|
|
AStream.Position := p;
|
|
s.LoadFromStream(AStream, TEncoding.Default);
|
|
end;
|
|
end;
|
|
|
|
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
|
|
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) and modWebHttpServer.AppSource.IsSourceRegistered(FFileName) 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_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;
|
|
|
|
FErrorLine := ln;
|
|
|
|
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.
|