1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
#include "qb.h"
6
#include "text.h"
7
8
int qb_running = 1;
9
10
typedef struct doc_line {
11
char *line;
12
int allocated, stored;
13
struct doc_line *prev, *next;
14
} doc_line_t;
15
16
static doc_line_t *active_doc;
17
static int total_lines = 1;
18
19
static int cursor_x = 0;
20
static int cursor_y = 0;
21
22
static int scroll_x = 0;
23
static int scroll_y = 0;
24
25
static SDL_Keycode shift_table[][2] = {
26
{SDLK_QUOTE, SDLK_QUOTEDBL},
27
{SDLK_COMMA, SDLK_LESS},
28
{SDLK_MINUS, SDLK_UNDERSCORE},
29
{SDLK_PERIOD, SDLK_GREATER},
30
{SDLK_SLASH, SDLK_QUESTION},
31
{SDLK_0, SDLK_RIGHTPAREN},
32
{SDLK_1, SDLK_EXCLAIM},
33
{SDLK_2, SDLK_AT},
34
{SDLK_3, SDLK_HASH},
35
{SDLK_4, SDLK_DOLLAR},
36
{SDLK_5, SDLK_PERCENT},
37
{SDLK_6, SDLK_CARET},
38
{SDLK_7, SDLK_AMPERSAND},
39
{SDLK_8, SDLK_ASTERISK},
40
{SDLK_9, SDLK_LEFTPAREN},
41
{SDLK_SEMICOLON, SDLK_COLON},
42
{SDLK_LEFTBRACKET, '{'},
43
{SDLK_BACKSLASH, '|'},
44
{SDLK_RIGHTBRACKET, '}'},
45
{SDLK_BACKQUOTE, '~'},
46
{SDLK_EQUALS, SDLK_PLUS},
47
};
48
49
static int min(int a, int b) {
50
if (a < b) {
51
return a;
52
}
53
return b;
54
}
55
56
static doc_line_t *create_doc_line(void) {
57
doc_line_t *d = malloc(sizeof(*d));
58
memset(d, 0, sizeof(*d));
59
d->line = malloc(8);
60
d->allocated = 8;
61
return d;
62
}
63
64
static void free_doc_line(doc_line_t *d) {
65
free(d->line);
66
free(d);
67
}
68
69
static doc_line_t *get_current_doc_line(void) {
70
doc_line_t *d = active_doc;
71
for (int y = 0; y < cursor_y; ++y) {
72
d = d->next;
73
}
74
return d;
75
}
76
77
static void ensure_available(doc_line_t *d, int required) {
78
int allocated = d->allocated;
79
while (allocated < d->stored + required) {
80
allocated *= 2;
81
}
82
83
if (allocated == d->allocated) {
84
return;
85
}
86
87
char *line = malloc(allocated);
88
memcpy(line, d->line, d->stored);
89
free(d->line);
90
d->line = line;
91
d->allocated = allocated;
92
}
93
94
static void insert_character(doc_line_t *d, int offset, char c) {
95
ensure_available(d, 1);
96
bcopy(d->line + offset, d->line + offset + 1, d->stored - offset);
97
d->line[offset] = c;
98
++d->stored;
99
}
100
101
static void split_line(doc_line_t *d, int offset) {
102
doc_line_t *n = create_doc_line();
103
n->next = d->next;
104
n->prev = d;
105
d->next = n;
106
107
n->stored = d->stored - offset;
108
ensure_available(n, n->stored);
109
memcpy(n->line, d->line + offset, n->stored);
110
111
d->stored -= n->stored;
112
113
++total_lines;
114
}
115
116
static void delete_at(doc_line_t *d, int offset, int dir) {
117
/* dir should be -1 (backspace) or 0 (delete) */
118
if (dir == -1 && offset == 0) {
119
doc_line_t *p = d->prev;
120
121
if (!p) {
122
/* WRONG
123
* WAY
124
* GO BACK */
125
return;
126
}
127
128
--cursor_y;
129
cursor_x = p->stored;
130
131
ensure_available(p, d->stored);
132
memcpy(p->line + p->stored, d->line, d->stored);
133
p->next = d->next;
134
p->stored += d->stored;
135
if (d->next) {
136
d->next->prev = p;
137
}
138
139
free_doc_line(d);
140
--total_lines;
141
} else if (dir == -1) {
142
/* offset > 0 */
143
bcopy(d->line + offset, d->line + offset - 1, d->stored - offset);
144
--d->stored;
145
--cursor_x;
146
} else if (offset == d->stored) {
147
/* dir == 0 */
148
doc_line_t *n = d->next;
149
150
if (!n) {
151
return;
152
}
153
154
ensure_available(d, n->stored);
155
memcpy(d->line + d->stored, n->line, n->stored);
156
d->stored += n->stored;
157
158
d->next = n->next;
159
if (n->next) {
160
n->next->prev = d;
161
}
162
163
free_doc_line(n);
164
--total_lines;
165
} else {
166
/* dir == 0, offset < d->stored */
167
bcopy(d->line + offset + 1, d->line + offset, d->stored - offset - 1);
168
--d->stored;
169
}
170
}
171
172
static char get_character(SDL_Keycode sym, Uint16 mod) {
173
if (sym >= SDLK_a && sym <= SDLK_z) {
174
if (mod & (KMOD_SHIFT | KMOD_CAPS)) {
175
return (char) (sym - ('a' - 'A'));
176
}
177
return (char) sym;
178
}
179
180
if (mod & KMOD_SHIFT) {
181
for (int i = 0; i < sizeof(shift_table); ++i) {
182
if (shift_table[i][0] == sym) {
183
return shift_table[i][1];
184
}
185
}
186
}
187
188
return (char) sym;
189
}
190
191
static int is_printable_key(SDL_Keycode sym) {
192
return sym >= SDLK_SPACE && sym <= SDLK_z;
193
}
194
195
void render(void) {
196
for (int i = 0; i < 80 * 25; ++i) {
197
screen[i] = 0x1700;
198
}
199
200
/* Render the menu. */
201
202
for (int x = 0; x < 80; ++x) {
203
screen[x] = 0x7000;
204
}
205
206
const char *menu_options[] = {
207
"File",
208
"Edit",
209
"View",
210
"Search",
211
"Run",
212
"Debug",
213
"Calls",
214
"Options",
215
NULL,
216
};
217
218
int offset = 2;
219
for (int i = 0; ; ++i) {
220
if (!menu_options[i]) {
221
break;
222
}
223
224
int len = strlen(menu_options[i]);
225
for (int j = 0; j < len; ++j) {
226
screen[0 * 80 + offset + 1 + j] = 0x7000 | (menu_options[i][j]);
227
}
228
229
offset += len + 2;
230
}
231
232
screen[0 * 80 + 74] = 0x7000 + 'H';
233
screen[0 * 80 + 75] = 0x7000 + 'e';
234
screen[0 * 80 + 76] = 0x7000 + 'l';
235
screen[0 * 80 + 77] = 0x7000 + 'p';
236
237
/* Render the titlebar. */
238
239
screen[1 * 80 + 0] = 0x17da;
240
for (int x = 1; x < 79; ++x) {
241
screen[1 * 80 + x] = 0x17c4;
242
}
243
244
const char *file = "Untitled";
245
int flen = strlen(file);
246
int start = 40 - flen / 2;
247
screen[1 * 80 + start - 1] = 0x7000;
248
249
int j;
250
for (j = 0; j < flen; ++j) {
251
screen[1 * 80 + start + j] = 0x7000 | file[j];
252
}
253
254
screen[1 * 80 + start + j] = 0x7000;
255
256
/* Render the little fullscreen widget at the right. */
257
258
screen[1 * 80 + 75] = 0x17b4;
259
screen[1 * 80 + 76] = 0x7112;
260
screen[1 * 80 + 77] = 0x17c3;
261
262
screen[1 * 80 + 79] = 0x17bf;
263
264
/* Draw the editing area and borders. */
265
266
for (int y = 2; y < 24; ++y) {
267
screen[y * 80 + 0] = screen[y * 80 + 79] = 0x17b3;
268
for (int x = 1; x < 79; ++x) {
269
screen[y * 80 + x] = 0x1700;
270
}
271
}
272
273
/* Render the editing text. */
274
275
doc_line_t *line = active_doc;
276
for (int y = 0; y < scroll_y && line; ++y, line = line->next) {}
277
278
for (int y = 0; y < 21 && line; ++y, line = line->next) {
279
for (int x = scroll_x; x < min(line->stored, 78 + scroll_x); ++x) {
280
screen[(y + 2) * 80 + 1 + x - scroll_x] += line->line[x];
281
}
282
}
283
284
/* Draw the vertical scrollbar. */
285
286
screen[2 * 80 + 79] = 0x7018;
287
288
for (int y = 3; y < 22; ++y) {
289
screen[y * 80 + 79] = 0x70b0;
290
}
291
292
screen[(3 + (int)((float) cursor_y / (total_lines - 1) * (21 - 3))) * 80 + 79] = 0x0000;
293
294
screen[22 * 80 + 79] = 0x7019;
295
296
/* Draw the horizontal scrollbar. */
297
298
screen[23 * 80 + 1] = 0x701b;
299
300
for (int x = 2; x < 78; ++x) {
301
screen[23 * 80 + x] = 0x70b0;
302
}
303
screen[23 * 80 + (int)((float) scroll_x / 178 * 75) + 2] = 0x0000;
304
305
screen[23 * 80 + 78] = 0x701a;
306
307
/* Draw the help line. */
308
309
const char *footer[] = {
310
"Shift+F1=Help",
311
"F6=Window",
312
"F2=Subs",
313
"F5=Run",
314
"F8=Step",
315
NULL,
316
};
317
318
for (int x = 0; x < 80; ++x) {
319
screen[24 * 80 + x] = 0x3000;
320
}
321
322
offset = 1;
323
for (int i = 0; ; ++i) {
324
if (!footer[i]) {
325
break;
326
}
327
328
int len = strlen(footer[i]);
329
screen[24 * 80 + offset] += '<';
330
int j;
331
for (j = 0; j < len; ++j) {
332
screen[24 * 80 + offset + 1 + j] += footer[i][j];
333
}
334
screen[24 * 80 + offset + 1 + j] += '>';
335
336
offset += len + 3;
337
}
338
339
/* Draw the ruler. */
340
341
screen[24 * 80 + 62] += 0xb3;
342
343
char *counter;
344
asprintf(&counter, "%05d:%03d", cursor_y + 1, cursor_x + 1);
345
int len = strlen(counter);
346
for (int i = 0; i < len; ++i) {
347
screen[24 * 80 + 70 + i] += counter[i];
348
}
349
free(counter);
350
351
/* Place the cursor. */
352
353
screen_cursor_x = cursor_x + 1 - scroll_x;
354
screen_cursor_y = cursor_y + 2 - scroll_y;
355
}
356
357
static void check_scroll(void) {
358
/* window height: 21 lines (2 to 22) */
359
if (cursor_y < scroll_y) {
360
scroll_y = cursor_y;
361
} else if (cursor_y > scroll_y + 20) {
362
scroll_y = cursor_y - 20;
363
}
364
365
/* window width: 78 characters (1 to 78) */
366
if (cursor_x < scroll_x) {
367
scroll_x = cursor_x;
368
} else if (cursor_x > scroll_x + 77) {
369
scroll_x = cursor_x - 77;
370
}
371
}
372
373
void qb_init(void) {
374
active_doc = create_doc_line();
375
active_doc->line = strdup("10 PRINT \"LOL\"");
376
active_doc->stored = strlen(active_doc->line);
377
active_doc->allocated = active_doc->stored + 1;
378
379
active_doc->next = create_doc_line();
380
active_doc->next->prev = active_doc;
381
active_doc->next->line = strdup("20 GOTO 10");
382
active_doc->next->stored = strlen(active_doc->next->line);
383
active_doc->next->allocated = active_doc->next->stored + 1;
384
385
active_doc->next->next = create_doc_line();
386
active_doc->next->next->prev = active_doc->next;
387
388
total_lines = 3;
389
390
render();
391
}
392
393
void qb_keypress(SDL_Keycode sym, Uint16 mod) {
394
if (sym == SDLK_ESCAPE) {
395
qb_running = 0;
396
return;
397
}
398
399
if (sym == SDLK_DOWN && cursor_y < total_lines - 1) {
400
++cursor_y;
401
int max = get_current_doc_line()->stored;
402
if (cursor_x > max) {
403
cursor_x = max;
404
}
405
} else if (sym == SDLK_UP && cursor_y > 0) {
406
--cursor_y;
407
int max = get_current_doc_line()->stored;
408
if (cursor_x > max) {
409
cursor_x = max;
410
}
411
} else if (sym == SDLK_LEFT && cursor_x > 0) {
412
--cursor_x;
413
} else if (sym == SDLK_RIGHT) {
414
if (cursor_x < get_current_doc_line()->stored) {
415
++cursor_x;
416
}
417
} else if (is_printable_key(sym) && get_current_doc_line()->stored < 255) {
418
insert_character(
419
get_current_doc_line(),
420
cursor_x,
421
get_character(sym, mod));
422
++cursor_x;
423
} else if (sym == SDLK_RETURN) {
424
split_line(get_current_doc_line(), cursor_x);
425
cursor_x = 0;
426
++cursor_y;
427
} else if (sym == SDLK_BACKSPACE) {
428
delete_at(get_current_doc_line(), cursor_x, -1);
429
} else if (sym == SDLK_DELETE) {
430
delete_at(get_current_doc_line(), cursor_x, 0);
431
} else if (sym == SDLK_HOME) {
432
cursor_x = 0;
433
} else if (sym == SDLK_END) {
434
cursor_x = get_current_doc_line()->stored;
435
}
436
437
check_scroll();
438
render();
439
text_refresh();
440
}
441
442
/* vim: set sw=4 et: */
443