xref: /haiku/src/apps/terminal/HistoryBuffer.cpp (revision 1deede7388b04dbeec5af85cae7164735ea9e70d)
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 
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 
34 HistoryBuffer::~HistoryBuffer()
35 {
36 	delete[] fLines;
37 	delete[] fBuffer;
38 }
39 
40 
41 status_t
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
69 HistoryBuffer::Clear()
70 {
71 	fNextLine = 0;
72 	fSize = 0;
73 	fBufferAllocationOffset = 0;
74 }
75 
76 
77 TerminalLine*
78 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 	uint32 attributes = 0;
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 = 0;
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 = 0;
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 |= 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 = 0;
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
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 	uint32 attributes = 0;
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 & CHAR_ATTRIBUTES) != attributes) {
150 			attributes = cell.attributes & CHAR_ATTRIBUTES;
151 			if (attributes != 0)
152 				attributesRuns++;
153 		}
154 		if (IS_WIDTH(cell.attributes))
155 			i++;
156 	}
157 
158 //debug_printf("  attributesRuns: %ld, byteLength: %ld\n", attributesRuns, byteLength);
159 
160 	// allocate and translate the line
161 	HistoryLine* historyLine = _AllocateLine(attributesRuns, byteLength);
162 
163 	attributes = 0;
164 	AttributesRun* attributesRun = historyLine->AttributesRuns();
165 
166 	char* chars = historyLine->Chars();
167 	for (int32 i = 0; i < line->length; i++) {
168 		const TerminalCell& cell = line->cells[i];
169 
170 		// copy char
171 		int32 charLength = cell.character.ByteCount();
172 		memcpy(chars, cell.character.bytes, charLength);
173 		chars += charLength;
174 
175 		// deal with attributes
176 		if ((cell.attributes & CHAR_ATTRIBUTES) != attributes) {
177 			// terminate the previous attributes run
178 			if (attributes != 0) {
179 				attributesRun->length = i - attributesRun->offset;
180 				attributesRun++;
181 			}
182 
183 			attributes = cell.attributes & CHAR_ATTRIBUTES;
184 
185 			// init the new one
186 			if (attributes != 0) {
187 				attributesRun->attributes = attributes;
188 				attributesRun->offset = i;
189 			}
190 		}
191 
192 		if (IS_WIDTH(cell.attributes))
193 			i++;
194 	}
195 
196 	// set the last attributes run's length
197 	if (attributes != 0)
198 		attributesRun->length = line->length - attributesRun->offset;
199 
200 	historyLine->softBreak = line->softBreak;
201 	historyLine->attributes = line->attributes;
202 //debug_printf("  line: \"%.*s\", history size now: %ld\n", historyLine->byteLength, historyLine->Chars(), fSize);
203 }
204 
205 
206 void
207 HistoryBuffer::AddEmptyLines(int32 count)
208 {
209 	if (count <= 0)
210 		return;
211 
212 	if (count > fCapacity)
213 		count = fCapacity;
214 
215 	if (count + fSize > fCapacity)
216 		DropLines(count + fSize - fCapacity);
217 
218 	// All lines use the same buffer address, since they don't use any memory.
219 	AttributesRun* attributesRun
220 		= (AttributesRun*)(fBuffer + fBufferAllocationOffset);
221 
222 	for (int32 i = 0; i < count; i++) {
223 		HistoryLine* line = &fLines[fNextLine];
224 		fNextLine = (fNextLine + 1) % fCapacity;
225 		line->attributesRuns = attributesRun;
226 		line->attributesRunCount = 0;
227 		line->byteLength = 0;
228 		line->softBreak = false;
229 	}
230 
231 	fSize += count;
232 }
233 
234 
235 void
236 HistoryBuffer::DropLines(int32 count)
237 {
238 	if (count <= 0)
239 		return;
240 
241 	if (count < fSize) {
242 		fSize -= count;
243 	} else {
244 		fSize = 0;
245 		fNextLine = 0;
246 		fBufferAllocationOffset = 0;
247 	}
248 }
249 
250 
251 HistoryLine*
252 HistoryBuffer::_AllocateLine(int32 attributesRuns, int32 byteLength)
253 {
254 	// we need at least one spare line slot
255 	int32 toDrop = 0;
256 	if (fSize == fCapacity)
257 		toDrop = 1;
258 
259 	int32 bytesNeeded = attributesRuns * sizeof(AttributesRun) + byteLength;
260 
261 	if (fBufferAllocationOffset + bytesNeeded > fBufferSize) {
262 		// drop all lines after the allocation index
263 		for (; toDrop < fSize; toDrop++) {
264 			HistoryLine* line = _LineAt(fSize - toDrop - 1);
265 			int32 offset = (uint8*)line->AttributesRuns() - fBuffer;
266 			if (offset < fBufferAllocationOffset)
267 				break;
268 		}
269 
270 		fBufferAllocationOffset = 0;
271 	}
272 
273 	// drop all lines interfering
274 	int32 nextOffset = (fBufferAllocationOffset + bytesNeeded + 1) & ~1;
275 	for (; toDrop < fSize; toDrop++) {
276 		HistoryLine* line = _LineAt(fSize - toDrop - 1);
277 		int32 offset = (uint8*)line->AttributesRuns() - fBuffer;
278 		if (offset + line->BufferSize() <= fBufferAllocationOffset
279 				|| offset >= nextOffset) {
280 			break;
281 		}
282 	}
283 
284 	DropLines(toDrop);
285 
286 	// init the line
287 	HistoryLine* line = &fLines[fNextLine];
288 	fNextLine = (fNextLine + 1) % fCapacity;
289 	fSize++;
290 	line->attributesRuns = (AttributesRun*)(fBuffer + fBufferAllocationOffset);
291 	line->attributesRunCount = attributesRuns;
292 	line->byteLength = byteLength;
293 
294 	fBufferAllocationOffset = (fBufferAllocationOffset + bytesNeeded + 1) & ~1;
295 		// DropLines() may have changed fBufferAllocationOffset, so don't use
296 		// nextOffset.
297 
298 	return line;
299 }
300