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

Hans-Peter Diettrich DrDiettrich1 at aol.com
Tue Dec 16 07:21:49 CET 2008


Martin Friebe schrieb:

> 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.

Are these characters inside the Unicode BMP?

Actually I did a translation into WideChar, so that only characters from 
the Unicode BMP can be processed. In this model I'd insert a dummy 
character into the output string, that is not displayed but compensates 
for the width of the preceding double-width character.

And what about RTL text? IMO there exist limits for the task of an 
source code editor, where it's easier to use or port some existing text 
processing component, instead of reinventing the wheel.

Full Unicode requires assistance by some sophisticated library, that can 
deal with all the oddities of character sequences (ligatures...), and 
that should be provided and maintained by the platform. Windows has such 
a Uniscribe library, see
<http://www.catch22.net/tuts/neatpad/11>
This editor tutorial convinced me to either stay with truly monospaced 
characters, or let an according library do all drawing. Nothing 
half-baked in between. The same for the *input* of Chinese or other 
exceptional character sets (IME editors...).


> 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.

No problem, my tabs have a special display encoding, that forces the 
caret to move to the next ordinary character cell.

Is it really still a column block, when a double-width character hangs 
out on it's right boundary?


> 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.

Right, I left the implementation of the highlighters and folding to 
dedicated objects. The classes have to implement only the very slim 
interface of the base class, everything else is open end.

BTW, I stored "characteristic" info in fixed size records, which can 
easily saved and exchanged together with the source file or the global 
settings. No encapsulation, but easy to use, and little chances for 
coding errors.


> 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))

In my model nobody has to know about the painting, all required 
information resides in the line buffer and viewport outline. Apart from 
the basic client and gutter size (in pixels) and the font size, the 
viewport outline contains the overall grid dimension, corresponding to 
the line/row count of the document after block folding, the visible area 
is described by a scrollable offset within the grid, or (0,0) for 
painting in client address space, and the current extent of the viewport 
(client area of the control), in both fully and partially visible 
characters. The separation into fully (page size) and partially visible 
(viewport size) characters allows to e.g. scroll by (fully visible) 
pages in both directions, while painting and display of the caret stops 
only at the viewport margins - the latter (caret display) is not 
properly implemented in the current LazEdit!

>> 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.

All that has to be encapsulated in every single view(er). When the code 
explorer is a view, it's internals have almost nothing in common with 
the text viewer. A gutter also is kind of an viewer, coupled with the 
text viewer only by a common TopLine, but independent otherwise.

Of course the various viewers are related to a distinct document and 
helper objects, from which they obtain all the information to be 
displayed, but the kind of required information depends on their 
individual tasks.

The document base class has (virtual) functions for the conversion 
between stored (file based) and visible (possibly folded) coordinates, 
so that the details of folding are encapsulated. When the gutter 
painter/viewer requires information about blocks, it also can obtain 
that information from the according methods in the document base class.


> 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".

Okay - with the subtle difference that in my model the painter is 
provided by the grid-provider with all required information, eliminating 
the need for any callbacks to other objects. The extent of information, 
required by an painter, can be determined easily, and only that 
information has to be passed to the painter.

Did you realize that mutliple views of the same source file can have 
different TopLines, viewport sizes, word-wrap settings, and much more? I 
really cannot see how one grid-provider can fit these different needs of 
multiple views. A meaningful separation IMO were:
- file provider: access in file coordinates (by characters or lines).
- block manager: access to visible (expanded) blocks, in display 
lines/columns.
- view: mapping of visible lines into display (row/col) coordinates.
- painter: mapping of viewport into pixel coordinates.

The latter (pixel mapping) occurs in two places, in the grid itself from 
mouse coordinates in grid coordinates, and in the painter from grid 
coordinates into canvas coordinates (different directions). The required 
information is stored in the viewport outline.


> 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)

