aboutsummaryrefslogtreecommitdiffstats
path: root/src/files_map-editor.adb
blob: a1f673e8317502429c4ad5cc9c3af26ae5d028be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package body Files_Map.Editor is
   --  Compute the number of character in FILE between [START_POS; END_POS)
   function Get_Range_Length (File : Source_File_Entry;
                              Start_Pos : Source_Ptr;
                              End_Pos : Source_Ptr) return Source_Ptr
   is
      pragma Assert (File <= Source_Files.Last);
      F : Source_File_Record renames Source_Files.Table (File);
      Res : Source_Ptr;
   begin
      pragma Assert (End_Pos >= Start_Pos);
      pragma Assert (End_Pos <= F.File_Length);
      Res := End_Pos - Start_Pos;

      --  Returns now if the gap is outside the range.
      if F.Gap_Last < Start_Pos or else F.Gap_Start >= End_Pos then
         return Res;
      end if;

      --  Check the gap is completly within the range.
      if F.Gap_Last > End_Pos or else F.Gap_Start <= Start_Pos then
         raise Internal_Error;
      end if;

      return Res - (F.Gap_Last - F.Gap_Start + 1);
   end Get_Range_Length;

   --  Move the gap to the end of LINE.
   procedure Move_Gap (File : Source_File_Entry;
                       Line : Positive)
   is
      use Lines_Tables;

      F : Source_File_Record renames Source_Files.Table (File);
      New_Start : Source_Ptr;
      Gap_Len : Source_Ptr;
      Diff : Source_Ptr;
   begin
      if Line = Lines_Tables.Last (F.Lines) then
         New_Start := F.File_Length;
         if New_Start = F.Gap_Start then
            --  No move.
            return;
         end if;
      else
         New_Start := Line_To_Position (File, Line + 1);
         if New_Start = F.Gap_Last + 1 then
            --  No move (the gap is already at end of LINE).
            return;
         end if;
      end if;

      Gap_Len := F.Gap_Last - F.Gap_Start + 1;

      if New_Start < F.Gap_Start then
         --  The gap is moved toward the start of the file by DIFF bytes:
         --     |   [A][XXXX]    |
         --  => |   [XXXX][B]    |
         Diff := F.Gap_Start - New_Start;
         --  Move [A] to [B].
         F.Source (F.Gap_Last - Diff + 1 .. F.Gap_Last) :=
           F.Source (New_Start .. New_Start + Diff - 1);
         --  Renumber
         --  Lines starting from line + 1 until location < Gap_Start should
         --  have their location added by gap_len.
         for L in Line + 1 .. Last (F.Lines) loop
            exit when F.Lines.Table (L) >= F.Gap_Start;
            F.Lines.Table (L) := F.Lines.Table (L) + Gap_Len;
         end loop;
      else
         --  The gap is moved toward the end of the file by DIFF bytes.
         --     |   [XXXX][A]    |
         --  => |   [B][XXXX]    |
         Diff := New_Start - F.Gap_Start;
         --  Move [A] to [B].
         F.Source (F.Gap_Start .. F.Gap_Start + Diff - 1) :=
           F.Source (F.Gap_Last + 1 .. F.Gap_Last + 1 + Diff - 1);
         --  Renumber
         --  Lines starting from LINE downto location > Gap_Start should have
         --  their location substracted by gap_len.
         for L in reverse 1 .. Line loop
            exit when F.Lines.Table (L) <= F.Gap_Start;
            F.Lines.Table (L) := F.Lines.Table (L) - Gap_Len;
         end loop;
      end if;

      --  Adjust gap.
      F.Gap_Start := New_Start;
      F.Gap_Last := New_Start + Gap_Len - 1;
   end Move_Gap;

   procedure Replace_Text (File : Source_File_Entry;
                           Start_Line : Positive;
                           Start_Off  : Natural;
                           End_Line   : Positive;
                           End_Off    : Natural;
                           Text : String)
   is
      pragma Assert (File <= Source_Files.Last);
      F : Source_File_Record renames Source_Files.Table (File);
      Start_Pos : constant Source_Ptr :=
        Line_To_Position (File, Start_Line) + Source_Ptr (Start_Off);
      End_Pos : constant Source_Ptr :=
        Line_To_Position (File, End_Line) + Source_Ptr (End_Off);
      Text_Size : constant Source_Ptr := Text'Length;
      Gap_Size : Source_Ptr;
      Range_Size : Source_Ptr;
   begin
      Gap_Size := F.Gap_Last - F.Gap_Start + 1;
      Range_Size := Get_Range_Length (File, Start_Pos, End_Pos);

      --  Check there is enough space.
      if Text_Size > Gap_Size + Range_Size then
         raise Constraint_Error;
      end if;

      --  Move gap
      Move_Gap (File, End_Line);

      --  Replace text, handle new lines.
      --  Deletion.
      --     End_Pos --|    |---- Gap_Start
      --    |   [ABCDEFGHn][XXX]  |
      -- => |   [ABRGHn][XXXXXX]  |
      --           |-- Start_Pos
      --
      --  Insertion
      --   End_Pos --|   |---- Gap_Start
      --      |  [ABCDn][XXXXX] |
      --  =>  |  [ABRRRDn][XXX] |
      --            |-- Start_Pos
      declare
         Move_Len : constant Source_Ptr := F.Gap_Start - End_Pos;
         New_Pos  : constant Source_Ptr := Start_Pos + Text_Size;
      begin
         F.Source (New_Pos .. New_Pos + Move_Len - 1) :=
           F.Source (End_Pos .. End_Pos + Move_Len - 1);
         --  FIXME: clear gap when extended.
         F.Gap_Start := New_Pos + Move_Len;
         --  Copy.
         F.Source (Start_Pos .. Start_Pos + Text_Size - 1) :=
           File_Buffer (Text);
      end;

      --  Renumber.
      if Start_Line /= End_Line then
         --  Not handled.
         --  FIXME: also handle newlines in TEXT.
         raise Internal_Error;
      end if;
   end Replace_Text;

   procedure Replace_Text_Ptr (File : Source_File_Entry;
                               Start_Line : Positive;
                               Start_Off  : Natural;
                               End_Line   : Positive;
                               End_Off    : Natural;
                               Str : Thin_String_Ptr;
                               Str_Len : Natural) is
   begin
      Replace_Text (File, Start_Line, Start_Off, End_Line, End_Off,
                    Str (1 .. Str_Len));
   end Replace_Text_Ptr;

   procedure Set_Gap (File : Source_File_Entry;
                      First : Source_Ptr;
                      Last : Source_Ptr)
   is
      pragma Assert (File <= Source_Files.Last);
      F : Source_File_Record renames Source_Files.Table (File);
   begin
      F.Gap_Start := First;
      F.Gap_Last := Last;
   end Set_Gap;
end Files_Map.Editor;