dexed/src/ce_synmemo.pas

670 lines
17 KiB
Plaintext

unit ce_synmemo;
{$I ce_defines.inc}
interface
uses
Classes, SysUtils, SynEdit, ce_d2syn, ce_txtsyn ,SynEditHighlighter, controls,
lcltype, LazSynEditText, SynEditKeyCmds, SynHighlighterLFM, SynEditMouseCmds,
SynEditFoldedView, crc, ce_common, ce_observer, ce_writableComponent, Forms,
graphics, ExtCtrls;
type
TCESynMemo = class;
// SImple THintWindow descendant allowing the font size to be in sync with the editor.
TCEEditorHintWindow = class(THintWindow)
public
class var FontSize: Integer;
function CalcHintRect(MaxWidth: Integer; const AHint: String;
AData: Pointer): TRect; override;
end;
// Stores the state of a particulat source code folding.
TCEFoldCache = class(TCollectionItem)
private
fCollapsed: boolean;
fLineIndex: Integer;
fNestedIndex: Integer;
published
property isCollapsed: boolean read fCollapsed write fCollapsed;
property lineIndex: Integer read fLineIndex write fLineIndex;
property nestedIndex: Integer read fNestedIndex write fNestedIndex;
end;
// Stores the state of a document between two cessions.
TCESynMemoCache = class(TWritableLfmTextComponent)
private
fMemo: TCESynMemo;
fFolds: TCollection;
fCaretPosition: Integer;
fSelectionEnd: Integer;
fFontSize: Integer;
fSourceFilename: string;
procedure setFolds(someFolds: TCollection);
published
property caretPosition: Integer read fCaretPosition write fCaretPosition;
property sourceFilename: string read fSourceFilename write fSourceFilename;
property folds: TCollection read fFolds write setFolds;
property selectionEnd: Integer read fSelectionEnd write fSelectionEnd;
property fontSize: Integer read fFontSize write fFontSize;
public
constructor create(aComponent: TComponent); override;
destructor destroy; override;
//
procedure beforeSave; override;
procedure afterLoad; override;
procedure save;
procedure load;
end;
// buffer of caret positions allowing to jump quickly to the most recent locations.
TCESynMemoPositions = class
private
fPos: Integer;
fMax: Integer;
fList: TFPList;
fMemo: TCustomSynEdit;
public
constructor create(aMemo: TCustomSynEdit);
destructor destroy; override;
procedure store;
procedure back;
procedure next;
end;
TCESynMemo = class(TSynEdit)
private
fFilename: string;
fModified: boolean;
fFileDate: double;
fIsDSource: boolean;
fIsTxtFile: boolean;
fIsConfig: boolean;
fIdentifier: string;
fTempFileName: string;
fMultiDocSubject: TCECustomSubject;
fDefaultFontSize: Integer;
fPositions: TCESynMemoPositions;
fMousePos: TPoint;
fCallTipWin: TCEEditorHintWindow;
fDDocWin: TCEEditorHintWindow;
fIdleTimer: TIdleTimer;
fMayHint: boolean;
function getMouseStart: Integer;
procedure changeNotify(Sender: TObject);
procedure identifierToD2Syn;
procedure saveCache;
procedure loadCache;
procedure setDefaultFontSize(aValue: Integer);
procedure hintWinClick(sender: TObject);
procedure EditorIdle(sender: TObject);
procedure InitHintWins;
protected
procedure SetVisible(Value: Boolean); override;
procedure SetHighlighter(const Value: TSynCustomHighlighter); override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure KeyUp(var Key: Word; Shift: TShiftState); override;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y:Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y:Integer); override;
published
property defaultFontSize: Integer read fDefaultFontSize write setDefaultFontSize;
public
constructor Create(aOwner: TComponent); override;
destructor destroy; override;
procedure setFocus; override;
//
procedure checkFileDate;
procedure loadFromFile(const aFilename: string);
procedure saveToFile(const aFilename: string);
procedure save;
procedure saveTempFile;
//
property Identifier: string read fIdentifier;
property fileName: string read fFilename;
property modified: boolean read fModified;
property tempFilename: string read fTempFileName;
//
property isDSource: boolean read fIsDSource;
property isProjectSource: boolean read fIsConfig;
property TextView;
//
property MouseStart: Integer read getMouseStart;
end;
var
D2Syn: TSynD2Syn;
LfmSyn: TSynLfmSyn;
TxtSyn: TSynTxtSyn;
implementation
uses
ce_interfaces, ce_staticmacro, ce_dcd, SynEditHighlighterFoldBase;
function TCEEditorHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: String; AData: Pointer): TRect;
begin
Font.Size:= FontSize;
result := inherited CalcHintRect(MaxWidth, AHint, AData);
end;
{$REGION TCESynMemoCache -------------------------------------------------------}
constructor TCESynMemoCache.create(aComponent: TComponent);
begin
inherited create(nil);
if (aComponent is TCESynMemo) then
fMemo := TCESynMemo(aComponent);
fFolds := TCollection.Create(TCEFoldCache);
end;
destructor TCESynMemoCache.destroy;
begin
fFolds.Free;
inherited;
end;
procedure TCESynMemoCache.setFolds(someFolds: TCollection);
begin
fFolds.Assign(someFolds);
end;
procedure TCESynMemoCache.beforeSave;
var
i, start, prev: Integer;
itm : TCEFoldCache;
begin
if fMemo = nil then exit;
//
fCaretPosition := fMemo.SelStart;
fSourceFilename := fMemo.fileName;
fSelectionEnd := fMemo.SelEnd;
fFontSize := fMemo.Font.Size;
TCEEditorHintWindow.FontSize := fMemo.Font.Size;
//
// TODO-cEditor Cache: >nested< folding persistence
// cf. other ways: http://forum.lazarus.freepascal.org/index.php?topic=26748.msg164722#msg164722
prev := fMemo.Lines.Count-1;
for i := fMemo.Lines.Count-1 downto 0 do
begin
// - CollapsedLineForFoldAtLine() does not handle the sub-folding.
// - TextView visibility is increased so this is not the standard way of getting the infos.
start := fMemo.TextView.CollapsedLineForFoldAtLine(i);
if start = -1 then
continue;
if start = prev then
continue;
prev := start;
itm := TCEFoldCache(fFolds.Add);
itm.isCollapsed := true;
itm.fLineIndex := start;
end;
end;
procedure TCESynMemoCache.afterLoad;
var
i: integer;
itm : TCEFoldCache;
begin
if fMemo = nil then exit;
//
if fFontSize > 0 then
fMemo.Font.Size := fFontSize;
// Currently collisions are not handled.
if fMemo.fileName <> fSourceFilename then exit;
//
for i := 0 to fFolds.Count-1 do
begin
itm := TCEFoldCache(fFolds.Items[i]);
if not itm.isCollapsed then
continue;
fMemo.TextView.FoldAtLine(itm.lineIndex-1);
end;
//
fMemo.SelStart := fCaretPosition;
fMemo.SelEnd := fSelectionEnd;
end;
{$IFDEF DEBUG}{$R-}{$ENDIF}
procedure TCESynMemoCache.save;
var
fname: string;
tempn: string;
chksm: Cardinal;
begin
tempn := fMemo.fileName;
if tempn = fMemo.tempFilename then exit;
if not fileExists(tempn) then exit;
//
fname := getCoeditDocPath + 'editorcache' + DirectorySeparator;
ForceDirectories(fname);
chksm := crc32(0, nil, 0);
chksm := crc32(chksm, @tempn[1], length(tempn));
fname := fname + format('%.8X.txt', [chksm]);
saveToFile(fname);
end;
procedure TCESynMemoCache.load;
var
fname: string;
tempn: string;
chksm: Cardinal;
begin
tempn := fMemo.fileName;
if not fileExists(tempn) then exit;
//
fname := getCoeditDocPath + 'editorcache' + DirectorySeparator;
chksm := crc32(0, nil, 0);
chksm := crc32(chksm, @tempn[1], length(tempn));
fname := fname + format('%.8X.txt', [chksm]);
//
if not fileExists(fname) then exit;
loadFromFile(fname);
end;
{$IFDEF DEBUG}{$R+}{$ENDIF}
{$ENDREGION}
{$REGION TCESynMemoPositions ---------------------------------------------------}
constructor TCESynMemoPositions.create(aMemo: TCustomSynEdit);
begin
fList := TFPList.Create;
fMax := 20;
fMemo := aMemo;
fPos := -1;
end;
destructor TCESynMemoPositions.destroy;
begin
fList.Free;
inherited;
end;
procedure TCESynMemoPositions.back;
begin
Inc(fPos);
{$HINTS OFF}
if fPos < fList.Count then
fMemo.CaretY := NativeInt(fList.Items[fPos])
{$HINTS ON}
else Dec(fPos);
end;
procedure TCESynMemoPositions.next;
begin
Dec(fPos);
{$HINTS OFF}
if fPos > -1 then
fMemo.CaretY := NativeInt(fList.Items[fPos])
{$HINTS ON}
else Inc(fPos);
end;
procedure TCESynMemoPositions.store;
var
delta: NativeInt;
const
thresh = 6;
begin
fPos := 0;
{$HINTS OFF}{$WARNINGS OFF}
if fList.Count > 0 then
begin
delta := fMemo.CaretY - NativeInt(fList.Items[fPos]);
if (delta > -thresh) and (delta < thresh) then exit;
end;
fList.Insert(0, Pointer(NativeInt(fMemo.CaretY)));
{$HINTS ON}{$WARNINGS ON}
while fList.Count > fMax do
fList.Delete(fList.Count-1);
end;
{$ENDREGION --------------------------------------------------------------------}
{$REGION TCESynMemo ------------------------------------------------------------}
constructor TCESynMemo.Create(aOwner: TComponent);
begin
inherited;
InitHintWins;
self.ShowHint:=false;
SetDefaultKeystrokes; // not called in inherited if owner = nil !
fDefaultFontSize := 10;
fIdleTimer := TIdleTimer.Create(self);
fIdleTimer.OnTimer := @EditorIdle;
Gutter.LineNumberPart.ShowOnlyLineNumbersMultiplesOf := 5;
Gutter.LineNumberPart.MarkupInfo.Foreground := clGray;
Gutter.SeparatorPart.LineOffset := 1;
Gutter.SeparatorPart.LineWidth := 1;
Gutter.SeparatorPart.MarkupInfo.Foreground := clGray;
Gutter.CodeFoldPart.MarkupInfo.Foreground := clGray;
BracketMatchColor.Foreground:=clRed;
//
MouseLinkColor.Style:= [fsUnderline];
with MouseActions.Add do begin
Command := emcMouseLink;
shift := [ssCtrl];
ShiftMask := [ssCtrl];
end;
//
Highlighter := D2Syn;
//
fTempFileName := GetTempDir(false) + 'temp_' + uniqueObjStr(self) + '.d';
fFilename := '<new document>';
fModified := false;
//ShowHint := true;
TextBuffer.AddNotifyHandler(senrUndoRedoAdded, @changeNotify);
//
fPositions := TCESynMemoPositions.create(self);
fMultiDocSubject := TCEMultiDocSubject.create;
subjDocNew(TCEMultiDocSubject(fMultiDocSubject), self);
end;
destructor TCESynMemo.destroy;
begin
saveCache;
//
subjDocClosing(TCEMultiDocSubject(fMultiDocSubject), self);
fMultiDocSubject.Free;
fPositions.Free;
//
if fileExists(fTempFileName) then
sysutils.DeleteFile(fTempFileName);
inherited;
end;
procedure TCESynMemo.setDefaultFontSize(aValue: Integer);
var
old: Integer;
begin
old := Font.Size;
if aValue < 5 then aValue := 5;
fDefaultFontSize:= aValue;
if Font.Size = old then
Font.Size := fDefaultFontSize;
end;
procedure TCESynMemo.setFocus;
begin
inherited;
checkFileDate;
identifierToD2Syn;
subjDocFocused(TCEMultiDocSubject(fMultiDocSubject), self);
end;
procedure TCESynMemo.SetVisible(Value: Boolean);
begin
inherited;
if Value then setFocus;
end;
procedure TCESynMemo.hintWinClick(sender: TObject);
begin
with THintWindow(sender) do Hide;
end;
procedure TCESynMemo.InitHintWins;
begin
if fCallTipWin = nil then begin
fCallTipWin := TCEEditorHintWindow.Create(self);
fCallTipWin.Color := clInfoBk + $01010100;
fCallTipWin.Font.Color:= clInfoText;
fCallTipWin.OnClick:= @hintWinClick;
end;
if fDDocWin = nil then begin
fDDocWin := TCEEditorHintWindow.Create(self);
fDDocWin.Color := clInfoBk + $01010100;
fDDocWin.Font.Color:= clInfoText;
fDDocWin.OnClick:= @hintWinClick;
end;
end;
procedure TCESynMemo.EditorIdle(sender: TObject);
var
str: string;
begin
if not Visible then exit;
if not isDSource then exit;
//
if not fMayHint then exit;
if Identifier = '' then exit;
DcdWrapper.getDdocFromCursor(str);
//
if (length(str) > 0) then
if str[1] = #13 then
str := str[2..length(str)];
if (length(str) > 0) then
if str[1] = #10 then
str := str[2..length(str)];
//
if str <> '' then
begin
fDDocWin.FontSize := Font.Size;
fDDocWin.Font.Size:=Font.Size;
fDDocWin.HintRect := fCallTipWin.CalcHintRect(0, str, nil);
fDDocWin.OffsetHintRect(mouse.CursorPos, Font.Size);
fDDocWin.ActivateHint(fDDocWin.HintRect, str);
end;
end;
procedure TCESynMemo.SetHighlighter(const Value: TSynCustomHighlighter);
begin
inherited;
fIsDSource := Highlighter = D2Syn;
fIsConfig := Highlighter = LfmSyn;
fIsTxtFile := Highlighter = TxtSyn;
end;
procedure TCESynMemo.identifierToD2Syn;
begin
fIdentifier := GetWordAtRowCol(LogicalCaretXY);
if fIsDSource then
D2Syn.CurrentIdentifier := fIdentifier
else if fIsTxtFile then
TxtSyn.CurrIdent := fIdentifier;
end;
procedure TCESynMemo.changeNotify(Sender: TObject);
begin
identifierToD2Syn;
fModified := true;
fPositions.store;
subjDocChanged(TCEMultiDocSubject(fMultiDocSubject), self);
end;
procedure TCESynMemo.loadFromFile(const aFilename: string);
var
ext: string;
begin
ext := extractFileExt(aFilename);
if dExtList.IndexOf(ext) = -1 then
Highlighter := TxtSyn;
Lines.LoadFromFile(aFilename);
fFilename := aFilename;
FileAge(fFilename, fFileDate);
//
loadCache;
//
fModified := false;
if Showing then setFocus;
subjDocChanged(TCEMultiDocSubject(fMultiDocSubject), self);
end;
procedure TCESynMemo.saveToFile(const aFilename: string);
var
ext: string;
begin
Lines.SaveToFile(aFilename);
fFilename := aFilename;
ext := extractFileExt(aFilename);
if dExtList.IndexOf(ext) <> -1 then
Highlighter := D2Syn;
FileAge(fFilename, fFileDate);
fModified := false;
if fFilename <> fTempFileName then
subjDocChanged(TCEMultiDocSubject(fMultiDocSubject), self);
end;
procedure TCESynMemo.save;
begin
Lines.SaveToFile(fFilename);
FileAge(fFilename, fFileDate);
fModified := false;
if fFilename <> fTempFileName then
subjDocChanged(TCEMultiDocSubject(fMultiDocSubject), self);
end;
procedure TCESynMemo.saveTempFile;
begin
saveToFile(fTempFileName);
fModified := false;
end;
procedure TCESynMemo.saveCache;
var
cache: TCESynMemoCache;
begin
cache := TCESynMemoCache.create(self);
try
cache.save;
finally
cache.free;
end;
end;
procedure TCESynMemo.loadCache;
var
cache: TCESynMemoCache;
begin
cache := TCESynMemoCache.create(self);
try
cache.load;
finally
cache.free;
end;
end;
procedure TCESynMemo.checkFileDate;
var
newDate: double;
begin
if fFilename = fTempFileName then exit;
if not FileAge(fFilename, newDate) then exit;
if fFileDate = newDate then exit;
if fFileDate <> 0.0 then
begin
if dlgOkCancel(format('"%s" has been modified by another program, load the new version ?',
[shortenPath(fFilename, 25)])) = mrOk then
begin
Lines.LoadFromFile(fFilename);
fModified := false;
end;
end;
fFileDate := newDate;
end;
procedure TCESynMemo.KeyDown(var Key: Word; Shift: TShiftState);
begin
inherited;
identifierToD2Syn;
if not (Shift = [ssCtrl]) then exit;
//
case Key of
VK_ADD: if Font.Size < 50 then Font.Size := Font.Size + 1;
VK_SUBTRACT: if Font.Size > 3 then Font.Size := Font.Size - 1;
VK_DECIMAL: Font.Size := fDefaultFontSize;
end;
TCEEditorHintWindow.FontSize := Font.Size;
fDDocWin.Hide;
end;
procedure TCESynMemo.KeyUp(var Key: Word; Shift: TShiftState);
var
str: string;
begin
if Key in [VK_PRIOR, VK_NEXT, Vk_UP] then
fPositions.store;
inherited;
//
if StaticEditorMacro.automatic then
StaticEditorMacro.Execute;
//
if Key = 53 then
begin
if fCallTipWin = nil then
begin
fCallTipWin := TCEEditorHintWindow.Create(self);
fCallTipWin.Color := clInfoBk + $01010100;
end;
DcdWrapper.getCallTip(str);
if str <> '' then
begin
fCallTipWin.FontSize := Font.Size;
fCallTipWin.HintRect := fCallTipWin.CalcHintRect(0, str, nil);
fCallTipWin.OffsetHintRect(point(CaretXPix, CaretYPix), Font.Size);
fCallTipWin.ActivateHint(str);
end;
end else fCallTipWin.Hide;
end;
function TCESynMemo.getMouseStart: Integer;
var
i, le: Integer;
begin
result := 0;
le := getLineEndingLength(fFilename);
for i:= 0 to fMousePos.y-2 do
result += length(Lines.Strings[i]) + le;
result += fMousePos.x;
end;
procedure TCESynMemo.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
fDDocWin.Hide;
fCallTipWin.Hide;
fMayHint := ((Y > 10) or (Y < -10)) and ((X > 10) or (X < -10));
fMousePos := PixelsToRowColumn(Point(X,Y));
inherited;
if ssLeft in Shift then
identifierToD2Syn;
end;
procedure TCESynMemo.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y:Integer);
begin
inherited;
identifierToD2Syn;
fDDocWin.Hide;
fCallTipWin.Hide;
end;
procedure TCESynMemo.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y:Integer);
begin
inherited;
if (Button = mbMiddle) and (Shift = [ssCtrl]) then
Font.Size := fDefaultFontSize
//TODO-cLCL&LAZ-specific: test this feature under gtk2/linux on next release, should work
else if Button = mbExtra1 then
fPositions.back
else if Button = mbExtra2 then
fPositions.next
else if Button = mbLeft then
fPositions.store;
end;
{$ENDREGION --------------------------------------------------------------------}
initialization
D2Syn := TSynD2Syn.create(nil);
LfmSyn := TSynLFMSyn.Create(nil);
TxtSyn := TSynTxtSyn.create(nil);
//
LfmSyn.KeyAttri.Foreground := clNavy;
LfmSyn.KeyAttri.Style := [fsBold];
LfmSyn.NumberAttri.Foreground := clMaroon;
LfmSyn.StringAttri.Foreground := clBlue;
//
TCEEditorHintWindow.FontSize := 10;
finalization
D2Syn.Free;
LfmSyn.Free;
TxtSyn.Free;
end.