Recent

Author Topic: TSynEdit XML tag completion?  (Read 784 times)

dsiders

  • Hero Member
  • *****
  • Posts: 1393
TSynEdit XML tag completion?
« on: March 26, 2025, 08:36:24 pm »
I have been using TSynEdit along TSynXMLSyn to display XML-based content, and it works pretty well. I was hoping to find a highlighter/completion component to assist with editing. Basically, it needs to be smart enough to complete tag names when '</' is entered into the edit control. For example:

Code: Text  [Select][+][-]
  1. <document>
  2.   <para>Lorem ipsum...</
  3. </document>
  4.  

Entering '</' would cause the current tag/element to be inserted, like: '</para>'.

I looked through the wiki and this forum for ideas or suggestion - but did not find anything that I recognized as being relevant.  I am assuming that I need to create a TSynXMLTagCompletion highlighter, but have no idea about how it should interact with the edit control.

Suggestions are greatly appreciated.
Preview the next Lazarus documentation release at: https://dsiders.gitlab.io/lazdocsnext

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11047
  • Debugger - SynEdit - and more
    • wiki
Re: TSynEdit XML tag completion?
« Reply #1 on: March 26, 2025, 09:09:44 pm »
There isn't an existing one... Not that I know of.
You would need to fill the list yourself.

