Skip to content

Commit d59daf6

Browse files
committed
Improve replacement of multiple stream selections
Borrows and internalizes the clipboard hack from bfcf374 Note that replacement is always global, whether or not the selections have gaps (via `Skip Current & Go to Next Multi-select` or, since v8.6.1, `CTRL`+`click` on the unwanted selection). This was simpler to implement and more responsive to SCI_UNDO when mistakes are made *Not implemented:* * encoding emoji in multiple stream selection mode
1 parent a2d8d08 commit d59daf6

4 files changed

Lines changed: 160 additions & 106 deletions

File tree

src/LibNppPlugin/NppSimpleObjects.pas

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ interface
1515

1616
{$I '..\Include\SciApi.inc'}
1717

18+
const
19+
MULTISELECTION_MASK: 0..4 = $4;
20+
1821
type
1922
//////////////////////////////////////////////////////////////////////////////////////////////////
2023
TActiveDocument = class;
@@ -84,7 +87,7 @@ TSelection = class(TTextRange)
8487
property StartPos: Sci_Position read GetStart write SetStart;
8588
property EndPos: Sci_Position read GetEnd write SetEnd;
8689
property Length: Sci_Position read GetLength write SetLength;
87-
property Text: WideString read GetText write SetText;
90+
property Text: WideString read GetText;
8891
end;
8992
{ -------------------------------------------------------------------------------------------- }
9093
TTextRangeMark = class
@@ -116,9 +119,9 @@ TWindowedObject = class
116119
end;
117120

118121
{ -------------------------------------------------------------------------------------------- }
122+
TSelectionMode = (smStreamSingle = SC_SEL_STREAM, smColumn, smLines, smThin, smStreamMulti);
119123
TActiveDocument = class(TWindowedObject)
120124
private
121-
122125
FSelection: TSelection;
123126

