[Lazarus] Code Structure / SourceEdit and SyneEdit [Re: Mouse Link in SynEdit (only link-able items)]

Martin Friebe lazarus at mfriebe.de
Mon Dec 15 13:13:49 CET 2008


Hans-Peter Diettrich wrote:
> Martin Friebe schrieb:
>   
>> The only reason I did skip the "grid" in the name is that a generic 
>> painter base class will not define a griod (or maybe it will but based 
>> on pixels). So it does not block anyone from implementing a proportional 
>> painter
>>     
> Okay, but a proportional representation will have a very different 
> coordinate mapping, so that most of the basic functions (mouse, cursor 
> movements) are very different. My approach addressed exactly those low 
> level tasks, with the CharGrid already being a specialized grid class, 
> with cells containing characters, and added font and text properties.
>
> My primary goal was a stable base component, that can be turned into any 
> text viewer or editor, by adding specialized document interfaces in 
> derived classes. For a proportional representation the design will have 
> to be turned upside down, and the base class will have to be specialized 
> and follow the design of the related content handler(s).
>   
Then how to you handle double width chars? This si one of the problems I 
still have to address.
Even in a proportional font, some chars (Chinese, and other) have twice 
the width of a normal char. They will 2 positions in the grid.
Actually it is the same issue as tabs.

Each char needs to have an info, how many cells it will allocate. Now if 
that is the case then to display proportional fonts, all I need to to 
make the grid size 1 pixel.
-- I do *not* encourage that. I do not say this is a good thing for a 
source editor. But given how close SynEdit comes to proportional display 
anyway, I keep at least an open mind about a structure that would allow 
to implement it. --

Column Blocks, as well as horizontal caret movement have to deal with 
this anyway, since you may allow to be in the middle of a tab. But you 
can not permit to have the caret in the middle of a chinese char.

>> That canvas/handle holder does not replace the (Grid)Painter. It is used 
>> by the GridPainter(s), and used by the GutterPainter(s). It may even be 
>> thyat it does not need to exist, and canvas and handle can be passed to 
>> all the Painters in Form of their LCL classes.
>>     
> Then my CharGrid is kind of a canvas holder, which performs the mapping 
> between document (content) and viewport (painting) space. The painters 
> do not have to know about the organization of the canvas, they only have 
> to paint given information within their actual clipping area (part of a 
> display line). The content holder (source file) can be switched at any 
> time, whereupon the CharGrid adjusts the viewport (window) to the new 
> content extent.
>   
The question still is how do your painters to all that. IMHO the mapping 
into a grid requires a lot of info (folded/ word wrapped/ tabs,...) as 
well as highlighting info. The painter as I see it collects all this 
info, but the info is provided by other objects.

The View port for example does not do the painting, it is a helper class 
to map the right code into the grid. It is used by the painters, but 
also used outside.
It allows (without accessing the painter) to check if a char or the 
caret is in the visible area. ( There still is the question if it will 
be the painter or the viewport who defines the size of each grid cell 
(basically the font size))

>> As It currently stands all the info about everything is hold by the Main 
>> SynEdit Class, and all other classes need to ask the central SynEdit 
>> Class. That is undesirable.
>>     
>
> ACK. A MVC (model-view-controller) approach migth be better. The model 
> holds the source files, the view manages painting and user interface 
> (mouse and keyboard), and the controller updates the document upon input 
> or other commands, and synchronizes the related view(s) afterwards.
>   
The view IMHO is more than one class, that gradually apply the mapping 
from a Source-Holder (TStringList) to a char-grid. The Painter then 
transfers each char to from the grid to the canvas.

That is what I am currently trying to do

the painter looks at a "grid-provider",  a grid provider may read either 
the source or another grid-provider as input. I currently call those 
grid-provider "View".

