Welcome to the new Friends-of-FPC!

Here you can find all kinds of information about the FreePascal Compiler. We have many tutorials and howtos as well as a selection of tools to help you with your programming. We also have some example codes for you. And if you want to contribute some information/ sources/ tools yourself you can do so.
Also we have finally relaunched the FoFPC forum. It's your chance for some Q&A about everything FreePascal.

Friends-of-FPC

Tutorials: Learn how to code with FreePascal.

Source Codes: A collection of examples, miscellaneous source codes and open source stuff.

Tools and Help Files: Intro- duction of some tools that might help you with FPC.

Community

Forum: Ask or answer questions about the FreePascal Compiler, programming or just babble about coding.

Contribute! Contribute your own Tutorial, Source Codes or Tools and send them to us!

Website

About: Information about Friends-of-FPC.org.

How to write an editor - Chapter 1 - by Aiwendil

The first steps...

First of all we should take a look at what we need to accomplish, what we want to accomplish will be a later matter.

What we need are:

  • Buffers
    • Create buffer
      • Get memory
      • Update chain
    • Destroy buffer
      • Copy active buffer-pointer
      • Remove buffer from chain
      • Free old buffermemory through copy
    • Write buffer
    • Read buffer
  • Screen-updater
    • Determinde which kind of update
      • Complete?
      • Partial
  • File-operations
    • Load file
      • Open file
      • Read from file to line
      • Create buffer
      • Put line in buffer
      • Loop to 'load file' if there was no error
    • Save file
      • Check if file exist
        • If it does then ask if it should be overwritten
    • Create/Rewrite file
      • Put buffer in line
      • Write line to file
      • Loop to 'save file' if there was no error
  • Keyboard-handler
    • Normal chars
      • Translate
        • Buffer-operations
        • Resize buffer
        • Add char to buffer
      • Loop to 'keyboard-handler'
    • Commands
      • Identify commands
        • Actions
      • Loop to 'keyboard-handler' if not prevented

As you can see most of the program is already done by now all we have to do is to sort it and put the code down in a file, a pure number-cruncher in other words.

The two approaches

There are two ways one can write this, either in a large file (or several and use includes) or split it up into several units/libraries. I will use the later of the approches since it will keep the code clear and easy to find, the drawback with this is that it is a bit slower (you will most likely never notice the diffference when it comes to speed).

The files

I will split it up into the following files:

  • buffers.pas - buffer-operations.
  • screen.pas - screen routines, mainly the updater.
  • fileops.pas - file-operations, load, save, and so on
  • keyboard.pas - keyboard-handling and routines.
  • types.pas - shared types and constast.

As you can see I will have one for for each type of task performed, this will force you to write readable code as well as keeping the procedures small and specialized, this will give you an almost forgotten way to really optimize code and it will be easier to "tap into" a certain procedure/function to see how many times it's called, and if it's called at all. One important thing with this approach is that you should smartlink everything (that will make sure that only the needed code will be compiled into the complete program).

Oh well, that's enough boring stuff for now. Time to get on to the fun parts

Unce upon a time, there was an empty space.. that we shall fill with code

A note for the following sections, I will include code quite often and you could most likely copy and paste it, but if you are unsure of what a certain thing do then look it up and try to write the thing yourself.

A second note for the following sections, I will discard all memory errors which will make the program easier to write and understand but it will also be more likely to crash instead of reporting an error if the memory would run out.

Let's start with buffers, there are several nice ways to do this (and plenty of ugly ways), I prefer to use linked lists, and when I favour speed over memory I use double linked lists and also when I'm too lazy to optimize (this tutor will use double linked lists).

Declare this one in the interface part of the unit buffers.pas since that will force all your other procedures to be aware of this unit (and if you only use the procedures in this unit for all buffer manipulation it will make debugging easier).

First hard decision shall we use pascal-style or asciiz style. Both have their advantages. With pascal it's easy but limited, with asciiz it is hard but only the memory available sets the limit. I will use pascal-style in this tutor since they are easier to work with, if you want to convert it to asciiz take a look at the unit strings that comes with your compiler.

