{ *************************************************************************** }
{                                                                             }
{ EControl Syntax Editor SDK                                                  }
{                                                                             }
{ Copyright (c) 2004 - 2008 EControl Ltd., Zaharov Michael                    }
{     www.econtrol.ru                                                         }
{     support@econtrol.ru                                                     }
{                                                                             }
{ *************************************************************************** }
{$I ecSyntEdit.INC}

unit ecKeyMap;

interface

uses Windows, Classes, Contnrs, Dialogs, Controls, ecStrUtils, SysUtils, ecLists
     {$IFDEF EC_DOTNET}, System.Runtime.InteropServices, System.Text{$ENDIF};

type
  TExecuteCommandEvent = procedure(Sender: TObject; Command: integer;
                           Data: ecPointer; var Handled: Boolean) of object;

  TKeyQueue = class(TPersistent)
  private
    FKeyList: TList;
    FLastTick: Cardinal;
    FMaxInterval: Cardinal;
    FMaxQueueSize: integer;
    function GetCount: integer;
    function GetKeys(Index: integer): TShortCut;
    procedure SetMaxQueueSize(const Value: integer);
    procedure CutList;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;

    procedure Clear;
    procedure Add(ShortCut: TShortCut);

    property Count: integer read GetCount;
    property Keys[Index: integer]: TShortCut read GetKeys; default;
  published
    property MaxInterval: Cardinal read FMaxInterval write FMaxInterval default 1000;
    property MaxQueueSize: integer read FMaxQueueSize write SetMaxQueueSize default 3;
  end;

  TKeyDef = class(TCollectionItem)
  private
    FOptionalControlKeys: Boolean;
    FShortCut: TShortCut;
    procedure SetOptionalControlKeys(const Value: Boolean);
    procedure SetShortCut(const Value: TShortCut);
    function GetAsString: string;
    procedure SetAsString(const Value: string);
  protected
    function GetDisplayName: String; override;
  public
    procedure Assign(Source: TPersistent); override;
    function IsHandled(Key: TShortCut; IgnoreCtrl: Boolean): Boolean;
    function IsEqual(Other: TKeyDef): Boolean;

    property AsString: string read GetAsString write SetAsString;
  published
    property ShortCut: TShortCut read FShortCut write SetShortCut;
    property OptionalControlKeys: Boolean read FOptionalControlKeys write SetOptionalControlKeys default False;
  end;

  TKeyDefs = class(TCollection)
  private
    function GetItem(Index: integer): TKeyDef;
  public
    function Add: TKeyDef;
    property Items[Index: integer]: TKeyDef read GetItem; default;
  end;

  TKeyStroke = class(TCollectionItem)
  private
    FKeyDefs: TKeyDefs;
    procedure SetKeyDefs(const Value: TKeyDefs);
    function GetAsString: string;
    procedure SetAsString(const Value: string);
  protected
    function GetDisplayName: String; override;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    function IsHandled(KeyQueue: TKeyQueue; IgnoreCtrl: Boolean): Boolean;
    function IsEqual(Other: TKeyStroke): Boolean;

    property AsString: string read GetAsString write SetAsString;
  published
    property KeyDefs: TKeyDefs read FKeyDefs write SetKeyDefs;
  end;

  TKeyStrokes = class(TCollection)
  private
    function GetItem(Index: integer): TKeyStroke;
  public
    function IsEqual(Other: TKeyStrokes): Boolean;
    function Add: TKeyStroke;
    function AddDbl(Key: DWORD): TKeyStroke; // for compatibility
    function Last: TKeyStroke;
    property Items[Index: integer]: TKeyStroke read GetItem; default;
  end;

  TecCommandItem = class(TCollectionItem)
  private
    FCommand: integer;
    FCaption: string;
    FKeyStrokes: TKeyStrokes;
    FDisplayName: string;
    FCustomizable: Boolean;
    FCategory: string;
    procedure SetCaption(const Value: string);
    procedure SetCommand(const Value: integer);
    procedure SetKeyStrokes(const Value: TKeyStrokes);
    procedure SetCategory(const Value: string);
    function GetMenuShortCut: TShortCut;
  protected
    function GetDisplayName: String; override;
    procedure SetDisplayName(const Value: String); override;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;

    function IsHandled(KeyQueue: TKeyQueue; IgnoreCtrl: Boolean): integer;

    property MenuShortCut: TShortCut read GetMenuShortCut;
  published
    property Command: integer read FCommand write SetCommand;
    property KeyStrokes: TKeyStrokes read FKeyStrokes write SetKeyStrokes;
    property Customizable: Boolean read FCustomizable write FCustomizable default True;
    property Caption: string read FCaption write SetCaption;
    property Category: string read FCategory write SetCategory;
    property DisplayName;
  end;

  TecCommandItems = class(TOwnedCollection)
  private
    FIDIndex: TSortedList;
    function GetItem(Index: integer): TecCommandItem;
  protected
    procedure Update(Item: TCollectionItem); override;
  public
    destructor Destroy; override;

    function ItemByID(ID: integer): TecCommandItem;
    function Add: TecCommandItem;
    property Items[Index: integer]: TecCommandItem read GetItem; default;
  end;

  EDuplicateKeyStroke = class(Exception)
  public
    KeyStrokeIdx: integer;
    Command: TecCommandItem;
    constructor CreateCmd(Cmd: TecCommandItem; AKeyStrokeIdx: integer);
  end;

  TSyntKeyMapping = class(TComponent)
  private
    FItems: TecCommandItems;
    FUseFirstControlKeys: Boolean;
    FOnExecuteCommand: TExecuteCommandEvent;
    procedure SetItems(const Value: TecCommandItems);
    procedure ReadData(Reader: TReader);
    procedure SetAsString(const Value: string);
    function GetAsString: string;
  protected
    procedure DefineProperties(Filer: TFiler); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;

    procedure Add(Command: integer; const Category, Caption, Desc: string; Key1: DWORD = 0; Key2: DWORD = 0);

    function IsHandled(KeyQueue: TKeyQueue): TecCommandItem;
    function IsHandledCmd(KeyQueue: TKeyQueue): integer;
    function CommandByID(Cmd: integer): TecCommandItem;
    function Customize: Boolean;
    function ExecuteCommand(Sender: TObject; Command: integer; Data: ecPointer): Boolean; virtual;
    procedure Validate;

  published
    property AsString: string read GetAsString write SetAsString stored False;
    property Items: TecCommandItems read FItems write SetItems;
    property UseFirstControlKeys: Boolean read FUseFirstControlKeys write FUseFirstControlKeys default False;
    property OnExecuteCommand: TExecuteCommandEvent read FOnExecuteCommand write FOnExecuteCommand;
  end;

procedure InitDefaultKeyMapping(KeyMapping: TSyntKeyMapping);
function DefaultKeyMapping: TSyntKeyMapping;

implementation

uses ecCmdConst, ecKeyMapDlg, menus, ecSysUtils, Math;

{ TKeyQueue }

constructor TKeyQueue.Create;
begin
  inherited Create;
  FMaxInterval := 1000;
  FMaxQueueSize := 3;
  FKeyList := TList.Create;