124127
function GetEditor(): TActiveDocument;
@@ -133,26 +136,22 @@ TActiveDocument = class(TWindowedObject)
133136
function GetNextLineStart(): Sci_Position;
134137
function GetLangType(): LangType;
135138
procedure SetLangType(const AValue: LangType);
136-
137139
function GetCurrentPos(): Sci_Position;
138140
procedure SetCurrentPos(const AValue: Sci_Position);
139141
function GetSelection: TSelection;
140142
function GetFirstVisibleLine: Sci_Position;
141143
function GetLinesOnScreen: Sci_Position;
142144
public
143145
destructor Destroy(); override;
144-
145146
function Activate(): TActiveDocument;
146-
147147
procedure Insert(const Text: WideString; const Position: Sci_Position = Sci_Position(High(Cardinal)));
148-
149148
function GetRange(const StartPosition: Sci_Position = 0; const LastPosition: Sci_Position = Sci_Position(High(Cardinal))): TTextRange;
150149
function GetLines(const FirstLine: Sci_Position; const Count: Sci_Position = 1): TTextRange;
151-
150+
function CharWidth: Byte;
151+
function SelectionMode: TSelectionMode;
152152
procedure Select(const Start: Sci_Position = 0; const Length: Sci_Position = Sci_Position(High(Cardinal)));
153-
procedure SelectLines(const FirstLine: Sci_Position; const LineCount: Sci_Position = 1);
154-
procedure SelectColumns(const FirstPosition, LastPosition: Sci_Position);
155-
153+
procedure SelectMultiple(const Start: Sci_Position; const ALength: Sci_Position; MatchAll: Boolean = True);
154+
procedure ReplaceSelection(const AValue: WideString);
156155
procedure Find(const AText: WideString; var ATarget: TTextRange; const AOptions: integer = 0;
157156
const AStartPos: Sci_Position = -1; const AEndPos: Sci_Position = -1); overload;
158157
procedure Find(const AText: WideString; var ATarget: TTextRange; const AOptions: integer); overload;
@@ -164,7 +163,6 @@ TActiveDocument = class(TWindowedObject)
164163
property LineCount: Sci_Position read GetLineCount;
165164
property NextLineStartPosition: Sci_Position read GetNextLineStart;
166165
property Language: LangType read GetLangType write SetLangType;
167-
168166
property CurrentPosition:Sci_Position read GetCurrentPos write SetCurrentPos;
169167
property Selection: TSelection read GetSelection;
170168
property TopLine: Sci_Position read GetFirstVisibleLine;
@@ -690,6 +688,14 @@ procedure TActiveDocument.Find(const AText: WideString; var ATarget: TTextRange;
690688
end;
691689
end;
692690

691+
{ ------------------------------------------------------------------------------------------------ }
692+
function TActiveDocument.SelectionMode: TSelectionMode;
693+
begin
694+
Result := TSelectionMode(Self.SendMessage(SCI_GETSELECTIONMODE));
695+
if (Ord(Result) = SC_SEL_STREAM) and (Self.SendMessage(SCI_GETSELECTIONS) > 1) then
696+
Result := smStreamMulti;
697+
end;
698+
693699
{ ------------------------------------------------------------------------------------------------ }
694700

695701
function TActiveDocument.GetCurrentPos: Sci_Position;
@@ -719,6 +725,15 @@ function TActiveDocument.GetLinesOnScreen: Sci_Position;
719725

720726
{ ------------------------------------------------------------------------------------------------ }
721727

728+
function TActiveDocument.CharWidth: Byte;
729+
begin
730+
Result := SizeOf(AnsiChar);
731+
if (SendMessage(SCI_GETCODEPAGE) = SC_CP_UTF8) then
732+
Result := Sizeof(Widechar);
733+
end;
734+
735+
{ ------------------------------------------------------------------------------------------------ }
736+
722737
function TActiveDocument.GetLangType: LangType;
723738
var
724739
LT: integer;
@@ -743,42 +758,52 @@ function TActiveDocument.GetSelection: TSelection;
743758
{ ------------------------------------------------------------------------------------------------ }
744759
procedure TActiveDocument.Select(const Start, Length: Sci_Position);
745760
var
746-
SelMode: cardinal; // TODO: implement this as a property of the editor (or the selection object?)
761+
SelMode: TSelectionMode;
747762
begin
748-
SelMode := SendMessage(SCI_GETSELECTIONMODE);
749-
if SelMode <> SC_SEL_STREAM then
763+
SelMode := TSelectionMode(Ord(SelectionMode) and (not MULTISELECTION_MASK));
764+
if SelMode <> smStreamSingle then
750765
SendMessage(SCI_SETSELECTIONMODE, SC_SEL_STREAM);
751766
SendMessage(SCI_SETSEL, Start, Start + Length);
752-
if SelMode <> SC_SEL_STREAM then
753-
SendMessage(SCI_SETSELECTIONMODE, SelMode);
767+
if SelMode <> smStreamSingle then
768+
SendMessage(SCI_SETSELECTIONMODE, Ord(SelMode))
754769
end;
755770

756771
{ ------------------------------------------------------------------------------------------------ }
757772

758-
procedure TActiveDocument.SelectColumns(const FirstPosition, LastPosition: Sci_Position);
773+
procedure TActiveDocument.SelectMultiple(const Start: Sci_Position; const ALength: Sci_Position; MatchAll: Boolean);
759774
var
760-
SelMode: cardinal; // TODO: implement this as a property of the editor (or the selection object?)
775+
SciMsg : cardinal;
761776
begin
762-
SelMode := SendMessage(SCI_GETSELECTIONMODE);
763-
if SelMode <> SC_SEL_RECTANGLE then
764-
SendMessage(SCI_SETSELECTIONMODE, SC_SEL_RECTANGLE);
765-
SendMessage(SCI_SETSEL, FirstPosition, LastPosition);
766-
if SelMode <> SC_SEL_RECTANGLE then
767-
SendMessage(SCI_SETSELECTIONMODE, SelMode);
777+
SendMessage(SCI_CANCEL);
778+
FSelection.StartPos := SendMessage(SCI_POSITIONAFTER, (Start - (CharWidth shr 1)));
779+
FSelection.EndPos := FSelection.StartPos + ALength;
780+
SciMsg := SCI_MULTIPLESELECTADDEACH;
781+
if not MatchAll then
782+
SciMsg := SCI_MULTIPLESELECTADDNEXT;
783+
SendMessage(SciMsg);
768784
end;
769785

770786
{ ------------------------------------------------------------------------------------------------ }
771787

772-
procedure TActiveDocument.SelectLines(const FirstLine, LineCount: Sci_Position);
788+
procedure TActiveDocument.ReplaceSelection(const AValue: WideString);
773789
var
774-
SelMode: cardinal; // TODO: implement this as a property of the editor (or the selection object?)
775-
begin
776-
SelMode := SendMessage(SCI_GETSELECTIONMODE);
777-
if SelMode <> SC_SEL_LINES then
778-
SendMessage(SCI_SETSELECTIONMODE, SC_SEL_LINES);
779-
SendMessage(SCI_SETSEL, SendMessage(SCI_POSITIONFROMLINE, FirstLine), SendMessage(SCI_GETLINEENDPOSITION, FirstLine + LineCount));
780-
if SelMode <> SC_SEL_LINES then
781-
SendMessage(SCI_SETSELECTIONMODE, SelMode);
790+
Chars: AnsiString;
791+
MultiPasteMode: Cardinal;
792+
begin
793+
case Self.SendMessage(SCI_GETCODEPAGE) of
794+
SC_CP_UTF8:
795+
Chars := UTF8Encode(AValue)
796+
else
797+
Chars := RawByteString(AValue);
798+
end;
799+
if (SelectionMode = smStreamMulti) then begin
800+
MultiPasteMode := SendMessage(SCI_GETMULTIPASTE);
801+
SendMessage(SCI_SETMULTIPASTE, SC_MULTIPASTE_EACH);
802+
SendMessage(SCI_COPYTEXT, System.Length(Chars), PAnsiChar(Chars));
803+
SendMessage(SCI_PASTE);
804+
SendMessage(SCI_SETMULTIPASTE, MultiPasteMode);
805+
end else
806+
Selection.SetText(AValue);
782807
end;
783808

784809
{ ================================================================================================ }

src/U_Entities.pas

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ procedure EncodeEntities(Scope: TEntityReplacementScope; const Options: TEntityR
140140
doc := npp.App.ActiveDocument;
141141
Text := doc.Selection.Text;
142142
if DoEncodeEntities(Text, FetchEntities, Options) > 0 then begin
143-
doc.Selection.Text := Text;
143+
doc.ReplaceSelection(Text);
144144
doc.Selection.ClearSelection;
145145
end;
146146
end;
@@ -150,8 +150,9 @@ procedure EncodeEntities(Scope: TEntityReplacementScope; const Options: TEntityR
150150
{ ------------------------------------------------------------------------------------------------ }
151151
function DoEncodeEntities(var Text: WideString; const Entities: THashedStringList; const Options: TEntityReplacementOptions): Integer;
152152
var
153+
Doc: TActiveDocument;
153154
CharIndex, EntityIndex: integer;
154-
ReplaceEntity: boolean;
155+
ReplaceEntity, MultiSel: boolean;
155156
EncodedEntity: WideString;
156157
EntitiesReplaced: integer;
157158
begin
@@ -162,6 +163,8 @@ function DoEncodeEntities(var Text: WideString; const Entities: THashedStringLis
162163
end;
163164

164165
EncodedEntity := '';
166+
Doc := Npp.App.ActiveDocument;
167+
MultiSel := (doc.SelectionMode = smStreamMulti);
165168
for CharIndex := Length(Text) downto 1 do begin
166169
EntityIndex := Entities.IndexOfName(IntToStr(integer(Ord(Text[CharIndex]))));
167170
if EntityIndex > -1 then begin
@@ -177,10 +180,16 @@ function DoEncodeEntities(var Text: WideString; const Entities: THashedStringLis
177180
ReplaceEntity := False;
178181
end;
179182
if ReplaceEntity then begin
180-
Text := Copy(Text, 1, CharIndex - 1)
181-
+ '&' + EncodedEntity + ';'
182-
+ Copy(Text, CharIndex + 1);
183+
if MultiSel then begin
184+
doc.SelectMultiple(doc.Selection.StartPos + Pos(Text[CharIndex], Text) - 1, doc.CharWidth);
185+
Text := '&' + EncodedEntity + ';';
186+
end else begin
187+
Text := Copy(Text, 1, CharIndex - 1)
188+
+ '&' + EncodedEntity + ';'
189+
+ Copy(Text, CharIndex + 1);
190+
end;
183191
Inc(EntitiesReplaced);
192+
if MultiSel then Break;
184193
end;
185194
end;
186195
Result := EntitiesReplaced;
@@ -199,7 +208,7 @@ function DecodeEntities(Scope: TEntityReplacementScope = ersSelection): Integer;
199208
CharIndex, EntityIndex: integer;
200209
EntitiesReplaced: integer;
201210
FirstPos, LastPos, NextIndex, i: Integer;
202-
IsNumeric, IsHex, IsValid: boolean;
211+
IsNumeric, IsHex, IsValid, MultiSel: boolean;
203212
AllowedChars: WideString;
204213
Entity: string;
205214
CodePoint: integer;
@@ -224,6 +233,7 @@ function DecodeEntities(Scope: TEntityReplacementScope = ersSelection): Integer;
224233
if not (Pos(';', Text) > CharIndex) then
225234
Exit;
226235

236+
MultiSel := (doc.SelectionMode = smStreamMulti);
227237
while CharIndex > 0 do begin
228238
FirstPos := CharIndex;
229239
LastPos := FirstPos;
@@ -297,11 +307,18 @@ function DecodeEntities(Scope: TEntityReplacementScope = ersSelection): Integer;
297307
end;
298308

299309
if IsValid then begin
300-
Text := Copy(Text, 1, FirstPos - 1)
301-
+ WideChar(CodePoint)
302-
+ Copy(Text, NextIndex);
303-
Dec(NextIndex, (LastPos - FirstPos + 1));
310+
if MultiSel then begin
311+
if IsNumeric then Inc(LastPos);
312+
doc.SelectMultiple(doc.Selection.StartPos + FirstPos - 1, (LastPos - FirstPos) + 1);
313+
Text := WideChar(CodePoint);
314+
end else begin
315+
Text := Copy(Text, 1, FirstPos - 1)
316+
+ WideChar(CodePoint)
317+
+ Copy(Text, NextIndex);
318+
Dec(NextIndex, (LastPos - FirstPos + 1));
319+
end;
304320
Inc(EntitiesReplaced);
321+
if MultiSel then Break;
305322
end;
306323

307324
CharIndex := PosEx('&', Text, NextIndex);
@@ -312,7 +329,7 @@ function DecodeEntities(Scope: TEntityReplacementScope = ersSelection): Integer;
312329
end;
313330

314331
if EntitiesReplaced > 0 then begin
315-
doc.Selection.Text := Text;
332+
doc.ReplaceSelection(Text);
316333
doc.Selection.ClearSelection;
317334
end;
318335

0 commit comments

Comments
 (0)