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

Martin Friebe lazarus at mfriebe.de
Thu Dec 18 13:51:22 CET 2008


In some places below, I am unsure what you meant by "component":
- the "view" (as in MVC)
- the overall editor (as in SynEdit)

Hans-Peter Diettrich wrote:
> Martin Friebe schrieb:
>   
>> Model shall contain
>> - the raw text
>> - bookmarks, and other marks
>> - foldable secitions
>> - ...
>>
>> Other information can be local to a specific Component (in case multiply 
>> component display the same model in more than one window). An example 
>> would be which sections are folded. This info may or may not be part of 
>> the model
>>     
>
> Right. We should start with the persistent information, stored together 
> with the text on disk, then add all information that is common to all 
> (multiple) Views.
>   
Serializing the Model for storage purposes (such as saving to a file or 
many files) for me comes after the decision how the model looks.

There may be more than one way to serialize the model. As there are many 
ways to use SynEdit.
- In Lazarus it is used a SourceEditor, so serializing should write the 
Text information into one file, and other information into another file.
- In User Applications, people may wish to serialize the model into just 
one file (which can not be edited by other editors)

>> We could differ between a public and a private model. (Probably not needed)
>>     
> Most probably not needed in this form, default is the public Model.
Actually we may need. Example folding.
Having the same model displayed in many Windows, the user may want to 
fold different nodes in each Window.
Or even use different highlighters, leading to different nodes being 
available.

The design of this must leave the choice to the user, which information 
is hold in the public, and which in the private model. The private Model 
can store the same info as the public. The private model only stores 
info the user whises to be different from the public model, all other 
info will be forwarded.

However concerning the design of the other Classes (Controller/View), 
the differentation between private or public model is not relevant. They 
access one Model. This Model knows what to do.

> Folding has to be reflected in a component, both as a structure (block 
> tree in the gutter) and as currently visible lines (for the text 
> painter), so that a helper object/class is appropriate for retrieving 
> all the information in an synchronized way. Then one instance can reside 
> in the Model, holding the shared definition of the blocks, and another 
> instance can be part of the Views, doing individual folding - that's 
> only a matter of how a new View is initialized.
>   
Ack
> View.Folding := Model.Folding; //object reference
> if FoldingPerView then begin
>    PrivateFolding := TFoldingState.Create(View.Folding);
>    View.Folding := PrivateFolding;
> end;
>
> [This is what you would consider as "pushing" another item on the stack, 
> see below]
>   
My implementation probably looks slightly different. There will be no 
dedicated properties (on the View) for dedicated features.

  View.Model := TheModel;
Then the View can internally distribute this. The Model can be a Private 
Model, which acts as a wrapper to the public Model.

This way it is easier to add features, without the need to change the 
way the view is set up.

>> In this terms if you wish to implement new feature (which are not 
>> covered by the set of existing properties), an component that inherits 
>> from the original is the most likely way. This inherited component can 
>> either implement the features itself, or create additional ( or 
>> substitute existing) helper classes to archive the new functionality
>>     
> Isn't LazSynEdit such a customized version of a SynEdit?
>   
yes and no. SynLazarus is a fork off, of an early Synedit. Not done by 
inheritance.
>> But in the following I would like to look at the details of a Component 
>> that provides the following functionality (and can be used by any 
>> editor, Memo or anything else):
>>
>> A component that has:
>> - a Model (was: RawSource) property
>>   - For ease of access an extended TStrings interface can be assumed 
>> (this simplifies the case, as it already implements the text to be 
>> organized into lines)
>>     
>
> ACK. UTF-8 encoding, I suppose? Access should be routed through the 
> folding object.
>   
For SynEdit in Lazarus, currently UTF8. Ideally most of the code should 
be agnostic to the encoding, and the remainder exchangeable.
The design of the Classes involved most not be based on an encoding. 
(except maybe the design of the model)

