{------------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: SynEdit.pas, released 2000-04-07. The Original Code is based on mwCustomEdit.pas by Martin Waldenburg, part of the mwEdit component suite. Portions created by Martin Waldenburg are Copyright (C) 1998 Martin Waldenburg. All Rights Reserved. Contributors to the SynEdit and mwEdit projects are listed in the Contributors.txt file. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 2 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL and not to allow others to use your version of this file under the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under either the MPL or the GPL. $Id$ You may retrieve the latest version of this file at the SynEdit home page, located at http://SynEdit.SourceForge.net Known Issues: -DoubleBuffered -Font.CharSet -THintWindow -DragAcceptFiles -------------------------------------------------------------------------------} unit SynEdit; {$IFDEF WINCE} {$IFnDEF WinIME} {$DEFINE WithoutWinIME} {$ENDIF} {$ENDIF} {$IFDEF Windows} {$IFnDEF WithoutWinIME} {$DEFINE WinIME} {$DEFINE WinIMEFull} {$ENDIF} {$ENDIF} {$IFDEF LCLCOCOA} {$DEFINE CocoaIME} {$ENDIF} {$I synedit.inc} {$IFDEF LCLGTK1} {$DEFINE EnableDoubleBuf} // gtk1 does not have double buffering {$ENDIF} {$IFDEF LCLGTK2} { $DEFINE EnableDoubleBuf} // gtk2.10 paints faster to memory // gtk2.12 paints faster directly {$ENDIF} {$IFDEF Windows} (* * On Windows 10 there is an issue, where under certain conditions a "ghost" of the text caret remains visible. That is one or a series of vertical black lines remain on the screen. * This can be reproduced, by moving part (eg bottom) of editor off screen (not just behind the taskbar, but off screen). Then press caret down to scroll. Ghost carets will scroll in with the new text. Similar move caret to a place off-screen, unfocus editor, and refocus by clicking into the editor (move caret while setting focus). To reproduce, the editor must have a visible gutter; and must not have "current line" highlight. * The conditions to cause this: - Caret must be in part of editor that is outside the screen. - Carte must be destroyed (maybe only hidden?), or ScrollWindowEx must affect caret - Caret must be in a part of the editor for which NO call to "invalidate" was made, but which will be repainted. E.g. the gutter, but not the line area received an invalidate, and another line above/below was invalidated (can happen through ScrollWindowEx). -> In this case the paint message receives a rect, that contains the caret, even though the part containing the caret was never explicitly invalidated. If this happens, while the caret is on screen (even if hidden behind another window/taskbar) then all works ok. But if the caret was off screen, a permanent image of the caret will remain (once scrolled/moved into the screen area). It seem that in this case windows does not update the information, that the caret became "invisible" when paint did paint over it. So if the already overpainted caret, is later (by Windows) attempted to be hidden by a final "xor", then it actually is made permanently visible. As a solution, in the above conditions, the full line (actually the text part with the caret) must be invalidated too. Since this is hard to track, the workaround will invalidate the full line, in any case that potentially could meet the conditions *) {$DEFINE Windows10GhostCaretIssue} {$ENDIF} interface { $DEFINE SYNSCROLLDEBUG} { $DEFINE VerboseKeys} { $DEFINE VerboseSynEditInvalidate} { $DEFINE SYNDEBUGPRINT} {$IFDEF SynUndoDebug} {$Define SynUndoDebugItems} {$Define SynUndoDebugCalls} {$ENDIF} uses LazSynIMMBase, {$IFDEF WinIME} LazSynIMM, {$ENDIF} {$IFDEF CocoaIME} LazSynCocoaIMM, {$ENDIF} {$IFDEF Gtk2IME} LazSynGtk2IMM, {$ENDIF} {$IFDEF USE_UTF8BIDI_LCL} FreeBIDI, utf8bidi, {$ENDIF} {$IFDEF WithSynExperimentalCharWidth} SynEditTextSystemCharWidth, {$ENDIF} Types, SysUtils, Classes, // LCL LCLProc, LCLIntf, LCLType, LMessages, LResources, Messages, Controls, Graphics, Forms, StdCtrls, ExtCtrls, Menus, Clipbrd, StdActns, // LazUtils LazUtilities, LazMethodList, LazLoggerBase, LazTracer, LazUTF8, // SynEdit SynEditTypes, SynEditSearch, SynEditKeyCmds, SynEditMouseCmds, SynEditMiscProcs, SynEditPointClasses, SynBeautifier, SynEditMarks, // Markup SynEditMarkup, SynEditMarkupHighAll, SynEditMarkupBracket, SynEditMarkupWordGroup, SynEditMarkupCtrlMouseLink, SynEditMarkupSpecialLine, SynEditMarkupSelection, SynEditMarkupSpecialChar, // Lines SynEditTextBase, LazSynEditText, SynEditTextBuffer, SynEditLines, SynEditTextTrimmer, SynEditTextTabExpander, SynEditTextDoubleWidthChars, SynEditFoldedView, // Gutter SynGutterBase, SynGutter, SynEditMiscClasses, SynEditHighlighter, LazSynTextArea, SynEditTextBidiChars, SynGutterCodeFolding, SynGutterChanges, SynGutterLineNumber, SynGutterMarks, SynGutterLineOverview, LazEditMiscProcs, LazEditTextAttributes, LazEditTextGridPainter; const // SynDefaultFont is determined in InitSynDefaultFont() SynDefaultFontName: String = ''; SynDefaultFontHeight: Integer = 13; SynDefaultFontSize: Integer = 10; SynDefaultFontPitch: TFontPitch = fpFixed; SynDefaultFontQuality: TFontQuality = fqNonAntialiased; // maximum scroll range MAX_SCROLL = 32767; type TSynEditMarkupClass = SynEditMarkup.TSynEditMarkupClass; TSynReplaceAction = (raCancel, raSkip, raReplace, raReplaceAll); TSynDropFilesEvent = procedure(Sender: TObject; X, Y: integer; AFiles: TStrings) of object; TPaintEvent = procedure(Sender: TObject; ACanvas: TCanvas) of object; TChangeUpdatingEvent = procedure(ASender: TObject; AnUpdating: Boolean) of object; TProcessCommandEvent = procedure(Sender: TObject; var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer) of object; TReplaceTextEvent = procedure(Sender: TObject; const ASearch, AReplace: string; Line, Column: integer; var ReplaceAction: TSynReplaceAction) of object; TSynCopyPasteAction = (scaContinue, scaPlainText, scaAbort); TSynCopyPasteEvent = procedure(Sender: TObject; var AText: String; var AMode: TSynSelectionMode; ALogStartPos: TPoint; var AnAction: TSynCopyPasteAction) of object; TSynEditCaretType = SynEditPointClasses.TSynCaretType; TSynCaretAdjustMode = ( // used in TextBetweenPointsEx scamIgnore, // Caret stays at the same numeric values, if text is inserted before caret, the text moves, but the caret stays scamAdjust, // Caret moves with text. Except if it is at a selection boundary, in which case it stays with the selection (movement depends on setMoveBlock/setExtendBlock) scamForceAdjust, // Caret moves with text. Can be used if the caret should move away from the bound of a persistent selection scamEnd, scamBegin ); (* This is used, if text is *replaced*. What to do with marks in text that is deleted/replaced *) TSynMarksAdjustMode = ( // used in SetTextBetweenPoints smaMoveUp, // smaKeep // smaDrop ); TSynEditTextFlag = ( setSelect, // select the new text setPersistentBlock, // keep/move existent selection (only if not setSelect) setMoveBlock, // weak persistent // see TSynBlockPersistMode setExtendBlock // strong persistent // see TSynBlockPersistMode ); TSynEditTextFlags = set of TSynEditTextFlag; TSynEditHasTextFlag = ( shtIncludeVirtual // trailing spaces ); TSynEditHasTextFlags = set of TSynEditHasTextFlag; TSynStateFlag = (sfCaretChanged, sfHideCursor, sfEnsureCursorPos, sfEnsureCursorPosAtResize, sfEnsureCursorPosForEditRight, sfEnsureCursorPosForEditLeft, sfExplicitTopLine, sfExplicitLeftChar, // when doing EnsureCursorPos keep top/Left, if they where set explicitly after the caret (only applies before handle creation) sfPreventScrollAfterSelect, sfIgnoreNextChar, sfPainting, sfHasPainted, sfHasScrolled, sfScrollbarChanged, sfHorizScrollbarVisible, sfVertScrollbarVisible, sfGutterResized, sfAfterLoadFromFileNeeded, sfAfterHandleCreatedNeeded, // Mouse-states sfLeftGutterClick, sfRightGutterClick, sfInClick, sfDblClicked, sfTripleClicked, sfQuadClicked, sfWaitForDragging, sfWaitForDraggingNoCaret, sfIsDragging, // SynEdit is drag-source sfDraggingOver, // SynEdit is drag target sfWaitForMouseSelecting, sfMouseSelecting, sfMouseDoneSelecting, sfIgnoreUpClick, sfSelChanged ); //mh 2000-10-30 TSynStateFlags = set of TSynStateFlag; TSynEditorMouseOption = SynEditMouseCmds.TSynEditorMouseOption; //emUseMouseActions // Enable mouse actions //emAltSetsColumnMode // Allows to activate "column" selection mode, if <Alt> key is pressed and text is being selected with mouse //emDragDropEditing // Allows to drag-and-drop text blocks within the control //emRightMouseMovesCursor // When clicking with the right mouse button, for a popup menu, move the caret to clicked position //emDoubleClickSelectsLine // Selects entire line with double-click, otherwise double-click selects only current word //emShowCtrlMouseLinks // Pressing <Ctrl> key (SYNEDIT_LINK_MODIFIER) will highlight the word under mouse cursor //emCtrlWheelZoom // Allows to zoom editor by <Ctrl+MouseWheel> commands TSynEditorMouseOptions = SynEditMouseCmds.TSynEditorMouseOptions; // options for textbuffersharing TSynEditorShareOption = ( eosShareMarks // Shared Editors use the same list of marks ); TSynEditorShareOptions = set of TSynEditorShareOption; TSynVisibleSpecialChars = SynEditTypes.TSynVisibleSpecialChars; const SYNEDIT_OLD_MOUSE_OPTIONS = [ eoAltSetsColumnMode, // eoDragDropEditing, // Allows you to select a block of text and drag it within the document to another location eoRightMouseMovesCursor, // When clicking with the right mouse for a popup menu, move the cursor to that location eoDoubleClickSelectsLine, // Select line on double click eoShowCtrlMouseLinks // Pressing Ctrl (SYNEDIT_LINK_MODIFIER) will highlight the word under the mouse cursor ]; SYNEDIT_OLD_MOUSE_OPTIONS_MAP: array [eoAltSetsColumnMode..eoShowCtrlMouseLinks] of TSynEditorMouseOption = ( emAltSetsColumnMode, // eoAltSetsColumnMode emDragDropEditing, // eoDragDropEditing emRightMouseMovesCursor, // eoRightMouseMovesCursor emDoubleClickSelectsLine, // eoDoubleClickSelectsLine emShowCtrlMouseLinks // eoShowCtrlMouseLinks ); SYNEDIT_DEFAULT_SHARE_OPTIONS = [ eosShareMarks ]; SYNEDIT_DEFAULT_VISIBLESPECIALCHARS = [ vscSpace, vscTabAtLast ]; type // use scAll to update a statusbar when another TCustomSynEdit got the focus TSynStatusChange = SynEditTypes.TSynStatusChange; TSynStatusChanges = SynEditTypes.TSynStatusChanges; TStatusChangeEvent = SynEditTypes.TStatusChangeEvent; TCustomSynEdit = class; { TLazSynEditPlugin } TLazSynEditPlugin = class(TSynEditFriend) protected procedure BeforeEditorChange; virtual; procedure AfterEditorChange; virtual; procedure RegisterToEditor(AValue: TCustomSynEdit); procedure UnRegisterFromEditor(AValue: TCustomSynEdit); procedure SetEditor(const AValue: TCustomSynEdit); virtual; function GetEditor: TCustomSynEdit; function OwnedByEditor: Boolean; virtual; // if true, this will be destroyed by synedit procedure DoEditorDestroyed(const AValue: TCustomSynEdit); virtual; procedure DoEditorAdded(AValue: TCustomSynEdit); virtual; procedure DoEditorRemoving(AValue: TCustomSynEdit); virtual; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; property Editor: TCustomSynEdit read GetEditor write SetEditor; end; { TSynHookedKeyTranslationList } TSynHookedKeyTranslationList = Class(TMethodList) public procedure CallHookedKeyTranslationHandlers(Sender: TObject; Code: word; SState: TShiftState; var Data: pointer; var IsStartOfCombo: boolean; var Handled: boolean; var Command: TSynEditorCommand; // ComboKeyStrokes decides, either FinishComboOnly, or new stroke var ComboKeyStrokes: TSynEditKeyStrokes ); end; { TSynUndoRedoItemHandlerList } TSynUndoRedoItemHandlerList = Class(TMethodList) public function CallUndoRedoItemHandlers(Caller: TObject; Item: TSynEditUndoItem): Boolean; end; { TLazSynMouseDownEventList } TLazSynMouseDownEventList = Class(TMethodList) public procedure CallMouseDownHandlers(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); end; { TLazSynKeyDownEventList } TLazSynKeyDownEventList = Class(TMethodList) public procedure CallKeyDownHandlers(Sender: TObject; var Key: Word; Shift: TShiftState); end; { TLazSynKeyPressEventList } TLazSynKeyPressEventList = Class(TMethodList) public procedure CallKeyPressHandlers(Sender: TObject; var Key: char); end; { TLazSynUtf8KeyPressEventList } TLazSynUtf8KeyPressEventList = Class(TMethodList) public procedure CallUtf8KeyPressHandlers(Sender: TObject; var UTF8Key: TUTF8Char); end; TSynMouseLinkEvent = procedure ( Sender: TObject; X, Y: Integer; var AllowMouseLink: Boolean) of object; TSynHomeMode = (synhmDefault, synhmFirstWord); TSynCoordinateMappingFlag = SynEditTypes.TSynCoordinateMappingFlag; TSynCoordinateMappingFlags = SynEditTypes.TSynCoordinateMappingFlags; TLazSynWordBoundary = ( swbWordBegin, swbWordEnd, swbTokenBegin, swbTokenEnd, swbCaseChange, swbWordSmart // begin or end of word with smart gaps (1 char) ); { TSynScrollOnEditOptions } TSynScrollOnEditOptions = class(TPersistent) private FKeepBorderDistance: integer; FKeepBorderDistancePercent: integer; FOnChange: TNotifyEvent; FScrollExtraColumns: integer; FScrollExtraMax: integer; FScrollExtraPercent: integer; procedure SetKeepBorderDistance(AValue: integer); procedure SetKeepBorderDistancePercent(AValue: integer); procedure SetScrollExtraColumns(AValue: integer); procedure SetScrollExtraMax(AValue: integer); procedure SetScrollExtraPercent(AValue: integer); protected FCurrentDistance: integer; FCurrentColumns: integer; public constructor Create; procedure Assign(Source: TPersistent); override; procedure SetDefaults; virtual; property OnChange: TNotifyEvent read FOnChange write FOnChange; property KeepBorderDistance: integer read FKeepBorderDistance write SetKeepBorderDistance; property KeepBorderDistancePercent: integer read FKeepBorderDistancePercent write SetKeepBorderDistancePercent; property ScrollExtraColumns: integer read FScrollExtraColumns write SetScrollExtraColumns; property ScrollExtraPercent: integer read FScrollExtraPercent write SetScrollExtraPercent; property ScrollExtraMax: integer read FScrollExtraMax write SetScrollExtraMax; end; { TSynScrollOnEditLeftOptions } TSynScrollOnEditLeftOptions = class(TSynScrollOnEditOptions) private const CKeepBorderDistance = 2; CKeepBorderDistancePercent = 0; CScrollExtraColumns = 5; CScrollExtraPercent = 20; CScrollExtraMax = 10; public procedure SetDefaults; override; published property KeepBorderDistance default CKeepBorderDistance; property KeepBorderDistancePercent default CKeepBorderDistancePercent; property ScrollExtraColumns default CScrollExtraColumns; property ScrollExtraPercent default CScrollExtraPercent; property ScrollExtraMax default CScrollExtraMax; end; { TSynScrollOnEditRightOptions } TSynScrollOnEditRightOptions = class(TSynScrollOnEditOptions) private const CKeepBorderDistance = 0; CKeepBorderDistancePercent = 0; CScrollExtraColumns = 10; CScrollExtraPercent = 30; CScrollExtraMax = 25; public procedure SetDefaults; override; published property KeepBorderDistance default CKeepBorderDistance; property KeepBorderDistancePercent default CKeepBorderDistancePercent; property ScrollExtraColumns default CScrollExtraColumns; property ScrollExtraPercent default CScrollExtraPercent; property ScrollExtraMax default CScrollExtraMax; end; { TCustomSynEdit } TCustomSynEdit = class(TSynEditBase) procedure SelAvailChange(Sender: TObject); private FImeHandler: LazSynIme; {$IFDEF Gtk2IME} protected procedure GTK_IMComposition(var Message: TMessage); message LM_IM_COMPOSITION; {$ENDIF} {$IFDEF CocoaIME} private procedure COCOA_IMComposition(var Message: TMessage); message LM_IM_COMPOSITION; {$ENDIF} {$IFDEF WinIME} private procedure WMImeRequest(var Msg: TMessage); message WM_IME_REQUEST; procedure WMImeNotify(var Msg: TMessage); message WM_IME_NOTIFY; procedure WMImeStartComposition(var Msg: TMessage); message WM_IME_STARTCOMPOSITION; procedure WMImeComposition(var Msg: TMessage); message WM_IME_COMPOSITION; procedure WMImeEndComposition(var Msg: TMessage); message WM_IME_ENDCOMPOSITION; {$ENDIF} private procedure SetImeHandler(AValue: LazSynIme); protected // SynEdit takes ownership property ImeHandler: LazSynIme read FImeHandler write SetImeHandler; private procedure WMDropFiles(var Msg: TMessage); message WM_DROPFILES; procedure WMEraseBkgnd(var Msg: TMessage); message WM_ERASEBKGND; procedure WMGetDlgCode(var Msg: TWMGetDlgCode); message WM_GETDLGCODE; procedure WMHScroll(var Msg: TLMScroll); message WM_HSCROLL; procedure WMKillFocus(var Msg: TWMKillFocus); message WM_KILLFOCUS; procedure WMExit(var Message: TLMExit); message LM_EXIT; procedure WMMouseWheel(var Message: TLMMouseEvent); message LM_MOUSEWHEEL; procedure WMMouseHorizWheel(var Message: TLMMouseEvent); message LM_MOUSEHWHEEL; //procedure WMMouseWheel(var Msg: TMessage); message WM_MOUSEWHEEL; procedure WMSetFocus(var Msg: TLMSetFocus); message WM_SETFOCUS; procedure WMVScroll(var Msg: TLMScroll); message WM_VSCROLL; protected procedure CMWantSpecialKey(var Message: TLMessage); message CM_WANTSPECIALKEY; private FScrollOnEditLeftOptions: TSynScrollOnEditOptions; FScrollOnEditRightOptions: TSynScrollOnEditOptions; FTextCursor, FOffTextCursor, FOverrideCursor: TCursor; FBlockIndent: integer; FBlockTabIndent: integer; FCaret: TSynEditCaret; FInternalCaret: TSynEditCaret; //FScreenCaret: TSynEditScreenCaret; FInternalBlockSelection: TSynEditSelection; FLastCaretXForMoveSelection: Integer; FOnChangeUpdating: TChangeUpdatingEvent; FMouseSelectionMode: TSynSelectionMode; FMouseSelectionCmd: TSynEditorMouseCommand; fMarkupManager : TSynEditMarkupManager; fMarkupHighAll : TSynEditMarkupHighlightAll; fMarkupHighCaret : TSynEditMarkupHighlightAllCaret; fMarkupBracket : TSynEditMarkupBracket; fMarkupWordGroup : TSynEditMarkupWordGroup; fMarkupCtrlMouse : TSynEditMarkupCtrlMouseLink; fMarkupSpecialLine : TSynEditMarkupSpecialLine; fMarkupSelection : TSynEditMarkupSelection; fMarkupSpecialChar : TSynEditMarkupSpecialChar; fFontDummy: TFont; FLastSetFontSize: Integer; fInserting: Boolean; FLastMouseLocation: TSynMouseLocationInfo; FChangedLinesStart: integer; // 1 based, 0 means invalid FChangedLinesEnd: integer; // 1 based, 0 means invalid, -1 means rest of screen FChangedLinesDiff: integer; // count changed +/- FBeautifier, FDefaultBeautifier: TSynCustomBeautifier; FBeautifyStartLineIdx, FBeautifyEndLineIdx: Integer; FFoldedLinesView: TSynEditFoldedView; FShareOptions: TSynEditorShareOptions; FVisibleSpecialChars: TSynVisibleSpecialChars; FTextViewsManager: TSynTextViewsManager; FTrimmedLinesView: TSynEditStringTrimmingList; FDoubleWidthChrLinesView: SynEditStringDoubleWidthChars; FBidiChrLinesView: TSynEditStringBidiChars; {$IFDEF WithSynExperimentalCharWidth} FSysCharWidthLinesView: TSynEditStringSystemWidthChars; {$ENDIF} FTabbedLinesView: TSynEditStringTabExpanderBase; FTheLinesView: TSynEditStringsLinked; FLines: TSynEditStringListBase; // The real (un-mapped) line-buffer FStrings: TStrings; // External TStrings based interface to the Textbuffer fMaxLeftChar: Integer; // 1024 FPaintLock: Integer; FPaintLockOwnerCnt: Integer; FUndoBlockAtPaintLock: Integer; FStatusChangeLock: Integer; FRecalcCharsAndLinesLock: Integer; FDoingResizeLock: Integer; FInvalidateRect: TRect; FIsInDecPaintLock: Boolean; FScrollBars: TScrollStyle; FOldTopView: Integer; FLastTextChangeStamp: Int64; fHighlighter: TSynCustomHighlighter; fUndoList: TSynEditUndoList; fRedoList: TSynEditUndoList; FBookMarks: array[0..9] of TSynEditMark; fMouseDownX: integer; fMouseDownY: integer; FMouseDownButton: TMouseButton; FMouseDownShift: TShiftState; FConfirmMouseDownMatchAct: TSynEditMouseAction; FConfirmMouseDownMatchFound: Boolean; FMouseWheelAccumulator, FMouseWheelLinesAccumulator: Array [Boolean] of integer; fOverwriteCaret: TSynEditCaretType; fInsertCaret: TSynEditCaretType; FKeyStrokes: TSynEditKeyStrokes; FCurrentComboKeyStrokes: TSynEditKeyStrokes; // Holding info about the keystroke(s) already received for a mult-stroke-combo FMouseActions, FMouseSelActions, FMouseTextActions: TSynEditMouseInternalActions; FMouseActionSearchHandlerList: TSynEditMouseActionSearchList; FMouseActionExecHandlerList: TSynEditMouseActionExecList; FMouseActionShiftMask: TShiftState; FMarkList: TSynEditMarkList; FUseUTF8: boolean deprecated 'always true'; fWantTabs: boolean; FLeftGutter, FRightGutter: TSynGutter; fTabWidth: integer; fTextDrawer: TLazEditTextGridPainter; FPaintLineColor, FPaintLineColor2: TSynSelectedColor; fStateFlags: TSynStateFlags; fStatusChanges: TSynStatusChanges; fTSearch: TSynEditSearch; fHookedCommandHandlers: TList; FHookedKeyTranslationList: TSynHookedKeyTranslationList; FUndoRedoItemHandlerList: TSynUndoRedoItemHandlerList; FMouseDownEventList: TLazSynMouseDownEventList; FQueryMouseCursorList: TObject; FKeyDownEventList: TLazSynKeyDownEventList; FKeyUpEventList: TLazSynKeyDownEventList; FKeyPressEventList: TLazSynKeyPressEventList; FUtf8KeyPressEventList: TLazSynUtf8KeyPressEventList; FStatusChangedList: TObject; FPaintEventHandlerList: TObject; // TSynPaintEventHandlerList FScrollEventHandlerList: TObject; // TSynScrollEventHandlerList FPlugins: TList; fScrollTimer: TTimer; FScrollDeltaX, FScrollDeltaY: Integer; FInMouseClickEvent: Boolean; // event handlers FOnCutCopy: TSynCopyPasteEvent; FOnPaste: TSynCopyPasteEvent; fOnChange: TNotifyEvent; FOnClearMark: TPlaceMarkEvent; // djlp 2000-08-29 fOnCommandProcessed: TProcessCommandEvent; fOnDropFiles: TSynDropFilesEvent; fOnPaint: TPaintEvent; FOnPlaceMark: TPlaceMarkEvent; fOnProcessCommand: TProcessCommandEvent; fOnProcessUserCommand: TProcessCommandEvent; fOnReplaceText: TReplaceTextEvent; fOnSpecialLineColors: TSpecialLineColorsEvent;// needed, because bug fpc 11926 fOnStatusChange: TStatusChangeEvent; FOnSpecialLineMarkup: TSpecialLineMarkupEvent;// needed, because bug fpc 11926 FOnClickLink: TMouseEvent; FOnMouseLink: TSynMouseLinkEvent; FPendingFoldState: String; procedure DoTopViewChanged(Sender: TObject); function GetIsStickySelecting: Boolean; procedure SetScrollOnEditLeftOptions(AValue: TSynScrollOnEditOptions); procedure SetScrollOnEditRightOptions(AValue: TSynScrollOnEditOptions); procedure SetTabViewClass(AValue: TSynEditStringTabExpanderClass); procedure UpdateScreenCaret; procedure AquirePrimarySelection; function GetChangeStamp: int64; function GetDefSelectionMode: TSynSelectionMode; function GetFoldedCodeLineColor: TSynSelectedColor; function GetFoldState: String; function GetHiddenCodeLineColor: TSynSelectedColor; function GetPaintLockOwner: TSynEditBase; function GetPlugin(Index: Integer): TLazSynEditPlugin; function GetRightEdge: Integer; function GetRightEdgeColor: TColor; function GetTextBetweenPoints(aStartPoint, aEndPoint: TPoint): String; procedure SetBlockTabIndent(AValue: integer); procedure SetBracketMatchColor(AValue: TSynSelectedColor); procedure SetTextCursor(AValue: TCursor); procedure SetDefSelectionMode(const AValue: TSynSelectionMode); procedure SetFoldedCodeColor(AValue: TSynSelectedColor); procedure SetFoldedCodeLineColor(AValue: TSynSelectedColor); procedure SetFoldState(const AValue: String); procedure SetHiddenCodeLineColor(AValue: TSynSelectedColor); procedure SetHighlightAllColor(AValue: TSynSelectedColor); procedure SetIncrementColor(AValue: TSynSelectedColor); procedure SetLineHighlightColor(AValue: TSynSelectedColor); procedure SetMouseLinkColor(AValue: TSynSelectedColor); procedure SetOffTextCursor(AValue: TCursor); procedure SetPaintLockOwner(const AValue: TSynEditBase); procedure SetShareOptions(const AValue: TSynEditorShareOptions); procedure SetTextBetweenPointsSimple(aStartPoint, aEndPoint: TPoint; const AValue: String); procedure SetTextBetweenPointsEx(aStartPoint, aEndPoint: TPoint; aCaretMode: TSynCaretAdjustMode; const AValue: String); procedure SetVisibleSpecialChars(AValue: TSynVisibleSpecialChars); procedure SurrenderPrimarySelection; procedure ComputeCaret(X, Y: Integer); procedure DoBlockIndent(AColumnIndentOutside: Boolean = False); procedure DoBlockIndentColSel(AnIndentOutside: Boolean = False; ADeleteAtRightBound: Boolean = False); procedure DoBlockUnindent(AColumnIndentOutside: Boolean = False); procedure DoBlockUnindentColSel(AnIndentOutside: Boolean = False; ADeleteAtLeftBound: Boolean = False); procedure DoHomeKey(aMode: TSynHomeMode = synhmDefault); procedure DoEndKey; procedure DoTabKey; function FindHookedCmdEvent(AHandlerProc: THookedCommandEvent): integer; function GetBracketHighlightStyle: TSynEditBracketHighlightStyle; function GetCanPaste: Boolean; function GetFoldedCodeColor: TSynSelectedColor; function GetMarkup(Index: integer): TSynEditMarkup; function GetMarkupByClass(Index: TSynEditMarkupClass): TSynEditMarkup; function GetCaretUndo: TSynEditUndoItem; function GetHighlightAllColor : TSynSelectedColor; function GetIncrementColor : TSynSelectedColor; function GetLineHighlightColor: TSynSelectedColor; function GetOnGutterClick : TGutterClickEvent; function GetBracketMatchColor : TSynSelectedColor; function GetMouseLinkColor : TSynSelectedColor; function GetTrimSpaceType: TSynEditStringTrimmingType; procedure SetBracketHighlightStyle(const AValue: TSynEditBracketHighlightStyle); procedure SetOnGutterClick(const AValue : TGutterClickEvent); procedure SetSpecialLineColors(const AValue : TSpecialLineColorsEvent); procedure SetSpecialLineMarkup(const AValue : TSpecialLineMarkupEvent); function GetHookedCommandHandlersCount: integer; function GetLineText: string; function GetCharLen(const Line: string; CharStartPos: integer): integer; // TODO: deprecated procedure SetBeautifier(NewBeautifier: TSynCustomBeautifier); function GetMaxUndo: Integer; procedure SetTrimSpaceType(const AValue: TSynEditStringTrimmingType); function SynGetText: string; procedure GutterChanged(Sender: TObject); procedure GutterResized(Sender: TObject); // x-pixel pos of first char on canvas function TextLeftPixelOffset(IncludeGutterTextDist: Boolean = True): Integer; function TextRightPixelOffset: Integer; function IsPointInSelection(Value: TPoint; AnIgnoreAtSelectionBound: Boolean = False): boolean; procedure LockUndo; procedure MoveCaretHorz(DX: integer); procedure MoveCaretVert(DY: integer; UseScreenLine: Boolean = False); procedure PrimarySelectionRequest(const RequestedFormatID: TClipboardFormat; Data: TStream); procedure ScanRanges(ATextChanged: Boolean = True); procedure IdleScanRanges(Sender: TObject; var Done: Boolean); procedure DoBlockSelectionChanged(Sender: TObject); procedure SetBlockIndent(const AValue: integer); procedure SetCaretAndSelection(const ptCaret, ptBefore, ptAfter: TPoint; Mode: TSynSelectionMode = smCurrent; MakeSelectionVisible: Boolean = False; AForceSingleLineSelected: Boolean = False ); procedure SetGutter(const Value: TSynGutter); procedure SetRightGutter(const AValue: TSynGutter); procedure RemoveHooksFromHighlighter; procedure SetInsertCaret(const Value: TSynEditCaretType); procedure SetInsertMode(const Value: boolean); procedure SetKeystrokes(const Value: TSynEditKeyStrokes); procedure SetLastMouseCaret(const AValue: TPoint); function CurrentMaxLeftChar(AIncludeCharsInWin: Boolean = False): Integer; function CurrentMaxLineLen: Integer; procedure SetLineText(Value: string); procedure SetMaxLeftChar(Value: integer); procedure SetMaxUndo(const Value: Integer); procedure UpdateOptions; procedure UpdateOptions2; procedure UpdateMouseOptions; procedure SetOverwriteCaret(const Value: TSynEditCaretType); procedure SetRightEdge(Value: Integer); procedure SetRightEdgeColor(Value: TColor); procedure SetScrollBars(const Value: TScrollStyle); function GetSelectionMode : TSynSelectionMode; procedure SetSelectionMode(const Value: TSynSelectionMode); procedure SetTabWidth(Value: integer); procedure SynSetText(const Value: string); function CurrentMaxTopView: Integer; procedure ScrollAfterTopLineChanged; procedure SetWantTabs(const Value: boolean); procedure SetWordBlock(Value: TPoint); procedure SetLineBlock(Value: TPoint; WithLeadSpaces: Boolean = True); procedure SetParagraphBlock(Value: TPoint); procedure RecalcScrollOnEdit(Sender: TObject); procedure RecalcCharsAndLinesInWin(CheckCaret: Boolean); procedure StatusChangedEx(Sender: TObject; Changes: TSynStatusChanges); procedure UndoRedoAdded(Sender: TObject); procedure ModifiedChanged(Sender: TObject); procedure UnlockUndo; procedure UpdateCaret(IgnorePaintLock: Boolean = False); procedure UpdateScrollBars; procedure ChangeTextBuffer(NewBuffer: TSynEditStringList); function IsMarkListShared: Boolean; procedure RecreateMarkList; procedure DestroyMarkList; procedure ExtraLineCharsChanged(Sender: TObject); procedure InternalBeginUndoBlock(aList: TSynEditUndoList = nil); // includes paintlock procedure InternalEndUndoBlock(aList: TSynEditUndoList = nil); protected FScreenCaretPainterClass: TSynEditScreenCaretPainterClass deprecated 'need refactor'; {$IFDEF EnableDoubleBuf} BufferBitmap: TBitmap; // the double buffer SavedCanvas: TCanvas; // the normal TCustomControl canvas during paint {$ENDIF} FTextArea: TLazSynTextArea; FLeftGutterArea, FRightGutterArea: TLazSynGutterArea; FPaintArea: TLazSynSurfaceManager; property ScreenCaret: TSynEditScreenCaret read FScreenCaret; property OnClickLink : TMouseEvent read FOnClickLink write FOnClickLink; property OnMouseLink: TSynMouseLinkEvent read FOnMouseLink write FOnMouseLink; procedure Paint; override; procedure StartPaintBuffer(const ClipRect: TRect); procedure EndPaintBuffer(const ClipRect: TRect); procedure DoOnPaint; virtual; function GetPaintArea: TLazSynSurfaceManager; override; procedure IncPaintLock; procedure DecPaintLock; procedure DoIncPaintLock(Sender: TObject); procedure DoDecPaintLock(Sender: TObject); procedure DoIncForeignPaintLock(Sender: TObject); procedure DoDecForeignPaintLock(Sender: TObject); procedure SetUpdateState(NewUpdating: Boolean; Sender: TObject); virtual; // Called *before* paintlock, and *after* paintlock procedure IncStatusChangeLock; procedure DecStatusChangeLock; procedure StatusChanged(AChanges: TSynStatusChanges); override; property PaintLockOwner: TSynEditBase read GetPaintLockOwner write SetPaintLockOwner; property TextDrawer: TLazEditTextGridPainter read fTextDrawer; procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy; const AXProportion, AYProportion: Double); override; protected function WaitingForInitialSize: boolean; inline; procedure DoHandleInitialSizeFinished; procedure DoAllAutoSize; override; procedure CreateHandle; override; procedure CreateParams(var Params: TCreateParams); override; procedure CreateWnd; override; procedure DestroyWnd; override; procedure VisibleChanged; override; procedure Loaded; override; function GetChildOwner: TComponent; override; procedure KeyDown(var Key: Word; Shift: TShiftState); override; procedure UTF8KeyPress(var Key: TUTF8Char); override; procedure KeyPress(var Key: Char); override; procedure KeyUp(var Key : Word; Shift : TShiftState); override; procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override; procedure MouseMove(Shift: TShiftState; X, Y: Integer); override; procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override; procedure DragTimerHandler; procedure ScrollTimerHandler(Sender: TObject); procedure DoContextPopup(MousePos: TPoint; var Handled: Boolean); override; procedure FindAndHandleMouseAction(AButton: TSynMouseButton; AShift: TShiftState; X, Y: Integer; ACCount:TSynMAClickCount; ADir: TSynMAClickDir; out AnActionResult: TSynEditMouseActionResult; AWheelDelta: Integer = 0); function DoHandleMouseAction(AnActionList: TSynEditMouseActions; var AnInfo: TSynEditMouseActionInfo): Boolean; procedure DoHandleMouseActionResult(AnActionResult: TSynEditMouseActionResult); function CheckDragDropAccecpt(ANewCaret: TPoint; ASource: TObject; out ADropMove: boolean): boolean; protected function GetBlockBegin: TPoint; override; function GetBlockEnd: TPoint; override; procedure SetBlockBegin(Value: TPoint); override; procedure SetBlockEnd(Value: TPoint); override; function GetCharsInWindow: Integer; override; function GetCharWidth: integer; override; function GetLeftChar: Integer; override; function GetLineHeight: integer; override; function GetLinesInWindow: Integer; override; function GetTopLine: Integer; override; procedure SetLeftChar(Value: Integer); override; procedure SetTopLine(Value: Integer); override; function GetCaretX : Integer; override; function GetCaretY : Integer; override; function GetCaretXY: TPoint; override; procedure SetCaretX(const Value: Integer); override; procedure SetCaretY(const Value: Integer); override; procedure SetCaretXY(Value: TPoint); override; function GetLogicalCaretXY: TPoint; override; procedure SetLogicalCaretXY(const NewLogCaretXY: TPoint); override; function GetMouseActions: TSynEditMouseActions; override; function GetMouseSelActions: TSynEditMouseActions; override; function GetMouseTextActions: TSynEditMouseActions; override; procedure SetMouseActions(const AValue: TSynEditMouseActions); override; procedure SetMouseSelActions(const AValue: TSynEditMouseActions); override; procedure SetMouseTextActions(AValue: TSynEditMouseActions); override; procedure SetExtraCharSpacing(const Value: integer); override; procedure SetExtraLineSpacing(const Value: integer); override; procedure SetHighlighter(const Value: TSynCustomHighlighter); virtual; procedure UpdateShowing; override; procedure SetColor(Value: TColor); override; (* Fractions go +/- 1..256 // Fraction bigger 256 mean on opposite side outside hot zone *) function GetDragHotZoneInfo(x,y: Integer; out HorizFraction, VertFraction: Integer): boolean; procedure DragOver(Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); override; procedure DoOnResize; override; procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: integer; WithThemeSpace: Boolean); override; function RealGetText: TCaption; override; procedure RealSetText(const Value: TCaption); override; function GetLines: TStrings; override; function GetModified: Boolean; override; function GetViewedTextBuffer: TSynEditStringsLinked; override; function GetFoldedTextBuffer: TObject; override; function GetTextBuffer: TSynEditStrings; override; procedure SetLines(Value: TStrings); override; function GetMarkupMgr: TObject; override; function GetCanRedo: Boolean; override; function GetCanUndo: Boolean; override; function GetCaretObj: TSynEditCaret; override; function GetHighlighterObj: TObject; override; function GetMarksObj: TObject; override; function GetSelectedColor : TSynSelectedColor; override; function GetTextViewsManager: TSynTextViewsManager; override; procedure FontChanged(Sender: TObject); override; procedure HighlighterAttrChanged(Sender: TObject); Procedure LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer); Procedure LineTextChanged(Sender: TSynEditStrings; AIndex, ACount : Integer); procedure SizeOrFontChanged(bFont: boolean); procedure DoHighlightChanged(Sender: TSynEditStrings; AIndex, ACount : Integer); procedure ListCleared(Sender: TObject); procedure FoldChanged(Sender: TSynEditStrings; aIndex, aCount: Integer); function GetTopView : Integer; procedure SetTopView(AValue : Integer); procedure MarkListChange(Sender: TSynEditMark; Changes: TSynEditMarkChangeReasons); procedure NotifyHookedCommandHandlers(var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer; ATime: THookedCommandFlag); virtual; function NextWordLogicalPos(ABoundary: TLazSynWordBoundary = swbWordBegin; WordEndForDelete : Boolean = false): TPoint; function PrevWordLogicalPos(ABoundary: TLazSynWordBoundary = swbWordBegin): TPoint; procedure RecalcCharExtent; procedure RedoItem(Item: TSynEditUndoItem); procedure CaretChanged(Sender: TObject); procedure SetModified(Value: boolean); override; procedure SetMouseOptions(AValue: TSynEditorMouseOptions); override; procedure SetName(const Value: TComponentName); override; procedure SetOptions(Value: TSynEditorOptions); override; procedure SetOptions2(Value: TSynEditorOptions2); override; procedure SetSelectedColor(const AValue : TSynSelectedColor); override; procedure SetSelTextExternal(const Value: string); override; procedure SetSelTextPrimitive(PasteMode: TSynSelectionMode; Value: PChar; AddToUndoList: Boolean = false); procedure UndoItem(Item: TSynEditUndoItem); procedure UpdateCursor; procedure DoOnCommandProcessed(Command: TSynEditorCommand; AChar: TUTF8Char; Data: pointer); virtual; // no method DoOnDropFiles, intercept the WM_DROPFILES instead procedure DoOnProcessCommand(var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer); virtual; function DoOnReplaceText(const ASearch, AReplace: string; Line, Column: integer): TSynReplaceAction; virtual; procedure DoOnStatusChange(Changes: TSynStatusChanges); virtual; property LastMouseCaret: TPoint read FLastMouseLocation.LastMouseCaret write SetLastMouseCaret; // TODO: deprecate? see MouseMove function GetSelEnd: integer; override; //L505 function GetSelStart: integer; override; procedure SetSelEnd(const Value: integer); override; procedure SetSelStart(const Value: integer); override; property TextView : TSynEditStringsLinked read FTheLinesView; function PasteFromClipboardEx(ClipHelper: TSynClipboardStream; AForceColumnMode: Boolean = False): Boolean; function FindNextUnfoldedLine(iLine: integer; Down: boolean): Integer; // Todo: Reduce the argument list of Creategutter function CreateGutter(AOwner : TSynEditBase; ASide: TSynGutterSide; ATextDrawer: TLazEditTextGridPainter): TSynGutter; virtual; public constructor Create(AOwner: TComponent); override; procedure BeforeDestruction; override; destructor Destroy; override; procedure AfterLoadFromFile; procedure BeginUndoBlock{$IFDEF SynUndoDebugBeginEnd}(ACaller: String = ''){$ENDIF}; override; procedure BeginUpdate(WithUndoBlock: Boolean = True); override; procedure EndUndoBlock{$IFDEF SynUndoDebugBeginEnd}(ACaller: String = ''){$ENDIF}; override; procedure EndUpdate; override; public property TopView: Integer read GetTopView write SetTopView; // TopLine converted into Visible(View) lines // Caret function CaretXPix: Integer; override; function CaretYPix: Integer; override; procedure EnsureCursorPosVisible; procedure MoveCaretToVisibleArea; procedure MoveCaretIgnoreEOL(const NewCaret: TPoint); procedure MoveLogicalCaretIgnoreEOL(const NewLogCaret: TPoint); // Selection procedure ClearSelection; procedure SelectAll; procedure SelectToBrace; procedure SelectWord; procedure SelectLine(WithLeadSpaces: Boolean = True); procedure SelectParagraph; property BlockBegin: TPoint read GetBlockBegin write SetBlockBegin; // Set Blockbegin. For none persistent also sets Blockend. Setting Caret may undo this and should be done before setting block property BlockEnd: TPoint read GetBlockEnd write SetBlockEnd; property SelStart: Integer read GetSelStart write SetSelStart; // 1-based byte pos of first selected char deprecated 'Use "BlockBegin" / SynMemo compatibility - very slow / SynEdit operates on x/y'; property SelEnd: Integer read GetSelEnd write SetSelEnd; // 1-based byte pos of first char after selction end deprecated 'Use "BlockEnd" / SynMemo compatibility - very slow / SynEdit operates on x/y'; property IsBackwardSel: Boolean read GetIsBackwardSel; property SelText: string read GetSelText write SetSelTextExternal; // Text Raw (not undo-able) procedure Clear; procedure Append(const Value: String); function HasText(AFlags: TSynEditHasTextFlags = []): Boolean; property LineText: string read GetLineText write SetLineText; // textline at CaretY property Text: string read SynGetText write SynSetText; // No uncommited (trailing/trimmable) spaces // Text (unho-able) procedure ClearAll; procedure InsertTextAtCaret(aText: String; aCaretMode : TSynCaretAdjustMode = scamEnd); property TextBetweenPoints[aStartPoint, aEndPoint: TPoint]: String // Logical Points read GetTextBetweenPoints write SetTextBetweenPointsSimple; property TextBetweenPointsEx[aStartPoint, aEndPoint: TPoint; CaretMode: TSynCaretAdjustMode]: String write SetTextBetweenPointsEx; procedure SetTextBetweenPoints(aStartPoint, aEndPoint: TPoint; const AValue: String; aFlags: TSynEditTextFlags = []; aCaretMode: TSynCaretAdjustMode = scamIgnore; aMarksMode: TSynMarksAdjustMode = smaMoveUp; aSelectionMode: TSynSelectionMode = smNormal ); function GetLineState(ALine: Integer): TSynLineState; override; procedure MarkTextAsSaved; // BoorMark procedure ClearBookMark(BookMark: Integer); function GetBookMark(BookMark: integer; var X, Y: integer): boolean; function GetBookMark(BookMark: integer; var X, Y, ALeft, ATop: integer): boolean; procedure GotoBookMark(BookMark: Integer); function IsBookmark(BookMark: integer): boolean; procedure SetBookMark(BookMark: Integer; X: Integer; Y: Integer; AnLeft: Integer = -1; AnTop: Integer = -1); property Marks: TSynEditMarkList read fMarkList; // Undo/Redo procedure ClearUndo; override; procedure Redo; override; procedure Undo; override; // Clipboard procedure CopyToClipboard; procedure CutToClipboard; procedure PasteFromClipboard(AForceColumnMode: Boolean = False); procedure DoCopyToClipboard(SText: string; FoldInfo: String = ''); property CanPaste: Boolean read GetCanPaste; procedure DragDrop(Source: TObject; X, Y: Integer); override; function ExecuteAction(ExeAction: TBasicAction): boolean; override; procedure CommandProcessor(Command:TSynEditorCommand; AChar: TUTF8Char; Data:pointer; ASkipHooks: THookedCommandFlags = []); virtual; procedure ExecuteCommand(Command: TSynEditorCommand; const AChar: TUTF8Char; Data: pointer); virtual; function GetHighlighterAttriAtRowCol(XY: TPoint; out Token: string; out Attri: TSynHighlighterAttributes): boolean; function GetHighlighterAttriAtRowColEx(XY: TPoint; out Token: string; out TokenType, Start: Integer; out Attri: TSynHighlighterAttributes; ContinueIfPossible: boolean = False): boolean; function GetHighlighterAttriAtRowColEx(XY: TPoint; out TokenType: Integer; ContinueIfPossible: boolean = False): boolean; procedure CaretAtIdentOrString(XY: TPoint; out AtIdent, NearString: Boolean); procedure GetWordBoundsAtRowCol(const XY: TPoint; out StartX, EndX: integer); override; function GetWordAtRowCol(XY: TPoint): string; override; function NextTokenPos: TPoint; virtual; deprecated; // use next word pos instead function NextWordPos: TPoint; virtual; function PrevWordPos: TPoint; virtual; function IdentChars: TSynIdentChars; function IsIdentChar(const c: TUTF8Char): boolean; function IsLinkable(Y, X1, X2: Integer): Boolean; override; procedure InvalidateGutter; override; procedure InvalidateLine(Line: integer); override; procedure InvalidateGutterLines(FirstLine, LastLine: integer); override; // Currently invalidates full line => that may change procedure InvalidateLines(FirstLine, LastLine: integer); override; // Byte to Char function LogicalToPhysicalPos(const p: TPoint): TPoint; override; function LogicalToPhysicalCol(const Line: String; Index, LogicalPos : integer): integer; override; // Char to Byte function PhysicalToLogicalPos(const p: TPoint): TPoint; override; function PhysicalToLogicalCol(const Line: string; Index, PhysicalPos: integer): integer; override; function PhysicalLineLength(Line: String; Index: integer): integer; override; (* from SynMemo - NOT recommended to use - Extremly slow code SynEdit (and SynMemo) is a Linebased Editor and not meant to be accessed as a contineous text Warning: This ignoces trailing spaces (same as in SynMemo). Result may be incorrect. If the caret must be adjusted use SetTextBetweenPoints() *) function CharIndexToRowCol(Index: integer): TPoint; experimental; deprecated 'SynMemo compatibility - very slow / SynEdit operates on x/y'; function RowColToCharIndex(RowCol: TPoint): integer; experimental; deprecated 'SynMemo compatibility - very slow / SynEdit operates on x/y'; // End "from SynMemo" // Pixel function ScreenColumnToXValue(Col: integer): integer; // map screen column to screen pixel function ScreenXYToPixels(RowCol: TScreenPoint): TPoint; // converts screen position (1,1) based function RowColumnToPixels(RowCol: TScreenPoint): TPoint; deprecated 'use ScreenXYToPixels(TextXYToScreenXY(point))'; function PixelsToRowColumn(Pixels: TPoint; aFlags: TSynCoordinateMappingFlags = [scmLimitToLines]): TPhysPoint; function PixelsToLogicalPos(const Pixels: TPoint): TLogPoint; // function ScreenRowToRow(ScreenRow: integer; LimitToLines: Boolean = True): integer; override; deprecated 'use ScreenXYToTextXY'; function RowToScreenRow(PhysicalRow: integer): integer; override; deprecated 'use TextXYToScreenXY'; (* ScreenXY: First visible (scrolled in) screen line is 1 First column is 1 => column does not take scrolling into account *) function ScreenXYToTextXY(AScreenXY: TPhysScreenPoint; LimitToLines: Boolean = True): TPhysPoint; override; function TextXYToScreenXY(APhysTextXY: TPhysPoint): TPhysScreenPoint; override; procedure Notification(AComponent: TComponent; Operation: TOperation); override; procedure RegisterCommandHandler(AHandlerProc: THookedCommandEvent; AHandlerData: pointer; AFlags: THookedCommandFlags = [hcfPreExec, hcfPostExec]); override; procedure UnregisterCommandHandler(AHandlerProc: THookedCommandEvent); override; procedure RegisterMouseActionSearchHandler(AHandlerProc: TSynEditMouseActionSearchProc); override; procedure UnregisterMouseActionSearchHandler(AHandlerProc: TSynEditMouseActionSearchProc); override; procedure RegisterMouseActionExecHandler(AHandlerProc: TSynEditMouseActionExecProc); override; procedure UnregisterMouseActionExecHandler(AHandlerProc: TSynEditMouseActionExecProc); override; procedure RegisterKeyTranslationHandler(AHandlerProc: THookedKeyTranslationEvent); override; procedure UnRegisterKeyTranslationHandler(AHandlerProc: THookedKeyTranslationEvent); override; procedure RegisterUndoRedoItemHandler(AHandlerProc: TSynUndoRedoItemEvent); override; procedure UnRegisterUndoRedoItemHandler(AHandlerProc: TSynUndoRedoItemEvent); override; procedure RegisterStatusChangedHandler(AStatusChangeProc: TStatusChangeEvent; AChanges: TSynStatusChanges); override; procedure UnRegisterStatusChangedHandler(AStatusChangeProc: TStatusChangeEvent); override; procedure RegisterBeforeMouseDownHandler(AHandlerProc: TMouseEvent); override; procedure UnregisterBeforeMouseDownHandler(AHandlerProc: TMouseEvent); override; procedure RegisterQueryMouseCursorHandler(AHandlerProc: TSynQueryMouseCursorEvent); override; procedure UnregisterQueryMouseCursorHandler(AHandlerProc: TSynQueryMouseCursorEvent); override; procedure RegisterBeforeKeyDownHandler(AHandlerProc: TKeyEvent); override; procedure UnregisterBeforeKeyDownHandler(AHandlerProc: TKeyEvent); override; procedure RegisterBeforeKeyUpHandler(AHandlerProc: TKeyEvent); override; procedure UnregisterBeforeKeyUpHandler(AHandlerProc: TKeyEvent); override; procedure RegisterBeforeKeyPressHandler(AHandlerProc: TKeyPressEvent); override; procedure UnregisterBeforeKeyPressHandler(AHandlerProc: TKeyPressEvent); override; procedure RegisterBeforeUtf8KeyPressHandler(AHandlerProc: TUTF8KeyPressEvent); override; procedure UnregisterBeforeUtf8KeyPressHandler(AHandlerProc: TUTF8KeyPressEvent); override; procedure RegisterPaintEventHandler(APaintEventProc: TSynPaintEventProc; AnEvents: TSynPaintEvents); override; procedure UnRegisterPaintEventHandler(APaintEventProc: TSynPaintEventProc); override; procedure RegisterScrollEventHandler(AScrollEventProc: TSynScrollEventProc; AnEvents: TSynScrollEvents); override; procedure UnRegisterScrollEventHandler(AScrollEventProc: TSynScrollEventProc); override; function SearchReplace(const ASearch, AReplace: string; AOptions: TSynSearchOptions): integer; function SearchReplaceEx(const ASearch, AReplace: string; AOptions: TSynSearchOptions; AStart: TPoint): integer; procedure SetUseIncrementalColor(const AValue : Boolean); procedure SetDefaultKeystrokes; virtual; procedure ResetMouseActions; // set mouse-actions according to current Options / may clear them procedure SetOptionFlag(Flag: TSynEditorOption; Value: boolean); Procedure SetHighlightSearch(const ASearch: String; AOptions: TSynSearchOptions); function UpdateAction(TheAction: TBasicAction): boolean; override; procedure WndProc(var Msg: TMessage); override; procedure EraseBackground(DC: HDC); override; procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override; function FindGutterFromGutterPartList(const APartList: TObject): TObject; override; public procedure FindMatchingBracket; override; function FindMatchingBracket(PhysStartBracket: TPoint; StartIncludeNeighborChars, MoveCaret, SelectBrackets, OnlyVisible: Boolean ): TPoint; override; // Returns Physical function FindMatchingBracketLogical(LogicalStartBracket: TPoint; StartIncludeNeighborChars, MoveCaret, SelectBrackets, OnlyVisible: Boolean ): TPoint; override; // Returns Logical //code fold procedure CodeFoldAction(iLine: integer); deprecated; procedure UnfoldAll; deprecated; procedure FoldAll(StartLevel : Integer = 0; IgnoreNested : Boolean = False); deprecated; property FoldState: String read GetFoldState write SetFoldState; procedure AddKey(Command: TSynEditorCommand; Key1: word; SS1: TShiftState; Key2: word; SS2: TShiftState); public property MaxLeftChar: integer read fMaxLeftChar write SetMaxLeftChar default 1024; property ScrollOnEditLeftOptions: TSynScrollOnEditOptions read FScrollOnEditLeftOptions write SetScrollOnEditLeftOptions; property ScrollOnEditRightOptions: TSynScrollOnEditOptions read FScrollOnEditRightOptions write SetScrollOnEditRightOptions; property UseIncrementalColor : Boolean write SetUseIncrementalColor; property PaintLock: Integer read fPaintLock; property UseUTF8: boolean read FUseUTF8; deprecated 'always true'; procedure Invalidate; override; property ChangeStamp: int64 read GetChangeStamp; procedure ShareTextBufferFrom(AShareEditor: TCustomSynEdit); procedure UnShareTextBuffer; function PluginCount: Integer; property Plugin[Index: Integer]: TLazSynEditPlugin read GetPlugin; function MarkupCount: Integer; property Markup[Index: integer]: TSynEditMarkup read GetMarkup; property MarkupByClass[Index: TSynEditMarkupClass]: TSynEditMarkup read GetMarkupByClass; property TrimSpaceType: TSynEditStringTrimmingType read GetTrimSpaceType write SetTrimSpaceType; public // Caret procedure SetCaretTypeSize(AType: TSynCaretType;AWidth, AHeight, AXOffs, AYOffs: Integer); property InsertCaret: TSynEditCaretType read FInsertCaret write SetInsertCaret default ctVerticalLine; property OverwriteCaret: TSynEditCaretType read FOverwriteCaret write SetOverwriteCaret default ctBlock; // Selection property DefaultSelectionMode: TSynSelectionMode read GetDefSelectionMode write SetDefSelectionMode default smNormal; property SelectionMode: TSynSelectionMode read GetSelectionMode write SetSelectionMode default smNormal; property IsStickySelecting: Boolean read GetIsStickySelecting; // Cursor procedure UpdateCursorOverride; override; // Colors property MarkupManager: TSynEditMarkupManager read fMarkupManager; property Color default clWhite; property Cursor: TCursor read FTextCursor write SetTextCursor default crIBeam; property OffTextCursor: TCursor read FOffTextCursor write SetOffTextCursor default crDefault; property IncrementColor: TSynSelectedColor read GetIncrementColor write SetIncrementColor; property HighlightAllColor: TSynSelectedColor read GetHighlightAllColor write SetHighlightAllColor; property BracketMatchColor: TSynSelectedColor read GetBracketMatchColor write SetBracketMatchColor; property MouseLinkColor: TSynSelectedColor read GetMouseLinkColor write SetMouseLinkColor; property LineHighlightColor: TSynSelectedColor read GetLineHighlightColor write SetLineHighlightColor; property FoldedCodeColor: TSynSelectedColor read GetFoldedCodeColor write SetFoldedCodeColor; property FoldedCodeLineColor: TSynSelectedColor read GetFoldedCodeLineColor write SetFoldedCodeLineColor; property HiddenCodeLineColor: TSynSelectedColor read GetHiddenCodeLineColor write SetHiddenCodeLineColor; property Beautifier: TSynCustomBeautifier read fBeautifier write SetBeautifier; property BlockIndent: integer read FBlockIndent write SetBlockIndent default 2; property BlockTabIndent: integer read FBlockTabIndent write SetBlockTabIndent default 0; property Highlighter: TSynCustomHighlighter read fHighlighter write SetHighlighter; property Gutter: TSynGutter read FLeftGutter write SetGutter; property RightGutter: TSynGutter read FRightGutter write SetRightGutter; property InsertMode: boolean read fInserting write SetInsertMode default true; property Keystrokes: TSynEditKeyStrokes read FKeystrokes write SetKeystrokes; property MaxUndo: Integer read GetMaxUndo write SetMaxUndo default 1024; property ShareOptions: TSynEditorShareOptions read FShareOptions write SetShareOptions default SYNEDIT_DEFAULT_SHARE_OPTIONS; experimental; property VisibleSpecialChars: TSynVisibleSpecialChars read FVisibleSpecialChars write SetVisibleSpecialChars; property RightEdge: Integer read GetRightEdge write SetRightEdge default 80; property RightEdgeColor: TColor read GetRightEdgeColor write SetRightEdgeColor default clSilver; property ScrollBars: TScrollStyle read FScrollBars write SetScrollBars default ssBoth; property BracketHighlightStyle: TSynEditBracketHighlightStyle read GetBracketHighlightStyle write SetBracketHighlightStyle; property TabWidth: integer read fTabWidth write SetTabWidth default 8; property WantTabs: boolean read fWantTabs write SetWantTabs default True; property TabViewClass: TSynEditStringTabExpanderClass write SetTabViewClass; // Events property OnChange: TNotifyEvent read FOnChange write FOnChange; property OnChangeUpdating: TChangeUpdatingEvent read FOnChangeUpdating write FOnChangeUpdating; property OnCutCopy: TSynCopyPasteEvent read FOnCutCopy write FOnCutCopy; property OnPaste: TSynCopyPasteEvent read FOnPaste write FOnPaste; property OnDropFiles: TSynDropFilesEvent read fOnDropFiles write fOnDropFiles; property OnGutterClick: TGutterClickEvent read GetOnGutterClick write SetOnGutterClick; property OnPaint: TPaintEvent read fOnPaint write fOnPaint; // OnPlaceBookmark only triggers for Bookmarks property OnPlaceBookmark: TPlaceMarkEvent read FOnPlaceMark write FOnPlaceMark; // OnClearBookmark only triggers for Bookmarks property OnClearBookmark: TPlaceMarkEvent read FOnClearMark write FOnClearMark; property OnKeyDown; property OnKeyPress; property OnProcessCommand: TProcessCommandEvent read FOnProcessCommand write FOnProcessCommand; property OnProcessUserCommand: TProcessCommandEvent read FOnProcessUserCommand write FOnProcessUserCommand; property OnCommandProcessed: TProcessCommandEvent read fOnCommandProcessed write fOnCommandProcessed; property OnReplaceText: TReplaceTextEvent read fOnReplaceText write fOnReplaceText; property OnSpecialLineColors: TSpecialLineColorsEvent read FOnSpecialLineColors write SetSpecialLineColors; deprecated; property OnSpecialLineMarkup: TSpecialLineMarkupEvent read FOnSpecialLineMarkup write SetSpecialLineMarkup; property OnStatusChange: TStatusChangeEvent read fOnStatusChange write fOnStatusChange; end; TSynEdit = class(TCustomSynEdit) published // inherited properties property Align; property Beautifier; property BlockIndent; property BlockTabIndent; property BorderSpacing; property Anchors; property Constraints; property Color; property Cursor default crIBeam; property OffTextCursor default crDefault; property Enabled; property Font; property Height; property Name; property ParentColor; property ParentFont; property ParentShowHint; property PopupMenu; property ShowHint; property TabOrder; property TabStop default True; property Tag; property Visible; property Width; // inherited events property OnClick; property OnContextPopup; property OnDblClick; property OnTripleClick; property OnQuadClick; property OnDragDrop; property OnDragOver; // ToDo Docking property OnEndDock; property OnEndDrag; property OnEnter; property OnExit; property OnKeyDown; property OnKeyPress; property OnKeyUp; property OnMouseDown; property OnMouseMove; property OnMouseUp; property OnClickLink; property OnMouseLink; property OnMouseEnter; property OnMouseLeave; property OnMouseWheel; property OnMouseWheelDown; property OnMouseWheelUp; // ToDo Docking property OnStartDock; property OnStartDrag; property OnUTF8KeyPress; // TCustomSynEdit properties property BookMarkOptions; property BorderStyle default bsSingle; property ExtraCharSpacing; property ExtraLineSpacing; property Gutter; property RightGutter; property HideSelection; property Highlighter; property InsertCaret; property InsertMode; property Keystrokes; property MouseActions; property MouseTextActions; property MouseSelActions; property Lines; property MaxLeftChar; property MaxUndo; property OnResize; property Options; property Options2; property MouseOptions; property VisibleSpecialChars; property OverwriteCaret; property ReadOnly; property RightEdge; property RightEdgeColor; property ScrollBars; property SelectedColor; property ScrollOnEditLeftOptions; property ScrollOnEditRightOptions; property IncrementColor; property HighlightAllColor; property BracketHighlightStyle; property BracketMatchColor; property FoldedCodeColor; property MouseLinkColor; property LineHighlightColor; property DefaultSelectionMode; property SelectionMode; property TabWidth; property WantTabs; // TCustomSynEdit events property OnChange; property OnChangeUpdating; property OnCutCopy; property OnPaste; property OnClearBookmark; // djlp 2000-08-29 property OnCommandProcessed; property OnDropFiles; property OnGutterClick; property OnPaint; property OnPlaceBookmark; property OnProcessCommand; property OnProcessUserCommand; property OnReplaceText; property OnShowHint; property OnSpecialLineColors; deprecated; property OnSpecialLineMarkup; property OnStatusChange; end; procedure Register; implementation var LOG_SynMouseEvents: PLazLoggerLogGroup; const GutterTextDist = 2; //Pixel type TSynTextViewsManagerInternal = class(TSynTextViewsManager) public property TextBuffer; end; { TSynEditMarkListInternal } TSynEditMarkListInternal = class(TSynEditMarkList) private function GetLinesView: TSynEditStringsLinked; procedure SetLinesView(const AValue: TSynEditStringsLinked); protected procedure AddOwnerEdit(AEdit: TSynEditBase); procedure RemoveOwnerEdit(AEdit: TSynEditBase); property LinesView: TSynEditStringsLinked read GetLinesView write SetLinesView; end; TSynStatusChangedHandlerList = Class(TSynFilteredMethodList) public procedure Add(AHandler: TStatusChangeEvent; Changes: TSynStatusChanges); procedure Remove(AHandler: TStatusChangeEvent); procedure CallStatusChangedHandlers(Sender: TObject; Changes: TSynStatusChanges); end; { TSynPaintEventHandlerList } TSynPaintEventHandlerList = Class(TSynFilteredMethodList) public procedure Add(AHandler: TSynPaintEventProc; Changes: TSynPaintEvents); procedure Remove(AHandler: TSynPaintEventProc); procedure CallPaintEventHandlers(Sender: TObject; AnEvent: TSynPaintEvent; const rcClip: TRect); end; { TSynScrollEventHandlerList} TSynScrollEventHandlerList = Class(TSynFilteredMethodList) public procedure Add(AHandler: TSynScrollEventProc; Changes: TSynScrollEvents); procedure Remove(AHandler: TSynScrollEventProc); procedure CallScrollEventHandlers(Sender: TObject; AnEvent: TSynScrollEvent; dx, dy: Integer; const rcScroll, rcClip: TRect); end; { TSynQueryMouseCursorList } TSynQueryMouseCursorList = Class(TSynMethodList) public procedure Add(AHandler: TSynQueryMouseCursorEvent); procedure Remove(AHandler: TSynQueryMouseCursorEvent); procedure CallScrollEventHandlers(Sender: TObject; const AMouseLocation: TSynMouseLocationInfo; var AnCursor: TCursor); end; { TSynEditUndoCaret } TSynEditUndoCaret = class(TSynEditUndoItem) private FCaretPos: TPoint; protected function IsEqualContent(AnItem: TSynEditUndoItem): Boolean; override; function DebugString: String; override; public constructor Create(CaretPos: TPoint); function IsCaretInfo: Boolean; override; function PerformUndo(Caller: TObject): Boolean; override; end; { TSynEditUndoSelCaret } TSynEditUndoSelCaret = class(TSynEditUndoItem) private FCaretPos, FBeginPos, FEndPos: TPoint; FBlockMode: TSynSelectionMode; FForceSingleLineSelected: Boolean; protected function IsEqualContent(AnItem: TSynEditUndoItem): Boolean; override; function DebugString: String; override; public function IsCaretInfo: Boolean; override; constructor Create(CaretPos, BeginPos, EndPos: TPoint; BlockMode: TSynSelectionMode; AForceSingleLineSelected: Boolean); function PerformUndo(Caller: TObject): Boolean; override; end; { TSynEditUndoIndent } TSynEditUndoIndent = class(TSynEditUndoItem) public FPosY1, FPosY2, FCnt, FTabCnt: Integer; public constructor Create(APosY, EPosY, ACnt, ATabCnt: Integer); function PerformUndo(Caller: TObject): Boolean; override; end; { TSynEditUndoUnIndent } TSynEditUndoUnIndent = class(TSynEditUndoItem) public FPosY1, FPosY2: Integer; FText: String; public constructor Create(APosY, EPosY: Integer; AText: String); function PerformUndo(Caller: TObject): Boolean; override; end; { TSynEditMouseGlobalActions } TSynEditMouseGlobalActions = class(TSynEditMouseInternalActions) protected procedure InitForOptions(AnOptions: TSynEditorMouseOptions); override; end; { TSynEditMouseTextActions } TSynEditMouseTextActions = class(TSynEditMouseInternalActions) protected procedure InitForOptions(AnOptions: TSynEditorMouseOptions); override; end; { TSynEditMouseSelActions } TSynEditMouseSelActions = class(TSynEditMouseInternalActions) protected procedure InitForOptions(AnOptions: TSynEditorMouseOptions); override; end; { THookedCommandHandlerEntry } THookedCommandHandlerEntry = class(TObject) private FEvent: THookedCommandEvent; FData: pointer; FFlags: THookedCommandFlags; function Equals(AEvent: THookedCommandEvent): boolean; reintroduce; public constructor Create(AEvent: THookedCommandEvent; AData: pointer; AFlags: THookedCommandFlags); end; { TSynEditUndoCaret } function TSynEditUndoCaret.IsEqualContent(AnItem: TSynEditUndoItem): Boolean; begin Result := (FCaretPos.x = TSynEditUndoCaret(AnItem).FCaretPos.x) and (FCaretPos.y = TSynEditUndoCaret(AnItem).FCaretPos.y); end; function TSynEditUndoCaret.DebugString: String; begin Result := 'CaretPos='+dbgs(FCaretPos); end; constructor TSynEditUndoCaret.Create(CaretPos: TPoint); begin FCaretPos := CaretPos; {$IFDEF SynUndoDebugItems}debugln(['--- Undo Insert ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF} end; function TSynEditUndoCaret.IsCaretInfo: Boolean; begin Result := True; end; function TSynEditUndoCaret.PerformUndo(Caller: TObject): Boolean; begin Result := Caller is TCustomSynEdit; if Result then {$IFDEF SynUndoDebugItems}debugln(['--- Undo Perform ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF} with TCustomSynEdit(Caller) do begin FCaret.LineCharPos := FCaretPos; FTheLinesView.CurUndoList.AddChange(TSynEditUndoCaret.Create(FCaretPos)); end; end; { TSynEditUndoSelCaret } constructor TSynEditUndoSelCaret.Create(CaretPos, BeginPos, EndPos: TPoint; BlockMode: TSynSelectionMode; AForceSingleLineSelected: Boolean); begin FCaretPos := CaretPos; FBeginPos := BeginPos; FEndPos := EndPos; FBlockMode := BlockMode; FForceSingleLineSelected := AForceSingleLineSelected; {$IFDEF SynUndoDebugItems}debugln(['--- Undo Insert ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF} end; function TSynEditUndoSelCaret.IsEqualContent(AnItem: TSynEditUndoItem): Boolean; begin Result := (FCaretPos.x = TSynEditUndoSelCaret(AnItem).FCaretPos.x) and (FCaretPos.y = TSynEditUndoSelCaret(AnItem).FCaretPos.y) and (FBeginPos.x = TSynEditUndoSelCaret(AnItem).FBeginPos.x) and (FBeginPos.y = TSynEditUndoSelCaret(AnItem).FBeginPos.y) and (FEndPos.x = TSynEditUndoSelCaret(AnItem).FEndPos.x) and (FEndPos.y = TSynEditUndoSelCaret(AnItem).FEndPos.y) and (FBlockMode = TSynEditUndoSelCaret(AnItem).FBlockMode) and (FForceSingleLineSelected = TSynEditUndoSelCaret(AnItem).FForceSingleLineSelected); end; function TSynEditUndoSelCaret.DebugString: String; begin Result := 'CaretPos='+dbgs(FCaretPos) + ' Begin=' + dbgs(FBeginPos) + ' End=' + dbgs(FEndPos) + ' Mode=' + dbgs(ord(FBlockMode)); end; function TSynEditUndoSelCaret.IsCaretInfo: Boolean; begin Result := True; end; function TSynEditUndoSelCaret.PerformUndo(Caller: TObject): Boolean; begin Result := Caller is TCustomSynEdit; if Result then {$IFDEF SynUndoDebugItems}debugln(['--- Undo Perform ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF} with TCustomSynEdit(Caller) do begin SetCaretAndSelection(FCaretPos, FBeginPos, FEndPos, FBlockMode, True, FForceSingleLineSelected); FTheLinesView.CurUndoList.AddChange(TSynEditUndoSelCaret.Create(FCaretPos, FBeginPos, FEndPos, FBlockMode, FForceSingleLineSelected)); end; end; { TSynEditUndoIndent } constructor TSynEditUndoIndent.Create(APosY, EPosY, ACnt, ATabCnt: Integer); begin FPosY1 := APosY; FPosY2 := EPosY; FCnt := ACnt; FTabCnt := ATabCnt; end; function TSynEditUndoIndent.PerformUndo(Caller: TObject): Boolean; begin Result := False; end; { TSynEditUndoUnIndent } constructor TSynEditUndoUnIndent.Create(APosY, EPosY: Integer; AText: String); begin FPosY1 := APosY; FPosY2 := EPosY; FText := AText; end; function TSynEditUndoUnIndent.PerformUndo(Caller: TObject): Boolean; begin Result := False; end; function Roundoff(X: Extended): Longint; begin if (x >= 0) then begin Result := TruncToInt(x + 0.5) end else begin Result := TruncToInt(x - 0.5); end; end; { TSynEditMouseGlobalActions } procedure TSynEditMouseGlobalActions.InitForOptions(AnOptions: TSynEditorMouseOptions); begin // Normal wheel: scroll dependent on visible scroll-bars AddCommand(emcWheelScrollDown, False, mbXWheelDown, ccAny, cdDown, [], []); AddCommand(emcWheelScrollUp, False, mbXWheelUp, ccAny, cdDown, [], []); AddCommand(emcWheelHorizScrollDown, False, mbXWheelLeft, ccAny, cdDown, [], []); AddCommand(emcWheelHorizScrollUp, False, mbXWheelRight, ccAny, cdDown, [], []); if emCtrlWheelZoom in AnOptions then begin AddCommand(emcWheelZoomOut, False, mbXWheelDown, ccAny, cdDown, [ssCtrl], [ssCtrl]); AddCommand(emcWheelZoomIn, False, mbXWheelUp, ccAny, cdDown, [ssCtrl], [ssCtrl]); end; end; { TSynEditMouseTextActions } procedure TSynEditMouseTextActions.InitForOptions(AnOptions: TSynEditorMouseOptions); var rmc: Boolean; begin Clear; rmc := (emRightMouseMovesCursor in AnOptions); //// eoRightMouseMovesCursor //if (eoRightMouseMovesCursor in ChangedOptions) then begin // for i := FMouseActions.Count-1 downto 0 do // if FMouseActions[i].Button = mbXRight then // FMouseActions[i].MoveCaret := (eoRightMouseMovesCursor in fOptions); //end; AddCommand(emcStartSelections, True, mbXLeft, ccSingle, cdDown, [], [ssShift, ssAlt], emcoSelectionStart); AddCommand(emcStartSelections, True, mbXLeft, ccSingle, cdDown, [ssShift], [ssShift, ssAlt], emcoSelectionContinue); if (emAltSetsColumnMode in AnOptions) then begin AddCommand(emcStartColumnSelections, True, mbXLeft, ccSingle, cdDown, [ssAlt], [ssShift, ssAlt], emcoSelectionStart); AddCommand(emcStartColumnSelections, True, mbXLeft, ccSingle, cdDown, [ssShift, ssAlt], [ssShift, ssAlt], emcoSelectionContinue); end; if (emShowCtrlMouseLinks in AnOptions) then AddCommand(emcMouseLink, False, mbXLeft, ccSingle, cdUp, [SYNEDIT_LINK_MODIFIER], [ssShift, ssAlt, ssCtrl] + [SYNEDIT_LINK_MODIFIER]); if (emDoubleClickSelectsLine in AnOptions) then begin AddCommand(emcSelectLine, True, mbXLeft, ccDouble, cdDown, [], []); AddCommand(emcSelectPara, True, mbXLeft, ccTriple, cdDown, [], []); end else begin AddCommand(emcSelectWord, True, mbXLeft, ccDouble, cdDown, [], []); AddCommand(emcSelectLine, True, mbXLeft, ccTriple, cdDown, [], []); end; AddCommand(emcSelectPara, True, mbXLeft, ccQuad, cdDown, [], []); AddCommand(emcContextMenu, rmc, mbXRight, ccSingle, cdUp, [], [], emcoSelectionCaretMoveNever); AddCommand(emcPasteSelection, True, mbXMiddle, ccSingle, cdDown, [], []); end; { TSynEditMouseSelActions } procedure TSynEditMouseSelActions.InitForOptions(AnOptions: TSynEditorMouseOptions); begin Clear; //rmc := (eoRightMouseMovesCursor in AnOptions); if (emDragDropEditing in AnOptions) then AddCommand(emcStartDragMove, False, mbXLeft, ccSingle, cdDown, [], [ssShift]); end; { THookedCommandHandlerEntry } constructor THookedCommandHandlerEntry.Create(AEvent: THookedCommandEvent; AData: pointer; AFlags: THookedCommandFlags); begin inherited Create; fEvent := AEvent; fData := AData; FFlags := AFlags; end; function THookedCommandHandlerEntry.Equals(AEvent: THookedCommandEvent): boolean; begin with TMethod(fEvent) do Result := (Code = TMethod(AEvent).Code) and (Data = TMethod(AEvent).Data); end; function dbgs(aStateFlag: TSynStateFlag): string; overload; begin Result:=''; WriteStr(Result, aStateFlag) end; function dbgs(aStateFlags: TSynStateFlags): string; overload; var i: TSynStateFlag; begin Result := ''; for i := low(TSynStateFlags) to high(TSynStateFlags) do if i in aStateFlags then begin if Result <> '' then Result := Result + ','; Result := Result + dbgs(i); end; if Result <> '' then Result := '[' + Result + ']'; end; procedure InitSynDefaultFont; begin if SynDefaultFontName <> '' then exit; Screen.Fonts; {$UNDEF SynDefaultFont} {$IFDEF LCLgtk} SynDefaultFontName := '-adobe-courier-medium-r-normal-*-*-140-*-*-*-*-iso10646-1'; SynDefaultFontHeight := 14; {$DEFINE SynDefaultFont} {$ENDIF} {$IFDEF LCLcarbon} SynDefaultFontName := 'Monaco'; // Note: carbon is case sensitive SynDefaultFontHeight := 12; {$DEFINE SynDefaultFont} {$ENDIF} {$IFDEF LCLcocoa} SynDefaultFontName := 'Andale Mono'; // Note: cocoa is case sensitive SynDefaultFontHeight := 10; {$DEFINE SynDefaultFont} {$ENDIF} // LCLgtk2 and LCLQt use default settings {$IFnDEF SynDefaultFont} SynDefaultFontName := 'Courier New'; SynDefaultFontHeight := -13; {$ENDIF} if Screen.Fonts.IndexOf(SynDefaultFontName) >= 0 then exit; if Screen.Fonts.IndexOf('DejaVu Sans Mono') >= 0 then begin SynDefaultFontName := 'DejaVu Sans Mono'; SynDefaultFontHeight := 13; end; end; { TSynScrollOnEditOptions } procedure TSynScrollOnEditOptions.SetKeepBorderDistance(AValue: integer); begin if FKeepBorderDistance = AValue then Exit; AValue := MinMax(AValue, 0, 1000); FKeepBorderDistance := AValue; end; procedure TSynScrollOnEditOptions.SetKeepBorderDistancePercent(AValue: integer); begin if FKeepBorderDistancePercent = AValue then Exit; AValue := MinMax(AValue, 0, 100); FKeepBorderDistancePercent := AValue; end; procedure TSynScrollOnEditOptions.SetScrollExtraColumns(AValue: integer); begin if FScrollExtraColumns = AValue then Exit; AValue := MinMax(AValue, 0, 1000); FScrollExtraColumns := AValue; end; procedure TSynScrollOnEditOptions.SetScrollExtraMax(AValue: integer); begin if FScrollExtraMax = AValue then Exit; AValue := MinMax(AValue, 0, 1000); FScrollExtraMax := AValue; end; procedure TSynScrollOnEditOptions.SetScrollExtraPercent(AValue: integer); begin if FScrollExtraPercent = AValue then Exit; AValue := MinMax(AValue, 0, 100); FScrollExtraPercent := AValue; end; procedure TSynScrollOnEditOptions.Assign(Source: TPersistent); begin if not(Source is TSynScrollOnEditOptions) then exit; FKeepBorderDistance := TSynScrollOnEditOptions(Source).FKeepBorderDistance; FKeepBorderDistancePercent := TSynScrollOnEditOptions(Source).FKeepBorderDistancePercent; FScrollExtraColumns := TSynScrollOnEditOptions(Source).FScrollExtraColumns; FScrollExtraMax := TSynScrollOnEditOptions(Source).FScrollExtraMax; FScrollExtraPercent := TSynScrollOnEditOptions(Source).FScrollExtraPercent; if Assigned(FOnChange) then FOnChange(Self); end; constructor TSynScrollOnEditOptions.Create; begin inherited Create; SetDefaults; end; procedure TSynScrollOnEditOptions.SetDefaults; begin // end; { TSynScrollOnEditLeftOptions } procedure TSynScrollOnEditLeftOptions.SetDefaults; begin inherited SetDefaults; FKeepBorderDistance := CKeepBorderDistance; FKeepBorderDistancePercent := CKeepBorderDistancePercent; FScrollExtraColumns := CScrollExtraColumns; FScrollExtraMax := CScrollExtraMax; FScrollExtraPercent := CScrollExtraPercent; end; { TSynScrollOnEditRightOptions } procedure TSynScrollOnEditRightOptions.SetDefaults; begin FKeepBorderDistance := CKeepBorderDistance; FKeepBorderDistancePercent := CKeepBorderDistancePercent; FScrollExtraColumns := CScrollExtraColumns; FScrollExtraMax := CScrollExtraMax; FScrollExtraPercent := CScrollExtraPercent; end; { TCustomSynEdit } procedure TCustomSynEdit.AquirePrimarySelection; var FormatList: Array [0..1] of TClipboardFormat; begin if (not SelAvail) or (PrimarySelection.OnRequest=@PrimarySelectionRequest) then exit; FormatList[0] := CF_TEXT; FormatList[1] := TSynClipboardStream.ClipboardFormatId; try PrimarySelection.SetSupportedFormats(2, @FormatList[0]); PrimarySelection.OnRequest:=@PrimarySelectionRequest; except end; end; function TCustomSynEdit.GetChangeStamp: int64; begin Result := TSynEditStringList(FLines).TextChangeStamp; end; function TCustomSynEdit.GetCharsInWindow: Integer; begin if not Assigned(FTextArea) then Result := -1 else Result := FTextArea.CharsInWindow; end; function TCustomSynEdit.GetCharWidth: integer; begin if not Assigned(FTextArea) then Result := -1 else Result := FTextArea.CharWidth; end; function TCustomSynEdit.GetDefSelectionMode: TSynSelectionMode; begin Result := FBlockSelection.SelectionMode; end; function TCustomSynEdit.GetFoldedCodeLineColor: TSynSelectedColor; begin Result := FFoldedLinesView.MarkupInfoFoldedCodeLine; end; function TCustomSynEdit.GetFoldState: String; begin Result := FFoldedLinesView.GetFoldDescription(0, 0, -1, -1, True); end; function TCustomSynEdit.GetHiddenCodeLineColor: TSynSelectedColor; begin Result := FFoldedLinesView.MarkupInfoHiddenCodeLine; end; function TCustomSynEdit.GetLeftChar: Integer; begin if not Assigned(FTextArea) then Result := -1 else Result := FTextArea.LeftChar; end; function TCustomSynEdit.GetLineHeight: integer; begin if not Assigned(FTextArea) then Result := -1 else Result := FTextArea.LineHeight; end; function TCustomSynEdit.GetLinesInWindow: Integer; begin if not Assigned(FTextArea) then Result := -1 else Result := FTextArea.LinesInWindow; end; function TCustomSynEdit.GetModified: Boolean; begin Result := TSynEditStringList(FLines).Modified; end; function TCustomSynEdit.GetMouseActions: TSynEditMouseActions; begin Result := FMouseActions.UserActions; end; function TCustomSynEdit.GetMouseSelActions: TSynEditMouseActions; begin Result := FMouseSelActions.UserActions; end; function TCustomSynEdit.GetMouseTextActions: TSynEditMouseActions; begin Result := FMouseTextActions.UserActions; end; function TCustomSynEdit.GetPaintLockOwner: TSynEditBase; begin Result := TSynEditStringList(FLines).PaintLockOwner; end; function TCustomSynEdit.GetPlugin(Index: Integer): TLazSynEditPlugin; begin Result := TLazSynEditPlugin(fPlugins[Index]); end; function TCustomSynEdit.GetRightEdge: Integer; begin if not Assigned(FTextArea) then Result := -1 else Result := FTextArea.RightEdgeColumn; end; function TCustomSynEdit.GetRightEdgeColor: TColor; begin if not Assigned(FTextArea) then Result := clNone else Result := FTextArea.RightEdgeColor; end; function TCustomSynEdit.GetTextBetweenPoints(aStartPoint, aEndPoint: TPoint): String; begin FInternalBlockSelection.SelectionMode := smNormal; FInternalBlockSelection.StartLineBytePos := aStartPoint; FInternalBlockSelection.EndLineBytePos := aEndPoint; Result := FInternalBlockSelection.SelText; end; function TCustomSynEdit.GetTopLine: Integer; begin if not Assigned(FTextArea) then Result := -1 else Result := FTheLinesView.ViewToTextIndex(ToIdx(FTextArea.TopLine)) + 1; end; procedure TCustomSynEdit.SetBlockTabIndent(AValue: integer); begin if FBlockTabIndent = AValue then Exit; FBlockTabIndent := AValue; end; procedure TCustomSynEdit.SetBracketMatchColor(AValue: TSynSelectedColor); begin fMarkupBracket.MarkupInfo.Assign(AValue); end; procedure TCustomSynEdit.SetTextCursor(AValue: TCursor); begin if FTextCursor = AValue then exit; FTextCursor := AValue; UpdateCursor; end; procedure TCustomSynEdit.SetOffTextCursor(AValue: TCursor); begin if FOffTextCursor = AValue then Exit; FOffTextCursor := AValue; UpdateCursor; end; procedure TCustomSynEdit.SetDefSelectionMode(const AValue: TSynSelectionMode); begin FBlockSelection.SelectionMode := AValue; // Includes active end; procedure TCustomSynEdit.SetFoldedCodeColor(AValue: TSynSelectedColor); begin FFoldedLinesView.MarkupInfoFoldedCode.Assign(AValue); end; procedure TCustomSynEdit.SetFoldedCodeLineColor(AValue: TSynSelectedColor); begin FFoldedLinesView.MarkupInfoFoldedCodeLine.Assign(AValue); end; procedure TCustomSynEdit.SurrenderPrimarySelection; begin if PrimarySelection.OnRequest=@PrimarySelectionRequest then PrimarySelection.OnRequest:=nil; end; function TCustomSynEdit.PixelsToRowColumn(Pixels: TPoint; aFlags: TSynCoordinateMappingFlags = [scmLimitToLines]): TPhysPoint; // converts the client area coordinate // to Caret position (physical position, (1,1) based) // To get the text/logical position use PixelsToLogicalPos begin Result := YToPos(FTextArea.PixelsToRowColumn(Pixels, aFlags)); Result := ScreenXYToTextXY(Result, scmLimitToLines in aFlags); end; function TCustomSynEdit.PixelsToLogicalPos(const Pixels: TPoint): TLogPoint; begin Result:=PhysicalToLogicalPos(PixelsToRowColumn(Pixels)); end; function TCustomSynEdit.ScreenRowToRow(ScreenRow: integer; LimitToLines: Boolean = True): integer; // ScreenRow is 0-base // result is 1-based begin Result := ToPos(FTheLinesView.ViewToTextIndex(ToIdx(TopView + ScreenRow))); if LimitToLines and (Result >= Lines.Count) then Result := Lines.Count; // DebugLn(['=== SrceenRow TO Row In:',ScreenRow,' out:',Result, ' topline=',TopLine, ' view topline=',FFoldedLinesView.TopLine]); end; function TCustomSynEdit.RowToScreenRow(PhysicalRow: integer): integer; // returns -1 for lines above visible screen (<TopLine) // 0 for the first line // 0 to LinesInWindow for visible lines (incl last partial visible line) // and returns LinesInWindow+1 for lines below visible screen begin Result := ToPos(FTheLinesView.TextToViewIndex(ToIdx(PhysicalRow))) - TopView; if Result < -1 then Result := -1; if Result > LinesInWindow+1 then Result := LinesInWindow+1; // DebugLn(['=== Row TO ScreenRow In:',PhysicalRow,' out:',Result]); end; function TCustomSynEdit.ScreenXYToTextXY(AScreenXY: TPhysScreenPoint; LimitToLines: Boolean): TPhysPoint; begin AScreenXY.y := AScreenXY.y + ToIdx(TopView); Result := FTheLinesView.ViewXYToTextXY(AScreenXY); if LimitToLines and (Result.y > Lines.Count) then Result.y := Lines.Count; end; function TCustomSynEdit.TextXYToScreenXY(APhysTextXY: TPhysPoint): TPhysScreenPoint; begin Result := FTheLinesView.TextXYToViewXY(APhysTextXY); Result.y := Result.y - ToIdx(TopView); end; function TCustomSynEdit.ScreenXYToPixels(RowCol: TScreenPoint): TPoint; // converts screen position (1,1) based // to client area coordinate (0,0 based on canvas) begin Result := FTextArea.RowColumnToPixels(YToIdx(RowCol)); end; function TCustomSynEdit.RowColumnToPixels(RowCol: TScreenPoint): TPoint; begin Result := ScreenXYToPixels(TextXYToScreenXY(RowCol)); end; procedure TCustomSynEdit.ComputeCaret(X, Y: Integer); // set caret to pixel position begin FCaret.LineCharPos := PixelsToRowColumn(Point(X,Y)); end; procedure TCustomSynEdit.DoCopyToClipboard(SText: string; FoldInfo: String = ''); var ClipHelper: TSynClipboardStream; PasteAction: TSynCopyPasteAction; PMode: TSynSelectionMode; begin PasteAction := scaContinue; if length(FoldInfo) = 0 then PasteAction := scaPlainText; PMode := SelectionMode; if assigned(FOnCutCopy) then begin FOnCutCopy(self, SText, PMode, FBlockSelection.FirstLineBytePos, PasteAction); if PasteAction = scaAbort then exit;; end; if SText = '' then exit; Clipboard.Clear; ClipHelper := TSynClipboardStream.Create; try ClipHelper.Text := SText; ClipHelper.SelectionMode := PMode; // TODO if scaPlainText and smNormal, then avoid synedits own clipboard format if PasteAction = scaContinue then begin // Fold if length(FoldInfo) > 0 then ClipHelper.AddTag(synClipTagFold, @FoldInfo[1], length(FoldInfo)); end; if not ClipHelper.WriteToClipboard(Clipboard) then begin {$IFDEF SynClipboardExceptions}raise ESynEditError.Create('Clipboard copy operation failed');{$ENDIF} end; finally ClipHelper.Free; end; end; procedure TCustomSynEdit.CopyToClipboard; var FInfo: String; begin if SelAvail then begin FInfo := ''; if eoFoldedCopyPaste in fOptions2 then FInfo := FFoldedLinesView.GetFoldDescription( FBlockSelection.FirstLineBytePos.Y - 1, FBlockSelection.FirstLineBytePos.X, FBlockSelection.LastLineBytePos.Y - 1, FBlockSelection.LastLineBytePos.X); DoCopyToClipboard(SelText, FInfo); end; end; procedure TCustomSynEdit.CutToClipboard; var FInfo: String; begin if SelAvail then begin FInfo := ''; if eoFoldedCopyPaste in fOptions2 then FInfo := FFoldedLinesView.GetFoldDescription( FBlockSelection.FirstLineBytePos.Y - 1, FBlockSelection.FirstLineBytePos.X, FBlockSelection.LastLineBytePos.Y - 1, FBlockSelection.LastLineBytePos.X); DoCopyToClipboard(SelText, FInfo); SetSelTextExternal(''); end; end; procedure TCustomSynEdit.DoTopViewChanged(Sender: TObject); begin FTheLinesView := TSynEditStringsLinked(Sender); if FPaintArea = nil then exit; // In SynEdit.Create FPaintArea.DisplayView := FTheLinesView.DisplayView; FCaret.Lines := FTheLinesView; FInternalCaret.Lines := FTheLinesView; FBlockSelection.Lines := FTheLinesView; FInternalBlockSelection.Lines := FTheLinesView; FMarkupManager.Lines := FTheLinesView; FTextArea.TheLinesView := FTheLinesView; if FMarkList <> nil then // skip, if in ChangeTextBuffer / senrTextBufferChanged hasn't been sent yet SizeOrFontChanged(True); end; function TCustomSynEdit.GetIsStickySelecting: Boolean; begin Result := FBlockSelection.StickyAutoExtend; end; procedure TCustomSynEdit.SetScrollOnEditLeftOptions( AValue: TSynScrollOnEditOptions); begin FScrollOnEditLeftOptions.Assign(AValue); RecalcScrollOnEdit(nil); end; procedure TCustomSynEdit.SetScrollOnEditRightOptions( AValue: TSynScrollOnEditOptions); begin FScrollOnEditRightOptions.Assign(AValue); RecalcScrollOnEdit(nil); end; procedure TCustomSynEdit.SetTabViewClass(AValue: TSynEditStringTabExpanderClass ); var i: Integer; begin i := FTextViewsManager.IndexOf(FTabbedLinesView); FTextViewsManager.RemoveSynTextView(FTabbedLinesView); FTabbedLinesView.Free; FTabbedLinesView := AValue.Create; FTextViewsManager.AddTextView(FTabbedLinesView, i); FTabbedLinesView.TabWidth := fTabWidth; end; constructor TCustomSynEdit.Create(AOwner: TComponent); begin inherited Create(AOwner); SetInline(True); ControlStyle:=ControlStyle+[csOwnedChildrenNotSelectable]; FDoingResizeLock := 0; FPaintLock := 0; FStatusChangeLock := 0; FUndoBlockAtPaintLock := 0; FRecalcCharsAndLinesLock := 0; FUseUTF8 {%H-}:= True; FStatusChangedList := TSynStatusChangedHandlerList.Create; FPaintEventHandlerList := TSynPaintEventHandlerList.Create; FScrollEventHandlerList := TSynScrollEventHandlerList.Create; FDefaultBeautifier := TSynBeautifier.Create(self); FBeautifier := FDefaultBeautifier; FLines := TSynEditStringList.Create; FLines.IsUtf8 := True; TSynEditStringList(FLines).AttachSynEdit(Self); FCaret := TSynEditCaret.Create; FCaret.MaxLeftChar := @CurrentMaxLineLen; FCaret.AddChangeHandler(@CaretChanged); FInternalCaret := TSynEditCaret.Create; FInternalCaret.MaxLeftChar := @CurrentMaxLineLen; {$ifdef LCLMui} FScreenCaretPainterClass{%H-} := TSynEditScreenCaretPainterInternal; {$else} FScreenCaretPainterClass{%H-} := TSynEditScreenCaretPainterSystem; {$endif} FTextViewsManager := TSynTextViewsManagerInternal.Create(FLines, @DoTopViewChanged); // Create the lines/views FTrimmedLinesView := TSynEditStringTrimmingList.Create(fCaret); FTextViewsManager.AddTextView(FTrimmedLinesView); FDoubleWidthChrLinesView := SynEditStringDoubleWidthChars.Create(); FTextViewsManager.AddTextView(FDoubleWidthChrLinesView); {$IFDEF WithSynExperimentalCharWidth} //FSysCharWidthLinesView := TSynEditStringSystemWidthChars.Create(FDoubleWidthChrLinesView, Self.Canvas); FSysCharWidthLinesView := TSynEditStringSystemWidthChars.Create(Self.Canvas); FTextViewsManager.AddTextView(FSysCharWidthLinesView); FBidiChrLinesView := TSynEditStringBidiChars.Create(); FTextViewsManager.AddTextView(FBidiChrLinesView); FTabbedLinesView := TSynEditStringTabExpander.Create(); FTextViewsManager.AddTextView(FTabbedLinesView); {$ELSE} {$IFnDEF WithOutSynBiDi} FBidiChrLinesView := TSynEditStringBidiChars.Create(); FTextViewsManager.AddTextView(FBidiChrLinesView); {$ENDIF} // ftab, currently has LengthOfLongestLine, therefore must be after DoubleWidthChar FTabbedLinesView := TSynEditStringTabExpander.Create(); FTextViewsManager.AddTextView(FTabbedLinesView); {$ENDIF} // WithSynExperimentalCharWidth FFoldedLinesView := TSynEditFoldedView.Create(Self, fCaret); FFoldedLinesView.AddChangeHandler(senrLineMappingChanged, @FoldChanged); // External Accessor FStrings := TSynEditLines.Create(TSynEditStringList(FLines), @MarkTextAsSaved); FCaret.Lines := FTheLinesView; FInternalCaret.Lines := FTheLinesView; FFontDummy := TFont.Create; with FTheLinesView do begin AddChangeHandler(senrLineCount, @LineCountChanged); AddChangeHandler(senrLineChange, @LineTextChanged); AddChangeHandler(senrHighlightChanged, @DoHighlightChanged); AddNotifyHandler(senrCleared, @ListCleared); AddNotifyHandler(senrUndoRedoAdded, @Self.UndoRedoAdded); AddNotifyHandler(senrModifiedChanged, @ModifiedChanged); AddNotifyHandler(senrIncPaintLock, @DoIncPaintLock); AddNotifyHandler(senrDecPaintLock, @DoDecPaintLock); AddNotifyHandler(senrIncOwnedPaintLock, @DoIncForeignPaintLock); AddNotifyHandler(senrDecOwnedPaintLock, @DoDecForeignPaintLock); end; FScreenCaret := TSynEditScreenCaret.Create(Self); FScreenCaret.OnExtraLineCharsChanged := @ExtraLineCharsChanged; FUndoList := TSynEditStringList(fLines).UndoList; FRedoList := TSynEditStringList(fLines).RedoList; FUndoList.OnNeedCaretUndo := @GetCaretUndo; {$IFDEF SynUndoDebugCalls} fUndoList.DebugName := 'UNDO'; fRedoList.DebugName := 'REDO'; {$ENDIF} FBlockSelection := TSynEditSelection.Create(FTheLinesView, True); FBlockSelection.Caret := FCaret; FBlockSelection.InvalidateLinesMethod := @InvalidateLines; FBlockSelection.AddChangeHandler(@DoBlockSelectionChanged); FInternalBlockSelection := TSynEditSelection.Create(FTheLinesView, False); FInternalBlockSelection.InvalidateLinesMethod := @InvalidateLines; // No need for caret, on interanl block FWordBreaker := TSynWordBreaker.Create; FScrollOnEditLeftOptions := TSynScrollOnEditLeftOptions.Create; FScrollOnEditLeftOptions.OnChange := @RecalcScrollOnEdit; FScrollOnEditRightOptions := TSynScrollOnEditRightOptions.Create; FScrollOnEditRightOptions.OnChange := @RecalcScrollOnEdit; RecreateMarkList; fFontDummy.Style := [fsBold]; fTextDrawer := TLazEditTextGridPainter.Create(Canvas, fFontDummy); {$IFDEF WithSynExperimentalCharWidth} FSysCharWidthLinesView.TextDrawer := fTextDrawer; {$ENDIF} // WithSynExperimentalCharWidth FPaintLineColor := TSynSelectedColor.Create; FPaintLineColor2 := TSynSelectedColor.Create; FLeftGutter := CreateGutter(self, gsLeft, FTextDrawer); FLeftGutter.RegisterChangeHandler(@GutterChanged); FLeftGutter.RegisterResizeHandler(@GutterResized); FLeftGutter.DoAutoSize; FRightGutter := CreateGutter(self, gsRight, FTextDrawer); FRightGutter.RegisterChangeHandler(@GutterChanged); FRightGutter.RegisterResizeHandler(@GutterResized); FRightGutter.DoAutoSize; ControlStyle := ControlStyle + [csOpaque, csSetCaption, csTripleClicks, csQuadClicks]; Height := 150; Width := 200; FTextCursor := crIBeam; FOffTextCursor := crDefault; FOverrideCursor := crDefault; inherited Cursor := FTextCursor; fPlugins := TList.Create; FHookedKeyTranslationList := TSynHookedKeyTranslationList.Create; FUndoRedoItemHandlerList := TSynUndoRedoItemHandlerList.Create; // needed before setting color fMarkupHighCaret := TSynEditMarkupHighlightAllCaret.Create(self); fMarkupHighCaret.Selection := FBlockSelection; fMarkupHighAll := TSynEditMarkupHighlightAll.Create(self); fMarkupBracket := TSynEditMarkupBracket.Create(self); fMarkupWordGroup := TSynEditMarkupWordGroup.Create(self); fMarkupCtrlMouse := TSynEditMarkupCtrlMouseLink.Create(self); fMarkupSpecialLine := TSynEditMarkupSpecialLine.Create(self); fMarkupSelection := TSynEditMarkupSelection.Create(self, FBlockSelection); fMarkupSpecialChar := TSynEditMarkupSpecialChar.Create(self); fMarkupSelection.MarkupInfoSeletion.SetAllPriorities(50); fMarkupManager := TSynEditMarkupManager.Create(self); fMarkupManager.AddMarkUp(fMarkupSpecialChar); fMarkupManager.AddMarkUp(fMarkupSpecialLine); fMarkupManager.AddMarkUp(fMarkupHighCaret); fMarkupManager.AddMarkUp(fMarkupHighAll); fMarkupManager.AddMarkUp(fMarkupCtrlMouse); fMarkupManager.AddMarkUp(fMarkupBracket); fMarkupManager.AddMarkUp(fMarkupWordGroup); fMarkupManager.AddMarkUp(fMarkupSelection); fMarkupManager.Lines := FTheLinesView; fMarkupManager.Caret := FCaret; fMarkupManager.InvalidateLinesMethod := @InvalidateLines; {$IFDEF WinIME} {$IFDEF WinIMEFull} FImeHandler := LazSynImeFull.Create(Self); {$ELSE} FImeHandler := LazSynImeSimple.Create(Self); LazSynImeSimple(FImeHandler).TextDrawer := FTextDrawer; {$ENDIF} FImeHandler.InvalidateLinesMethod := @InvalidateLines; {$ENDIF} {$IFDEF CocoaIME} FImeHandler := LazSynImeCocoa.Create(Self); FImeHandler.InvalidateLinesMethod := @InvalidateLines; {$ENDIF} {$IFDEF Gtk2IME} FImeHandler := LazSynImeGtk2 .Create(Self); FImeHandler.InvalidateLinesMethod := @InvalidateLines; {$ENDIF} fFontDummy.Name := SynDefaultFontName; fFontDummy.Height := SynDefaultFontHeight; fFontDummy.Pitch := SynDefaultFontPitch; fFontDummy.Quality := SynDefaultFontQuality; FLastSetFontSize := fFontDummy.Height; FLastMouseLocation.LastMouseCaret := Point(-1,-1); FLastMouseLocation.LastMousePoint := Point(-1,-1); fBlockIndent := 2; FTextArea := TLazSynTextArea.Create(Self, FTextDrawer); FTextArea.RightEdgeVisible := not(eoHideRightMargin in SYNEDIT_DEFAULT_OPTIONS); FTextArea.ExtraCharSpacing := 0; FTextArea.ExtraLineSpacing := 0; FTextArea.MarkupManager := fMarkupManager; FTextArea.TheLinesView := FTheLinesView; FTextArea.Highlighter := nil; FTextArea.OnStatusChange := @StatusChangedEx; FLeftGutterArea := TLazSynGutterArea.Create(Self); FLeftGutterArea.TextArea := FTextArea; FLeftGutterArea.Gutter := FLeftGutter; FRightGutterArea := TLazSynGutterArea.Create(Self); FRightGutterArea.TextArea := FTextArea; FRightGutterArea.Gutter := FRightGutter; FPaintArea := TLazSynSurfaceManager.Create(Self); FPaintArea.TextArea := FTextArea; FPaintArea.LeftGutterArea := FLeftGutterArea; FPaintArea.RightGutterArea := FRightGutterArea; FPaintArea.DisplayView := FTheLinesView.DisplayView; Color := clWhite; Font.Assign(fFontDummy); Font.OnChange := @FontChanged; FontChanged(nil); ParentFont := False; ParentColor := False; TabStop := True; fInserting := True; fMaxLeftChar := 1024; ScrollBars := ssBoth; BorderStyle := bsSingle; fInsertCaret := ctVerticalLine; fOverwriteCaret := ctBlock; FKeystrokes := TSynEditKeyStrokes.Create(Self); FCurrentComboKeyStrokes := nil; if assigned(Owner) and not (csLoading in Owner.ComponentState) then begin SetDefaultKeystrokes; end; FMouseActions := TSynEditMouseGlobalActions.Create(Self); FMouseSelActions := TSynEditMouseSelActions.Create(Self); FMouseTextActions := TSynEditMouseTextActions.Create(Self); FMouseActionSearchHandlerList := TSynEditMouseActionSearchList.Create; FMouseActionExecHandlerList := TSynEditMouseActionExecList.Create; fWantTabs := True; fTabWidth := 8; FOldTopView := 1; FFoldedLinesView.TopViewPos := 1; // find / replace fTSearch := TSynEditSearch.Create; FOptions := SYNEDIT_DEFAULT_OPTIONS; FOptions2 := SYNEDIT_DEFAULT_OPTIONS2; FShareOptions := SYNEDIT_DEFAULT_SHARE_OPTIONS; FVisibleSpecialChars := SYNEDIT_DEFAULT_VISIBLESPECIALCHARS; fMarkupSpecialChar.VisibleSpecialChars := SYNEDIT_DEFAULT_VISIBLESPECIALCHARS; UpdateOptions; UpdateOptions2; UpdateMouseOptions; UpdateCaret; fScrollTimer := TTimer.Create(Self); fScrollTimer.Enabled := False; fScrollTimer.Interval := 100; fScrollTimer.OnTimer := @ScrollTimerHandler; // Accessibility AccessibleRole := larTextEditorMultiline; AccessibleValue := Self.Text; AccessibleDescription := 'source code editor'; end; function TCustomSynEdit.GetChildOwner: TComponent; begin result := self; end; procedure TCustomSynEdit.GetChildren(Proc: TGetChildProc; Root: TComponent); begin if root = self then begin Proc(FLeftGutter.Parts); // only save right gutter, if it has gutter-parts // move to parts-class if FRightGutter.Parts.Count > 0 then Proc(FRightGutter.Parts); end; end; procedure TCustomSynEdit.CreateParams(var Params: TCreateParams); (* const ScrollBar: array[TScrollStyle] of DWORD = (0, WS_HSCROLL, WS_VSCROLL, WS_HSCROLL or WS_VSCROLL, WS_HSCROLL, WS_VSCROLL, WS_HSCROLL or WS_VSCROLL); BorderStyles: array[TBorderStyle] of DWORD = (0, WS_BORDER); ClassStylesOff = CS_VREDRAW or CS_HREDRAW; *) begin inherited CreateParams(Params); (* with Params do begin {$IFOPT R+}{$DEFINE RangeCheckOn}{$R-}{$ENDIF} WindowClass.Style := WindowClass.Style and not Cardinal(ClassStylesOff); Style := Style or ScrollBar[FScrollBars] or BorderStyles[BorderStyle] or WS_CLIPCHILDREN; {$IFDEF RangeCheckOn}{$R+}{$ENDIF} if NewStyleControls and (BorderStyle = bsSingle) then begin Style := Style and not Cardinal(WS_BORDER); ExStyle := ExStyle or WS_EX_CLIENTEDGE; end; end; *) end; procedure TCustomSynEdit.IncPaintLock; begin if FIsInDecPaintLock then exit; if (PaintLockOwner = nil) then begin PaintLockOwner := Self; FLines.SendNotification(senrIncOwnedPaintLock, Self); // DoIncForeignPaintLock end; inc(FPaintLockOwnerCnt); if FPaintLockOwnerCnt = 1 then FLines.BeginUpdate(Self); end; procedure TCustomSynEdit.DecPaintLock; begin if FIsInDecPaintLock then exit; if FPaintLockOwnerCnt = 1 then FLines.EndUpdate(Self); dec(FPaintLockOwnerCnt); if (PaintLockOwner = Self) and (FPaintLockOwnerCnt = 0) then begin FLines.SendNotification(senrDecOwnedPaintLock, Self); // DoDecForeignPaintLock PaintLockOwner := nil; end; end; procedure TCustomSynEdit.DoIncForeignPaintLock(Sender: TObject); begin if Sender = Self then exit; FCaret.IncAutoMoveOnEdit; FBlockSelection.IncPersistentLock; end; procedure TCustomSynEdit.DoDecForeignPaintLock(Sender: TObject); begin if Sender = Self then exit; FBlockSelection.DecPersistentLock; FCaret.DecAutoMoveOnEdit; end; procedure TCustomSynEdit.SetUpdateState(NewUpdating: Boolean; Sender: TObject); begin if assigned(FOnChangeUpdating) then FOnChangeUpdating(Self, NewUpdating); end; procedure TCustomSynEdit.IncStatusChangeLock; begin inc(FStatusChangeLock); end; procedure TCustomSynEdit.DecStatusChangeLock; begin dec(FStatusChangeLock); if FStatusChangeLock = 0 then StatusChanged([]); end; procedure TCustomSynEdit.DoIncPaintLock(Sender: TObject); begin if FIsInDecPaintLock then exit; if FPaintLock = 0 then begin SetUpdateState(True, Self); FInvalidateRect := Rect(-1, -1, -2, -2); FOldTopView := TopView; FLastTextChangeStamp := TSynEditStringList(FLines).TextChangeStamp; FMarkupManager.IncPaintLock; end; inc(FPaintLock); FFoldedLinesView.Lock; //DecPaintLock triggers ScanRanges, and folds must wait FTrimmedLinesView.Lock; // Lock before caret FBlockSelection.Lock; FCaret.Lock; FScreenCaret.Lock; end; procedure TCustomSynEdit.DoDecPaintLock(Sender: TObject); begin if FIsInDecPaintLock then exit; FIsInDecPaintLock := True; try if (FUndoBlockAtPaintLock >= FPaintLock) then begin if (FUndoBlockAtPaintLock > FPaintLock) then debugln(['***** SYNEDIT: Fixing auto-undo-block FUndoBlockAtPaintLock=',FUndoBlockAtPaintLock,' FPaintLock=',FPaintLock]); FUndoBlockAtPaintLock := 0; EndUndoBlock{$IFDEF SynUndoDebugBeginEnd}('TCustomSynEdit.DoDecPaintLock'){$ENDIF}; end; FCaret.Unlock; // Maybe after FFoldedLinesView FBlockSelection.Unlock; // FTrimmedLinesView sets the highlighter to modified. Until fixed, must be done before ScanRanges FTrimmedLinesView.UnLock; // Must be unlocked after caret // May Change lines if (FPaintLock=1) and (not WaitingForInitialSize) then begin ScanRanges(FLastTextChangeStamp <> TSynEditStringList(FLines).TextChangeStamp); if sfAfterLoadFromFileNeeded in fStateFlags then AfterLoadFromFile; if FChangedLinesStart > 0 then begin InvalidateLines(FChangedLinesStart, FChangedLinesEnd); InvalidateGutterLines(FChangedLinesStart, FChangedLinesEnd); end; FChangedLinesStart:=0; FChangedLinesEnd:=0; FChangedLinesDiff:=0; end; // When fixed FCaret, FBlockSelection, FTrimmedLinesView can move here FFoldedLinesView.UnLock; // after ScanRanges, but before UpdateCaret (* FFoldedLinesView.UnLock Any unfold (caused by caret move) will be done (deferred) in UnLock. UpdateCaret may call MoveCaretToVisibleArea (which depends on what is folded) Therefore UnLock must be called before UpdateCaret Caret.Unlock must be done before UpdateCaret, because it sends the events to FFoldedLinesView which triggers any unfold. *) Dec(FPaintLock); if (FPaintLock = 0) and (not WaitingForInitialSize) then begin ScrollAfterTopLineChanged; if sfScrollbarChanged in fStateFlags then UpdateScrollbars; // must be past UpdateScrollbars; but before UpdateCaret (for ScrollBar-Auto-show) if sfEnsureCursorPos in fStateFlags then EnsureCursorPosVisible; // TODO: This may call SetTopLine, change order // This does Paintlock, should be before final decrease // Must be after EnsureCursorPosVisible (as it does MoveCaretToVisibleArea) if FCaret.LinePos > Max(FLines.Count, 1) then FCaret.LinePos := Max(FLines.Count, 1); if sfCaretChanged in fStateFlags then UpdateCaret; //if sfScrollbarChanged in fStateFlags then // UpdateScrollbars; fMarkupHighCaret.CheckState; // Todo: need a global lock, including the markup // Todo: Markup can do invalidation, should be before ScrollAfterTopLineChanged; end; if (FPaintLock = 0) then begin FMarkupManager.DecPaintLock; FBlockSelection.AutoExtend := False; if fStatusChanges <> [] then DoOnStatusChange(fStatusChanges); end; finally FScreenCaret.UnLock; FIsInDecPaintLock := False; if FPaintLock = 0 then begin SetUpdateState(False, Self); if FInvalidateRect.Bottom >= FInvalidateRect.Top then begin InvalidateRect(Handle, @FInvalidateRect, False); {$IFDEF SynCheckPaintLock} debugln('Returning from Paintlock, wich had Paint called while active'); DumpStack; {$ENDIF} end; if sfSelChanged in FStateFlags then SelAvailChange(nil); end; end; end; procedure TCustomSynEdit.BeforeDestruction; begin inherited BeforeDestruction; {$IFDEF SynCheckPaintLock} if (FPaintLockOwnerCnt > 0) then begin debugln(['TCustomSynEdit.Destroy: Paintlock=', FPaintLock, ' FInvalidateRect=', dbgs(FInvalidateRect)]); DumpStack; end; {$ENDIF} // block all events during destruction inc(FPaintLock); FMarkupManager.IncPaintLock; FFoldedLinesView.Lock; //DecPaintLock triggers ScanRanges, and folds must wait FTrimmedLinesView.Lock; // Lock before caret FBlockSelection.Lock; FCaret.Lock; end; destructor TCustomSynEdit.Destroy; var i: integer; p: TList; begin Destroying; inc(FPaintLock); // block all events during destruction Application.RemoveOnIdleHandler(@IdleScanRanges); SurrenderPrimarySelection; Highlighter := nil; Beautifier:=nil; if fPlugins <> nil then begin p := FPlugins; FPlugins := nil; for i := p.Count - 1 downto 0 do TLazSynEditPlugin(p[i]).DoEditorDestroyed(Self); p.Free; end; // free listeners while other fields are still valid if Assigned(fHookedCommandHandlers) then begin for i := 0 to fHookedCommandHandlers.Count - 1 do THookedCommandHandlerEntry(fHookedCommandHandlers[i]).Free; FreeAndNil(fHookedCommandHandlers); end; FLeftGutter.UnRegisterChangeHandler(@GutterChanged); FLeftGutter.UnRegisterResizeHandler(@GutterResized); FRightGutter.UnRegisterChangeHandler(@GutterChanged); FRightGutter.UnRegisterResizeHandler(@GutterResized); FreeAndNil(FHookedKeyTranslationList); FreeAndNil(FUndoRedoItemHandlerList); fHookedCommandHandlers:=nil; fPlugins:=nil; FCaret.Lines := nil; FInternalCaret.Lines := nil; FMarkList.UnRegisterChangeHandler(@MarkListChange); FreeAndNil(FPaintArea); FreeAndNil(FLeftGutterArea); FreeAndNil(FRightGutterArea); FreeAndNil(FTextArea); FreeAndNil(fTSearch); FreeAndNil(FImeHandler); FreeAndNil(fMarkupManager); FreeAndNil(fKeyStrokes); FreeAndNil(FMouseActionSearchHandlerList); FreeAndNil(FMouseActionExecHandlerList); FreeAndNil(FMouseActions); FreeAndNil(FMouseSelActions); FreeAndNil(FMouseTextActions); FreeAndNil(FLeftGutter); FreeAndNil(FRightGutter); FreeAndNil(FPaintLineColor); FreeAndNil(FPaintLineColor2); FreeAndNil(fTextDrawer); FreeAndNil(fFontDummy); DestroyMarkList; // before detach from FLines FreeAndNil(FWordBreaker); FreeAndNil(FInternalBlockSelection); FreeAndNil(FBlockSelection); FreeAndNil(FStrings); FreeAndNil(FTextViewsManager); FFoldedLinesView := nil; // destroyed by FTextViewsManager TSynEditStringList(FLines).DetachSynEdit(Self); if TSynEditStringList(FLines).AttachedSynEditCount = 0 then FreeAndNil(fLines); FreeAndNil(fCaret); FreeAndNil(fInternalCaret); FreeAndNil(FScreenCaret); FreeAndNil(FStatusChangedList); FreeAndNil(FPaintEventHandlerList); FreeAndNil(FScrollEventHandlerList); FBeautifier := nil; FreeAndNil(FDefaultBeautifier); FreeAndNil(FKeyDownEventList); FreeAndNil(FKeyUpEventList); FreeAndNil(FMouseDownEventList); FreeAndNil(FKeyPressEventList); FreeAndNil(FQueryMouseCursorList); FreeAndNil(FUtf8KeyPressEventList); FreeAndNil(FScrollOnEditLeftOptions); FreeAndNil(FScrollOnEditRightOptions); inherited Destroy; end; function TCustomSynEdit.GetBlockBegin: TPoint; begin Result := FBlockSelection.FirstLineBytePos; end; function TCustomSynEdit.GetBlockEnd: TPoint; begin Result := FBlockSelection.LastLineBytePos; end; function TCustomSynEdit.GetBracketHighlightStyle: TSynEditBracketHighlightStyle; begin Result := fMarkupBracket.HighlightStyle; end; function TCustomSynEdit.CaretXPix: Integer; var p: TPoint; begin p := FCaret.ViewedLineCharPos; p.y := p.y - TopView + 1; Result := ScreenXYToPixels(p).X; end; function TCustomSynEdit.CaretYPix: Integer; var p: TPoint; begin p := FCaret.ViewedLineCharPos; p.y := p.y - TopView + 1; Result := ScreenXYToPixels(p).Y; end; procedure TCustomSynEdit.FontChanged(Sender: TObject); begin // TODO: inherited ? FPaintArea.ForegroundColor := Font.Color; FLastSetFontSize := Font.Height; RecalcCharExtent; end; function TCustomSynEdit.GetTextBuffer: TSynEditStrings; begin Result := FLines; end; function TCustomSynEdit.GetTextViewsManager: TSynTextViewsManager; begin Result := FTextViewsManager; end; function TCustomSynEdit.GetLineText: string; begin Result := FCaret.LineText; end; function TCustomSynEdit.GetMarkupByClass(Index: TSynEditMarkupClass): TSynEditMarkup; begin Result := fMarkupManager.MarkupByClass[Index]; end; function TCustomSynEdit.GetHighlightAllColor : TSynSelectedColor; begin result := fMarkupHighAll.MarkupInfo; end; function TCustomSynEdit.GetHighlighterObj: TObject; begin Result := fHighlighter; end; function TCustomSynEdit.GetIncrementColor : TSynSelectedColor; begin result := fMarkupSelection.MarkupInfoIncr; end; function TCustomSynEdit.GetLineHighlightColor: TSynSelectedColor; begin Result := fMarkupSpecialLine.MarkupLineHighlightInfo; end; function TCustomSynEdit.GetOnGutterClick : TGutterClickEvent; begin Result := FLeftGutter.OnGutterClick; end; function TCustomSynEdit.GetSelectedColor : TSynSelectedColor; begin result := fMarkupSelection.MarkupInfoSeletion; end; procedure TCustomSynEdit.SetSelectedColor(const AValue : TSynSelectedColor); begin fMarkupSelection.MarkupInfoSeletion.Assign(AValue); end; procedure TCustomSynEdit.SetSpecialLineColors(const AValue : TSpecialLineColorsEvent); begin fOnSpecialLineColors:=AValue; fMarkupSpecialLine.OnSpecialLineColors := AValue; end; procedure TCustomSynEdit.SetSpecialLineMarkup(const AValue : TSpecialLineMarkupEvent); begin FOnSpecialLineMarkup:=AValue; fMarkupSpecialLine.OnSpecialLineMarkup := AValue; end; function TCustomSynEdit.GetBracketMatchColor : TSynSelectedColor; begin Result := fMarkupBracket.MarkupInfo; end; function TCustomSynEdit.GetMouseLinkColor : TSynSelectedColor; begin Result := fMarkupCtrlMouse.MarkupInfo; end; function TCustomSynEdit.GetTrimSpaceType: TSynEditStringTrimmingType; begin Result := FTrimmedLinesView.TrimType; end; function TCustomSynEdit.GetViewedTextBuffer: TSynEditStringsLinked; begin Result := FTheLinesView; end; function TCustomSynEdit.GetFoldedTextBuffer: TObject; begin Result := FFoldedLinesView; end; procedure TCustomSynEdit.SetBracketHighlightStyle(const AValue: TSynEditBracketHighlightStyle); begin fMarkupBracket.HighlightStyle := AValue; end; procedure TCustomSynEdit.SetOnGutterClick(const AValue : TGutterClickEvent); begin FLeftGutter.OnGutterClick := AValue; // Todo: the IDE uses this for the left gutter only end; procedure TCustomSynEdit.SetUseIncrementalColor(const AValue : Boolean); begin fMarkupSelection.UseIncrementalColor:=AValue; end; function TCustomSynEdit.GetCharLen(const Line: string; CharStartPos: integer): integer; begin Result := FLines.LogicPosAddChars(Line, CharStartPos, 1, True) - CharStartPos; end; function TCustomSynEdit.GetLogicalCaretXY: TPoint; begin Result := FCaret.LineBytePos; end; function TCustomSynEdit.GetMarksObj: TObject; begin Result := FMarkList; end; procedure TCustomSynEdit.SetLogicalCaretXY(const NewLogCaretXY: TPoint); begin FCaret.ChangeOnTouch; FCaret.LineBytePos := NewLogCaretXY; end; procedure TCustomSynEdit.SetBeautifier(NewBeautifier: TSynCustomBeautifier); begin if fBeautifier = NewBeautifier then exit; if NewBeautifier = nil then fBeautifier := FDefaultBeautifier else fBeautifier := NewBeautifier; end; procedure TCustomSynEdit.SetTrimSpaceType(const AValue: TSynEditStringTrimmingType); begin FTrimmedLinesView.TrimType := AValue; end; function TCustomSynEdit.SynGetText: string; begin Result := fLines.Text; end; function TCustomSynEdit.RealGetText: TCaption; begin if FLines<>nil then Result := FLines.Text else Result := ''; end; procedure TCustomSynEdit.SetImeHandler(AValue: LazSynIme); begin if FImeHandler = AValue then Exit; FreeAndNil(FImeHandler); FImeHandler := AValue; end; {$ifdef Gtk2IME} procedure TCustomSynEdit.GTK_IMComposition(var Message: TMessage); begin FImeHandler.WMImeComposition(Message); end; {$endif} {$ifdef CocoaIME} procedure TCustomSynEdit.Cocoa_IMComposition(var Message: TMessage); begin Message.Result := PtrInt(FImeHandler); end; {$endif} {$IFDEF WinIME} procedure TCustomSynEdit.WMImeRequest(var Msg: TMessage); begin FImeHandler.WMImeRequest(Msg); end; procedure TCustomSynEdit.WMImeNotify(var Msg: TMessage); begin FImeHandler.WMImeNotify(Msg); end; procedure TCustomSynEdit.WMImeComposition(var Msg: TMessage); begin FImeHandler.WMImeComposition(Msg); end; procedure TCustomSynEdit.WMImeStartComposition(var Msg: TMessage); begin FImeHandler.WMImeStartComposition(Msg); end; procedure TCustomSynEdit.WMImeEndComposition(var Msg: TMessage); begin FImeHandler.WMImeEndComposition(Msg); end; {$ENDIF} procedure TCustomSynEdit.InvalidateGutter; begin InvalidateGutterLines(-1, -1); end; procedure TCustomSynEdit.InvalidateGutterLines(FirstLine, LastLine: integer); // Todo: move to gutter var offs: Integer; begin if sfPainting in fStateFlags then exit; if Visible and HandleAllocated then begin {$IFDEF VerboseSynEditInvalidate} DebugLnEnter(['TCustomSynEdit.InvalidateGutterLines ',DbgSName(self), ' FirstLine=',FirstLine, ' LastLine=',LastLine]); {$ENDIF} {$IFDEF Windows10GhostCaretIssue} if (FLeftGutter.Visible or FRightGutter.Visible) and (sfCaretChanged in fStateFlags) then InvalidateLines(FirstLine, LastLine); {$ENDIF} if (FirstLine = -1) and (LastLine = -1) then begin FPaintArea.InvalidateGutterLines(-1, -1); end else begin if (LastLine <> -1) and (LastLine < FirstLine) then SwapInt(FirstLine, LastLine); offs := 0; if FPaintLock > 0 then begin // pretend we haven't scrolled offs := - (FOldTopView - TopView); end; FPaintArea.InvalidateGutterLines(FirstLine-1, LastLine-1, offs); end; {$IFDEF VerboseSynEditInvalidate} DebugLnExit(['TCustomSynEdit.InvalidateGutterLines ',DbgSName(self)]); {$ENDIF} FScreenCaret.WaitForPaint; end; end; procedure TCustomSynEdit.InvalidateLines(FirstLine, LastLine: integer); var offs: Integer; begin if sfPainting in fStateFlags then exit; if Visible and HandleAllocated then begin {$IFDEF VerboseSynEditInvalidate} DebugLnEnter(['TCustomSynEdit.InvalidateTextLines ',DbgSName(self), ' FirstLine=',FirstLine, ' LastLine=',LastLine]); {$ENDIF} if (FirstLine = -1) and (LastLine = -1) then begin FPaintArea.InvalidateTextLines(-1, -1); end else begin if (LastLine <> -1) and (LastLine < FirstLine) then SwapInt(FirstLine, LastLine); offs := 0; if FPaintLock > 0 then begin // pretend we haven't scrolled offs := - (FOldTopView - TopView); end; FPaintArea.InvalidateTextLines(FirstLine-1, LastLine-1, offs); end; {$IFDEF VerboseSynEditInvalidate} DebugLnExit(['TCustomSynEdit.InvalidateTextLines ',DbgSName(self)]); {$ENDIF} FScreenCaret.WaitForPaint; end; end; procedure TCustomSynEdit.KeyDown(var Key: Word; Shift: TShiftState); var Data: pointer; C: char; Cmd: TSynEditorCommand; IsStartOfCombo, Handled: boolean; begin FInMouseClickEvent := False; {$IFDEF VerboseKeys} DebugLn('[TCustomSynEdit.KeyDown] ',dbgs(Key),' ',dbgs(Shift)); {$ENDIF} if (not WantTabs) and (Key = VK_TAB) and ((Shift - [ssShift]) = []) then begin inherited KeyDown(Key, Shift); exit; end; // Run even before OnKeyDown if FKeyDownEventList <> nil then FKeyDownEventList.CallKeyDownHandlers(Self, Key, Shift); if Key=0 then exit; inherited; if assigned(fMarkupCtrlMouse) then fMarkupCtrlMouse.UpdateCtrlState(Shift); if Key in [VK_SHIFT, VK_CONTROL, VK_MENU, VK_LSHIFT, VK_LCONTROL, VK_LMENU, VK_RSHIFT, VK_RCONTROL, VK_RMENU, VK_LWIN, VK_RWIN] then exit; Data := nil; C := #0; try // If the translations requires Data, memory will be allocated for it via a // GetMem call. The client must call FreeMem on Data if it is not NIL. IsStartOfCombo := False; Handled := False; // Check 2nd stroke in SynEdit.KeyStrokes if FCurrentComboKeyStrokes <> nil then begin // Run hooked first, it might want to "steal" the key(s) Cmd := 0; FHookedKeyTranslationList.CallHookedKeyTranslationHandlers(self, Key, Shift, Data, IsStartOfCombo, Handled, Cmd, FCurrentComboKeyStrokes); if not Handled then begin Cmd := KeyStrokes.FindKeycodeEx(Key, Shift, Data, IsStartOfCombo, True, FCurrentComboKeyStrokes); if IsStartOfCombo then FCurrentComboKeyStrokes := FKeyStrokes; Handled := (Cmd <> ecNone) or IsStartOfCombo; end; if not IsStartOfCombo then begin FCurrentComboKeyStrokes.ResetKeyCombo; FCurrentComboKeyStrokes := nil; end; end; assert(Handled or (FCurrentComboKeyStrokes=nil), 'FCurrentComboKeyStrokes<>nil, should be handled'); // Check 1st/single stroke in Hooked KeyStrokes if not Handled then begin FCurrentComboKeyStrokes := nil; FHookedKeyTranslationList.CallHookedKeyTranslationHandlers(self, Key, Shift, Data, IsStartOfCombo, Handled, Cmd, FCurrentComboKeyStrokes); if (not IsStartOfCombo) and (FCurrentComboKeyStrokes <> nil) then FCurrentComboKeyStrokes.ResetKeyCombo; // should not happen end; // Check 1st/single stroke in SynEdit.KeyStrokes if not Handled then begin FKeyStrokes.ResetKeyCombo; Cmd := KeyStrokes.FindKeycodeEx(Key, Shift, Data, IsStartOfCombo); if IsStartOfCombo then FCurrentComboKeyStrokes := FKeyStrokes; end; if Cmd <> ecNone then begin // Reset FCurrentComboKeyStrokes => no open combo assert(FCurrentComboKeyStrokes=nil, 'FCurrentComboKeyStrokes<>nil, should be ecNone'); if FCurrentComboKeyStrokes <> nil then FCurrentComboKeyStrokes.ResetKeyCombo; FCurrentComboKeyStrokes := nil; Include(FStateFlags, sfHideCursor); LastMouseCaret := Point(-1,-1); // includes update cursor //DebugLn(['[TCustomSynEdit.KeyDown] key translated ',cmd]); Key := 0; // eat it. Include(fStateFlags, sfIgnoreNextChar); CommandProcessor(Cmd, C, Data); end else if IsStartOfCombo then begin // this key could be the start of a two-key-combo shortcut Key := 0; // eat it. Include(fStateFlags, sfIgnoreNextChar); end else Exclude(fStateFlags, sfIgnoreNextChar); finally if Data <> nil then FreeMem(Data); end; UpdateCursor; SelAvailChange(nil); //DebugLn('[TCustomSynEdit.KeyDown] END ',dbgs(Key),' ',dbgs(Shift)); end; procedure TCustomSynEdit.KeyUp(var Key: Word; Shift: TShiftState); var CurMouseActionShiftMask: TShiftState; begin {$IFDEF VerboseKeys} DebugLn(['[TCustomSynEdit.KeyUp] ',Key ,' Shift=',ssShift in Shift,' Ctrl=',ssCtrl in Shift,' Alt=',ssAlt in Shift]); {$ENDIF} // Run even before OnKeyUp if FKeyUpEventList <> nil then FKeyUpEventList.CallKeyDownHandlers(Self, Key, Shift); CurMouseActionShiftMask := FMouseActionShiftMask; case Key of VK_SHIFT, VK_LSHIFT, VK_RSHIFT: Exclude(FMouseActionShiftMask, ssShift); VK_CONTROL, VK_LCONTROL, VK_RCONTROL: Exclude(FMouseActionShiftMask, ssCtrl); VK_MENU, VK_LMENU, VK_RMENU: FMouseActionShiftMask := FMouseActionShiftMask - [ssAlt, ssAltGr]; end; if (Key in [VK_MENU, VK_LMENU]) and (ssAlt in CurMouseActionShiftMask) then begin {$IfDef WINDOWS} Key := 0; {$EndIf} end; if Key=0 then exit; inherited KeyUp(Key, Shift); if sfIgnoreNextChar in fStateFlags then Exclude(FStateFlags, sfIgnoreNextChar); if assigned(fMarkupCtrlMouse) then fMarkupCtrlMouse.UpdateCtrlState(Shift); UpdateCursor; end; procedure TCustomSynEdit.Loaded; begin inherited Loaded; UpdateCaret; end; procedure TCustomSynEdit.UTF8KeyPress(var Key: TUTF8Char); var OnKeyPressFired : boolean = false; begin if Key='' then exit; // Run even before OnKeyPress if FUtf8KeyPressEventList <> nil then FUtf8KeyPressEventList.CallUtf8KeyPressHandlers(Self, Key); if Key='' then exit; // don't fire the event if key is to be ignored if not (sfIgnoreNextChar in fStateFlags) then begin Include(FStateFlags, sfHideCursor); if Assigned(OnUTF8KeyPress) then OnUTF8KeyPress(Self, Key); // The key will be handled in UTFKeyPress always and KeyPress won't be called // so we we fire the OnKeyPress here if (ord(key[1])< %11000000) and (key[1]<>#0) and Assigned(OnKeyPress) then begin OnKeyPress(Self, Key[1]); OnKeyPressFired:= true;//used to prevent from double firing issue #0026444 end; {$IFDEF VerboseKeys} DebugLn('TCustomSynEdit.UTF8KeyPress ',DbgSName(Self),' Key="',DbgStr(Key),'"'); {$ENDIF} CommandProcessor(ecChar, Key, nil); // Check if ecChar has handled the Key; Todo: move the condition, in one common place if (not ReadOnly or OnKeyPressFired) and ((Key = #13) or (Key >= #32)) and (Key <> #127) then Key:=''; end else begin // don't ignore further keys Exclude(fStateFlags, sfIgnoreNextChar); // Key was handled anyway, so eat it! Key:=''; end; end; procedure TCustomSynEdit.KeyPress(var Key: Char); begin if Key=#0 then exit; // Run even before OnKeyPress if FKeyPressEventList <> nil then FKeyPressEventList.CallKeyPressHandlers(Self, Key); if Key=#0 then exit; // don't fire the event if key is to be ignored if not (sfIgnoreNextChar in fStateFlags) then begin Include(FStateFlags, sfHideCursor); {$IFDEF VerboseKeys} DebugLn('TCustomSynEdit.KeyPress ',DbgSName(Self),' Key="',DbgStr(Key),'"'); {$ENDIF} if Assigned(OnKeyPress) then OnKeyPress(Self, Key); CommandProcessor(ecChar, Key, nil); // Check if ecChar has handled the Key; Todo: move the condition, in one common place if not ReadOnly and ((Key = #13) or (Key >= #32)) and (Key <> #127) then Key:=#0; end else begin // don't ignore further keys Exclude(fStateFlags, sfIgnoreNextChar); // Key was handled anyway, so eat it! Key:=#0; end; end; function TCustomSynEdit.DoHandleMouseAction(AnActionList: TSynEditMouseActions; var AnInfo: TSynEditMouseActionInfo): Boolean; var CaretDone, ResetMouseCapture: Boolean; AnAction: TSynEditMouseAction; procedure MoveCaret; begin FCaret.LineCharPos := AnInfo.NewCaret.LineCharPos; CaretDone := True; end; function GetWheelScrollAmount(APageSize: integer): integer; const WHEEL_DELTA = 120; //WHEEL_PAGESCROLL = MAXDWORD; var WClicks, WLines: Integer; IsHoriz: Boolean; begin IsHoriz := AnInfo.Button in [mbXWheelLeft, mbXWheelRight]; Inc(FMouseWheelAccumulator[IsHoriz], AnInfo.WheelDelta); Inc(FMouseWheelLinesAccumulator[IsHoriz], MinMax(Mouse.WheelScrollLines, 1, APageSize) * AnInfo.WheelDelta); WClicks := FMouseWheelAccumulator[IsHoriz] div WHEEL_DELTA; WLines := FMouseWheelLinesAccumulator[IsHoriz] div WHEEL_DELTA; dec(FMouseWheelAccumulator[IsHoriz], WClicks * WHEEL_DELTA); dec(FMouseWheelLinesAccumulator[IsHoriz], WLines * WHEEL_DELTA); case AnAction.Option of emcoWheelScrollSystem: begin Result := Abs(WLines); end; emcoWheelScrollLines: begin Result := Abs(WClicks); If Result = 0 then exit; if AnAction.Option2 > 0 then Result := Result * AnAction.Option2; if (Result > APageSize) then Result := APageSize; exit; end; emcoWheelScrollPages: Result := Abs(WClicks) * APageSize; emcoWheelScrollPagesLessOne: Result := Abs(WClicks) * (APageSize - 1); else begin Result := Abs(WLines); exit; end; end; If Result = 0 then exit; if AnAction.Option2 > 0 then Result := MulDiv(Result, AnAction.Option2, 100); if (Result > APageSize) then Result := APageSize; if (Result < 1) then Result := 1; end; var ACommand: TSynEditorMouseCommand; ClipHelper: TSynClipboardStream; i, j: integer; p1, p2: TPoint; s: String; AnActionResultDummy: TSynEditMouseActionResult; DownMisMatch: Boolean; begin AnAction := nil; Result := False; while not Result do begin AnAction := AnActionList.FindCommand(AnInfo, AnAction); if AnAction = nil then exit(False); if (FConfirmMouseDownMatchAct <> nil) then begin // simulated up click at the coordinates of the down click if FConfirmMouseDownMatchAct = AnAction then begin FConfirmMouseDownMatchFound := True; exit(True); end; if not(crLastDownPosSearchAll in FConfirmMouseDownMatchAct.ButtonUpRestrictions) then exit(True); continue; end; if (AnAction.ClickDir = cdUp) and (AnAction.ButtonUpRestrictions - [crAllowFallback] <> []) then begin DownMisMatch := ( (crLastDownButton in AnAction.ButtonUpRestrictions) and (SynMouseButtonMap[FMouseDownButton] <> AnInfo.Button) ) or ( (crLastDownShift in AnAction.ButtonUpRestrictions) and (FMouseDownShift * [ssShift, ssAlt, ssCtrl, ssMeta, ssSuper, ssHyper, ssAltGr, ssCaps, ssNum, ssScroll] <> AnInfo.Shift * [ssShift, ssAlt, ssCtrl, ssMeta, ssSuper, ssHyper, ssAltGr, ssCaps, ssNum, ssScroll]) ) or ( (crLastDownPosSameLine in AnAction.ButtonUpRestrictions) and (PixelsToRowColumn(Point(fMouseDownX, fMouseDownY)).y <> AnInfo.NewCaret.LinePos) ); If (not DownMisMatch) and (crLastDownPos in AnAction.ButtonUpRestrictions) then begin try FConfirmMouseDownMatchAct := AnAction; FConfirmMouseDownMatchFound := False; // simulate up click at the coordinates of the down click FindAndHandleMouseAction(AnInfo.Button, AnInfo.Shift, fMouseDownX, fMouseDownY, AnInfo.CCount, cdUp, AnActionResultDummy); finally FConfirmMouseDownMatchAct := nil; end; DownMisMatch := not FConfirmMouseDownMatchFound; end; If DownMisMatch then exit(not(crAllowFallback in AnAction.ButtonUpRestrictions)); end; ACommand := AnAction.Command; AnInfo.CaretDone := False; // Opening the context menu must not unset the block selection // Therefore if a non persistent block is given, it shall ignore the caret move. if (ACommand = emcContextMenu) and FBlockSelection.SelAvail and not FBlockSelection.Persistent then begin case AnAction.Option of emcoSelectionCaretMoveOutside: AnInfo.CaretDone := (CompareCarets(AnInfo.NewCaret.LineBytePos, FBlockSelection.FirstLineBytePos) <= 0) and (CompareCarets(AnInfo.NewCaret.LineBytePos, FBlockSelection.LastLineBytePos) >= 0); emcoSelectionCaretMoveAlways: AnInfo.CaretDone := False; else AnInfo.CaretDone := True; end; end; // Plugins/External Result := FMouseActionExecHandlerList.CallExecHandlers(AnAction, AnInfo); // Gutter if not Result then Result := FLeftGutter.DoHandleMouseAction(AnAction, AnInfo); if not Result then Result := FRightGutter.DoHandleMouseAction(AnAction, AnInfo); if Result then begin if (not AnInfo.CaretDone) and AnAction.MoveCaret then MoveCaret; if (AnAction.IgnoreUpClick) then AnInfo.IgnoreUpClick := True; FMouseActionShiftMask := AnInfo.Shift; exit; end; Result := True; CaretDone := AnInfo.CaretDone; ResetMouseCapture := True; if (ACommand = emcWheelScrollDown) then begin // sroll dependant on visible scrollbar / or not at all if (fStateFlags * [sfVertScrollbarVisible, sfHorizScrollbarVisible] = [sfHorizScrollbarVisible]) then ACommand := emcWheelHorizScrollDown else ACommand := emcWheelVertScrollDown; end; if (ACommand = emcWheelScrollUp) then begin // sroll dependant on visible scrollbar / or not at all if (fStateFlags * [sfVertScrollbarVisible, sfHorizScrollbarVisible] = [sfHorizScrollbarVisible]) then ACommand := emcWheelHorizScrollUp else ACommand := emcWheelVertScrollUp; end; case ACommand of emcNone: ; // do nothing, but result := true emcStartSelections, emcStartColumnSelections, emcStartLineSelections, emcStartLineSelectionsNoneEmpty, emcStartSelectTokens, emcStartSelectWords, emcStartSelectLines: begin FMouseSelectionCmd := emcNone; FBlockSelection.AutoExtend := AnAction.Option = emcoSelectionContinue; FCaret.ChangeOnTouch; MoveCaret; FMouseSelectionMode := FBlockSelection.SelectionMode; case ACommand of emcStartColumnSelections: FMouseSelectionMode := smColumn; emcStartLineSelections: FMouseSelectionMode := smLine; emcStartLineSelectionsNoneEmpty: begin FMouseSelectionMode := smLine; if (AnAction.Option <> emcoSelectionContinue) or (not SelAvail) then FBlockSelection.StartLineBytePos := FCaret.LineBytePos; FBlockSelection.ActiveSelectionMode := smLine; FBlockSelection.AutoExtend := True; FBlockSelection.ForceSingleLineSelected := True; FBlockSelection.AutoExtend := AnAction.Option = emcoSelectionContinue; end; emcStartSelectTokens, emcStartSelectWords, emcStartSelectLines: begin FMouseSelectionCmd := ACommand; AnInfo.NewCaret.LineCharPos := PixelsToRowColumn(Point(AnInfo.MouseX, AnInfo.MouseY), [scmLimitToLines, scmForceLeftSidePos]); s := AnInfo.NewCaret.LineText; i := length(s) + 1; p1 := AnInfo.NewCaret.LineBytePos; if (p1.X >= i) then p1.X := Max(1, i - 1); p2 := p1; if (AnAction.Option = emcoSelectionContinue) and SelAvail then p1 := FBlockSelection.StartLineBytePos; if p2.X = i then begin p2 := Point(1, Min(FTheLinesView.Count, p2.Y + 1)); end else begin case ACommand of emcStartSelectTokens: begin if (AnAction.Option <> emcoSelectionContinue) or (not SelAvail) then p1.X := Max(1, FWordBreaker.PrevBoundary(s, p1.X, True)); p2.X := FWordBreaker.NextBoundary(s, p2.X); if p2.X < 1 then p2.X := i; end; emcStartSelectWords: begin if (AnAction.Option <> emcoSelectionContinue) or (not SelAvail) then p1.X := Max(1, Max(FWordBreaker.PrevWordEnd(s, p1.X, True), FWordBreaker.PrevWordStart(s, p1.X, True))); j := FWordBreaker.NextWordStart(s, p2.X); if j < 1 then j := i; p2.X := FWordBreaker.NextWordEnd(s, p2.X); if p2.X < 1 then p2.X := i; p2.X := Min(p2.X, j); end; emcStartSelectLines: begin if (AnAction.Option <> emcoSelectionContinue) or (not SelAvail) then p1.X := 1; p2 := Point(1, Min(FTheLinesView.Count, p1.Y + 1)); end; end; end; if (AnAction.Option <> emcoSelectionContinue) or (not SelAvail) then FBlockSelection.StartLineBytePos := p1; FBlockSelection.AutoExtend := True; FCaret.ChangeOnTouch; FCaret.LineBytePos := p2; FBlockSelection.EndLineBytePos := p2; // caret might be locked FBlockSelection.BeginMinimumSelection; end; end; if (AnAction.Option = emcoSelectionContinue) then begin // only set ActiveSelectionMode if we continue an existing selection // Otherwise we are just setting the caret, selection will start on mouse move FBlockSelection.ActiveSelectionMode := FMouseSelectionMode; Include(fStateFlags, sfMouseDoneSelecting); // TODO Add sfMouseSelecting, maybe only if caret did indeed move end; MouseCapture := True; ResetMouseCapture := False; Include(fStateFlags, sfWaitForMouseSelecting); end; emcSelectWord: begin if AnAction.MoveCaret then MoveCaret; SetWordBlock(AnInfo.NewCaret.LineBytePos); end; emcSelectLine: begin if AnAction.MoveCaret then MoveCaret; SetLineBlock(AnInfo.NewCaret.LineBytePos, AnAction.Option = emcoSelectLineFull); end; emcSelectPara: begin if AnAction.MoveCaret then MoveCaret; SetParagraphBlock(AnInfo.NewCaret.LineBytePos); end; emcStartDragMove: begin if SelAvail then begin Include(fStateFlags, sfWaitForDragging); if AnAction.Option = emcoNotDragedNoCaretOnUp then Include(fStateFlags, sfWaitForDraggingNoCaret); MouseCapture := True; ResetMouseCapture := False; end else Result := False; // Currently only drags smNormal end; emcPasteSelection: begin if not ReadOnly then begin ClipHelper := TSynClipboardStream.Create; try ClipHelper.ReadFromClipboard(PrimarySelection); if ClipHelper.TextP <> nil then begin MoveCaret; if (not FBlockSelection.Persistent) then FBlockSelection.Clear; Result := PasteFromClipboardEx(ClipHelper); end else Result := False; finally ClipHelper.Free; end; end; end; emcMouseLink: begin if assigned(fMarkupCtrlMouse) and fMarkupCtrlMouse.IsMouseOverLink and assigned(FOnClickLink) then begin if AnAction.MoveCaret then MoveCaret; FOnClickLink(Self, SynMouseButtonBackMap[AnInfo.Button], AnInfo.Shift, AnInfo.MouseX, AnInfo.MouseY); end else Result := False; end; emcContextMenu: begin if AnAction.MoveCaret and (not CaretDone) then begin MoveCaret; end; AnInfo.ActionResult.DoPopUpEvent := True; AnInfo.ActionResult.PopUpEventX := AnInfo.MouseX; AnInfo.ActionResult.PopUpEventY := AnInfo.MouseY; AnInfo.ActionResult.PopUpMenu := PopupMenu; end; emcSynEditCommand: begin if AnAction.MoveCaret then MoveCaret; CommandProcessor(AnAction.Option, #0, nil); end; emcWheelHorizScrollDown, emcWheelHorizScrollUp: begin i := GetWheelScrollAmount(CharsInWindow); if ACommand = emcWheelHorizScrollUp then i := -i; if i <> 0 then begin LeftChar := LeftChar + i; if fStateFlags * [sfMouseSelecting, sfWaitForMouseSelecting] <> [] then begin FStateFlags := FStateFlags - [sfWaitForMouseSelecting] + [sfMouseSelecting, sfMouseDoneSelecting]; ResetMouseCapture := False; AnInfo.NewCaret.LineCharPos := PixelsToRowColumn(Point(AnInfo.MouseX, AnInfo.MouseY)); FBlockSelection.AutoExtend := True; MoveCaret; FBlockSelection.ActiveSelectionMode := FMouseSelectionMode; end; end; end; emcWheelVertScrollDown, emcWheelVertScrollUp: begin i := GetWheelScrollAmount(LinesInWindow); if ACommand = emcWheelVertScrollUp then i := -i; if i <> 0 then begin TopView := TopView + i; if fStateFlags * [sfMouseSelecting, sfWaitForMouseSelecting] <> [] then begin FStateFlags := FStateFlags - [sfWaitForMouseSelecting] + [sfMouseSelecting, sfMouseDoneSelecting]; ResetMouseCapture := False; AnInfo.NewCaret.LineCharPos := PixelsToRowColumn(Point(AnInfo.MouseX, AnInfo.MouseY)); FBlockSelection.AutoExtend := True; MoveCaret; FBlockSelection.ActiveSelectionMode := FMouseSelectionMode; end; end; end; emcWheelZoomOut, emcWheelZoomIn: begin if ( (ACommand = emcWheelZoomOut) and (abs(Font.Height) < 3) ) or ( (ACommand = emcWheelZoomIn) and (abs(Font.Height) > 50) ) then begin Result := False; end else begin j := 1; if ACommand = emcWheelZoomIn then j := -1; i := FLastSetFontSize; if Font.Height < 0 then Font.Height := Font.Height + j else Font.Height := Font.Height - j; FLastSetFontSize := i; end; end; emcWheelZoomNorm: begin Font.Height := FLastSetFontSize; end; else Result := False; // ACommand was not handled => Fallback to parent Context end; if Result and (not CaretDone) and AnAction.MoveCaret then MoveCaret; if Result and (AnAction.IgnoreUpClick) then AnInfo.IgnoreUpClick := True; if ResetMouseCapture then MouseCapture := False; if FBlockSelection.AutoExtend and (FPaintLock = 0) then FBlockSelection.AutoExtend := False; if Result then FMouseActionShiftMask := AnInfo.Shift; end; end; procedure TCustomSynEdit.DoHandleMouseActionResult(AnActionResult: TSynEditMouseActionResult); var Handled: Boolean; begin if AnActionResult.PopUpMenu <> nil then begin Handled := False; if AnActionResult.DoPopUpEvent then inherited DoContextPopup(Point(AnActionResult.PopUpEventX, AnActionResult.PopUpEventY), Handled); if not Handled then begin AnActionResult.PopupMenu.PopupComponent:=self; AnActionResult.PopupMenu.PopUp; end; end; end; procedure TCustomSynEdit.UpdateShowing; begin if (sfAfterHandleCreatedNeeded in fStateFlags) and (not AutoSizeDelayed) and HandleAllocated then begin DoIncPaintLock(nil); // prevent calculations during inherited and ONLY during inherited try DoHandleInitialSizeFinished; finally DoDecPaintLock(nil); // run UpdateScrollBars end; end; inherited UpdateShowing; if fMarkupManager <> nil then fMarkupManager.DoVisibleChanged(IsVisible); if HandleAllocated then UpdateScreenCaret; end; procedure TCustomSynEdit.SetColor(Value: TColor); begin inherited SetColor(Value); FPaintArea.BackgroundColor := Color; end; function TCustomSynEdit.GetDragHotZoneInfo(x, y: Integer; out HorizFraction, VertFraction: Integer): boolean; var b: TRect; HotWidth: Integer; begin HorizFraction := 0; VertFraction := 0; b := FTextArea.Bounds; HotWidth := Min(LineHeight * 11 div 4, (b.Right - b.Left) div 4); if x < b.Left + HotWidth then HorizFraction := (x - (b.Left + HotWidth)) * 256 div HotWidth else if x >= b.Right - HotWidth then HorizFraction := (x - (b.Right - 1 - HotWidth)) * 256 div HotWidth; if y < b.Top + HotWidth then VertFraction := (y - (b.Top + HotWidth)) * 256 div HotWidth else if y >= b.Bottom - HotWidth then VertFraction := (y - (b.Bottom - 1 - HotWidth)) * 256 div HotWidth; Result := ( (HorizFraction <> 0) or (VertFraction <> 0) ) and (abs(HorizFraction) <= 256) and (abs(VertFraction) <= 256); end; procedure TCustomSynEdit.FindAndHandleMouseAction(AButton: TSynMouseButton; AShift: TShiftState; X, Y: Integer; ACCount: TSynMAClickCount; ADir: TSynMAClickDir; out AnActionResult: TSynEditMouseActionResult; AWheelDelta: Integer); var Info: TSynEditMouseActionInfo; begin FInternalCaret.AssignFrom(FCaret); FInternalCaret.Invalidate; FInternalCaret.LineCharPos := PixelsToRowColumn(Point(X,Y)); with Info do begin NewCaret := FInternalCaret; Button := AButton; Shift := AShift; MouseX := X; MouseY := Y; WheelDelta := AWheelDelta; CCount := ACCount; Dir := ADir; IgnoreUpClick := False; ActionResult.DoPopUpEvent := False; ActionResult.PopUpMenu := nil; end; try // Check plugins/external handlers if FMouseActionSearchHandlerList.CallSearchHandlers(Info, @DoHandleMouseAction) then exit; if FLeftGutter.Visible and (X < FLeftGutter.Width) then begin // mouse event occurred in Gutter ? if FLeftGutter.MaybeHandleMouseAction(Info, @DoHandleMouseAction) then exit; end else if FRightGutter.Visible and (X > ClientWidth - FRightGutter.Width) then begin // mouse event occurred in Gutter ? if FRightGutter.MaybeHandleMouseAction(Info, @DoHandleMouseAction) then exit; end else begin // mouse event occurred in selected block ? if SelAvail and (X >= FTextArea.Bounds.Left) and (X < FTextArea.Bounds.Right) and (Y >= FTextArea.Bounds.Top) and (Y < FTextArea.Bounds.Bottom) and IsPointInSelection(FInternalCaret.LineBytePos) then if DoHandleMouseAction(FMouseSelActions.GetActionsForOptions(MouseOptions), Info) then exit; // mouse event occurred in text? if DoHandleMouseAction(FMouseTextActions.GetActionsForOptions(MouseOptions), Info) then exit; end; DoHandleMouseAction(FMouseActions.GetActionsForOptions(MouseOptions), Info); finally if Info.IgnoreUpClick then include(fStateFlags, sfIgnoreUpClick); AnActionResult := Info.ActionResult; end; end; function TCustomSynEdit.FindGutterFromGutterPartList(const APartList: TObject): TObject; begin if APartList is TSynGutterPartList then Result := Gutter else if APartList is TSynRightGutterPartList then Result := RightGutter else Result := nil; end; procedure TCustomSynEdit.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var CType: TSynMAClickCount; AnActionResult: TSynEditMouseActionResult; begin DebugLnEnter(LOG_SynMouseEvents, ['>> TCustomSynEdit.MouseDown Mouse=',X,',',Y, ' Shift=',dbgs(Shift), ' Caret=',dbgs(CaretXY),', BlockBegin=',dbgs(BlockBegin),' BlockEnd=',dbgs(BlockEnd), ' StateFlags=',dbgs(fStateFlags)]); Exclude(FStateFlags, sfHideCursor); FInMouseClickEvent := True; if FMouseDownEventList <> nil then FMouseDownEventList.CallMouseDownHandlers(Self, Button, Shift, X, Y); if (X>=ClientWidth-ScrollBarWidth) or (Y>=ClientHeight-ScrollBarWidth) then begin inherited MouseDown(Button, Shift, X, Y); DebugLnExit(LOG_SynMouseEvents, ['<< TCustomSynEdit.MouseDown outside client']); exit; end; LastMouseCaret:=PixelsToRowColumn(Point(X,Y)); fMouseDownX := X; fMouseDownY := Y; FMouseDownButton := Button; FMouseDownShift := Shift; fStateFlags := fStateFlags - [sfDblClicked, sfTripleClicked, sfQuadClicked, sfLeftGutterClick, sfRightGutterClick, sfWaitForMouseSelecting, sfMouseSelecting, sfMouseDoneSelecting, sfWaitForDragging, sfWaitForDraggingNoCaret, sfIgnoreUpClick ]; Include(fStateFlags, sfInClick); if ssQuad in Shift then begin CType := ccQuad; Include(fStateFlags, sfQuadClicked); end else if ssTriple in Shift then begin CType := ccTriple; Include(fStateFlags, sfTripleClicked); end else if ssDouble in Shift then begin CType := ccDouble; Include(fStateFlags, sfDblClicked); end else CType := ccSingle; IncPaintLock; try if (X < TextLeftPixelOffset(False)) then begin Include(fStateFlags, sfLeftGutterClick); FLeftGutter.MouseDown(Button, Shift, X, Y); end; if (X > ClientWidth - TextRightPixelOffset - ScrollBarWidth) then begin Include(fStateFlags, sfRightGutterClick); FRightGutter.MouseDown(Button, Shift, X, Y); end; FindAndHandleMouseAction(SynMouseButtonMap[Button], Shift, X, Y, CType, cdDown, AnActionResult); finally DecPaintLock; end; DoHandleMouseActionResult(AnActionResult); inherited MouseDown(Button, Shift, X, Y); LCLIntf.SetFocus(Handle); UpdateCaret; SelAvailChange(nil); DebugLnExit(LOG_SynMouseEvents, ['<< TCustomSynEdit.MouseDown StateFlags=',dbgs(fStateFlags)]); end; procedure TCustomSynEdit.MouseMove(Shift: TShiftState; X, Y: Integer); var forw: Boolean; s: String; i, j: Integer; p1: TPoint; begin Exclude(FStateFlags, sfHideCursor); inherited MouseMove(Shift, x, y); if (sfLeftGutterClick in fStateFlags) then FLeftGutter.MouseMove(Shift, X, Y); if (sfRightGutterClick in fStateFlags) then FRightGutter.MouseMove(Shift, X, Y); FLastMouseLocation.LastMousePoint := Point(X,Y); LastMouseCaret := PixelsToRowColumn(Point(X,Y)); // TODO: Used for ctrl-Link => Use LastMousePoint, and calculate only, if modifier is down UpdateCursor; if (sfWaitForMouseSelecting in fStateFlags) and MouseCapture and ( (abs(fMouseDownX-X) >= MinMax(CharWidth div 2, 2, 4)) or (abs(fMouseDownY-Y) >= MinMax(LineHeight div 2, 2, 4)) ) then begin FStateFlags := FStateFlags - [sfWaitForMouseSelecting] + [sfMouseSelecting]; FBlockSelection.StickyAutoExtend := False; end; //debugln('TCustomSynEdit.MouseMove sfWaitForDragging=',dbgs(sfWaitForDragging in fStateFlags),' MouseCapture=',dbgs(MouseCapture),' GetCaptureControl=',DbgSName(GetCaptureControl)); if MouseCapture and (sfWaitForDragging in fStateFlags) then begin if (Abs(fMouseDownX - X) >= GetSystemMetrics(SM_CXDRAG)) or (Abs(fMouseDownY - Y) >= GetSystemMetrics(SM_CYDRAG)) then begin FStateFlags := FStateFlags -[sfWaitForDragging, sfWaitForDraggingNoCaret, sfWaitForMouseSelecting, sfMouseSelecting] + [sfIsDragging]; FBlockSelection.StickyAutoExtend := False; //debugln('TCustomSynEdit.MouseMove BeginDrag'); BeginDrag(true); end; end else if (fStateFlags * [sfMouseSelecting, sfIsDragging] = [sfMouseSelecting]) and MouseCapture then begin //DebugLn(' TCustomSynEdit.MouseMove CAPTURE Mouse=',dbgs(X),',',dbgs(Y),' Caret=',dbgs(CaretXY),', BlockBegin=',dbgs(BlockBegin),' BlockEnd=',dbgs(BlockEnd)); // compare to Bounds => Padding area does not scroll if (X >= FTextArea.Bounds.Left) and (X < FTextArea.Bounds.Right) and (Y >= FTextArea.Bounds.Top) and (Y < FTextArea.Bounds.Bottom) then begin FInternalCaret.AssignFrom(FCaret); FInternalCaret.Invalidate; FInternalCaret.LineCharPos := PixelsToRowColumn(Point(X,Y)); if (FMouseSelectionCmd in [emcStartSelectTokens, emcStartSelectWords, emcStartSelectLines]) then begin FInternalCaret.Invalidate; FInternalCaret.LineCharPos := PixelsToRowColumn(Point(X,Y), [scmForceLeftSidePos]); forw := ComparePoints(FInternalCaret.LineBytePos, FBlockSelection.StartLineBytePos) >= 0; s := FInternalCaret.LineText; i := length(s) + 1; p1 := FInternalCaret.LineBytePos; case FMouseSelectionCmd of emcStartSelectTokens: begin if forw then p1.X := FWordBreaker.NextBoundary(s, p1.X) else p1.X := Max(1, FWordBreaker.PrevBoundary(s, p1.X, True)); end; emcStartSelectWords: begin if forw then begin j := FWordBreaker.NextWordStart(s, p1.X); if j < 1 then j := i; p1.X := FWordBreaker.NextWordEnd(s, p1.X); if p1.X < 1 then p1.X := i; p1.X := Min(p1.X, j); end else p1.X := Max(1, Max(FWordBreaker.PrevWordEnd(s, p1.X, True), FWordBreaker.PrevWordStart(s, p1.X, True))); end; emcStartSelectLines: begin if forw then p1.X := i else p1.X := 1; end; end; if p1.X < 1 then p1.X := i; FInternalCaret.Invalidate; FInternalCaret.LineBytePos := p1; end; if (sfMouseSelecting in fStateFlags) and not FInternalCaret.IsAtPos(FCaret) then Include(fStateFlags, sfMouseDoneSelecting); FBlockSelection.StickyAutoExtend := False; FBlockSelection.AutoExtend := sfMouseSelecting in fStateFlags; Include(fStateFlags, sfPreventScrollAfterSelect); // not PaintLocked => setting caret will directly call EnsureCursorPos FCaret.LineBytePos := FInternalCaret.LineBytePos; exclude(fStateFlags, sfPreventScrollAfterSelect); FBlockSelection.AutoExtend := False; end else begin // begin scrolling? if X < FTextArea.Bounds.Left then FScrollDeltaX := Min((X - FTextArea.Bounds.Left - CharWidth) div CharWidth, -1) else if x >= FTextArea.Bounds.Right then FScrollDeltaX := Max((X - FTextArea.Bounds.Right + 1 + CharWidth) div CharWidth, 1) else FScrollDeltaX := 0; if Y < FTextArea.Bounds.Top then FScrollDeltaY := Min((Y - FTextArea.Bounds.Top - LineHeight) div LineHeight, -1) else if Y >= FTextArea.Bounds.Bottom then FScrollDeltaY := Max((Y - FTextArea.Bounds.Bottom + 1 + LineHeight) div LineHeight, 1) else FScrollDeltaY := 0; fScrollTimer.Interval := 100; fScrollTimer.Enabled := (fScrollDeltaX <> 0) or (fScrollDeltaY <> 0); if (sfMouseSelecting in fStateFlags) and ((fScrollDeltaX <> 0) or (fScrollDeltaY <> 0)) then Include(fStateFlags, sfMouseDoneSelecting); end; if sfMouseDoneSelecting in fStateFlags then begin FBlockSelection.ActiveSelectionMode := FMouseSelectionMode; end; end else if MouseCapture and (fStateFlags * [sfIsDragging, sfWaitForMouseSelecting] = []) then begin MouseCapture:=false; fScrollTimer.Enabled := False; end; end; procedure TCustomSynEdit.ScrollTimerHandler(Sender: TObject); var ViewedCaret: TPoint; CurMousePos: TPoint; X, Y: Integer; begin fScrollTimer.Interval := 100; if sfDraggingOver in fStateFlags then begin DragTimerHandler; exit; end; // changes to line / column in one go DoIncPaintLock(Self); // No editing is taking place try CurMousePos:=Point(0,0); GetCursorPos(CurMousePos); CurMousePos:=ScreenToClient(CurMousePos); // PixelsToViewedXY ViewedCaret := YToPos(FTextArea.PixelsToRowColumn(CurMousePos, [])); ViewedCaret.y := ViewedCaret.y + ToIdx(TopView); // recalculate scroll deltas if CurMousePos.X < FTextArea.Bounds.Left then FScrollDeltaX := Min((CurMousePos.X - FTextArea.Bounds.Left - CharWidth) div CharWidth, -1) else if CurMousePos.x >= FTextArea.Bounds.Right then FScrollDeltaX := Max((CurMousePos.X - FTextArea.Bounds.Right + 1 + CharWidth) div CharWidth, 1) else FScrollDeltaX := 0; if CurMousePos.Y < FTextArea.Bounds.Top then FScrollDeltaY := Min((CurMousePos.Y - FTextArea.Bounds.Top - LineHeight) div LineHeight, -1) else if CurMousePos.Y >= FTextArea.Bounds.Bottom then FScrollDeltaY := Max((CurMousePos.Y - FTextArea.Bounds.Bottom + 1 + LineHeight) div LineHeight, 1) else FScrollDeltaY := 0; fScrollTimer.Enabled := (fScrollDeltaX <> 0) or (fScrollDeltaY <> 0); // now scroll if fScrollDeltaX <> 0 then begin LeftChar := LeftChar + fScrollDeltaX; X := LeftChar; if fScrollDeltaX > 0 then // scrolling right? Inc(X, CharsInWindow); FCaret.ViewedLineCharPos := Point(X, ViewedCaret.Y); if (not(sfIsDragging in fStateFlags)) then SetBlockEnd(LogicalCaretXY); end; if fScrollDeltaY <> 0 then begin if GetKeyState(VK_SHIFT) < 0 then TopView := TopView + fScrollDeltaY * LinesInWindow else TopView := TopView + fScrollDeltaY; if fScrollDeltaY > 0 then Y := TopView + LinesInWindow else Y := TopView; // scrolling up if Y < 1 // past end of file then y := FCaret.ViewedLinePos; FCaret.ViewedLineCharPos := Point(ViewedCaret.X, Y); if (not(sfIsDragging in fStateFlags)) then SetBlockEnd(LogicalCaretXY); end; finally if sfEnsureCursorPos in fStateFlags then Include(fStateFlags, sfPreventScrollAfterSelect); DoDecPaintLock(Self); end; end; procedure TCustomSynEdit.DoContextPopup(MousePos: TPoint; var Handled: Boolean); begin Handled := FInMouseClickEvent; if not Handled then Exclude(FStateFlags, sfHideCursor); end; procedure TCustomSynEdit.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var wasDragging, wasSelecting, ignoreUp : Boolean; CType: TSynMAClickCount; AnActionResult: TSynEditMouseActionResult; begin DebugLn(LOG_SynMouseEvents, ['>> TCustomSynEdit.MouseUp Mouse=',X,',',Y, ' Shift=',dbgs(Shift), ' Caret=',dbgs(CaretXY),', BlockBegin=',dbgs(BlockBegin),' BlockEnd=',dbgs(BlockEnd), ' StateFlags=',dbgs(fStateFlags)]); Exclude(FStateFlags, sfHideCursor); FInMouseClickEvent := True; wasDragging := (sfIsDragging in fStateFlags); wasSelecting := (sfMouseDoneSelecting in fStateFlags); ignoreUp := (sfIgnoreUpClick in fStateFlags); Exclude(fStateFlags, sfIsDragging); Exclude(fStateFlags, sfWaitForMouseSelecting); Exclude(fStateFlags, sfMouseSelecting); Exclude(fStateFlags, sfMouseDoneSelecting); Exclude(fStateFlags, sfIgnoreUpClick); fScrollTimer.Enabled := False; inherited MouseUp(Button, Shift, X, Y); MouseCapture := False; if not (sfInClick in fStateFlags) then exit; if sfQuadClicked in fStateFlags then begin CType := ccQuad; end else if sfTripleClicked in fStateFlags then begin CType := ccTriple; end else if sfDblClicked in fStateFlags then begin CType := ccDouble; end else CType := ccSingle; fStateFlags:=fStateFlags - [sfInClick, sfDblClicked,sfTripleClicked,sfQuadClicked]; if fStateFlags * [sfWaitForDragging, sfWaitForDraggingNoCaret] = [sfWaitForDragging] then begin ComputeCaret(X, Y); SetBlockBegin(LogicalCaretXY); SetBlockEnd(LogicalCaretXY); Exclude(fStateFlags, sfWaitForDragging); end; if (X>=ClientWidth-ScrollBarWidth) or (Y>=ClientHeight-ScrollBarWidth) then exit; LastMouseCaret:=PixelsToRowColumn(Point(X,Y)); if wasDragging or wasSelecting or ignoreUp then exit; IncPaintLock; try if (sfLeftGutterClick in fStateFlags) then begin FLeftGutter.MouseUp(Button, Shift, X, Y); Exclude(fStateFlags, sfLeftGutterClick); end; if (sfRightGutterClick in fStateFlags) then begin FRightGutter.MouseUp(Button, Shift, X, Y); Exclude(fStateFlags, sfRightGutterClick); end; FindAndHandleMouseAction(SynMouseButtonMap[Button], Shift, X, Y, CType, cdUp, AnActionResult); finally DecPaintLock; end; DoHandleMouseActionResult(AnActionResult); SelAvailChange(nil); //DebugLn('TCustomSynEdit.MouseUp END Mouse=',X,',',Y,' Caret=',CaretX,',',CaretY,', BlockBegin=',BlockBegin.X,',',BlockBegin.Y,' BlockEnd=',BlockEnd.X,',',BlockEnd.Y); end; procedure TCustomSynEdit.DragTimerHandler; var hf, vf: Integer; CurMousePos: TPoint; begin CurMousePos:=Point(0,0); GetCursorPos(CurMousePos); CurMousePos:=ScreenToClient(CurMousePos); GetDragHotZoneInfo(CurMousePos.x, CurMousePos.y, hf, vf); if ( (hf = 0) and (vf = 0) ) or (abs(hf) >= 384) or (abs(vf) >= 384) then begin fScrollTimer.Enabled := False; Exclude(fStateFlags, sfDraggingOver); exit; end; if sfIsDragging in fStateFlags then FBlockSelection.IncPersistentLock; DoIncPaintLock(Self); try if hf <> 0 then LeftChar := LeftChar + hf div 32; if vf <> 0 then TopView := TopView + vf div 32; FCaret.LineCharPos:=PixelsToRowColumn(CurMousePos); finally if sfEnsureCursorPos in fStateFlags then Include(fStateFlags, sfPreventScrollAfterSelect); DoDecPaintLock(Self); if sfIsDragging in fStateFlags then FBlockSelection.DecPersistentLock; end; end; procedure TCustomSynEdit.Paint; var rcClip: TRect; begin // Get the invalidated rect. Compute the invalid area in lines / columns. rcClip := Canvas.ClipRect; If FPaintLock > 0 then begin debugln(['Warning: SynEdit.Paint called during PaintLock']); {$IFDEF SynCheckPaintLock} DumpStack; {$ENDIF} // Ensure this will be repainted after PaintLock if FInvalidateRect.Top < 0 then FInvalidateRect := rcClip else types.UnionRect(FInvalidateRect, FInvalidateRect, rcClip); // Just paint the background SetBkColor(Canvas.Handle, ColorToRGB(Color)); InternalFillRect(Canvas.Handle, rcClip); if rcClip.Left <= TextLeftPixelOffset(False) then begin rcClip.Right := TextLeftPixelOffset(False)+1; SetBkColor(Canvas.Handle, ColorToRGB(FLeftGutter.Color)); InternalFillRect(Canvas.Handle, rcClip); end; exit; end; {$IFDEF EnableDoubleBuf} //rcClip:=Rect(0,0,ClientWidth,ClientHeight); StartPaintBuffer(rcClip); {$ENDIF} {$IFDEF SYNSCROLLDEBUG} debugln(['PAINT ',DbgSName(self),' sfHasScrolled=',dbgs(sfHasScrolled in fStateFlags),' rect=',dbgs(rcClip)]); {$ENDIF} Include(fStateFlags,sfPainting); Exclude(fStateFlags, sfHasScrolled); TSynPaintEventHandlerList(FPaintEventHandlerList).CallPaintEventHandlers(Self, peBeforePaint, rcClip); FScreenCaret.BeginPaint(rcClip); // Now paint everything while the caret is hidden. try FPaintArea.Paint(Canvas, rcClip); DoOnPaint; finally UpdateCaret; // Todo: this is to call only ShowCaret() / do not create caret here / Issue 0021924 FScreenCaret.FinishPaint(rcClip); // after update caret TSynPaintEventHandlerList(FPaintEventHandlerList).CallPaintEventHandlers(Self, peAfterPaint, rcClip); {$IFDEF EnableDoubleBuf} EndPaintBuffer(rcClip); {$ENDIF} Exclude(fStateFlags,sfPainting); Include(fStateFlags, sfHasPainted); end; end; procedure TCustomSynEdit.CodeFoldAction(iLine: integer); // iLine is 1 based as parameter var ScrY: Integer; begin if (iLine<=0) or (iLine>FTheLinesView.Count) then exit; ScrY := ToIdx(TextXYToScreenXY(Point(1, iLine)).y); //DebugLn(['****** FoldAction at ',iLine,' scrline=',ScrY, ' type ', SynEditCodeFoldTypeNames[FFoldedLinesView.FoldType[ScrY]], ' view topline=',FFoldedLinesView.TopLine ]); if FFoldedLinesView.FoldType[ScrY] * [cfCollapsedFold, cfCollapsedHide] <> [] then FFoldedLinesView.UnFoldAtTextIndex(iLine) else if FFoldedLinesView.FoldType[ScrY] * [cfFoldStart] <> [] then FFoldedLinesView.FoldAtTextIndex(iLine); end; function TCustomSynEdit.FindNextUnfoldedLine(iLine: integer; Down: boolean ): Integer; // iLine is 1 based begin Result := Max(0, FTheLinesView.TextToViewIndex(ToIdx(iLine))); if Down then Result := ToPos(FTheLinesView.ViewToTextIndex(Result+1)) else Result := ToPos(FTheLinesView.ViewToTextIndex(Result)); end; function TCustomSynEdit.CreateGutter(AOwner : TSynEditBase; ASide: TSynGutterSide; ATextDrawer: TLazEditTextGridPainter): TSynGutter; begin Result := TSynGutter.Create(AOwner, ASide, ATextDrawer); end; procedure TCustomSynEdit.UnfoldAll; begin FFoldedLinesView.UnfoldAll; Invalidate; end; procedure TCustomSynEdit.FoldAll(StartLevel : Integer = 0; IgnoreNested : Boolean = False); begin FFoldedLinesView.FoldAll(StartLevel, IgnoreNested); Invalidate; end; procedure TCustomSynEdit.StartPaintBuffer(const ClipRect: TRect); {$IFDEF EnableDoubleBuf} var NewBufferWidth: Integer; NewBufferHeight: Integer; {$ENDIF} begin {$IFDEF EnableDoubleBuf} if (SavedCanvas<>nil) then RaiseGDBException(''); if BufferBitmap=nil then BufferBitmap:=TBitmap.Create; NewBufferWidth:=BufferBitmap.Width; NewBufferHeight:=BufferBitmap.Height; if NewBufferWidth<ClipRect.Right then NewBufferWidth:=ClipRect.Right; if NewBufferHeight<ClipRect.Bottom then NewBufferHeight:=ClipRect.Bottom; BufferBitmap.Width:=NewBufferWidth; BufferBitmap.Height:=NewBufferHeight; SavedCanvas:=Canvas; Canvas:=BufferBitmap.Canvas; {$ENDIF} end; procedure TCustomSynEdit.EndPaintBuffer(const ClipRect: TRect); begin {$IFDEF EnableDoubleBuf} if (SavedCanvas=nil) then RaiseGDBException(''); if not (SavedCanvas is TControlCanvas) then RaiseGDBException(''); Canvas:=SavedCanvas; SavedCanvas:=nil; Canvas.CopyRect(ClipRect,BufferBitmap.Canvas,ClipRect); {$ENDIF} end; function TCustomSynEdit.NextWordLogicalPos(ABoundary: TLazSynWordBoundary; WordEndForDelete: Boolean): TPoint; var i, j, CX, CY, NX, OX, LineLen: integer; Line, ULine: string; CWidth: TPhysicalCharWidths; r, InclCurrent: Boolean; begin Result := LogicalCaretXY; CX := Result.X; CY := Result.Y; if (CY < 1) then begin Result.X := 1; Result.Y := 1; exit; end; i := FTheLinesView.Count; if (CY > i) or ((CY = i) and (CX > length(FTheLinesView[i-1]))) then begin Result.Y := i; Result.X := length(FTheLinesView[Result.Y-1]) + 1; exit; end; Line := FTheLinesView[CY - 1]; InclCurrent := False; LineLen := Length(Line); if CX > LineLen then begin Line := FTheLinesView[CY]; LineLen := Length(Line); Inc(CY); CX := 1; InclCurrent := True; end; case ABoundary of swbWordBegin: begin CX := WordBreaker.NextWordStart(Line, CX, InclCurrent); if (CX <= 0) and not InclCurrent then CX := LineLen + 1; if (CX <= 0) and InclCurrent then CX := 1; end; swbWordEnd: begin CX := WordBreaker.NextWordEnd(Line, CX); if (CX <= 0) then CX := LineLen + 1; end; swbWordSmart: begin NX := WordBreaker.NextWordEnd(Line, CX); if (NX <= 0) then NX := LineLen + 1; CX := WordBreaker.NextWordStart(Line, CX, InclCurrent); if (CX <= 0) and not InclCurrent then CX := LineLen + 1; if (CX <= 0) and InclCurrent then CX := 1; if ((ABoundary=swbWordSmart) and (NX<CX-1)) then // step over 1 char gap CX := NX; end; swbTokenBegin: begin if not ( InclCurrent and ((CX <= 1) or (Line[CX-1] in FWordBreaker.WhiteChars)) and ((CX > LineLen) or not(Line[CX] in FWordBreaker.WhiteChars)) ) then CX := WordBreaker.NextBoundary(Line, CX); if (CX > 0) and (CX <= LineLen) and (Line[CX] in FWordBreaker.WhiteChars) then CX := WordBreaker.NextBoundary(Line, CX); if (CX <= 0) then CX := LineLen + 1; end; swbTokenEnd: begin CX := WordBreaker.NextBoundary(Line, CX); if (CX > 1) and (Line[CX-1] in FWordBreaker.WhiteChars) then CX := WordBreaker.NextBoundary(Line, CX); if (CX <= 0) then CX := LineLen + 1; end; swbCaseChange: begin NX := WordBreaker.NextWordStart(Line, CX, InclCurrent); if (NX <= 0) and not InclCurrent then NX := LineLen + 1; if (NX <= 0) and InclCurrent then NX := 1; ULine := UTF8UpperCase(Line); CWidth := FTheLinesView.GetPhysicalCharWidths(CY - 1); // for utf 8 OX := CX; i := Length(ULine); // skip upper While (CX < NX) and (CX <= i) do begin // check entire next utf-8 char to be equal r := (CX = OX) or (CX <= 1) or (Line[CX-1] <> '_') or ((CX <= i) and (Line[CX] = '_')); j := CX; repeat r := r and (Line[j] = ULine[j]); inc(j); until (j > i) or ((CWidth[j-1] and PCWMask) <> 0); if not r then break; CX := j; end; // skip lowercase ULine := UTF8LowerCase(Line); While (CX < NX) and (CX <= i) do begin // check entire next utf-8 char to be equal r := (CX = OX) or (CX <= 1) or (Line[CX-1] <> '_') or ((CX <= i) and (Line[CX] = '_')); j := CX; repeat r := r and (Line[j] = ULine[j]); inc(j); until (j > i) or ((CWidth[j-1] and PCWMask) <> 0); if not r then break; CX := j; end; end; end; Result := Point(CX, CY); end; function TCustomSynEdit.PrevWordLogicalPos(ABoundary: TLazSynWordBoundary): TPoint; procedure CheckLineStart(var CX, CY: integer); var Line: String; begin if CX <= 0 then if CY > 1 then begin // just position at the end of the previous line // Todo skip spaces Dec(CY); Line := FTheLinesView[CY - 1]; CX := Length(Line) + 1; end else CX := 1; end; var i, j, CX, CY, NX, OX: integer; Line, ULine: string; CWidth: TPhysicalCharWidths; r: Boolean; begin Result := LogicalCaretXY; CX := Result.X; CY := Result.Y; if (CY < 1) then begin Result.X := 1; Result.Y := 1; exit; end; if (CY > FTheLinesView.Count) then begin Result.Y := FTheLinesView.Count; Result.X := length(FTheLinesView[Result.Y-1]) + 1; exit; end; Line := FTheLinesView[CY - 1]; case ABoundary of swbWordBegin: begin CX := WordBreaker.PrevWordStart(Line, Min(CX, Length(Line) + 1)); CheckLineStart(CX, CY); end; swbWordEnd: begin CX := WordBreaker.PrevWordEnd(Line, Min(CX, Length(Line) + 1)); CheckLineStart(CX, CY); end; swbWordSmart: begin Dec(CX); // step over 1 char gap if WordBreaker.IsAtWordStart(Line, CX) then NX := CX else NX := WordBreaker.PrevWordStart(Line, Min(CX, Length(Line) + 1)); CX := WordBreaker.PrevWordEnd(Line, Min(CX, Length(Line) + 1)); if (NX>CX-1) then // select the nearest CX := NX; CheckLineStart(CX, CY); end; swbTokenBegin: begin CX := WordBreaker.PrevBoundary(Line, Min(CX, Length(Line) + 1)); if (CX > 0) and (Line[CX] in FWordBreaker.WhiteChars) then CX := WordBreaker.PrevBoundary(Line, Min(CX, Length(Line) + 1)); if CX = 1 then CX := -1; CheckLineStart(CX, CY); end; swbTokenEnd: begin CX := WordBreaker.PrevBoundary(Line, Min(CX, Length(Line) + 1)); if (CX > 1) and (Line[CX-1] in FWordBreaker.WhiteChars) then CX := WordBreaker.PrevBoundary(Line, Min(CX, Length(Line) + 1)); if CX = 1 then CX := -1; CheckLineStart(CX, CY); end; swbCaseChange: begin NX := WordBreaker.PrevWordStart(Line, Min(CX, Length(Line) + 1)); ULine := UTF8LowerCase(Line); CWidth := FTheLinesView.GetPhysicalCharWidths(CY - 1); // for utf 8 OX := CX; i := Length(ULine); if CX > i + 1 then CX := i + 1; // skip lowercase While (CX > NX) and (CX - 1 > 0) do begin // check entire previous utf-8 char to be equal r := (CX = OX) or (Line[CX - 1] <> '_') or ((CX <= i) and (Line[CX] = '_')); j := CX; repeat dec(j); r := r and (Line[j] = ULine[j]); until (j < 1) or ((CWidth[j-1] and PCWMask) <> 0); if not r then break; CX := j; end; // skip upper While (CX > NX) and (CX - 1 > 0) do begin // check entire previous utf-8 char to be not equal j := CX; r := true; repeat dec(j); r := r and (Line[j] = ULine[j]); until (j < 1) or ((CWidth[j-1] and PCWMask) <> 0); r := r or not( (CX = OX) or (Line[CX - 1] <> '_') or ((CX <= i) and (Line[CX] = '_')) ); if r then break; CX := j; end; if (CX - 1 < 1) then CX := NX; CheckLineStart(CX, CY); end; end; Result := Point(CX, CY); end; procedure TCustomSynEdit.EraseBackground(DC: HDC); begin // we are painting everything ourselves, so not need to erase background end; procedure TCustomSynEdit.Invalidate; begin {$IFDEF VerboseSynEditInvalidate} DebugLn(['TCustomSynEdit.Invalidate ',DbgSName(self)]); {$ENDIF} inherited Invalidate; end; function TCustomSynEdit.PluginCount: Integer; begin Result := fPlugins.Count; end; function TCustomSynEdit.MarkupCount: Integer; begin Result := FMarkupManager.Count; end; procedure TCustomSynEdit.SetCaretTypeSize(AType: TSynCaretType; AWidth, AHeight, AXOffs, AYOffs: Integer); begin FScreenCaret.SetCaretTypeSize(AType, AWidth, AHeight, AXOffs, AYOffs); end; procedure TCustomSynEdit.UpdateCursorOverride; var c: TCursor; begin c := crDefault; TSynQueryMouseCursorList(FQueryMouseCursorList).CallScrollEventHandlers(Self, FLastMouseLocation, c); FOverrideCursor := c; end; procedure TCustomSynEdit.PasteFromClipboard(AForceColumnMode: Boolean); var ClipHelper: TSynClipboardStream; begin ClipHelper := TSynClipboardStream.Create; try ClipHelper.ReadFromClipboard(Clipboard); PasteFromClipboardEx(ClipHelper, AForceColumnMode); finally ClipHelper.Free; end; end; function TCustomSynEdit.PasteFromClipboardEx(ClipHelper: TSynClipboardStream; AForceColumnMode: Boolean): Boolean; var PTxt: PChar; PStr: String; PMode: TSynSelectionMode; InsStart: TPoint; PasteAction: TSynCopyPasteAction; begin Result := False; InternalBeginUndoBlock; try PTxt := ClipHelper.TextP; if AForceColumnMode then PMode := smColumn else PMode := ClipHelper.SelectionMode; PasteAction := scaContinue; if assigned(FOnPaste) then begin if ClipHelper.IsPlainText then PasteAction := scaPlainText; InsStart := FCaret.LineBytePos; if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then InsStart := FBlockSelection.FirstLineBytePos; PStr := PTxt; FOnPaste(self, PStr, PMode, InsStart, PasteAction); PTxt := PChar(PStr); if (PStr = '') or (PasteAction = scaAbort) then exit; end; if ClipHelper.TextP = nil then exit; Result := True; if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then FBlockSelection.SelText := ''; InsStart := FCaret.LineBytePos; FInternalBlockSelection.StartLineBytePos := InsStart; FInternalBlockSelection.SetSelTextPrimitive(PMode, PTxt); FCaret.LineBytePos := FInternalBlockSelection.StartLineBytePos; if PasteAction = scaPlainText then exit; if eoFoldedCopyPaste in fOptions2 then begin PTxt := ClipHelper.GetTagPointer(synClipTagFold); if PTxt <> nil then begin ScanRanges; FFoldedLinesView.ApplyFoldDescription(InsStart.Y -1, InsStart.X, FInternalBlockSelection.StartLinePos-1, FInternalBlockSelection.StartBytePos, PTxt, ClipHelper.GetTagLen(synClipTagFold)); end; end; finally InternalEndUndoBlock; end; end; procedure TCustomSynEdit.SelectAll; var LastPt: TPoint; begin DoIncPaintLock(Self); // No editing is taking place LastPt := Point(1, FTheLinesView.Count); if LastPt.y > 0 then Inc(LastPt.x, Length(FTheLinesView[LastPt.y - 1])) else LastPt.y := 1; SetCaretAndSelection(LogicalToPhysicalPos(LastPt), Point(1, 1), LastPt); FBlockSelection.ActiveSelectionMode := smNormal; if eoNoScrollOnSelectRange in FOptions2 then Include(fStateFlags, sfPreventScrollAfterSelect); DoDecPaintLock(Self); end; procedure TCustomSynEdit.SetHighlightSearch(const ASearch: String; AOptions: TSynSearchOptions); begin fMarkupHighAll.SearchOptions := AOptions; fMarkupHighAll.SearchString := ASearch; end; procedure TCustomSynEdit.SelectToBrace; begin DoIncPaintLock(Self); // No editing is taking place FindMatchingBracket(CaretXY,true,true,true,false); if eoNoScrollOnSelectRange in FOptions2 then Include(fStateFlags, sfPreventScrollAfterSelect); DoDecPaintLock(Self); end; procedure TCustomSynEdit.SelectWord; begin SetWordBlock(LogicalCaretXY); end; procedure TCustomSynEdit.SelectLine(WithLeadSpaces: Boolean = True); begin SetLineBlock(CaretXY, WithLeadSpaces); end; procedure TCustomSynEdit.SelectParagraph; begin SetParagraphBlock(CaretXY); end; procedure TCustomSynEdit.Clear; begin FTheLinesView.Clear; end; procedure TCustomSynEdit.Append(const Value: String); begin FTheLinesView.Append(Value); end; function TCustomSynEdit.HasText(AFlags: TSynEditHasTextFlags): Boolean; begin if shtIncludeVirtual in AFlags then Result := (FTheLinesView.Count > 1) or ( (FTheLinesView.Count = 1) and ((FTheLinesView[0] <> '')) ) else Result := (FLines.Count > 1) or ( (FLines.Count = 1) and ((FLines[0] <> '')) ); end; procedure TCustomSynEdit.DoBlockSelectionChanged(Sender : TObject); begin StatusChanged([scSelection]); if HandleAllocated and Focused then SelAvailChange(nil); end; procedure TCustomSynEdit.SetBlockBegin(Value: TPoint); // logical position (byte) begin fBlockSelection.StartLineBytePos := Value; end; procedure TCustomSynEdit.SetBlockEnd(Value: TPoint); // logical position (byte) begin fBlockSelection.EndLineBytePos := Value; end; procedure TCustomSynEdit.SetBlockIndent(const AValue: integer); begin if fBlockIndent=AValue then exit; fBlockIndent:=AValue; end; function TCustomSynEdit.GetCaretX : Integer; begin Result:= FCaret.CharPos; end; function TCustomSynEdit.GetCaretY : Integer; begin Result:= FCaret.LinePos; end; function TCustomSynEdit.GetCaretUndo: TSynEditUndoItem; begin if SelAvail then Result := TSynEditUndoSelCaret.Create(FCaret.LineCharPos, FBlockSelection.StartLineBytePos, FBlockSelection.EndLineBytePos, FBlockSelection.ActiveSelectionMode, FBlockSelection.ForceSingleLineSelected) else Result := TSynEditUndoCaret.Create(FCaret.LineCharPos); end; function TCustomSynEdit.GetMarkup(Index: integer): TSynEditMarkup; begin Result := fMarkupManager.Markup[Index]; end; procedure TCustomSynEdit.SetCaretX(const Value: Integer); begin FCaret.ChangeOnTouch; // setting the caret always clears selection (even setting to current pos / no change) FCaret.CharPos := Value; end; procedure TCustomSynEdit.SetCaretY(const Value: Integer); begin FCaret.ChangeOnTouch; // setting the caret always clears selection (even setting to current pos / no change) FCaret.LinePos := Value; end; function TCustomSynEdit.GetCaretXY: TPoint; begin Result := FCaret.LineCharPos; end; function TCustomSynEdit.GetFoldedCodeColor: TSynSelectedColor; begin Result := FFoldedLinesView.MarkupInfoFoldedCode; end; function TCustomSynEdit.GetLines: TStrings; begin Result := FStrings; end; procedure TCustomSynEdit.SetCaretXY(Value: TPoint); // physical position (screen) begin FCaret.ChangeOnTouch; // setting the caret always clears selection (even setting to current pos / no change) FCaret.LineCharPos:= Value; end; procedure TCustomSynEdit.CaretChanged(Sender: TObject); begin Include(fStateFlags, sfCaretChanged); fStateFlags := fStateFlags - [sfExplicitTopLine, sfExplicitLeftChar]; if FCaret.OldCharPos <> FCaret.CharPos then Include(fStatusChanges, scCaretX); if FCaret.OldLinePos <> FCaret.LinePos then Include(fStatusChanges, scCaretY); if (FCaret.OldViewedLineCharPos.Y < 0) or (FCaret.OldViewedLineCharPos.Y <> FCaret.ViewedLineCharPos.y) then begin InvalidateGutterLines(FCaret.OldLinePos, FCaret.OldLinePos); InvalidateGutterLines(FCaret.LinePos, FCaret.LinePos); end; EnsureCursorPosVisible; if fPaintLock = 0 then fMarkupHighCaret.CheckState; // Todo need a global lock, including the markup end; function TCustomSynEdit.CurrentMaxLeftChar(AIncludeCharsInWin: Boolean ): Integer; begin if WaitingForInitialSize then // don't know chars in window yet exit(MaxInt); Result := FTheLinesView.LengthOfLongestLine + 1; if (eoScrollPastEolAddPage in Options2) then Result := Result + CharsInWindow - 1 - FScreenCaret.ExtraLineChars; if (eoScrollPastEol in Options) and (Result < fMaxLeftChar) then Result := fMaxLeftChar; if (eoScrollPastEolAutoCaret in Options2) and (Result < FCaret.CharPos) then Result := FCaret.CharPos; if AIncludeCharsInWin then Result := Result + FScreenCaret.ExtraLineChars else Result := Result - CharsInWindow + FScreenCaret.ExtraLineChars; if Result < 1 then Result := 1; end; function TCustomSynEdit.CurrentMaxLineLen: Integer; // called by TSynEditCaret begin if WaitingForInitialSize then // don't know chars in window yet exit(MaxInt); if (eoScrollPastEolAutoCaret in Options2) then exit(MaxInt); Result := FTheLinesView.LengthOfLongestLine + 1; if (eoScrollPastEolAddPage in Options2) then Result := Result + CharsInWindow - 1 - FScreenCaret.ExtraLineChars; if (eoScrollPastEol in Options) and (Result < fMaxLeftChar + 1) then Result := fMaxLeftChar; if Result < 1 then Result := 1; end; procedure TCustomSynEdit.SetLeftChar(Value: Integer); begin //{BUG21996} DebugLn(['TCustomSynEdit.SetLeftChar=',Value,' Caret=',dbgs(CaretXY),', BlockBegin=',dbgs(BlockBegin),' BlockEnd=',dbgs(BlockEnd), ' StateFlags=',dbgs(fStateFlags), ' paintlock', FPaintLock]); Value := Min(Value, CurrentMaxLeftChar); Value := Max(Value, 1); if WaitingForInitialSize then Include(fStateFlags, sfExplicitLeftChar); if Value <> FTextArea.LeftChar then begin FTextArea.LeftChar := Value; UpdateScrollBars; InvalidateLines(-1, -1); StatusChanged([scLeftChar]); end; end; procedure TCustomSynEdit.SetLines(Value: TStrings); begin if HandleAllocated then FStrings.Assign(Value); end; function TCustomSynEdit.GetMarkupMgr: TObject; begin Result := fMarkupManager; end; function TCustomSynEdit.GetCaretObj: TSynEditCaret; begin Result := FCaret; end; procedure TCustomSynEdit.SetLineText(Value: string); begin FCaret.LineText := Value; end; procedure TCustomSynEdit.SetName(const Value: TComponentName); var TextToName: boolean; begin TextToName := (ComponentState * [csDesigning, csLoading] = [csDesigning]) and (TrimRight(Text) = Name); inherited SetName(Value); if TextToName then Text := Value; end; function TCustomSynEdit.WaitingForInitialSize: boolean; begin Result := (sfAfterHandleCreatedNeeded in fStateFlags) or AutoSizeDelayed or (not HandleAllocated); end; procedure TCustomSynEdit.DoHandleInitialSizeFinished; begin Exclude(fStateFlags, sfAfterHandleCreatedNeeded); Application.RemoveOnIdleHandler(@IdleScanRanges); fStateFlags := fStateFlags - [sfHorizScrollbarVisible, sfVertScrollbarVisible]; UpdateScrollBars; // just set sfScrollbarChanged inc(FDoingResizeLock); try FLeftGutter.RecalcBounds; FRightGutter.RecalcBounds; finally dec(FDoingResizeLock); if sfGutterResized in fStateFlags then begin Exclude(fStateFlags, sfGutterResized); RecalcCharsAndLinesInWin(False); UpdateScrollBars; end else if sfScrollbarChanged in fStateFlags then UpdateScrollBars; end; end; procedure TCustomSynEdit.DoAllAutoSize; begin if (sfAfterHandleCreatedNeeded in fStateFlags) and (not AutoSizeDelayed) and HandleAllocated then begin DoIncPaintLock(nil); // prevent calculations during inherited and ONLY during inherited try inherited DoAllAutoSize; DoHandleInitialSizeFinished; finally DoDecPaintLock(nil); // run UpdateScrollBars end; end else inherited DoAllAutoSize; end; procedure TCustomSynEdit.CreateHandle; begin if (not AutoSizeDelayed) then begin DoIncPaintLock(nil); // prevent calculations during inherited and ONLY during inherited try inherited CreateHandle; //SizeOrFontChanged will be called DoHandleInitialSizeFinished; finally DoDecPaintLock(nil); // run UpdateScrollBars end; end else begin Include(fStateFlags, sfAfterHandleCreatedNeeded); inherited CreateHandle; //SizeOrFontChanged will be called end; StatusChanged([scHandleCreated]); end; procedure TCustomSynEdit.SetScrollBars(const Value: TScrollStyle); begin if (FScrollBars <> Value) then begin FScrollBars := Value; UpdateScrollBars; Invalidate; end; end; procedure TCustomSynEdit.SetSelTextPrimitive(PasteMode: TSynSelectionMode; Value: PChar; AddToUndoList: Boolean = false); Begin IncPaintLock; if not AddToUndoList then begin fUndoList.Lock; fRedoList.Lock; end; try FBlockSelection.SetSelTextPrimitive(PasteMode, Value); finally if not AddToUndoList then begin fUndoList.Unlock; fRedoList.Unlock; end; DecPaintLock; end; end; procedure TCustomSynEdit.SetSelTextExternal(const Value: string); begin // undo entry added InternalBeginUndoBlock; try FBlockSelection.SelText := Value; finally InternalEndUndoBlock; end; end; procedure TCustomSynEdit.SynSetText(const Value: string); begin IncStatusChangeLock; // can affect width (via gutter) and caret FLines.Text := Value; DecStatusChangeLock; end; procedure TCustomSynEdit.RealSetText(const Value: TCaption); begin IncStatusChangeLock; // can affect width (via gutter) and caret FLines.Text := Value; // Do not trim DecStatusChangeLock; end; function TCustomSynEdit.CurrentMaxTopView: Integer; begin Result := FTheLinesView.ViewedCount; if not(eoScrollPastEof in Options) then Result := Result + 1 - Max(0, LinesInWindow); Result := Max(Result, 1); end; procedure TCustomSynEdit.SetTopLine(Value: Integer); var NewTopView: Integer; begin // TODO : Above hidden line only if folded, if hidden then use below if not FTheLinesView.IsTextIdxVisible(ToIdx(Value)) then Value := FindNextUnfoldedLine(Value, False); if WaitingForInitialSize then Include(fStateFlags, sfExplicitTopLine); NewTopView := ToPos(FTheLinesView.TextToViewIndex(ToIdx(Value))); if NewTopView <> TopView then begin TopView := NewTopView; end; end; procedure TCustomSynEdit.ScrollAfterTopLineChanged; var Delta: Integer; srect: TRect; begin if (sfPainting in fStateFlags) or (fPaintLock <> 0) or WaitingForInitialSize then exit; Delta := FOldTopView - TopView; {$IFDEF SYNSCROLLDEBUG} if (sfHasScrolled in fStateFlags) then debugln(['ScrollAfterTopLineChanged with sfHasScrolled Delta=',Delta,' topline=',TopLine, ' FOldTopView=',FOldTopView ]); {$ENDIF} if Delta <> 0 then begin // TODO: SW_SMOOTHSCROLL --> can't get it work if (Abs(Delta) >= LinesInWindow) or (sfHasScrolled in FStateFlags) then begin {$IFDEF SYNSCROLLDEBUG} debugln(['ScrollAfterTopLineChanged does invalidet Delta=',Delta]); {$ENDIF} Invalidate; end else begin srect := FPaintArea.Bounds; srect.Top := FTextArea.TextBounds.Top; srect.Bottom := FTextArea.TextBounds.Bottom; TSynScrollEventHandlerList(FScrollEventHandlerList).CallScrollEventHandlers(Self, peBeforeScroll, 0, LineHeight * Delta, srect, srect); FScreenCaret.BeginScroll(0, LineHeight * Delta, srect, srect); if ScrollWindowEx(Handle, 0, LineHeight * Delta, @srect, @srect, 0, nil, SW_INVALIDATE) then begin {$IFDEF SYNSCROLLDEBUG} debugln(['ScrollAfterTopLineChanged did scroll Delta=',Delta]); {$ENDIF} include(fStateFlags, sfHasScrolled); Include(fStateFlags, sfCaretChanged); // need to update FScreenCaret.FinishScroll(0, LineHeight * Delta, srect, srect, True); TSynScrollEventHandlerList(FScrollEventHandlerList).CallScrollEventHandlers(Self, peAfterScroll, 0, LineHeight * Delta, srect, srect); end else begin FScreenCaret.FinishScroll(0, LineHeight * Delta, srect, srect, False); TSynScrollEventHandlerList(FScrollEventHandlerList).CallScrollEventHandlers(Self, peAfterScrollFailed, 0, LineHeight * Delta, srect, srect); Invalidate; // scrollwindow failed, invalidate all {$IFDEF SYNSCROLLDEBUG} debugln(['ScrollAfterTopLineChanged does invalidet (scroll failed) Delta=',Delta]); {$ENDIF} end; end; end; FOldTopView := TopView; if (Delta <> 0) and (eoAlwaysVisibleCaret in fOptions2) then MoveCaretToVisibleArea; end; procedure TCustomSynEdit.MoveCaretToVisibleArea; // scroll to make the caret visible var NewCaretXY: TPoint; MaxY: LongInt; begin {$IFDEF SYNDEBUG} if (sfEnsureCursorPos in fStateFlags) then debugln('SynEdit. skip MoveCaretToVisibleArea'); {$ENDIF} if WaitingForInitialSize or (sfEnsureCursorPos in fStateFlags) then exit; NewCaretXY:=CaretXY; if NewCaretXY.X < LeftChar then NewCaretXY.X := LeftChar else if NewCaretXY.X > LeftChar + CharsInWindow - FScreenCaret.ExtraLineChars then NewCaretXY.X := LeftChar + CharsInWindow - FScreenCaret.ExtraLineChars; if NewCaretXY.Y < TopLine then NewCaretXY.Y := TopLine else begin MaxY:= ScreenRowToRow(Max(0,LinesInWindow-1)); if NewCaretXY.Y > MaxY then NewCaretXY.Y := MaxY; end; if CompareCarets(CaretXY,NewCaretXY)<>0 then begin //DebugLn(['TCustomSynEdit.MoveCaretToVisibleArea Old=',dbgs(CaretXY),' New=',dbgs(NewCaretXY)]); FCaret.LineCharPos:=NewCaretXY; end; end; procedure TCustomSynEdit.MoveCaretIgnoreEOL(const NewCaret: TPoint); begin FCaret.IncForcePastEOL; FCaret.LineCharPos := NewCaret; FCaret.DecForcePastEOL; end; procedure TCustomSynEdit.MoveLogicalCaretIgnoreEOL(const NewLogCaret: TPoint); begin MoveCaretIgnoreEOL(LogicalToPhysicalPos(NewLogCaret)); end; procedure TCustomSynEdit.UpdateCaret(IgnorePaintLock: Boolean = False); var p: TPoint; begin if ( (PaintLock <> 0) and not IgnorePaintLock ) or WaitingForInitialSize then begin Include(fStateFlags, sfCaretChanged); end else begin Exclude(fStateFlags, sfCaretChanged); if eoAlwaysVisibleCaret in fOptions2 then MoveCaretToVisibleArea; p := FCaret.ViewedLineCharPos; p.y := p.y - TopView + 1; FScreenCaret.DisplayPos := ScreenXYToPixels(p); end; end; procedure TCustomSynEdit.UpdateScrollBars; var ScrollInfo: TScrollInfo; begin if WaitingForInitialSize or (PaintLock <> 0) or (FDoingResizeLock <> 0) then Include(fStateFlags, sfScrollbarChanged) else begin Exclude(fStateFlags, sfScrollbarChanged); ScrollInfo.cbSize := SizeOf(ScrollInfo); ScrollInfo.fMask := SIF_ALL or SIF_DISABLENOSCROLL and not SIF_TRACKPOS; ScrollInfo.nMin := 1; ScrollInfo.nTrackPos := 0; // Horizontal ScrollInfo.nMax := CurrentMaxLeftChar(True); if ((fScrollBars in [ssBoth, ssHorizontal]) or ((fScrollBars in [ssAutoBoth, ssAutoHorizontal]) and (ScrollInfo.nMax - 1 > CharsInWindow)) ) xor (sfHorizScrollbarVisible in fStateFlags) then begin if (sfHorizScrollbarVisible in fStateFlags) then exclude(fStateFlags, sfHorizScrollbarVisible) else include(fStateFlags, sfHorizScrollbarVisible); if fStateFlags * [sfEnsureCursorPos, sfEnsureCursorPosAtResize] <> [] then include(fStateFlags, sfEnsureCursorPosAtResize); {$IFDEF LCLWin32} // Some bug in Windows? ShowScrollBar will not always remove the scrollbar // This will make the scrollbar un-scrollable, after that it can be hidden if not (sfHorizScrollbarVisible in fStateFlags) then begin ScrollInfo.nMax := 99; ScrollInfo.nPage := ScrollInfo.nMax-1; ScrollInfo.nPos := 1; SetScrollInfo(Handle, SB_HORZ, ScrollInfo, False); end; {$ENDIF} ShowScrollBar(Handle, SB_Horz, sfHorizScrollbarVisible in fStateFlags); RecalcCharsAndLinesInWin(True); end; if (sfHorizScrollbarVisible in fStateFlags) then begin ScrollInfo.nPage := CharsInWindow; ScrollInfo.nPos := LeftChar; SetScrollInfo(Handle, SB_HORZ, ScrollInfo, True); {$IFNDEF LCLWin32} {$IFnDEF SynScrollBarWorkaround} if not (sfHorizScrollbarVisible in fStateFlags) then ShowScrollBar(Handle, SB_Horz, False); {$ENDIF} {$ENDIF} end; //DebugLn('[TCustomSynEdit.UpdateScrollbars] nMin=',ScrollInfo.nMin,' nMax=',ScrollInfo.nMax, //' nPage=',ScrollInfo.nPage,' nPos=',ScrollInfo.nPos,' ClientW=',ClientWidth); // Vertical ScrollInfo.nMax := FTheLinesView.ViewedCount+1; if (eoScrollPastEof in Options) then Inc(ScrollInfo.nMax, LinesInWindow - 1); if ((fScrollBars in [ssBoth, ssVertical]) or ((fScrollBars in [ssAutoBoth, ssAutoVertical]) and (ScrollInfo.nMax - 1 > LinesInWindow)) ) xor (sfVertScrollbarVisible in fStateFlags) then begin if (sfVertScrollbarVisible in fStateFlags) then exclude(fStateFlags, sfVertScrollbarVisible) else include(fStateFlags, sfVertScrollbarVisible); if fStateFlags * [sfEnsureCursorPos, sfEnsureCursorPosAtResize] <> [] then include(fStateFlags, sfEnsureCursorPosAtResize); {$IFDEF LCLWin32} // Some bug in Windows? ShowScrollBar will not always remove the scrollbar // This will make the scrollbar un-scrollable, after that it can be hidden if not (sfVertScrollbarVisible in fStateFlags) then begin ScrollInfo.nMax := 99; ScrollInfo.nPage := ScrollInfo.nMax-1; ScrollInfo.nPos := 1; SetScrollInfo(Handle, SB_Vert, ScrollInfo, False); end; {$ENDIF} ShowScrollBar(Handle, SB_Vert, sfVertScrollbarVisible in fStateFlags); RecalcCharsAndLinesInWin(True); end; if (sfVertScrollbarVisible in fStateFlags) then begin ScrollInfo.nPage := LinesInWindow; ScrollInfo.nPos := TopView; SetScrollInfo(Handle, SB_VERT, ScrollInfo, True); {$IFNDEF LCLWin32} {$IFnDEF SynScrollBarWorkaround} if not (sfVertScrollbarVisible in fStateFlags) then ShowScrollBar(Handle, SB_Vert, False); {$ENDIF} {$ENDIF} end; end; end; procedure TCustomSynEdit.SelAvailChange(Sender: TObject); begin if PaintLock > 0 then begin Include(FStateFlags, sfSelChanged); exit; end; Exclude(FStateFlags, sfSelChanged); if SelAvail then AquirePrimarySelection else SurrenderPrimarySelection; end; procedure TCustomSynEdit.WMDropFiles(var Msg: TMessage); {TODO: DropFiles var i, iNumberDropped: integer; szPathName: array[0..260] of char; Point: TPoint; FilesList: TStringList; } begin LastMouseCaret:=Point(-1,-1); {TODO: DropFiles try if Assigned(fOnDropFiles) then begin FilesList := TStringList.Create; try iNumberDropped := DragQueryFile(TLCLHandle(Msg.wParam), Cardinal(-1), nil, 0); DragQueryPoint(TLCLHandle(Msg.wParam), Point); for i := 0 to iNumberDropped - 1 do begin DragQueryFile(TLCLHandle(Msg.wParam), i, szPathName, SizeOf(szPathName)); FilesList.Add(szPathName); end; fOnDropFiles(Self, Point.X, Point.Y, FilesList); finally FilesList.Free; end; end; finally Msg.Result := 0; DragFinish(TLCLHandle(Msg.wParam)); end;} end; procedure TCustomSynEdit.WMExit(var Message: TLMExit); begin LastMouseCaret:=Point(-1,-1); end; procedure TCustomSynEdit.WMEraseBkgnd(var Msg: TMessage); begin Msg.Result := 1; end; procedure TCustomSynEdit.WMGetDlgCode(var Msg: TWMGetDlgCode); begin inherited; Msg.Result := DLGC_WANTARROWS or DLGC_WANTCHARS or DLGC_WANTALLKEYS; if fWantTabs and (GetKeyState(VK_CONTROL) >= 0) then Msg.Result := Msg.Result or DLGC_WANTTAB; end; procedure TCustomSynEdit.WMHScroll(var Msg: TLMScroll); begin case Msg.ScrollCode of // Scrolls to start / end of the line SB_TOP: LeftChar := 1; SB_BOTTOM: LeftChar := CurrentMaxLeftChar; // Scrolls one char left / right SB_LINEDOWN: LeftChar := LeftChar + 1; SB_LINEUP: LeftChar := LeftChar - 1; // Scrolls one page of chars left / right SB_PAGEDOWN: LeftChar := LeftChar + Max(1, (CharsInWindow - Ord(eoScrollByOneLess in fOptions))); SB_PAGEUP: LeftChar := LeftChar - Max(1, (CharsInWindow - Ord(eoScrollByOneLess in fOptions))); // Scrolls to the current scroll bar position SB_THUMBPOSITION, SB_THUMBTRACK: LeftChar := Msg.Pos; end; end; procedure TCustomSynEdit.WMKillFocus(var Msg: TWMKillFocus); begin if fCaret = nil then exit; // This SynEdit is in Destroy Exclude(FStateFlags, sfHideCursor); Exclude(fStateFlags, sfInClick); {$IFDEF VerboseFocus} DebugLn(['[TCustomSynEdit.WMKillFocus] A ',DbgSName(Self), ' time=', dbgs(Now*86640)]); {$ENDIF} LastMouseCaret:=Point(-1,-1); // Todo: Under Windows, keeping the Caret only works, if no other component creates a caret FScreenCaretPainterClass{%H-} := TSynEditScreenCaretPainterClass(ScreenCaret.Painter.ClassType); UpdateScreenCaret; if HideSelection and SelAvail then Invalidate; if FImeHandler <> nil then FImeHandler.FocusKilled; FMouseActionShiftMask := []; inherited; StatusChanged([scFocus]); end; procedure TCustomSynEdit.WMSetFocus(var Msg: TLMSetFocus); begin if fCaret = nil then exit; // This SynEdit is in Destroy Exclude(FStateFlags, sfHideCursor); LastMouseCaret:=Point(-1,-1); {$IFDEF VerboseFocus} DebugLn(['[TCustomSynEdit.WMSetFocus] A ',DbgSName(Self), ' time=', dbgs(Now*86640)]); {$ENDIF} FScreenCaret.DestroyCaret; // Ensure recreation. On Windows only one caret exists, and it must be moved to the focused editor if ScreenCaret.Painter.ClassType <> FScreenCaretPainterClass{%H-} then ScreenCaret.ChangePainter(FScreenCaretPainterClass{%H-}); if ScreenCaret.Painter.ClassType <> TSynEditScreenCaretPainterSystem then // system painter does not use timer FScreenCaret.PaintTimer.ResetInterval; FScreenCaret.Visible := not(eoNoCaret in FOptions) and IsVisible; //if HideSelection and SelAvail then // Invalidate; FMouseActionShiftMask := []; inherited; //DebugLn('[TCustomSynEdit.WMSetFocus] END'); StatusChanged([scFocus]); end; procedure TCustomSynEdit.DoOnResize; begin inherited; if WaitingForInitialSize then exit; inc(FDoingResizeLock); // prevent UpdateScrollBars; IncStatusChangeLock; // defer status events FScreenCaret.Lock; try FLeftGutter.RecalcBounds; FRightGutter.RecalcBounds; // SizeOrFontChanged will call RecalcCharsAndLinesInWin which may have been skipped in GutterResized SizeOrFontChanged(FALSE); if sfEnsureCursorPosAtResize in fStateFlags then EnsureCursorPosVisible; Exclude(fStateFlags, sfEnsureCursorPosAtResize); finally try FScreenCaret.UnLock; dec(FDoingResizeLock); UpdateScrollBars; finally DecStatusChangeLock; end; end; //debugln('TCustomSynEdit.Resize ',dbgs(Width),',',dbgs(Height),',',dbgs(ClientWidth),',',dbgs(ClientHeight)); // SetLeftChar(LeftChar); //mh 2000-10-19 end; procedure TCustomSynEdit.CalculatePreferredSize(var PreferredWidth, PreferredHeight: integer; WithThemeSpace: Boolean); begin // synedit has no preferred size PreferredWidth:=0; PreferredHeight:=0; end; var ScrollHintWnd: THintWindow; function GetScrollHint: THintWindow; begin if ScrollHintWnd = nil then begin ScrollHintWnd := HintWindowClass.Create(Application); ScrollHintWnd.Name:='SynEditScrollHintWnd'; ScrollHintWnd.Visible := FALSE; end; Result := ScrollHintWnd; Result.AutoHide := True; // Because SB_ENDSCROLL never happens under LCL-GTK2 Result.HideInterval := 1500; end; procedure TCustomSynEdit.WMVScroll(var Msg: TLMScroll); var s: ShortString; rc: TRect; pt: TPoint; ScrollHint: THintWindow; begin {$IFDEF SYNSCROLLDEBUG} debugln('TCustomSynEdit.WMVScroll A ',DbgSName(Self),' Msg.ScrollCode=',dbgs(Msg.ScrollCode),' SB_PAGEDOWN=',dbgs(SB_PAGEDOWN),' SB_PAGEUP=',dbgs(SB_PAGEUP)); {$ENDIF} case Msg.ScrollCode of // Scrolls to start / end of the text SB_TOP: TopView := 1; SB_BOTTOM: TopView := FTheLinesView.ViewedCount; // Scrolls one line up / down SB_LINEDOWN: TopView := TopView + 1; SB_LINEUP: TopView := TopView - 1; // Scrolls one page of lines up / down SB_PAGEDOWN: TopView := TopView + Max(1, (LinesInWindow - Ord(eoScrollByOneLess in fOptions))); // TODO: scroll half page ? SB_PAGEUP: TopView := TopView - Max(1, (LinesInWindow - Ord(eoScrollByOneLess in fOptions))); // Scrolls to the current scroll bar position SB_THUMBPOSITION, SB_THUMBTRACK: begin TopView := Msg.Pos; if eoShowScrollHint in fOptions then begin ScrollHint := GetScrollHint; if not ScrollHint.Visible then begin ScrollHint.Color := Application.HintColor; ScrollHint.Visible := TRUE; end; s := Format('line %d', [TopLine]); rc := ScrollHint.CalcHintRect(200, s, Nil); pt := ClientToScreen(Point(ClientWidth-ScrollBarWidth - rc.Right - 4, 10)); if eoScrollHintFollows in fOptions then pt.y := Mouse.CursorPos.y - (rc.Bottom div 2); Types.OffsetRect(rc, pt.x, pt.y); ScrollHint.ActivateWithBounds(rc, s); ScrollHint.Invalidate; ScrollHint.Update; end; end; // Ends scrolling SB_ENDSCROLL: if eoShowScrollHint in fOptions then with GetScrollHint do begin Visible := FALSE; ActivateWithBounds(Rect(0, 0, 0, 0), ''); end; end; end; procedure TCustomSynEdit.CMWantSpecialKey(var Message: TLMessage); begin if (Message.wParam = VK_TAB) then begin if WantTabs then Message.Result := 1 else Message.Result := 0; end else inherited; end; procedure TCustomSynEdit.UpdateScreenCaret; begin if not HandleAllocated then exit; if (not IsVisible) or ( (not Focused) and (not (eoPersistentCaret in fOptions)) ) or (eoNoCaret in FOptions) then begin FScreenCaret.Hide; FScreenCaret.DestroyCaret; exit; end; if Focused then begin // everything else is done in WM_SETFOCUS; FScreenCaret.Visible := True; end else begin // eoPersistentCaret is set if ScreenCaret.Painter.ClassType <> TSynEditScreenCaretPainterInternal then ScreenCaret.ChangePainter(TSynEditScreenCaretPainterInternal); if (eoPersistentCaretStopBlink in FOptions2) then FScreenCaret.PaintTimer.Interval := 0 else ScreenCaret.PaintTimer.ResetInterval; FScreenCaret.Visible := True; end; end; procedure TCustomSynEdit.ScanRanges(ATextChanged: Boolean = True); begin if WaitingForInitialSize then begin Application.RemoveOnIdleHandler(@IdleScanRanges); // avoid duplicate add if assigned(FHighlighter) then Application.AddOnIdleHandler(@IdleScanRanges, False); exit; end; //TODO: exit if in paintlock ??? if not assigned(FHighlighter) then begin if ATextChanged then begin fMarkupManager.TextChanged(FChangedLinesStart, FChangedLinesEnd, FChangedLinesDiff); // TODO: see TSynEditFoldedView.LineCountChanged, this is only needed, because NeedFixFrom does not always work FFoldedLinesView.FixFoldingAtTextIndex(FChangedLinesStart, FChangedLinesEnd); end; exit; end; FHighlighter.CurrentLines := FLines; // Trailing spaces are not needed FHighlighter.ScanRanges; // Todo: text may not have changed if ATextChanged then fMarkupManager.TextChanged(FChangedLinesStart, FChangedLinesEnd, FChangedLinesDiff); end; procedure TCustomSynEdit.IdleScanRanges(Sender: TObject; var Done: Boolean); begin Application.RemoveOnIdleHandler(@IdleScanRanges); if not assigned(FHighlighter) then exit; FHighlighter.CurrentLines := FLines; // Trailing spaces are not needed if not FHighlighter.IdleScanRanges{%H-} then exit; // Move to the end; give others a change too Application.AddOnIdleHandler(@IdleScanRanges, False); Done := False; end; procedure TCustomSynEdit.LineCountChanged(Sender: TSynEditStrings; AIndex, ACount: Integer); begin {$IFDEF SynFoldDebug}debugln(['FOLD-- LineCountChanged Aindex', AIndex, ' ACount=', ACount]);{$ENDIF} FBlockSelection.StickyAutoExtend := False; if (AIndex < FBeautifyStartLineIdx) or (FBeautifyStartLineIdx < 0) then FBeautifyStartLineIdx := AIndex; if ACount > 0 then begin if (AIndex > FBeautifyEndLineIdx) then FBeautifyEndLineIdx := AIndex + ACount - 1 else FBeautifyEndLineIdx := FBeautifyEndLineIdx + ACount; end else begin FBeautifyEndLineIdx := FBeautifyEndLineIdx + ACount; if (FBeautifyEndLineIdx < AIndex) then FBeautifyEndLineIdx := AIndex; end; if PaintLock>0 then begin // FChangedLinesStart is also given to Markup.TextChanged; but it is not used there if (FChangedLinesStart<1) or (FChangedLinesStart>AIndex+1) then FChangedLinesStart:=AIndex+1; FChangedLinesEnd := -1; // Invalidate the rest of lines FChangedLinesDiff := FChangedLinesDiff + ACount; end else begin ScanRanges; InvalidateLines(AIndex + 1, -1); InvalidateGutterLines(AIndex + 1, -1); if FCaret.LinePos > FLines.Count then FCaret.LinePos := FLines.Count; end; if TopLine > AIndex + 1 then TopLine := TopLine + ACount // will call UpdateScrollBars else UpdateScrollBars; end; procedure TCustomSynEdit.LineTextChanged(Sender: TSynEditStrings; AIndex, ACount: Integer); begin {$IFDEF SynFoldDebug}debugln(['FOLD-- LineTextChanged Aindex', AIndex, ' ACount=', ACount]);{$ENDIF} FBlockSelection.StickyAutoExtend := False; if (AIndex < FBeautifyStartLineIdx) or (FBeautifyStartLineIdx < 0) then FBeautifyStartLineIdx := AIndex; if (AIndex + ACount - 1 > FBeautifyEndLineIdx) then FBeautifyEndLineIdx := AIndex + ACount - 1; if PaintLock>0 then begin if (FChangedLinesStart<1) or (FChangedLinesStart>AIndex+1) then FChangedLinesStart:=AIndex+1; if (FChangedLinesEnd >= 0) and (FChangedLinesEnd<AIndex+1) then FChangedLinesEnd:=AIndex + 1 + MaX(ACount, 0); // TODO: why 2 (TWO) extra lines? end else begin ScanRanges; InvalidateLines(AIndex + 1, AIndex + ACount); InvalidateGutterLines(AIndex + 1, AIndex + ACount); end; UpdateScrollBars; end; procedure TCustomSynEdit.DoHighlightChanged(Sender: TSynEditStrings; AIndex, ACount: Integer); begin InvalidateLines(AIndex + 1, AIndex + 1 + ACount); InvalidateGutterLines(AIndex + 1, AIndex + 1 + ACount); FFoldedLinesView.FixFoldingAtTextIndex(AIndex, AIndex + ACount); if FPendingFoldState <> '' then SetFoldState(FPendingFoldState); end; procedure TCustomSynEdit.ListCleared(Sender: TObject); begin ClearUndo; // invalidate the *whole* client area Invalidate; // set caret and selected block to start of text SetBlockBegin(Point(1, 1)); SetCaretXY(Point(1, 1)); // scroll to start of text TopView := 1; LeftChar := 1; StatusChanged(scTextCleared); end; procedure TCustomSynEdit.FoldChanged(Sender: TSynEditStrings; aIndex, aCount: Integer); var i: Integer; begin {$IFDEF SynFoldDebug}debugln(['FOLD-- FoldChanged; Index=', aIndex, ' TopView=', TopView, ' ScreenRowToRow(LinesInWindow + 1)=', ScreenRowToRow(LinesInWindow + 1)]);{$ENDIF} TopView := TopView; if (not FTheLinesView.IsTextIdxVisible(ToIdx(CaretY))) and (FTheLinesView.ViewedCount > 0) then begin i := Max(0, FTheLinesView.TextToViewIndex(ToIdx(CaretY))); i := ToPos(FTheLinesView.ViewToTextIndex(i)); // unfolded line, above the fold SetCaretXY(Point(1, i)); UpdateCaret; end else if eoAlwaysVisibleCaret in fOptions2 then MoveCaretToVisibleArea; UpdateScrollBars; if aIndex + 1 > Max(1, ScreenRowToRow(LinesInWindow + 1)) then exit; if aIndex + 1 < TopLine then aIndex := TopLine - 1; InvalidateLines(aIndex + 1, -1); InvalidateGutterLines(aIndex + 1, -1); end; procedure TCustomSynEdit.SetTopView(AValue : Integer); begin // don't use MinMax here, it will fail in design mode (Lines.Count is zero, // but the painting code relies on TopLine >= 1) {$IFDEF SYNSCROLLDEBUG} if (fPaintLock = 0) and (not FIsInDecPaintLock) then debugln(['SetTopView outside Paintlock New=',AValue, ' Old=', FFoldedLinesView.TopLine]); if (sfHasScrolled in fStateFlags) then debugln(['SetTopView with sfHasScrolled Value=',AValue, ' FOldTopView=',FOldTopView ]); {$ENDIF} TSynEditStringList(FLines).SendCachedNotify; // TODO: review AValue := Min(AValue, CurrentMaxTopView); AValue := Max(AValue, 1); if WaitingForInitialSize then Include(fStateFlags, sfExplicitTopLine); (* ToDo: FFoldedLinesView.TopLine := AValue; Required, if "TopView := TopView" or "TopLine := TopLine" is called, after ScanRanges (used to be: LineCountChanged / LineTextChanged) *) FFoldedLinesView.TopViewPos := AValue; if FTextArea.TopLine <> AValue then begin if FPaintLock = 0 then FOldTopView := TopView; FTextArea.TopLine := AValue; UpdateScrollBars; // call MarkupMgr before ScrollAfterTopLineChanged, in case we aren't in a PaintLock fMarkupManager.TopLine := TopLine; if (sfPainting in fStateFlags) then debugln('SetTopline inside paint'); ScrollAfterTopLineChanged; StatusChanged([scTopLine]); end else fMarkupManager.TopLine := TopLine; {$IFDEF SYNSCROLLDEBUG} if (fPaintLock = 0) and (not FIsInDecPaintLock) then debugln('SetTopline outside Paintlock EXIT'); {$ENDIF} end; function TCustomSynEdit.GetTopView : Integer; begin Result := FTextArea.TopLine; end; procedure TCustomSynEdit.SetWordBlock(Value: TPoint); var TempString: string; x: Integer; begin { Value is the position of the Caret in bytes } Value.y := MinMax(Value.y, 1, FTheLinesView.Count); TempString := FTheLinesView[Value.Y - 1]; if TempString = '' then exit; x := MinMax(Value.x, 1, Length(TempString)+1); Value.X := WordBreaker.PrevWordStart(TempString, x, True); if Value.X < 0 then Value.X := WordBreaker.NextWordStart(TempString, x); if Value.X < 0 then exit; DoIncPaintLock(Self); // No editing is taking place FBlockSelection.StartLineBytePos := Value; Value.X := WordBreaker.NextWordEnd(TempString, Value.X); FBlockSelection.EndLineBytePos := Value; FBlockSelection.ActiveSelectionMode := smNormal; FCaret.LineBytePos := Value; DoDecPaintLock(Self); end; procedure TCustomSynEdit.SetLineBlock(Value: TPoint; WithLeadSpaces: Boolean = True); var ALine: string; x, x2: Integer; begin DoIncPaintLock(Self); // No editing is taking place FBlockSelection.StartLineBytePos := Point(1,MinMax(Value.y, 1, FTheLinesView.Count)); FBlockSelection.EndLineBytePos := Point(1,MinMax(Value.y+1, 1, FTheLinesView.Count)); if (FBlockSelection.StartLinePos >= 1) and (FBlockSelection.StartLinePos <= FTheLinesView.Count) then begin ALine:=FTheLinesView[FBlockSelection.StartLinePos - 1]; x2:=length(ALine)+1; if not WithLeadSpaces then begin x := CountLeadWhiteSpace(PChar(ALine)) + 1; FBlockSelection.StartLineBytePos := Point(x,MinMax(Value.y, 1, FTheLinesView.Count)); while (x2 > x) and (ALine[X2-1] in [' ',#9]) do dec(x2); end; FBlockSelection.EndLineBytePos := Point(x2, MinMax(Value.y, 1, FTheLinesView.Count)); end; FBlockSelection.ActiveSelectionMode := smNormal; LogicalCaretXY := FBlockSelection.EndLineBytePos; //DebugLn(' FFF2 ',Value.X,',',Value.Y,' BlockBegin=',BlockBegin.X,',',BlockBegin.Y,' BlockEnd=',BlockEnd.X,',',BlockEnd.Y); DoDecPaintLock(Self); end; procedure TCustomSynEdit.SetParagraphBlock(Value: TPoint); var ParagraphStartLine, ParagraphEndLine, ParagraphEndX: integer; begin DoIncPaintLock(Self); // No editing is taking place ParagraphStartLine := MinMax(Value.y, 1, FTheLinesView.Count); ParagraphEndLine := MinMax(Value.y+1, 1, FTheLinesView.Count); ParagraphEndX := 1; while (ParagraphStartLine > 1) and (Trim(FTheLinesView[ParagraphStartLine-1])<>'') do dec(ParagraphStartLine); while (ParagraphEndLine <= FTheLinesView.Count) and (Trim(FTheLinesView[ParagraphEndLine-1])<>'') do inc(ParagraphEndLine); if (ParagraphEndLine > FTheLinesView.Count) then begin dec(ParagraphEndLine); ParagraphEndX := length(FTheLinesView[ParagraphEndLine-1]) + 1; end; FBlockSelection.StartLineBytePos := Point(1, ParagraphStartLine); FBlockSelection.EndLineBytePos := Point(ParagraphEndX, ParagraphEndLine); FBlockSelection.ActiveSelectionMode := smNormal; CaretXY := FBlockSelection.EndLineBytePos; //DebugLn(' FFF3 ',Value.X,',',Value.Y,' BlockBegin=',BlockBegin.X,',',BlockBegin.Y,' BlockEnd=',BlockEnd.X,',',BlockEnd.Y); if eoNoScrollOnSelectRange in FOptions2 then Include(fStateFlags, sfPreventScrollAfterSelect); DoDecPaintLock(Self); end; function TCustomSynEdit.GetCanUndo: Boolean; begin result := fUndoList.CanUndo; end; function TCustomSynEdit.GetCanRedo: Boolean; begin result := fRedoList.CanUndo; end; function TCustomSynEdit.GetCanPaste:Boolean; begin Result := (Clipboard.HasFormat(CF_TEXT) or Clipboard.HasFormat(TSynClipboardStream.ClipboardFormatId) ) end; procedure TCustomSynEdit.Redo; var Item: TSynEditUndoItem; Group: TSynEditUndoGroup; begin Group := fRedoList.PopItem; if Group <> nil then begin; {$IFDEF SynUndoDebugCalls} DebugLnEnter(['>> TCustomSynEdit.Redo ',DbgSName(self), ' ', dbgs(Self), ' Group', dbgs(Group), ' cnt=', Group.Count]); {$ENDIF} IncPaintLock; FTheLinesView.IsRedoing := True; Item := Group.Pop; if Item <> nil then begin InternalBeginUndoBlock; fUndoList.CurrentGroup.Reason := Group.Reason; fUndoList.IsInsideRedo := True; try repeat RedoItem(Item); Item := Group.Pop; until (Item = nil); finally InternalEndUndoBlock; end; end; FTheLinesView.IsRedoing := False; Group.Free; if fRedoList.IsTopMarkedAsUnmodified then fUndoList.MarkTopAsUnmodified; DecPaintLock; {$IFDEF SynUndoDebugCalls} DebugLnExit(['<< TCustomSynEdit.Redo ',DbgSName(self), ' ', dbgs(Self)]); end else begin DebugLn(['<< TCustomSynEdit.Redo - NO GROUP ',DbgSName(self), ' ', dbgs(Self)]); {$ENDIF} end; end; procedure TCustomSynEdit.RedoItem(Item: TSynEditUndoItem); var Line, StrToDelete: PChar; x, y, Len, Len2: integer; begin if Assigned(Item) then try FCaret.IncForcePastEOL; if Item.ClassType = TSynEditUndoIndent then begin // re-insert the column SetCaretAndSelection(LogicalToPhysicalPos(Point(1,TSynEditUndoIndent(Item).FPosY1)), Point(1, TSynEditUndoIndent(Item).FPosY1), Point(2, TSynEditUndoIndent(Item).FPosY2), smNormal); x := FBlockIndent; y := FBlockTabIndent; FBlockIndent := TSynEditUndoIndent(Item).FCnt; FBlockTabIndent := TSynEditUndoIndent(Item).FTabCnt; DoBlockIndent; FBlockIndent := x; FBlockTabIndent := y; end else if Item.ClassType = TSynEditUndoUnIndent then begin // re-delete the (raggered) column // add to undo list fUndoList.AddChange(TSynEditUndoUnIndent.Create(TSynEditUndoUnIndent(Item).FPosY1, TSynEditUndoUnIndent(Item).FPosY2, TSynEditUndoUnIndent(Item).FText)); // Delete string fUndoList.Lock; StrToDelete := PChar(TSynEditUndoUnIndent(Item).FText); x := -1; for y := TSynEditUndoUnIndent(Item).FPosY1 to TSynEditUndoUnIndent(Item).FPosY2 do begin Line := PChar(FTheLinesView[y - 1]); Len := CountLeadWhiteSpace(Line); Len2 := GetEOL(StrToDelete) - StrToDelete; if (Len2 > 0) and (Len >= Len2) then FTheLinesView.EditDelete(1+Len-Len2, y, Len2); inc(StrToDelete, Len2+1); end; fUndoList.Unlock; end else if not Item.PerformUndo(self) then if not FUndoRedoItemHandlerList.CallUndoRedoItemHandlers(Self, Item) then FTheLinesView.EditRedo(Item); finally FCaret.DecForcePastEOL; Item.Free; end; end; procedure TCustomSynEdit.UpdateCursor; begin if (sfHideCursor in FStateFlags) and (eoAutoHideCursor in fOptions2) then begin inherited Cursor := crNone; exit; end; if (FOverrideCursor <> crDefault) then inherited Cursor := FOverrideCursor else if (FLastMouseLocation.LastMousePoint.X >= FTextArea.Bounds.Left) and (FLastMouseLocation.LastMousePoint.X < FTextArea.Bounds.Right) and (FLastMouseLocation.LastMousePoint.Y >= FTextArea.Bounds.Top) and (FLastMouseLocation.LastMousePoint.Y < FTextArea.Bounds.Bottom) then inherited Cursor := FTextCursor else inherited Cursor := FOffTextCursor; end; procedure TCustomSynEdit.Undo; var Item: TSynEditUndoItem; Group: TSynEditUndoGroup; begin Group := fUndoList.PopItem; if Group <> nil then begin {$IFDEF SynUndoDebugCalls} DebugLnEnter(['>> TCustomSynEdit.Undo ',DbgSName(self), ' ', dbgs(Self), ' Group', dbgs(Group), ' cnt=', Group.Count]); {$ENDIF} IncPaintLock; FTheLinesView.IsUndoing := True; Item := Group.Pop; if Item <> nil then begin InternalBeginUndoBlock(fRedoList); fRedoList.CurrentGroup.Reason := Group.Reason; fUndoList.Lock; try repeat UndoItem(Item); Item := Group.Pop; until (Item = nil); finally // Todo: Decide what do to, If there are any trimable spaces. FTrimmedLinesView.ForceTrim; fUndoList.UnLock; InternalEndUndoBlock(fRedoList); end; end; FTheLinesView.IsUndoing := False; Group.Free; if fUndoList.IsTopMarkedAsUnmodified then fRedoList.MarkTopAsUnmodified; DecPaintLock; {$IFDEF SynUndoDebugCalls} DebugLnExit(['<< TCustomSynEdit.Undo ',DbgSName(self), ' ', dbgs(Self)]); end else begin DebugLn(['<< TCustomSynEdit.Undo - NO GROUP ',DbgSName(self), ' ', dbgs(Self)]); {$ENDIF} end; end; procedure TCustomSynEdit.UndoItem(Item: TSynEditUndoItem); var Line, OldText: PChar; y, Len, Len2, LenT: integer; s: String; begin if Assigned(Item) then try FCaret.IncForcePastEOL; if Item.ClassType = TSynEditUndoIndent then begin // add to redo list fRedoList.AddChange(TSynEditUndoIndent.Create(TSynEditUndoIndent(Item).FPosY1, TSynEditUndoIndent(Item).FPosY2, TSynEditUndoIndent(Item).FCnt, TSynEditUndoIndent(Item).FTabCnt)); // quick unintend (must all be spaces, as inserted...) fRedoList.Lock; Len2 := TSynEditUndoIndent(Item).FCnt; LenT := TSynEditUndoIndent(Item).FTabCnt; for y := TSynEditUndoIndent(Item).FPosY1 to TSynEditUndoIndent(Item).FPosY2 do begin Line := PChar(FTheLinesView[y - 1]); if Len2 > 0 then begin Len := CountLeadWhiteSpace(Line); FTheLinesView.EditDelete(Len+1-Len2, y, Len2); end; if LenT > 0 then FTheLinesView.EditDelete(1, y, LenT); end; fRedoList.Unlock; end else if Item.ClassType = TSynEditUndoUnIndent then begin fRedoList.AddChange(TSynEditUndoUnIndent.Create(TSynEditUndoUnIndent(Item).FPosY1, TSynEditUndoUnIndent(Item).FPosY2, TSynEditUndoUnIndent(Item).FText)); // reinsert the string fRedoList.Lock; OldText := PChar(TSynEditUndoUnIndent(Item).FText); for y := TSynEditUndoUnIndent(Item).FPosY1 to TSynEditUndoUnIndent(Item).FPosY2 do begin Len2 := GetEOL(OldText) - OldText; if Len2 > 0 then begin Line := PChar(FTheLinesView[y - 1]); Len := CountLeadWhiteSpace(Line); SetLength(s, Len2); Move(OldText^, s[1], Len2); FTheLinesView.EditInsert(Len+1, y, s); end; inc(OldText, Len2+1); end; fRedoList.Unlock; end else if not Item.PerformUndo(self) then if not FUndoRedoItemHandlerList.CallUndoRedoItemHandlers(Self, Item) then FTheLinesView.EditUndo(Item); finally FTrimmedLinesView.UndoTrimmedSpaces := False; FCaret.DecForcePastEOL; Item.Free; end; end; procedure TCustomSynEdit.SetFoldState(const AValue: String); begin if assigned(fHighlighter) then begin fHighlighter.CurrentLines := FTheLinesView; if fHighlighter.NeedScan then begin FPendingFoldState := AValue; exit; end; end; if sfAfterLoadFromFileNeeded in fStateFlags then begin FPendingFoldState := AValue; exit; end; FFoldedLinesView.Lock; FFoldedLinesView.ApplyFoldDescription(0, 0, -1, -1, PChar(AValue), length(AValue), True); FFoldedLinesView.UnLock; FPendingFoldState := ''; end; procedure TCustomSynEdit.SetHiddenCodeLineColor(AValue: TSynSelectedColor); begin FFoldedLinesView.MarkupInfoHiddenCodeLine.Assign(AValue); end; procedure TCustomSynEdit.SetHighlightAllColor(AValue: TSynSelectedColor); begin fMarkupHighAll.MarkupInfo.Assign(AValue); end; procedure TCustomSynEdit.SetIncrementColor(AValue: TSynSelectedColor); begin fMarkupSelection.MarkupInfoIncr.Assign(AValue); end; procedure TCustomSynEdit.SetLineHighlightColor(AValue: TSynSelectedColor); begin fMarkupSpecialLine.MarkupLineHighlightInfo.Assign(AValue); end; procedure TCustomSynEdit.SetMouseActions(const AValue: TSynEditMouseActions); begin FMouseActions.UserActions := AValue; end; procedure TCustomSynEdit.SetMouseLinkColor(AValue: TSynSelectedColor); begin fMarkupCtrlMouse.MarkupInfo.Assign(AValue); end; procedure TCustomSynEdit.SetMouseSelActions(const AValue: TSynEditMouseActions); begin FMouseSelActions.UserActions := AValue; end; procedure TCustomSynEdit.SetMouseTextActions(AValue: TSynEditMouseActions); begin FMouseTextActions.UserActions := AValue; end; procedure TCustomSynEdit.SetPaintLockOwner(const AValue: TSynEditBase); begin TSynEditStringList(FLines).PaintLockOwner := AValue; end; procedure TCustomSynEdit.SetShareOptions(const AValue: TSynEditorShareOptions); var ChangedOptions: TSynEditorShareOptions; OldMarkList: TSynEditMarkList; it: TSynEditMarkIterator; MListShared: Boolean; begin if FShareOptions = AValue then exit; ChangedOptions:=(FShareOptions - AValue) + (AValue - FShareOptions); FShareOptions := AValue; if (eosShareMarks in ChangedOptions) then begin MListShared := IsMarkListShared; if ( (FShareOptions * [eosShareMarks] = []) and MListShared ) or ( (eosShareMarks in FShareOptions) and (not MListShared) and (TSynEditStringList(FLines).AttachedSynEditCount > 1) ) then begin OldMarkList := FMarkList; FMarkList := nil; RecreateMarkList; it := TSynEditMarkIterator.Create(OldMarkList); it.GotoBOL; while it.Next do begin // Todo: prevent notifications if it.Mark.OwnerEdit = Self then FMarkList.Add(it.Mark); end; it.Free; FreeAndNil(FMarkList); end; end; StatusChanged([scOptions]); end; procedure TCustomSynEdit.ChangeTextBuffer(NewBuffer: TSynEditStringList); var OldBuffer: TSynEditStringList; begin FLines.SendNotification(senrTextBufferChanging, FLines); // Send the old buffer DestroyMarkList; // Detach Highlighter if FHighlighter <> nil then FHighlighter.DetachFromLines(FLines); // Set the New Lines OldBuffer := TSynEditStringList(FLines); Flines := NewBuffer; TSynEditStringList(FLines).AttachSynEdit(Self); TSynTextViewsManagerInternal(FTextViewsManager).TextBuffer := FLines; FUndoList := NewBuffer.UndoList; FRedoList := NewBuffer.RedoList; // Recreate te public access to FLines FreeAndNil(FStrings); FStrings := TSynEditLines.Create(TSynEditStringList(FLines), @MarkTextAsSaved); // Flines has been set to the new buffer; and self is attached to the new FLines // FTheLinesView points to new FLines RecreateMarkList; // Attach Highlighter if FHighlighter <> nil then FHighlighter.AttachToLines(FLines); OldBuffer.DetachSynEdit(Self); FLines.SendNotification(senrTextBufferChanged, OldBuffer); // Send the old buffer OldBuffer.SendNotification(senrTextBufferChanged, OldBuffer); // Send the old buffer if OldBuffer.AttachedSynEditCount = 0 then OldBuffer.Free; end; function TCustomSynEdit.IsMarkListShared: Boolean; var i, j: Integer; begin j := 0; i := TSynEditStringList(FLines).AttachedSynEditCount - 1; while (i >= 0) and (j <= 1) do begin if TCustomSynEdit(TSynEditStringList(FLines).AttachedSynEdits[i]).FMarkList = FMarkList then inc(j); dec(i); end; Result := j > 1; end; procedure TCustomSynEdit.RecreateMarkList; var s: TSynEditBase; i: Integer; begin DestroyMarkList; if (TSynEditStringList(FLines).AttachedSynEditCount > 1) and (eosShareMarks in FShareOptions) then begin s := TSynEditStringList(FLines).AttachedSynEdits[0]; if s = Self then s := TSynEditStringList(FLines).AttachedSynEdits[1]; FMarkList := TCustomSynEdit(s).FMarkList; TSynEditMarkListInternal(fMarkList).AddOwnerEdit(Self); for i := 0 to 9 do FBookMarks[i] := TCustomSynEdit(s).fBookMarks[i]; end else begin FMarkList := TSynEditMarkListInternal.Create(self, FTheLinesView); for i := 0 to 9 do FBookMarks[i] := nil; end; FMarkList.RegisterChangeHandler(@MarkListChange, [low(TSynEditMarkChangeReason)..high(TSynEditMarkChangeReason)]); end; procedure TCustomSynEdit.DestroyMarkList; var it: TSynEditMarkIterator; s: TSynEditBase; begin if FMarkList = nil then exit; TSynEditMarkListInternal(fMarkList).RemoveOwnerEdit(Self); FMarkList.UnRegisterChangeHandler(@MarkListChange); if IsMarkListShared then begin s := TSynEditStringList(FLines).AttachedSynEdits[0]; if s = Self then s := TSynEditStringList(FLines).AttachedSynEdits[1]; // TODO: find one that shares the MarkList (if someday partial sharing of Marks is avail) if TSynEditMarkListInternal(FMarkList).LinesView = FTheLinesView then TSynEditMarkListInternal(FMarkList).LinesView := TCustomSynEdit(s).FTheLinesView; it := TSynEditMarkIterator.Create(FMarkList); it.GotoBOL; while it.Next do begin // Todo: prevent notifications if it.Mark.OwnerEdit = Self then it.Mark.OwnerEdit := s; end; it.Free; FMarkList := nil; end else FreeAndNil(FMarkList); end; procedure TCustomSynEdit.ShareTextBufferFrom(AShareEditor: TCustomSynEdit); begin if fPaintLock <> 0 then RaiseGDBException('Cannot change TextBuffer while paintlocked'); ChangeTextBuffer(TSynEditStringList(AShareEditor.FLines)); end; procedure TCustomSynEdit.UnShareTextBuffer; begin if fPaintLock <> 0 then RaiseGDBException('Cannot change TextBuffer while paintlocked'); if TSynEditStringList(FLines).AttachedSynEditCount = 1 then exit; ChangeTextBuffer(TSynEditStringList.Create); end; procedure TCustomSynEdit.ExtraLineCharsChanged(Sender: TObject); begin UpdateScrollBars; end; procedure TCustomSynEdit.SetTextBetweenPointsSimple(aStartPoint, aEndPoint: TPoint; const AValue: String); begin InternalBeginUndoBlock; try FInternalBlockSelection.SelectionMode := smNormal; FInternalBlockSelection.StartLineBytePos := aStartPoint; FInternalBlockSelection.EndLineBytePos := aEndPoint; FInternalBlockSelection.SelText := AValue; finally InternalEndUndoBlock; end; end; procedure TCustomSynEdit.SetTextBetweenPointsEx(aStartPoint, aEndPoint: TPoint; aCaretMode: TSynCaretAdjustMode; const AValue: String); begin SetTextBetweenPoints(aStartPoint, aEndPoint, AValue, [], aCaretMode); end; procedure TCustomSynEdit.SetTextBetweenPoints(aStartPoint, aEndPoint: TPoint; const AValue: String; aFlags: TSynEditTextFlags; aCaretMode: TSynCaretAdjustMode; aMarksMode: TSynMarksAdjustMode; aSelectionMode: TSynSelectionMode); var CaretAtBlock: (cabNo, cabBegin, cabEnd); begin InternalBeginUndoBlock; try CaretAtBlock := cabNo; if aCaretMode = scamForceAdjust then FCaret.IncAutoMoveOnEdit else if aCaretMode = scamAdjust then begin if FBlockSelection.SelAvail then begin if FCaret.IsAtLineByte(FBlockSelection.StartLineBytePos) then CaretAtBlock := cabBegin else if FCaret.IsAtLineByte(FBlockSelection.EndLineBytePos) then CaretAtBlock := cabEnd; end; if CaretAtBlock = cabNo then FCaret.IncAutoMoveOnEdit; end; if setPersistentBlock in aFlags then FBlockSelection.IncPersistentLock; if setMoveBlock in aFlags then FBlockSelection.IncPersistentLock(sbpWeak); if setExtendBlock in aFlags then FBlockSelection.IncPersistentLock(sbpStrong); if aSelectionMode = smCurrent then FInternalBlockSelection.SelectionMode := FBlockSelection.ActiveSelectionMode else FInternalBlockSelection.SelectionMode := aSelectionMode; FInternalBlockSelection.StartLineBytePos := aStartPoint; FInternalBlockSelection.EndLineBytePos := aEndPoint; aStartPoint := FInternalBlockSelection.FirstLineBytePos; if aCaretMode = scamBegin then FCaret.LineBytePos := aStartPoint; FInternalBlockSelection.SetSelTextPrimitive(FInternalBlockSelection.ActiveSelectionMode, PChar(AValue), (aMarksMode = smaKeep) or (aStartPoint.y = aEndPoint.y) ); if aCaretMode = scamEnd then FCaret.LineBytePos := FInternalBlockSelection.StartLineBytePos; if setSelect in aFlags then begin FBlockSelection.StartLineBytePos := aStartPoint; FBlockSelection.ActiveSelectionMode := FInternalBlockSelection.SelectionMode; FBlockSelection.EndLineBytePos := FInternalBlockSelection.StartLineBytePos; if FBlockSelection.ActiveSelectionMode = smLine then FBlockSelection.EndLineBytePos := Point(FBlockSelection.StartBytePos + 1, FBlockSelection.EndLinePos - 1); end; finally if CaretAtBlock = cabBegin then FCaret.LineBytePos := FBlockSelection.StartLineBytePos else if CaretAtBlock = cabEnd then FCaret.LineBytePos := FBlockSelection.EndLineBytePos; if setPersistentBlock in aFlags then FBlockSelection.DecPersistentLock; if setMoveBlock in aFlags then FBlockSelection.DecPersistentLock; if setExtendBlock in aFlags then FBlockSelection.DecPersistentLock; if (CaretAtBlock = cabNo) and (aCaretMode in [scamAdjust, scamForceAdjust]) then FCaret.DecAutoMoveOnEdit; InternalEndUndoBlock; end; end; procedure TCustomSynEdit.SetVisibleSpecialChars(AValue: TSynVisibleSpecialChars); begin if FVisibleSpecialChars = AValue then Exit; FVisibleSpecialChars := AValue; fMarkupSpecialChar.VisibleSpecialChars := AValue; if eoShowSpecialChars in Options then FPaintArea.VisibleSpecialChars := AValue else FPaintArea.VisibleSpecialChars := []; if eoShowSpecialChars in Options then Invalidate; StatusChanged([scOptions]); end; function TCustomSynEdit.GetLineState(ALine: Integer): TSynLineState; begin with TSynEditStringList(fLines) do if [sfModified, sfSaved] * Flags[ALine] = [sfModified] then Result := slsUnsaved else if [sfModified, sfSaved] * Flags[ALine] = [sfModified, sfSaved] then Result := slsSaved else Result := slsNone; end; procedure TCustomSynEdit.ClearBookMark(BookMark: Integer); begin if (BookMark in [0..9]) and assigned(fBookMarks[BookMark]) then FBookMarks[BookMark].Free; end; procedure TCustomSynEdit.GotoBookMark(BookMark: Integer); var LogCaret: TPoint; m: TSynEditBookMark; begin if (BookMark in [0..9]) and assigned(fBookMarks[BookMark]) and (fBookMarks[BookMark].Line <= fLines.Count) then begin m := TSynEditBookMark(fBookMarks[BookMark]); if (eoBookmarkRestoresScroll in FOptions2) and (m.TopLeftMark <> nil) and (m.TopLeftMark.Line > 0) then begin TopLine := m.TopLeftMark.Line; LeftChar := m.TopLeftMark.Column; end; LogCaret:=Point(m.Column, m.Line); DoIncPaintLock(Self); // No editing is taking place FCaret.ChangeOnTouch; FCaret.LineBytePos := LogCaret; DoDecPaintLock(Self); end; end; function TCustomSynEdit.IsLinkable(Y, X1, X2: Integer): Boolean; begin Result := X1 <> X2; if Result and Assigned(FOnMouseLink) then FOnMouseLink(Self, X1, Y, Result); end; procedure TCustomSynEdit.SetBookMark(BookMark: Integer; X: Integer; Y: Integer; AnLeft: Integer; AnTop: Integer); var i: Integer; mark: TSynEditBookMark; begin if (BookMark in [0..9]) and (Y >= 1) and (Y <= Max(1, fLines.Count)) then begin mark := TSynEditBookMark.Create(self); X := PhysicalToLogicalPos(Point(X, Y)).x; with mark do begin Line := Y; Column := X; ImageIndex := Bookmark; BookmarkNumber := Bookmark; Visible := true; InternalImage := (BookMarkOptions.BookmarkImages = nil); if AnLeft > 0 then mark.SetTopLeft(AnTop, AnLeft); end; for i := 0 to 9 do if assigned(fBookMarks[i]) and (fBookMarks[i].Line = Y) then ClearBookmark(i); if assigned(fBookMarks[BookMark]) then ClearBookmark(BookMark); FMarkList.Add(mark); end; end; procedure TCustomSynEdit.WndProc(var Msg: TMessage); // Prevent Alt-Backspace from beeping const ALT_KEY_DOWN = $20000000; begin // ASAP after a paint // in case an App.AsyncCall takes longer if (Msg.msg <> WM_PAINT) and (Msg.msg <> LM_PAINT) and (sfHasPainted in fStateFlags) and (FScreenCaret <> nil) then FScreenCaret.AfterPaintEvent; if (Msg.Msg = WM_SYSCHAR) and (Msg.wParam = VK_BACK) and (Msg.lParam and ALT_KEY_DOWN <> 0) then Msg.Msg := 0 else inherited; end; procedure TCustomSynEdit.InsertTextAtCaret(aText: String; aCaretMode : TSynCaretAdjustMode = scamEnd); begin TextBetweenPointsEx[FCaret.LineBytePos, FCaret.LineBytePos, aCaretMode] := aText; end; function TCustomSynEdit.CheckDragDropAccecpt(ANewCaret: TPoint; ASource: TObject; out ADropMove: boolean): boolean; begin // if from other control then move when SHIFT, else copy // if from Self then copy when CTRL, else move if ASource <> Self then begin ADropMove := GetKeyState(VK_SHIFT) < 0; Result := TRUE; end else begin ADropMove := GetKeyState(VK_CONTROL) >= 0; Result := not IsPointInSelection(ANewCaret, not ADropMove); end; end; procedure TCustomSynEdit.DragOver(Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); var DropMove, InHotZone: boolean; hf, vf: Integer; begin inherited; LastMouseCaret:=Point(-1,-1); Exclude(fStateFlags, sfDraggingOver); if (eoAcceptDragDropEditing in FOptions2) and (Source is TCustomSynEdit) then begin Accept := (X >= FTextArea.Bounds.Left) and (X < FTextArea.Bounds.Right) and (Y >= FTextArea.Bounds.Top) and (Y < FTextArea.Bounds.Bottom); InHotZone := GetDragHotZoneInfo(x, y, hf, vf); if InHotZone then begin if not (sfDraggingOver in fStateFlags) then begin fScrollTimer.Interval := 500; end; fScrollTimer.Enabled := True; Include(fStateFlags, sfDraggingOver); end else if ( (hf <> 0) or (vf <> 0) ) and (abs(hf) < 384) and (abs(vf) < 384) then Include(fStateFlags, sfDraggingOver); // keep scrolling if Accept and (not ReadOnly) and TCustomSynEdit(Source).SelAvail then begin //if State = dsDragLeave then //restore prev caret position // ComputeCaret(FMouseDownX, FMouseDownY) //else //position caret under the mouse cursor FInternalCaret.AssignFrom(FCaret); FInternalCaret.Invalidate; FInternalCaret.LineCharPos := PixelsToRowColumn(Point(X,Y)); Accept := CheckDragDropAccecpt(FInternalCaret.LineBytePos, Source, DropMove); if Accept then begin FBlockSelection.IncPersistentLock; Include(fStateFlags, sfPreventScrollAfterSelect); // not PaintLocked => setting caret will directly call EnsureCursorPos try FCaret.LineCharPos := FInternalCaret.LineCharPos; finally FBlockSelection.DecPersistentLock; exclude(fStateFlags, sfPreventScrollAfterSelect); end; if DropMove then DragCursor := crDrag else DragCursor := crMultiDrag; end; end; end; if not (sfDraggingOver in fStateFlags) then fScrollTimer.Enabled := False; end; procedure TCustomSynEdit.DragDrop(Source: TObject; X, Y: Integer); var NewCaret: TPoint; DropMove: boolean; DragDropText: string; FoldInfo: String; BlockSel: TSynEditSelection; sm: TSynSelectionMode; begin if (eoAcceptDragDropEditing in FOptions2) and (not ReadOnly) and (Source is TCustomSynEdit) and TCustomSynEdit(Source).SelAvail then begin IncPaintLock; try inherited; ComputeCaret(X, Y); NewCaret := LogicalCaretXY; if CheckDragDropAccecpt(NewCaret, Source, DropMove) then begin InternalBeginUndoBlock; //mh 2000-11-20 try DragDropText := TCustomSynEdit(Source).SelText; BlockSel := TCustomSynEdit(Source).FBlockSelection; if eoFoldedCopyPaste in fOptions2 then FoldInfo := TCustomSynEdit(Source).FFoldedLinesView.GetFoldDescription( BlockSel.FirstLineBytePos.Y - 1, BlockSel.FirstLineBytePos.X, BlockSel.LastLineBytePos.Y - 1, BlockSel.LastLineBytePos.X); sm := BlockSel.ActiveSelectionMode; if sm = smLine then sm := smNormal; // delete the selected text if necessary if DropMove then begin if Source <> Self then TCustomSynEdit(Source).SelText := '' else begin FInternalCaret.AssignFrom(FCaret); FInternalCaret.IncAutoMoveOnEdit; FBlockSelection.SelText := ''; FInternalCaret.DecAutoMoveOnEdit; NewCaret := FInternalCaret.LineBytePos; end; end; // insert the selected text FCaret.IncForcePastEOL; try if (eoPersistentBlock in Options2) and SelAvail then SetTextBetweenPoints(NewCaret, NewCaret, DragDropText, [setMoveBlock], scamEnd, smaMoveUp, sm) else SetTextBetweenPoints(NewCaret, NewCaret, DragDropText, [setSelect], scamEnd, smaMoveUp, sm); if (FoldInfo <> '') and (sm <> smColumn) then begin ScanRanges; FFoldedLinesView.ApplyFoldDescription(NewCaret.Y -1, NewCaret.X, FBlockSelection.EndLinePos-1, FBlockSelection.EndBytePos, PChar(FoldInfo), length(FoldInfo)); end; finally FCaret.DecForcePastEOL; end; finally InternalEndUndoBlock; end; end; finally DecPaintLock; end; end else inherited; end; procedure TCustomSynEdit.SetRightEdge(Value: Integer); begin // Todo: check and invalidate in text area if FTextArea.RightEdgeColumn <> Value then begin FPaintArea.RightEdgeColumn := Value; if FTextArea.RightEdgeVisible then Invalidate; end; end; procedure TCustomSynEdit.SetRightEdgeColor(Value: TColor); var nX: integer; rcInval: TRect; begin // Todo: check and invalidate in text area if RightEdgeColor <> Value then begin FPaintArea.RightEdgeColor := Value; if HandleAllocated then begin nX := FTextArea.ScreenColumnToXValue(FTextArea.RightEdgeColumn + 1); rcInval := Rect(nX - 1, 0, nX + 1, ClientHeight-ScrollBarWidth); {$IFDEF VerboseSynEditInvalidate} DebugLn(['TCustomSynEdit.SetRightEdgeColor ',dbgs(rcInval)]); {$ENDIF} InvalidateRect(Handle, @rcInval, FALSE); end; end; end; function TCustomSynEdit.GetMaxUndo: Integer; begin result := fUndoList.MaxUndoActions; end; procedure TCustomSynEdit.SetMaxUndo(const Value: Integer); begin if Value > -1 then begin fUndoList.MaxUndoActions := Value; fRedoList.MaxUndoActions := Value; end; end; procedure TCustomSynEdit.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if Operation = opRemove then begin if AComponent = fHighlighter then begin fHighlighter.DetachFromLines(FLines); fHighlighter := nil; fMarkupHighCaret.Highlighter := nil; fMarkupWordGroup.Highlighter := nil; FFoldedLinesView.Highlighter := nil; FPaintArea.Highlighter := nil; if not (csDestroying in ComponentState) then begin RecalcCharExtent; Invalidate; end; end; if (BookMarkOptions <> nil) then if (AComponent = BookMarkOptions.BookmarkImages) then begin BookMarkOptions.BookmarkImages := nil; InvalidateGutterLines(-1, -1); end; end; end; procedure TCustomSynEdit.RemoveHooksFromHighlighter; begin if not Assigned(fHighlighter) then exit; fHighlighter.UnhookAttrChangeEvent(@HighlighterAttrChanged); fHighlighter.DetachFromLines(FLines); fHighlighter.RemoveFreeNotification(self); end; procedure TCustomSynEdit.SetHighlighter(const Value: TSynCustomHighlighter); begin if Value <> fHighlighter then begin FPendingFoldState := ''; RemoveHooksFromHighlighter; if Assigned(Value) then begin Value.HookAttrChangeEvent( @HighlighterAttrChanged); Value.FreeNotification(Self); Value.AttachToLines(FLines); end; fHighlighter := Value; IncPaintLock; try // Ensure to free all copies in SynEit.Notification too fMarkupHighCaret.Highlighter := Value; fMarkupWordGroup.Highlighter := Value; FFoldedLinesView.Highlighter := Value; FPaintArea.Highlighter := Value; FWordBreaker.Reset; if fHighlighter<>nil then begin fTSearch.IdentChars := fHighlighter.IdentChars; FWordBreaker.IdentChars := fHighlighter.IdentChars; FWordBreaker.WordBreakChars := fHighlighter.WordBreakChars; end else begin fTSearch.ResetIdentChars; end; RecalcCharExtent; ScanRanges; // Todo: Skip if paintlocked // There may not have been a scan if fHighlighter <> nil then FHighlighter.CurrentLines := FLines; FLines.SendNotification(senrHighlightChanged, FLines, -1, -1); finally DecPaintLock; end; end; end; procedure TCustomSynEdit.SetInsertMode(const Value: boolean); begin if fInserting <> Value then begin fInserting := Value; if InsertMode then FScreenCaret.DisplayType := FInsertCaret else FScreenCaret.DisplayType := FOverwriteCaret; StatusChanged([scInsertMode]); end; end; procedure TCustomSynEdit.SetInsertCaret(const Value: TSynEditCaretType); begin if FInsertCaret <> Value then begin FInsertCaret := Value; if InsertMode then FScreenCaret.DisplayType := fInsertCaret; StatusChanged([scOptions]); end; end; procedure TCustomSynEdit.SetOverwriteCaret(const Value: TSynEditCaretType); begin if FOverwriteCaret <> Value then begin FOverwriteCaret := Value; if not InsertMode then FScreenCaret.DisplayType := fOverwriteCaret; StatusChanged([scOptions]); end; end; procedure TCustomSynEdit.SetMaxLeftChar(Value: integer); begin Value := MinMax(Value, 1, MAX_SCROLL); // horz scrolling is only 16 bit if fMaxLeftChar <> Value then begin fMaxLeftChar := Value; Invalidate; end; end; procedure TCustomSynEdit.EnsureCursorPosVisible; var PhysCaretXY: TPoint; MinX: Integer; MaxX: Integer; PhysBlockBeginXY: TPoint; PhysBlockEndXY: TPoint; begin if (PaintLockOwner <> nil) and (PaintLockOwner <> Self) and (not (eoAlwaysVisibleCaret in fOptions2)) then exit; if WaitingForInitialSize or (fPaintLock > 0) or (FWinControlFlags * [wcfInitializing, wcfCreatingHandle] <> []) then begin include(fStateFlags, sfEnsureCursorPos); exit; end; //{BUG21996} DebugLnEnter(['TCustomSynEdit.EnsureCursorPosVisible Caret=',dbgs(CaretXY),', BlockBegin=',dbgs(BlockBegin),' BlockEnd=',dbgs(BlockEnd), ' StateFlags=',dbgs(fStateFlags), ' paintlock', FPaintLock]); exclude(fStateFlags, sfEnsureCursorPos); DoIncPaintLock(Self); // No editing is taking place try // Make sure X is visible //DebugLn('[TCustomSynEdit.EnsureCursorPosVisible] A CaretX=',CaretX,' LeftChar=',LeftChar,' CharsInWindow=',CharsInWindow,' ClientWidth=',ClientWidth); PhysCaretXY:=FCaret.ViewedLineCharPos; // try to make the current selection visible as well MinX:=PhysCaretXY.X; MaxX:=PhysCaretXY.X; // sfMouseSelecting: ignore block while selecting by mouse if SelAvail and not(sfMouseSelecting in fStateFlags) then begin PhysBlockBeginXY:=FBlockSelection.ViewedFirstLineCharPos; PhysBlockEndXY :=FBlockSelection.ViewedLastLineCharPos; if (PhysBlockBeginXY.X<>PhysBlockEndXY.X) or (PhysBlockBeginXY.Y<>PhysBlockEndXY.Y) then begin if (FBlockSelection.ActiveSelectionMode <> smColumn) and (PhysBlockBeginXY.Y<>PhysBlockEndXY.Y) then PhysBlockBeginXY.X:=1; if MinX>PhysBlockBeginXY.X then MinX:=Max(PhysBlockBeginXY.X,PhysCaretXY.X-CharsInWindow+1); if MinX>PhysBlockEndXY.X then MinX:=Max(PhysBlockEndXY.X,PhysCaretXY.X-CharsInWindow+1); if MaxX<PhysBlockBeginXY.X then MaxX:=Min(PhysBlockBeginXY.X,MinX+CharsInWindow-1); if MaxX<PhysBlockEndXY.X then MaxX:=Min(PhysBlockEndXY.X,MinX+CharsInWindow-1); end; end; if not (sfPreventScrollAfterSelect in fStateFlags) then begin if not (sfExplicitLeftChar in fStateFlags) then begin {DebugLn('TCustomSynEdit.EnsureCursorPosVisible A CaretX=',dbgs(PhysCaretXY.X), ' BlockX=',dbgs(PhysBlockBeginXY.X)+'-'+dbgs(PhysBlockEndXY.X), ' CharsInWindow='+dbgs(CharsInWindow), MinX='+dbgs(MinX),' MaxX='+dbgs(MaxX), ' LeftChar='+dbgs(LeftChar), '');} MaxX := MaxX - (Max(1, CharsInWindow) - 1 - FScreenCaret.ExtraLineChars); if (sfEnsureCursorPosForEditLeft in fStateFlags) then dec(MinX, FScrollOnEditLeftOptions.FCurrentDistance) else if (sfEnsureCursorPosForEditRight in fStateFlags) then inc(MaxX, FScrollOnEditRightOptions.FCurrentDistance); if MinX < LeftChar then begin if sfEnsureCursorPosForEditLeft in fStateFlags then MinX := Min(MinX, PhysCaretXY.X - FScrollOnEditLeftOptions.FCurrentColumns); LeftChar := MinX; end else if LeftChar < MaxX then begin if sfEnsureCursorPosForEditRight in fStateFlags then MaxX := Max(MaxX, PhysCaretXY.X + FScrollOnEditRightOptions.FCurrentColumns - (Max(1, CharsInWindow) - 1 - FScreenCaret.ExtraLineChars)); LeftChar := MaxX; end else LeftChar := LeftChar; //mh 2000-10-19 end; if not (sfExplicitTopLine in fStateFlags) then begin //DebugLn(['TCustomSynEdit.EnsureCursorPosVisible B LeftChar=',LeftChar,' MinX=',MinX,' MaxX=',MaxX,' CharsInWindow=',CharsInWindow]); // Make sure Y is visible if PhysCaretXY.Y < TopView then TopView := PhysCaretXY.Y else if PhysCaretXY.Y > TopView + Max(0, LinesInWindow-1) then TopView := PhysCaretXY.Y - Max(0, LinesInWindow-1) else TopView := TopView; //mh 2000-10-19 end; end; fStateFlags := fStateFlags - [sfPreventScrollAfterSelect, sfEnsureCursorPosForEditRight, sfEnsureCursorPosForEditLeft]; finally DoDecPaintLock(Self); //{BUG21996} DebugLnExit(['TCustomSynEdit.EnsureCursorPosVisible Caret=',dbgs(CaretXY),', BlockBegin=',dbgs(BlockBegin),' BlockEnd=',dbgs(BlockEnd), ' StateFlags=',dbgs(fStateFlags), ' paintlock', FPaintLock]); end; end; procedure TCustomSynEdit.SetKeystrokes(const Value: TSynEditKeyStrokes); begin if Value = nil then FKeystrokes.Clear else FKeystrokes.Assign(Value); end; procedure TCustomSynEdit.SetExtraCharSpacing(const Value: integer); begin if ExtraCharSpacing=Value then exit; inherited; FPaintArea.ExtraCharSpacing := Value; FontChanged(self); end; procedure TCustomSynEdit.SetLastMouseCaret(const AValue: TPoint); begin if (FLastMouseLocation.LastMouseCaret.X=AValue.X) and (FLastMouseLocation.LastMouseCaret.Y=AValue.Y) then exit; FLastMouseLocation.LastMouseCaret:=AValue; if assigned(fMarkupCtrlMouse) then fMarkupCtrlMouse.LastMouseCaret := AValue; UpdateCursor; end; procedure TCustomSynEdit.SetDefaultKeystrokes; begin FKeystrokes.ResetDefaults; end; procedure TCustomSynEdit.ResetMouseActions; begin FMouseActions.Options := MouseOptions; FMouseActions.ResetUserActions; FMouseSelActions.Options := MouseOptions; FMouseSelActions.ResetUserActions; FMouseTextActions.Options := MouseOptions; FMouseTextActions.ResetUserActions; FLeftGutter.ResetMouseActions; FRightGutter.ResetMouseActions; end; procedure TCustomSynEdit.CommandProcessor(Command: TSynEditorCommand; AChar: TUTF8Char; Data: pointer; ASkipHooks: THookedCommandFlags); var InitialCmd: TSynEditorCommand; BeautifyWorker: TSynCustomBeautifier; begin IncLCLRefCount; try {$IFDEF VerboseKeys} DebugLn(['[TCustomSynEdit.CommandProcessor] ',Command ,' AChar=',AChar,' Data=',DbgS(Data)]); {$ENDIF} if (Command <> ecMoveSelectUp) and (Command <> ecMoveSelectDown) then FLastCaretXForMoveSelection := -1; // first the program event handler gets a chance to process the command InitialCmd := Command; if not(hcfInit in ASkipHooks) then NotifyHookedCommandHandlers(Command, AChar, Data, hcfInit); DoOnProcessCommand(Command, AChar, Data); if Command <> ecNone then begin try InternalBeginUndoBlock; FBeautifyStartLineIdx := -1; FBeautifyEndLineIdx := -1; if assigned(FBeautifier) then begin BeautifyWorker := FBeautifier.GetCopy; BeautifyWorker.AutoIndent := (eoAutoIndent in FOptions); BeautifyWorker.BeforeCommand(self, FTheLinesView, FCaret, Command, InitialCmd); end; // notify hooked command handlers before the command is executed inside of // the class if (Command <> ecNone) and not(hcfPreExec in ASkipHooks) then NotifyHookedCommandHandlers(Command, AChar, Data, hcfPreExec); // internal command handler if (Command <> ecNone) and (Command < ecUserFirst) then ExecuteCommand(Command, AChar, Data); // notify hooked command handlers after the command was executed inside of // the class (only if NOT handled by hcfPreExec) if (Command <> ecNone) and not(hcfPostExec in ASkipHooks) then NotifyHookedCommandHandlers(Command, AChar, Data, hcfPostExec); if Command <> ecNone then DoOnCommandProcessed(Command, AChar, Data); if assigned(BeautifyWorker) then begin tsyneditstringlist(FLines).FlushNotificationCache; BeautifyWorker.AutoIndent := (eoAutoIndent in FOptions); BeautifyWorker.AfterCommand(self, FTheLinesView, FCaret, Command, InitialCmd, FBeautifyStartLineIdx+1, FBeautifyEndLineIdx+1); FreeAndNil(BeautifyWorker); end; finally InternalEndUndoBlock; {$IFDEF SynCheckPaintLock} if (FPaintLock > 0) and (FInvalidateRect.Bottom >= FInvalidateRect.Top) then begin debugln(['TCustomSynEdit.CommandProcessor: Paint called while locked InitialCmd=', InitialCmd, ' Command=', Command]); DumpStack; end; {$ENDIF} end; end; Command := InitialCmd; if not(hcfFinish in ASkipHooks) then NotifyHookedCommandHandlers(Command, AChar, Data, hcfFinish); finally DecLCLRefCount; end; end; procedure TCustomSynEdit.ExecuteCommand(Command: TSynEditorCommand; const AChar: TUTF8Char; Data: pointer); const SEL_MODE: array[ecNormalSelect..ecLineSelect] of TSynSelectionMode = ( smNormal, smColumn, smLine); var CX: Integer; Len: Integer; Temp: string; Helper: string; moveBkm, CurBack: boolean; WP: TPoint; Caret: TPoint; CaretNew: TPoint; counter: Integer; LogCounter: integer; LogCaretXY: TPoint; CY: Integer; CurSm: TSynSelectionMode; begin IncPaintLock; IncLCLRefCount; try fUndoList.CurrentReason := Command; if Command in [ecSelectionStart..ecSelectionEnd] then FBlockSelection.StickyAutoExtend := False; if Command in [ecSelColCmdRangeStart..ecSelColCmdRangeEnd] then FBlockSelection.ActiveSelectionMode := smColumn; if Command in [ecSelCmdRangeStart..ecSelCmdRangeEnd] then FBlockSelection.ActiveSelectionMode := FBlockSelection.SelectionMode; if (Command <> ecMoveSelectUp) and (Command <> ecMoveSelectDown) then FLastCaretXForMoveSelection := -1; FBlockSelection.AutoExtend := Command in [ecSelectionStart..ecSelectionEnd]; FCaret.ChangeOnTouch; case Command of // horizontal caret movement or selection ecLeft, ecSelLeft, ecColSelLeft: begin if (eoCaretSkipsSelection in Options2) and (Command=ecLeft) and SelAvail and FCaret.IsAtLineByte(FBlockSelection.LastLineBytePos) then begin if not (eoCaretMoveEndsSelection in Options2) then FBlockSelection.IgnoreNextCaretMove; FCaret.LineBytePos := FBlockSelection.FirstLineBytePos; end else if (eoCaretMoveEndsSelection in Options2) and SelAvail then FBlockSelection.Clear else MoveCaretHorz(-1); end; ecRight, ecSelRight, ecColSelRight: begin if (eoCaretSkipsSelection in Options2) and (Command=ecRight) and SelAvail and FCaret.IsAtLineByte(FBlockSelection.FirstLineBytePos) then begin if not (eoCaretMoveEndsSelection in Options2) then FBlockSelection.IgnoreNextCaretMove; FCaret.LineBytePos := FBlockSelection.LastLineBytePos; end else if (eoCaretMoveEndsSelection in Options2) and SelAvail then FBlockSelection.Clear else MoveCaretHorz(1); end; ecPageLeft, ecSelPageLeft, ecColSelPageLeft: begin FCaret.CharPos := Max(1, FCaret.CharPos - Max(1, CharsInWindow)); end; ecPageRight, ecSelPageRight, ecColSelPageRight: begin FCaret.IncForceAdjustToNextChar; FCaret.CharPos := FCaret.CharPos + Max(1, CharsInWindow); FCaret.DecForceAdjustToNextChar; end; ecLineStart, ecSelLineStart, ecColSelLineStart: begin DoHomeKey; end; ecLineTextStart, ecSelLineTextStart, ecColSelLineTextStart: begin DoHomeKey(synhmFirstWord); end; ecLineEnd, ecSelLineEnd, ecColSelLineEnd: begin DoEndKey; end; // vertical caret movement or selection ecUp, ecSelUp, ecColSelUp: begin MoveCaretVert(-1, (Command = ecUp) or (Command = ecSelUp) ); end; ecDown, ecSelDown, ecColSelDown: begin MoveCaretVert(1, (Command = ecDown) or (Command = ecSelDown)); end; ecPageUp, ecSelPageUp, ecPageDown, ecSelPageDown, ecColSelPageUp, ecColSelPageDown: begin counter := LinesInWindow; if (eoHalfPageScroll in fOptions) then counter:=counter div 2; if eoScrollByOneLess in fOptions then Dec(counter); counter := Max(1, counter); if (Command in [ecPageUp, ecSelPageUp, ecColSelPageUp]) then counter := -counter; TopView := TopView + counter; MoveCaretVert(counter, (Command = ecPageUp) or (Command = ecSelPageUp) or (Command = ecPageDown) or (Command = ecSelPageDown) ); end; ecPageTop, ecSelPageTop, ecColSelPageTop: begin FCaret.LinePos := TopLine; end; ecPageBottom, ecSelPageBottom, ecColSelPageBottom: begin FCaret.LinePos := ScreenRowToRow(LinesInWindow - 1); end; ecEditorTop, ecSelEditorTop: begin FCaret.LineCharPos := Point(1, ToPos(FTheLinesView.ViewToTextIndex(0))); end; ecEditorBottom, ecSelEditorBottom: begin CaretNew := Point(1, ToPos(FTheLinesView.ViewToTextIndex(ToIdx(FTheLinesView.ViewedCount)))); if (CaretNew.Y > 0) then CaretNew.X := Length(FTheLinesView[CaretNew.Y - 1]) + 1; FCaret.LineCharPos := CaretNew; end; ecColSelEditorTop: begin FCaret.LinePos := ToPos(FTheLinesView.ViewToTextIndex(0)); end; ecColSelEditorBottom: begin FCaret.LinePos := ToPos(FTheLinesView.ViewToTextIndex(ToIdx(FTheLinesView.ViewedCount))); end; // goto special line / column position ecGotoXY, ecSelGotoXY: if Assigned(Data) then begin FCaret.LineCharPos := PPoint(Data)^; end; // word selection ecWordLeft, ecSelWordLeft, ecColSelWordLeft, ecWordEndLeft, ecSelWordEndLeft, ecHalfWordLeft, ecSelHalfWordLeft, ecSmartWordLeft, ecSelSmartWordLeft: begin case Command of ecWordEndLeft, ecSelWordEndLeft: CaretNew := PrevWordLogicalPos(swbWordEnd); ecHalfWordLeft, ecSelHalfWordLeft: CaretNew := PrevWordLogicalPos(swbCaseChange); ecSmartWordLeft, ecSelSmartWordLeft: CaretNew := PrevWordLogicalPos(swbWordSmart); else CaretNew := PrevWordLogicalPos; end; if not FTheLinesView.IsTextIdxVisible(ToIdx(CaretNew.Y)) then begin CY := FindNextUnfoldedLine(CaretNew.Y, False); CaretNew := Point(1 + Length(FTheLinesView[CY-1]), CY); end; FCaret.LineBytePos := CaretNew; end; ecWordRight, ecSelWordRight, ecColSelWordRight, ecWordEndRight, ecSelWordEndRight, ecHalfWordRight, ecSelHalfWordRight, ecSmartWordRight, ecSelSmartWordRight: begin case Command of ecWordEndRight, ecSelWordEndRight: CaretNew := NextWordLogicalPos(swbWordEnd); ecHalfWordRight, ecSelHalfWordRight: CaretNew := NextWordLogicalPos(swbCaseChange); ecSmartWordRight, ecSelSmartWordRight: CaretNew := NextWordLogicalPos(swbWordSmart); else CaretNew := NextWordLogicalPos; end; if not FTheLinesView.IsTextIdxVisible(ToIdx(CaretNew.Y)) then CaretNew := Point(1, FindNextUnfoldedLine(CaretNew.Y, True)); FCaret.LineBytePos := CaretNew; end; ecStickySelection, ecStickySelectionCol, ecStickySelectionLine: begin case command of ecStickySelection: FBlockSelection.ActiveSelectionMode := smNormal; ecStickySelectionCol: FBlockSelection.ActiveSelectionMode := smColumn; ecStickySelectionLine: FBlockSelection.ActiveSelectionMode := smLine; end; FBlockSelection.StickyAutoExtend := True; end; ecStickySelectionStop: begin FBlockSelection.StickyAutoExtend := False; end; ecSelectAll: begin SelectAll; end; ecDeleteLastChar: if not ReadOnly then begin if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then SetSelTextExternal('') else begin Temp := LineText; Len := Length(Temp); LogCaretXY := FCaret.LineBytePos; Caret := CaretXY; //debugln('ecDeleteLastChar B Temp="',DbgStr(Temp),'" CaretX=',dbgs(CaretX),' LogCaretXY=',dbgs(LogCaretXY)); if LogCaretXY.X > Len +1 then begin // past EOL; only move caret one column FCaret.IncForcePastEOL; CaretX := CaretX - 1; FCaret.DecForcePastEOL; end else if CaretX = 1 then begin // join this line with the last line if possible if CaretY > 1 then begin CaretY := CaretY - 1; CaretX := PhysicalLineLength(FTheLinesView[CaretY - 1], CaretY - 1) + 1; FTheLinesView.EditLineJoin(CaretY); end; end else begin // delete char LogCounter := LogCaretXY.X; if FCaret.BytePosOffset = 0 then begin LogCaretXY.X := FTheLinesView.LogicPosAddChars(Temp, LogCaretXY.X, -1, [lpStopAtCodePoint]); LogCounter := LogCounter - LogCaretXY.X; end else LogCounter := GetCharLen(Temp, LogCaretXY.X); FTheLinesView.EditDelete(LogCaretXY.X, LogCaretXY.Y, LogCounter); FCaret.BytePos := LogCaretXY.X; Include(fStateFlags, sfEnsureCursorPosForEditLeft); end; end; end; ecDeleteChar, ecDeleteCharNoCrLf: if not ReadOnly then begin if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then SetSelTextExternal('') else begin Temp := LineText; Len := Length(Temp); LogCaretXY:=LogicalCaretXY; if LogCaretXY.X <= Len then begin // delete char Counter:=GetCharLen(Temp,LogCaretXY.X); FTheLinesView.EditDelete(LogCaretXY.X, CaretY, Counter); SetLogicalCaretXY(LogCaretXY); end else if Command = ecDeleteChar then begin // join line with the line after if CaretY < FTheLinesView.Count then begin Helper := StringOfChar(' ', LogCaretXY.X - 1 - Len); FTheLinesView.EditLineJoin(CaretY, Helper); end; end; end; end; ecDeleteWord, ecDeleteEOL: if not ReadOnly then begin Helper := ''; Caret := CaretXY; if Command = ecDeleteWord then begin Len := LogicalToPhysicalCol(LineText, CaretY-1,Length(LineText)+1)-1; if CaretX > Len + 1 then begin Helper := StringOfChar(' ', CaretX - 1 - Len); CaretX := 1 + Len; end; // if we are not in a word, delete word + spaces (up to next token) if WordBreaker.IsAtWordStart(LineText, LogicalCaretXY.X) or WordBreaker.IsAtWordEnd(LineText, LogicalCaretXY.X) or (not WordBreaker.IsInWord(LineText, LogicalCaretXY.X)) or (LogicalCaretXY.X > Length(LineText)) then WP := NextWordLogicalPos(swbTokenBegin, True) else // if we are inside a word, delete to word-end WP := NextWordLogicalPos(swbWordEnd, True); end else WP := Point(Length(LineText) + 1, CaretY); if (WP.X <> FCaret.BytePos) or (WP.Y <> FCaret.LinePos) then begin FInternalBlockSelection.StartLineBytePos := WP; FInternalBlockSelection.EndLineBytePos := LogicalCaretXY; FInternalBlockSelection.ActiveSelectionMode := smNormal; FInternalBlockSelection.SetSelTextPrimitive(smNormal, PChar(Helper)); FCaret.BytePos := FInternalBlockSelection.StartBytePos; end; end; ecDeleteLastWord, ecDeleteBOL: if not ReadOnly then begin if Command = ecDeleteLastWord then WP := PrevWordLogicalPos else WP := Point(1, CaretY); if (WP.X <> FCaret.BytePos) or (WP.Y <> FCaret.LinePos) then begin FInternalBlockSelection.StartLineBytePos := WP; FInternalBlockSelection.EndLineBytePos := LogicalCaretXY; FInternalBlockSelection.ActiveSelectionMode := smNormal; FInternalBlockSelection.SetSelTextPrimitive(smNormal, nil); FCaret.LineBytePos := WP; end; end; ecDeleteLine, ecDeleteLineKeepX: if not ReadOnly then begin CY := FCaret.LinePos; if (Cy < FTheLinesView.Count) then FTheLinesView.EditLinesDelete(CY, 1) else if (Cy = FTheLinesView.Count) and (FTheLinesView[CY-1] <> '') then FTheLinesView.EditDelete(1, Cy, length(FTheLinesView[Cy-1])); if Command = ecDeleteLineKeepX then FCaret.ValidateXPos else CaretXY := Point(1, CY); // like seen in the Delphi editor end; ecClearAll: begin if not ReadOnly then ClearAll; end; ecInsertLine, ecLineBreak: if not ReadOnly then begin if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then SetSelTextExternal(''); Temp := LineText; LogCaretXY:=LogicalCaretXY; Len := Length(Temp); if LogCaretXY.X > Len + 1 then LogCaretXY.X := Len + 1; FTheLinesView.EditLineBreak(LogCaretXY.X, LogCaretXY.Y); if Command = ecLineBreak then CaretXY := Point(1, CaretY + 1) else CaretXY := CaretXY; end; ecTab: if not ReadOnly then try FCaret.IncForcePastEOL; DoTabKey; finally FCaret.DecForcePastEOL; end; ecShiftTab: if not ReadOnly then if SelAvail and (eoTabIndent in Options) then DoBlockUnindent; ecMatchBracket: FindMatchingBracket; ecChar: if not ReadOnly and ((AChar = #9) or (AChar >= #32)) and (AChar <> #127) then begin if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then begin SetSelTextExternal(AChar); end else begin try FCaret.IncForcePastEOL; FCaret.IncForceAdjustToNextChar; LogCaretXY := FCaret.LineBytePos; Temp := LineText; Len := Length(Temp); if (not fInserting) and (LogCaretXY.X - 1 < Len) then begin counter := GetCharLen(Temp,LogCaretXY.X); FTheLinesView.EditDelete(LogCaretXY.X, LogCaretXY.Y, counter); Len := Len - counter; end; {$IFDEF USE_UTF8BIDI_LCL} // TODO: improve utf8bidi for tabs Len := VLength(LineText, drLTR); (*if Len < CaretX then Temp := StringOfChar(' ', CaretX - Len) else Temp := '' *) FTheLinesView.EditInsert(CaretX, LogCaretXY.Y, (*Temp +*) AChar); {$ELSE} FTheLinesView.EditInsert(LogCaretXY.X, LogCaretXY.Y, (*Temp +*) AChar); {$ENDIF} //CaretX := CaretX + 1; FCaret.BytePos := LogCaretXY.X + length(AChar); Include(fStateFlags, sfEnsureCursorPosForEditRight); finally FCaret.DecForceAdjustToNextChar; FCaret.DecForcePastEOL; end; end; end else if not ReadOnly and (AChar = #13) then begin // ecLineBreak is not assigned // Insert a linebreak, but do not apply any other functionality (such as indent) if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then SetSelTextExternal(''); LogCaretXY:=LogicalCaretXY; FTheLinesView.EditLineBreak(LogCaretXY.X, LogCaretXY.Y); CaretXY := Point(1, CaretY + 1); EnsureCursorPosVisible; end; ecUndo: begin if not ReadOnly then Undo; end; ecRedo: begin if not ReadOnly then Redo; end; ecGotoMarker0..ecGotoMarker9: begin if BookMarkOptions.EnableKeys then GotoBookMark(Command - ecGotoMarker0); end; ecSetMarker0..ecSetMarker9,ecToggleMarker0..ecToggleMarker9: begin if BookMarkOptions.EnableKeys then begin if (Command >= ecSetMarker0) and (Command <= ecSetMarker9) then CX := Command - ecSetMarker0 else CX := Command - ecToggleMarker0; if assigned(fBookMarks[CX]) then begin moveBkm := ((Command >= ecSetMarker0) and (Command <= ecSetMarker9)) or (fBookMarks[CX].Line <> CaretY); ClearBookMark(CX); if moveBkm then SetBookMark(CX, CaretX, CaretY, LeftChar, TopLine); end else //SetBookMark(CX, CaretX, CaretY); SetBookMark(CX, CaretX, CaretY, LeftChar, TopLine); end; // if BookMarkOptions.EnableKeys end; ecCut: begin if (not ReadOnly) and SelAvail then CutToClipboard; end; ecCopy: begin CopyToClipboard; end; ecPaste, ecPasteAsColumns: begin if not ReadOnly then PasteFromClipboard(Command = ecPasteAsColumns); end; ecCopyAdd, ecCutAdd: if SelAvail then begin Temp := Clipboard.AsText; Helper := SelText; if (Temp <> '') and (not (Temp[Length(Temp)] in [#10,#13, #9, #32])) and not( (Helper[1] in [#10,#13, #9, #32]) and ( (Length(Helper) = 1) or (not IsCombiningCodePoint(PChar(Helper)+1))) ) then Temp := Temp + ' '; Clipboard.AsText := Temp + Helper; if (not ReadOnly) and (Command = ecCutAdd) then SelText := ''; end; ecCopyCurrentLine, ecCutCurrentLine, ecCopyAddCurrentLine, ecCutAddCurrentLine: begin FInternalBlockSelection.AssignFrom(FBlockSelection); FInternalBlockSelection.ActiveSelectionMode := smLine; FInternalBlockSelection.ForceSingleLineSelected := True; Temp := ''; if (Command = ecCopyAddCurrentLine) or (Command = ecCutAddCurrentLine) then begin Temp := Clipboard.AsText; if (Temp <> '') and not (Temp[Length(Temp)] in [#10,#13]) then Temp := Temp + LineEnding; end; Clipboard.AsText := Temp + FInternalBlockSelection.SelText; if (not ReadOnly) and ((Command = ecCutCurrentLine) or (Command = ecCutAddCurrentLine)) then begin FCaret.IncAutoMoveOnEdit; FInternalBlockSelection.SelText := ''; FCaret.DecAutoMoveOnEdit; end; FInternalBlockSelection.ForceSingleLineSelected := False; end; ecMoveLineUp: if (not ReadOnly) then begin if FBlockSelection.SelAvail then CY := BlockBegin.Y else CY := FCaret.LinePos; if CY > 1 then begin InternalBeginUndoBlock; if not FBlockSelection.SelAvail then FBlockSelection.Clear; FBlockSelection.IncPersistentLock(sbpWeak); if SelAvail and (BlockEnd.x = 1) then FTheLinesView.EditLinesInsert(BlockEnd.y, 1, FTheLinesView[ToIdx(CY) - 1]) else FTheLinesView.EditLinesInsert(BlockEnd.y + 1, 1, FTheLinesView[ToIdx(CY) - 1]); FCaret.IncAutoMoveOnEdit; FTheLinesView.EditLinesDelete(CY - 1, 1); FCaret.DecAutoMoveOnEdit; FBlockSelection.DecPersistentLock; InternalEndUndoBlock; end; end; ecMoveLineDown: if (not ReadOnly) then begin if FBlockSelection.SelAvail then begin CY := BlockEnd.Y; if (BlockEnd.x = 1) then Dec(CY); end else CY := FCaret.LinePos; if CY < FTheLinesView.Count - 1 then begin InternalBeginUndoBlock; if not FBlockSelection.SelAvail then FBlockSelection.Clear; FBlockSelection.IncPersistentLock(sbpWeak); FCaret.IncAutoMoveOnEdit; FTheLinesView.EditLinesInsert(BlockBegin.y, 1, FTheLinesView[ToIdx(CY) + 1]); FTheLinesView.EditLinesDelete(CY + 2, 1); FCaret.DecAutoMoveOnEdit; FBlockSelection.DecPersistentLock; InternalEndUndoBlock; end; end; ecDuplicateLine: if (not ReadOnly) then begin InternalBeginUndoBlock; if not FBlockSelection.SelAvail then FBlockSelection.Clear; FBlockSelection.IncPersistentLock(sbpWeak); FInternalBlockSelection.AssignFrom(FBlockSelection); if FInternalBlockSelection.IsBackwardSel then begin FInternalBlockSelection.StartLineBytePos := FBlockSelection.EndLineBytePos; FInternalBlockSelection.EndLineBytePos := FBlockSelection.StartLineBytePos; end; FInternalBlockSelection.ActiveSelectionMode := smLine; FInternalBlockSelection.ForceSingleLineSelected := True; If (FInternalBlockSelection.EndBytePos = 1) and (FInternalBlockSelection.EndLinePos > FInternalBlockSelection.StartLinePos) then FInternalBlockSelection.EndLineBytePos := Point(1, FInternalBlockSelection.EndLinePos-1); Temp := FInternalBlockSelection.SelText; FInternalBlockSelection.ForceSingleLineSelected := False; FInternalBlockSelection.StartLineBytePos := Point(1, FInternalBlockSelection.LastLineBytePos.y+1); FInternalBlockSelection.SelText := Temp; FBlockSelection.DecPersistentLock; InternalEndUndoBlock; end; ecMoveSelectUp, ecMoveSelectDown, ecMoveSelectLeft, ecMoveSelectRight: if (not ReadOnly) and SelAvail then begin InternalBeginUndoBlock; if FLastCaretXForMoveSelection < 0 then FLastCaretXForMoveSelection := FBlockSelection.FirstLineBytePos.x; CurSm := FBlockSelection.ActiveSelectionMode; CurBack := FBlockSelection.IsBackwardSel; Temp := FBlockSelection.SelText; if CurSm = smColumn then FCaret.IncForcePastEOL; SetSelTextExternal(''); FCaret.LineBytePos := FBlockSelection.StartLineBytePos; if (Command = ecMoveSelectUp) or (Command = ecMoveSelectDown) then FCaret.KeepCaretXPos := FLastCaretXForMoveSelection; case Command of ecMoveSelectUp: MoveCaretVert(-1); ecMoveSelectDown: MoveCaretVert(1); ecMoveSelectLeft: FCaret.MoveHoriz(-1); ecMoveSelectRight: FCaret.MoveHoriz(1); end; FBlockSelection.Clear; FBlockSelection.SetSelTextPrimitive(CurSm, PChar(Temp), False, True); if CurBack then begin FBlockSelection.SortSelectionPoints(True); FCaret.LineBytePos := FBlockSelection.EndLineBytePos; end; if (Command = ecMoveSelectUp) or (Command = ecMoveSelectDown) then FCaret.KeepCaretXPos := FLastCaretXForMoveSelection; if CurSm = smColumn then FCaret.DecForcePastEOL; InternalEndUndoBlock; end; ecDuplicateSelection: if (not ReadOnly) and SelAvail then begin InternalBeginUndoBlock; FCaret.IncForcePastEOL; CurSm := FBlockSelection.ActiveSelectionMode; CurBack := FBlockSelection.IsBackwardSel; Temp := FBlockSelection.SelText; FCaret.LineBytePos := FBlockSelection.FirstLineBytePos; FBlockSelection.Clear; FBlockSelection.SetSelTextPrimitive(CurSm, PChar(Temp), False, True); if CurBack then FBlockSelection.SortSelectionPoints(True); FCaret.DecForcePastEOL; InternalEndUndoBlock; end; ecScrollUp: begin TopView := TopView - 1; if CaretY > ScreenRowToRow(LinesInWindow-1) then CaretY := ScreenRowToRow(LinesInWindow-1); end; ecScrollDown: begin TopView := TopView + 1; if CaretY < TopLine then CaretY := TopLine; end; ecScrollLeft: begin LeftChar := LeftChar - 1; if CaretX > LeftChar + CharsInWindow then CaretX := LeftChar + CharsInWindow; end; ecScrollRight: begin LeftChar := LeftChar + 1; if CaretX < LeftChar then CaretX := LeftChar; end; ecInsertMode: begin InsertMode := TRUE; end; ecOverwriteMode: begin InsertMode := FALSE; end; ecToggleMode: begin InsertMode := not InsertMode; end; ecBlockSetBegin: begin FBlockSelection.Hide := CompareCarets(FCaret.LineBytePos, FBlockSelection.EndLineBytePos) <= 0; FBlockSelection.StartLineBytePosAdjusted := FCaret.LineBytePos; end; ecBlockSetEnd: begin FBlockSelection.Hide := CompareCarets(FCaret.LineBytePos, FBlockSelection.StartLineBytePos) >= 0; FBlockSelection.EndLineBytePos := FCaret.LineBytePos; end; ecBlockToggleHide: begin FBlockSelection.Hide := not FBlockSelection.Hide; end; ecBlockHide: begin FBlockSelection.Hide := True; end; ecBlockShow: begin FBlockSelection.Hide := False; end; ecBlockMove: begin if SelAvail then begin helper := FBlockSelection.SelText; FInternalBlockSelection.AssignFrom(FBlockSelection); FBlockSelection.IncPersistentLock; FBlockSelection.StartLineBytePos := FCaret.LineBytePos; // Track the Adjustment of the insert position FInternalBlockSelection.SelText := ''; FCaret.LineBytePos := FBlockSelection.StartLineBytePos; Caret := FCaret.LineBytePos; FBlockSelection.SelText := Helper; FBlockSelection.DecPersistentLock; CaretNew := FCaret.LineBytePos; FBlockSelection.StartLineBytePos := Caret; FBlockSelection.EndLineBytePos := CaretNew; end; end; ecBlockCopy: begin if SelAvail then InsertTextAtCaret(FBlockSelection.SelText, scamEnd); end; ecBlockDelete: begin if SelAvail then FBlockSelection.SelText := ''; end; ecBlockGotoBegin: begin FCaret.LineBytePos := FBlockSelection.FirstLineBytePos; end; ecBlockGotoEnd: begin FCaret.LineBytePos := FBlockSelection.LastLineBytePos; end; ecBlockIndent, ecBlockIndentMove: if not ReadOnly then DoBlockIndent(Command = ecBlockIndentMove); ecBlockUnindent, ecBlockUnindentMove: if not ReadOnly then DoBlockUnindent(Command = ecBlockUnindentMove); ecColumnBlockShiftRight, ecColumnBlockMoveRight: if not ReadOnly then DoBlockIndentColSel(Command = ecColumnBlockMoveRight, True); ecColumnBlockShiftLeft, ecColumnBlockMoveLeft: if not ReadOnly then DoBlockUnindentColSel(Command = ecColumnBlockMoveLeft, True); ecNormalSelect, ecColumnSelect, ecLineSelect: begin DefaultSelectionMode := SEL_MODE[Command]; end; EcToggleMarkupWord: FMarkupHighCaret.ToggleCurrentWord; ecZoomOut, ecZoomIn: begin if not (( (Command = ecZoomOut) and (abs(Font.Height) < 3) ) or ( (Command = ecZoomIn) and (abs(Font.Height) > 50) )) then begin CY := 1; if Command = ecZoomIn then CY := -1; CX := FLastSetFontSize; if Font.Height < 0 then Font.Height := Font.Height + CY else Font.Height := Font.Height - CY; FLastSetFontSize := CX; end; end; ecZoomNorm: begin Font.Height := FLastSetFontSize; end; end; finally DecPaintLock; DecLCLRefCount; end; end; procedure TCustomSynEdit.DoOnCommandProcessed(Command: TSynEditorCommand; AChar: TUTF8Char; Data: pointer); begin if Assigned(fOnCommandProcessed) then fOnCommandProcessed(Self, Command, AChar, Data); end; procedure TCustomSynEdit.DoOnProcessCommand(var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer); begin //DebugLn(['TCustomSynEdit.DoOnProcessCommand Command=',Command]); if Command < ecUserFirst then begin if Assigned(FOnProcessCommand) then FOnProcessCommand(Self, Command, AChar, Data); end else begin if Assigned(FOnProcessUserCommand) then FOnProcessUserCommand(Self, Command, AChar, Data); end; end; procedure TCustomSynEdit.ClearAll; begin InternalBeginUndoBlock; try SelectAll; SelText:=''; finally InternalEndUndoBlock; end; end; procedure TCustomSynEdit.ClearSelection; begin if SelAvail then SelText := ''; end; function TCustomSynEdit.GetSelectionMode : TSynSelectionMode; begin Result := fBlockSelection.ActiveSelectionMode; end; procedure TCustomSynEdit.SetSelectionMode(const Value: TSynSelectionMode); begin fBlockSelection.ActiveSelectionMode := Value; end; procedure TCustomSynEdit.InternalBeginUndoBlock(aList: TSynEditUndoList); begin if aList = nil then aList := fUndoList; {$IFDEF SynUndoDebugBeginEnd} DebugLnEnter(['>> TCustomSynEdit.InternalBeginUndoBlock', DbgSName(self), ' ', dbgs(Self), ' aList=', aList, ' FPaintLock=', FPaintLock, ' InGroupCount=',aList.InGroupCount]); {$ENDIF} aList.OnNeedCaretUndo := @GetCaretUndo; aList.BeginBlock; IncPaintLock; end; procedure TCustomSynEdit.InternalEndUndoBlock(aList: TSynEditUndoList); begin if aList = nil then aList := fUndoList; DecPaintLock; aList.EndBlock; // Todo: Doing this after DecPaintLock, can cause duplicate calls to StatusChanged(scModified) {$IFDEF SynUndoDebugBeginEnd} DebugLnExit(['<< TCustomSynEdit.InternalEndUndoBlock', DbgSName(self), ' ', dbgs(Self), ' aList=', aList, ' FPaintLock=', FPaintLock, ' InGroupCount=',aList.InGroupCount]); {$ENDIF} end; procedure TCustomSynEdit.BeginUndoBlock{$IFDEF SynUndoDebugBeginEnd}(ACaller: String = ''){$ENDIF}; begin {$IFDEF SynUndoDebugBeginEnd} DebugLnEnter(['>> TCustomSynEdit.BeginUndoBlock ', DbgSName(self), ' ', dbgs(Self), ' Caller=', ACaller, ' FPaintLock=', FPaintLock, ' InGroupCount=',fUndoList.InGroupCount, ' FIsInDecPaintLock=',dbgs(FIsInDecPaintLock)]); if ACaller = '' then DumpStack; {$ENDIF} fUndoList.OnNeedCaretUndo := @GetCaretUndo; fUndoList.BeginBlock; //FTrimmedLinesView.Lock; end; procedure TCustomSynEdit.BeginUpdate(WithUndoBlock: Boolean = True); begin IncPaintLock; {$IFDEF SynUndoDebugBeginEnd} if WithUndoBlock and (FPaintLock = 0) and (FUndoBlockAtPaintLock = 0) then DebugLn(['************** TCustomSynEdit.BeginUpdate PAINTLOCK NOT INCREASED ', DbgSName(self), ' ', dbgs(Self), ' FPaintLock=', FPaintLock, ' InGroupCount=',fUndoList.InGroupCount, ' FIsInDecPaintLock=',dbgs(FIsInDecPaintLock)]); {$ENDIF} if WithUndoBlock and (FPaintLock > 0) and (FUndoBlockAtPaintLock = 0) then begin FUndoBlockAtPaintLock := FPaintLock; BeginUndoBlock{$IFDEF SynUndoDebugBeginEnd}('SynEdit.BeginUpdate'){$ENDIF}; end; end; procedure TCustomSynEdit.EndUndoBlock{$IFDEF SynUndoDebugBeginEnd}(ACaller: String = ''){$ENDIF}; begin // Write all trimming info to the end of the undo block, // so it will be undone first, and other UndoItems do see the expected spaces //FTrimmedLinesView.UnLock; ////FFoldedLinesView.UnLock; fUndoList.EndBlock; {$IFDEF SynUndoDebugBeginEnd} DebugLnExit(['<< TCustomSynEdit.EndUndoBlock', DbgSName(self), ' ', dbgs(Self), ' Caller=', ACaller, ' FPaintLock=', FPaintLock, ' InGroupCount=',fUndoList.InGroupCount, ' FIsInDecPaintLock=',dbgs(FIsInDecPaintLock)]); //if ACaller = '' then DumpStack; {$ENDIF} end; procedure TCustomSynEdit.EndUpdate; begin DecPaintLock; end; procedure TCustomSynEdit.AddKey(Command: TSynEditorCommand; Key1: word; SS1: TShiftState; Key2: word; SS2: TShiftState); var Key: TSynEditKeyStroke; begin Key := Keystrokes.Add; Key.Command := Command; Key.Key := Key1; Key.Shift := SS1; Key.Key2 := Key2; Key.Shift2 := SS2; end; procedure TCustomSynEdit.AfterLoadFromFile; begin if WaitingForInitialSize or ( (FPaintLock > 0) and not((FPaintLock = 1) and FIsInDecPaintLock) ) then begin Include(fStateFlags, sfAfterLoadFromFileNeeded); exit; end; Exclude(fStateFlags, sfAfterLoadFromFileNeeded); if assigned(FFoldedLinesView) then begin ScanRanges; FFoldedLinesView.UnfoldAll; FFoldedLinesView.CollapseDefaultFolds; if FPendingFoldState <> '' then SetFoldState(FPendingFoldState); TopView := TopView; end; end; procedure TCustomSynEdit.MarkListChange(Sender: TSynEditMark; Changes: TSynEditMarkChangeReasons); begin if (smcrAdded in Changes) and Sender.IsBookmark then begin FBookMarks[Sender.BookmarkNumber] := Sender; if Assigned(FOnPlaceMark) then FOnPlaceMark(Self, Sender); end; if (smcrRemoved in Changes) and Sender.IsBookmark then begin FBookMarks[Sender.BookmarkNumber] := nil; if Assigned(FOnClearMark) then FOnClearMark(Self, Sender); end; if (not Sender.Visible) and (not (smcrVisible in Changes)) then exit; if smcrLine in Changes then begin InvalidateLine(Sender.OldLine); // TODO: only if mark has special line color, or other code markup InvalidateGutterLines(Sender.OldLine, Sender.OldLine); end; InvalidateLine(Sender.Line); // TODO: only if mark has special line color, or other code markup InvalidateGutterLines(Sender.Line, Sender.Line); end; function TCustomSynEdit.GetSelStart: integer; //L505 begin function llen(const data: string): integer; begin result := length(Data) + length(LineEnding); end; var loop: integer; p: TPoint; begin if SelAvail then begin p:=BlockBegin; end else begin p:=LogicalCaretXY; end; result := 0; loop := 0; while (loop < (p.Y - 1)) and (loop < FTheLinesView.Count) do begin result := result + llen(FTheLinesView[loop]); inc(loop); end; if loop < FTheLinesView.Count then result := result + Min(p.X, length(FTheLinesView[loop]) + 1); end; procedure TCustomSynEdit.SetSelStart(const Value: integer); function llen(const data: string): integer; begin result := length(Data) + length(LineEnding); end; var loop: integer; count: integer; begin assert(Value > 0, 'SelStart must be >= 1'); loop := 0; count := 0; while (loop < FTheLinesView.Count) and (count + llen(FTheLinesView[loop]) < value) do begin count := count + llen(FTheLinesView[loop]); inc(loop); end; { CaretX := value - count; CaretY := loop + 1; fBlockBegin.X := CaretX; fBlockBegin.Y := CaretY;} //This seems the same as above, but uses the other fixes inside of SetCaretXY //to adjust the cursor pos correctly. FCaret.LineBytePos := Point(value - count, loop + 1); BlockBegin := Point(value - count, loop + 1); end; function TCustomSynEdit.GetSelEnd: integer; function llen(const data: string): integer; begin result := length(Data) + length(LineEnding); end; var loop: integer; p: TPoint; begin if SelAvail then begin p := BlockEnd; end else begin p := LogicalCaretXY; end; result := 0; loop := 0; while (loop < (p.y - 1)) and (loop < FTheLinesView.Count) do begin Result := result + llen(FTheLinesView[loop]); inc(loop); end; if loop<FTheLinesView.Count then result := result + p.x; end; procedure TCustomSynEdit.SetSelEnd(const Value: integer); function llen(const data: string): integer; begin result := length(Data) + length(LineEnding); end; var p: TPoint; loop: integer; count: integer; begin assert(Value > 0, 'SelEnd must be >= 1'); loop := 0; count := 0; while (loop < FTheLinesView.Count) and (count + llen(FTheLinesView[loop]) < value) do begin count := count + llen(FTheLinesView.strings[loop]); inc(loop); end; p.x := value - count; p.y := loop + 1; BlockEnd := p; end; procedure TCustomSynEdit.SetExtraLineSpacing(const Value: integer); begin if ExtraLineSpacing=Value then exit; inherited; FPaintArea.ExtraLineSpacing := Value; FontChanged(self); end; function TCustomSynEdit.GetBookMark(BookMark: integer; var X, Y: integer): boolean; var i: integer; begin Result := false; if assigned(Marks) then for i := 0 to Marks.Count - 1 do if Marks[i].IsBookmark and (Marks[i].BookmarkNumber = BookMark) then begin X := Marks[i].Column; Y := Marks[i].Line; X := LogicalToPhysicalPos(Point(X, Y)).x; Result := true; Exit; end; end; function TCustomSynEdit.GetBookMark(BookMark: integer; var X, Y, ALeft, ATop: integer): boolean; var i: integer; m: TSynEditMark; begin Result := false; if assigned(Marks) then for i := 0 to Marks.Count - 1 do if Marks[i].IsBookmark and (Marks[i].BookmarkNumber = BookMark) then begin m := Marks[i]; X := m.Column; Y := m.Line; X := LogicalToPhysicalPos(Point(X, Y)).x; if (m is TSynEditBookMark) and (TSynEditBookMark(m).TopLeftMark <> nil) then begin ALeft := TSynEditBookMark(m).TopLeftMark.Column; ATop := TSynEditBookMark(m).TopLeftMark.Line; end; Result := true; Exit; end; end; function TCustomSynEdit.IsBookmark(BookMark: integer): boolean; var x, y: integer; begin Result := GetBookMark(BookMark, x{%H-}, y{%H-}); end; procedure TCustomSynEdit.MarkTextAsSaved; begin TSynEditStringList(fLines).MarkSaved; if FLeftGutter.Visible and (FLeftGutter.ChangesPart(0) <> nil) and FLeftGutter.ChangesPart(0).Visible then InvalidateGutter; // Todo: Make the ChangeGutterPart an observer end; procedure TCustomSynEdit.ClearUndo; begin fUndoList.Clear; fRedoList.Clear; end; procedure TCustomSynEdit.SetGutter(const Value: TSynGutter); begin FLeftGutter.Assign(Value); end; procedure TCustomSynEdit.SetRightGutter(const AValue: TSynGutter); begin FRightGutter.Assign(AValue); end; procedure TCustomSynEdit.GutterChanged(Sender: TObject); begin if (csLoading in ComponentState) then exit; InvalidateGutter; //Todo: move to gutter end; procedure TCustomSynEdit.GutterResized(Sender: TObject); begin if (csLoading in ComponentState) then exit; GutterChanged(Sender); if (FDoingResizeLock <> 0) then begin Include(fStateFlags, sfGutterResized); exit end; if not WaitingForInitialSize then begin RecalcCharsAndLinesInWin(False); UpdateScrollBars; Invalidate; end; end; function TCustomSynEdit.TextLeftPixelOffset(IncludeGutterTextDist: Boolean): Integer; begin if FLeftGutter.Visible then begin Result := FLeftGutter.Width; if IncludeGutterTextDist then inc(Result, GutterTextDist); end else begin Result := 0; if IncludeGutterTextDist then inc(Result, 1); // include space for caret at pos.x=1 (if FOffsetX = -1) end; end; function TCustomSynEdit.TextRightPixelOffset: Integer; begin if FRightGutter.Visible then Result := FRightGutter.Width else Result := 0; end; procedure TCustomSynEdit.LockUndo; begin fUndoList.Lock; fRedoList.Lock end; procedure TCustomSynEdit.UnlockUndo; begin fUndoList.Unlock; fRedoList.Unlock; end; procedure TCustomSynEdit.WMMouseWheel(var Message: TLMMouseEvent); var lState: TShiftState; MousePos: TPoint; AnActionResult: TSynEditMouseActionResult; begin if ((sfHorizScrollbarVisible in fStateFlags) and (Message.Y > ClientHeight)) or ((sfVertScrollbarVisible in fStateFlags) and (Message.X > ClientWidth)) then begin // mouse is over scrollbar inherited; // include OnMouseWheel...; exit; end; MousePos.X := Message.X; MousePos.Y := Message.Y; if DoMouseWheel(Message.State, Message.WheelDelta, MousePos) then begin Message.Result := 1; // handled exit; end; lState := Message.State - [ssCaps, ssNum, ssScroll]; // Remove unreliable states, see http://bugs.freepascal.org/view.php?id=20065 IncPaintLock; try if Message.WheelDelta > 0 then begin FindAndHandleMouseAction(mbXWheelUp, lState, Message.X, Message.Y, ccSingle, cdDown, AnActionResult, Message.WheelDelta); end else begin // send megative delta FindAndHandleMouseAction(mbXWheelDown, lState, Message.X, Message.Y, ccSingle, cdDown, AnActionResult, Message.WheelDelta); end; finally DecPaintLock; end; DoHandleMouseActionResult(AnActionResult); Message.Result := 1 // handled, skip further handling by interface end; procedure TCustomSynEdit.WMMouseHorizWheel(var Message: TLMMouseEvent); var lState: TShiftState; MousePos: TPoint; AnActionResult: TSynEditMouseActionResult; begin if ((sfHorizScrollbarVisible in fStateFlags) and (Message.Y > ClientHeight)) or ((sfVertScrollbarVisible in fStateFlags) and (Message.X > ClientWidth)) then begin // mouse is over scrollbar inherited; // include OnMouseWheel...; exit; end; MousePos.X := Message.X; MousePos.Y := Message.Y; if DoMouseWheelHorz(Message.State, Message.WheelDelta, MousePos) then begin Message.Result := 1; // handled exit; end; lState := Message.State - [ssCaps, ssNum, ssScroll]; // Remove unreliable states, see http://bugs.freepascal.org/view.php?id=20065 IncPaintLock; try if Message.WheelDelta > 0 then begin FindAndHandleMouseAction(mbXWheelLeft, lState, Message.X, Message.Y, ccSingle, cdDown, AnActionResult, Message.WheelDelta); end else begin // send megative delta FindAndHandleMouseAction(mbXWheelRight, lState, Message.X, Message.Y, ccSingle, cdDown, AnActionResult, Message.WheelDelta); end; finally DecPaintLock; end; DoHandleMouseActionResult(AnActionResult); Message.Result := 1 // handled, skip further handling by interface end; procedure TCustomSynEdit.SetWantTabs(const Value: boolean); begin fWantTabs := Value; end; procedure TCustomSynEdit.SetTabWidth(Value: integer); begin Value := MinMax(Value, 1{0}, 256); if (Value <> fTabWidth) then begin fTabWidth := Value; FTabbedLinesView.TabWidth := Value; Invalidate; // to redraw text containing tab chars end; end; // find / replace function TCustomSynEdit.SearchReplace(const ASearch, AReplace: string; AOptions: TSynSearchOptions): integer; var StartPos: TPoint; begin if (ssoFindContinue in AOptions) and SelAvail then begin if ssoBackwards in AOptions then StartPos := BlockBegin else StartPos := BlockEnd; end else StartPos := LogicalCaretXY; Result := SearchReplaceEx(ASearch, AReplace, AOptions, StartPos); end; function TCustomSynEdit.SearchReplaceEx(const ASearch, AReplace: string; AOptions: TSynSearchOptions; AStart: TPoint): integer; var ptStart, ptEnd: TPoint; // start and end of the search range ptCurrent: TPoint; // current search position nFound: integer; bBackward, bFromCursor: boolean; bPrompt: boolean; bReplace, bReplaceAll, SelIsColumn, ZeroLen: boolean; nAction: TSynReplaceAction; CurReplace: string; ptFoundStart, ptFoundEnd: TPoint; ptFoundStartSel, ptFoundEndSel: TPoint; ReplaceBlockSelection: TSynEditSelection; function InValidSearchRange(First, Last: integer): boolean; begin Result := TRUE; case FBlockSelection.ActiveSelectionMode of smNormal: if ((ptCurrent.Y = ptStart.Y) and (First < ptStart.X)) or ((ptCurrent.Y = ptEnd.Y) and (Last > ptEnd.X)) then Result := FALSE; smColumn: Result := (First >= ptStart.X) and (Last <= ptEnd.X); end; end; procedure SetFoundCaretAndSel; begin if ptFoundStartSel.y < 0 then exit; BlockBegin := ptFoundStartSel; if bBackward then LogicalCaretXY := BlockBegin; BlockEnd := ptFoundEndSel; if not bBackward then LogicalCaretXY := ptFoundEndSel; end; begin Result := 0; ReplaceBlockSelection := nil; // can't search for or replace an empty string if Length(ASearch) = 0 then exit; // get the text range to search in, ignore the "Search in selection only" // option if nothing is selected bBackward := (ssoBackwards in AOptions); bPrompt := (ssoPrompt in AOptions); bReplace := (ssoReplace in AOptions); bReplaceAll := (ssoReplaceAll in AOptions); bFromCursor := not (ssoEntireScope in AOptions); SelIsColumn := False; if not SelAvail then Exclude(AOptions, ssoSelectedOnly); if (ssoSelectedOnly in AOptions) then begin ptStart := BlockBegin; ptEnd := BlockEnd; // search the whole line in the line selection mode if (FBlockSelection.ActiveSelectionMode = smLine) then begin ptStart.X := 1; ptEnd.X := Length(FTheLinesView[ptEnd.Y - 1]) + 1; end else if (FBlockSelection.ActiveSelectionMode = smColumn) then // make sure the start column is smaller than the end column if (ptStart.X > ptEnd.X) then begin nFound := ptStart.X; ptStart.X := ptEnd.X; ptEnd.X := nFound; end; // ignore the cursor position when searching in the selection if bBackward then ptCurrent := ptEnd else ptCurrent := ptStart; SelIsColumn := FBlockSelection.ActiveSelectionMode = smColumn; ReplaceBlockSelection := TSynEditSelection.Create(FTheLinesView, False); ReplaceBlockSelection.AssignFrom(FBlockSelection); end else begin ptStart := Point(1, 1); ptEnd.Y := FTheLinesView.Count; ptEnd.X := Length(FTheLinesView[ptEnd.Y - 1]) + 1; if bFromCursor then if bBackward then ptEnd := AStart else ptStart := AStart; if bBackward then ptCurrent := ptEnd else ptCurrent := ptStart; end; // initialize the search engine fTSearch.Sensitive := ssoMatchCase in AOptions; fTSearch.Whole := ssoWholeWord in AOptions; fTSearch.Pattern := ASearch; fTSearch.RegularExpressions := ssoRegExpr in AOptions; fTSearch.RegExprMultiLine := ssoRegExprMultiLine in AOptions; fTSearch.Replacement:=AReplace; fTSearch.Backwards:=bBackward; // search while the current search position is inside of the search range IncPaintLock; BeginUndoBlock{$IFDEF SynUndoDebugBeginEnd}('SynEdit.SearchReplaceEx'){$ENDIF}; try ptFoundStartSel.y := -1; //DebugLn(['TCustomSynEdit.SearchReplace ptStart=',dbgs(ptStart),' ptEnd=',dbgs(ptEnd),' ASearch="',dbgstr(ASearch),'" AReplace="',dbgstr(AReplace),'"']); while fTSearch.FindNextOne(FTheLinesView,ptStart,ptEnd,ptFoundStart,ptFoundEnd, True) do begin //DebugLn(['TCustomSynEdit.SearchReplace FOUND ptStart=',dbgs(ptStart),' ptEnd=',dbgs(ptEnd),' ptFoundStart=',dbgs(ptFoundStart),' ptFoundEnd=',dbgs(ptFoundEnd)]); // check if found place is entirely in range ZeroLen := ptFoundStart = ptFoundEnd; if ssoRegExpr in AOptions then begin ptFoundStart.X := FTheLinesView.LogicPosAdjustToChar(FTheLinesView[ToIdx(ptFoundStart.Y)], ptFoundStart.X, False); ptFoundEnd.X := FTheLinesView.LogicPosAdjustToChar(FTheLinesView[ToIdx(ptFoundEnd.Y)], ptFoundEnd.X, True); end; if ( (not SelIsColumn) or ( (ptFoundStart.Y=ptFoundEnd.Y) and (ptFoundStart.X >= ReplaceBlockSelection.ColumnStartBytePos[ptFoundStart.Y]) and (ptFoundEnd.X <= ReplaceBlockSelection.ColumnEndBytePos[ptFoundStart.Y]) ) ) and ( not( ZeroLen and (ptStart = ptFoundStart) and (ssoFindContinue in AOptions) and (not SelAvail) ) ) and ( ZeroLen = (ptFoundStart = ptFoundEnd) ) then begin // pattern found Inc(Result); // Select the text, so the user can see it in the OnReplaceText event // handler or as the search result. ptFoundStartSel := ptFoundStart; ptFoundEndSel := ptFoundEnd; //SetFoundCaretAndSel; // If it's a 'search' only we can leave the procedure now. if not (bReplace or bReplaceAll) then exit; // Prompt and replace or replace all. If user chooses to replace // all after prompting, turn off prompting. CurReplace:=AReplace; if ssoRegExpr in AOptions then CurReplace:=fTSearch.RegExprReplace; if bPrompt and Assigned(fOnReplaceText) then begin SetFoundCaretAndSel; EnsureCursorPosVisible; try EndUndoBlock; DecPaintLock; nAction := DoOnReplaceText(ASearch,CurReplace, ptFoundStart.Y,ptFoundStart.X); finally IncPaintLock; BeginUndoBlock{$IFDEF SynUndoDebugBeginEnd}('SynEdit.SearchReplaceEx(prompt)'){$ENDIF}; end; if nAction = raCancel then exit; end else nAction := raReplace; if not (nAction = raSkip) then begin // user has been prompted and has requested to silently replace all // so turn off prompting if nAction = raReplaceAll then begin bReplaceAll := True; bPrompt := False; end; // replace text //DebugLn(['TCustomSynEdit.SearchReplace OldSel="',dbgstr(SelText),'"']); //SetSelTextExternal(CurReplace); SetTextBetweenPoints(ptFoundStart, ptFoundEnd, CurReplace, [setSelect], scamIgnore); //DebugLn(['TCustomSynEdit.SearchReplace NewSel="',dbgstr(SelText),'"']); // adjust positions ptEnd:=AdjustPositionAfterReplace(ptEnd,ptFoundStart,ptFoundEnd, CurReplace); ptFoundEnd:=AdjustPositionAfterReplace(ptFoundEnd, ptFoundStart,ptFoundEnd,CurReplace); ptFoundEndSel := ptFoundEnd; end; if not bReplaceAll then exit; end; Exclude(AOptions, ssoFindContinue); // ZeroLen will now be handled below // shrink search range for next search if ssoSearchInReplacement in AOptions then begin if bBackward then begin ptEnd:=ptFoundEnd; end else begin ptStart:=ptFoundStart; end; end else begin if bBackward then begin ptEnd:=ptFoundStart; end else begin ptStart:=ptFoundEnd; end; end; if ZeroLen then begin FInternalCaret.Invalidate; FInternalCaret.LineBytePos := ptStart; if bBackward then begin if not FInternalCaret.MoveHoriz(-1) then ptStart := Point(length(FTheLinesView[ptStart.Y - 1]), ptStart.Y - 1) else ptStart := FInternalCaret.LineBytePos; end else begin if not FInternalCaret.MoveHoriz(1) then ptStart := Point(1, ptStart.Y + 1) else ptStart := FInternalCaret.LineBytePos; end; end; //DebugLn(['TCustomSynEdit.SearchReplace FIND NEXT ptStart=',dbgs(ptStart),' ptEnd=',dbgs(ptEnd)]); end; finally SetFoundCaretAndSel; FreeAndNil(ReplaceBlockSelection); EndUndoBlock; DecPaintLock; end; end; function TCustomSynEdit.IsPointInSelection(Value: TPoint; AnIgnoreAtSelectionBound: Boolean): boolean; var ptBegin, ptEnd: TPoint; i: Integer; begin Result := SelAvail; if not Result then exit; ptBegin := BlockBegin; ptEnd := BlockEnd; Result := (Value.Y >= ptBegin.Y) and (Value.Y <= ptEnd.Y); if not Result then exit; if AnIgnoreAtSelectionBound then i := 0 else i := 1; case FBlockSelection.ActiveSelectionMode of smLine: begin Result := TRUE; end; smColumn: begin Result := (Value.x > FBlockSelection.ColumnStartBytePos[Value.y] - i) and (Value.x < FBlockSelection.ColumnEndBytePos[Value.y] + i); end; else begin Result := ( (Value.Y > ptBegin.Y) or (Value.X > ptBegin.X - i) ) and ( (Value.Y < ptEnd.Y) or (Value.X < ptEnd.X + i) ); end; end; end; procedure TCustomSynEdit.SetOptions(Value: TSynEditorOptions); var ChangedOptions: TSynEditorOptions; m: TSynEditorOption; MOpt: TSynEditorMouseOptions; f: Boolean; begin Value := Value - SYNEDIT_UNIMPLEMENTED_OPTIONS; if (Value = FOptions) then exit; ChangedOptions:=(FOptions-Value)+(Value-FOptions); FOptions := Value; UpdateOptions; if eoScrollPastEol in ChangedOptions then begin Include(fStateFlags, sfScrollbarChanged); LeftChar := LeftChar; if sfScrollbarChanged in fStateFlags then UpdateScrollbars; end; if (eoScrollPastEof in ChangedOptions) then begin UpdateScrollBars; TopView := TopView; end; // (un)register HWND as drop target if (eoDropFiles in ChangedOptions) and not (csDesigning in ComponentState) and (not WaitingForInitialSize) then ; // ToDo DragAcceptFiles if (ChangedOptions * [eoPersistentCaret, eoNoCaret] <> []) and (not WaitingForInitialSize) then begin UpdateCaret; UpdateScreenCaret; end; if (eoShowSpecialChars in ChangedOptions) then begin if eoShowSpecialChars in FOptions then FPaintArea.VisibleSpecialChars := VisibleSpecialChars else FPaintArea.VisibleSpecialChars := []; if HandleAllocated then Invalidate; end; fMarkupSpecialChar.Enabled := (eoShowSpecialChars in fOptions); if (eoHideRightMargin in ChangedOptions) then begin FPaintArea.RightEdgeVisible := not(eoHideRightMargin in FOptions); Invalidate; end; (* Deal with deprecated Mouse values Those are all controlled by mouse-actions. As long as the default mouse actions are set, the below will act as normal *) MOpt := MouseOptions; f := False; for m := low(SYNEDIT_OLD_MOUSE_OPTIONS_MAP) to high(SYNEDIT_OLD_MOUSE_OPTIONS_MAP) do if (m in SYNEDIT_OLD_MOUSE_OPTIONS) and (m in ChangedOptions) then begin f := True; if (m in FOptions) then MOpt := MOpt + [SYNEDIT_OLD_MOUSE_OPTIONS_MAP[m]] else MOpt := MOpt - [SYNEDIT_OLD_MOUSE_OPTIONS_MAP[m]]; end; if f then MouseOptions := MOpt; FOptions := Value; // undo changes applied by MouseOptions StatusChanged([scOptions]); end; procedure TCustomSynEdit.UpdateOptions; begin FTrimmedLinesView.Enabled := eoTrimTrailingSpaces in fOptions; FCaret.AllowPastEOL := (eoScrollPastEol in fOptions) or (fOptions2 * [eoScrollPastEolAddPage, eoScrollPastEolAutoCaret] <> []); FCaret.KeepCaretX := (eoKeepCaretX in fOptions); FBlockSelection.Enabled := not(eoNoSelection in fOptions); FUndoList.GroupUndo := eoGroupUndo in fOptions; end; procedure TCustomSynEdit.SetOptions2(Value: TSynEditorOptions2); var ChangedOptions: TSynEditorOptions2; begin if (Value <> fOptions2) then begin ChangedOptions := (fOptions2 - Value) + (Value - fOptions2); fOptions2 := Value; UpdateOptions2; if eoAlwaysVisibleCaret in fOptions2 then MoveCaretToVisibleArea; if (eoAutoHideCursor in ChangedOptions) and not(eoAutoHideCursor in fOptions2) then UpdateCursor; if (eoPersistentCaretStopBlink in ChangedOptions) then UpdateScreenCaret; if ChangedOptions * [eoScrollPastEolAddPage, eoScrollPastEolAutoCaret] <> [] then begin Include(fStateFlags, sfScrollbarChanged); LeftChar := LeftChar; if sfScrollbarChanged in fStateFlags then UpdateScrollbars; end; StatusChanged([scOptions]); end; end; procedure TCustomSynEdit.UpdateOptions2; begin FBlockSelection.Persistent := eoPersistentBlock in fOptions2; FCaret.SkipTabs := (eoCaretSkipTab in fOptions2); FCaret.AllowPastEOL := (eoScrollPastEol in fOptions) or (fOptions2 * [eoScrollPastEolAddPage, eoScrollPastEolAutoCaret] <> []); if Assigned(fMarkupSelection) then fMarkupSelection.ColorTillEol := eoColorSelectionTillEol in fOptions2; end; procedure TCustomSynEdit.SetMouseOptions(AValue: TSynEditorMouseOptions); var ChangedOptions: TSynEditorMouseOptions; m: TSynEditorOption; begin if MouseOptions = AValue then Exit; ChangedOptions := (MouseOptions-AValue)+(AValue-MouseOptions); inherited; // changes take effect when MouseActions are accessed for m := low(SYNEDIT_OLD_MOUSE_OPTIONS_MAP) to high(SYNEDIT_OLD_MOUSE_OPTIONS_MAP) do if (m in SYNEDIT_OLD_MOUSE_OPTIONS) and (SYNEDIT_OLD_MOUSE_OPTIONS_MAP[m] in ChangedOptions) and not(SYNEDIT_OLD_MOUSE_OPTIONS_MAP[m] in MouseOptions) then FOptions := FOptions - [m]; if (emShowCtrlMouseLinks in ChangedOptions) then begin if assigned(fMarkupCtrlMouse) then fMarkupCtrlMouse.UpdateCtrlMouse; UpdateCursor; end; StatusChanged([scOptions]); end; procedure TCustomSynEdit.UpdateMouseOptions; begin // end; procedure TCustomSynEdit.SetOptionFlag(Flag: TSynEditorOption; Value: boolean); begin if (Value <> (Flag in fOptions)) then begin if Value then Options := Options + [Flag] else Options := Options - [Flag]; end; end; procedure TCustomSynEdit.SizeOrFontChanged(bFont: boolean); begin if not WaitingForInitialSize then begin LastMouseCaret:=Point(-1,-1); //DebugLn('TCustomSynEdit.SizeOrFontChanged LinesInWindow=',dbgs(LinesInWindow),' ClientHeight=',dbgs(ClientHeight),' ',dbgs(LineHeight)); //debugln('TCustomSynEdit.SizeOrFontChanged A ClientWidth=',dbgs(ClientWidth),' FLeftGutter.Width=',dbgs(FLeftGutter.Width),' ScrollBarWidth=',dbgs(ScrollBarWidth),' CharWidth=',dbgs(CharWidth),' CharsInWindow=',dbgs(CharsInWindow),' Width=',dbgs(Width)); if bFont then Invalidate; Include(fStateFlags, sfScrollbarChanged); RecalcCharsAndLinesInWin(True); if sfScrollbarChanged in fStateFlags then UpdateScrollbars; end; end; procedure TCustomSynEdit.RecalcScrollOnEdit(Sender: TObject); begin FScrollOnEditLeftOptions.FCurrentDistance := Min(Max( FScrollOnEditLeftOptions.KeepBorderDistance, CharsInWindow * FScrollOnEditLeftOptions.KeepBorderDistancePercent div 100 ), Max(0, CharsInWindow - 1) div 2 ); FScrollOnEditLeftOptions.FCurrentColumns := Min(Min(Max( FScrollOnEditLeftOptions.ScrollExtraColumns, CharsInWindow * FScrollOnEditLeftOptions.ScrollExtraPercent div 100 ), FScrollOnEditLeftOptions.ScrollExtraMax ), Max(0, CharsInWindow - 1) div 2 ); FScrollOnEditRightOptions.FCurrentDistance := Min(Max( FScrollOnEditRightOptions.KeepBorderDistance, CharsInWindow * FScrollOnEditRightOptions.KeepBorderDistancePercent div 100 ), Max(0, CharsInWindow - 1) div 2 ); FScrollOnEditRightOptions.FCurrentColumns := Min(Min(Max( FScrollOnEditRightOptions.ScrollExtraColumns, CharsInWindow * FScrollOnEditRightOptions.ScrollExtraPercent div 100 ), FScrollOnEditRightOptions.ScrollExtraMax ), Max(0, CharsInWindow - 1) div 2 ); end; procedure TCustomSynEdit.RecalcCharsAndLinesInWin(CheckCaret: Boolean); var l, r: Integer; begin if FRecalcCharsAndLinesLock > 0 then exit; IncStatusChangeLock; try if FLeftGutter.Visible then l := FLeftGutter.Width else l := 0; if FRightGutter.Visible then r := FRightGutter.Width else r := 0; // TODO: lock FTextArea, so size re-calc is done once only FPaintArea.SetBounds(0, 0, ClientHeight - ScrollBarWidth, ClientWidth - ScrollBarWidth); FPaintArea.LeftGutterWidth := l; FPaintArea.RightGutterWidth := r; if FLeftGutter.Visible then FPaintArea.Padding[bsLeft] := GutterTextDist else FPaintArea.Padding[bsLeft] := 1; if FRightGutter.Visible then FPaintArea.Padding[bsRight] := 0 //GutterTextDist else FPaintArea.Padding[bsRight] := 0; FFoldedLinesView.LinesInWindow := LinesInWindow; FMarkupManager.LinesInWindow := LinesInWindow; FScreenCaret.Lock; FScreenCaret.ClipRect := FTextArea.Bounds; //FScreenCaret.ClipRect := Rect(TextLeftPixelOffset(False), 0, // ClientWidth - TextRightPixelOffset - ScrollBarWidth + 1, // ClientHeight - ScrollBarWidth); // TextBounds.Left => account for padding FScreenCaret.ClipExtraPixel := FTextArea.Bounds.Right - FTextArea.TextBounds.Left - CharsInWindow * CharWidth; UpdateCaret; FScreenCaret.UnLock; if CheckCaret then begin LeftChar := LeftChar; if not (eoScrollPastEof in Options) then TopView := TopView; end; RecalcScrollOnEdit(nil); finally DecStatusChangeLock; end; end; procedure TCustomSynEdit.MoveCaretHorz(DX: integer); var NewCaret: TPoint; s: String; begin // char or halfchar left/right DoIncPaintLock(Self); // No editing is taking place try if not FCaret.MoveHoriz(DX) then begin if DX < 0 then begin if (FCaret.LinePos > 1) and (not(eoScrollPastEol in fOptions)) and (fOptions2 * [eoScrollPastEolAddPage, eoScrollPastEolAutoCaret] = []) then begin // move to end of prev line NewCaret.Y:= ToPos(FTheLinesView.AddVisibleOffsetToTextIndex(ToIdx(FCaret.LinePos), -1)); if NewCaret.Y <> FCaret.LinePos then begin s:=FTheLinesView[NewCaret.Y-1]; NewCaret.X := length(s) + 1; FCaret.LineBytePos := NewCaret; end; end; end else begin if (not(eoScrollPastEol in fOptions)) and (fOptions2 * [eoScrollPastEolAddPage, eoScrollPastEolAutoCaret] = []) then begin // move to begin of next line NewCaret.Y:= ToPos(FTheLinesView.AddVisibleOffsetToTextIndex(ToIdx(FCaret.LinePos), +1)); if NewCaret.Y <= ToPos(FTheLinesView.ViewToTextIndex(ToIdx(FTheLinesView.ViewedCount))) then begin NewCaret.X := 1; FCaret.LineBytePos := NewCaret; end; end end; end; finally DoDecPaintLock(Self); end; end; procedure TCustomSynEdit.MoveCaretVert(DY: integer; UseScreenLine: Boolean); // moves Caret vertical DY unfolded lines var NewCaret: TPoint; begin DoIncPaintLock(Self); // No editing is taking place if UseScreenLine then begin FCaret.ViewedLinePos := FCaret.ViewedLinePos + DY; end else begin NewCaret:=CaretXY; NewCaret.Y:=ToPos(FTheLinesView.AddVisibleOffsetToTextIndex(ToIdx(NewCaret.Y), DY)); FCaret.LinePos := NewCaret.Y; end; DoDecPaintLock(Self); end; procedure TCustomSynEdit.SetCaretAndSelection(const ptCaret, ptBefore, ptAfter: TPoint; Mode: TSynSelectionMode; MakeSelectionVisible: Boolean; AForceSingleLineSelected: Boolean); // caret is physical (screen) // Before, After is logical (byte) var L1, L2, LBottomLine, LCaretFirst, LCaretLast: Integer; begin DoIncPaintLock(Self); // No editing is taking place CaretXY := ptCaret; SetBlockBegin(ptBefore); SetBlockEnd(ptAfter); FBlockSelection.ForceSingleLineSelected := AForceSingleLineSelected; if Mode <> smCurrent then FBlockSelection.ActiveSelectionMode := Mode; if MakeSelectionVisible then begin //l1 := FBlockSelection.FirstLineBytePos;; LBottomLine := ToPos(FTheLinesView.AddVisibleOffsetToTextIndex(ToIdx(TopLine), LinesInWindow)); LCaretFirst := CaretY; LCaretLast := Max(1, ToPos(FTheLinesView.AddVisibleOffsetToTextIndex(ToIdx(CaretY), 1-LinesInWindow))); // Will have caret on last visible line l1 := Min(LCaretFirst, FBlockSelection.FirstLineBytePos.y); l2 := Max(LCaretFirst, FBlockSelection.LastLineBytePos.y); if CaretY < TopLine then begin // Scrolling up, Topline = L1 ; but ensure Caret TopLine := Max(LCaretLast, Min(LCaretFirst, L1 )); end else if CaretY > LBottomLine then begin // Scrolling down, LastLine = L2 TopLine := Max(LCaretLast, Min(LCaretFirst, ToPos(FTheLinesView.AddVisibleOffsetToTextIndex(ToIdx(L2), 1-LinesInWindow)) )); end else begin // Caret alreayd visible, check block if l1 < TopLine then TopLine := Max(LCaretLast, Min(LCaretFirst, L1 )) else if l2 > LBottomLine then TopLine := Max(LCaretLast, Min(LCaretFirst, ToPos(FTheLinesView.AddVisibleOffsetToTextIndex(ToIdx(L2), 1-LinesInWindow)) )); end; end; DoDecPaintLock(Self); end; procedure TCustomSynEdit.RecalcCharExtent; var i: Integer; begin (* Highlighter or Font changed *) IncStatusChangeLock; try inc(FRecalcCharsAndLinesLock); try FFontDummy.Assign(Font); with FFontDummy do begin // Keep GTK happy => By ensuring a change the XFLD fontname gets cleared {$IFDEF LCLGTK1} Pitch := fpVariable; Style := [fsBold]; Pitch := fpDefault; // maybe Fixed {$ENDIF} // TODO: Clear style only, if Highlighter uses styles Style := []; // Reserved for Highlighter end; //debugln(['TCustomSynEdit.RecalcCharExtent ',fFontDummy.Name,' ',fFontDummy.Size]); //debugln('TCustomSynEdit.RecalcCharExtent CharHeight=',dbgs(CharHeight)); fTextDrawer.SetBaseFont(FFontDummy); if Assigned(fHighlighter) then for i := 0 to Pred(fHighlighter.AttrCount) do fTextDrawer.AddBaseStyle(fHighlighter.Attribute[i].Style); fTextDrawer.CharExtra := ExtraCharSpacing; StatusChanged([scFontOrStyleChanged]); // Font or Spacing FTextArea.FontChanged; // needed early to load now values // SynStatus changes are still locked FLines.IsUtf8 := True; finally dec(FRecalcCharsAndLinesLock); // RecalcCharsAndLinesInWin will be called by SizeOrFontChanged end; FScreenCaret.Lock; try FScreenCaret.CharWidth := CharWidth; FScreenCaret.CharHeight := LineHeight - Max(0, FPaintArea.TextArea.ExtraLineSpacing); SizeOrFontChanged(TRUE); finally FScreenCaret.UnLock; end; UpdateScrollBars; finally DecStatusChangeLock; end; end; procedure TCustomSynEdit.HighlighterAttrChanged(Sender: TObject); begin RecalcCharExtent; Invalidate; // TODO: obey paintlock if fHighlighter.AttributeChangeNeedScan then begin FHighlighter.CurrentLines := FTheLinesView; FHighlighter.ScanAllRanges; fMarkupManager.TextChanged(1, FTheLinesView.Count, 0); end; end; procedure TCustomSynEdit.StatusChangedEx(Sender: TObject; Changes: TSynStatusChanges); begin StatusChanged(Changes); end; procedure TCustomSynEdit.StatusChanged(AChanges: TSynStatusChanges); begin fStatusChanges := fStatusChanges + AChanges; if (PaintLock = 0) and (FStatusChangeLock = 0) and (fStatusChanges <> []) then DoOnStatusChange(fStatusChanges); end; procedure TCustomSynEdit.DoTabKey; var i, iLine: integer; PrevLine, Spaces: string; p: PChar; OldCaretX: integer; begin if (eoTabIndent in Options) and SelAvail then begin DoBlockIndent; exit; end; InternalBeginUndoBlock; try i := 0; OldCaretX := CaretX; if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then SelText := ''; // With a multi-line block the caret may have advanced, avoid negative spaces if CaretX > OldCaretX then OldCaretX := CaretX; if eoSmartTabs in fOptions then begin iLine := CaretY - 1; if (iLine > 0) and (iLine < FTheLinesView.Count) then begin repeat Dec(iLine); if iLine < 0 then break; PrevLine := FTheLinesView[iLine]; until PhysicalLineLength(PrevLine, iLine) > OldCaretX - 1; if iLine >= 0 then begin p := @PrevLine[PhysicalToLogicalCol(PrevLine, iLine, OldCaretX)]; // scan over non-whitespaces while not (p^ in [#0, #9, #32]) do inc(p); // scan over whitespaces while (p^ in [#9, #32]) do inc(p); if IsCombiningCodePoint(p) then dec(p); i := LogicalToPhysicalCol(PrevLine, iLine, p-@PrevLine[1]+1) - CaretX; end; end; end; if i <= 0 then begin i := TabWidth - (CaretX - 1) mod TabWidth; if i = 0 then i := TabWidth; end; // i now contains the needed spaces if eoTabsToSpaces in Options then Spaces := CreateTabsAndSpaces(CaretX,i,TabWidth, 0) else Spaces := CreateTabsAndSpaces(CaretX,i,TabWidth, MaxInt); if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then begin SetSelTextExternal(Spaces); end else begin FCaret.IncAutoMoveOnEdit; FTheLinesView.EditInsert(FCaret.BytePos, FCaret.LinePos, Spaces); FCaret.DecAutoMoveOnEdit; Include(fStateFlags, sfEnsureCursorPosForEditRight); end; finally InternalEndUndoBlock; end; EnsureCursorPosVisible; end; procedure TCustomSynEdit.CreateWnd; begin inherited; if (eoDropFiles in fOptions) and not (csDesigning in ComponentState) then // ToDo DragAcceptFiles //old DragAcceptFiles(Handle, TRUE); ; SizeOrFontChanged(true); end; procedure TCustomSynEdit.DestroyWnd; begin {$IFDEF SynCheckPaintLock} if (FPaintLock > 0) then begin debugln(['TCustomSynEdit.DestroyWnd: Paintlock=', FPaintLock, ' FInvalidateRect=', dbgs(FInvalidateRect)]); DumpStack; end; {$ENDIF} if (eoDropFiles in fOptions) and not (csDesigning in ComponentState) then begin // ToDo DragAcceptFiles //DragAcceptFiles(Handle, FALSE); ; end; {$IFDEF EnableDoubleBuf} FreeAndNil(BufferBitmap); {$ENDIF} SurrenderPrimarySelection; if FFoldedLinesView <> nil then FFoldedLinesView.LinesInWindow := -1; // Mark as "not HandleAllocated" / WaitingForInitialSize; inherited DestroyWnd; end; procedure TCustomSynEdit.VisibleChanged; begin inherited VisibleChanged; UpdateScreenCaret; // This may no longer be needed. It is now done in UpdateShowing end; procedure TCustomSynEdit.DoAutoAdjustLayout( const AMode: TLayoutAdjustmentPolicy; const AXProportion, AYProportion: Double ); begin inherited DoAutoAdjustLayout(AMode, AXProportion, AYProportion); if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then begin FLeftGutter.ScalePPI(AXProportion); FRightGutter.ScalePPI(AXProportion); end; end; procedure TCustomSynEdit.DoBlockIndent(AColumnIndentOutside: Boolean); var BB,BE : TPoint; Line : PChar; Len, e, y: integer; Spaces, Tabs: String; begin if SelAvail and (SelectionMode = smColumn) then begin DoBlockIndentColSel(AColumnIndentOutside, False); exit; end; IncPaintLock; FBlockSelection.IncPersistentLock; try // build text to insert if not SelAvail then begin BB := CaretXY; BE := CaretXY; e := BE.y; end else begin BB := BlockBegin; BE := BlockEnd; if FBlockSelection.LastLineHasSelection then e := BE.y else e := BE.y - 1; end; Spaces := StringOfChar(#32, FBlockIndent); Tabs := StringOfChar( #9, FBlockTabIndent); fUndoList.Lock; fRedoList.Lock; try for y := BB.Y to e do begin Line := PChar(FTheLinesView[y - 1]); Len := CountLeadWhiteSpace(Line); FTheLinesView.EditInsert(Len + 1, y, Spaces); FTheLinesView.EditInsert(1, y, Tabs); end; finally fUndoList.Unlock; fRedoList.Unlock; end; fUndoList.AddChange(TSynEditUndoIndent.Create(BB.Y, e, FBlockIndent, FBlockTabIndent)); finally FTrimmedLinesView.ForceTrim; // Otherwise it may reset the block FCaret.LineBytePos := FBlockSelection.EndLineBytePos; FBlockSelection.DecPersistentLock; DecPaintLock; end; end; procedure TCustomSynEdit.DoBlockIndentColSel(AnIndentOutside: Boolean; ADeleteAtRightBound: Boolean ); var BB,BE, BB2, BE2: TPoint; Len, y, LeftBytePos, RightCharPos, RightBytePos, DelPos, DelLen: integer; LineStr, TabStr, SpaceStr: String; Bounds: array of record LeftByte, RightByte: integer; end; begin if not (SelAvail and (SelectionMode = smColumn)) then exit; if (FBlockIndent <= 0) and (FBlockTabIndent <= 0) then exit; IncPaintLock; try // build text to insert BB := BlockBegin; BE := BlockEnd; BB2 := FBlockSelection.StartLineBytePos; BE2 := FBlockSelection.EndLineBytePos; SetLength(Bounds, BE.Y - BB.Y + 1); for y := BB.Y to BE.y do begin Bounds[y-BB.y].LeftByte := FBlockSelection.ColumnStartBytePos[y]; Bounds[y-BB.y].RightByte := FBlockSelection.ColumnEndBytePos[y]; end; SpaceStr := StringOfChar(#32, FBlockIndent); TabStr := StringOfChar( #9, FBlockTabIndent); RightCharPos := FBlockSelection.ColumnRightCharPos; FBlockSelection.Clear; for y := BB.Y to BE.y do begin LineStr := FTheLinesView[y - 1]; LeftBytePos := Bounds[y-BB.y].LeftByte; if AnIndentOutside then begin Len := CountBackwardWhiteSpace(PChar(LineStr), LeftBytePos-1); if ADeleteAtRightBound then RightBytePos := Bounds[y-BB.y].RightByte; LeftBytePos := LeftBytePos - Len; if FBlockIndent = 0 then Len := 0; end else begin if FBlockIndent > 0 then Len := CountLeadWhiteSpace(PChar(LineStr)+LeftBytePos-1) else Len := 0; if ADeleteAtRightBound or (Len > 0) then RightBytePos := Bounds[y-BB.y].RightByte; if (Len > 0) and (LeftBytePos + Len > RightBytePos) then Len := Max(0, RightBytePos - LeftBytePos); end; if Len = 0 then begin FTheLinesView.EditInsert(LeftBytePos, y, TabStr+SpaceStr); end else begin if SpaceStr <> '' then FTheLinesView.EditInsert(LeftBytePos + Len, y, SpaceStr); if TabStr <> '' then FTheLinesView.EditInsert(LeftBytePos, y, TabStr); end; if ADeleteAtRightBound then begin FInternalCaret.Invalidate; if AnIndentOutside then begin DelPos := RightBytePos + Length(TabStr) + Length(SpaceStr); FInternalCaret.LineBytePos := Point(DelPos, y); DelLen := FInternalCaret.CharPos - RightCharPos; // Phys-Columns FInternalCaret.CharPos := FInternalCaret.CharPos + DelLen; DelLen := FInternalCaret.BytePos - DelPos; end else begin FInternalCaret.LineCharPos := Point(RightCharPos, y); DelPos := FInternalCaret.BytePos; DelLen := RightBytePos + Length(TabStr) + Length(SpaceStr) - DelPos; end; if DelLen > 0 then FTheLinesView.EditDelete(DelPos, y, DelLen); end; end; finally FTrimmedLinesView.ForceTrim; // Otherwise it may reset the block if (not ADeleteAtRightBound) or AnIndentOutside then begin if AnIndentOutside then begin BB2.X := BB2.X + Length(TabStr) + Length(SpaceStr); BE2.X := BE2.X + Length(TabStr) + Length(SpaceStr); end else begin if BB2.X > BE2.X then BB2.X := BB2.X + Length(TabStr) + Length(SpaceStr) else BE2.X := BE2.X + Length(TabStr) + Length(SpaceStr); end; end; FBlockSelection.StartLineBytePos := BB2; FBlockSelection.EndLineBytePos := BE2; FBlockSelection.ActiveSelectionMode := smColumn; FCaret.LineBytePos := BE2; DecPaintLock; end; end; procedure TCustomSynEdit.DoBlockUnindent(AColumnIndentOutside: Boolean); const LineEnd = #10; var BB, BE: TPoint; FullStrToDelete: String; Line: PChar; Len, LogP1, PhyP1, LogP2, PhyP2, y, StrToDeleteLen, StrToDeletePos, e : integer; i, i2, j: Integer; SomethingDeleted : Boolean; HasTab: Boolean; begin if SelAvail and (SelectionMode = smColumn) then begin DoBlockUnindentColSel(AColumnIndentOutside, False); exit; end; if not SelAvail then begin BB := CaretXY; BE := CaretXY; e := BE.y; end else begin BB := BlockBegin; BE := BlockEnd; // convert selection to complete lines if FBlockSelection.LastLineHasSelection then e := BE.y else e := BE.y - 1; end; IncPaintLock; FBlockSelection.IncPersistentLock; // build string to delete StrToDeleteLen := (fBlockIndent+length(LineEnd)) * (e - BB.y + 1) + 1; // chars per line * lines-1 + last line + null char SetLength(FullStrToDelete, StrToDeleteLen); StrToDeletePos := 1; try SomethingDeleted := False; fUndoList.Lock; fRedoList.Lock; // before locking the undo list for y := BB.Y to e do begin Line := PChar(FTheLinesView[y - 1]); Len := CountLeadWhiteSpace(Line, HasTab); LogP1 := Len + 1; if HasTab and (Len > 0) then begin // LogP1, PhyP1 log and phys of the first none-whitespace PhyP1 := LogicalToPhysicalPos(Point(LogP1, y)).x; // LogP2, PhyP2 log and phys of the point to which to delete back LogP2 := PhysicalToLogicalPos(Point( Max(PhyP1 - FBlockIndent, 1), y )).x; PhyP2 := LogicalToPhysicalPos(Point(LogP2,y)).x; if PhyP1 - PhyP2 <> FBlockIndent then begin // need tab to space move(Line[LogP2-1], FullStrToDelete[StrToDeletePos], LogP1 - LogP2); inc(StrToDeletePos, LogP1 - LogP2); FullStrToDelete[StrToDeletePos] := LineEnd; inc(StrToDeletePos, 1); FTheLinesView.EditDelete(LogP2, y, LogP1 - LogP2); SomethingDeleted := True; fUndoList.Unlock; fRedoList.Unlock; FTheLinesView.EditInsert(LogP2, y, StringOfChar(' ', PhyP1 - PhyP2 - FBlockIndent)); fUndoList.Lock; fRedoList.Lock; continue; end; // tabs present, but no replacement needed (LogP1, LogP2 are correct end else begin // no tabs present LogP2 := Max(LogP1 - FBlockIndent, 1); end; // Remove spaces (or tab) if LogP1 - LogP2 > 0 then begin move(Line[LogP2-1], FullStrToDelete[StrToDeletePos], LogP1 - LogP2); inc(StrToDeletePos, LogP1 - LogP2); end; FullStrToDelete[StrToDeletePos] := LineEnd; inc(StrToDeletePos, 1); if LogP1 - LogP2 > 0 then FTheLinesView.EditDelete(LogP2, y, LogP1 - LogP2); SomethingDeleted := SomethingDeleted or (LogP1 - LogP2 > 0); // Todo: create FullTabStrToDelete for tabs fUndoList.Unlock; fRedoList.Unlock; Line := PChar(FTheLinesView[y - 1]); j := 0; for i := 1 to FBlockTabIndent do begin i2 := fTabWidth; while (i2 > 0) and IsSpaceChar(Line+j) do begin dec(i2); inc(j); end; if (i2 > 0) and (Line[j] = #9) then inc(j); end; if j > 0 then FTheLinesView.EditDelete(1, y, j); fUndoList.Lock; fRedoList.Lock; end; fUndoList.Unlock; fRedoList.Unlock; if SomethingDeleted then begin SetLength(FullStrToDelete, StrToDeletePos - 1); fUndoList.AddChange(TSynEditUndoUnIndent.Create(BB.Y, e, FullStrToDelete)); end; FTrimmedLinesView.ForceTrim; // Otherwise it may reset the block finally FCaret.LineBytePos := FBlockSelection.EndLineBytePos; FBlockSelection.DecPersistentLock; DecPaintLock; end; end; procedure TCustomSynEdit.DoBlockUnindentColSel(AnIndentOutside: Boolean; ADeleteAtLeftBound: Boolean); var BB,BE, BB2, BE2: TPoint; Len, y, LeftBytePos, RightBytePos, RightCharPos, LeftCharPos, TabW: integer; CurSpaceSpaceAdd, CurSpaceDel, CurSpaceDelPos, SpaceStartCharPos: integer; CurTabDel, CurTabDelPhysWidth, CurTabSpaceAdd, TabEndBytePos, TmpCharPos, t: integer; LineStr: String; Bounds: array of record LeftByte, RightByte: integer; end; LPC: TSynLogicalPhysicalConvertor; BbIsRight: Boolean; function LogToPhys(X: Integer): integer; inline; begin Result := LPC.LogicalToPhysical(ToIdx(y), X); end; function PhysToLog(X: Integer): integer; inline; begin Result := LPC.PhysicalToLogical(ToIdx(y), X); end; function PhysToLog(X: Integer; out Offs: integer): integer; inline; begin Result := LPC.PhysicalToLogical(ToIdx(y), X, Offs, cspDefault, []); end; begin if not (SelAvail and (SelectionMode = smColumn)) then exit; if (FBlockIndent <= 0) and (FBlockTabIndent <= 0) then exit; IncPaintLock; try TabW := TabWidth; BB := BlockBegin; BE := BlockEnd; BB2 := FBlockSelection.StartLineBytePos; BE2 := FBlockSelection.EndLineBytePos; BbIsRight := BB2.X > BE2.X; SetLength(Bounds, BE.Y - BB.Y + 1); for y := BB.Y to BE.y do begin Bounds[y-BB.y].LeftByte := FBlockSelection.ColumnStartBytePos[y]; Bounds[y-BB.y].RightByte := FBlockSelection.ColumnEndBytePos[y]; end; LPC := FTheLinesView.LogPhysConvertor; RightCharPos := FBlockSelection.ColumnRightCharPos; FBlockSelection.Clear; for y := BB.Y to BE.y do begin LineStr := FTheLinesView[y - 1]; LeftBytePos := Bounds[y-BB.y].LeftByte; RightBytePos := Bounds[y-BB.y].RightByte; CurSpaceDel := 0; CurSpaceSpaceAdd := 0; CurTabDel := 0; if ADeleteAtLeftBound then begin if AnIndentOutside then begin LeftCharPos := Max(1, LogToPhys(LeftBytePos) - FBlockIndent); if FBlockTabIndent > 0 then LeftCharPos := LeftCharPos - Max(0, FBlockTabIndent-1)*TabW - (LeftCharPos-1) mod TabW; CurSpaceDelPos := PhysToLog(LeftCharPos, CurSpaceSpaceAdd); CurSpaceDel := LeftBytePos - CurSpaceDelPos; end else begin TmpCharPos := LogToPhys(LeftBytePos) + FBlockIndent; if FBlockTabIndent > 0 then TmpCharPos := TmpCharPos + Max(0, FBlockTabIndent-1)*TabW + (TabW - (TmpCharPos-1) mod TabW); CurSpaceDelPos := LeftBytePos; CurSpaceDel := PhysToLog(TmpCharPos, CurSpaceSpaceAdd) - CurSpaceDelPos; if CurSpaceDel > RightBytePos - LeftBytePos then begin CurSpaceDel := RightBytePos - LeftBytePos; CurSpaceSpaceAdd := 0; end; end; end else begin if AnIndentOutside then begin Len := CountBackwardWhiteSpace(PChar(LineStr), LeftBytePos-1); RightBytePos := LeftBytePos; LeftBytePos := LeftBytePos - Len; end else begin Len := Min(CountLeadWhiteSpace(PChar(LineStr)+LeftBytePos-1), RightBytePos - LeftBytePos); if Len > Length(LineStr) - LeftBytePos then Len := Length(LineStr) - LeftBytePos; end; if Len = 0 then Continue; CurSpaceDelPos := LeftBytePos + Len; // used as end for tab // pretend zero spaces (* find spaces to delete *) if FBlockIndent > 0 then begin SpaceStartCharPos := Max( LogToPhys(CurSpaceDelPos) - FBlockIndent, LogToPhys(LeftBytePos) ); CurSpaceDelPos := PhysToLog(SpaceStartCharPos, CurSpaceSpaceAdd); CurSpaceDel := LeftBytePos + Len - CurSpaceDelPos; if (CurSpaceDel > Len) then begin CurSpaceDelPos := LeftBytePos; CurSpaceDel := Len; CurSpaceSpaceAdd := 0; end; Len := CurSpaceDelPos - LeftBytePos; end; (* find tabs to delete *) if (FBlockTabIndent > 0) and ( (Len > 0) or (CurSpaceSpaceAdd > 0) ) then begin (* actual tabs / only at very start of gap *) while (CurTabDel < FBlockTabIndent) and (LineStr[LeftBytePos+CurTabDel] = #9) do inc(CurTabDel); (* Take spaces/tab mix instead / ending at a tabstop *) if CurTabDel < FBlockTabIndent then begin LeftCharPos := LogToPhys(LeftBytePos+CurTabDel); CurTabDelPhysWidth := (FBlockTabIndent-CurTabDel)*TabW - (LeftCharPos-1) mod TabW; if CurTabDelPhysWidth > 0 then begin TabEndBytePos := PhysToLog(LeftCharPos+CurTabDelPhysWidth, CurTabSpaceAdd); assert(CurTabSpaceAdd=0, 'TCustomSynEdit.DoBlockUnindentColSel: CurTabSpaceAdd=0'); CurTabDel := CurTabDel + TabEndBytePos - (LeftBytePos+CurTabDel); end; end; if (CurTabDel > Len - CurSpaceDel) then begin CurSpaceSpaceAdd := Max(0, CurSpaceSpaceAdd - Max(0, LogToPhys(LeftBytePos+CurTabDel) - LogToPhys(CurSpaceDelPos))); CurTabDel := 0; CurSpaceDelPos := LeftBytePos; CurSpaceDel := Len; end; end; end; if CurSpaceDel > 0 then begin FTheLinesView.EditDelete(CurSpaceDelPos, y, CurSpaceDel); if CurSpaceSpaceAdd > 0 then FTheLinesView.EditInsert(CurSpaceDelPos, y, StringOfChar(' ', CurSpaceSpaceAdd)); end; if CurTabDel > 0 then begin FTheLinesView.EditDelete(LeftBytePos, y, CurTabDel); end; if ADeleteAtLeftBound then begin RightBytePos := RightBytePos - CurSpaceDel; TmpCharPos := LogToPhys(RightBytePos); t := Max(0, (RightCharPos-TmpCharPos)); FTheLinesView.EditInsert(RightBytePos, y, CreateTabsAndSpaces(RightBytePos, t, TabW, FBlockTabIndent)); end; if (not ADeleteAtLeftBound) or AnIndentOutside then begin if AnIndentOutside then begin if (y = BB2.y) then BB2.X := BB2.X + CurSpaceSpaceAdd - CurSpaceDel - CurTabDel; if (y = BE2.y) then BE2.X := BE2.X + CurSpaceSpaceAdd - CurSpaceDel - CurTabDel; end else begin if (y = BB2.y) and (BbIsRight) then BB2.X := BB2.X + CurSpaceSpaceAdd - CurSpaceDel - CurTabDel; if (y = BE2.y) and (not BbIsRight) then BE2.X := BE2.X + CurSpaceSpaceAdd - CurSpaceDel - CurTabDel; end; end; end; finally FTrimmedLinesView.ForceTrim; // Otherwise it may reset the block FBlockSelection.StartLineBytePos := BB2; FBlockSelection.EndLineBytePos := BE2; FBlockSelection.ActiveSelectionMode := smColumn; FCaret.LineBytePos := BE2; DecPaintLock; end; end; procedure TCustomSynEdit.DoHomeKey(aMode: TSynHomeMode = synhmDefault); // jump to start of line (x=1), // or if already there, jump to first non blank char // or if blank line, jump to line indent position // if eoEnhanceHomeKey and behind alternative point then jump first var s: string; FirstNonBlank: Integer; LineStart: LongInt; OldPos: TPoint; NewPos: TPoint; begin OldPos := CaretXY; NewPos := OldPos; if not(eoEnhanceHomeKey in fOptions) and (CaretX > 1) and (aMode in [synhmDefault]) then begin // not at start of line -> jump to start of line NewPos.X := 1; end else begin // calculate line start position FirstNonBlank := -1; if CaretY <= FTheLinesView.Count then begin s := FTheLinesView[CaretXY.Y - 1]; // search first non blank char pos FirstNonBlank := CountLeadWhiteSpace(PChar(s)) + 1; if FirstNonBlank > length(s) then FirstNonBlank := -1; end else s := ''; if (FirstNonBlank >= 1) or (aMode in [synhmFirstWord]) then begin // this line is not blank if FirstNonBlank < 1 then FirstNonBlank := 1; LineStart := LogicalToPhysicalPos(Point(FirstNonBlank, CaretY)).x; end else begin // this line is blank // -> use automatic line indent LineStart := FBeautifier.GetDesiredIndentForLine(Self, FTheLinesView, FCaret); end; NewPos.X:=LineStart; if (eoEnhanceHomeKey in fOptions) and (aMode in [synhmDefault]) and (OldPos.X>1) and (OldPos.X<=NewPos.X) then begin NewPos.X:=1; end; end; FCaret.LineCharPos := NewPos; end; procedure TCustomSynEdit.DoEndKey; // jump to start of line (x=1), // or if already there, jump to first non blank char // or if blank line, jump to line indent position // if eoEnhanceHomeKey and behind alternative point then jump first var s: string; LastNonBlank: Integer; LineEnd: LongInt; OldPos: TPoint; NewPos: TPoint; begin OldPos := CaretXY; NewPos := OldPos; s := LineText; if not (eoEnhanceEndKey in fOptions2) and (FCaret.BytePos <> Length(s)+1) then begin // not at end of real line -> jump to end of line FCaret.BytePos := Length(s)+1; end else begin // calculate line end position LastNonBlank := -1; if s <> '' then begin // search first non blank char pos LastNonBlank := Length(s); while (LastNonBlank > 0) and (s[LastNonBlank] in [#32, #9]) do dec(LastNonBlank); end; if LastNonBlank >=1 then begin // this line is not blank LineEnd := LogicalToPhysicalPos(Point(LastNonBlank + 1, CaretY)).x; end else begin // this line is blank // -> use automatic line indent LineEnd := FBeautifier.GetDesiredIndentForLine(Self, FTheLinesView, FCaret); end; NewPos.X:=LineEnd; if (eoEnhanceEndKey in fOptions2) and (FCaret.BytePos <> Length(s)+1) and (OldPos.X >= NewPos.X) then begin FCaret.BytePos := Length(s)+1; end else FCaret.LineCharPos := NewPos; end; end; function TCustomSynEdit.ExecuteAction(ExeAction: TBasicAction): boolean; begin if ExeAction is TEditAction then begin Result := TRUE; if ExeAction is TEditCut then CutToClipboard else if ExeAction is TEditCopy then CopyToClipboard else if ExeAction is TEditPaste then PasteFromClipboard else if ExeAction is TEditDelete then ClearSelection else if ExeAction is TEditUndo then Undo else if ExeAction is TEditSelectAll then SelectAll; end else Result := inherited ExecuteAction(ExeAction); end; function TCustomSynEdit.UpdateAction(TheAction: TBasicAction): boolean; begin if TheAction is TEditAction then begin Result := Focused; if Result then begin if (TheAction is TEditCut) then TEditAction(TheAction).Enabled := SelAvail and (not ReadOnly) else if (TheAction is TEditCopy) then TEditAction(TheAction).Enabled := SelAvail else if TheAction is TEditPaste then TEditAction(TheAction).Enabled := CanPaste and (not ReadOnly) else if TheAction is TEditDelete then TEditAction(TheAction).Enabled := (not ReadOnly) else if TheAction is TEditUndo then TEditAction(TheAction).Enabled := CanUndo and (not ReadOnly) else if TheAction is TEditSelectAll then TEditAction(TheAction).Enabled := TRUE; end; end else Result := inherited UpdateAction(TheAction); end; procedure TCustomSynEdit.SetModified(Value: boolean); begin TSynEditStringList(FLines).Modified := Value; end; procedure TCustomSynEdit.InvalidateLine(Line: integer); begin InvalidateLines(Line, Line); InvalidateGutterLines(Line, Line); end; procedure TCustomSynEdit.FindMatchingBracket; begin FindMatchingBracket(CaretXY,false,true,false,false); end; function TCustomSynEdit.FindMatchingBracket(PhysStartBracket: TPoint; StartIncludeNeighborChars, MoveCaret, SelectBrackets, OnlyVisible: Boolean): TPoint; begin Result := FindMatchingBracketLogical(PhysicalToLogicalPos(PhysStartBracket), StartIncludeNeighborChars, MoveCaret, SelectBrackets, OnlyVisible); if Result.Y > 0 then Result := LogicalToPhysicalPos(Result); end; function TCustomSynEdit.FindMatchingBracketLogical(LogicalStartBracket: TPoint; StartIncludeNeighborChars, MoveCaret, SelectBrackets, OnlyVisible: Boolean): TPoint; // returns physical (screen) position of end bracket const // keep the ' last Brackets: array[0..7] of char = ('(', ')', '[', ']', '{', '}', '''', '"'); type TokenPos = Record X: Integer; Attr: Integer; end; var Line, s1: string; PosX, PosY: integer; StartPt: TPoint; // for ContextMatch BracketKind, TmpStart: Integer; SearchingForward: Boolean; TmpAttr : TSynHighlighterAttributes; // for IsContextBracket MaxKnownTokenPos, LastUsedTokenIdx, TokenListCnt: Integer; TokenPosList: Array of TokenPos; // remove all text, that is not of desired attribute function IsContextBracket: boolean; var i, l: Integer; begin if not assigned(fHighlighter) then exit(true); if PosX > MaxKnownTokenPos then begin // Token is not yet known l := Length(TokenPosList); if l < max(CharsInWindow * 2, 32) then begin l := max(CharsInWindow * 2, 32); SetLength(TokenPosList, l); end; // Init the Highlighter only once per line if MaxKnownTokenPos < 1 then begin fHighlighter.CurrentLines := FTheLinesView; fHighlighter.StartAtLineIndex(PosY - 1); TokenListCnt := 0; end else fHighlighter.Next; i := TokenListCnt; while not fHighlighter.GetEol do begin if i >= l then begin l := l * 4; SetLength(TokenPosList, l); end; TokenPosList[i].X := fHighlighter.GetTokenPos + 1; TokenPosList[i].Attr := fHighlighter.GetTokenKind; if TokenPosList[i].X > PosX then begin TokenListCnt := i + 1; MaxKnownTokenPos := TokenPosList[i].X; Result := TokenPosList[i-1].Attr = BracketKind; LastUsedTokenIdx := i; // -1; TODO: -1 only if searching backwards exit; end; inc(i); fHighlighter.Next; end; MaxKnownTokenPos := Length(Line) + 1; // 1 based end+1 of last token (start pos of none existing after eol token) if i >= l then begin l := l * 4; SetLength(TokenPosList, l); end; TokenPosList[i].X := MaxKnownTokenPos; TokenListCnt := i + 1; Result := (i > 0) and (TokenPosList[i-1].Attr = BracketKind); LastUsedTokenIdx := i; // -1; TODO: -1 only if searching backwards exit; end; // Token is in previously retrieved values i := LastUsedTokenIdx; while (i > 0) and (TokenPosList[i].X > PosX) do dec(i); Result := TokenPosList[i].Attr = BracketKind; if not SearchingForward then LastUsedTokenIdx := i; end; procedure DoMatchingBracketFound; var EndPt, DummyPt: TPoint; begin // matching bracket found, set caret and bail out Result := Point(PosX, PosY); // start with logical (byte) position if SelectBrackets then begin EndPt:=Result; if (EndPt.Y < StartPt.Y) or ((EndPt.Y = StartPt.Y) and (EndPt.X < StartPt.X)) then begin DummyPt:=StartPt; StartPt:=EndPt; EndPt:=DummyPt; end; inc(EndPt.X); SetCaretAndSelection(CaretXY, StartPt, EndPt); end else if MoveCaret then LogicalCaretXY := Result; end; procedure DoFindMatchingQuote(q: char); var Test: char; Len, PrevPosX, PrevCnt: integer; begin StartPt:=Point(PosX,PosY); GetHighlighterAttriAtRowColEx(StartPt, s1, BracketKind, TmpStart, TmpAttr); // Checck if we have a complete token, e.g. Highlightec returned entire "string" if (TmpStart = PosX) and (Length(s1)>1) and (s1[Length(s1)] = q) then begin PosX := PosX + Length(s1) - 1; DoMatchingBracketFound; exit; end; if (TmpStart + Length(s1) - 1 = PosX) and (Length(s1)>1) and (s1[1] = q) then begin PosX := PosX - Length(s1) + 1; DoMatchingBracketFound; exit; end; MaxKnownTokenPos := 0; Len := PosX; PrevPosX := -1; PrevCnt := 0; // search until start of line SearchingForward := False; while PosX > 1 do begin Dec(PosX); Test := Line[PosX]; if (Test = q) and IsContextBracket then begin inc(PrevCnt); if PrevPosX < 0 then PrevPosX := PosX; end; end; // 1st, 3rd, 5th, ... are opening if (PrevPosX > 0) and (PrevCnt mod 2 = 1) then begin PosX := PrevPosX; DoMatchingBracketFound; exit; end; PosX := Len; Len := Length(Line); SearchingForward := True; LastUsedTokenIdx := TokenListCnt; while PosX < Len do begin Inc(PosX); Test := Line[PosX]; if (Test = q) and IsContextBracket then begin DoMatchingBracketFound; exit; end; end; if (PrevPosX > 0) then begin PosX := PrevPosX; DoMatchingBracketFound; exit; end; end; procedure DoFindMatchingBracket(i: integer); var Test, BracketInc, BracketDec: char; NumBrackets, Len: integer; begin StartPt:=Point(PosX,PosY); GetHighlighterAttriAtRowColEx(StartPt, s1, BracketKind, TmpStart, TmpAttr); MaxKnownTokenPos := 0; BracketInc := Brackets[i]; BracketDec := Brackets[i xor 1]; // 0 -> 1, 1 -> 0, ... // search for the matching bracket (that is until NumBrackets = 0) NumBrackets := 1; if Odd(i) then begin // closing bracket -> search opening bracket SearchingForward := False; repeat // search until start of line while PosX > 1 do begin Dec(PosX); Test := Line[PosX]; if (Test = BracketInc) and IsContextBracket then Inc(NumBrackets) else if (Test = BracketDec) and IsContextBracket then begin Dec(NumBrackets); if NumBrackets = 0 then begin DoMatchingBracketFound; exit; end; end; end; // get previous line if possible if PosY = 1 then break; Dec(PosY); if OnlyVisible and ((PosY<TopLine) or (PosY >= ScreenRowToRow(LinesInWindow))) then break; Line := FTheLinesView[PosY - 1]; MaxKnownTokenPos := 0; PosX := Length(Line) + 1; until FALSE; end else begin // opening bracket -> search closing bracket SearchingForward := True; repeat // search until end of line Len := Length(Line); while PosX < Len do begin Inc(PosX); Test := Line[PosX]; if (Test = BracketInc) and IsContextBracket then Inc(NumBrackets) else if (Test = BracketDec) and IsContextBracket then begin Dec(NumBrackets); if NumBrackets = 0 then begin DoMatchingBracketFound; exit; end; end; end; // get next line if possible if PosY = FTheLinesView.Count then break; Inc(PosY); if OnlyVisible and ((PosY < TopLine) or (PosY >= ScreenRowToRow(LinesInWindow))) then break; Line := FTheLinesView[PosY - 1]; MaxKnownTokenPos := 0; PosX := 0; until FALSE; end; end; procedure DoCheckBracket; var i: integer; Test: char; begin if Length(Line) >= PosX then begin Test := Line[PosX]; // is it one of the recognized brackets? for i := Low(Brackets) to High(Brackets) do begin if Test = Brackets[i] then begin // this is the bracket, get the matching one and the direction if Brackets[i] in ['''', '"'] then DoFindMatchingQuote(Brackets[i]) else DoFindMatchingBracket(i); exit; end; end; end; end; begin Result.X:=-1; Result.Y:=-1; PosX := LogicalStartBracket.X; PosY := LogicalStartBracket.Y; if (PosY<1) or (PosY>FTheLinesView.Count) then exit; if OnlyVisible and ((PosY<TopLine) or (PosY >= ScreenRowToRow(LinesInWindow))) then exit; Line := FTheLinesView[PosY - 1]; DoCheckBracket; if Result.Y>0 then exit; if StartIncludeNeighborChars then begin if PosX>1 then begin // search in front dec(PosX); DoCheckBracket; if Result.Y>0 then exit; inc(PosX); end; if PosX<Length(Line) then begin // search behind inc(PosX); DoCheckBracket; if Result.Y>0 then exit; end; end; end; //L505 begin function TCustomSynEdit.GetHighlighterAttriAtRowCol(XY: TPoint; out Token: string; out Attri: TSynHighlighterAttributes): boolean; var TmpType, TmpStart: Integer; begin Result := GetHighlighterAttriAtRowColEx(XY, Token, TmpType, TmpStart, Attri); end; function TCustomSynEdit.GetHighlighterAttriAtRowColEx(XY: TPoint; out Token: string; out TokenType, Start: Integer; out Attri: TSynHighlighterAttributes; ContinueIfPossible: boolean ): boolean; var PosX, PosY: integer; Line: string; begin PosY := ToIdx(XY.Y); if Assigned(Highlighter) and (PosY >= 0) and (PosY < FTheLinesView.Count) then begin Line := FTheLinesView[PosY]; PosX := XY.X; if (PosX > 0) and (PosX <= Length(Line)) then begin if (not ContinueIfPossible) or (fHighlighter.CurrentLines <> FTheLinesView) or (fHighlighter.LineIndex <> PosY) or (fHighlighter.GetTokenPos + 1 + Highlighter.GetTokenLen >= PosX) then begin fHighlighter.CurrentLines := FTheLinesView; Highlighter.StartAtLineIndex(PosY); end; while not Highlighter.GetEol do begin Start := Highlighter.GetTokenPos + 1; Token := Highlighter.GetToken; if (PosX >= Start) and (PosX < Start + Length(Token)) then begin Attri := Highlighter.GetTokenAttribute; TokenType := Highlighter.GetTokenKind; exit(True); end; Highlighter.Next; end; end; end; Token := ''; Attri := nil; TokenType := -1; Result := False; end; function TCustomSynEdit.GetHighlighterAttriAtRowColEx(XY: TPoint; out TokenType: Integer; ContinueIfPossible: boolean): boolean; var PosX, PosY, Start: integer; Line: string; begin PosY := ToIdx(XY.Y); if Assigned(Highlighter) and (PosY >= 0) and (PosY < FTheLinesView.Count) then begin Line := FTheLinesView[PosY]; PosX := XY.X; if (PosX > 0) and (PosX <= Length(Line)) then begin if (not ContinueIfPossible) or (fHighlighter.CurrentLines <> FTheLinesView) or (fHighlighter.LineIndex <> PosY) or (fHighlighter.GetTokenPos + 1 + Highlighter.GetTokenLen >= PosX) then begin fHighlighter.CurrentLines := FTheLinesView; Highlighter.StartAtLineIndex(PosY); end; while not Highlighter.GetEol do begin Start := Highlighter.GetTokenPos + 1; if (PosX >= Start) and (PosX < Start + Highlighter.GetTokenLen) then begin TokenType := Highlighter.GetTokenKind; exit(True); end; Highlighter.Next; end; end; end; TokenType := -1; Result := False; end; procedure TCustomSynEdit.CaretAtIdentOrString(XY: TPoint; out AtIdent, NearString: Boolean); // This is optimized to check if cursor is on identifier or string. var PosX, PosY, Start: integer; Line, Token: string; Attri, PrevAttri: TSynHighlighterAttributes; begin PosY := XY.Y -1; PrevAttri := nil; AtIdent := False; NearString := False; //DebugLn('TCustomSynEdit.CaretAtIdentOrString: Enter'); if Assigned(Highlighter) and (PosY >= 0) and (PosY < FTheLinesView.Count) then begin Line := FTheLinesView[PosY]; fHighlighter.CurrentLines := FTheLinesView; Highlighter.StartAtLineIndex(PosY); PosX := XY.X; //DebugLn([' CaretAtIdentOrString: Line="',Line,'", PosX=',PosX,', PosY=', PosY]); if (PosX > 0) and (PosX <= Length(Line)+1) then begin while not Highlighter.GetEol do begin Start := Highlighter.GetTokenPos + 1; Token := Highlighter.GetToken; Attri := Highlighter.GetTokenAttribute; //DebugLn([' CaretAtIdentOrString: Start=', Start, ', Token=', Token]); if PosX = Start then begin AtIdent := (Attri = Highlighter.IdentifierAttribute) or (PrevAttri = Highlighter.IdentifierAttribute); NearString := (Attri = Highlighter.StringAttribute) or (PrevAttri = Highlighter.StringAttribute); // If cursor is on end-quote. //DebugLn([' CaretAtIdentOrString: Success 1! Attri=', Attri, // ', AtIdent=', AtIdent, ', NearString=', NearString]); exit; end; if (PosX >= Start) and (PosX <= Start + Length(Token)) and ( (Attri = Highlighter.IdentifierAttribute) or (Attri = Highlighter.StringAttribute) ) then begin AtIdent := Attri = Highlighter.IdentifierAttribute; NearString := Attri = Highlighter.StringAttribute; //DebugLn([' CaretAtIdentOrString: Success 2! Attri=', Attri, // ', AtIdent=', AtIdent, ', NearString=', NearString]); exit; end; PrevAttri := Attri; Highlighter.Next; end; end; end; end; function TCustomSynEdit.IdentChars: TSynIdentChars; begin Result := FWordBreaker.IdentChars; // Maybe WordChars? end; function TCustomSynEdit.IsIdentChar(const c: TUTF8Char): boolean; begin Result:=(length(c)=1) and (c[1] in IdentChars); end; procedure TCustomSynEdit.GetWordBoundsAtRowCol(const XY: TPoint; out StartX, EndX: integer); // all params are logical (byte) positions var Line: string; begin StartX:=XY.X; EndX:=XY.X; Line := FTheLinesView[XY.Y - 1]; if WordBreaker.IsInWord(Line, XY.X) then begin StartX := WordBreaker.PrevWordStart(Line, XY.X, True); EndX := WordBreaker.NextWordEnd(Line, XY.X, True); end; end; function TCustomSynEdit.GetWordAtRowCol(XY: TPoint): string; var StartX, EndX: integer; Line: string; begin GetWordBoundsAtRowCol(XY, StartX, EndX); Line := FTheLinesView[XY.Y - 1]; Result := Copy(Line, StartX, EndX - StartX); end; function TCustomSynEdit.NextTokenPos: TPoint; var CX, CY, LineLen: integer; Line: string; CurIdentChars, WhiteChars: TSynIdentChars; nTokenPos, nTokenLen: integer; sToken: PChar; LogCaret: TPoint; procedure FindFirstNonWhiteSpaceCharInNextLine; begin if CY < FTheLinesView.Count then begin Line := FTheLinesView[CY]; LineLen := Length(Line); Inc(CY); CX:=1; while (CX<=LineLen) and (Line[CX] in WhiteChars) do inc(CX); if CX>LineLen then CX:=1; end; end; begin LogCaret:=LogicalCaretXY; CX := LogCaret.X; CY := LogCaret.Y; // valid line? if (CY >= 1) and (CY <= FTheLinesView.Count) then begin Line := FTheLinesView[CY - 1]; LineLen := Length(Line); WhiteChars := FWordBreaker.WhiteChars; if CX > LineLen then begin FindFirstNonWhiteSpaceCharInNextLine; end else begin if fHighlighter<>nil then begin fHighlighter.CurrentLines := FTheLinesView; fHighlighter.StartAtLineIndex(CY - 1); while not fHighlighter.GetEol do begin nTokenPos := fHighlighter.GetTokenPos; // zero-based fHighlighter.GetTokenEx(sToken,nTokenLen); if (CX>nTokenPos) and (CX<=nTokenPos+nTokenLen) then begin CX:=nTokenPos+nTokenLen+1; break; end; // Let the highlighter scan the next token. fHighlighter.Next; end; if fHighlighter.GetEol then FindFirstNonWhiteSpaceCharInNextLine; end else begin // no highlighter CurIdentChars:=IdentChars; // find first "whitespace" if next char is not a "whitespace" if (Line[CX] in CurIdentChars) then begin // in a word -> move to end of word while (CX<=LineLen) and (Line[CX] in CurIdentChars) do inc(CX); end; if (Line[CX] in WhiteChars) then begin // skip white space while (CX<=LineLen) and (Line[CX] in WhiteChars) do inc(CX); end; // delete at least one char if (CX=CaretX) then inc(CX); end; end; end; Result := LogicalToPhysicalPos(Point(CX, CY)); end; function TCustomSynEdit.NextWordPos: TPoint; begin Result := LogicalToPhysicalPos(NextWordLogicalPos); end; function TCustomSynEdit.PrevWordPos: TPoint; begin Result := LogicalToPhysicalPos(PrevWordLogicalPos); end; function TCustomSynEdit.FindHookedCmdEvent(AHandlerProc: THookedCommandEvent): integer; var Entry: THookedCommandHandlerEntry; begin Result := GetHookedCommandHandlersCount - 1; while Result >= 0 do begin Entry := THookedCommandHandlerEntry(fHookedCommandHandlers[Result]); if Entry.Equals(AHandlerProc) then break; Dec(Result); end; end; function TCustomSynEdit.GetHookedCommandHandlersCount: integer; begin if Assigned(fHookedCommandHandlers) then Result := fHookedCommandHandlers.Count else Result := 0; end; procedure TCustomSynEdit.RegisterCommandHandler(AHandlerProc: THookedCommandEvent; AHandlerData: pointer; AFlags: THookedCommandFlags); begin if not Assigned(AHandlerProc) then begin {$IFDEF SYN_DEVELOPMENT_CHECKS} raise Exception.Create('Event handler is NIL in RegisterCommandHandler'); {$ENDIF} exit; end; if not Assigned(fHookedCommandHandlers) then fHookedCommandHandlers := TList.Create; if FindHookedCmdEvent(AHandlerProc) = -1 then fHookedCommandHandlers.Add(THookedCommandHandlerEntry.Create( AHandlerProc, AHandlerData, AFlags)) else {$IFDEF SYN_DEVELOPMENT_CHECKS} raise Exception.CreateFmt('Event handler (%p, %p) already registered', [TMethod(AHandlerProc).Data, TMethod(AHandlerProc).Code]); {$ENDIF} end; procedure TCustomSynEdit.UnregisterCommandHandler(AHandlerProc: THookedCommandEvent); var i: integer; begin if not Assigned(AHandlerProc) then begin {$IFDEF SYN_DEVELOPMENT_CHECKS} raise Exception.Create('Event handler is NIL in UnregisterCommandHandler'); {$ENDIF} exit; end; i := FindHookedCmdEvent(AHandlerProc); if i > -1 then begin THookedCommandHandlerEntry(fHookedCommandHandlers[i]).Free; fHookedCommandHandlers.Delete(i); end else {$IFDEF SYN_DEVELOPMENT_CHECKS} raise Exception.CreateFmt('Event handler (%p, %p) is not registered', [TMethod(AHandlerProc).Data, TMethod(AHandlerProc).Code]); {$ENDIF} end; procedure TCustomSynEdit.RegisterMouseActionSearchHandler(AHandlerProc: TSynEditMouseActionSearchProc); begin FMouseActionSearchHandlerList.Add(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.UnregisterMouseActionSearchHandler(AHandlerProc: TSynEditMouseActionSearchProc); begin FMouseActionSearchHandlerList.Remove(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.RegisterMouseActionExecHandler(AHandlerProc: TSynEditMouseActionExecProc); begin FMouseActionExecHandlerList.Add(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.UnregisterMouseActionExecHandler(AHandlerProc: TSynEditMouseActionExecProc); begin FMouseActionExecHandlerList.Remove(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.RegisterKeyTranslationHandler(AHandlerProc: THookedKeyTranslationEvent); begin FHookedKeyTranslationList.Add(TMEthod(AHandlerProc)); end; procedure TCustomSynEdit.UnRegisterKeyTranslationHandler(AHandlerProc: THookedKeyTranslationEvent); begin FHookedKeyTranslationList.Remove(TMEthod(AHandlerProc)); end; procedure TCustomSynEdit.RegisterUndoRedoItemHandler(AHandlerProc: TSynUndoRedoItemEvent); begin FUndoRedoItemHandlerList.Add(TMEthod(AHandlerProc)); end; procedure TCustomSynEdit.UnRegisterUndoRedoItemHandler(AHandlerProc: TSynUndoRedoItemEvent); begin FUndoRedoItemHandlerList.Remove(TMEthod(AHandlerProc)); end; procedure TCustomSynEdit.RegisterStatusChangedHandler(AStatusChangeProc: TStatusChangeEvent; AChanges: TSynStatusChanges); begin TSynStatusChangedHandlerList(FStatusChangedList).Add(AStatusChangeProc, AChanges); end; procedure TCustomSynEdit.UnRegisterStatusChangedHandler(AStatusChangeProc: TStatusChangeEvent); begin TSynStatusChangedHandlerList(FStatusChangedList).Remove(AStatusChangeProc); end; procedure TCustomSynEdit.RegisterBeforeMouseDownHandler(AHandlerProc: TMouseEvent); begin if FMouseDownEventList = nil then FMouseDownEventList := TLazSynMouseDownEventList.Create; FMouseDownEventList.Add(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.UnregisterBeforeMouseDownHandler(AHandlerProc: TMouseEvent); begin if FMouseDownEventList <> nil then FMouseDownEventList.Remove(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.RegisterQueryMouseCursorHandler(AHandlerProc: TSynQueryMouseCursorEvent); begin if FQueryMouseCursorList = nil then FQueryMouseCursorList := TSynQueryMouseCursorList.Create; TSynQueryMouseCursorList(FQueryMouseCursorList).Add(AHandlerProc); end; procedure TCustomSynEdit.UnregisterQueryMouseCursorHandler(AHandlerProc: TSynQueryMouseCursorEvent); begin if FQueryMouseCursorList <> nil then TSynQueryMouseCursorList(FQueryMouseCursorList).Remove(AHandlerProc); end; procedure TCustomSynEdit.RegisterBeforeKeyDownHandler(AHandlerProc: TKeyEvent); begin if FKeyDownEventList = nil then FKeyDownEventList := TLazSynKeyDownEventList.Create; FKeyDownEventList.Add(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.UnregisterBeforeKeyDownHandler(AHandlerProc: TKeyEvent); begin if FKeyDownEventList <> nil then FKeyDownEventList.Remove(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.RegisterBeforeKeyUpHandler(AHandlerProc: TKeyEvent); begin if FKeyUpEventList = nil then FKeyUpEventList := TLazSynKeyDownEventList.Create; FKeyUpEventList.Add(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.UnregisterBeforeKeyUpHandler(AHandlerProc: TKeyEvent); begin if FKeyUpEventList <> nil then FKeyUpEventList.Remove(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.RegisterBeforeKeyPressHandler(AHandlerProc: TKeyPressEvent); begin if FKeyPressEventList = nil then FKeyPressEventList := TLazSynKeyPressEventList.Create; FKeyPressEventList.Add(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.UnregisterBeforeKeyPressHandler(AHandlerProc: TKeyPressEvent); begin if FKeyPressEventList <> nil then FKeyPressEventList.Remove(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.RegisterBeforeUtf8KeyPressHandler(AHandlerProc: TUTF8KeyPressEvent); begin if FUtf8KeyPressEventList = nil then FUtf8KeyPressEventList := TLazSynUtf8KeyPressEventList.Create; FUtf8KeyPressEventList.Add(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.UnregisterBeforeUtf8KeyPressHandler(AHandlerProc: TUTF8KeyPressEvent); begin if FUtf8KeyPressEventList <> nil then FUtf8KeyPressEventList.Remove(TMethod(AHandlerProc)); end; procedure TCustomSynEdit.RegisterPaintEventHandler(APaintEventProc: TSynPaintEventProc; AnEvents: TSynPaintEvents); begin TSynPaintEventHandlerList(FPaintEventHandlerList).Add(APaintEventProc, AnEvents); end; procedure TCustomSynEdit.UnRegisterPaintEventHandler(APaintEventProc: TSynPaintEventProc); begin TSynPaintEventHandlerList(FPaintEventHandlerList).Remove(APaintEventProc); end; procedure TCustomSynEdit.RegisterScrollEventHandler(AScrollEventProc: TSynScrollEventProc; AnEvents: TSynScrollEvents); begin TSynScrollEventHandlerList(FScrollEventHandlerList).Add(AScrollEventProc, AnEvents); end; procedure TCustomSynEdit.UnRegisterScrollEventHandler(AScrollEventProc: TSynScrollEventProc); begin TSynScrollEventHandlerList(FScrollEventHandlerList).Remove(AScrollEventProc); end; procedure TCustomSynEdit.NotifyHookedCommandHandlers(var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer; ATime: THookedCommandFlag); var Handled: boolean; i: integer; Entry: THookedCommandHandlerEntry; begin Handled := FALSE; for i := 0 to GetHookedCommandHandlersCount - 1 do begin Entry := THookedCommandHandlerEntry(fHookedCommandHandlers[i]); if not(ATime in Entry.FFlags) then continue; // NOTE: Command should NOT be set to ecNone, because this might interfere // with other handlers. Set Handled to False instead (and check its value // to not process the command twice). Entry.fEvent(Self, ATime in [hcfPostExec, hcfFinish], Handled, Command, AChar, Data, Entry.fData); end; if Handled then Command := ecNone; end; procedure TCustomSynEdit.DoOnPaint; begin if Assigned(fOnPaint) then begin Canvas.Font.Assign(Font); Canvas.Brush.Color := Color; fOnPaint(Self, Canvas); end; end; function TCustomSynEdit.GetPaintArea: TLazSynSurfaceManager; begin Result := FPaintArea; end; function TCustomSynEdit.DoOnReplaceText(const ASearch, AReplace: string; Line, Column: integer): TSynReplaceAction; begin Result := raCancel; if Assigned(fOnReplaceText) then fOnReplaceText(Self, ASearch, AReplace, Line, Column, Result); end; procedure TCustomSynEdit.DoOnStatusChange(Changes: TSynStatusChanges); begin fStatusChanges := []; TSynStatusChangedHandlerList(FStatusChangedList).CallStatusChangedHandlers(Self, Changes); if Assigned(fOnStatusChange) then fOnStatusChange(Self, Changes); end; procedure TCustomSynEdit.UndoRedoAdded(Sender: TObject); begin // Todo: Check Paintlock, otherwise move to LinesChanged, LineCountChanged if Assigned(fOnChange) then fOnChange(Self); end; procedure TCustomSynEdit.ModifiedChanged(Sender: TObject); begin StatusChanged([scModified]); end; function TCustomSynEdit.LogicalToPhysicalPos(const p: TPoint): TPoint; begin Result := FTheLinesView.LogicalToPhysicalPos(p); end; function TCustomSynEdit.LogicalToPhysicalCol(const Line: String; Index, LogicalPos: integer): integer; // LogicalPos is 1-based // Index 0-based LineNumber begin Result := FTheLinesView.LogicalToPhysicalCol(Line, Index, LogicalPos); end; function TCustomSynEdit.PhysicalLineLength(Line: String; Index: integer): integer; begin Result:=LogicalToPhysicalCol(Line, Index, length(Line)+1) - 1 end; (* from SynMemo - NOT recommended to use - Extremly slow code SynEdit (and SynMemo) is a Linebased Editor and not meant to be accessed as a contineous text *) function TCustomSynEdit.CharIndexToRowCol(Index: integer): TPoint; var x, y, Chars: integer; e: string; LineEndLen: Integer; begin x := 0; y := 0; e:=LineEnding; LineEndLen:=length(e); Chars := 0; while y < TextBuffer.Count do begin x := Length(TextBuffer[y]); if Chars + x + LineEndLen > Index then begin x := Index - Chars; break; end; Inc(Chars, x + LineEndLen); x := 0; Inc(y); end; Result := Point(x + 1, y + 1); end; (* from SynMemo - NOT recommended to use - Extremly slow code SynEdit (and SynMemo) is a Linebased Editor and not meant to be accessed as a contineous text *) function TCustomSynEdit.RowColToCharIndex(RowCol: TPoint): integer; var i: integer; e: string; LineEndLen: Integer; begin Result := 0; RowCol.y := Min(TextBuffer.Count, RowCol.y) - 1; e:=LineEnding; LineEndLen:=length(e); for i := 0 to RowCol.y - 1 do Result := Result + Length(TextBuffer[i]) + LineEndLen; Result := Result + RowCol.x; end; function TCustomSynEdit.PhysicalToLogicalPos(const p: TPoint): TPoint; begin Result := FTheLinesView.PhysicalToLogicalPos(p); end; function TCustomSynEdit.PhysicalToLogicalCol(const Line: string; Index, PhysicalPos: integer): integer; begin Result := FTheLinesView.PhysicalToLogicalCol(Line, Index, PhysicalPos); end; function TCustomSynEdit.ScreenColumnToXValue(Col : integer) : integer; begin Result := FTextArea.ScreenColumnToXValue(Col); end; procedure TCustomSynEdit.PrimarySelectionRequest( const RequestedFormatID: TClipboardFormat; Data: TStream); var s: string; ClipHelper: TSynClipboardStream; begin if (not SelAvail) then exit; s:=SelText; if s = '' then exit; if RequestedFormatID = CF_TEXT then begin Data.Write(s[1],length(s)); end else if RequestedFormatID = TSynClipboardStream.ClipboardFormatId then begin ClipHelper := TSynClipboardStream.Create; try ClipHelper.SelectionMode := SelectionMode; // InternalText, so we don't need a 2nd call for CF_TEXT ClipHelper.InternalText := s; // Fold if eoFoldedCopyPaste in fOptions2 then s := FFoldedLinesView.GetFoldDescription( FBlockSelection.FirstLineBytePos.Y - 1, FBlockSelection.FirstLineBytePos.X, FBlockSelection.LastLineBytePos.Y - 1, FBlockSelection.LastLineBytePos.X); if length(s) > 0 then ClipHelper.AddTag(synClipTagFold, @s[1], length(s)); Data.Write(ClipHelper.Memory^, ClipHelper.Size); finally ClipHelper.Free; end; end; end; { TLazSynEditPlugin } constructor TLazSynEditPlugin.Create(AOwner: TComponent); begin if AOwner is TCustomSynEdit then begin inherited Create(nil); Editor := TCustomSynEdit(AOwner); end else inherited Create(AOwner); end; destructor TLazSynEditPlugin.Destroy; begin Editor := nil; inherited Destroy; end; procedure TLazSynEditPlugin.BeforeEditorChange; begin if (Editor <> nil) then begin DoEditorRemoving(Editor); UnRegisterFromEditor(Editor); end; end; procedure TLazSynEditPlugin.AfterEditorChange; begin if Editor <> nil then begin RegisterToEditor(Editor); DoEditorAdded(Editor); end; end; procedure TLazSynEditPlugin.RegisterToEditor(AValue: TCustomSynEdit); begin if AValue.fPlugins <> nil then AValue.fPlugins.Add(Self); end; procedure TLazSynEditPlugin.UnRegisterFromEditor(AValue: TCustomSynEdit); begin if AValue.fPlugins <> nil then AValue.fPlugins.Remove(Self); end; procedure TLazSynEditPlugin.SetEditor(const AValue: TCustomSynEdit); begin if AValue = FriendEdit then exit; BeforeEditorChange; FriendEdit := AValue; AfterEditorChange; end; function TLazSynEditPlugin.GetEditor: TCustomSynEdit; begin Result := FriendEdit as TCustomSynEdit; end; function TLazSynEditPlugin.OwnedByEditor: Boolean; begin Result := Owner = nil; end; procedure TLazSynEditPlugin.DoEditorDestroyed(const AValue: TCustomSynEdit); begin if Editor <> AValue then exit; if OwnedByEditor then begin // if no DoEditorDestroyed if TMethod(@DoEditorRemoving).Code = Pointer(@TLazSynEditPlugin.DoEditorDestroyed) then DoEditorRemoving(AValue); Free; end else Editor := nil; end; procedure TLazSynEditPlugin.DoEditorAdded(AValue: TCustomSynEdit); begin // end; procedure TLazSynEditPlugin.DoEditorRemoving(AValue: TCustomSynEdit); begin // end; procedure Register; begin RegisterClasses([TSynGutterPartList, TSynRightGutterPartList, TSynGutterSeparator, TSynGutterCodeFolding, TSynGutterLineNumber, TSynGutterChanges, TSynGutterMarks, TSynGutterLineOverview]); RegisterPropertyToSkip(TSynSelectedColor, 'OnChange', '', ''); RegisterPropertyToSkip(TSynSelectedColor, 'StartX', '', ''); RegisterPropertyToSkip(TSynSelectedColor, 'EndX', '', ''); RegisterPropertyToSkip(TSynGutter, 'ShowCodeFolding', '', ''); RegisterPropertyToSkip(TSynGutter, 'CodeFoldingWidth', '', ''); RegisterPropertyToSkip(TSynGutter, 'ShowChanges', '', ''); RegisterPropertyToSkip(TSynGutter, 'ShowLineNumbers', '', ''); RegisterPropertyToSkip(TSynGutter, 'ShowOnlyLineNumbersMultiplesOf', '', ''); RegisterPropertyToSkip(TSynGutter, 'ZeroStart', '', ''); RegisterPropertyToSkip(TSynGutter, 'MarkupInfoLineNumber', '', ''); RegisterPropertyToSkip(TSynGutter, 'MarkupInfoModifiedLine', '', ''); RegisterPropertyToSkip(TSynGutter, 'MarkupInfoCodeFoldingTree', '', ''); RegisterPropertyToSkip(TSynGutter, 'LeadingZeros', '', ''); RegisterPropertyToSkip(TSynGutter, 'DigitCount', '', ''); RegisterPropertyToSkip(TSynGutter, 'AllowSkipGutterSeparatorDraw', '', ''); RegisterPropertyToSkip(TSynGutter, 'GutterParts', '', ''); RegisterPropertyToSkip(TSynGutter, 'OnChange', '', ''); RegisterPropertyToSkip(TSynEdit, 'CFDividerDrawLevel', '', ''); end; { TSynHookedKeyTranslationList } procedure TSynHookedKeyTranslationList.CallHookedKeyTranslationHandlers(Sender: TObject; Code: word; SState: TShiftState; var Data: pointer; var IsStartOfCombo: boolean; var Handled: boolean; var Command: TSynEditorCommand; var ComboKeyStrokes: TSynEditKeyStrokes); var i: Integer; begin if ComboKeyStrokes <> nil then begin // Finish Combo for i := 0 to Count - 1 do THookedKeyTranslationEvent(Items[i])(Sender, Code, SState, Data, IsStartOfCombo, Handled, Command, True, ComboKeyStrokes); end else begin // New Stroke for i := 0 to Count - 1 do THookedKeyTranslationEvent(Items[i])(Sender, Code, SState, Data, IsStartOfCombo, Handled, Command, False, ComboKeyStrokes); end; end; { TLazSynUtf8KeyPressEventList } procedure TLazSynUtf8KeyPressEventList.CallUtf8KeyPressHandlers(Sender: TObject; var UTF8Key: TUTF8Char); var i: LongInt; begin i:=Count; while NextDownIndex(i) do TUTF8KeyPressEvent(Items[i])(Sender, UTF8Key); end; { TLazSynKeyPressEventList } procedure TLazSynKeyPressEventList.CallKeyPressHandlers(Sender: TObject; var Key: char); var i: LongInt; begin i:=Count; while NextDownIndex(i) do TKeyPressEvent(Items[i])(Sender, Key); end; { TSynUndoRedoItemHandlerList } function TSynUndoRedoItemHandlerList.CallUndoRedoItemHandlers(Caller: TObject; Item: TSynEditUndoItem): Boolean; var i: LongInt; begin i:=Count; Result := False; while NextDownIndex(i) and (not Result) do Result := TSynUndoRedoItemEvent(Items[i])(Caller, Item); end; { TLazSynMouseDownEventList } procedure TLazSynMouseDownEventList.CallMouseDownHandlers(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var i: LongInt; begin i:=Count; while NextDownIndex(i) do TMouseEvent(Items[i])(Sender, Button, Shift, X, Y); end; { TLazSynKeyDownEventList } procedure TLazSynKeyDownEventList.CallKeyDownHandlers(Sender: TObject; var Key: Word; Shift: TShiftState); var i: LongInt; begin i:=Count; while NextDownIndex(i) do TKeyEvent(Items[i])(Sender, Key, Shift); end; { TSynStatusChangedHandlerList } procedure TSynStatusChangedHandlerList.Add(AHandler: TStatusChangeEvent; Changes: TSynStatusChanges); begin AddBitFilter(TMethod(AHandler), LongInt(Changes)); end; procedure TSynStatusChangedHandlerList.Remove(AHandler: TStatusChangeEvent); begin inherited Remove(TMethod(AHandler)); end; procedure TSynStatusChangedHandlerList.CallStatusChangedHandlers(Sender: TObject; Changes: TSynStatusChanges); var i: Integer; begin i:=Count; while NextDownIndexBitFilter(i, LongInt(Changes)) do TStatusChangeEvent(FItems[i].FHandler)(Sender, Changes); end; { TSynPaintEventHandlerList } procedure TSynPaintEventHandlerList.Add(AHandler: TSynPaintEventProc; Changes: TSynPaintEvents); begin AddBitFilter(TMethod(AHandler), LongInt(Changes)); end; procedure TSynPaintEventHandlerList.Remove(AHandler: TSynPaintEventProc); begin inherited Remove(TMethod(AHandler)); end; procedure TSynPaintEventHandlerList.CallPaintEventHandlers(Sender: TObject; AnEvent: TSynPaintEvent; const rcClip: TRect); var i: Integer; begin i:=Count; while NextDownIndexBitFilter(i, LongInt([AnEvent])) do TSynPaintEventProc(FItems[i].FHandler)(Sender, AnEvent, rcClip); end; { TSynScrollEventHandlerList} procedure TSynScrollEventHandlerList.Add(AHandler: TSynScrollEventProc; Changes: TSynScrollEvents); begin AddBitFilter(TMethod(AHandler), LongInt(Changes)); end; procedure TSynScrollEventHandlerList.Remove(AHandler: TSynScrollEventProc); begin inherited Remove(TMethod(AHandler)); end; procedure TSynScrollEventHandlerList.CallScrollEventHandlers(Sender: TObject; AnEvent: TSynScrollEvent; dx, dy: Integer; const rcScroll, rcClip: TRect); var i: Integer; begin i:=Count; while NextDownIndexBitFilter(i, LongInt([AnEvent])) do TSynScrollEventProc(FItems[i].FHandler)(Sender, AnEvent, dx, dy, rcScroll, rcClip); end; { TSynQueryMouseCursorList } procedure TSynQueryMouseCursorList.Add(AHandler: TSynQueryMouseCursorEvent); begin inherited Add(TMethod(AHandler)); end; procedure TSynQueryMouseCursorList.Remove(AHandler: TSynQueryMouseCursorEvent); begin inherited Remove(TMethod(AHandler)); end; procedure TSynQueryMouseCursorList.CallScrollEventHandlers(Sender: TObject; const AMouseLocation: TSynMouseLocationInfo; var AnCursor: TCursor); var i, p: Integer; c: TObject; begin p := 0; c := nil; i:=Count; while NextDownIndex(i) do TSynQueryMouseCursorEvent(Items[i])(Sender, AMouseLocation, AnCursor, p, c); end; { TSynEditMarkListInternal } function TSynEditMarkListInternal.GetLinesView: TSynEditStringsLinked; begin Result := FLines; end; procedure TSynEditMarkListInternal.SetLinesView( const AValue: TSynEditStringsLinked); begin FLines.RemoveEditHandler(@DoLinesEdited); FLines := AValue; FLines.AddEditHandler(@DoLinesEdited); end; procedure TSynEditMarkListInternal.AddOwnerEdit(AEdit: TSynEditBase); begin FOwnerList.Add(AEdit); end; procedure TSynEditMarkListInternal.RemoveOwnerEdit(AEdit: TSynEditBase); begin FOwnerList.Remove(AEdit); end; initialization InitSynDefaultFont; Register; LOG_SynMouseEvents := DebugLogger.RegisterLogGroup('SynMouseEvents' {$IFDEF SynMouseEvents} , True {$ENDIF} ); end.