Skip to content

Commit 2ee0e5a

Browse files
committed
Add new command to select matching tags
The 'Select matching tags' command creates multiple selections on both tag names for synchronized editing, similar to 'editor.linkedEditing' in VS Code. Self-closing tags are handled by simply selecting the tag name Cf. https://code.visualstudio.com/Docs/languages/html#_auto-update-tags Closes ticket 41117e59c3 [^1] Completes a feature request [^2] -- [^1] https://fossil.2of4.net/npp_htmltag/tktview/41117e59c3 [^2] https://community.notepad-plus-plus.org/post/88110
1 parent 461a843 commit 2ee0e5a

2 files changed

Lines changed: 70 additions & 14 deletions

File tree

src/U_HTMLTagFinder.pas

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ interface
1313
uses
1414
NppSimpleObjects;
1515

16-
procedure FindMatchingTag(ASelect: boolean = False; AContentsOnly: Boolean = False);
16+
type TSelectionOptions = set of (soNone, soTags, soContents);
17+
procedure FindMatchingTag(SelectionOptions: TSelectionOptions = [soNone]);
1718

1819
////////////////////////////////////////////////////////////////////////////////////////////////////
1920
implementation
@@ -141,7 +142,7 @@ function ExtractTagName(AView: TActiveDocument;
141142
end {ExtractTagName};
142143

143144
{ ------------------------------------------------------------------------------------------------ }
144-
procedure FindMatchingTag(ASelect: boolean = False; AContentsOnly: Boolean = False);
145+
procedure FindMatchingTag(SelectionOptions: TSelectionOptions);
145146
var
146147
npp: TApplication;
147148
doc: TActiveDocument;
@@ -150,9 +151,9 @@ procedure FindMatchingTag(ASelect: boolean = False; AContentsOnly: Boolean = Fal
150151
Tag, NextTag, MatchingTag, Target: TTextRange;
151152
TagName: string;
152153
TagOpens, TagCloses: boolean;
153-
154+
InitPos: Sci_Position;
154155
Direction: TDirectionEnum;
155-
IsXML: boolean;
156+
IsXML, ASelect, AContentsOnly, TagsOnly: boolean;
156157
DisposeOfTag: boolean;
157158
i: integer;
158159
Found: TTextRange;
@@ -184,14 +185,41 @@ procedure FindMatchingTag(ASelect: boolean = False; AContentsOnly: Boolean = Fal
184185
end;
185186
end;
186187
// ---------------------------------------------------------------------------------------------
187-
var
188-
InitPos: Sci_Position;
188+
procedure SelectTags(Tag, MatchingTag: TTextRange);
189+
var
190+
Doc: TActiveDocument;
191+
TagAttrPos: Integer;
192+
begin
193+
Doc := Tag.Document;
194+
// Trim attributes from tag selection
195+
TagAttrPos := Pos(' ', Tag.Text);
196+
if TagAttrPos > Pos('<', Tag.Text) then
197+
Tag.EndPos := Tag.StartPos + TagAttrPos;
198+
// Trim '<' or '</' and '>' from selection
199+
if not Assigned(MatchingTag) then begin
200+
// Narrow selection for a self-closing tag
201+
Tag.StartPos := Tag.StartPos + Pos('<', Tag.Text);
202+
if Pos('/>', Tag.Text) > 0 then
203+
Tag.EndPos := Tag.EndPos - 1;
204+
end else
205+
Tag.StartPos := Tag.StartPos + (Pos('/', Tag.Text) shr 1) + 1;
206+
Doc.SendMessage(SCI_SETSELECTION, Tag.StartPos, Tag.EndPos - 1);
207+
if Assigned(MatchingTag) then begin
208+
TagAttrPos := Pos(' ', MatchingTag.Text);
209+
if TagAttrPos > Pos('<', MatchingTag.Text) then
210+
MatchingTag.EndPos := MatchingTag.StartPos + TagAttrPos;
211+
Doc.SendMessage(SCI_ADDSELECTION, MatchingTag.StartPos + (Pos('/', MatchingTag.Text) shr 1) + 1, MatchingTag.EndPos - 1);
212+
end;
213+
end;
214+
// ---------------------------------------------------------------------------------------------
189215
begin
190216
npp := GetApplication();
191217
doc := npp.ActiveDocument;
192218

193219
IsXML := (doc.Language = L_XML);
194-
220+
ASelect := not (soNone in SelectionOptions);
221+
AContentsOnly := ASelect and ([soContents] = SelectionOptions);
222+
TagsOnly := ASelect and (not (soContents in SelectionOptions));
195223
Tags := TStringList.Create;
196224
MatchingTag := nil;
197225
NextTag := nil;
@@ -298,8 +326,10 @@ procedure FindMatchingTag(ASelect: boolean = False; AContentsOnly: Boolean = Fal
298326
Tags.LineBreak := #9;
299327
if Assigned(MatchingTag) then begin
300328
if Tags.Count = 2 then begin
329+
// Matching tag may be hidden by a fold
330+
doc.SendMessage(SCI_FOLDLINE, doc.SendMessage(SCI_LINEFROMPOSITION, MatchingTag.StartPos), SC_FOLDACTION_EXPAND);
301331
Tag := TTextRange(Tags.Objects[0]);
302-
if ASelect then begin
332+
if ASelect and not TagsOnly then begin
303333
if Tag.StartPos < MatchingTag.StartPos then begin
304334
if AContentsOnly then begin
305335
Target := doc.GetRange(Tag.EndPos, MatchingTag.StartPos);
@@ -336,12 +366,14 @@ procedure FindMatchingTag(ASelect: boolean = False; AContentsOnly: Boolean = Fal
336366
finally
337367
Target.Free;
338368
end;
369+
end else if ASelect then begin
370+
SelectTags(Tag, MatchingTag);
339371
end else begin
340372
MatchingTag.Select;
341373
end;
342-
end else begin
343-
if ASelect then begin
344-
MatchingTag.Select;
374+
end else begin // Self-closing tag
375+
if TagsOnly then begin
376+
SelectTags(MatchingTag, nil);
345377
end else begin
346378
MatchingTag.Select;
347379
end;

src/U_Npp_HTMLTag.pas

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ TNppPluginHTMLTag = class(TNppPlugin)
4444
constructor Create;
4545
destructor Destroy; override;
4646
procedure commandFindMatchingTag;
47+
procedure commandSelectMatchingTags;
4748
procedure commandSelectTagContents;
4849
procedure commandSelectTagContentsOnly;
4950
procedure commandEncodeEntities(const InclLineBreaks: Boolean = False);
@@ -68,6 +69,7 @@ TNppPluginHTMLTag = class(TNppPlugin)
6869
end;
6970

7071
procedure _commandFindMatchingTag(); cdecl;
72+
procedure _commandSelectMatchingTags(); cdecl;
7173
procedure _commandSelectTagContents(); cdecl;
7274
procedure _commandSelectTagContentsOnly(); cdecl;
7375
procedure _commandEncodeEntities(); cdecl;
@@ -98,6 +100,11 @@ procedure _commandFindMatchingTag(); cdecl;
98100
npp.commandFindMatchingTag;
99101
end;
100102
{ ------------------------------------------------------------------------------------------------ }
103+
procedure _commandSelectMatchingTags(); cdecl;
104+
begin
105+
npp.commandSelectMatchingTags;
106+
end;
107+
{ ------------------------------------------------------------------------------------------------ }
101108
procedure _commandSelectTagContents(); cdecl;
102109
begin
103110
npp.commandSelectTagContents;
@@ -172,6 +179,9 @@ constructor TNppPluginHTMLTag.Create;
172179
sk := self.MakeShortcutKey(False, True, False, 'T'); // Alt-T
173180
self.AddFuncItem('&Find matching tag', _commandFindMatchingTag, sk);
174181

182+
sk := self.MakeShortcutKey(False, True, False, #113); // Alt-F2
183+
self.AddFuncItem('Select &matching tags', _commandSelectMatchingTags, sk);
184+
175185
sk := self.MakeShortcutKey(False, True, True, 'T'); // Alt-Shift-T
176186
self.AddFuncItem('&Select tag and contents', _commandSelectTagContents, sk);
177187

@@ -309,12 +319,26 @@ procedure TNppPluginHTMLTag.commandFindMatchingTag;
309319
Exit;
310320
{$ENDIF}
311321
try
312-
U_HTMLTagFinder.FindMatchingTag(False, False);
322+
U_HTMLTagFinder.FindMatchingTag;
313323
except
314324
HandleException(ExceptObject, ExceptAddr);
315325
end;
316326
end {TNppPluginHTMLTag.commandFindMatchingTag};
317327

328+
{ ------------------------------------------------------------------------------------------------ }
329+
procedure TNppPluginHTMLTag.commandSelectMatchingTags;
330+
begin
331+
{$IFDEF CPUX64}
332+
if not SupportsBigFiles then
333+
Exit;
334+
{$ENDIF}
335+
try
336+
U_HTMLTagFinder.FindMatchingTag([soTags]);
337+
except
338+
HandleException(ExceptObject, ExceptAddr);
339+
end;
340+
end {TNppPluginHTMLTag.commandSelectMatchingTags};
341+
318342
{ ------------------------------------------------------------------------------------------------ }
319343
procedure TNppPluginHTMLTag.commandSelectTagContents;
320344
begin
@@ -323,7 +347,7 @@ procedure TNppPluginHTMLTag.commandSelectTagContents;
323347
Exit;
324348
{$ENDIF}
325349
try
326-
U_HTMLTagFinder.FindMatchingTag(True, False);
350+
U_HTMLTagFinder.FindMatchingTag([soContents, soTags]);
327351
except
328352
HandleException(ExceptObject, ExceptAddr);
329353
end;
@@ -337,7 +361,7 @@ procedure TNppPluginHTMLTag.commandSelectTagContentsOnly;
337361
Exit;
338362
{$ENDIF}
339363
try
340-
U_HTMLTagFinder.FindMatchingTag(True, True);
364+
U_HTMLTagFinder.FindMatchingTag([soContents]);
341365
except
342366
HandleException(ExceptObject, ExceptAddr);
343367
end;

0 commit comments

Comments
 (0)