I am writing a plugin that marks specific lines, and will be trying to paint a highlight marker for specific lines over the code editor. To do this, I need to calculate the position onscreen of specific lines of code, ie rows in the buffer.
The Delphi code editor has some access to which lines are visible onscreen via IOTAEditView
's BottomRow
and TopRow
properties. However, in newer IDE versions code regions and methods can be folded - that is, several lines are collapsed into one. The first step to line highlight painting is to know which lines are visible and where they are located, and to do this I may need to keep track of which parts of the editor are folded and which are not. There seem to be OTAPI methods to invoke code folding (elision) but not to know when it occurs.
However, some plugins, such as Castalia, do manage this. How can it be done?
An IDE editor control has a method, IsLineElided
. Elision[*] is the IDE's internal term for a line being hidden when it is part of a collapsed region, method, or other structure. In the UI, this is called "folding", as in "code folding", but it's quite common for the internal term for something to be different to the UI term presented to the user.
This method is not publicly accessible; it's a method of the internal TEditControl class. To use it, you need to call an IDE method. Unlike a lot of IDE hacks you don't need to hook it, since you don't need to change its behaviour - just call it.
@Editorcontrol@TCustomEditControl@LineIsElided$qqri
TLineIsElidedProc = function(Self: TObject; LineNum: Integer): Boolean;
For example,
PFLineIsElided := GetProcAddress(CoreIDEHandle, StrIDELineIsElidedName);
You can get the core IDE BPL handle by reading loaded modules. The first parameter should be the editor window - not the ToolsAPI edit view, but the internal editor. This article shows the relationship between the editor control and IOTAEditView.
You can now ask if a line is elided (that is, is it hidden?) from your plugin like so:
if PFLineIsElided(FCodeEditor, 123) then ...
However, putting that together to see which areas are folded - or rather, since the top line of any folded region is still drawn, finding the line after which one or more lines are elided - require slightly more logic. The best way is to iterate through the lines onscreen in a view, IOTAEditView.TopRow
and BottomRow
. If the line after the one you're looking at is folded, but the one you're looking at isn't, then the one you're looking at is the representative line for the folded area (the line that has the +/- symbol in the gutter.)
Note that if you are painting on the code editor the difference between logical line numbers (line numbers as printed in the code gutter) and nominal line numbers (lines visible onscreen in the view) will be important for you, and code elision is what controls this. When code is folded, logical and nominal line numbers won't match: an edit view always draws nominal line numbers in order, but if there is a folded region in the middle, the logical line numbers will have gaps.
Further reading: A large article about integrating with the code editor, one section of which discusses code folding and handling line numbers. It's one of two on the topic of Delphi plugins / wizards integrating with the code editor on the Parnassus blog. Although it covers much more than folded code, if you're writing an IDE plugin that needs to handle this kind of stuff, there's a lot of useful material there. (Disclaimer: my blog.)
[*] As an aside, elision is an auto-antonym: a word that has two meanings that are opposites (the common example is 'cleave'.) One meaning of elision is omission or removal, and another meaning is joining or merging.