end;

destructor TKeyQueue.Destroy;
begin
  FreeAndNil(FKeyList);
  inherited;
end;

procedure TKeyQueue.Add(ShortCut: TShortCut);
begin
  if (GetTickCount - FLastTick > FMaxInterval) and (FMaxInterval <> 0) then
      Clear;
  FLastTick := GetTickCount;
  FKeyList.Insert(0, TObject(ShortCut));
  CutList;
end;

procedure TKeyQueue.Clear;
begin
  FKeyList.Clear;
end;

function TKeyQueue.GetCount: integer;
begin
  Result := FKeyList.Count;
end;

function TKeyQueue.GetKeys(Index: integer): TShortCut;
begin
  Result := TShortCut(FKeyList[Index]);
end;

procedure TKeyQueue.SetMaxQueueSize(const Value: integer);
begin
  if Value < 1 then
    raise Exception.Create('MaxQueueSize should be greater 1');
  FMaxQueueSize := Value;
  CutList;
end;

procedure TKeyQueue.CutList;
begin
  while FKeyList.Count > FMaxQueueSize do
    FKeyList.Delete(FKeyList.Count - 1);
end;

procedure TKeyQueue.Assign(Source: TPersistent);
begin
  if Source is TKeyQueue then
    with Source as TKeyQueue do
      begin
        Self.MaxInterval := MaxInterval;
        Self.MaxQueueSize := MaxQueueSize;
      end
  else inherited;
end;

{ TKeyDef }

procedure TKeyDef.Assign(Source: TPersistent);
begin
  if Source is TKeyDef then
    with Source as TKeyDef do
      begin
        Self.FOptionalControlKeys := FOptionalControlKeys;
        Self.FShortCut := FShortCut;
      end;
end;

function TKeyDef.GetAsString: string;
begin
  Result := ShortCutToText(FShortCut);
end;

function TKeyDef.GetDisplayName: String;
begin
  Result := AsString;
end;

function TKeyDef.IsEqual(Other: TKeyDef): Boolean;
begin
  Result := (Other.ShortCut = ShortCut) and
            (Other.OptionalControlKeys = OptionalControlKeys);
end;

function TKeyDef.IsHandled(Key: TShortCut; IgnoreCtrl: Boolean): Boolean;
const KeyMask = $0FFF;
begin
  if FOptionalControlKeys or IgnoreCtrl then
    Result := (FShortCut and KeyMask) = (Key and KeyMask)
  else
    Result := FShortCut = Key;
end;

procedure TKeyDef.SetAsString(const Value: string);
begin
  FShortCut := TextToShortCut(Value);
end;

procedure TKeyDef.SetOptionalControlKeys(const Value: Boolean);
begin
  if FOptionalControlKeys <> Value then
    begin
      FOptionalControlKeys := Value;
      Changed(False);
    end;
end;

procedure TKeyDef.SetShortCut(const Value: TShortCut);
begin
  if FShortCut <> Value then
    begin
      FShortCut := Value;
      Changed(False);
    end;
end;

{ TKeyDefs }

function TKeyDefs.Add: TKeyDef;
begin
  Result := TKeyDef(inherited Add);
end;

function TKeyDefs.GetItem(Index: integer): TKeyDef;
begin
  Result := TKeyDef(inherited Items[Index]);
end;

{ TKeyStroke }

procedure TKeyStroke.Assign(Source: TPersistent);
begin
  if Source is TKeyStroke then
     KeyDefs := TKeyStroke(Source).KeyDefs;
end;

constructor TKeyStroke.Create(Collection: TCollection);
begin
  inherited;
  FKeyDefs := TKeyDefs.Create(TKeyDef);
end;

destructor TKeyStroke.Destroy;
begin
  FreeAndNil(FKeyDefs);
  inherited;
end;

function TKeyStroke.GetAsString: string;
var i: integer;
begin
  Result := '';
  if KeyDefs.Count > 0 then
    begin
      Result := KeyDefs[0].AsString;
      for i := 1 to KeyDefs.Count - 1 do
        Result := Result + ' · ' + KeyDefs[i].AsString;
    end;
end;

function TKeyStroke.GetDisplayName: String;
begin
  Result := AsString;
end;

function TKeyStroke.IsEqual(Other: TKeyStroke): Boolean;
var i: integer;
begin
  Result := KeyDefs.Count = Other.KeyDefs.Count;
  if Result then
    for i := 0 to KeyDefs.Count - 1 do
      if not KeyDefs[i].IsEqual(Other.KeyDefs[i]) then
      begin
        Result := False;
        Exit;
      end;
end;

function TKeyStroke.IsHandled(KeyQueue: TKeyQueue; IgnoreCtrl: Boolean): Boolean;
var i, N: integer;
begin
  N := KeyDefs.Count;
  Result := (N > 0) and (KeyQueue.Count >= N);
  if Result then
    for i := N - 1 downto 0 do
      if not KeyDefs[i].IsHandled(KeyQueue[N - 1 - i], IgnoreCtrl and (i > 0)) then
        begin
          Result := False;
          Exit;
        end;
end;

procedure TKeyStroke.SetAsString(const Value: string);
var k: integer;
    S, S1: string;
begin
  KeyDefs.Clear;
  S := Value;
  repeat
    k := Pos('=>', S);
    if k = 0 then S1 := S else
      begin
        S1 := Copy(S, 1, k - 1);
        Delete(S, 1, k + 1);
      end;
    S1 := Trim(S1);
    if S1 <> '' then
      KeyDefs.Add.AsString := S1;
  until k = 0;
end;

procedure TKeyStroke.SetKeyDefs(const Value: TKeyDefs);
begin
  FKeyDefs.Assign(Value);
end;

{ TKeyStrokes }

function TKeyStrokes.Add: TKeyStroke;
begin
  Result := TKeyStroke(inherited Add);
end;

function TKeyStrokes.AddDbl(Key: DWORD): TKeyStroke;
begin
  if Key = 0 then Result := nil else
    begin
      Result := Add;
      if (Key and $FFFF) <> 0 then
        Result.KeyDefs.Add.ShortCut := TShortCut(Key and $FFFF);
      Key := Key shr 16;
      if (Key and $FFFF) <> 0 then
        Result.KeyDefs.Add.ShortCut := TShortCut(Key and $FFFF);
    end;
end;

function TKeyStrokes.GetItem(Index: integer): TKeyStroke;
begin
  Result := TKeyStroke(inherited Items[Index]);
end;

function TKeyStrokes.IsEqual(Other: TKeyStrokes): Boolean;
var i: integer;
begin
  Result := Count = Other.Count;
  if Result then
    for i := 0 to Count - 1 do
      if not Items[i].IsEqual(Other[i]) then
      begin
        Result := False;
        Exit;
      end;
end;

function TKeyStrokes.Last: TKeyStroke;
begin
  Result := Items[Count - 1];
end;

{ TecCommandItem }

constructor TecCommandItem.Create(Collection: TCollection);
begin
  inherited;
  FKeyStrokes := TKeyStrokes.Create(TKeyStroke);
  FCustomizable := True;