Each entry in the chain with buffers should have the following, a var to hold the data (a pointer), a pointer to the next object in the chain, and an pointer to the previous object in the chain.

 TYPE pBuffer = ^tBuffer;
      tBuffer = RECORD
                   Data : ^STRING;
                   Next : pBuffer;
                   Prev : pBuffer;
                END;

The reason that I made data as a pointer is that as long as the string are less than 250 chars we will save memory, and how often do you read documents that have more than about 90% of the lines longer than that? :)

Note however that pBuffer and tBuffer must both be in the same TYPE block (put a TYPE in front of tBuffer and you will get a fun error) and also, they belong in buffers.pas and NOT types.pas.

Next up we have a fun task, to allocate a buffer entry. This procedure will be smaller then I usually make stuff but it will also be more general which will make it easier to use it for all sorts of things.

This procedure will only allocate and set up all the values to defaults.

PROCEDURE AllocBuffer(VAR Buffer:pBuffer);
BEGIN
   New(Buffer);
   WITH Buffer^ DO BEGIN
      Data:=NIL;
      Next:=NIL;
      Prev:=NIL;
   END;
END;

And now for the deallocation of the buffer, this function will return false if it is Data is allocated, on success it will return true.

FUNCTION DeAllocBuffer(VAR Buffer:pBuffer):BOOLEAN;
BEGIN
   DeAllocBuffer:=True;
   IF Buffer<>NIL THEN 
      IF Buffer^.Data<>NIL THEN DeAllocBuffer:=False
      ELSE Dispose(Buffer);
END;

Now we have the routines to create and destroy buffers, that is all fine but now, is there anything we can do with it, yes - an entire program :)

Now we can play around with the Data in tBuffer, this is where the fun begins.. see if you can keep up with me :)

Will perform no errorchecking so make sure that Data points to NIL and that Buffer points to a pBuffer.

PROCEDURE AllocBufferData(VAR Buffer:pBuffer;S:STRING);
BEGIN
   WITH Buffer^ DO BEGIN
      GetMem(Data,Length(S)+1);
      Data^:=S;
   END;
END;

And now for the dealloc, partial error-checking, it will se if data points otherwise than nil and only proceed if it does.

PROCEDURE DeAllocBufferData(VAR Buffer:pBuffer);
BEGIN
   WITH Buffer^ DO BEGIN
      IF Data<>NIL THEN FreeMem(Data,Length(Data^)+1);
      Data:=NIL;
   END;
END;

And a function to get the data in Data in a Buffer.

FUNCTION GetBufferData(VAR Buffer:pBuffer):STRING;
BEGIN
   WITH Buffer^ DO 
      IF Data<>NIL THEN GetBufferData:=Data^
      ELSE GetBufferData:='';
END;

And now to resize the Buffer.Data, this is useful if you have edited a line and don't want to update the entire chain and all such.

PROCEDURE ResizeBufferData(VAR Buffer:pBuffer;S:STRING);
BEGIN
   DeAllocBufferData(Buffer);
   AllocBufferData(Buffer,S);
END;

This is another main advantage with keeping stuff small and simple, you can reuse large parts of your code and thanks to that the actual time typing and the amount of lines will be reduced.

And now for another fun thing with pointers, double-linked lists. Time to actually have the list properly initialized and set up in a usuable and still easy to use for, declare this type in the interface part of buffer.pas as well.

TYPE tBufferList = RECORD
                      FirstBuffer   : pBuffer;
                      LastBuffer    : pBuffer;
                      CurrentBuffer : pBuffer;
                   END;

As you can see all three of them are of the type pBuffer, but let's briefly go through what their purposes are. FirstBuffer will point to the first buffer in the list, this is mainly to speed things up, it is actually not needed but will as I said speed things up and also make coding a lot easier.

LastBuffer is like FirstBuffer but points to the last buffer in the list.

CurrentBuffer is the important one, this one if properly maintained will point to the current buffer and will be one of the most used pointers in the program.

And we need a procedure to set the tBufferList to some nice defaults.

PROCEDURE InitBufferList(VAR BufferList:tBufferList);
BEGIN
   WITH BufferList DO BEGIN
      FirstBuffer:=NIL;
      LastBuffer:=NIL;
      CurrentBuffer:=NIL;      
   END;
