1 /*
2 * Copyright 2013, Haiku, Inc. All rights reserved.
3 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
4 * Distributed under the terms of the MIT License.
5 *
6 * Authors:
7 * Ingo Weinhold, ingo_weinhold@gmx.de
8 * Siarzhuk Zharski, zharik@gmx.li
9 */
10
11 #include "HistoryBuffer.h"
12
13 #include <new>
14
15 #include <OS.h>
16
17 #include "TermConst.h"
18
19
HistoryBuffer()20 HistoryBuffer::HistoryBuffer()
21 :
22 fLines(NULL),
23 fWidth(0),
24 fCapacity(0),
25 fNextLine(0),
26 fSize(0),
27 fBuffer(NULL),
28 fBufferSize(0),
29 fBufferAllocationOffset(0)
30 {
31 }
32
33
~HistoryBuffer()34 HistoryBuffer::~HistoryBuffer()
35 {
36 delete[] fLines;
37 delete[] fBuffer;
38 }
39
40
41 status_t
Init(int32 width,int32 capacity)42 HistoryBuffer::Init(int32 width, int32 capacity)
43 {
44 if (width <= 0 || capacity <= 0)
45 return B_BAD_VALUE;
46
47 int32 bufferSize = (width + 4) * capacity;
48
49 if (capacity > 0) {
50 fLines = new(std::nothrow) HistoryLine[capacity];
51 fBuffer = new(std::nothrow) uint8[bufferSize];
52
53 if (fLines == NULL || fBuffer == NULL)
54 return B_NO_MEMORY;
55 }
56
57 fWidth = width;
58 fCapacity = capacity;
59 fNextLine = 0;
60 fSize = 0;
61 fBufferSize = bufferSize;
62 fBufferAllocationOffset = 0;
63
64 return B_OK;
65 }
66
67
68 void
Clear()69 HistoryBuffer::Clear()
70 {
71 fNextLine = 0;
72 fSize = 0;
73 fBufferAllocationOffset = 0;
74 }
75
76
77 TerminalLine*
GetTerminalLineAt(int32 index,TerminalLine * buffer) const78 HistoryBuffer::GetTerminalLineAt(int32 index, TerminalLine* buffer) const
79 {
80 HistoryLine* line = LineAt(index);
81 if (line == NULL)
82 return NULL;
83
84 int32 charCount = 0;
85 const char* chars = line->Chars();
86 buffer->length = 0;
87 Attributes attributes;
88 AttributesRun* attributesRun = line->AttributesRuns();
89 int32 attributesRunCount = line->attributesRunCount;
90 int32 nextAttributesAt = attributesRunCount > 0
91 ? attributesRun->offset : INT_MAX;
92
93 for (int32 i = 0; i < line->byteLength;) {
94 // get attributes
95 if (charCount == nextAttributesAt) {
96 if (charCount < attributesRun->offset) {
97 // the "hole" in attributes run
98 attributes.Reset();
99 nextAttributesAt = attributesRun->offset;
100 } else if (attributesRunCount > 0) {
101 attributes = attributesRun->attributes;
102 nextAttributesAt = attributesRun->offset
103 + attributesRun->length;
104 attributesRun++;
105 attributesRunCount--;
106 } else {
107 attributes.Reset();
108 nextAttributesAt = INT_MAX;
109 }
110 }
111
112 // copy character
113 TerminalCell& cell = buffer->cells[charCount++];
114 int32 charLength = UTF8Char::ByteCount(chars[i]);
115 cell.character.SetTo(chars + i, charLength);
116 i += charLength;
117
118 // set attributes
119 cell.attributes = attributes;
120
121 // full width char?
122 if (cell.character.IsFullWidth()) {
123 cell.attributes.state |= A_WIDTH;
124 // attributes of the second, "invisible" cell must be
125 // cleared to let full-width chars detection work properly
126 buffer->cells[charCount++].attributes.Reset();
127 }
128 }
129
130 buffer->length = charCount;
131 buffer->softBreak = line->softBreak;
132 buffer->attributes = line->attributes;
133
134 return buffer;
135 }
136
137
138 void
AddLine(const TerminalLine * line)139 HistoryBuffer::AddLine(const TerminalLine* line)
140 {
141 //debug_printf("HistoryBuffer::AddLine(%p): length: %d\n", line, line->length);
142 // determine the amount of memory we need for the line
143 Attributes attributes;
144 int32 attributesRuns = 0;
145 int32 byteLength = 0;
146 for (int32 i = 0; i < line->length; i++) {
147 const TerminalCell& cell = line->cells[i];
148 byteLength += cell.character.ByteCount();
149 if (cell != attributes) {
150 attributes.state = cell.attributes.state & CHAR_ATTRIBUTES;
151 attributes.foreground = cell.attributes.foreground;
152 attributes.background = cell.attributes.background;
153 if (attributes.state != 0)
154 attributesRuns++;
155 }
156 if (cell.attributes.IsWidth())
157 i++;
158 }
159
160 //debug_printf(" attributesRuns: %ld, byteLength: %ld\n", attributesRuns, byteLength);
161
162 // allocate and translate the line
163 HistoryLine* historyLine = _AllocateLine(attributesRuns, byteLength);
164
165 attributes.Reset();
166 AttributesRun* attributesRun = historyLine->AttributesRuns();
167
168 char* chars = historyLine->Chars();
169 for (int32 i = 0; i < line->length; i++) {
170 const TerminalCell& cell = line->cells[i];
171
172 // copy char
173 int32 charLength = cell.character.ByteCount();
174 memcpy(chars, cell.character.bytes, charLength);
175 chars += charLength;
176
177 // deal with attributes
178 if (cell != attributes) {
179 // terminate the previous attributes run
180 if (attributes.state != 0) {
181 attributesRun->length = i - attributesRun->offset;
182 attributesRun++;
183 }
184
185 attributes.state = cell.attributes.state & CHAR_ATTRIBUTES;
186 attributes.foreground = cell.attributes.foreground;
187 attributes.background = cell.attributes.background;
188
189 // init the new one
190 if (attributes.state != 0) {
191 attributesRun->attributes = attributes;
192 attributesRun->offset = i;
193 }
194 }
195
196 if (cell.attributes.IsWidth())
197 i++;
198 }
199
200 // set the last attributes run's length
201 if (attributes.state != 0)
202 attributesRun->length = line->length - attributesRun->offset;
203
204 historyLine->softBreak = line->softBreak;
205 historyLine->attributes = line->attributes;
206 //debug_printf(" line: \"%.*s\", history size now: %ld\n", historyLine->byteLength, historyLine->Chars(), fSize);
207 }
208
209
210 void
AddEmptyLines(int32 count)211 HistoryBuffer::AddEmptyLines(int32 count)
212 {
213 if (count <= 0)
214 return;
215
216 if (count > fCapacity)
217 count = fCapacity;
218
219 if (count + fSize > fCapacity)
220 DropLines(count + fSize - fCapacity);
221
222 // All lines use the same buffer address, since they don't use any memory.
223 AttributesRun* attributesRun
224 = (AttributesRun*)(fBuffer + fBufferAllocationOffset);
225
226 for (int32 i = 0; i < count; i++) {
227 HistoryLine* line = &fLines[fNextLine];
228 fNextLine = (fNextLine + 1) % fCapacity;
229 line->attributesRuns = attributesRun;
230 line->attributesRunCount = 0;
231 line->byteLength = 0;
232 line->softBreak = false;
233 }
234
235 fSize += count;
236 }
237
238
239 void
DropLines(int32 count)240 HistoryBuffer::DropLines(int32 count)
241 {
242 if (count <= 0)
243 return;
244
245 if (count < fSize) {
246 fSize -= count;
247 } else {
248 fSize = 0;
249 fNextLine = 0;
250 fBufferAllocationOffset = 0;
251 }
252 }
253
254
255 HistoryLine*
_AllocateLine(int32 attributesRuns,int32 byteLength)256 HistoryBuffer::_AllocateLine(int32 attributesRuns, int32 byteLength)
257 {
258 // we need at least one spare line slot
259 int32 toDrop = 0;
260 if (fSize == fCapacity)
261 toDrop = 1;
262
263 int32 bytesNeeded = attributesRuns * sizeof(AttributesRun) + byteLength;
264
265 if (fBufferAllocationOffset + bytesNeeded > fBufferSize) {
266 // drop all lines after the allocation index
267 for (; toDrop < fSize; toDrop++) {
268 HistoryLine* line = _LineAt(fSize - toDrop - 1);
269 int32 offset = (uint8*)line->AttributesRuns() - fBuffer;
270 if (offset < fBufferAllocationOffset)
271 break;
272 }
273
274 fBufferAllocationOffset = 0;
275 }
276
277 // drop all lines interfering
278 int32 nextOffset = (fBufferAllocationOffset + bytesNeeded + 1) & ~1;
279 for (; toDrop < fSize; toDrop++) {
280 HistoryLine* line = _LineAt(fSize - toDrop - 1);
281 int32 offset = (uint8*)line->AttributesRuns() - fBuffer;
282 if (offset + line->BufferSize() <= fBufferAllocationOffset
283 || offset >= nextOffset) {
284 break;
285 }
286 }
287
288 DropLines(toDrop);
289
290 // init the line
291 HistoryLine* line = &fLines[fNextLine];
292 fNextLine = (fNextLine + 1) % fCapacity;
293 fSize++;
294 line->attributesRuns = (AttributesRun*)(fBuffer + fBufferAllocationOffset);
295 line->attributesRunCount = attributesRuns;
296 line->byteLength = byteLength;
297
298 fBufferAllocationOffset = (fBufferAllocationOffset + bytesNeeded + 1) & ~1;
299 // DropLines() may have changed fBufferAllocationOffset, so don't use
300 // nextOffset.
301
302 return line;
303 }
304