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