One thing must change, currently the PaintLines code, combines the 
highlight info with the grid-view result. But that means mapping the 
highlight info.
The highlight info must be applied to the unmodified source, and then 
share the way through the grid-providers
(That's actually something I realized from this discussion => good)

So If  I display a text that has no tabs, no double width chars, no 
folds, no ...., then all I need is:

-source-buffer
-highlight info (does not re-organize the layout)
-viewport-grid
-painter

The view port grid, selects the correct lines, and within each line the 
correct substring. So if the text is horizontaly scrolled it cuts the 
beginning of each line, and in any case, it cuts any line that is to long.
There a 2 objects:
- The TViewPort => which defines the corner points / the rectangle
- The TViewPortTextView => which reveals the text in the ViewPort. E.g. 
returns 20 lines, with 80 chars in each line (like a grid)

If I have tabs, I add them
-source-buffer
-highlight info
-tab expander
-viewport-grid
-painter

DoubleWidth char view would then fill an empty-helper cell, after each 
chinese char (and add drawing info to the cell with the DBL char 
indicating this is a DBL) It also provides helper methods for the Caret 
Navigation.


Again I do not know the exact organization of your grid. But for me the 
(as an example) the tab-view/expander is not a subclass of the painter 
(or grid). The tab-view/expander class is a class of it's own 
(inheriting from an abstract TextView/GridMapper).

All the individual view/grid/mapping classes are organized in a stack. 
You can at anytime add/exchange mebers of the stack to archive new 
functionality.
>   
>> I extracted 2 classes already (but the ove isn't complete)
>> - TSynEditCaret:
>>    Storing all info about the caret. It will obviously need help from 
>> other modules, to deal with tabs, double-width chars, wrapped lines 
>> (that will probably be a specialized subclass, completely replacing the 
>> original), and other things
>> -TSynEditSelection:
>>   To deal with the selected block. This one is not very related to this 
>> discussion. But it has the same needs as the Caret
>>     
>
> The caret is private to every view, the outer world only has to retrieve 
> or modify the logical (content based) caret position. The same for the 
> selection, with content based row/col coordinates; the view will manage 
> the display of either a sequential or column based block hightlighting, 
> and merge the text attributes of all "block" sources (syntax, 
> hyperlinks, selection).
>
> BTW, just the requirement for column-based blocks discourages the use of 
> an proportional font. Elastic tabs may allow for blocks with consistent 
> left/right margins, but then the text will look strange in any other 
> editor or viewer, what's not desireable with shareable source code.
>   
>> I will have to add a TSynEditViewPortClass:
>>  This will at least store the Coordinates of the screen in the text (as 
>> in TopLine/LeftChar - LinesInWindow/CharWidthOfScreen), maybe a bit 
>> more.  To do so, it will need access to the Painter to get information 
>> about the grid (LineHeight, SingleCharWidth)
>>     
>
> This were my CharGrid, that translates everything between document 
> (model) and view space. The interface between document and view can be a 
> simple record, containing the document row count. My CharGrid then can 
> determine everything else from the text itself. I delegated that mapping 
> to the syntax highlighter, that already has the task of parsing the 
> text. It also will be involved in the determination of foldable blocks, 
> so that folding (list of blocks and their state) can be implemented 
> inside that class.
>   
See above. You always speak of your Grid in singular, as one class. For 
me this is a list of classes (the stack), plus the helper classes 
(Highlighter and Markup)

Of course the Stack I am speaking of, will depended on the situation 
present itself though a single interface.
> TopLine and LeftChar are not of any interest outside the view. A 
> ScrollIntoView method will be sufficient for the outer world, with 
> document based coordinates, perhaps with an anchor (alTop, alBottom, 
> alCenter).
>   
The View here being a SynEdit drawing a (possible shared) Textbuffer? 
True TopLine should not be needed outside, but it is needed for Caret 
Control.
Therefore I differ between the ViewPort (defining the rectangle) and The 
ViewportTextView  using the rectangle to provide the grid of chars which 
is to be displayed.
It is possible that those two indeed become just one class in the end. 
But from where SynEdit is now, I'll start them as 2 classes. It allows 
to take smaller steps, and hopefully reduce the amount of bugs I may 
introduce during this task.
>>>> - the highlighter
>>>> - the MarkUpManager   
>>>>         
>>> In my solution the highlighting (including hyperlinks) is implemented in 
>>> derived classes, by overriding the line-painting method. I ended up in a 
>>> single array, holding the scanner start state for every line - required 
>>>       
>> If I understand your description correct: This special array is 
>> currently part of SynEditCodeBuffer? In any case this is information for 
>> the highlighter. The (Grid)Drawer should never access this info 
>> directly. The grid drawer will ask the highlighter (In the current 
>> Synedit there may be a need to clean up the way the Highlighter is 
>> handled...)
>>     
>
> My hiliter has several purposes:
> - it parses a new document for it's line count, multi-line comments, 
> longest line (maybe for foldable blocks as well).
> - with folding, it translates between document line and visible row 
> numbers (not yet implemented).
> - it parses a given line for syntactic elements, setting the text 
> attributes in the display line buffer.
>
> Painting a line requires multiple actions:
> - the viewport row is mapped into a document line (or vice versa).
> - the according text is retrieved and stored in the line buffer, with 
> tab expansion, special character substitution and UTF translation if 
> required, bookmarks etc.
> - the syntax highlighter fills in character attributes.
> - hyperlink attributes are updated, according to the mouse position.
> - the visible part of the line buffer is determined, depending on the 
> viewport margins and wrapped lines.
>   
And I plan on a stack of Textviews, that will provide, collect and merge 
all the above properties
> Then the text and gutter painters are invoked for the supplied line 
> buffer, if their painting area is not clipped. The text painter 
> determines sequences of same text attributes, initializes the canvas 
> (DC) and paints the according characters.
>
>   
Best Regards
Martin



More information about the Lazarus mailing list