That's why keep both the characters and their text attributes in the 
line buffer, passed to the painter. I started with separate regions, 
describing the begin and length of tokens, selection etc., but it turned 
out that the handling of overlapping regions is accomplished easier by a 
direct mapping of the text attributes to every single character. The 
text attributes then can be used to e.g. determine, whether the mouse is 
over a (character in a) hyperlink.


> 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)

I see no need for according (separate) objects. The coordinate 
transformations are determined by the information in the grid outline. 
The outline is updated when the viewport size changes, the font size 
changes, or when the viewport is scrolled - i.e. by user actions. It 
also is updated when the current file changes, either to a different 
file, or by insertion/removal of text, or by block folding. When the 
information has been updated, the display is refreshed.

Line wrapping can occur only in a fixed width viewport with no 
horizontal scrolling capabilities. In this case the document lines can 
be broken into according display lines, which are stored in the line 
buffer cache as distinct lines, eventually containing continuation 
markers (characters). The mapping between physical (display) and logical 
(document based) lines can be stored in the line buffer records.


> Again I do not know the exact organization of your grid.

Attached the source code and documentation, if I don't forget...
[Too big for attachment, sorry]


> 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).

Why should tab expansion require a separate class, extending or 
descending from any other class? The tab settings are global (IDE wide), 
and can be reflected in a commonly used data structure or tab-expander 
singleton.

> 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.

Sounds good, but I doubt that this is feasable. The modules are so 
tigthly coupled, that an implementation in distinct units will be almost 
impossible. This way adding new functionality will require to edit the 
common unit, so that it doesn't matter in which class (common or 
separate) the functionality is implemented.

One such case is the docking manager, where I still don't see a chance 
to implement an different manager separately from Controls.pp. The 
anchor docking sample in fact lacks the drag-dock functionality, because 
the implementation would require access to and modification of the 
existing code base, hidden in the implementation section of Controls.pp. 
An extraction of the hidden classes leads to circular unit references 
all over, protected methods are inaccessible from other classes etc.

If you want to make LazEdit that modular and extensible, please supply a 
unit structure that really allows for such extensions. And don't forget 
proper object management, when a reference is changed to a different 
object - the docking manager implementation will result in memory leaks 
or other quirks, when the automatically created manager is replaced. 
When different viewers (or other objects) can share other objects, 
interfaces instead of classes may be a better solution for the lifetime 
management of the exchangeable objects.


> 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)

My design is bottom up, with open end. The base class implements a 
default behaviour, that can be modified in a derived class, as can be 
seen in the TTextViewer class. The base classes and the base unit(!) 
never must be touched when the functionality is extended.

A user of a CharGrid class, e.g. the Lazarus IDE, doesn't have to care 
about eventual related classes, it only uses the interface provided in 
the single (maybe derived) component class. Then it's also easy to hunt 
bugs, introduced by the implementation of extensions. Either the bug 
resides in the base class, then it can be fixed there, without having to 
wade through uncountable extensions, or it resides in the extension 
itself, and has to be fixed there. The more helper classes are put into 
the base component, the harder the maintenance and extension of such a 
class.


> Of course the Stack I am speaking of, will depended on the situation 
> present itself though a single interface.

I do not really understand why you need an stack? A pool or list of 
exchangeable object references looks more appropriate to me.


>> 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.

A shared text buffer with a shared caret or scroll position does make no 
sense to me. It is debatable whether bookmarks or block folding should 
be the same in multiple views of some file, with regards to the amount 
and management of such block trees, but the user must be allowed to move 
to different places in every view, select different parts of the text, 
have different (hyperlink) history lists, insert/overwrite modes etc.

Thus caret control has to be private to every view. Please try to 
separate all your intended helper objects, with regards to their later 
use, as being bound to a single document, a single view, or whatever 
else. Also keep in mind what has to be saved and restored when the user 
tabs through the file list of a notebook.

> 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.

We agree to disagree.

DoDi




More information about the Lazarus mailing list