end;

destructor TecCommandItem.Destroy;
begin
  FreeAndNil(FKeyStrokes);
  inherited;
end;

procedure TecCommandItem.Assign(Source: TPersistent);
begin
  if Source is TecCommandItem then
    with Source as TecCommandItem do
      begin
        Self.KeyStrokes := KeyStrokes;
        Self.DisplayName := DisplayName;
        Self.Command := Command;
        Self.Caption := Caption;
        Self.Category := Category;
        Self.Customizable := Customizable;
      end
  else inherited;
end;

procedure TecCommandItem.SetCaption(const Value: string);
begin
  if FCaption <> Value then
    begin
      FCaption := Value;
      Changed(False);
    end;
end;

procedure TecCommandItem.SetCommand(const Value: integer);
begin
  if FCommand <> Value then
    begin
//      if (Value <> 0) and
//         (TecCommandItems(Collection).ItemByID(Value) <> nil) then
//        raise Exception.CreateFmt('Command %d is already defined', [Value]);
      FCommand := Value;
      Changed(False);
    end;
end;

procedure TecCommandItem.SetKeyStrokes(const Value: TKeyStrokes);
begin
  FKeyStrokes.Assign(Value);
end;

function TecCommandItem.GetDisplayName: String;
begin
  Result := FDisplayName;
end;

procedure TecCommandItem.SetDisplayName(const Value: String);
begin
  FDisplayName := Value;
end;

function TecCommandItem.IsHandled(KeyQueue: TKeyQueue; IgnoreCtrl: Boolean): integer;
var i: integer;
begin
  Result := 0;
  for i := 0 to FKeyStrokes.Count - 1 do
    if FKeyStrokes[i].IsHandled(KeyQueue, IgnoreCtrl) and
       (Result < FKeyStrokes[i].KeyDefs.Count) then
      begin
        Result := FKeyStrokes[i].KeyDefs.Count;
      end;
end;

procedure TecCommandItem.SetCategory(const Value: string);
begin
  if FCategory <> Value then
    begin
      FCategory := Value;
      Changed(False);
    end;
end;

function TecCommandItem.GetMenuShortCut: TShortCut;
begin
  if (KeyStrokes.Count > 0) and (KeyStrokes[0].KeyDefs.Count > 0) then
    Result := KeyStrokes[0].KeyDefs[0].ShortCut
  else
    Result := 0;
end;

{ TecCommandItems }

function TecCommandItems.Add: TecCommandItem;
begin
  Result := TecCommandItem(inherited Add);
end;

destructor TecCommandItems.Destroy;
begin
  FreeAndNil(FIDIndex);
  inherited;
end;

function TecCommandItems.GetItem(Index: integer): TecCommandItem;
begin
  Result := TecCommandItem(inherited Items[Index]);
end;

type
  TCmdKey = class(TSortedItem)
  protected
    Command: TecCommandItem;
    function GetKey: Integer; override;
  public
    constructor Create(ACommand: TecCommandItem);
  end;

function TCmdKey.GetKey: Integer;
begin
  Result := Command.Command;
end;

constructor TCmdKey.Create(ACommand: TecCommandItem);
begin
  inherited Create;
  Command := ACommand;
end;

function TecCommandItems.ItemByID(ID: integer): TecCommandItem;
var i: integer;
begin
  if not Assigned(FIDIndex) then
    begin
      FIDIndex := TSortedList.Create(True);
      for i := 0 to Count - 1 do
        FIDIndex.Add(TCmdKey.Create(Items[i]));
    end;
  i := FIDIndex.PriorAt(ID);
  if (i <> -1) and (TCmdKey(FIDIndex[i]).Command.Command = ID) then
    Result := TCmdKey(FIDIndex[i]).Command
  else
    Result := nil;
end;

procedure TecCommandItems.Update(Item: TCollectionItem);
begin
  inherited;
  FreeAndNil(FIDIndex);
end;

{ TSyntKeyMapping }

constructor TSyntKeyMapping.Create(AOwner: TComponent);
begin
  inherited;
  FItems := TecCommandItems.Create(Self, TecCommandItem);
end;

destructor TSyntKeyMapping.Destroy;
begin
  FreeAndNil(FItems);
  inherited;
end;

procedure TSyntKeyMapping.Add(Command: integer; const Category, Caption,
  Desc: string; Key1, Key2: DWORD);
var Item: TecCommandItem;
  procedure AddKey(Key: DWORD);
  var K: TShortCut;
  begin
    if Key = 0 then Exit;
    with Item.KeyStrokes.Add do
      begin
        K := TShortCut(Key and $FFFF);
        if K <> 0 then KeyDefs.Add.ShortCut := K;
        K := TShortCut(Key shr 16);
        if K <> 0 then KeyDefs.Add.ShortCut := K;
      end;
  end;
begin
  Item := FItems.Add;
  Item.Command := Command;
  Item.Category := Category;
  Item.Caption := Caption;
  Item.DisplayName := Desc;
  if Key1 = $FFFFFFFF then
    Item.Customizable := False
  else
    begin
      AddKey(Key1);
      AddKey(Key2);
    end;
end;

function TSyntKeyMapping.IsHandled(KeyQueue: TKeyQueue): TecCommandItem;
var i, mx, N: integer;
begin
  mx := 0;
  Result := nil;
  for i := 0 to FItems.Count - 1 do
    begin
      N := FItems[i].IsHandled(KeyQueue, FUseFirstControlKeys);
      if N > mx then
        begin
          Result := FItems[i];
          mx := N;
        end;
    end;
end;

function TSyntKeyMapping.IsHandledCmd(KeyQueue: TKeyQueue): integer;
var cmd: TecCommandItem;
begin
  cmd := IsHandled(KeyQueue);
  if cmd <> nil then
    Result := cmd.Command
  else
    Result := 0;
end;

procedure TSyntKeyMapping.SetItems(const Value: TecCommandItems);
begin
  FItems.Assign(Value);
end;

function TSyntKeyMapping.CommandByID(Cmd: integer): TecCommandItem;
begin
  Result := FItems.ItemByID(Cmd);
end;

procedure TSyntKeyMapping.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('KeyInfo', ReadData, nil, False);
end;

// For compatibility
procedure TSyntKeyMapping.ReadData(Reader: TReader);
var cmd: integer;
    Key1, Key2: DWORD;
    CmdObj: TecCommandItem;
begin
  InitDefaultKeyMapping(Self);
  Reader.ReadListBegin;
  while not Reader.EndOfList do
    begin
      cmd := Reader.ReadInteger;
      Key1 := DWORD(Reader.ReadInteger);
      Key2 := DWORD(Reader.ReadInteger);
      CmdObj := CommandByID(cmd);
      if Assigned(CmdObj) then
        begin
          CmdObj.KeyStrokes.Clear;
          CmdObj.KeyStrokes.AddDbl(Key1);
          CmdObj.KeyStrokes.AddDbl(Key2);
        end;
    end;
  Reader.ReadListEnd;
end;