You might be able to retrieve the info from the HL. (I haven't checked the XML HL in a while, but it should follow the same implementation as the PasHL).

Maybe read the tutorial on the wiki, for writing a HL / not sure if needed or helpful.

Xml tags should be stored as folds (at least if folding is enabled).
Then there is a fold level.
You can get the foldlevel at the pos of the caret (more later). then search the lines for where that fold level opened (Line[x].MinFoldLevel).

You can get info for any pos in the line via
Code: Pascal  [Select][+][-]
  1.    NodeList := fHighlighter.FoldNodeInfo[lLineIdx];
  2.   NodeList.ClearFilter; // only needed once, in case the line was already used
  3.   NodeList.AddReference;
  4.   NodeList.ActionFilter := [sfaFoldFold]; // not sure maybe just sfaFold or maybe sfaOpen or combintation
  5.  

The incomplete node want be in there, but the one before should...

I haven't looked at this in a while.

Above is from syneditmarkupfoldcoloring.pas / But there are other bits of code working with it.
Maybe the syneditmarkupwordgroup.pp (doing word pairs based on their fold level) has a better example.
Its still a bit more than you need, because it can deal with tripple pairs...



Sorry this is all very vague.

I need to find a bit more time to go through the existing code, and remind myself.


Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11047
  • Debugger - SynEdit - and more
    • wiki
Re: TSynEdit XML tag completion?
« Reply #2 on: March 26, 2025, 09:20:10 pm »
So the MinLevel is described here https://wiki.freepascal.org/SynEdit_Highlighter#Folding

If you use the FoldNodeInfo to get the level of the current text
Quote
Code: Text  [Select][+][-]
  1. <document>
  2.   <para>Lorem ipsum...</
  3. </document>

document goes from level 0 to 1
para goes to level 2
you "</" is at level 2, you need to find the fold that opened at level 1 (MinLevel = 1)

In your case that is on the same line, but if it was an earlier line then use MinLevel to search that line.

Then for the found line, get the FoldNodeInfo, and iterate it for the correct node. It has the LogX, so you can get the text.



As for the FoldNodeInfo => as I said, I need to check myself

dsiders

  • Hero Member
  • *****
  • Posts: 1393
Re: TSynEdit XML tag completion?
« Reply #3 on: March 26, 2025, 11:57:47 pm »
There isn't an existing one... Not that I know of.
You would need to fill the list yourself.

You might be able to retrieve the info from the HL. (I haven't checked the XML HL in a while, but it should follow the same implementation as the PasHL).

Maybe read the tutorial on the wiki, for writing a HL / not sure if needed or helpful.

Xml tags should be stored as folds (at least if folding is enabled).
Then there is a fold level.
You can get the foldlevel at the pos of the caret (more later). then search the lines for where that fold level opened (Line
  • .MinFoldLevel).


You can get info for any pos in the line via
Code: Pascal  [Select][+][-]
  1.    NodeList := fHighlighter.FoldNodeInfo[lLineIdx];
  2.   NodeList.ClearFilter; // only needed once, in case the line was already used
  3.   NodeList.AddReference;
  4.   NodeList.ActionFilter := [sfaFoldFold]; // not sure maybe just sfaFold or maybe sfaOpen or combintation
  5.  

The incomplete node want be in there, but the one before should...

I haven't looked at this in a while.

Above is from syneditmarkupfoldcoloring.pas / But there are other bits of code working with it.
Maybe the syneditmarkupwordgroup.pp (doing word pairs based on their fold level) has a better example.
Its still a bit more than you need, because it can deal with tripple pairs...



Sorry this is all very vague.

I need to find a bit more time to go through the existing code, and remind myself.

Thanks for the feedback Martin. I'll try to digest this, and see what I can come up with.
Preview the next Lazarus documentation release at: https://dsiders.gitlab.io/lazdocsnext

Edson

  • Hero Member
  • *****
  • Posts: 1324
Re: TSynEdit XML tag completion?
« Reply #4 on: April 01, 2025, 05:09:18 pm »
Hi.

Maybe you can use https://github.com/t-edson/SynFacilCompletion. It's a highlighter with code completion, where the syntax and the completion behavior is defined in a syntax file.

Sadly, it is not designed for reading XML syntax text, but maybe you can adapt it or reuse some parts of the code.
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

dsiders

  • Hero Member
  • *****
  • Posts: 1393
Re: TSynEdit XML tag completion?
« Reply #5 on: April 01, 2025, 08:10:14 pm »
Hi.

Maybe you can use https://github.com/t-edson/SynFacilCompletion. It's a highlighter with code completion, where the syntax and the completion behavior is defined in a syntax file.

Sadly, it is not designed for reading XML syntax text, but maybe you can adapt it or reuse some parts of the code.

Thanks.

I have seen it before. I looked at it briefly... but as you mention there is no specific XML support, so I moved on. I'm trying to bend TSynXMLSyn to my will (as time permits).
Preview the next Lazarus documentation release at: https://dsiders.gitlab.io/lazdocsnext

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11047
  • Debugger - SynEdit - and more
    • wiki
Re: TSynEdit XML tag completion?
« Reply #6 on: April 07, 2025, 10:14:58 pm »
something like this may help

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   i, y, x, lvl: Integer;
  4.   list: TLazSynEditNestedFoldsList;
  5.   node: TSynFoldNodeInfo;
  6.   linetxt: String;
  7. begin
  8.   y := SynEdit1.CaretY - 1;
  9.   x := SynEdit1.CaretX;
  10.   if y = 0 then lvl := 0
  11.            else lvl := SynXMLSyn1.FoldBlockEndLevel(y-1);
  12.   list := TLazSynEditNestedFoldsList.Create(SynEdit1.TextViewsManager.SynTextView[0], SynXMLSyn1);
  13.   list.FoldFlags := [sfbIncludeDisabled];
  14.   list.IncludeOpeningOnLine := True;
  15.   list.Line := y;
  16.   i := list.Count;
  17.   while i > 0 do begin
  18.     dec(i);
  19.     node := list.HLNode[i];
  20.     if (node.LineIndex = y) and (node.LogXEnd > x) then
  21.       continue;
  22.     if (node.FoldLvlStart > lvl) then
  23.       continue;
  24.     linetxt := SynEdit1.Lines[node.LineIndex];
  25.     writeln(copy(linetxt, node.LogXStart, node.LogXEnd - node.LogXStart + 1));
  26.     exit;
  27.   end;
  28. end;
  29.  

SynEdit1.TextViewsManager.SynTextView[0] is a bit of a hack, to get an internal object...

TLazSynEditNestedFoldsList is sort of an internal helper.




Otherwise the way to do it is

TLazSynFoldNodeInfoList

Code: Pascal  [Select][+][-]
  1.   NodeList := SynXMLSyn1.FoldNodeInfo[y];
  2.   NodeList.AddReference;
  3.   NodeList.ActionFilter := [sfaOpen, sfaClose];
  4. ...
  5.  

and then go through the nodes.

If you need previous lines you need to scan them too. and check HL.FoldBlockMinLevel to see if a line has a low enough level.



You can use TLazSynFoldNodeInfoList for the current line, to get a better "lvl" than the example on top. Find the closest node before the x pos, and take the level.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11047
  • Debugger - SynEdit - and more
    • wiki
Re: TSynEdit XML tag completion?
« Reply #7 on: April 07, 2025, 10:38:53 pm »
Ok, updated a bit. this should work. you just need to add the text to the synedit.


Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   i, y, x, lvl: Integer;
  4.   list: TLazSynEditNestedFoldsList;
  5.   node: TSynFoldNodeInfo;
  6.   linetxt: String;
  7.   list2: TLazSynFoldNodeInfoList;
  8. begin
  9.   y := SynEdit1.CaretY - 1;
  10.   x := SynEdit1.CaretX;
  11.   if y = 0 then lvl := 0
  12.            else lvl := SynXMLSyn1.FoldBlockEndLevel(y-1);
  13.  
  14.   list2 := SynXMLSyn1.FoldNodeInfo[y];
  15.   list2.AddReference;
  16.   i := list2.Count;
  17.   while i > 0 do begin
  18.     dec(i);
  19.     node := list2.Item[i];
  20.     if node.LogXEnd < x then begin
  21.       if sfaOpen in node.FoldAction then begin
  22.         linetxt := SynEdit1.Lines[node.LineIndex];
  23.         writeln(copy(linetxt, node.LogXStart, node.LogXEnd - node.LogXStart + 1));
  24.         exit;
  25.       // got the opening node already
  26.         //lvl := node.NestLvlStart  // maybe FoldLvlStart ?
  27.       end
  28.       else
  29.         lvl := node.NestLvlEnd;  // maybe FoldLvlStart ?
  30.  
  31.       break;
  32.     end;
  33.   end;
  34.   list2.ReleaseReference;
  35.  
  36.   list := TLazSynEditNestedFoldsList.Create(SynEdit1.TextViewsManager.SynTextView[0], SynXMLSyn1);
  37.   list.IncludeOpeningOnLine := True;
  38.   list.FoldFlags := [sfbIncludeDisabled];
  39.   list.Line := y;
  40.   i := list.Count;
  41.   while i > 0 do begin
  42.     dec(i);
  43.     node := list.HLNode[i];
  44.     if (node.LineIndex = y) and (node.LogXEnd > x) then
  45.       continue;
  46.     if (node.NestLvlStart >= lvl) then
  47.       continue;
  48.     linetxt := SynEdit1.Lines[node.LineIndex];
  49.     writeln(copy(linetxt, node.LogXStart, node.LogXEnd - node.LogXStart + 1));
  50.     exit;
  51.   end;
  52. end;
  53.  

 

TinyPortal © 2005-2018