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
20HistoryBuffer::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
34HistoryBuffer::~HistoryBuffer()
35{
36	delete[] fLines;
37	delete[] fBuffer;
38}
39
40
41status_t
42HistoryBuffer::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
68void
69HistoryBuffer::Clear()
70{
71	fNextLine = 0;
72	fSize = 0;
73	fBufferAllocationOffset = 0;
74}
75
76
77TerminalLine*
78HistoryBuffer::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
138void
139HistoryBuffer::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
210void
211HistoryBuffer::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
239void
240HistoryBuffer::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
255HistoryLine*
256HistoryBuffer::_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