>>   - A Tab is a normal character that does has no information about it's 
>> later display properties.
>>   - Highlighting information is not part of the model. (Displaying the 
>> same model in 2 windows can be done with different Highlighters selected)
>>   - Markup (such as the selected block, if any) is not part of the model
>>   - This Text has information where it can be folded, but is *not* yet 
>> folded or wrapped
>>     
>
> Wordwrap should be implemented in the component, because it depends on
> the width of the window. Folding should be reflected in a helper object, 
> that allows to retrieve the visible lines. In the simplest case (source 
> not foldable at all) that object does a 1:1 mapping of the unfolded lines.
>   
Which Component ? The View (as in MVC)?

In my design both Folding and WordWrap will be a Class on the "Views" 
internal Stack.

If the MVC is correctly implement, then the only difference between 
Folding or not Folding is:
- An extension to the Model: This should be generic, by adding a class 
to it without the need of changing the Container.
- A Class added to the Views Stack. Again no other code in the View 
should change.

"no other code should change" of course this is not possible, but there 
are ways to get very close. You can have a list of enumerated Features 
(like ecCommand in Synedit)

const
  ftFoo = 1;
  ftFold = 2;
....
FoldData := TFoldData( Model.RetrieveFeature( ftFold ) );

Yet, while such a generic aproach is noce, for very common features it 
may be better to add direct accessor ("FoldData := Model.FoldData;")

>> - properties to define the ViewPort
>>   - The controller can change attributes such as topline or display area 
>> according to the users action.
>>   - The component is not concerned where those changes originate from
>>   - (The decorated TextDrawer (with scrollbars) can send info to the 
>> controller, about required Viewport changes)
>> - A canvas.
>>   This could either be internal part of the component or be given to it. 
>> Which one is the case should not matter for the design of the component.
>>     
>
> I disagree in many details :-(
>
> The physical viewport size is controlled by the user, who also controls 
> all scrolling and folding. This should all be done in the component. 
> Please specify the situations, where the contents should be scrolled 
> from outside the component. Hyperlink or bookmark jumps should be 
> handled in one ScrollIntoView method, that eventually also moves the 
> caret to the new position.
>   
Have you taken into account the difference between the TextDrawer, and 
the decorated TextDrawer? The above comment was not refering to the full 
"View component". It was talking about the undecorated TextDrawer.

Yes ViewPort Management is part of the View. But not of the undecorated 
textDrawer.

>> The component then retrieves the correct part(s) of the raw source, 
>> transforms it and paints it to the canvas
>>     
>
> This depends on the old and new position in the text and canvas. As long 
> as the new position is alreday visible, no redraw is required at all. 
> Only the component has all information available, to determine what 
> really has to be done.
>   
Yes, but I wasn't going into that much detail yet. In that case we have 
to handle external invalidation too. The OS may tell us to redraw parts 
of the Window

>> The component should also concern the application of highlighting or 
>> Markup information, provided it can retrieve it from the appropriate 
>> classes. (This will allow multiply Components to display the same Model 
>> , using different highlighters)
>>     
> It's a matter of invoking the methods of the right object in the right 
> moment. The object references are initialized once, their methods are 
> invoked when required.
>   
Yes. That is what I thought we talked about in the earlier mails in this 
thread?

For me this still is a having the right Class(es) on the Stack (the 
stack inside the View). Those Classes will have access of the Model (or 
parts of the model; as they will have been initialize accordingly). Each 
Class performs one step of the transformation.
Any knowledge about those tranformations outside those classes should be 
minimized (or ideal not exist at all)

This does allow the stack to contain any kind of transformation class. 
Classes can be added without changing any other code. (or with a minimum 
of those changes)

>> The different to a list of transformers is:
>> - in a list 2 neighbours would not necessarily know each others (they 
>> could, but that would almost make it a stack)
>> - a list controller would be needed.
>>     
>
> I'm used to see such constucts as pipes or chains of filters, according 
> to the data flow. Every object *has* to know about its input object, so 
> that it can invoke its methods. Kind of an controller has to be 
> implemented in either case, with regards to the construction and 
> destruction(!) of the objects.
>   
In implementation the difference are probably very minor. In design it 
distinguishes the idea behind it. For the stack the controller is 
reduced to a set-up and tear-down method.

In the stack every class knows about it's next higher level (so it knows 
it input). In that it is similar to a linked list or pipe.

