ToolsAPI Support for the Code Editor
Go Up to Extending the IDE Using the Tools API
Starting RAD Studio 11.3, users have a complete, comprehensive, and very detailed set of Tools API that can customize the Code Editor, such as painting, querying information, getting events for the Code Editor, and more.
Contents
What can you do
Some of the most exciting and useful plugins work within the editor. Therefore we aim to provide a thorough and valuable API that will spur innovation. Making it easy to write plugins that do things we haven’t even thought of.
These APIs allow users to intercept all stages of painting in the editor, either by editor line, editor gutter, or editor text, and with events called both before and after each stage, and either add to what the IDE does or fully replace it (preventing the IDE doing its normal painting for that stage.)
You can get a complete list of editors, edit views, and map between them. For an editor, users can query its state, with a multitude of information available, and query a more detailed state for each line. Paint events include full context. Use this to do any conceivable editor painting or get any information you need about editors and what’s present in them, even without painting.
Core concepts
The editor buffer (IOTAEditView.Buffer provides an IOTAEditBuffer) is where the text/code is stored. A view (IOTAEditView) is a view over this buffer and is used by an editor control (the VCL control that implements the editor.) There is one buffer for file contents but multiple views that allow users to edit that buffer, resulting in numerous editor windows of the same file. One editor control maps to / uses one view, and vice versa.
Editor line numbers vs. logical line numbers
Line state and some other areas in this API differentiate between the editor line number and logical line number. The difference is important when lines are elided. Elided
is the IDE’s term for folded code. An editor might have many visible lines on the screen, but at the fold point, the editor line vs. logical line numbers diverge, causing the fold point to be offscreen.
Logical lines are ‘real’ line numbers.
Editor lines are displayed in the editor or would be displayed if the editor was scrolled.
For example, in a file with ten lines (logical lines), with lines 4-7 folded/elided, the logical lines go up to ten, but the editor lines go up to seven (for elided sections, one line is always present.) Visible lines are the lines the editor has on screen, and because of folding, those can be a subset of the lines in the buffer. Thus, visible editor lines are always continuous, but logical line numbers may have gaps and jump at fold points.
You can convert between logical and editor line numbers in the line state for a line, which either kind of line number can query. That is, get the line state by logical line or editor line, and ask it what logical line or editor line it represents.
Code Editor Services
The starting point is the INTACodeEditorServices interface. This interface lets you:
- Register for editor events, including mouse, scrolling, resizing, and painting.
- Look up the view for an editor control and the editor control for a view. There is a single buffer, but multiple views of that buffer, and each editor control uses one view.
- List all editor controls and all edit views.
- Getting the top editor control (the most recently interacted with).
- Get the editor state for editor control. You can get the line state from the editor state for any line.
- Get some miscellaneous information about the editor.
- Reserve a portion of the editor gutter for your plugin’s use.
- Access the INTACodeEditorServices from the
BorlandIDEServices
as with other interfaces. - Invalidate, i.e., cause a repaint, for parts of an editor: by logical line, for a rectangle, or the whole editor.
Performant Code Editor Reading and Writing
Reading from the Editor Buffer
Use the IOTAEditReader
interface to read from the editor buffer. This API is very low-level and is highly performant. It allows you direct access to the editor’s content, and if you need to parse code, if you do so via pointer-based string parsing, you can do so entirely without string allocation and copying.
To get the contents of the editor buffer for a set of lines, call GetLineData
, passing the first line number, the last line number, the callback, and an arbitrary pointer which can represent any preferred data, which will be passed to the callback unchanged. This is used to point to an object instance to convert it to an object-oriented method call in the callback.
The callback gets the line number, the unchanged pointer passed in, and a pointer to line information. This line information structure contains the length of the line, the kind of line ending (an enum representing CRLF
, just CR
, just LF
, etc.), and a pointer to the line string.
The line string is in UTF8 format and is not null-terminated. It does not have the line ending bytes appended. This is a pointer to the line contents only, directly in the editor buffer’s memory.
The IOTAEditReader
interface lets you:
- Get the number of lines in the buffer via the
LinesInBuffer
property.
- Get the line data for a single line or a range of lines using the following procedure:
procedure GetLineData(AFirstLine: Integer; ARange; Integer;
ACallBack: TOTALineDataCallback; AUserData: Pointer);
- Getting the line data requires a callback as such:
TOTALineDataCallback = procedure(ALine: Longint; ALineInfo:
POTALineInfo; AUserData: Pointer)
You can also achieve things like a line count without the overhead of streaming the entire buffer and parsing (which the previous API required), as well as calculating the entire size of the buffer in bytes, again without requiring streaming.
Writing from the Editor Buffer
The following are different methods for writing to the editor buffer:
IOTAEditWriter
: an interface that provides a stream-like API with a position and then copy, insert, delete, etc. primitives. This method requires streamlining the editor text for operations to be implemented.Insert()
: Overloads taking a UTF8String. This method is more line-number-oriented.
More performant methods:
InsertBlankLine
: at a specific line number.InsertLine
: at a specific line number. This takes a pointer to UTF8 content or a UTF8String and inserts that content into the new line.DeleteLine
: for a specific line number.ReplaceLine
: for a specific line number with a pointer to UTF8 content or a UTF8String.
IOTAEditPosition
which provide primitives that are closer to how the editor is interacted with by a human; these are also reasonably high performance and are an alternative to the IOTAEditWriter
.Code Editor Events
Register an instance of the INTACodeEditorEvents notifier interface using AddEditorEventsNotifier.
This interface lets you be notified of different events; below are some examples.
Window Events
The following APIs allow you to check if something should be on the editor screen. You can query the new editor size by using the editor TWinControl Width and Height properties as you can any normal visual component.
- Resize the editor with EditorResized.
- Scroll the editor with EditorScrolled.
These events are only called when cevWindowEvents is returned in AllowedEvents.
Mouse Events
Mouse events allow users to implement interactable elements inside the code editor. Mouse events include:
These events are only called when cevMouseEvents is returned in AllowedEvents.
State changes Events
State changes events occur when lines are elided or un-elided and at which line it happens.
Optimization Events
The following are the optimization events users can implement in their code editor.
- AllowedEvents: returns what events the plugin is interested in.
- AllowedGutterStages and AllowedLineStages: similar masks for which gutter, line paint, and text paint stages you want your plugin to be called for when painting.
To avoid performance impact, use the result of these methods to ensure your plugin is called only for the events you need to be called for.
Paiting Events
Painting has several methods. There are methods for painting a line, text in the line, and the editor gutter. Painting is line-based, following the internal editor model where there is no model for painting something that is not associated with a line, though you can by painting the rectangle for it in each line.
For each of these three (gutter, line, text), the methods:
- Are called multiple times: once per painting stage and before and after that stage.
- The painting stage includes begin and end (i.e., before all painting and after all painting), the line background, general marks/markup, highlighted pairs, the right margin, and folded line indicators.
- Are passed context, which gives access to the painting rectangle, the canvas, line number, logical line number, filename, control, and edit view, and the ability to query the line state and editor state.
- Can prevent the IDE’s default handling of a particular stage from fully handling the painting of this stage. If there are multiple plugins painting, you cannot prevent other plugins from also painting.
Paint Methods
The INTACodeEditorEvents interface includes the following painting methods:
- BeginPaint and EndPaint: these methods are called before and after the entire editor painting. BeginPaint forces a repaint of the entire editor.
- These methods are only called when
cevBeginEndPaintEvents
is returned inAllowedEvents
.
- These methods are only called when
- PaintGutter: use this method to paint the editor gutter.
- This method is only called when
cevPaintGutterEvents
is returned inAllowedEvents
.
- This method is only called when
- PaintLine: this is the general method to paint a line. A common use of this method is to change the background painting or to add additional markup after the text has been painted.
- This method is only called when
cevPaintLineEvents
is returned inAllowedEvents
.
- This method is only called when
- PaintText: this method is called when painting code (i.e., text) contents in an editor line. Call this method for each logical text segment, such as each symbol or comment. The parameters also provide information about the column number at which it’s painted, the text itself, the syntax highlighting info, and if the text is selected. Because, like all methods, this can prevent the IDE’s painting, this can be used to modify the IDE’s code formatting.
- This method is only called when
cevPaintTextEvents
is returned inAllowedEvents
.
- This method is only called when
Registering Events and Paint Stages
Since these methods can be used to a granular extent (many painting stages, both before and after the IDE’s handling, for three different areas of painting), there is a potential performance hit when painting. This is addressed by subscribing to a granular subset of possible events, which corresponds to which methods will be called. Implement AllowedEvents to return which events will be called.
Then, for the events that are called, many have multiple stages in the painting process for which they can be called. Again, you should specify which stages you want to be called as an optimization. Implement AllowedGutterStages to return which paint stages you want to be notified of for the editor gutter painting and AllowedLineStages for which paint stages do you want to be notified of for painting a line. PaintText does not have multiple stages.
Editor State
The INTACodeEditorState interface lets users get information about what is displayed for a specific editor. This includes:
- The editor rectangle.
- The gutter size.
- The left edge for where the code is painted.
- The code editor component (as a TWinControl) and the edit view used.
- The top and bottom visible lines.
- The left and right visible columns (when scrolling horizontally).
- The width of the largest visible line in characters.
- Converting a pixel point to a character position (index and line).
- Converting a character position (index and line) to the character bounding rectangle.
- Querying if a specific line is visible.
- Querying information about elided lines.
- Getting the state for a specific line.
Get the editor state either by querying it from INTACodeEditorServices or through the Context parameter in the paint methods, which gives both editor and line state and other paint context. Editor state is created when it is queried.
Line State
The INTACodeEditorLineState interface lets users query information for a specific line. Get the line state through the INTACodeEditorState interface. For any line, you can query:
- The editor line number and logical line number.
- If it is an elided line: the visible line representing an elided section of lines and the start and end range of that elided section.
- Various rectangles: the whole line rectangle across the entire width of the editor, the gutter rect, gutter markup (e.g., where line numbers and folding +/- symbols are painted) rect, code area rect, a rectangle that bounds text.
- The text for a line and the subset of text that is visible for the line.
INTACodeEditorLineState now has a State property, which lets you know if:
eleLineHighlight
: The line is highlighted.eleErrorLine
: The line contains an error.eleExecution
: The line is currently executing.eleBreakpoint
: The line contains a breakpoint.eleInvalidBreakpoint
: The line contains an invalid breakpoint.eleDisabledBreakpoint
: The line contains a disabled breakpoint.eleCompiled
: The line has been compiled and is breakpointable.
Get the line state either by querying it from INTACodeEditorState for a specific editor or through the Context parameter in paint methods, which gives both editor and line state as well as other paint contexts. Line state is created when it is queried.
INTACodeEditorLineState now has a CellState property, indexed by column, which lets you know if the specific cell has any of the following states:
eceSelected
: The cell is currently selected.eceHotLink
: The cell contains an active hotlink.eceHotLinkable
: The cell supports hotlink.eceSyncEditBackground
: The cell is in the background of a synchronized edit group.eceSyncEditSearch
: The cell is part of a synchronized edit group that is being searched.eceSyncEditMatch
: The cell is part of a synchronized edit group that has a matching item.eceSearchMatch
: The cell is a match for a search query.eceExtraMatch
: The cell is a secondary match for a search query.eceBraceMatch
: The cell is a matching brace or bracket.eceHint
: The cell contains a hint.eceWarning
: The cell contains a warning message.eceError
: The cell contains an error message.eceDisabledCode
: The cell contains disabled code.eceFoldBox
: The cell is a fold box.
Editor Context Menu
The cEdMenuCat constants listed below define the different action list categories used to build the editor's local menu. Use INTAEditorLocalMenu
to add to the editor local menu and use these constants to control where a new action list is added.
Below is a list of which actions in each menu category:
cEdMenuCatIdentifier
: Find Declaration.cEdMenuCatBreakPoint
: Enabled, Breakpoint Properties.cEdMenuCatDebugDebug
: Run to Cursor.cEdMenuCatBase
: Open Source/Header File, open file at the cursor, Browse Symbol at Cursor, Topic Search, Next Buffer View, Prev Buffer View, Next Modification, Previous Modification, Next Unsaved Modification, Previous Unsaved ModificationcEdMenuCatModule
: Open Source/Header File, view messages, and show in Explorer.cEdMenuCatClipboard
: Cut, copy, and paste.cEdMenuCatBookMarks
: All goto and toggle bookmark items.cEdMenuCatDebug
: Debug, and Run To Cursor.cEdMenuCatDebug
: View, Message, Read Only, View as Form.cEdMenuCatView
: View as Form.cEdMenuCatElide
: Fold, Doc Comments, Methods, Nearest, Nested, Regions, Name Space, Types, Unfold, All, Nearest.cEdMenuCatVersionControl
: All version plug menu items.cEdMenuCatRefactor
: Find, Find References, Find Local References, Find Declaration Symbol, Refactor.cEdMenuCatFormat
: Surround, Sync Prototypes.cEdMenuCatRepository
: Add to Repository.cEdMenuCatLast
: View Messages, Read Only, Editor Options.
Code Editor options
The INTACodeEditorOptions interface lets users query information about colors, visible gutter, fonts, sizes, and more.
Editor Gutter Space
The editor gutter is currently the best place to draw per-line information. RAD Studio uses it for line numbers, line folding markers, line modification indications, breakpoints, bookmarks, and more. Reserve a portion of the editor gutter, which will make the gutter wider for your plugin. Do this through the RequestGutterColumn, specifying a size in pixels. This can be located before or after (to the left or right), where breakpoints or line numbers are painted.
The GetGutterColumnRect method allows users to get the rectangle for a specific registered gutter area. Or use the GetGutterColumnRect method to get the rectangle for the registered area in the editor gutter.
Other ToolsAPI changes
- As of 11.3, the following ToolsAPI for the editor painting are now deprecated:
INTAEditViewModifier.BeginPaint
INTAEditViewModifier.EndPaint
INTAEditViewModifier.PaintLine
We recommend using the new set of APIs.