xref: /haiku/src/apps/terminal/HistoryBuffer.cpp (revision 46b7da1f4f40f7157d74fc7fb26ff9ec7f2416f2)
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