Section courante

A propos

Section administrative du site

Introduction

Le concept d'«éditeur de texte» permet la modification de texte ASCII ou texte brute généralement proche du monde des développeurs et des administrateurs systèmes. Il permet la création et la modification des n'importes quels caractères contenus dans une police de caractères de style ASCII sur un ensemble de lignes que contient un fichier de format texte ASCII ou Unicode. Il existe un éditeur de texte dans la plupart des systèmes d'exploitation. Il ne propose aucun formatage de texte, il peut cependant avoir une syntaxe colorée représentant la compréhension symbolique du texte, comme d'un langage de programmation par exemple. Cette syntaxe colorée n'est pas sauvegardée dans le fichier ASCII et est calculée à la volée par l'éditeur de texte. Enfin, l'éditeur de texte ne supporte pas l'inclusion d'image et ne supportera jamais d'image, car cet aspect est destiné au traitement de texte et non aux éditeurs de texte.

Liste des éditeurs de texte

Les éditeurs les plus populaires en fonction des plateformes :

Plateforme Application
Windows TextPad, Notepad (Bloc note), Notepad++
Linux nano, pico, vi
Linux/KDE Kate
Linux/GNOME Gedit
Java jEdit
Mac OS 8 et 9 SimpleText
Mac OS X TextEdit

Liste des différentes méthodes de conception

Lorsqu'on conçoit un éditeur de texte, le choix de la structure interne détermine à la fois les performances, la simplicité du code et la capacité à gérer efficacement les opérations courantes comme l'insertion, la suppression ou le déplacement du curseur. Il existe plusieurs approches classiques ou innovantes, chacune avec ses avantages selon le contexte : mémoire disponible, taille moyenne des fichiers, ou réactivité requise. Avant de se lancer dans l'implémentation, il est essentiel de bien comprendre les compromis que chaque méthode implique.

Certaines structures favorisent la simplicité et la rapidité d'accès linéaire, comme les tableaux fixes ou dynamiques, idéaux pour de petits fichiers avec peu de modifications. D'autres, comme les listes chaînées ou les arbres (exemple rope), sont pensées pour les documents de taille plus importante et de nombreuses insertions ou suppressions dispersées. L'efficacité dépend donc de la fréquence et de la localisation des modifications dans le texte, ainsi que de la nécessité de visualiser rapidement certaines portions.

Enfin, les éditeurs peuvent intégrer des modèles hybrides ou différés, comme des tampons avec espace libre (gap buffer), des tampons circulaires, ou encore des mécanismes de mémoire cartographiée. Ces solutions optimisent soit la consommation mémoire, soit la réactivité aux changements. Il peut être aussi pertinent d'utiliser des approches spécialisées pour certaines fonctionnalités, comme l'historique d'annulation ou le traitement de très grands fichiers. Voici un tableau récapitulant les principales méthodes utilisées pour représenter le texte en mémoire dans un éditeur :

Méthode de conception Description
Tableau linéaire de 64 Ko Le texte est stocké dans un tableau fixe de caractères (exemple 64 Ko), chaque ligne suit linéairement. Simple mais peu flexible pour l'insertion/suppression.
Liste doublement chaînée (ligne par élément) Chaque élément de la liste correspond à une ligne de texte. Facile à insérer ou supprimer des lignes, mais moins efficace pour le rendu global.
Liste chaînée de blocs de lignes (buffer gap) Utilise un tampon (gap buffer) pour stocker du texte dans des blocs, avec une "zone vide" pour optimiser les insertions au curseur. Courant dans les éditeurs modernes.
Liste symétrique de listes de caractères Chaque élément d'une liste représente une ligne, elle-même sous forme de liste de caractères. Offre une grande souplesse, mais la navigation peut être lente.
Arbre de segments (rope) Structure arborescente où chaque noud contient un segment de texte. Idéal pour les très gros fichiers et les insertions fréquentes au milieu du texte.
Tampon circulaire (ring buffer) Utilisé pour des tampons temporaires, ce système boucle les caractères dans une mémoire finie. Moins utilisé pour l'édition longue.
Mémoire paginée ou segmentée Le texte est réparti sur des "pages" ou segments mémoire. Bon pour gérer de très gros fichiers ou des systèmes limités en RAM.
Fichier cartographié en mémoire (memory-mapped file) Utilise les capacités de l'OS pour mapper un fichier texte en mémoire, permettant d'éditer directement sur disque sans tout charger en RAM.
Vecteur de pointeurs sur lignes Un tableau dynamique de pointeurs sur des chaînes représentant les lignes. Pratique pour accéder rapidement à une ligne donnée.
Modèle basé sur un tampon de modification (diff buffer) Garde en mémoire uniquement les modifications, qui sont appliquées à un texte de base à la volée. Optimise la mémoire et les annulations.

Tableau linéaire de 64 Ko

La méthode du tableau linéaire de 64 Ko consiste à réserver en mémoire un espace fixe, généralement un tableau de 65 536 caractères, dans lequel le texte est entreposé de manière continue. Chaque caractère y occupe une position consécutive, sans distinction entre les lignes. Cette approche est simple à implémenter, idéale pour les systèmes contraints ou les éditeurs de texte basiques, notamment sur micro-ordinateurs 8 ou 16 bits. L'adresse du curseur correspond directement à un indice du tableau, ce qui rend l'accès très rapide. Cependant, ce modèle impose une limite de taille stricte au fichier édité. Les insertions ou suppressions nécessitent souvent un décalage de tous les caractères suivants.

Du point de vue de la gestion des lignes, cette méthode repose souvent sur des caractères de fin de ligne (comme CR ou LF) pour marquer les retours à la ligne. Il n'y a pas de structure dédiée à la segmentation logique du texte, ce qui signifie que toute opération agissant sur une ligne entière (comme couper ou insérer une ligne) nécessite un balayage du tableau pour identifier ses bornes. Cette faiblesse impacte les performances en cas de textes longs ou de fréquents déplacements verticaux. Les recherches et remplacements, en revanche, s'effectuent simplement par parcours séquentiel. Cette linéarité du stockage facilite aussi l'exportation ou la sauvegarde brute.

Ce type de conception trouve encore sa place dans certains éditeurs ultra-légers, embarqués ou destinés à des plateformes rétro. Sa rigidité en mémoire peut même être considérée comme un avantage dans des environnements critiques où il faut contrôler l'usage exact de chaque octet. Toutefois, dès que les exigences montent en complexité (par exemple, prise en charge de fichiers volumineux, encodage multioctet, ou annulation multiple), le tableau linéaire montre rapidement ses limites. Il reste donc davantage un modèle de base ou pédagogique qu'un choix optimal pour des éditeurs modernes.

Le modèle du tableau linéaire de 64 Ko a été largement utilisé dans plusieurs éditeurs de texte des années 1980, notamment dans les premières versions de Turbo Pascal (versions 1 à 3). À cette époque, les ordinateurs personnels comme le PC XT ou les machines compatibles MS-DOS disposaient de ressources limitées, rendant cette approche particulièrement adaptée. Le code source était chargé entièrement dans un tampon de taille fixe, souvent limité à 64 Ko, ce qui correspondait à la mémoire segmentée des processeurs 8086. D'autres éditeurs célèbres comme WordStar, EDLIN (éditeur en ligne de commande de DOS), ou encore certains éditeurs intégrés dans les systèmes CP/M adoptaient des structures similaires. Ce modèle a donc marqué une génération d'outils de développement et de traitement de texte, souvent optimisés pour la simplicité, la compacité et la vitesse d'exécution.

Liste doublement chaînée (ligne par élément)

