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