function TSyntKeyMapping.Customize: Boolean;
var frm: TecKeyMappingEditor;
    copy: TSyntKeyMapping;
begin
  copy := TSyntKeyMapping.Create(nil);
  try
    copy.Assign(Self);
    frm := TecKeyMappingEditor.Create(nil);
    frm.KeyMapping := copy;
    frm.IsDesign := csDesigning in ComponentState;
    Result := ShowDialogFromFrame(frm, 'Customize Key Mapping') = mrOk;
    if Result then
      Self.Assign(copy);
  finally
    copy.Free;
  end;
end;

procedure TSyntKeyMapping.Assign(Source: TPersistent);
begin
  if Source is TSyntKeyMapping then
    with Source as TSyntKeyMapping do
      begin
        Self.Items := Items;
        Self.UseFirstControlKeys := UseFirstControlKeys;
      end
  else inherited;

end;

procedure TSyntKeyMapping.SetAsString(const Value: string);
var j, k, N1, N2: integer;
    cmd: TecCommandItem;
    Mem: TMemoryStream;
  function ReadInt: integer;
  begin
    Mem.ReadBuffer(Result, sizeof(integer));
  end;
begin
//  Items.Clear;
//  InitDefaultKeyMapping(Self);
  if Length(Value) < 2 then Exit;

  Mem := TMemoryStream.Create;
  try
    Mem.Size := Length(Value) div 2;
    {$IFDEF EC_DOTNET}
    HexToBin( System.Text.ASCIIEncoding.ASCII.GetBytes(Value), 0, Mem.Memory, 0, Mem.Size);
    {$ELSE}
    HexToBin(PChar(Value), PChar(Mem.Memory), Mem.Size);
    {$ENDIF}
    while Mem.Position < Mem.Size do
      begin
        N1 := ReadInt;
        cmd := CommandByID(N1);
        N1 := ReadInt;
        if Assigned(Cmd) then Cmd.KeyStrokes.Clear;
        for j := 0 to N1 - 1 do
          begin
            if Assigned(Cmd) then Cmd.KeyStrokes.Add;
            N2 := ReadInt;
            for k := 0 to N2 - 1 do
              begin
                if Assigned(Cmd) then Cmd.KeyStrokes.Last.KeyDefs.Add.ShortCut := ReadInt
                  else ReadInt;
              end;
          end;
      end;
  finally
    Mem.Free;
  end;
end;

function TSyntKeyMapping.GetAsString: string;
var //Def: TSyntKeyMapping;
    i, j, k: integer;
//    cmd: TecCommandItem;
    Mem: TMemoryStream;
    {$IFDEF EC_DOTNET}
    Buf: TBytes;
    {$ENDIF}
  procedure WriteInt(V: integer);
  begin
    Mem.WriteBuffer(V, sizeof(integer));
  end;
begin
  Mem := TMemoryStream.Create;
  try
//    Def := DefaultKeyMapping;
    for i := 0 to Items.Count - 1 do
      with Items[i] do
      if Command <> 0 then
      begin
//        cmd := Def.CommandByID(Command);
//        if Assigned(cmd) and not cmd.KeyStrokes.IsEqual(KeyStrokes) then
//          begin
            WriteInt(Command);
            WriteInt(KeyStrokes.Count);
            for j := 0 to KeyStrokes.Count - 1 do
              begin
                WriteInt(KeyStrokes[j].KeyDefs.Count);
                for k := 0 to KeyStrokes[j].KeyDefs.Count - 1 do
                  WriteInt(KeyStrokes[j].KeyDefs[k].ShortCut);
              end;
//          end;
      end;
    {$IFDEF EC_DOTNET}
    SetLength(Buf, Mem.Size * 2);
    BinToHex( Mem.Memory, 0, Buf, 0, Mem.Size);
    Result := System.Text.ASCIIEncoding.ASCII.GetChars(Buf);
    {$ELSE}
    if Mem.Size > 0 then
      begin
        SetLength(Result, Mem.Size * 2);
        BinToHex(PChar(Mem.Memory), PChar(Result), Mem.Size);
      end;
    {$ENDIF}
  finally
    Mem.Free;
  end;
end;

function TSyntKeyMapping.ExecuteCommand(Sender: TObject; Command: integer;
  Data: ecPointer): Boolean;
begin
  Result := False;
  if Assigned(FOnExecuteCommand) then
    FOnExecuteCommand(Sender, Command, Data, Result);
end;

procedure TSyntKeyMapping.Validate;
var i, j, k, l: integer;
    Key: TKeyStroke;
begin
  for i := Items.Count - 1 downto 0 do
    for j := 0 to Items[i].KeyStrokes.Count - 1 do
      begin
        Key := Items[i].KeyStrokes[j];
        for k := i downto 0 do
          for l := ifThen(k = i, j + 1, 0) to Items[k].KeyStrokes.Count - 1 do
            if Key.IsEqual(Items[k].KeyStrokes[l]) then
              raise EDuplicateKeyStroke.CreateCmd(Items[i], j);
      end;
end;

{ EDuplicateKeyStroke }

constructor EDuplicateKeyStroke.CreateCmd(Cmd: TecCommandItem;
  AKeyStrokeIdx: integer);
begin
  inherited Create(Format('Command "%s" has duplicated keystroke "%s"', [Cmd.DisplayName, Cmd.KeyStrokes[AKeyStrokeIdx].AsString]));
  Command := Cmd;
  KeyStrokeIdx := AKeyStrokeIdx;
end;

procedure InitDefaultKeyMapping(KeyMapping: TSyntKeyMapping);
const
  SNavNoSelect =     'Navigation, no select';
  SNavNormSelect =   'Navigation, normal select';
  SNavColSelect =    'Navigation, columnar select';
  SScrolling  =      'Scrolling';
  SStdActions =      'Standard actions';
  SDeleting =        'Deleting text';
  SInserting =       'Inserting text';
  SIndent =          'Indents and Tabs';
  SSelModes =        'Selection modes';
  SChangeCase =      'Change case';
  SCollapsing =      'Text folding';
  SBookmarks =       'Bookmarks';
  SMarkers =         'Markers';
  SMacros =          'Macros';
  SMisc =            'Miscellaneous';
  SFindReplace =     'Search & Replace';
  SBlockOper =       'Block operations';
  STools =           'Tools';
