mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-03 00:03:40 +02:00
898 lines
28 KiB
ObjectPascal
898 lines
28 KiB
ObjectPascal
{-------------------------------------------------------------------------------
|
|
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.
|
|
|
|
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.
|
|
|
|
-------------------------------------------------------------------------------}
|
|
unit SynEditPointClasses;
|
|
|
|
{$I synedit.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LCLProc,
|
|
{$IFDEF SYN_MBCSSUPPORT}
|
|
Imm,
|
|
{$ENDIF}
|
|
SynEditTextBase, SynEditTypes, SynEditMiscProcs, SynEditTextBuffer;
|
|
|
|
type
|
|
|
|
TInvalidateLines = procedure(FirstLine, LastLine: integer) of Object;
|
|
TLinesCountChanged = procedure (FirstLine, Count: integer) of Object;
|
|
|
|
{ TSynEditPointBase }
|
|
|
|
TSynEditPointBase = class
|
|
protected
|
|
FLines: TSynEditStrings;
|
|
FOnChangeList: TMethodList;
|
|
public
|
|
constructor Create;
|
|
constructor Create(Lines: TSynEditStrings);
|
|
destructor Destroy; override;
|
|
procedure AddChangeHandler(AHandler: TNotifyEvent);
|
|
procedure RemoveChangeHandler(AHandler: TNotifyEvent);
|
|
property Lines: TSynEditStrings read FLines write FLines;
|
|
end;
|
|
|
|
TSynEditCaret = class;
|
|
|
|
{ TSynEditSelection }
|
|
|
|
TSynEditSelection = class(TSynEditPointBase)
|
|
FCaret: TSynEditCaret;
|
|
fUndoList: TSynEditUndoList;
|
|
FInvalidateLinesMethod: TInvalidateLines;
|
|
FLinesDeletedMethod: TLinesCountChanged;
|
|
FLinesInsertedMethod: TLinesCountChanged;
|
|
FEnabled: Boolean;
|
|
FSpacesToTabs: Boolean;
|
|
FSelectionMode: TSynSelectionMode;
|
|
FStartLinePos: Integer; // 1 based
|
|
FStartBytePos: Integer; // 1 based
|
|
FEndLinePos: Integer; // 1 based
|
|
FEndBytePos: Integer; // 1 based
|
|
private
|
|
function AdjustBytePosToCharacterStart(Line: integer; BytePos: integer): integer;
|
|
function GetFirstLineBytePos: TPoint;
|
|
function GetLastLineBytePos: TPoint;
|
|
procedure SetEnabled(const Value : Boolean);
|
|
procedure SetSelectionMode(const Value: TSynSelectionMode);
|
|
function GetStartLineBytePos: TPoint;
|
|
procedure SetStartLineBytePos(Value: TPoint);
|
|
function GetEndLineBytePos: TPoint;
|
|
procedure SetEndLineBytePos(Value: TPoint);
|
|
function GetSelText: string;
|
|
procedure SetSelText(const Value: string);
|
|
public
|
|
FMaxLeftChar: Integer;
|
|
constructor Create(ALines: TSynEditStrings);
|
|
//destructor Destroy; override;
|
|
procedure SetSelTextPrimitive(PasteMode: TSynSelectionMode; Value: PChar;
|
|
ATag: PInteger);
|
|
function SelAvail: Boolean;
|
|
function IsBackwardSel: Boolean; // SelStart < SelEnd ?
|
|
property Enabled: Boolean read FEnabled write SetEnabled;
|
|
property SpacesToTabs: Boolean read FSpacesToTabs write FSpacesToTabs;
|
|
property SelectionMode: TSynSelectionMode
|
|
read FSelectionMode write SetSelectionMode;
|
|
property SelText: String read GetSelText write SetSelText;
|
|
property StartLineBytePos: TPoint
|
|
read GetStartLineBytePos write SetStartLineBytePos;
|
|
property EndLineBytePos: TPoint
|
|
read GetEndLineBytePos write SetEndLineBytePos;
|
|
property StartLinePos: Integer read FStartLinePos;
|
|
property EndLinePos: Integer read FEndLinePos;
|
|
property StartBytePos: Integer read FStartBytePos;
|
|
property EndBytePos: Integer read FEndBytePos;
|
|
// Bounds ordered
|
|
property FirstLineBytePos: TPoint read GetFirstLineBytePos;
|
|
property LastLineBytePos: TPoint read GetLastLineBytePos;
|
|
property InvalidateLinesMethod : TInvalidateLines write FInvalidateLinesMethod;
|
|
property LinesDeletedMethod: TLinesCountChanged write FLinesDeletedMethod;
|
|
property LinesInsertedMethod: TLinesCountChanged write FLinesInsertedMethod;
|
|
property Caret: TSynEditCaret read FCaret write FCaret;
|
|
property UndoList: TSynEditUndoList read fUndoList write fUndoList;
|
|
end;
|
|
|
|
{ TSynEditCaret }
|
|
|
|
TSynEditCaret = class(TSynEditPointBase)
|
|
fLinePos: Integer; // 1 based
|
|
fCharPos: Integer; // 1 based
|
|
private
|
|
function GetLineCharPos: TPoint;
|
|
function GetLineText : string;
|
|
procedure SetLineCharPos(const AValue: TPoint);
|
|
procedure setCharPos(const AValue: Integer);
|
|
procedure setLinePos(const AValue: Integer);
|
|
procedure SetLineText(const AValue : string);
|
|
public
|
|
constructor Create;
|
|
property LinePos : Integer read fLinePos write setLinePos;
|
|
property CharPos : Integer read fCharPos write setCharPos;
|
|
property LineCharPos : TPoint read GetLineCharPos write SetLineCharPos;
|
|
property LineText: string read GetLineText write SetLineText;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TSynEditPointBase }
|
|
|
|
constructor TSynEditPointBase.Create;
|
|
begin
|
|
FOnChangeList := TMethodList.Create;
|
|
end;
|
|
|
|
constructor TSynEditPointBase.Create(Lines : TSynEditStrings);
|
|
begin
|
|
Create;
|
|
FLines := Lines;
|
|
end;
|
|
|
|
destructor TSynEditPointBase.Destroy;
|
|
begin
|
|
FreeAndNil(FOnChangeList);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TSynEditPointBase.AddChangeHandler(AHandler : TNotifyEvent);
|
|
begin
|
|
FOnChangeList.Add(TMethod(AHandler));
|
|
end;
|
|
|
|
procedure TSynEditPointBase.RemoveChangeHandler(AHandler : TNotifyEvent);
|
|
begin
|
|
FOnChangeList.Remove(TMethod(AHandler));
|
|
end;
|
|
|
|
{ TSynEditCaret }
|
|
|
|
function TSynEditCaret.GetLineCharPos : TPoint;
|
|
begin
|
|
Result := Point(fCharPos, fLinePos);
|
|
end;
|
|
|
|
procedure TSynEditCaret.SetLineCharPos(const AValue : TPoint);
|
|
begin
|
|
if (fCharPos = AValue.X) and (fLinePos = AValue.Y) then exit;
|
|
fCharPos:= AValue.X;
|
|
fLinePos:= AValue.Y;
|
|
fOnChangeList.CallNotifyEvents(self);
|
|
end;
|
|
|
|
procedure TSynEditCaret.setCharPos(const AValue : Integer);
|
|
begin
|
|
if fCharPos = AValue then exit;
|
|
fCharPos:= AValue;
|
|
fOnChangeList.CallNotifyEvents(self);
|
|
end;
|
|
|
|
procedure TSynEditCaret.setLinePos(const AValue : Integer);
|
|
begin
|
|
if fLinePos = AValue then exit;
|
|
fLinePos:= AValue;
|
|
fOnChangeList.CallNotifyEvents(self);
|
|
end;
|
|
|
|
function TSynEditCaret.GetLineText : string;
|
|
begin
|
|
if (LinePos >= 1) and (LinePos <= FLines.Count) then
|
|
Result := FLines[LinePos - 1]
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
procedure TSynEditCaret.SetLineText(const AValue : string);
|
|
begin
|
|
if (LinePos >= 1) and (LinePos <= Max(1, FLines.Count)) then
|
|
FLines[LinePos - 1] := AValue;
|
|
end;
|
|
|
|
constructor TSynEditCaret.Create;
|
|
begin
|
|
inherited Create;
|
|
fLinePos:= 1;
|
|
fCharPos:= 1;
|
|
end;
|
|
|
|
{ TSynEditSelection }
|
|
|
|
constructor TSynEditSelection.Create(ALines : TSynEditStrings);
|
|
begin
|
|
Inherited Create(ALines);
|
|
fMaxLeftChar := 1024;
|
|
FSelectionMode := smNormal;
|
|
FStartLinePos := 1;
|
|
FStartBytePos := 1;
|
|
FEndLinePos := 1;
|
|
FEndBytePos := 1;
|
|
FEnabled := True;
|
|
end;
|
|
|
|
function TSynEditSelection.GetSelText : string;
|
|
|
|
function CopyPadded(const S: string; Index, Count: integer): string;
|
|
var
|
|
SrcLen: Integer;
|
|
DstLen: integer;
|
|
P: PChar;
|
|
begin
|
|
SrcLen := Length(S);
|
|
DstLen := Index + Count;
|
|
if SrcLen >= DstLen then
|
|
Result := Copy(S, Index, Count)
|
|
else begin
|
|
SetLength(Result, DstLen);
|
|
P := PChar(Pointer(Result));
|
|
StrPCopy(P, Copy(S, Index, Count));
|
|
Inc(P, SrcLen);
|
|
FillChar(P^, DstLen - Srclen, $20);
|
|
end;
|
|
end;
|
|
|
|
procedure CopyAndForward(const S: string; Index, Count: Integer; var P:
|
|
PChar);
|
|
var
|
|
pSrc: PChar;
|
|
SrcLen: Integer;
|
|
DstLen: Integer;
|
|
begin
|
|
SrcLen := Length(S);
|
|
if (Index <= SrcLen) and (Count > 0) then begin
|
|
Dec(Index);
|
|
pSrc := PChar(Pointer(S)) + Index;
|
|
DstLen := Min(SrcLen - Index, Count);
|
|
Move(pSrc^, P^, DstLen);
|
|
Inc(P, DstLen);
|
|
P^ := #0;
|
|
end;
|
|
end;
|
|
|
|
procedure CopyPaddedAndForward(const S: string; Index, Count: Integer;
|
|
var P: PChar);
|
|
var
|
|
OldP: PChar;
|
|
Len: Integer;
|
|
begin
|
|
OldP := P;
|
|
CopyAndForward(S, Index, Count, P);
|
|
Len := Count - (P - OldP);
|
|
FillChar(P^, Len, #$20);
|
|
Inc(P, Len);
|
|
end;
|
|
|
|
|
|
const
|
|
sLineBreak = {$IFDEF SYN_LAZARUS}LineEnding{$ELSE}#$0D#$0A{$ENDIF};
|
|
var
|
|
First, Last, TotalLen: Integer;
|
|
ColFrom, ColTo: Integer;
|
|
I: Integer;
|
|
{$IFDEF SYN_MBCSSUPPORT}
|
|
l, r: Integer;
|
|
s: string;
|
|
{$ELSE}
|
|
ColLen: integer;
|
|
{$ENDIF}
|
|
P: PChar;
|
|
begin
|
|
if not SelAvail then
|
|
Result := ''
|
|
else begin
|
|
if IsBackwardSel then begin
|
|
ColFrom := FEndBytePos;
|
|
First := FEndLinePos - 1;
|
|
ColTo := FStartBytePos;
|
|
Last := FStartLinePos - 1;
|
|
end else begin
|
|
ColFrom := FStartBytePos;
|
|
First := FStartLinePos - 1;
|
|
ColTo := FEndBytePos;
|
|
Last := FEndLinePos - 1;
|
|
end;
|
|
TotalLen := 0;
|
|
case SelectionMode of
|
|
smNormal:
|
|
if (First = Last) then
|
|
Result := Copy(FLines[First], ColFrom, ColTo - ColFrom)
|
|
else begin
|
|
// step1: calculate total length of result string
|
|
TotalLen := Max(0, Length(FLines[First]) - ColFrom + 1);
|
|
for i := First + 1 to Last - 1 do
|
|
Inc(TotalLen, Length(FLines[i]));
|
|
Inc(TotalLen, ColTo - 1);
|
|
Inc(TotalLen, Length(sLineBreak) * (Last - First));
|
|
// step2: build up result string
|
|
SetLength(Result, TotalLen);
|
|
P := PChar(Pointer(Result));
|
|
CopyAndForward(FLines[First], ColFrom, MaxInt, P);
|
|
CopyAndForward(sLineBreak, 1, MaxInt, P);
|
|
for i := First + 1 to Last - 1 do begin
|
|
CopyAndForward(FLines[i], 1, MaxInt, P);
|
|
CopyAndForward(sLineBreak, 1, MaxInt, P);
|
|
end;
|
|
{$IFDEF SYN_LAZARUS}
|
|
CopyPaddedAndForward(FLines[Last], 1, ColTo - 1, P);
|
|
{$ELSE}
|
|
CopyAndForward(FLines[Last], 1, ColTo - 1, P);
|
|
{$ENDIF}
|
|
end;
|
|
smColumn:
|
|
begin
|
|
if ColFrom > ColTo then
|
|
SwapInt(ColFrom, ColTo);
|
|
// step1: calclate total length of result string
|
|
{$IFNDEF SYN_MBCSSUPPORT}
|
|
ColLen := ColTo - ColFrom;
|
|
TotalLen := ColLen + (ColLen + Length(sLineBreak)) * (Last - First);
|
|
// step2: build up result string
|
|
SetLength(Result, TotalLen);
|
|
P := PChar(Pointer(Result));
|
|
for i := First to Last - 1 do begin
|
|
CopyPaddedAndForward(FLines[i], ColFrom, ColLen, P);
|
|
CopyAndForward(sLineBreak, 1, MaxInt, P);
|
|
end;
|
|
CopyPaddedAndForward(FLines[Last], ColFrom, ColLen, P);
|
|
{$ELSE} //SYN_MBCSSUPPORT
|
|
for i := First to Last do begin
|
|
s := FLines[i];
|
|
l := ColFrom;
|
|
r := ColTo;
|
|
MBCSGetSelRangeInLineWhenColumnSelectionMode(s, l, r);
|
|
Inc(TotalLen, r - l);
|
|
end;
|
|
Inc(TotalLen, Length(sLineBreak) * (Last - First));
|
|
// step2: build up result string
|
|
SetLength(Result, TotalLen);
|
|
P := PChar(Result);
|
|
for i := First to Last - 1 do begin
|
|
s := FLines[i];
|
|
l := ColFrom;
|
|
r := ColTo;
|
|
MBCSGetSelRangeInLineWhenColumnSelectionMode(s, l, r);
|
|
CopyPaddedAndForward(s, l, r - l, P);
|
|
CopyAndForward(sLineBreak, 1, MaxInt, P);
|
|
end;
|
|
s := FLines[Last];
|
|
l := ColFrom;
|
|
r := ColTo;
|
|
MBCSGetSelRangeInLineWhenColumnSelectionMode(s, l, r);
|
|
CopyPaddedAndForward(FLines[Last], l, r - l, P);
|
|
{$ENDIF}
|
|
end;
|
|
smLine:
|
|
begin
|
|
// If block selection includes LastLine,
|
|
// line break code(s) of the last line will not be added.
|
|
// step1: calclate total length of result string
|
|
for i := First to Last do
|
|
Inc(TotalLen, Length(FLines[i]) + Length(sLineBreak));
|
|
if Last = FLines.Count then
|
|
Dec(TotalLen, Length(sLineBreak));
|
|
// step2: build up result string
|
|
SetLength(Result, TotalLen);
|
|
P := PChar(Pointer(Result));
|
|
for i := First to Last - 1 do begin
|
|
CopyAndForward(FLines[i], 1, MaxInt, P);
|
|
CopyAndForward(sLineBreak, 1, MaxInt, P);
|
|
end;
|
|
CopyAndForward(FLines[Last], 1, MaxInt, P);
|
|
if (Last + 1) < FLines.Count then
|
|
CopyAndForward(sLineBreak, 1, MaxInt, P);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditSelection.SetSelText(const Value : string);
|
|
var
|
|
StartOfBlock, EndOfBlock: TPoint;
|
|
begin
|
|
if SelAvail then begin
|
|
if IsBackwardSel then
|
|
fUndoList.AddChange(crDelete, StartLineBytePos, EndLineBytePos,
|
|
GetSelText, SelectionMode)
|
|
else
|
|
fUndoList.AddChange(crDeleteAfterCursor, EndLineBytePos, StartLineBytePos,
|
|
GetSelText, SelectionMode);
|
|
StartOfBlock := FirstLineBytePos;
|
|
EndOfBlock := LastLineBytePos;
|
|
end else begin
|
|
StartOfBlock := FCaret.LineCharPos;
|
|
EndOfBlock := FCaret.LineCharPos;
|
|
end;
|
|
StartLineBytePos := StartOfBlock;
|
|
EndLineBytePos := EndOfBlock;
|
|
SetSelTextPrimitive(smNormal, PChar(Value), nil);
|
|
if SelectionMode = smLine then
|
|
StartOfBlock.X := 1;
|
|
if length(Value) > 0 then
|
|
fUndoList.AddChange(crInsert, StartOfBlock, EndLineBytePos, '', smNormal);
|
|
end;
|
|
|
|
procedure TSynEditSelection.SetSelTextPrimitive(PasteMode : TSynSelectionMode; Value : PChar; ATag : PInteger);
|
|
var
|
|
BB, BE: TPoint;
|
|
TempString: string;
|
|
|
|
procedure DeleteSelection;
|
|
var
|
|
x, MarkOffset: Integer;
|
|
UpdateMarks: boolean;
|
|
NewCaretXY: TPoint;
|
|
{$IFDEF SYN_MBCSSUPPORT}
|
|
l, r: Integer;
|
|
{$ENDIF}
|
|
begin
|
|
UpdateMarks := FALSE;
|
|
MarkOffset := 0;
|
|
case SelectionMode of
|
|
smNormal:
|
|
begin
|
|
NewCaretXY := FLines.LogicalToPhysicalPos(BB);
|
|
if FLines.Count > 0 then begin
|
|
// Create a string that contains everything on the first line up
|
|
// to the selection mark, and everything on the last line after
|
|
// the selection mark.
|
|
TempString := Copy(FLines[BB.Y - 1], 1, BB.X - 1) +
|
|
Copy(FLines[BE.Y - 1], BE.X, MaxInt);
|
|
// Delete all FLines in the selection range.
|
|
TSynEditStrings(FLines).DeleteLines(BB.Y-1, BE.Y - BB.Y);
|
|
FLines[BB.Y - 1] := TempString;
|
|
end;
|
|
UpdateMarks := TRUE;
|
|
FCaret.LineCharPos := NewCaretXY;
|
|
end;
|
|
smColumn:
|
|
begin
|
|
// swap X if needed
|
|
if BB.X > BE.X then
|
|
{$IFDEF SYN_COMPILER_3_UP}
|
|
SwapInt(BB.X, BE.X);
|
|
{$ELSE}
|
|
begin
|
|
x := BB.X;
|
|
BB.X := BE.X;
|
|
BE.X := x;
|
|
end;
|
|
{$ENDIF}
|
|
NewCaretXY := FLines.LogicalToPhysicalPos(Point(BB.X, FEndLinePos));
|
|
for x := BB.Y - 1 to BE.Y - 1 do begin
|
|
TempString := FLines[x];
|
|
{$IFNDEF SYN_MBCSSUPPORT}
|
|
Delete(TempString, BB.X, BE.X - BB.X);
|
|
{$ELSE}
|
|
l := BB.X;
|
|
r := BE.X;
|
|
MBCSGetSelRangeInLineWhenColumnSelectionMode(TempString, l, r);
|
|
{$IFDEF USE_UTF8BIDI_LCL}
|
|
VDelete(TempString, l, r - 1);
|
|
{$ELSE USE_UTF8BIDI_LCL}
|
|
Delete(TempString, l, r - l);
|
|
{$ENDIF USE_UTF8BIDI_LCL}
|
|
{$ENDIF}
|
|
FLines[x] := TempString;
|
|
end;
|
|
// FLines never get deleted completely, so keep caret at end.
|
|
FCaret.LineCharPos := NewCaretXY;
|
|
// Column deletion never removes a line entirely, so no mark
|
|
// updating is needed here.
|
|
end;
|
|
smLine:
|
|
begin
|
|
if BE.Y = FLines.Count then begin
|
|
FLines[BE.Y - 1] := '';
|
|
for x := BE.Y - 2 downto BB.Y - 1 do
|
|
FLines.Delete(x);
|
|
end else
|
|
for x := BE.Y - 1 downto BB.Y - 1 do
|
|
FLines.Delete(x);
|
|
// smLine deletion always resets to first column.
|
|
FCaret.LineCharPos := Point(1, BB.Y);
|
|
UpdateMarks := TRUE;
|
|
MarkOffset := 1;
|
|
end;
|
|
end;
|
|
// Update marks
|
|
if UpdateMarks then
|
|
FLinesDeletedMethod(BB.Y, BE.Y - BB.Y + MarkOffset);
|
|
end;
|
|
|
|
procedure InsertText;
|
|
|
|
function CountLines(p: PChar): integer;
|
|
begin
|
|
Result := 0;
|
|
while p^ <> #0 do begin
|
|
if p^ = #13 then
|
|
Inc(p);
|
|
if p^ = #10 then
|
|
Inc(p);
|
|
Inc(Result);
|
|
p := GetEOL(p);
|
|
end;
|
|
end;
|
|
|
|
function InsertNormal: Integer;
|
|
var
|
|
sLeftSide: string;
|
|
sRightSide: string;
|
|
Str: string;
|
|
Start: PChar;
|
|
P: PChar;
|
|
LogCaretXY: TPoint;
|
|
PhysicalLineEndPos: LongInt;
|
|
begin
|
|
Result := 0;
|
|
LogCaretXY := FLines.PhysicalToLogicalPos(FCaret.LineCharPos);
|
|
sLeftSide := Copy(FCaret.LineText, 1, LogCaretXY.X - 1);
|
|
if LogCaretXY.X - 1 > Length(sLeftSide) then begin
|
|
PhysicalLineEndPos:= FLines.LogicalToPhysicalPos
|
|
(Point(Length(sLeftSide)+1, FCaret.LinePos)).X-1;
|
|
sLeftSide := sLeftSide
|
|
+ CreateTabsAndSpaces(FCaret.CharPos,
|
|
FCaret.CharPos-1-PhysicalLineEndPos,
|
|
FLines.TabWidth,
|
|
FSpacesToTabs);
|
|
end;
|
|
sRightSide := Copy(FCaret.LineText, LogCaretXY.X,
|
|
Length(FCaret.LineText) - (LogCaretXY.X - 1));
|
|
// step1: insert the first line of Value into current line
|
|
Start := PChar(Value);
|
|
P := GetEOL(Start);
|
|
if P^ <> #0 then begin
|
|
SetString(Str, Value, P - Start);
|
|
TSynEditStrings(FLines).InsertLines(FCaret.LinePos - 1, CountLines(P));
|
|
FLines[FCaret.LinePos - 1] := sLeftSide + Str;
|
|
end else begin
|
|
FLines[FCaret.LinePos - 1] := sLeftSide + Value + sRightSide;
|
|
FCaret.CharPos := FLines.LogicalToPhysicalPos(
|
|
Point(1 + Length(sLeftSide + Value),
|
|
FCaret.LinePos)).X;
|
|
end;
|
|
// step2: insert left lines of Value
|
|
while P^ <> #0 do begin
|
|
if P^ = #13 then
|
|
Inc(P);
|
|
if P^ = #10 then
|
|
Inc(P);
|
|
FCaret.LinePos := FCaret.LinePos + 1;
|
|
Start := P;
|
|
P := GetEOL(Start);
|
|
if P = Start then begin
|
|
if p^ <> #0 then
|
|
FLines[FCaret.LinePos - 1] := ''
|
|
else
|
|
FLines[FCaret.LinePos - 1] := sRightSide;
|
|
end else begin
|
|
SetString(Str, Start, P - Start);
|
|
if p^ <> #0 then
|
|
FLines[FCaret.LinePos - 1] := Str
|
|
else
|
|
FLines[FCaret.LinePos - 1] := Str + sRightSide
|
|
end;
|
|
if p^=#0 then
|
|
FCaret.CharPos := FLines.LogicalToPhysicalPos(
|
|
Point(1 + Length(FLines[FCaret.LinePos - 1]) - Length(sRightSide),
|
|
FCaret.LinePos)).X;
|
|
Inc(Result);
|
|
end;
|
|
// StatusChanged([scCaretX]);
|
|
end;
|
|
|
|
function InsertColumn: Integer;
|
|
var
|
|
Str: string;
|
|
Start: PChar;
|
|
P: PChar;
|
|
Len: Integer;
|
|
InsertPos: Integer;
|
|
LogicalInsertPos: Integer;
|
|
begin
|
|
// Insert string at current position
|
|
InsertPos := FCaret.CharPos;
|
|
Start := PChar(Value);
|
|
repeat
|
|
P := GetEOL(Start);
|
|
if P <> Start then begin
|
|
SetLength(Str, P - Start);
|
|
Move(Start^, Str[1], P - Start);
|
|
if FCaret.LinePos > FLines.Count then
|
|
FLines.Add(StringOfChar(' ', InsertPos - 1) + Str)
|
|
else begin
|
|
TempString := FLines[FCaret.LinePos - 1];
|
|
Len := Length(TempString);
|
|
LogicalInsertPos := FLines.PhysicalToLogicalCol(TempString,InsertPos);
|
|
if Len < LogicalInsertPos
|
|
then begin
|
|
TempString :=
|
|
TempString + StringOfChar(' ', LogicalInsertPos - Len - 1)
|
|
+ Str
|
|
end else begin
|
|
{$IFDEF SYN_MBCSSUPPORT}
|
|
if mbTrailByte = ByteType(TempString, InsertPos) then
|
|
Insert(Str, TempString, InsertPos + 1)
|
|
else
|
|
{$ENDIF}
|
|
System.Insert(Str, TempString, LogicalInsertPos);
|
|
end;
|
|
FLines[FCaret.LinePos - 1] := TempString;
|
|
end;
|
|
end;
|
|
if ATag <> nil then
|
|
ATag^ := P - Start;
|
|
if p^ in [#10,#13] then begin
|
|
if (p[1] in [#10,#13]) and (p[1]<>p^) then
|
|
inc(p,2)
|
|
else
|
|
Inc(P);
|
|
FCaret.LinePos := FCaret.LinePos + 1;
|
|
end;
|
|
Start := P;
|
|
until P^ = #0;
|
|
FCaret.CharPos:= FCaret.CharPos + Length(Str);
|
|
Result := 0;
|
|
end;
|
|
|
|
function InsertLine: Integer;
|
|
var
|
|
Start: PChar;
|
|
P: PChar;
|
|
Str: string;
|
|
n: Integer;
|
|
begin
|
|
Result := 0;
|
|
FCaret.CharPos := 1;
|
|
// Insert string before current line
|
|
Start := PChar(Value);
|
|
repeat
|
|
P := GetEOL(Start);
|
|
if P <> Start then begin
|
|
SetLength(Str, P - Start);
|
|
Move(Start^, Str[1], P - Start);
|
|
end else
|
|
Str := '';
|
|
if (P^ = #0) then begin // Not a full line?
|
|
n := FLines.Count;
|
|
if (n >= FCaret.LinePos) then
|
|
FLines[FCaret.LinePos - 1] := Str + FLines[FCaret.LinePos - 1]
|
|
else
|
|
FLines.Add(Str);
|
|
FCaret.CharPos := 1 + Length(Str);
|
|
end else begin
|
|
FLines.Insert(FCaret.LinePos - 1, Str);
|
|
FCaret.LinePos := FCaret.LinePos + 1;
|
|
Inc(Result);
|
|
if P^ = #13 then
|
|
Inc(P);
|
|
if P^ = #10 then
|
|
Inc(P);
|
|
Start := P;
|
|
end;
|
|
until P^ = #0;
|
|
// StatusChanged([scCaretX]);
|
|
end;
|
|
|
|
var
|
|
StartLine: Integer;
|
|
InsertedLines: Integer;
|
|
begin
|
|
if Value = '' then
|
|
Exit;
|
|
if FLines.Count = 0 then
|
|
FLines.Add('');
|
|
|
|
// Using a TStringList to do this would be easier, but if we're dealing
|
|
// with a large block of text, it would be very inefficient. Consider:
|
|
// Assign Value parameter to TStringList.Text: that parses through it and
|
|
// creates a copy of the string for each line it finds. That copy is passed
|
|
// to the Add method, which in turn creates a copy. Then, when you actually
|
|
// use an item in the list, that creates a copy to return to you. That's
|
|
// 3 copies of every string vs. our one copy below. I'd prefer no copies,
|
|
// but we aren't set up to work with PChars that well.
|
|
|
|
StartLine := FCaret.LinePos;
|
|
case PasteMode of
|
|
smNormal:
|
|
InsertedLines := InsertNormal;
|
|
smColumn:
|
|
InsertedLines := InsertColumn;
|
|
smLine:
|
|
InsertedLines := InsertLine;
|
|
else
|
|
InsertedLines := 0;
|
|
end;
|
|
// We delete selected based on the current selection mode, but paste
|
|
// what's on the clipboard according to what it was when copied.
|
|
// Update marks
|
|
if InsertedLines > 0 then
|
|
FLinesInsertedMethod(StartLine, InsertedLines);
|
|
// Force caret reset
|
|
// CaretXY := CaretXY;
|
|
end;
|
|
|
|
begin
|
|
FLines.BeginUpdate;
|
|
try
|
|
// BB is lower than BE
|
|
BB := FirstLineBytePos;
|
|
BE := LastLineBytePos;
|
|
if SelAvail then
|
|
DeleteSelection;
|
|
if (Value <> nil) and (Value[0] <> #0) then
|
|
InsertText;
|
|
finally
|
|
FLines.EndUpdate; // May reset Block Begin
|
|
end;
|
|
end;
|
|
|
|
function TSynEditSelection.GetStartLineBytePos : TPoint;
|
|
begin
|
|
Result.y := FStartLinePos;
|
|
Result.x := FStartBytePos;
|
|
end;
|
|
|
|
procedure TSynEditSelection.SetEnabled(const Value : Boolean);
|
|
begin
|
|
if FEnabled = Value then exit;
|
|
FEnabled := Value;
|
|
if not Enabled then SetStartLineBytePos(EndLineBytePos);
|
|
end;
|
|
|
|
procedure TSynEditSelection.SetStartLineBytePos(Value : TPoint);
|
|
// logical position (byte)
|
|
var
|
|
nInval1, nInval2: integer;
|
|
SelChanged: boolean;
|
|
begin
|
|
Value.x := MinMax(Value.x, 1, fMaxLeftChar);
|
|
Value.y := MinMax(Value.y, 1, fLines.Count);
|
|
if (SelectionMode = smNormal) then
|
|
if (Value.y >= 1) and (Value.y <= FLines.Count) then
|
|
Value.x := AdjustBytePosToCharacterStart(Value.y,Value.x)
|
|
else
|
|
Value.x := 1;
|
|
if SelAvail then begin
|
|
if FStartLinePos < FEndLinePos then begin
|
|
nInval1 := Min(Value.Y, FStartLinePos);
|
|
nInval2 := Max(Value.Y, FEndLinePos);
|
|
end else begin
|
|
nInval1 := Min(Value.Y, FEndLinePos);
|
|
nInval2 := Max(Value.Y, FStartLinePos);
|
|
end;
|
|
FInvalidateLinesMethod(nInval1, nInval2);
|
|
SelChanged := TRUE;
|
|
end else begin
|
|
SelChanged := (FStartBytePos <> Value.X) or (FStartLinePos <> Value.Y) or
|
|
(FEndBytePos <> Value.X) or (FEndLinePos <> Value.Y);
|
|
end;
|
|
FStartLinePos := Value.Y;
|
|
FStartBytePos := Value.X;
|
|
FEndLinePos := Value.Y;
|
|
FEndBytePos := Value.X;
|
|
if SelChanged then
|
|
fOnChangeList.CallNotifyEvents(self);
|
|
end;
|
|
|
|
function TSynEditSelection.GetEndLineBytePos : TPoint;
|
|
begin
|
|
Result.y := FEndLinePos;
|
|
Result.x := FEndBytePos;
|
|
end;
|
|
|
|
procedure TSynEditSelection.SetEndLineBytePos(Value : TPoint);
|
|
var
|
|
nLine: integer;
|
|
{$IFDEF SYN_MBCSSUPPORT}
|
|
s: string;
|
|
{$ENDIF}
|
|
begin
|
|
if FEnabled then begin
|
|
Value.x := MinMax(Value.x, 1, fMaxLeftChar);
|
|
Value.y := MinMax(Value.y, 1, fLines.Count);
|
|
if (SelectionMode = smNormal) then
|
|
if (Value.y >= 1) and (Value.y <= fLines.Count) then
|
|
Value.x := AdjustBytePosToCharacterStart(Value.y,Value.x)
|
|
else
|
|
Value.x := 1;
|
|
if (Value.X <> FEndBytePos) or (Value.Y <> FEndLinePos) then begin
|
|
{$IFDEF SYN_MBCSSUPPORT}
|
|
if Value.Y <= fLines.Count then begin
|
|
s := fLines[Value.Y - 1];
|
|
if (Length(s) >= Value.X) and (mbTrailByte = ByteType(s, Value.X)) then
|
|
Dec(Value.X);
|
|
end;
|
|
{$ENDIF}
|
|
if (Value.X <> FEndBytePos) or (Value.Y <> FEndLinePos) then begin
|
|
if (SelectionMode = smColumn) and (Value.X <> FEndBytePos) then begin
|
|
FInvalidateLinesMethod(
|
|
Min(FStartLinePos, Min(FEndLinePos, Value.Y)),
|
|
Max(FStartLinePos, Max(FEndLinePos, Value.Y)));
|
|
FEndLinePos := Value.Y;
|
|
FEndBytePos := Value.X;
|
|
end else begin
|
|
nLine := FEndLinePos;
|
|
FEndLinePos := Value.Y;
|
|
FEndBytePos := Value.X;
|
|
if (SelectionMode <> smColumn) or (FStartBytePos <> FEndBytePos) then
|
|
FInvalidateLinesMethod(nLine, FEndLinePos);
|
|
end;
|
|
FOnChangeList.CallNotifyEvents(self);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditSelection.SetSelectionMode(const Value: TSynSelectionMode);
|
|
begin
|
|
if FSelectionMode <> Value then begin
|
|
FSelectionMode := Value;
|
|
if SelAvail then
|
|
FInvalidateLinesMethod(-1, -1);
|
|
FOnChangeList.CallNotifyEvents(self);
|
|
end;
|
|
end;
|
|
|
|
function TSynEditSelection.AdjustBytePosToCharacterStart(Line : integer; BytePos : integer) : integer;
|
|
var
|
|
s: string;
|
|
begin
|
|
Result := BytePos;
|
|
if Result < 1 then
|
|
Result := 1
|
|
else if (Line >= 1) and (Line <= FLines.Count) then begin
|
|
s := FLines[Line-1];
|
|
if (Result <= length(s)) and FLines.IsUtf8 then
|
|
Result:=UTF8FindNearestCharStart(PChar(Pointer(s)),length(s),Result);
|
|
end;
|
|
end;
|
|
|
|
function TSynEditSelection.GetFirstLineBytePos: TPoint;
|
|
begin
|
|
if IsBackwardSel then
|
|
Result := EndLineBytePos
|
|
else
|
|
Result := StartLineBytePos;
|
|
end;
|
|
|
|
function TSynEditSelection.GetLastLineBytePos: TPoint;
|
|
begin
|
|
if IsBackwardSel then
|
|
Result := StartLineBytePos
|
|
else
|
|
Result := EndLineBytePos;
|
|
end;
|
|
|
|
function TSynEditSelection.SelAvail : Boolean;
|
|
begin
|
|
Result := (FStartBytePos <> FEndBytePos) or
|
|
((FStartLinePos <> FEndLinePos) and (FSelectionMode <> smColumn));
|
|
end;
|
|
|
|
function TSynEditSelection.IsBackwardSel: Boolean;
|
|
begin
|
|
Result := (FStartLinePos > FEndLinePos)
|
|
or ((FStartLinePos = FEndLinePos) and (FStartBytePos > FEndBytePos));
|
|
end;
|
|
|
|
end.
|
|
|