La structure de liste doublement chaînée (ligne par élément) est une méthode de conception très répandue dans les éditeurs de texte devant gérer efficacement des modifications ligne par ligne. Dans ce modèle, chaque noeud de la liste représente une ligne de texte, contenant un pointeur vers la ligne précédente et un vers la suivante. Cela permet de naviguer facilement vers le haut ou vers le bas du document, tout en facilitant l'insertion ou la suppression d'une ligne complète sans devoir déplacer l'ensemble du texte. Cette approche offre donc une bonne flexibilité structurelle.

L'un des grands avantages de cette structure est sa capacité à supporter des fichiers de taille variable sans nécessiter de mémoire contiguë. Chaque ligne est allouée dynamiquement, ce qui permet d'économiser de la mémoire, surtout pour des fichiers avec beaucoup de lignes courtes. Lorsqu'un utilisateur ajoute ou supprime une ligne, seules les connexions locales de la liste sont modifiées, ce qui rend ces opérations très rapides. En revanche, pour effectuer une recherche globale ou pour exporter le contenu complet, il faut parcourir l'ensemble de la liste, ce qui peut être moins performant que dans une structure linéaire.

Ce modèle a été utilisé dans plusieurs éditeurs de texte fonctionnant en mode texte ou en environnement UNIX, où les lignes sont souvent la base des opérations (exemple : ed, vi, ou des clones légers de emacs). Il est particulièrement efficace pour les environnements où les lignes sont des unités logiques, comme dans les scripts, le code source ou les fichiers de configuration. Il est également bien adapté à des fonctionnalités comme les marqueurs de ligne, les numéros de ligne ou les systèmes d'annulation localisée. Malgré des limitations pour certaines opérations globales, la liste doublement chaînée reste une option robuste et facile à maintenir pour l'édition textuelle structurée.