A linked list (or list in general), I would understand as something that 
allows me access (from the outside or controller / controller only if 
encapsulated) to elements in the middle. With a stack there should only 
be access to the bottom element (maybe in some implementations to the 
top element / I do not plan to access the top element)

>> As you can see in the image, I divide the TextDrawer into 2 sets of classes.
>>     
>
> I see. It's a matter of taste whether the ScrollBars are separate 
> elements, or are part of a ScrollableWindow - in either case they 
> determine the logical origin of the canvas. IMO also the TextDrawer 
> *implements* the Logical View, eventually using helper objects.
>   
Ack, it uses the Stack, with all the classes on it.

The canvas is passed in at setup time.
>> In terms of MVC this needs more clarification.
>>     
>
> You mean, when MVC is applied to the internal structure of the 
> TextDrawer, in contrast to the application viewpoint?
>   
No, the MVC has no idea about those internals. This line was only to 
indicate that some of my considerations (including most of my 
considerations in previous emails) where not made in the context of the 
"View" only. (Meaning I was exploring the internals of a MVC View and 
not the Model or Controller / The presence or need of I was taking for 
granted at that time, leaving it out of discussion)

Therefore discussing  some of this in the context of the full MVC some 
parts need further details (see below,  distinguishing between various 
aspects of folding is needed, if looking across the borders of the view )

>> I did put folding into the LogicalView (and therefore into the 
>> TextDrawer, which is the View of the MVC)
>>     
>
> IMO it's encapsulated in the stack, used to retrieve the visible lines 
> from the data source. The amount of currently visible lines determines 
> the overall "grid" size (height in lines), eventually increased by the 
> amount of continuation lines - the WordWrapper would be one level in 
> that stack. The gutter object manages both the painting and the mapping 
> of viewport row numbers back into file based line numbers (to be displayed).
>   
Yes that was what I was trying to say before. (but it failed on the 
differently used terminology).
The stack is part of the logical-view.

ACK

>> Now of course, The TextDrawer does not store any of the data (model). 
>> Folding consists of 2 parts:
>> - The information what is folded, which is part of the model (and 
>> modified by the controller)
>> - The application of this data, to provide the correct output.
>>
>> It is the concern of the TextDrawer to apply this information.
>>     
>
> And it's the concern of the designer of the stack objects, to provide 
> the most appropriate interface for the TextDrawer.
>   
The TextDrawer (undecorated) is the Facade (component) to the following 
objects:
- the stack (with all it's classes)
- the painter

So did you mean: for the stack objects to provide the most appropriate 
interface for the Painter?

>> This gets as outside the Display-Model.  A solution could be to have a 
>> ModularSynEdit (which has SourceModel Property) and an integrated 
>> SynEdit (which has it's own Model for compatibility with existing code)
>>     
>
> Could we agree about an CustomTextDrawer (as a general base class) and 
> an derived/customized LazSynEdit (as a readily usable component, 
> presented in the component palette)?
>   
Yes, A SynEdit should be customizable by picking individual other 
SynHelper components from the palette. (alternatively see below)

> The customization would consist primarily of the construction of the 
> stacks (pipes between Model and View), and secondarily in the reflection 
> of eventually modified interfaces of the stack objects, i.e. in the code 
> to access these objects.
>   
It may be more convenient to provide a special User Interface for this. 
Something like the anchor editor. In which you can select parts of your 
synedit


On a complete different issue, but it will become important when we go 
to implementation. I'd like to change many things to be more 
even/callback driven.

In the current implementation, if something changes, this something 
often knows about everyone else who needs to react to this change, and 
makes direct calls to all of them. That's real bad.
I' d like to revert that. Individual elements know, on who's changes 
they need to react, and register a callback handler. If the other one 
changes, it just makes all the callbacks. It has no need to know who 
receives the callback.

Example:
Interaction between Caret and Viewport.
- If the caret moves, the viewpoer may have to move, so currently the 
caret calls the viewport.
Better: the Viewport just registers on the carets OnChange callback List.

Best Regards
Martin









More information about the Lazarus mailing list