END;

And also needed are routines to put pBuffers into the list. I never said that pointers are easy things to work with, they are hard, most programmers learn to either hate them or love them. :)

This one is relative to the CurrentBuffer, the Buffer on among the parameters are the Buffer to add to the BufferList.

PROCEDURE AddBufferToList(VAR BufferList:tBufferList;VAR Buffer:pBuffer);
BEGIN
   IF Buffer=NIL THEN Exit;
   WITH BufferList DO WITH Buffer^ DO BEGIN
      Prev:=CurrentBuffer;
      IF CurrentBuffer<>NIL THEN BEGIN
         Next:=CurrentBuffer^.Next;
         CurrentBuffer^.Next:=Buffer;
      END ELSE Next:=NIL;
      IF FirstBuffer=NIL THEN FirstBuffer:=Buffer;
      IF LastBuffer=NIL THEN LastBuffer:=Buffer;
      IF LastBuffer=CurrentBuffer THEN LastBuffer:=Buffer;
      CurrentBuffer:=Buffer;
   END;
END;

And to remove it from the list, the Buffer on the list is the buffer to remove.

PROCEDURE RmBufferFromList(VAR BufferList:tBufferList;VAR Buffer:pBuffer);
BEGIN
   IF Buffer=NIL THEN Exit;
   WITH BufferList DO
      WITH Buffer^ DO BEGIN
         IF Next=NIL THEN LastBuffer:=Prev
            ELSE Next^.Prev:=Prev;
         IF Prev=NIL THEN FirstBuffer:=Next
            ELSE Prev^.Next:=Next;
         IF CurrentBuffer=Buffer THEN
            IF Next<>NIL THEN CurrentBuffer:=Next
            ELSE CurrentBuffer:=Prev;
      END;
END;

And some only to provide a nice programmers interface (for the sake of completness).

FUNCTION BufferNext(VAR Buffer:pBuffer):pBuffer;
{Returns what's in the var Next in Buffer}
BEGIN
   IF Buffer<>NIL THEN BufferNext:=Buffer^.Next
      ELSE BufferNext:=NIL;
END;

FUNCTION BufferPrev(VAR Buffer:pBuffer):pBuffer;
{Returns what's in the var Prev in Buffer}
BEGIN
   IF Buffer<>NIL THEN BufferPrev:=Buffer^.Prev
      ELSE BufferPrev:=NIL;
END;

And one in order to update the BufferList

FUNCTION GoBufferList(VAR BufferList:tBufferList;Next:Boolean):pBuffer;
{Will update CurrentBuffer to Next/Prev and also return the new value,
 if the Next/Prev is NIL this one will return NIL but not update 
 CurrentBuffer. If Next is true the it will go Next other wise it will go 
 Prev}
LABEL _NIL;
BEGIN
   WITH BufferList DO BEGIN
      IF CurrentBuffer=NIL THEN GOTO _NIL;
      CASE Next OF
         True  : BEGIN
                    IF CurrentBuffer^.Next=NIL THEN GOTO _NIL;
                    CurrentBuffer:=CurrentBuffer^.Next;
                 END;
         False : BEGIN
                    IF CurrentBuffer^.Prev=NIL THEN GOTO _NIL;
                    CurrentBuffer:=CurrentBuffer^.Prev;
                 END;
      END;
      GoBufferList:=CurrentBuffer;
   END;
   Exit;
   _NIL:
   GoBufferList:=NIL;
END;

And also we need something to make it easier to add strings to the BufferList so here comes a small piece of code for it.

PROCEDURE BufferAddStr(VAR BufferList:tBufferList;S:STRING);
VAR Buf : pBuffer;
BEGIN
   AllocBuffer(Buf);
   AllocBufferData(Buf,S);
   AddBufferToList(BufferList,Buf);
END;

Hmm, I think that was all that is needed for the buffers. We will notice otherwise, and the nice part with writing tutors in severeal parts are that one can fix bugs and errors as we go.

End of part1.

Back to previous page

Useful Links









Link to us