-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTerminalLineEditor.c
319 lines (274 loc) · 9.48 KB
/
TerminalLineEditor.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include <assert.h>
#include <stdbool.h>
#include "KeyPressEventDispatcher.h"
#include "line_Editor.h"
#include "HQueue.h"
//#define DEBUG
#ifdef DEBUG
FILE *file;
#endif
LineData *linedata;
/*
Puts the y and x postions of the mouse into the given x and y
variables passed as pointers
*/
int getCursor(int *x, int *y) {
struct termios newt, oldt;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
cfmakeraw(&newt);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
char buf[100];
// Due to a race condition where a keypress may be written to the stdin stream
// before the terminal response, we use an 'auxiliary' buffer, unhandledBuff,
// to store any unhandled keypresses that may have been written in.
char unhandledBuff[6];
int i = 0;
// queries the terminal for cursor position status
write(STDOUT_FILENO, "\33[6n", 5);
#ifdef DEBUG
fprintf(file, "get CURSOR\n");
#endif
// Terminal sends response to stdin and we need to read it in
while (i < sizeof(buf) - 1) {
read(STDIN_FILENO, &buf[i], 1);
// if (read(STDIN_FILENO, &buf[i], 1) != 1) {break;}
if (buf[i] == 'R') {
break;
} // acording to man pages it should end in 'R'
i++;
}
// Make it NULL terminated
buf[i] = '\0';
if (buf[0] != '\33' || buf[1] != '[') {
return -1;
}
// Todo add error handling in the case that sscanf does not succesfully match
// ie returns a value less than 2
// First two chars are characters that indicate it is an escape sequence
int ret =
sscanf(&buf[2], "%d;%d", y,
x); // Finnaly we put the response into the given y and x variable
#ifdef DEBUG
fprintf(file, "x: %d, y: %d \n", *x, *y);
fprintf(file, "Return: %d \n", ret);
#endif
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return 0;
}
// Todo make it add the change to the input buffer
void handleNonControl(char c) {
// Adds the character to editing buffer
insertChar(linedata->ldb, c, linedata->cursIdex - linedata->initP.x);
linedata->cursIdex++; // Move the cursor forward one
}
/*Takes part of a control sequence and handles it; only for cursor control*/
void handleControl(event event) {
switch (event.eventType) {
case RIGHT_ARROW: // Move cursor right
if (linedata->cursIdex ==
linedata->ldb->endIndex) { // don't want to move cursor back more
return;
} else {
linedata->cursIdex++;
}
break;
case LEFT_ARROW: // Move cursor left
if (linedata->initP.x ==
linedata->cursIdex) { // don't want to move cursor further more
return;
} else {
linedata->cursIdex--;
}
break;
case UP_ARROW: // update editing buffer to element in history Queue
qpUp(); //move quepointer up
linedata->ldb = Hpeek();
linedata->cursIdex = linedata->ldb->endIndex; // Moves the cursor to the end
break;
case DOWN_ARROW:
qpDown();
linedata->ldb = Hpeek();
linedata->cursIdex =linedata->ldb->endIndex; // Moves the cursor to the end
break;
case DELETE:
if (linedata->cursIdex == linedata->ldb->endIndex) {
return;
}
removeChar(linedata->ldb, (linedata->cursIdex - linedata->initP.x));
break;
case BACKSPACE: //Delete the char at the current cursor pos
if (linedata->cursIdex == linedata->initP.x) {
return;
}
linedata->cursIdex--;
assert(linedata != NULL);
assert(linedata->ldb != NULL);
removeChar(linedata->ldb, linedata->cursIdex - linedata->initP.x); // Remove from internal buffer
default:
break;
}
}
/*
This function is called when enter key is pressed; The address of the editing buffer
is assiged to the given pointer from getLine();
The event dispatcher terminated IE program is set back to cooked mode
*/
void terminateLineEditor() {
// write(STDOUT_FILENO, "\n", 1); // Moves to new line
terminateDispatcher();
printf("\n");
fflush(stdout);
linedata->terminate = true; // getLine() will no longer loop and we return to original call site
return;
}
/*
Frees the resources and data structures used by this editor;
Should only be called when the calling site is going to exit;
Most importantly this func frees and destroys the History editing buffer queue;
IE previus editing buffers will be lost.
*/
void destroyLineEditor() {
terminateDispatcher();
//Add here frees and destroys from varius data structures here
printf("\n");
fflush(stdout);
//write(STDOUT_FILENO, "\n", 1);
exit(0);
}
/* Handles the nescarry updates to display when terimnal
when the resize signal is recieved;
*/ //Todo Finish this
void terminalResizeSigHandler(int) {
#ifdef DEBUG
fprintf(file, "WE ARE RESIZING!\n");
#endif
// To update the window size values
ioctl(STDIN_FILENO, TIOCGWINSZ, &linedata->ws);
}
/*Handles and procceses all key events*/
void handleEvents(event event) {
switch (event.eventType) {
//Todo the bottom 3 should not be here. it doesnt make sense to have control outside of the noncontrol function
case QUIT_SEQUENCE:
terminateDispatcher();
exit(1);
//destroyLineEditor();
//destroyLineEditor();
break;
case ENTER:
terminateLineEditor();
// terminateLineEditor();
break;
case END_OF_LINE: // Control + C
destroyLineEditor();
break;
case NON_CONTROL:
handleNonControl(event.kp);
break;
default: // Control Char
handleControl(event);
break;
}
}
/*prints the internal editing buffer to console and correctly positions cursor*/
void pushToConsole() {
if (linedata->terminate == true) {return;}
int row, col;
// we need to translate from a 1d array cursor index to
// a 2d array pos
row = (linedata->cursIdex / linedata->ws.ws_col) + linedata->initP.y;
col = linedata->cursIdex % linedata->ws.ws_col;
#ifdef DEBUG
fprintf(file, "ROW: %d, COL: %d \n", row, col);
#endif
setbuf(stdout, NULL);
char *outputBuf = malloc(300);
// concates together the output buffer that will be printed
int index = 0;
index += sprintf(outputBuf + index, "\033[?25l"); // hides the cursor
index += sprintf(outputBuf + index, "\033[%d;%dH", linedata->initP.y,
linedata->initP.x); // moves it to the initial position
index +=
sprintf(outputBuf + index, "\033[J"); // clears screen starting at cursor
index +=
sprintf(outputBuf + index, "%s", (char *) linedata->ldb->strBuf); // The editing buffer
index += sprintf(outputBuf + index, "\033[%d;%dH", row,
col); // move cursor to desired position
index += sprintf(outputBuf + index, "\033[?25h"); // show cursor
outputBuf[index] = '\0'; // add a null terminator
write(STDOUT_FILENO, outputBuf,
index + 1); // The index should now contain the total num chars
free(outputBuf);
}
void initLineEdit() {
/*The signal called 'sigwinch' is sent to this proccess when the
// terminal window size is changed, thus we need to relect this
// change internaly;
*/
struct sigaction act = {0};
act.sa_handler = terminalResizeSigHandler;
sigaction(SIGWINCH, &act, NULL);
#ifdef DEBUG
file = fopen("LineEditorLog.txt", "w");
setbuf(file, NULL);
#endif
// file = fopen("LineEditorLog.txt", "w");
// setbuf(file, NULL);
// fprintf(file, "QUIT SEQ");
linedata = malloc(sizeof(LineData));
getCursor(&linedata->initP.x, &linedata->initP.y);
linedata->cursIdex = linedata->initP.x;
ioctl(STDIN_FILENO, TIOCGWINSZ, &linedata->ws); // Stores terminal window size information into the struct
// initialize the keypress event disptacher
initHQueue(); //Static History queue for storing previus ldBuffers
initDispatcher('q'); // TODO make 'q' a macro
initLDBuff(&linedata->ldb, 100); // Line editing buffer; all text/char gets stored here
HupdateCurrentBuffer(linedata->ldb);
linedata->ldb->endIndex = linedata->initP.x = linedata->cursIdex;
linedata->terminate = false;
}
/*
Line Editor stores the address of the buffer containing the inputed terminal text string once
enter has been pressed; calling proccess must free lineptr.
*/
void ldGetLine(char ** lineptr) {
fflush(stdout);
initLineEdit();
event event; // struct that contains key event info
// Todo add catagory field to filter between control events and typing events
while (linedata->terminate == false) {
if (pollEvent(&event) == 1) {
handleEvents(event);
pushToConsole();
}
}
Henqueue(linedata->ldb); // Enqueue the editing buffer to history queue
linedata->ldb->strBuf[linedata->ldb->endIndex] = '\0'; // add the null terminator
*lineptr = malloc(strlen(linedata->ldb->strBuf) + 1);
strcpy(*lineptr, linedata->ldb->strBuf);
}
/*
on initlization save the number of characters for 'prefix';
we have a field in the line editor attr struct that maintains the width of the
terminal ^this is changed if the terminal size changes
every move the cursor from typing checks if the position
we shall mantain 4 values related to the cursor position:
max_x the max col the cursor is allowed to be; when max_x is equal to the
terminal width it shall loop back to zero cur_x is the current col the cursor is
in;
max_y the max row that the cursor is alloeed to be; when max_x loops over to 0
max_y shall increase by one cur_y the current row of the cursor
init_x_y the initial positon of the cursor; cursor may never go any farther left
of this position
*/