Voici un exemple d'un programme EDLIN tiré du projet MSDOS-0 écrit en Turbo Pascal démontrant ce concept :

  1. { @author: Sylvain Maltais (support@gladir.com)
  2.   @created: 2022
  3.   @website(https://www.gladir.com/msdos0)
  4.   @abstract(Target: Turbo Pascal, Free Pascal)
  5. }
  6.  
  7. Program EDLIN;
  8.  
  9. Uses Crt,DOS,Strings;
  10.  
  11. Const
  12.  CommandList:Array[0..12]of String[16]=(
  13.   'A','C','D','E','I','L','M','P','Q','R','S','T','W'
  14.  );
  15.  
  16. Type
  17.  
  18.  PCharByteRec = Record
  19.   PChr:PChar;
  20.   Nm:Byte;
  21.  End;
  22.  
  23.  StrByteRec = Record
  24.   PChr:PChar;
  25.   Nm:Byte;
  26.   Len:Word;
  27.  End;
  28.  
  29.  StrWordRec = Record
  30.   PChr:PChar;
  31.   Nm,Len:Word;
  32.  End;
  33.  
  34.  PCharWordRec = Record
  35.   PChr:PChar;
  36.   Nm:Word;
  37.  End;
  38.  
  39.  RBufPtr = ^RBufRec;
  40.  
  41.  RBufRec = Record
  42.   Buf:Pointer;
  43.   Size:Word;
  44.   Previous,Next:RBufPtr;
  45.  End;
  46.  
  47.  ArrayList = Record
  48.   PCurrPtr,Count:LongInt;
  49.   CurrPtr,List,EndListPtr:RBufPtr;
  50.  End;
  51.  
  52. Const
  53.  MinRec=SizeOf(PCharWordRec)+1;
  54.  
  55. Function MaxAvail:LongInt;Begin
  56.  MaxAvail:=High(LongInt);
  57. End;
  58.  
  59. Function MemAlloc(Size:Word):Pointer;
  60. Var Ptr:Pointer;
  61. Begin
  62.  MemAlloc := NIL;
  63.  If(Size = 0)Then Exit;
  64.  If(MaxAvail < Size)Then Exit;
  65.  GetMem(Ptr,Size);
  66.  MemAlloc := Ptr;
  67. End;
  68.  
  69. Function MemNew(Size:Word):Pointer;
  70. Var
  71.  Ptr:Pointer;
  72. Begin
  73.  Ptr:=MemAlloc(Size);
  74.  If(Ptr<>NIL)Then FillChar(Ptr^,Size,0);
  75.  MemNew:=Ptr;
  76. End;
  77.  
  78. Function NewBlock(Var Buf;Size:Word):Pointer;
  79. Var
  80.  Ptr:Pointer;
  81. Begin
  82.  Ptr:=MemAlloc(Size);
  83.  If(Ptr<>NIL)Then Move(Buf,Ptr^,Size);
  84.  NewBlock:=Ptr;
  85. End;
  86.  
  87. Procedure ArrayListInit(Var Q:ArrayList);Begin
  88.  Q.PCurrPtr := -1;
  89.  Q.CurrPtr := NIL;
  90.  Q.List := NIL;
  91.  Q.EndListPtr := NIL;
  92.  Q.Count := 0;
  93. End;
  94.  
  95. Function ArrayListIsEmpty(Var Q:ArrayList):Boolean;
  96. Begin
  97.  ArrayListIsEmpty := (Q.List = NIL);
  98. End;
  99.  
  100. Function ArrayListAdd(Var Q:ArrayList;Size:Word):Pointer;
  101. Var
  102.  W:RBufRec;
  103.  WPtr:RBufPtr;
  104.  Addr:Pointer;
  105. Begin
  106.  ArrayListAdd:=NIL;
  107.  FillChar(W,SizeOf(W),0);
  108.  If Size>0Then Begin
  109.   Addr:=MemAlloc(Size);
  110.   If(Addr=NIL)Then Exit;
  111.   W.Buf:=Addr;
  112.   W.Size:=Size
  113.  End;
  114.  If(Q.List=NIL)Then Begin
  115.   Q.List:=NewBlock(W,SizeOf(RBufRec));
  116.   If(Q.List=NIL)Then Exit;
  117.   Q.EndListPtr:=Q.List
  118.  End
  119.   Else
  120.  Begin
  121.   WPtr:=Q.EndListPtr;
  122.   If(WPtr=NIL)Then Exit;
  123.   W.Previous:=WPtr;
  124.   WPtr^.Next:=NewBlock(W,SizeOf(RBufRec));
  125.   Q.EndListPtr:=WPtr^.Next;
  126.  End;
  127.  Inc(Q.Count);
  128.  ArrayListAdd:=Addr
  129. End;
  130.  
  131. Function ArrayListAddBuf(Var Q:ArrayList;Size:Word;Const Block):Boolean;
  132. Var
  133.  Ptr:Pointer;
  134. Begin
  135.  ArrayListAddBuf:=False;
  136.  Ptr:=ArrayListAdd(Q,Size);
  137.  If(Ptr<>NIL)Then Begin
  138.   Move(Block,Ptr^,Size);
  139.   ArrayListAddBuf:=True;
  140.  End;
  141. End;
  142.  
  143. Function ArrayList_AddBuf(Var Q:ArrayList;Size:Word):Pointer;
  144. Var Ptr:Pointer;
  145. Begin
  146.  ArrayList_AddBuf := NIL;
  147.  If Not(ArrayListAddBuf(Q,Size,Ptr))Then Exit;
  148.  ArrayList_AddBuf := Ptr;
  149. End;
  150.  
  151. Function ArrayList_SetPtr(Var Q:ArrayList;P:LongInt):Pointer;
  152. Var
  153.  WP:RBufPtr;
  154.  I:LongInt;
  155. Begin
  156.  WP:=Q.List;
  157.  For I:=1to(P)do Begin
  158.   WP:=WP^.Next;
  159.   If(WP=NIL)Then Begin
  160.    ArrayList_SetPtr:=NIL;
  161.    Exit;
  162.   End;
  163.  End;
  164.  ArrayList_SetPtr:=WP
  165. End;
  166.  
  167.  
  168. Function ArrayListIns(Var Q:ArrayList;P:LongInt;Size:Word):Pointer;
  169. Var
  170.  WP,NewP:RBufPtr;
  171.  Addr:Pointer;
  172. Begin
  173.  ArrayListIns:=NIL;
  174.  If(P>Q.Count)Then Exit;
  175.  If(P=Q.Count)Then ArrayListIns:=ArrayListAdd(Q,Size)
  176.   else
  177.  Begin
  178.   Addr:=NIL;
  179.   If P=0Then Begin
  180.    WP:=MemNew(SizeOf(Q.List^));
  181.    If(WP=NIL)Then Exit;
  182.    Q.List^.Previous:=WP;WP^.Next:=Q.List;
  183.    If Size>0Then Begin
  184.     Addr:=MemAlloc(Size);
  185.     If(Addr=NIL)Then Exit;
  186.     WP^.Buf:=Addr;WP^.Size:=Size
  187.    End;
  188.    Q.List:=WP
  189.   End
  190.    else
  191.   Begin
  192.    NewP:=MemNew(SizeOf(Q.List^));
  193.    If(NewP=NIL)Then Exit;
  194.    WP:=ArrayList_SetPtr(Q,P);
  195.    If(WP=NIL)Then Exit;
  196.    NewP^.Next:=WP;
  197.    NewP^.Previous:=WP^.Previous;
  198.    If Size>0Then Begin
  199.     Addr:=MemAlloc(Size);
  200.     If(Addr=NIL)Then Exit;
  201.     NewP^.Buf:=Addr;
  202.     NewP^.Size:=Size
  203.    End;
  204.    WP^.Previous^.Next:=NewP;
  205.    WP^.Previous:=NewP
  206.   End;
  207.   Inc(Q.Count);
  208.   ArrayListIns:=Addr
  209.  End
  210. End;
  211.  
  212. Function ArrayListInsBlock(Var Q:ArrayList;P:LongInt;Size:Word;Const Block):Boolean;
  213. Var
  214.  Ptr:Pointer;
  215. Begin
  216.  ArrayListInsBlock:=False;
  217.  Ptr:=ArrayListIns(Q,P,Size);
  218.  If(Ptr<>NIL)Then Begin
  219.   Move(Block,Ptr^,Size);
  220.   ArrayListInsBlock:=True;
  221.  End;
  222. End;
  223.  
  224.  
  225. Function ArrayListInsBuf(Var Q:ArrayList;P:LongInt;Size:Word;Var Addr:Pointer):Boolean;
  226. Var WP,NewP:RBufPtr; I:LongInt;
  227. Begin
  228.  ArrayListInsBuf := False;
  229.  If(P > Q.Count)Then Exit;
  230.  If(P = Q.Count)Then ArrayListInsBuf := ArrayListAddBuf(Q,Size,Addr)
  231.   else
  232.  Begin
  233.   ArrayListInsBuf := False;
  234.   If(P = 0)Then
  235.   Begin
  236.    WP := MemAlloc(SizeOf(Q.List^));
  237.    If(WP = NIL)Then Exit;
  238.    Q.List^.Previous := WP; WP^.Previous := NIL; WP^.Next := Q.List;
  239.    If(Size = 0)Then
  240.    Begin
  241.     WP^.Buf := NIL; WP^.Size := 0; Addr := NIL;
  242.    End
  243.     Else
  244.    Begin
  245.     Addr := MemAlloc(Size);
  246.     If(Addr = NIL)Then Exit;
  247.     WP^.Buf := Addr; WP^.Size := Size;
  248.    End;
  249.    Q.List := WP;
  250.   End
  251.    else
  252.   Begin
  253.    NewP := MemAlloc(SizeOf(Q.List^));
  254.    If(NewP = NIL)Then Exit;
  255.    WP := Q.List;
  256.    For I := 1 to P do
  257.    Begin
  258.     If(WP = NIL)Then Exit;
  259.     WP := WP^.Next;
  260.    End;
  261.    NewP^.Next := WP; NewP^.Previous := WP^.Previous;
  262.    If(Size = 0)Then
  263.    Begin
  264.     NewP^.Buf := NIL; NewP^.Size := 0; Addr := NIL;
  265.    End
  266.     Else
  267.    Begin
  268.     Addr := MemAlloc(Size);
  269.     If(Addr = NIL)Then Exit;
  270.     NewP^.Buf := Addr; NewP^.Size := Size;
  271.    End;
  272.    WP^.Previous^.Next := NewP; WP^.Previous := NewP;
  273.   End;
  274.   Inc(Q.Count); ArrayListInsBuf := True;
  275.  End;
  276. End;
  277.  
  278. Function ArrayListAddPChr(Var Q:ArrayList;PChr:PChar):Boolean;
  279. Type
  280.  TChar=Array[0..32767]of Char;
  281. Var
  282.  PBuf:^TChar;
  283.  L:Word;
  284. Begin
  285.  ArrayListAddPChr:=False;
  286.  L:=StrLen(PChr)+1;
  287.  PBuf:=ArrayListAdd(Q,L);
  288.  If(PBuf=NIL)Then Exit;
  289.  If L=1Then PBuf^[0]:=#0
  290.        Else Move(PChr^,PBuf^,L);
  291.  ArrayListAddPChr:=True
  292. End;
  293.  
  294. Function ArrayListAddPChrByte(Var Q:ArrayList;PChr:PChar;Num:Byte):Boolean;
  295. Var PCharByte:^PCharByteRec; Ptr:Pointer;
  296. Begin
  297.  ArrayListAddPChrByte := False;
  298.  If Not(ArrayListAddBuf(Q,SizeOf(PCharByteRec),Ptr))Then Exit;
  299.  PCharByte := Ptr; PCharByte^.PChr := PChr; PCHarByte^.Nm := Num;
  300.  ArrayListAddPChrByte := True;
  301. End;
  302.  
  303. Function ArrayListAddStrByte(Var Q:ArrayList;Str:String;Num:Byte):Boolean;
  304. Var StrByte:^StrByteRec; Ptr:Pointer; PChr:Array[0..255] of Char;
  305. Begin
  306.  ArrayListAddStrByte := False;
  307.  If Not(ArrayListAddBuf(Q,SizeOf(StrByteRec),Ptr))Then Exit;
  308.  StrByte := Ptr; StrPCopy(PChr,Str); StrByte^.PChr := StrNew(PChr);
  309.  StrByte^.Len := Length(Str); StrByte^.Nm := Num; ArrayListAddStrByte := True;
  310. End;
  311.  
  312. Function ArrayListAddStrWord(Var Q:ArrayList;Str:String;Num:Word):Boolean;
  313. Var StrWord:^StrWordRec; Ptr:Pointer; PChr:Array[0..255] of Char;
  314. Begin
  315.  ArrayListAddStrWord := False;
  316.  If Not(ArrayListAddBuf(Q,SizeOf(StrWordRec),Ptr))Then Exit;
  317.  StrWord := Ptr; StrPCopy(PChr,Str); StrWord^.PChr := StrNew(PChr);
  318.  StrWord^.Len := Length(Str); StrWord^.Nm := Num; ArrayListAddStrWord := True;
  319. End;
  320.  
  321. Function ArrayListAddLn(Var Q:ArrayList):Boolean;
  322. Begin
  323.  ArrayListAddLn := ArrayListAddPChr(Q,NIL);
  324. End;
  325.  
  326. Function ArrayListAddStr(Var Q:ArrayList;Const Str:String):Boolean;
  327. Var
  328.  Ptr:Pointer;
  329.  PC:PChar Absolute Ptr;
  330.  Size:Word;
  331. Begin
  332.  If Length(Str)=0Then ArrayListAddStr:=ArrayListAddLn(Q)
  333.   Else
  334.  Begin
  335.   ArrayListAddStr:=False;
  336.   Size:=Length(Str)+1;
  337.   If(Size<MinRec)Then Size:=MinRec;
  338.   Ptr:=ArrayListAdd(Q,Size);
  339.   If(Ptr=NIL)Then Exit;
  340.   StrPCopy(PC,Str);
  341.   ArrayListAddStr:=True
  342.  End;
  343. End;
  344.  
  345. Function ArrayListInsStr(Var Q:ArrayList;P:LongInt;Str:String):Boolean;
  346. Var Ptr:Pointer; PChr:PChar; Size:Word;
  347. Begin
  348.  ArrayListInsStr := False; Size := Length(Str)+1;
  349.  If(Size < 32)Then Size := 32;
  350.  If Not(ArrayListInsBuf(Q,P,Size,Ptr))Then Exit;
  351.  If(Ptr = NIL)Then Exit;
  352.  PChr := Ptr; StrPCopy(PChr,Str); ArrayListInsStr := True;
  353. End;
  354.  
  355. Function ArrayListInsStrWord(Var Q:ArrayList;P:LongInt;Str:String;Num:Word):Boolean;
  356. Var StrWord:^StrWordRec; Ptr:Pointer; PChr:Array[0..255] of Char;
  357. Begin
  358.  ArrayListInsStrWord := False;
  359.  If Not(ArrayListInsBuf(Q,P,SizeOf(StrWordRec),Ptr))Then Exit;
  360.  StrWord := Ptr; StrPCopy(PChr,Str);
  361.  StrWord^.PChr := StrNew(PChr); StrWord^.Len := Length(Str);
  362.  StrWord^.Nm := Num; ArrayListInsStrWord := True;
  363. End;
  364.  
  365. Function ArrayListAddPChrWord(Var Q:ArrayList;PChr:PChar;Num:Word):Boolean;
  366. Var PCharWord:^PCharWordRec; Ptr:Pointer;
  367. Begin
  368.  ArrayListAddPChrWord := False;
  369.  If Not(ArrayListAddBuf(Q,SizeOf(PCharWordRec),Ptr))Then Exit;
  370.  PCharWord := Ptr; PCharWord^.PChr := PChr;
  371.  PCHarWord^.Nm := Num; ArrayListAddPChrWord := True;
  372. End;
  373.  
  374. Function ArrayListGetBuf(Var Q:ArrayList;P:LongInt;Var Size:Word):Pointer;
  375. Var WP:RBufPtr; I:LongInt;
  376. Begin
  377.  Size := 0; ArrayListGetBuf := Nil;
  378.  If(P < 0)or(P >= Q.Count)Then Exit;
  379.  If(P = 0)Then
  380.  Begin
  381.   ArrayListGetBuf := Q.List^.Buf; Size := Q.List^.Size;
  382.  End
  383.   Else
  384.  Begin
  385.   WP := Q.List;
  386.   For I := 1 to P do
  387.   Begin
  388.    If(WP = NIL)Then Exit;
  389.    WP := WP^.Next;
  390.   End;
  391.   If(WP = NIL)Then Exit;
  392.   ArrayListGetBuf := WP^.Buf; Size := WP^.Size;
  393.  End;
  394. End;
  395.  
  396. Procedure ArrayListPrevious(Var Q:ArrayList);Begin
  397.  If Not(Q.CurrPtr = NIL)Then
  398.  Begin
  399.   Q.CurrPtr := Q.CurrPtr^.Previous;
  400.   Dec(Q.PCurrPtr);
  401.  End;
  402. End;
  403.  
  404. Procedure ArrayListNext(Var Q:ArrayList);Begin
  405.  If Not(Q.CurrPtr = NIL)Then
  406.  Begin
  407.   Q.CurrPtr := Q.CurrPtr^.Next;
  408.   Inc(Q.PCurrPtr);
  409.  End;
  410. End;
  411.  
  412. Procedure ArrayListSetPtr(Var Q:ArrayList;P:LongInt);
  413. Var WP:RBufPtr; I:LongInt;
  414. Begin
  415.  If(P = 0)Then
  416.  Begin
  417.   Q.PCurrPtr := 0; Q.CurrPtr := Q.List;
  418.  End
  419.   else
  420.  If Not(Q.PCurrPtr = P)Then
  421.  Begin
  422.   If(Q.PCurrPtr - 1 = P)Then ArrayListPrevious(Q) else
  423.   If(Q.PCurrPtr + 1 = P)Then ArrayListNext(Q)
  424.    else
  425.   Begin
  426.    WP := Q.List;
  427.    If(P > 0)Then For I := 1 to P do
  428.    Begin
  429.     If(WP = NIL)Then Exit;
  430.     WP := WP^.Next;
  431.    End;
  432.    If(WP = NIL)Then Exit;
  433.    Q.PCurrPtr := P;
  434.    Q.CurrPtr  := WP;
  435.   End;
  436.  End;
  437. End;
  438.  
  439. Function ArrayList_GetCurrBuf(Var Q:ArrayList):Pointer;Begin
  440.  If(Q.CurrPtr = NIL)Then ArrayList_GetCurrBuf := NIL Else ArrayList_GetCurrBuf := Q.CurrPtr^.Buf;
  441. End;
  442.  
  443. Function ArrayListGetCurrBuf(Var Q:ArrayList;Var Size:Word):Pointer;Begin
  444.  If(Q.CurrPtr = NIL)Then
  445.  Begin
  446.   ArrayListGetCurrBuf := NIL; Size := 0;
  447.  End
  448.   Else
  449.  Begin
  450.   ArrayListGetCurrBuf := Q.CurrPtr^.Buf; Size := Q.CurrPtr^.Size;
  451.  End;
  452. End;
  453.  
  454. Function ArrayList_GetBuf(Var Q:ArrayList;P:LongInt):Pointer;
  455. Var Size:Word;
  456. Begin
  457.  ArrayList_GetBuf := ArrayListGetBuf(Q,P,Size);
  458. End;
  459.  
  460. Function ArrayListGetCurrStr(Var Q:ArrayList):String;Begin
  461.  ArrayListGetCurrStr := StrPas(ArrayList_GetCurrBuf(Q));
  462. End;
  463.  
  464. Function ArrayList_GetStr(Var Q:ArrayList;P:LongInt):String;Begin
  465.  ArrayList_GetStr := StrPas(ArrayList_GetBuf(Q,P));
  466. End;
  467.  
  468. Function ArrayListRemoveAt(Var Q:ArrayList;P:LongInt):Boolean;
  469. Var WP:RBufPtr; I:LongInt;
  470. Begin
  471.  ArrayListRemoveAt := False;
  472.  If(Q.Count = 0)or(P < 0)or(P >= Q.Count)Then Exit;
  473.  If(P = 0)Then
  474.  Begin
  475.   If(Q.List = NIL)Then Exit;
  476.   WP := Q.List;
  477.   FreeMem(WP^.Buf,WP^.Size);
  478.   FreeMem(WP,SizeOf(WP^));
  479.   If(Q.Count > 1)Then
  480.   Begin
  481.    If(Q.List^.Next = NIL)Then Exit;
  482.    WP^.Next^.Previous := NIL; Q.List := Q.List^.Next; Q.CurrPtr := NIL;
  483.    Q.PCurrPtr := -1; Dec(Q.Count); ArrayListRemoveAt := True;
  484.    Exit;
  485.   End
  486.    else
  487.   Begin
  488.    Q.PCurrPtr := -1; Q.CurrPtr := NIL; Q.List := NIL; Q.EndListPtr := NIL;
  489.    Q.Count := 0; ArrayListRemoveAt := True;
  490.    Exit;
  491.   End;
  492.  End
  493.   else
  494.  Begin
  495.   WP := Q.List;
  496.   For I := 1 to P do
  497.   Begin
  498.    If(WP = NIL)Then Exit;
  499.    WP := WP^.Next;
  500.   End;
  501.   If(WP = NIL)Then Exit;
  502.   If(Q.Count - 1 = P)Then
  503.   Begin
  504.    Q.EndListPtr := WP^.Previous; WP^.Previous^.Next := NIL;
  505.   End
  506.    Else
  507.   Begin
  508.    WP^.Next^.Previous := WP^.Previous; WP^.Previous^.Next := WP^.Next;
  509.   End;
  510.   FreeMem(WP^.Buf,WP^.Size);
  511.   FreeMem(WP,SizeOf(Q.List^));
  512.   Dec(Q.Count);
  513.   ArrayListRemoveAt := True;
  514.  End;
  515. End;
  516.  
  517. Function ArrayListSetBuf(Var Q:ArrayList;P:LongInt;Size:Word;Var Addr:Pointer):Boolean;
  518. Var WP:RBufPtr; I:LongInt;
  519. Begin
  520.  ArrayListSetBuf := False;
  521.  If(P < 0)or(P > Q.Count)Then Exit;
  522.  If(P = Q.Count)Then
  523.  Begin
  524.   ArrayListSetBuf := ArrayListAddBuf(Q,Size,Addr);
  525.   Exit;
  526.  End;
  527.  If(P = 0)Then
  528.  Begin
  529.   FreeMem(Q.List^.Buf,Q.List^.Size);
  530.   If(Size = 0)Then
  531.   Begin
  532.    Q.List^.Buf := NIL; Q.List^.Size := 0;
  533.   End
  534.    Else
  535.   Begin
  536.    Addr := MemAlloc(Size);
  537.    If(Addr = NIL)Then Exit;
  538.    Q.List^.Buf := Addr; Q.List^.Size := Size;
  539.   End;
  540.   ArrayListSetBuf := True;
  541.   Exit;
  542.  End;
  543.  WP := Q.List;
  544.  For I := 1 to P do
  545.  Begin
  546.   If(WP = NIL)Then Exit;
  547.   WP := WP^.Next;
  548.  End;
  549.  FreeMem(WP^.Buf,WP^.Size);
  550.  If(Size = 0)Then
  551.  Begin
  552.   WP^.Buf := NIL; WP^.Size := 0;
  553.  End
  554.   Else
  555.  Begin
  556.   Addr := MemAlloc(Size);
  557.   If(Addr = NIL)Then Exit;
  558.   WP^.Buf := Addr; WP^.Size := Size;
  559.  End;
  560.  ArrayListSetBuf := True;
  561. End;
  562.  
  563. Function ArrayList_SetBuf(Var Q:ArrayList;P:LongInt;Size:Word):Pointer;
  564. Var Ptr:Pointer;
  565. Begin
  566.  ArrayList_SetBuf := NIL;
  567.  If Not(ArrayListSetBuf(Q,P,Size,Ptr))Then Exit;
  568.  ArrayList_SetBuf := Ptr;
  569. End;
  570.  
  571. Function ArrayListCount(Var Q:ArrayList):LongInt;Begin
  572.  ArrayListCount := Q.Count;
  573. End;
  574.  
  575. Function ArrayListMaxList(Var Q:ArrayList):LongInt;Begin
  576.  ArrayListMaxList := Q.Count - 1;
  577. End;
  578.  
  579. Procedure ArrayListPopCurrPtr(Var Q:ArrayList;Addr:Pointer);Begin
  580.  Q.CurrPtr:=Addr;
  581. End;
  582.  
  583. Procedure ArrayListDone(Var Q:ArrayList);
  584. Var WP:RBufPtr; Ptr:^StrByteRec;
  585. Begin
  586.  WP := Q.List;
  587.  While Not(WP = NIL) do
  588.  Begin
  589.   If(WP^.Size = SizeOf(StrByteRec))Then
  590.   Begin
  591.    Ptr := WP^.Buf;
  592.    StrDispose(Ptr^.PChr);
  593.   End;
  594.   FreeMem(WP^.Buf,WP^.Size);
  595.   FreeMem(WP,SizeOf(RBufRec));
  596.   WP := WP^.Next;
  597.  End;
  598. End;
  599.  
  600. Var
  601.  Language:(_French,_English,_Germany,_Italian,_Spain);
  602.  TmpLanguage:String;
  603.  CommandFound,Terminated:Boolean;
  604.  CmdStr:String;
  605.  CurrCommand,ParamList:String;
  606.  List:ArrayList;
  607.  I:Integer;
  608.  P:LongInt;
  609.  PX:LongInt;
  610.  J,X,Y:Byte;
  611.  InsMode,Modified:Boolean;
  612.  CurrPtr:Pointer;
  613.  FileName:String;
  614.  
  615. Function RTrim(s:String):String;
  616. Var
  617.  i:Integer;
  618. Begin
  619.  i:=Length(s);
  620.  While (i>0)and(s[i]in[#9,' '])do Dec(i);
  621.  s[0]:=Chr(i);
  622.  RTrim:=S;
  623. End;
  624.  
  625.  
  626. Function LTrim(S:String):String;
  627. Var
  628.  I:Byte;
  629. Begin
  630.  For I:=1to Length(S)do Begin
  631.   If S[I]<>' 'Then Begin
  632.    LTrim:=Copy(S,I,255);
  633.    Exit;
  634.   End;
  635.  End;
  636.  LTrim:=S;
  637. End;
  638.  
  639. Function StrToUpper(S:String):String;
  640. Var
  641.  I:Byte;
  642. Begin
  643.  For I:=1 to Length(S)do Begin
  644.   If S[I] in['a'..'z']Then S[I]:=Chr(Ord(S[I])-32);
  645.  End;
  646.  StrToUpper:=S;
  647. End;
  648.  
  649. Procedure LoadText(FileName:String);
  650. Var
  651.  TextFile:Text;
  652.  CurrLine:String;
  653. Begin
  654.  Assign(TextFile,FileName);
  655.  {$I-}Reset(TextFile);{$I+}
  656.  If IOResult=0Then Begin
  657.   While Not EOF(TextFile) do Begin
  658.    ReadLn(TextFile,CurrLine);
  659.    ArrayListAddStr(List,CurrLine);
  660.   End;
  661.   Close(TextFile);
  662.  End;
  663. End;
  664.  
  665. Procedure SaveText(FileName:String);
  666. Var
  667.  TextFile:Text;
  668.  CurrLine:String;
  669.  I:LongInt;
  670. Begin
  671.  Assign(TextFile,FileName);
  672.  Rewrite(TextFile);
  673.  If Not ArrayListIsEmpty(List)Then Begin
  674.   ArrayListSetPtr(List,0);
  675.   For I:=1 to ArrayListCount(List)do Begin
  676.    CurrLine:=ArrayListGetCurrStr(List);
  677.    WriteLn(TextFile,CurrLine);
  678.    ArrayListNext(List);
  679.   End;
  680.  End;
  681.  Close(TextFile);
  682. End;
  683.  
  684. Function TEPopCurr:PChar;Begin
  685.  ArrayListPopCurrPtr(List,CurrPtr);
  686.  TEPopCurr:=ArrayList_GetCurrBuf(List)
  687. End;
  688.  
  689. Procedure ShowPrompt;Begin
  690.  Write('*');
  691. End;
  692.  
  693. Procedure ExtractCommand;
  694. Var
  695.  I:Byte;
  696. Begin
  697.  CurrCommand:='';
  698.  ParamList:='';
  699.  For I:=1 to Length(CmdStr)do Begin
  700.   If(CmdStr[I]in['A'..'Z','a'..'z'])Then Begin
  701.    CurrCommand:=StrToUpper(Copy(CmdStr,I,255));
  702.    ParamList:=RTrim(LTrim(Copy(CmdStr,1,I-1)));
  703.    Exit;
  704.   End;
  705.  End;
  706.  ParamList:=CmdStr;
  707. End;
  708.  
  709. Procedure ACommand;Begin
  710.  WriteLn('Cette commande n''est pas mise en oeuvre');
  711. End;
  712.  
  713. Procedure CCommand;Begin
  714.  WriteLn('Cette commande n''est pas mise en oeuvre');
  715. End;
  716.  
  717. Procedure DCommand;Begin
  718.  WriteLn('Cette commande n''est pas mise en oeuvre');
  719. End;
  720.  
  721. Procedure ECommand;Begin
  722.  WriteLn('Cette commande n''est pas mise en oeuvre');
  723. End;
  724.  
  725. Procedure ICommand;
  726. Var
  727.  Value:LongInt;
  728.  Err:Word;
  729.  CurrLine:String;
  730. Begin
  731.  If ParamList<>''Then Begin
  732.   Val(ParamList,Value,Err);
  733.   If Err>0Then WriteLn('Numero de ligne invalide');
  734.   If Value>=List.Count Then WriteLn('La ligne n''existe pas')Else
  735.   If Value=0Then WriteLn('La ligne doit etre superieur a 0')
  736.    Else
  737.   Begin
  738.    P:=Value-1;
  739.    ArrayListSetPtr(List,P);
  740.   End;
  741.  End;
  742.  Repeat
  743.   Write(' ':5,P+1,':');
  744.   ReadLn(CurrLine);
  745.   If CurrLine<>^Z Then Begin
  746.    If P<List.Count Then Begin
  747.      If Not ArrayListInsStr(List,P,CurrLine)Then Begin
  748.       WriteLn('Manque de memoire');
  749.      End;
  750.    End
  751.     Else
  752.    ArrayListAddStr(List,CurrLine);
  753.    Inc(P);
  754.   End;
  755.   If CurrLine=#3Then Break;
  756.  Until CurrLine=^Z;
  757. End;
  758.  
  759. Procedure LCommand;
  760. Var
  761.  I:LongInt;
  762.  CurrLine:String;
  763. Begin
  764.  ArrayListSetPtr(List,0);
  765.  If Not ArrayListIsEmpty(List)Then For I:=0 to ArrayListMaxList(List) do Begin
  766.   CurrLine:=ArrayListGetCurrStr(List);
  767.   WriteLn(' ':5,I+1,':',CurrLine);
  768.   ArrayListNext(List);
  769.  End;
  770. End;
  771.  
  772. Procedure MCommand;Begin
  773.  WriteLn('Cette commande n''est pas mise en oeuvre');
  774. End;
  775.  
  776. Procedure PCommand;Begin
  777.  WriteLn('Cette commande n''est pas mise en oeuvre');
  778. End;
  779.  
  780. Procedure QCommand;Begin
  781.  Write('Annule l''edition (O/N) ?');
  782.  If ReadKey in['Y','y','O','o']Then Begin
  783.   WriteLn('Oui');
  784.   Terminated:=True;
  785.  End
  786.    Else
  787.  WriteLn('Non');
  788. End;
  789.  
  790. Procedure RCommand;Begin
  791.  WriteLn('Cette commande n''est pas mise en oeuvre');
  792. End;
  793.  
  794. Procedure SCommand;Begin
  795.  WriteLn('Cette commande n''est pas mise en oeuvre');
  796. End;
  797.  
  798. Procedure TCommand;Begin
  799.  WriteLn('Cette commande n''est pas mise en oeuvre');
  800. End;
  801.  
  802. Procedure WCommand;Begin
  803.  SaveText(FileName);
  804. End;
  805.  
  806. Procedure EditLineCommand;
  807. Var
  808.  Value:LongInt;
  809.  Err:Word;
  810.  Ptr:PChar;
  811.  CurrLine:String;
  812. Begin
  813.  Val(ParamList,Value,Err);
  814.  If Err>0Then WriteLn('Numero de ligne invalide');
  815.  If Value>=List.Count Then WriteLn('La ligne n''existe pas')Else
  816.   If Value=0Then WriteLn('La ligne doit etre superieur a 0')
  817.   Else
  818.  Begin
  819.   P:=Value-1;
  820.   ArrayListSetPtr(List,P);
  821.   CurrLine:=ArrayListGetCurrStr(List);
  822.   WriteLn(' ':5,Value,':',CurrLine);
  823.   Write(' ':5,Value,':');
  824.   ReadLn(CurrLine);
  825.   Ptr:=ArrayList_SetBuf(List,P,Length(CurrLine)+1);
  826.   StrPCopy(Ptr,CurrLine);
  827.   Inc(P);
  828.  End;
  829. End;
  830.  
  831. Procedure UnknownCommand;Begin
  832.  WriteLn('Commande non reconnu');;
  833.  WriteLn;
  834. End;
  835.  
  836. BEGIN
  837.  {$IFDEF FPC}
  838.   {$IFDEF WINDOWS}
  839.    SetUseACP(False);
  840.   {$ENDIF}
  841.  {$ENDIF}
  842.  Language:=_French;
  843.  TmpLanguage:=GetEnv('LANGUAGE');
  844.  If TmpLanguage<>''Then Begin
  845.   If TmpLanguage[1]='"'Then TmpLanguage:=Copy(TmpLanguage,2,255);
  846.   If StrToUpper(Copy(TmpLanguage,1,2))='EN'Then Language:=_English Else
  847.   If StrToUpper(Copy(TmpLanguage,1,2))='GR'Then Language:=_Germany Else
  848.   If StrToUpper(Copy(TmpLanguage,1,2))='IT'Then Language:=_Italian Else
  849.   If StrToUpper(Copy(TmpLanguage,1,2))='SP'Then Language:=_Spain;
  850.  End;
  851.  If(ParamStr(1)='/?')or(ParamStr(1)='--help')or(ParamStr(1)='-h')Then Begin
  852.   Case Language of
  853.    _Germany:Begin
  854.     WriteLn('Startet den EDLIN, der ASCII-Dateien erzeugt und ver"ndert.');
  855.     WriteLn;
  856.     WriteLn('EDLIN');
  857.    End;
  858.    Else Begin
  859.     WriteLn('EDLIN : Cette commande permet d''editer un fichier texte ASCII.');
  860.     WriteLn;
  861.     WriteLn('Syntaxe : EDLIN [nomdufichier]');
  862.    End;
  863.   End;
  864.  End
  865.   Else
  866.  Begin
  867.   Modified:=False;
  868.   InsMode:=True;
  869.   P:=0;PX:=0;
  870.   X:=0;Y:=0;
  871.   FileName:='';
  872.   For I:=1 to ParamCount do Begin
  873.    FileName:=ParamStr(I);
  874.   End;
  875.   ArrayListInit(List);
  876.   If FileName<>''Then Begin
  877.    LoadText(FileName);
  878.    P:=List.Count-1;
  879.   End;
  880.   Terminated:=False;
  881.   Repeat
  882.    ShowPrompt;
  883.    ReadLn(CmdStr);
  884.    ExtractCommand;
  885.    CommandFound:=False;
  886.    For J:=Low(CommandList) to High(CommandList) do Begin
  887.     If CurrCommand=CommandList[J]Then Begin
  888.      Case(J)of
  889.       0:ACommand;
  890.       1:CCommand;
  891.       2:DCommand;
  892.       3:ECommand;
  893.       4:ICommand;
  894.       5:LCommand;
  895.       6:MCommand;
  896.       7:PCommand;
  897.       8:QCommand;
  898.       9:RCommand;
  899.       10:SCommand;
  900.       11:TCommand;
  901.       12:WCommand;
  902.      End;
  903.      If J<=High(CommandList)Then Begin
  904.       CommandFound:=True;
  905.       WriteLn;
  906.       Break;
  907.      End;
  908.     End;
  909.    End;
  910.    If(ParamList<>'')and(CurrCommand='')Then EditLineCommand Else
  911.    If Not(CommandFound)Then UnknownCommand;
  912.   Until Terminated;
  913.   ArrayListDone(List);
  914.  End;
  915. END.

Liste chaînée de blocs de lignes (buffer gap)

La méthode de la liste chaînée de blocs de lignes, souvent implémentée via un gap buffer, est une structure intermédiaire très populaire dans les éditeurs de texte interactifs. Elle repose sur un tableau de caractères accompagné d'un "espace vide" (le gap) qui se déplace en fonction de la position du curseur. Ce vide permet d'insérer ou de supprimer des caractères à l'emplacement courant sans devoir déplacer l'ensemble du texte. La liste chaînée vient ici organiser ces blocs, chacun contenant une portion de texte ou un segment logique.

Chaque bloc de la liste représente un fragment du texte, ce qui permet de manipuler de grandes zones sans devoir réallouer toute la mémoire. Lorsqu'une modification est effectuée loin du gap actuel, le système déplace le gap à la position du curseur en copiant temporairement du texte, puis applique l'opération. Cette stratégie optimise fortement les performances pour les modifications locales fréquentes, comme la frappe continue ou les corrections. Elle équilibre bien rapidité, souplesse et gestion mémoire dans les éditeurs modernes.

Le gap buffer est utilisé dans de nombreux éditeurs légers ou intégrés à des IDEs, notamment dans les premières versions de GNU Emacs ou des éditeurs en mode console. Sa force réside dans sa capacité à offrir une expérience fluide même sur des systèmes modestes. Cependant, il devient moins efficace pour les textes très volumineux ou les modifications fréquentes dispersées. Dans ces cas, il est parfois combiné à d'autres structures comme des arbres de segments pour gagner en évolutivité. Cela en fait un excellent compromis pour les éditeurs temps réel avec édition locale.

Liste symétrique de listes de caractères

La liste symétrique de listes de caractères est une structure hiérarchique dans laquelle chaque élément de la liste principale représente une ligne de texte, et chaque ligne est elle-même une sous-liste de caractères. Cette approche offre une flexibilité maximale pour les opérations localisées, car on peut modifier une ligne sans affecter les autres. Elle permet également d'implémenter finement des fonctionnalités comme l'annulation par ligne, le repli de code, ou encore la coloration syntaxique ligne par ligne. La structure reste cependant plus lourde en gestion mémoire.

Ce modèle est particulièrement utile pour les éditeurs où les lignes sont des unités de traitement indépendantes, comme dans les environnements de programmation. Par exemple, on peut supprimer ou insérer une ligne complète simplement en manipulant la liste externe, tandis que les modifications à l'intérieur d'une ligne ne concernent que la sous-liste locale. Cela favorise une conception modulaire du traitement du texte. Toutefois, la navigation globale ou le rendu du document complet peuvent devenir coûteux si les opérations nécessitent de parcourir toutes les sous-listes.

L'inconvénient principal de cette méthode réside dans la gestion des nombreuses petites allocations mémoire, ce qui peut affecter la performance sur de très grands documents. De plus, la complexité structurelle implique un code plus difficile à maintenir que les approches linéaires ou à blocs. Malgré cela, ce modèle trouve sa place dans des contextes où l'édition structurée et localisée est prioritaire. Il peut aussi être utile dans les environnements collaboratifs ou pour les éditeurs spécialisés dans les langages à indentation significative, comme Python ou comme des applications comme le MonsterBook. Sa précision dans la manipulation de texte ligne par ligne est sa grande force.

Arbre de segments (rope)

La structure de l'arbre de segments, aussi connue sous le nom de rope, est spécialement conçue pour les éditeurs de texte devant gérer des documents volumineux et des modifications fréquentes. Elle représente le texte comme un arbre binaire équilibré, où chaque noeud interne contient la longueur totale de ses fils gauches, et les feuilles contiennent les chaînes de caractères. Cette organisation permet un accès rapide à n'importe quel caractère, tout en facilitant les insertions, suppressions ou découpages. Contrairement aux tableaux ou listes simples, la rope n'a pas besoin de déplacer de grandes portions de texte en mémoire.

L'un des grands atouts des ropes est leur capacité à gérer des textes de plusieurs mégaoctets, voire gigaoctets, sans perte significative de performance. Par exemple, une insertion au milieu du texte ne nécessite pas de copier tout le contenu : il suffit de réorganiser quelques nouds dans l'arbre. Cette approche offre donc une très bonne complexité algorithmique, souvent logarithmique pour les opérations courantes. Elle est utilisée dans des éditeurs avancés ou dans des systèmes collaboratifs où la stabilité des pointeurs et la performance sont critiques. Toutefois, elle nécessite une gestion soignée de l'équilibrage et de la mémoire.

Le principal inconvénient de cette méthode réside dans sa complexité de mise en ouvre. L'arbre doit rester équilibré pour offrir de bonnes performances, ce qui implique une gestion dynamique sophistiquée. En outre, les opérations d'affichage peuvent être plus lentes si l'arbre contient de nombreuses petites feuilles, car il faut parcourir plus de noeuds. Malgré cela, les ropes restent une solution très performante pour les éditeurs de texte professionnels, notamment ceux embarquant des fonctionnalités comme le défilement fluide, l'annulation multigranulaire, ou la collaboration en temps réel. Elles constituent une fondation robuste pour les outils nécessitant une scalabilité élevée.

Tampon circulaire (ring buffer)

Le tampon circulaire (ou ring buffer) est une structure de données utilisée dans certains éditeurs de texte pour gérer des flux de caractères de manière continue dans une mémoire de taille fixe. Il fonctionne comme un tableau circulaire : lorsqu'on atteint la fin, on revient au début. Ce type de tampon est souvent utilisé pour gérer des historiques, des journaux, ou des zones de texte temporaire comme les undo buffers (tampons d'annulation). Il permet une lecture et une écriture rapides sans réallocation mémoire.

Dans le contexte d'un éditeur de texte, un ring buffer est rarement utilisé comme structure principale d'entreposage du texte, mais plutôt comme composante auxiliaire. Par exemple, il peut enregistrer les dernières frappes clavier pour des fonctionnalités d'annulation, ou stocker les blocs de texte copiés/coupés dans le presse-papiers interne. Son avantage réside dans sa simplicité et sa rapidité en lecture/écriture séquentielle, surtout pour des zones à taille constante. Toutefois, il n'est pas adapté aux modifications aléatoires dans le document principal, car il ne gère pas bien l'insertion ou la suppression arbitraires.

Cette structure est également utile dans les éditeurs conçus pour des systèmes embarqués ou des terminaux à ressources limitées, où il faut éviter les allocations dynamiques complexes. Elle peut aussi jouer un rôle dans les communications asynchrones au sein d'un éditeur (exemple : entre moteur de rendu et interface utilisateur). Bien que peu utilisée pour entreposer le corps du texte lui-même, sa présence dans les couches internes d'un éditeur renforce la fluidité des interactions utilisateur. En résumé, le ring buffer est un outil complémentaire plus qu'un moteur principal de représentation textuelle.

Mémoire paginée ou segmentée

La mémoire paginée ou segmentée est une approche de conception qui divise le contenu textuel en portions fixes appelées pages ou segments. Chaque page contient une partie du document, et seule une portion est chargée en mémoire à la fois. Cette méthode est particulièrement adaptée aux éditeurs de texte destinés à gérer de très gros fichiers, dépassant largement la capacité de la mémoire vive. Elle permet de travailler sur un texte sans avoir à l'intégrer complètement en mémoire, ce qui évite les ralentissements ou erreurs de dépassement.

Lorsqu'un utilisateur fait défiler ou modifie le texte, les segments concernés sont chargés dynamiquement, tandis que les autres peuvent être libérés ou temporairement entreposés sur disque. Cela ressemble au fonctionnement des systèmes de mémoire virtuelle d'un système d'exploitation. Ce type de structure nécessite un système efficace de gestion de cache pour minimiser les lectures/écritures disque, surtout si le support est lent. Il est aussi courant d'ajouter une indexation des lignes pour accéder rapidement aux pages concernées. C'est une solution performante mais plus complexe à mettre en ouvre.

On retrouve cette méthode dans des éditeurs professionnels ou historiques comme Brief, IBM XEDIT ou certains éditeurs hexadécimaux, qui devaient manipuler des fichiers binaires ou textes de plusieurs mégaoctets à une époque où la RAM était très limitée. Aujourd'hui encore, des variantes de cette approche sont utilisées dans des éditeurs modernes comme Sublime Text ou VSCode, optimisant l'ouverture rapide de gros fichiers. En résumé, la mémoire paginée/segmentée permet une mise à l'échelle importante, au prix d'une gestion mémoire plus sophistiquée et de mécanismes de préchargement intelligents.

Des cadres d'application comme Turbo Vision, utilisés pour les IDE Borland en mode texte, géraient certains écrans ou fichiers en mémoire segmentée, avec des tampons adaptés à la taille de segments DOS (généralement 16 Ko ou 64 Ko). Ainsi, bien que Borland ne parlait pas explicitement de "pagination mémoire" dans ses documentations, ses produits incorporaient des concepts voisins pour contourner les limitations matérielles de l'époque. Ces techniques étaient très proches des modèles que l'on associe aujourd'hui aux éditeurs capables de charger de gros fichiers de manière partielle.

Fichier cartographié en mémoire (memory-mapped file)

La méthode du fichier cartographié en mémoire (memory-mapped file) consiste à lier directement un fichier sur disque à une portion d'espace mémoire, de sorte que le système d'exploitation gère l'accès aux données comme si elles étaient en RAM. Dans un éditeur de texte, cette approche permet de manipuler de très gros fichiers sans les charger entièrement en mémoire. Le texte est accessible comme un tableau en mémoire, mais seul ce qui est nécessaire est réellement lu ou écrit, à la demande. Cela réduit l'usage mémoire tout en conservant d'excellentes performances.

Cette technique est particulièrement efficace dans les systèmes modernes qui disposent de mémoire virtuelle et de gestion fine des pages. Elle repose sur des appels système comme mmap sous Unix/Linux ou CreateFileMapping sous Windows. L'éditeur n'a pas besoin d'allouer manuellement des tampons ni de gérer lui-même les lectures/écritures disque : le système d'exploitation s'en occupe en arrière-plan. Cela simplifie aussi le code de gestion du texte, tout en permettant des recherches, affichages et éditions très rapides. Elle est souvent utilisée dans des éditeurs comme Sublime Text, Kate, ou certains outils de développement bas-niveau.

Cependant, cette méthode n'est pas sans limites. Les systèmes de fichiers ou les OS plus anciens ne la prennent pas toujours en charge, ou la limitent en taille ou en granularité. De plus, une mauvaise gestion des accès concurrents ou des modifications directes dans le fichier mappé peut entraîner des comportements imprévisibles ou corrompre le contenu. Il est également nécessaire de gérer correctement la synchronisation pour s'assurer que les modifications sont bien écrites sur disque. Malgré cela, pour les éditeurs modernes à hautes performances, le fichier cartographié en mémoire reste une solution de choix.

Vecteur de pointeurs sur lignes

La méthode du vecteur de pointeurs sur lignes consiste à entreposer les lignes de texte dans des zones mémoire indépendantes et à les référencer via un tableau dynamique de pointeurs. Chaque entrée du vecteur pointe vers une chaîne représentant une ligne complète. Cela permet un accès direct et rapide à n'importe quelle ligne par son indice, ce qui est particulièrement utile pour les fonctions comme "aller à la ligne", "afficher ligne n", ou l'édition structurée. Cette méthode offre un bon compromis entre vitesse d'accès et flexibilité mémoire.

L'un des grands avantages de cette approche est la simplicité des opérations sur les lignes : pour insérer ou supprimer une ligne, il suffit de manipuler le vecteur en ajoutant ou retirant un pointeur. Le contenu des lignes étant entreposé séparément, il n'est pas nécessaire de déplacer tout le texte en mémoire. Cela rend aussi les modifications localisées très efficaces, car seule la ligne modifiée est réallouée. Le vecteur lui-même peut être redimensionné dynamiquement, ce qui permet une grande souplesse dans la gestion des documents de taille variable.

Cependant, cette méthode n'est pas optimale pour les recherches ou modifications sur de très grandes portions de texte d'un seul tenant, car elle ne prend pas en charge la continuité mémoire. De plus, l'allocation séparée de chaque ligne peut engendrer une fragmentation de la mémoire, notamment pour des fichiers avec beaucoup de lignes courtes. Malgré cela, ce modèle est très utilisé dans des éditeurs structurés ou des outils de développement, car il favorise la clarté du code, la facilité de navigation, et l'intégration d'outils de traitement ligne par ligne comme la numérotation ou le pliage.

Modèle basé sur un tampon de modification (diff buffer)

Le modèle basé sur un tampon de modification (ou diff buffer) repose sur une idée simple : au lieu de modifier directement le texte original, on conserve une trace des différences (insertion, suppression, remplacement) dans une structure à part. L'éditeur travaille donc avec une vue virtuelle du texte, combinant l'original et les modifications enregistrées. Cela permet un gain de performance important, car seule une portion du texte est réellement modifiée, et les opérations sont différées. Ce modèle est très utile pour gérer l'annulation, les comparaisons ou le travail collaboratif.

Le tampon de modification fonctionne souvent sous forme d'une liste de "patchs" ou de blocs de modifications indexés par position. Lorsqu'un affichage ou une sauvegarde est demandé, l'éditeur reconstruit le texte en appliquant dynamiquement ces patchs à l'état de base. Cela réduit le coût des écritures et optimise la mémoire pour les grands fichiers ou les systèmes à ressources limitées. L'un des bénéfices majeurs est la possibilité d'implémenter un historique complet des modifications, utile pour des fonctionnalités comme l'annulation multi-niveaux ou la comparaison de versions.

Ce modèle est particulièrement adapté aux éditeurs de texte intégrés à des outils de versionnement, de traitement différentiel ou d'édition collaborative. Il permet aussi une certaine immutabilité du texte de base, ce qui peut être crucial dans des environnements de développement multi-utilisateurs ou pour garantir la sécurité des fichiers. Toutefois, sa complexité augmente avec la multiplication des modifications, et il peut nécessiter un recalcul ou une consolidation périodique des différences. Il s'agit donc d'un système puissant, mais qui demande une gestion fine pour rester performant à grande échelle.

L'éditeur de texte Epsilon, conçu pour les programmeurs, utilise un modèle basé sur un tampon de modification (diff buffer) afin d'optimiser les performances d'édition. Au lieu de modifier directement le texte source, Epsilon enregistre les modifications dans un tampon distinct, ce qui permet de reconstruire dynamiquement l'état actuel du fichier à partir de l'original et des changements successifs. Ce mécanisme facilite l'annulation multiple, accélère l'édition et limite la fragmentation mémoire. Ce modèle est particulièrement adapté aux tâches de programmation exigeant souplesse et robustesse dans la gestion des modifications.



Dernière mise à jour : Mercredi, le 26 avril 2017