1/*
2 * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "pager.h"
8
9#include <ctype.h>
10#include <string.h>
11
12#include <algorithm>
13
14#include <boot/platform/generic/text_console.h>
15
16
17// #pragma mark - PagerTextSource
18
19
20PagerTextSource::~PagerTextSource()
21{
22}
23
24
25// #pragma mark -
26
27
28static size_t
29next_line(const PagerTextSource& textSource, size_t width, size_t offset,
30	char* buffer, size_t bufferSize)
31{
32	size_t bytesRead = textSource.Read(offset, buffer, bufferSize - 1);
33	if (bytesRead == 0)
34		return 0;
35
36	buffer[bytesRead] = '\0';
37
38	// replace all '\0's by spaces
39	for (size_t i = 0; i < bytesRead; i++) {
40		if (buffer[i] == '\0')
41			buffer[i] = ' ';
42	}
43
44	if (const char* lineEnd = strchr(buffer, '\n'))
45		bytesRead = lineEnd - buffer;
46
47	if (bytesRead > (size_t)width)
48		bytesRead = width;
49
50	// replace unprintables by '.'
51	for (size_t i = 0; i < bytesRead; i++) {
52		if (!isprint(buffer[i]))
53			buffer[i] = '.';
54	}
55
56	bool lineBreak = buffer[bytesRead] == '\n';
57
58	buffer[bytesRead] = '\0';
59
60	return bytesRead + (lineBreak ? 1 : 0);
61}
62
63
64static int32
65count_lines(const PagerTextSource& textSource, size_t width, char* buffer,
66	size_t bufferSize)
67{
68	int32 lineCount = 0;
69	size_t offset = 0;
70
71	while (true) {
72		size_t bytesRead = next_line(textSource, width, offset, buffer,
73			bufferSize);
74		if (bytesRead == 0)
75			break;
76
77		offset += bytesRead;
78		lineCount++;
79	}
80
81	return lineCount;
82}
83
84
85static size_t
86offset_of_line(const PagerTextSource& textSource, size_t width, char* buffer,
87	size_t bufferSize, int32 line)
88{
89	int32 lineCount = 0;
90	size_t offset = 0;
91
92	while (true) {
93		if (line == lineCount)
94			return offset;
95
96		size_t bytesRead = next_line(textSource, width, offset, buffer,
97			bufferSize);
98		if (bytesRead == 0)
99			break;
100
101		offset += bytesRead;
102		lineCount++;
103	}
104
105	return offset;
106}
107
108
109// #pragma mark -
110
111
112void
113pager(const PagerTextSource& textSource)
114{
115	console_set_cursor(0, 0);
116
117	int32 width = console_width();
118	int32 height = console_height();
119
120	char lineBuffer[256];
121
122	int32 lineCount = count_lines(textSource, width, lineBuffer,
123		sizeof(lineBuffer));
124	int32 topLine = 0;
125
126	bool quit = false;
127	while (!quit) {
128		// get the text offset for the top line
129		size_t offset = offset_of_line(textSource, width, lineBuffer,
130			sizeof(lineBuffer), topLine);
131
132		// clear the screen and print the lines
133		console_clear_screen();
134
135		int32 screenLine = 0;
136		while (screenLine + 1 < height) {
137			size_t bytesRead = next_line(textSource, width, offset, lineBuffer,
138				sizeof(lineBuffer));
139			if (bytesRead == 0)
140				break;
141
142			console_set_cursor(0, screenLine);
143			puts(lineBuffer);
144
145			offset += bytesRead;
146			screenLine++;
147		}
148
149		// print the statistics line at the bottom
150		console_set_cursor(0, height - 1);
151		console_set_color(BLACK, GRAY);
152		int32 bottomLine = std::min(topLine + height - 2, lineCount - 1);
153		printf("%" B_PRId32 " - %" B_PRId32 "  %" B_PRId32 "%%",
154			topLine, bottomLine, (int32)((bottomLine + 1) * 100 / lineCount));
155		console_set_color(WHITE, BLACK);
156
157		// wait for a key that changes the position
158		int32 previousTopLine = topLine;
159
160		while (!quit && topLine == previousTopLine) {
161			switch (console_wait_for_key()) {
162				case TEXT_CONSOLE_KEY_ESCAPE:
163				case 'q':
164				case 'Q':
165					// quit
166					quit = true;
167					break;
168
169				case TEXT_CONSOLE_KEY_DOWN:
170				case TEXT_CONSOLE_KEY_RETURN:
171				case 'j':
172				case 'J':
173					// next line
174					topLine++;
175					break;
176
177				case TEXT_CONSOLE_KEY_UP:
178				case 'k':
179				case 'K':
180					// previous line
181					topLine--;
182					break;
183
184				case TEXT_CONSOLE_KEY_PAGE_UP:
185					// previous page
186					topLine -= height - 1;
187					break;
188
189				case TEXT_CONSOLE_KEY_PAGE_DOWN:
190					// next page
191					topLine += height - 1;
192					break;
193
194				case TEXT_CONSOLE_KEY_HOME:
195					// beginning of text
196					topLine = 0;
197					break;
198
199				case TEXT_CONSOLE_KEY_END:
200					// end of text
201					topLine = lineCount;
202					break;
203			}
204
205			if (topLine > lineCount - (height - 1))
206				topLine = lineCount - (height - 1);
207			if (topLine < 0)
208				topLine = 0;
209		}
210	}
211}
212