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