begin
  KeyMapping.Add(smLeft,         SNavNoSelect, '&Left',         'Move cursor left one char',         VK_LEFT);
  KeyMapping.Add(smRight,        SNavNoSelect, '&Right',        'Move cursor right one char',        VK_RIGHT);
  KeyMapping.Add(smUp,           SNavNoSelect, '&Up',           'Move cursor up one line',           VK_UP);
  KeyMapping.Add(smDown,         SNavNoSelect, '&Down',         'Move cursor down one line',         VK_DOWN);
  KeyMapping.Add(smWordLeft,     SNavNoSelect, 'Word Left',     'Move cursor left one word',         VK_LEFT + scCtrl);
  KeyMapping.Add(smWordRight,    SNavNoSelect, 'Word Right',    'Move cursor right one word',        VK_RIGHT + scCtrl);
  KeyMapping.Add(smLineStart,    SNavNoSelect, 'Begin of Line', 'Move cursor to beginning of line',  0);
  KeyMapping.Add(smLineEnd,      SNavNoSelect, 'End of Line',   'Move cursor to end of line',        0);
  KeyMapping.Add(smPageUp,       SNavNoSelect, 'Page Up',       'Move cursor up one page',           VK_PRIOR);
  KeyMapping.Add(smPageDown,     SNavNoSelect, 'Page Down',     'Move cursor down one page',         VK_NEXT);
  KeyMapping.Add(smPageLeft,     SNavNoSelect, 'Page Left',     'Move cursor right one page',        0);
  KeyMapping.Add(smPageRight,    SNavNoSelect, 'Page Right',    'Move cursor left one page',         0);
  KeyMapping.Add(smPageTop,      SNavNoSelect, 'Top of Page',   'Move cursor to top of page',        VK_PRIOR + scCtrl);
  KeyMapping.Add(smPageBottom,   SNavNoSelect, 'Bottom of Page','Move cursor to bottom of page',     VK_NEXT + scCtrl);
  KeyMapping.Add(smEditorTop,    SNavNoSelect, 'Begin of Text', 'Move cursor to absolute beginning', VK_HOME + scCtrl);
  KeyMapping.Add(smEditorBottom, SNavNoSelect, 'End of Text',   'Move cursor to absolute end',       VK_END + scCtrl);
  KeyMapping.Add(smFirstLetter,  SNavNoSelect,  'First Char',   'Move cursor to first char of line', VK_HOME);
  KeyMapping.Add(smLastLetter,   SNavNoSelect, 'Last Char',     'Move cursor to last char of line',  VK_END);
  KeyMapping.Add(smLeftUp,       SNavNoSelect, 'Left-up',       'Move cursor left and up at line start', 0);
  KeyMapping.Add(smGotoXY,       SNavNoSelect, '',              'Move cursor to specified position', $FFFFFFFF);

  KeyMapping.Add(smSelLeft,        SNavNormSelect, '', 'Move cursor and select left one char',         scShift + VK_LEFT);
  KeyMapping.Add(smSelRight,       SNavNormSelect, '', 'Move cursor and select right one char',        scShift + VK_RIGHT);
  KeyMapping.Add(smSelUp,          SNavNormSelect, '', 'Move cursor and select up one line',           scShift + VK_UP);
  KeyMapping.Add(smSelDown,        SNavNormSelect, '', 'Move cursor and select down one line',         scShift + VK_DOWN);
  KeyMapping.Add(smSelWordLeft,    SNavNormSelect, '', 'Move cursor and select left one word',         scShift + VK_LEFT + scCtrl);
  KeyMapping.Add(smSelWordRight,   SNavNormSelect, '', 'Move cursor and select right one word',        scShift + VK_RIGHT + scCtrl);
  KeyMapping.Add(smSelLineStart,   SNavNormSelect, '', 'Move cursor and select to beginning of line',  0);
  KeyMapping.Add(smSelLineEnd,     SNavNormSelect, '', 'Move cursor and select to end of line',        0);
  KeyMapping.Add(smSelPageUp,      SNavNormSelect, '', 'Move cursor and select up one page',           scShift + VK_PRIOR);
  KeyMapping.Add(smSelPageDown,    SNavNormSelect, '', 'Move cursor and select down one page',         scShift + VK_NEXT);
  KeyMapping.Add(smSelPageLeft,    SNavNormSelect, '', 'Move cursor and select right one page',        0);
  KeyMapping.Add(smSelPageRight,   SNavNormSelect, '', 'Move cursor and select left one page',         0);
  KeyMapping.Add(smSelPageTop,     SNavNormSelect, '', 'Move cursor and select to top of page',        scShift + VK_PRIOR + scCtrl);
  KeyMapping.Add(smSelPageBottom,  SNavNormSelect, '', 'Move cursor and select to bottom of page',     scShift + VK_NEXT + scCtrl);
  KeyMapping.Add(smSelEditorTop,   SNavNormSelect, '', 'Move cursor and select to absolute beginning', scShift + VK_HOME + scCtrl);
  KeyMapping.Add(smSelEditorBottom,SNavNormSelect, '', 'Move cursor and select to absolute end',       scShift + VK_END + scCtrl);
  KeyMapping.Add(smSelFirstLetter, SNavNormSelect, '', 'Move cursor and select to first char of line', scShift + VK_HOME);
  KeyMapping.Add(smSelLastLetter,  SNavNormSelect, '', 'Move cursor and select to last char of line',  scShift + VK_END);
  KeyMapping.Add(smSelLeftUp,      SNavNormSelect, '', 'Move cursor and select left and up at line start', 0);
  KeyMapping.Add(smSelGotoXY,         SNavNormSelect, '', 'Move cursor to specified position and select', $FFFFFFFF);

  KeyMapping.Add(smColSelLeft,      SNavColSelect, '', 'Move cursor and column select left one char',         scAlt + scShift + VK_LEFT);
  KeyMapping.Add(smColSelRight,     SNavColSelect, '', 'Move cursor and column select right one char',        scAlt + scShift + VK_RIGHT);
  KeyMapping.Add(smColSelUp,        SNavColSelect, '', 'Move cursor and column select up one line',           scAlt + scShift + VK_UP);
  KeyMapping.Add(smColSelDown,      SNavColSelect, '', 'Move cursor and column select down one line',         scAlt + scShift + VK_DOWN);
  KeyMapping.Add(smColSelWordLeft,  SNavColSelect, '', 'Move cursor and column select left one word',         scAlt + scShift + VK_LEFT + scCtrl);
  KeyMapping.Add(smColSelWordRight, SNavColSelect, '', 'Move cursor and column select right one word',        scAlt + scShift + VK_RIGHT + scCtrl);
  KeyMapping.Add(smColSelLineStart, SNavColSelect, '', 'Move cursor and column select to beginning of line',  0);
  KeyMapping.Add(smColSelLineEnd,   SNavColSelect, '', 'Move cursor and column select to end of line',        0);
  KeyMapping.Add(smColSelPageUp,    SNavColSelect, '', 'Move cursor and column select up one page',           scAlt + scShift + VK_PRIOR);
  KeyMapping.Add(smColSelPageDown,  SNavColSelect, '', 'Move cursor and column select down one page',         scAlt + scShift + VK_NEXT);
  KeyMapping.Add(smColSelPageLeft,  SNavColSelect, '', 'Move cursor and column select right one page',        0);
  KeyMapping.Add(smColSelPageRight, SNavColSelect, '', 'Move cursor and column select left one page',         0);
  KeyMapping.Add(smColSelPageTop,   SNavColSelect, '', 'Move cursor and column select to top of page',        scAlt + scShift + VK_PRIOR + scCtrl);
  KeyMapping.Add(smColSelPageBottom,SNavColSelect, '', 'Move cursor and column select to bottom of page',     scAlt + scShift + VK_NEXT + scCtrl);
  KeyMapping.Add(smColSelEditorTop, SNavColSelect, '', 'Move cursor and column select to absolute beginning', scAlt + scShift + VK_HOME + scCtrl);
  KeyMapping.Add(smColSelEditorBottom,SNavColSelect, '', 'Move cursor and column select to absolute end',     scAlt + scShift + VK_END + scCtrl);
  KeyMapping.Add(smColFirstLetter, SNavColSelect, '',  'Move cursor and column select to first char of line', 0{scAlt + scShift + VK_HOME});
  KeyMapping.Add(smColLastLetter,SNavColSelect, '',    'Move cursor and column select to last char of line',  scAlt + scShift + VK_END);
  KeyMapping.Add(smColLeftUp,       SNavColSelect,'', 'Move cursor and column select left and up at line start', 0);
  KeyMapping.Add(smColSelGotoXY, SNavColSelect,'', 'Move cursor to specified position and column select', $FFFFFFFF);

  KeyMapping.Add(smScrollUp,       SScrolling, 'Scroll Up',    'Scroll up one line leaving cursor position unchanged',   VK_UP + scCtrl);
  KeyMapping.Add(smScrollDown,     SScrolling, 'Scroll Down',  'Scroll down one line leaving cursor position unchanged', VK_DOWN + scCtrl);
  KeyMapping.Add(smScrollLeft,     SScrolling, 'Scroll Left',  'Scroll left one char leaving cursor position unchanged', 0);
  KeyMapping.Add(smScrollRight,    SScrolling, 'Scroll Right', 'Scroll right one char leaving cursor position unchanged',0);
  KeyMapping.Add(smScrollPageUp,   SScrolling, 'Scroll Page Up',   'Scroll up one page leaving cursor position unchanged', 0);
  KeyMapping.Add(smScrollPageDown, SScrolling, 'Scroll Page Down', 'Scroll down one page leaving cursor position unchanged', 0);
  KeyMapping.Add(smScrollPageLeft, SScrolling, 'Scroll Page Left', 'Scroll left one screen leaving cursor position unchanged', 0);
  KeyMapping.Add(smScrollPageRight,SScrolling, 'Scroll Page Right','Scroll right one screen leaving cursor position unchanged', 0);
  KeyMapping.Add(smScrollAbsUp,    SScrolling, 'Scroll to begin',  'Scroll to absolute beginning leaving cursor position unchanged', 0);
  KeyMapping.Add(smScrollAbsDown,  SScrolling, 'Scroll to end',    'Scroll to absolute end leaving cursor position unchanged', 0);
  KeyMapping.Add(smScrollAbsLeft,  SScrolling, 'Scroll to left',   'Scroll to absolute left leaving cursor position unchanged', 0);
  KeyMapping.Add(smScrollAbsRight, SScrolling, 'Scroll to right',  'Scroll to absolute right leaving cursor position unchanged', 0);

  KeyMapping.Add(smCopy,           SStdActions, '&Copy',       'Copy selection to clipboard',                    scCtrl + Ord('C'), scCtrl + VK_INSERT);
  KeyMapping.Add(smCut,            SStdActions, 'Cu&t',        'Cut selection to clipboard',                     scCtrl + Ord('X'), scShift + VK_DELETE);
  KeyMapping.Add(smPaste,          SStdActions, '&Paste',      'Paste clipboard to current position',            scCtrl + Ord('V'), scShift + VK_INSERT);
  KeyMapping.Add(smUndo,           SStdActions, '&Undo',       'Perform undo if available',                      scCtrl + Ord('Z'), scAlt + VK_BACK);
  KeyMapping.Add(smRedo,           SStdActions, '&Redo',       'Perform redo if available',                      scCtrl + Ord('Z') + scShift);
  KeyMapping.Add(smSelectAll,      SStdActions, 'Select &All', 'Select entire contents of editor, cursor to end',scCtrl + Ord('A'));
  KeyMapping.Add(smClearSelection, SStdActions, '&Delete',     'Delete current selection',                        scCtrl + VK_DELETE);
  KeyMapping.Add(smCopyAsRTF,      SStdActions, 'Copy As RTF', 'Copy to clipboard in RTF format',                0);

  KeyMapping.Add(smDeleteLastChar, SDeleting, 'Back Delete Char',      'Delete last char (i.e. backspace key)',      VK_BACK);
  KeyMapping.Add(smDeleteChar,     SDeleting, 'Delete Char',           'Delete char at cursor (i.e. delete key)',    VK_DELETE);
  KeyMapping.Add(smDeleteWord,     SDeleting, 'Delete Word',           'Delete from cursor to next word',            scCtrl + Ord('T'));
  KeyMapping.Add(smDeleteLastWord, SDeleting, 'Back Delete Word',      'Delete from cursor to start of word',        scCtrl + VK_BACK);
  KeyMapping.Add(smDeleteBOL,      SDeleting, 'Delete to Line Begin',  'Delete from cursor to beginning of line',    scCtrl + Ord('B'));
  KeyMapping.Add(smDeleteEOL,      SDeleting, 'Delete to Line End',    'Delete from cursor to end of line'{,          scCtrl + Ord('E')});
  KeyMapping.Add(smDeleteLine,     SDeleting, 'Delete Line',           'Delete current line',                        scCtrl + Ord('Y'));
  KeyMapping.Add(smClearAll,       SDeleting, 'Clear all',             'Delete everything',                          0);

  KeyMapping.Add(smLineBreak,      SInserting, 'New Line', 'Break line at current position, move caret to new line', VK_RETURN);
  KeyMapping.Add(smInsertLine,     SInserting, '',         'Break line at current position, leave caret',            scCtrl + Ord('N'));
  KeyMapping.Add(smChar,           SInserting, '',         'Insert a character at current position (Data = PChar)', $FFFFFFFF);
  KeyMapping.Add(smString,         SInserting, '',         'Insert a whole string (Data = PChar)', $FFFFFFFF);

  KeyMapping.Add(smBlockIndent,    SIndent, '&Indent',   'Indent selection',         scCtrl + VK_TAB);
  KeyMapping.Add(smBlockUnindent,  SIndent, '&Unindent', 'Unindent selection',       scShift + VK_TAB);
  KeyMapping.Add(smTab,            SIndent, '',         'Tab key',                  VK_TAB);
  KeyMapping.Add(smTabChar,        SIndent, '',         'Insert Tab char',          scCtrl + Ord('I'));

  KeyMapping.Add(smInsertMode,     SSelModes, 'Insert Mode',        'Set insert mode',     0);
  KeyMapping.Add(smOverwriteMode,  SSelModes, 'Overwrite Mode',     'Set overwrite mode',  0);
  KeyMapping.Add(smToggleMode,     SSelModes, 'Toggle Insert Mode', 'Toggle insert/overwrite mode', VK_INSERT);
  KeyMapping.Add(smNormalSelect,   SSelModes, 'Normal Selection',   'Normal selection mode', scCtrl + Ord('O') + Ord('K') shl 16);
  KeyMapping.Add(smColumnSelect,   SSelModes, 'Column Selection',   'Column selection mode', scCtrl + Ord('O') + Ord('C') shl 16);
  KeyMapping.Add(smLineSelect,     SSelModes, 'Line Selection',     'Line selection mode',   scCtrl + Ord('O') + Ord('L') shl 16);
  KeyMapping.Add(smMarkSelStart,   SSelModes, 'Mark Selection Start', 'Marks the beginning of a block', scCtrl + Ord('K') + Ord('B') shl 16);
  KeyMapping.Add(smMarkSelEnd,     SSelModes, 'Mark Selection End', 'Marks the end of a block', scCtrl + Ord('K') + Ord('K') shl 16);
  KeyMapping.Add(smResetSelection, SSelModes, 'Reset selection', 'Reset selection', VK_ESCAPE);

  KeyMapping.Add(smUpperCase,      SChangeCase, 'Word Upper Case',  'Upper case to current or previous word',           0);
  KeyMapping.Add(smLowerCase,      SChangeCase, 'Word Lower Case',  'Lower case to current or previous word',           0);
  KeyMapping.Add(smToggleCase,     SChangeCase, 'Word Toggle Case', 'Toggle case to current or previous word',          0);
  KeyMapping.Add(smTitleCase,      SChangeCase, 'Word Title Case',  'Title case to current or previous word',           0);
  KeyMapping.Add(smUpperCaseBlock, SChangeCase, 'Selection Upper Case',  'Upper case to current selection or current char',  scCtrl + Ord('K') + Ord('N') shl 16);
  KeyMapping.Add(smLowerCaseBlock, SChangeCase, 'Selection Lower Case',  'Lower case to current selection or current char',  scCtrl + Ord('K') + Ord('O') shl 16);
  KeyMapping.Add(smToggleCaseBlock,SChangeCase, 'Selection Toggle Case', 'Toggle case to current selection or current char',0);
  KeyMapping.Add(smTitleCaseBlock, SChangeCase, 'Selection Title Case', 'Title case to current selection',               0);

  KeyMapping.Add(smToggleCollapse, SCollapsing, 'Toggle Collapse', 'Collapse/expand block at current line',   0);
  KeyMapping.Add(smCollapse,       SCollapsing, 'Collapse',        'Collapse block at current line',          0);
  KeyMapping.Add(smExpand,         SCollapsing, 'Expand',          'Expand block at current line',            0);
  KeyMapping.Add(smFullCollapse,   SCollapsing, 'Full Collapse',   'Collapse all blocks in the text',         0);
  KeyMapping.Add(smFullExpand,     SCollapsing, 'Full Expand',     'Expand all collapsed blocks in the text', 0);
  KeyMapping.Add(smCollapseSelection, SCollapsing, 'Collapse Selection', 'Collapse selected block',          0);
  KeyMapping.Add(smToggleCollapseNearest, SCollapsing, 'Toggle Collapse Nearest', 'Collapse/expand nearest block', scCtrl + 187);
  KeyMapping.Add(smInSelCollapse,  SCollapsing, 'Collapse in selection', 'Collapse ranges in selection');
  KeyMapping.Add(smInSelExpand,    SCollapsing, 'Expand in selection', 'Expand ranges in selection');
  KeyMapping.Add(smToggleFolding,  SCollapsing, 'Toggle Folding', 'Toggle Folding');

  KeyMapping.Add(smGotoBookmark0, SBookmarks, 'Goto Bookmark 0', 'Goto Bookmark 0', scCtrl + Ord('0'));
  KeyMapping.Add(smGotoBookmark1, SBookmarks, 'Goto Bookmark 1', 'Goto Bookmark 1', scCtrl + Ord('1'));
  KeyMapping.Add(smGotoBookmark2, SBookmarks, 'Goto Bookmark 2', 'Goto Bookmark 2', scCtrl + Ord('2'));
  KeyMapping.Add(smGotoBookmark3, SBookmarks, 'Goto Bookmark 3', 'Goto Bookmark 3', scCtrl + Ord('3'));
  KeyMapping.Add(smGotoBookmark4, SBookmarks, 'Goto Bookmark 4', 'Goto Bookmark 4', scCtrl + Ord('4'));
  KeyMapping.Add(smGotoBookmark5, SBookmarks, 'Goto Bookmark 5', 'Goto Bookmark 5', scCtrl + Ord('5'));
  KeyMapping.Add(smGotoBookmark6, SBookmarks, 'Goto Bookmark 6', 'Goto Bookmark 6', scCtrl + Ord('6'));
  KeyMapping.Add(smGotoBookmark7, SBookmarks, 'Goto Bookmark 7', 'Goto Bookmark 7', scCtrl + Ord('7'));
  KeyMapping.Add(smGotoBookmark8, SBookmarks, 'Goto Bookmark 8', 'Goto Bookmark 8', scCtrl + Ord('8'));
  KeyMapping.Add(smGotoBookmark9, SBookmarks, 'Goto Bookmark 9', 'Goto Bookmark 9', scCtrl + Ord('9'));

  KeyMapping.Add(smSetBookmark0,  SBookmarks, 'Toggle Bookmark 0','Toggle Bookmark 0', scCtrl + scShift + Ord('0'));
  KeyMapping.Add(smSetBookmark1,  SBookmarks, 'Toggle Bookmark 1','Toggle Bookmark 1', scCtrl + scShift + Ord('1'));
  KeyMapping.Add(smSetBookmark2,  SBookmarks, 'Toggle Bookmark 2','Toggle Bookmark 2', scCtrl + scShift + Ord('2'));
  KeyMapping.Add(smSetBookmark3,  SBookmarks, 'Toggle Bookmark 3','Toggle Bookmark 3', scCtrl + scShift + Ord('3'));
  KeyMapping.Add(smSetBookmark4,  SBookmarks, 'Toggle Bookmark 4','Toggle Bookmark 4', scCtrl + scShift + Ord('4'));
  KeyMapping.Add(smSetBookmark5,  SBookmarks, 'Toggle Bookmark 5','Toggle Bookmark 5', scCtrl + scShift + Ord('5'));
  KeyMapping.Add(smSetBookmark6,  SBookmarks, 'Toggle Bookmark 6','Toggle Bookmark 6', scCtrl + scShift + Ord('6'));
  KeyMapping.Add(smSetBookmark7,  SBookmarks, 'Toggle Bookmark 7','Toggle Bookmark 7', scCtrl + scShift + Ord('7'));
  KeyMapping.Add(smSetBookmark8,  SBookmarks, 'Toggle Bookmark 8','Toggle Bookmark 8', scCtrl + scShift + Ord('8'));
  KeyMapping.Add(smSetBookmark9,  SBookmarks, 'Toggle Bookmark 9','Toggle Bookmark 9', scCtrl + scShift + Ord('9'));

  KeyMapping.Add(smDropMarker,      SMarkers, 'Drop marker',      'Drops marker to the current position', scAlt + VK_HOME);
  KeyMapping.Add(smCollectMarker,   SMarkers, 'Collect marker',   'Collect marker (jump back)',           scAlt + VK_END);
  KeyMapping.Add(smSwapMarker,      SMarkers, 'Swap marker',      'Swap marker (keep position, jump back)', scAlt + scShift + VK_HOME);
  KeyMapping.Add(smChangeRangeSide, SMarkers, 'Jump to matching bracket', 'Jump to matching bracket (change range side)', scCtrl + 219);

  KeyMapping.Add(smMacroPlay,      SMacros, 'Play macro', 'Play macro');
  KeyMapping.Add(smMacroRecStart,  SMacros, 'Start macro recording', 'Start macro recording');
  KeyMapping.Add(smMacroRecStop,   SMacros, 'Stop macro recording', 'Stop macro recording');
  KeyMapping.Add(smMacroRecCancel, SMacros, 'Cancel macro recording', 'Cancel macro recording');

  KeyMapping.Add(smToggleNonPrinted,         SMisc, 'Show Non Printed', 'Show/Hide non printed text/characters',0);
  KeyMapping.Add(smToggleWordWrap,           SMisc, 'Toggle Word Wrap', 'Toggle Word Wrap');
  KeyMapping.Add(smToggleLineNumbersVisible, SMisc, 'Show line numbers', 'Show/Hide line numbers',0);
  KeyMapping.Add(smCommentLines,             SMisc, 'Comment lines',  'Comment selected lines');
  KeyMapping.Add(smUncommentLines,           SMisc, 'Uncomment lines',  'Uncomment selected lines');
  KeyMapping.Add(smSortAscending,            SMisc, 'Ascending sort', 'Ascending sort of selected lines');
  KeyMapping.Add(smSortDescending,           SMisc, 'Descending sort', 'Descending sort of selected lines');
  KeyMapping.Add(smGotoLine,                 SMisc, 'Go to line number', 'Go to line number', scAlt + Ord('G'));
  KeyMapping.Add(smAlignTokens,              SMisc, 'Aligns tokens', 'Aligns tokens in selected lines');
  KeyMapping.Add(smMoveLinesUp,              SMisc, 'Moves lines up', 'Moves selected lines one line up');
  KeyMapping.Add(smMoveLinesDown,            SMisc, 'Moves lines down', 'Moves selected lines one line down');
  KeyMapping.Add(smDuplicateLine,            SMisc, 'Duplicate line', 'Duplicate current line');

  KeyMapping.Add(smPrint,                    SMisc, 'Print all text', 'Print all text');
  KeyMapping.Add(smPrintSelection,           SMisc, 'Print selected text', 'Print selected text');
  KeyMapping.Add(smPrintPreview,             SMisc, 'Print preview', 'Print preview');
  KeyMapping.Add(smPageSetup,                SMisc, 'Page Setup...', 'Page Setup dialog');

  KeyMapping.Add(smFindDialog,      SFindReplace, 'Find Dialog', 'Find Dialog', scCtrl + Ord('F'));
  KeyMapping.Add(smFindNext,        SFindReplace, 'Find Next', 'Find Next');
  KeyMapping.Add(smFindPrev,        SFindReplace, 'Find Previous', 'Find Previous');
  KeyMapping.Add(smFindAll,         SFindReplace, 'Find All', 'Find All');
  KeyMapping.Add(smFindFirst,       SFindReplace, 'Find First', 'Find First');
  KeyMapping.Add(smFindLast,        SFindReplace, 'Find Last', 'Find Last');
  KeyMapping.Add(smSearchAgain,     SFindReplace, 'Search Again', 'Search Again', VK_F3);
  KeyMapping.Add(smFindCurrentWordNext,  SFindReplace, 'Find Current Word Next', 'Find Current Word Next', scCtrl + scShift + VK_DOWN);
  KeyMapping.Add(smFindCurrentWordPrior, SFindReplace, 'Find Current Word Prior', 'Find Current Word Prior', scCtrl + scShift + VK_UP);
  KeyMapping.Add(smIncrementalSearch,SFindReplace,'Incremental Search', 'Incremental Search', scCtrl + Ord('E'));

  KeyMapping.Add(smSearchMarkReset, SFindReplace, 'Reset searches', 'Reset search marks');
  KeyMapping.Add(smSearchMarkNext,  SFindReplace, 'Next search', 'Go to next search mark');
  KeyMapping.Add(smSearchMarkPrev,  SFindReplace, 'Previous search', 'Go to previous search mark');

  KeyMapping.Add(smReplaceDialog,   SFindReplace, 'Replace Dialog', 'Replace Dialog', scCtrl + Ord('R'));
  KeyMapping.Add(smReplaceNext,     SFindReplace, 'Replace Next', 'Replace Next');
  KeyMapping.Add(smReplacePrev,     SFindReplace, 'Replace Previous', 'Replace Previous');
  KeyMapping.Add(smReplaceAll,      SFindReplace, 'Replace All', 'Replace All');
  KeyMapping.Add(smReplaceFirst,    SFindReplace, 'Replace First', 'Replace First');
  KeyMapping.Add(smReplaceLast,     SFindReplace, 'Replace Last', 'Replace Last');
  KeyMapping.Add(smReplaceAgain,    SFindReplace, 'Replace Again', 'Replace Again');

  KeyMapping.Add(smCopyPasteFileEnd    , SBlockOper, '', 'Block Copy & Paste end of file');
  KeyMapping.Add(smCopyPasteFileStart  , SBlockOper, '', 'Block Copy & Paste start of file');
  KeyMapping.Add(smCutPasteFileEnd     , SBlockOper, '', 'Block Cut & Paste end of file');
  KeyMapping.Add(smCutPasteFileStart   , SBlockOper, '', 'Block Cut & Paste start of file');
  KeyMapping.Add(smCopyPasteAbove      , SBlockOper, '', 'Block Copy & Paste above selected block');
  KeyMapping.Add(smCopyPasteBelow      , SBlockOper, '', 'Block Copy & Paste below top of file');

  KeyMapping.Add(smAutoComplete,  STools, 'Auto completion', 'Auto completion popup', scCtrl + VK_SPACE);
  KeyMapping.Add(smCodeTemplate,  STools, 'Code templates', 'Code templates popup', scCtrl + Ord('J'));
  KeyMapping.Add(smParameterHint, STools, 'Code parameters', 'Code parameters tool tip', scCtrl + scShift + VK_SPACE);
  KeyMapping.Add(smSelCharacter,  STools, 'Select character', 'Select character popup', scCtrl + 190);
  KeyMapping.Add(smAutoCorrectCur,STools, 'Auto correct current word', 'Auto correct current word');
  KeyMapping.Add(smAutoCorrectAll,STools, 'Auto correct all words', 'Auto correct all words');

end;

var
  FDefaultKeyMapping: TSyntKeyMapping = nil;

function DefaultKeyMapping: TSyntKeyMapping;
begin
  if FDefaultKeyMapping = nil then
    begin
      FDefaultKeyMapping := TSyntKeyMapping.Create(nil);
      InitDefaultKeyMapping(FDefaultKeyMapping);
    end;
  Result := FDefaultKeyMapping;
end;

initialization

finalization
  if Assigned(FDefaultKeyMapping) then
    FreeAndNil(FDefaultKeyMapping);

end.

