-
-
Notifications
You must be signed in to change notification settings - Fork 579
Expand file tree
/
Copy pathLineEditor.c
More file actions
243 lines (208 loc) · 6.81 KB
/
LineEditor.c
File metadata and controls
243 lines (208 loc) · 6.81 KB
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
/*
htop - LineEditor.c
(C) 2004-2026 htop dev team
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
#include "config.h" // IWYU pragma: keep
#include "LineEditor.h"
#include <ctype.h>
#include <string.h>
#include "CRT.h"
#include "Panel.h"
#include "ProvideCurses.h"
void LineEditor_init(LineEditor* this) {
LineEditor_initWithMax(this, LINEEDITOR_MAX);
}
void LineEditor_initWithMax(LineEditor* this, size_t maxLen) {
this->buffer[0] = '\0';
this->len = 0;
this->cursor = 0;
this->scroll = 0;
this->maxLen = (maxLen > 0 && maxLen <= LINEEDITOR_MAX) ? maxLen : LINEEDITOR_MAX;
}
void LineEditor_reset(LineEditor* this) {
this->buffer[0] = '\0';
this->len = 0;
this->cursor = 0;
this->scroll = 0;
}
void LineEditor_setText(LineEditor* this, const char* text) {
if (!text)
text = "";
size_t copyLen = this->maxLen;
strncpy(this->buffer, text, copyLen);
this->buffer[copyLen] = '\0';
this->len = strnlen(this->buffer, this->maxLen);
this->cursor = this->len;
this->scroll = 0;
}
/* Move cursor left by one character */
static inline void moveCursorLeft(LineEditor* this) {
if (this->cursor > 0)
this->cursor--;
}
/* Move cursor right by one character */
static inline void moveCursorRight(LineEditor* this) {
if (this->cursor < this->len)
this->cursor++;
}
/* Move cursor to the previous word boundary (Ctrl+Left / word-left) */
static void moveCursorWordLeft(LineEditor* this) {
size_t pos = this->cursor;
/* skip whitespace before cursor */
while (pos > 0 && isspace((unsigned char)this->buffer[pos - 1]))
pos--;
/* skip non-whitespace (the word itself) */
while (pos > 0 && !isspace((unsigned char)this->buffer[pos - 1]))
pos--;
this->cursor = pos;
}
/* Move cursor to the next word boundary (Ctrl+Right / word-right) */
static void moveCursorWordRight(LineEditor* this) {
size_t pos = this->cursor;
size_t len = this->len;
/* skip non-whitespace */
while (pos < len && !isspace((unsigned char)this->buffer[pos]))
pos++;
/* skip whitespace */
while (pos < len && isspace((unsigned char)this->buffer[pos]))
pos++;
this->cursor = pos;
}
/* Delete the character before cursor (Backspace) */
static bool deleteCharBefore(LineEditor* this) {
if (this->cursor == 0)
return false;
size_t pos = this->cursor - 1;
memmove(this->buffer + pos, this->buffer + this->cursor, this->len - this->cursor + 1);
this->len--;
this->cursor = pos;
return true;
}
/* Delete the character at cursor (Delete) */
static bool deleteCharAt(LineEditor* this) {
if (this->cursor >= this->len)
return false;
memmove(this->buffer + this->cursor, this->buffer + this->cursor + 1, this->len - this->cursor);
this->len--;
return true;
}
/* Insert a printable character at cursor */
static bool insertChar(LineEditor* this, char ch) {
if (this->len >= this->maxLen)
return false;
memmove(this->buffer + this->cursor + 1, this->buffer + this->cursor, this->len - this->cursor + 1);
this->buffer[this->cursor] = ch;
this->cursor++;
this->len++;
return true;
}
bool LineEditor_handleKey(LineEditor* this, int ch) {
switch (ch) {
case KEY_LEFT:
case KEY_CTRL('B'):
moveCursorLeft(this);
return false;
case KEY_RIGHT:
case KEY_CTRL('F'):
moveCursorRight(this);
return false;
case KEY_HOME:
case KEY_CTRL('A'):
this->cursor = 0;
return false;
case KEY_END:
case KEY_CTRL('E'):
this->cursor = this->len;
return false;
case KEY_SLEFT: /* Shift+Left (ncurses stock) and Ctrl+Left (htop home grown) */
moveCursorWordLeft(this);
return false;
case KEY_SRIGHT: /* Shift+Right (ncurses stock) and Ctrl+Right (htop home grown) */
moveCursorWordRight(this);
return false;
case KEY_DC: /* Delete */
return deleteCharAt(this);
case KEY_BACKSPACE:
case 127: /* DEL / Backspace in some terminals */
return deleteCharBefore(this);
case KEY_CTRL('W'): {
/* Delete word before cursor (like bash Ctrl-W) */
size_t end = this->cursor;
/* skip whitespace before cursor */
while (this->cursor > 0 && isspace((unsigned char)this->buffer[this->cursor - 1]))
this->cursor--;
/* skip non-whitespace */
while (this->cursor > 0 && !isspace((unsigned char)this->buffer[this->cursor - 1]))
this->cursor--;
if (this->cursor == end)
return false;
size_t deleted = end - this->cursor;
memmove(this->buffer + this->cursor, this->buffer + end, this->len - end + 1);
this->len -= deleted;
return true;
}
case KEY_CTRL('K'):
/* Delete from cursor to end of line */
if (this->cursor >= this->len)
return false;
this->buffer[this->cursor] = '\0';
this->len = this->cursor;
return true;
case KEY_CTRL('U'):
/* Delete from start of line to cursor */
if (this->cursor == 0)
return false;
memmove(this->buffer, this->buffer + this->cursor, this->len - this->cursor + 1);
this->len -= this->cursor;
this->cursor = 0;
return true;
default:
if (ch > 0 && ch < 256 && isprint((unsigned char)ch)) {
return insertChar(this, (char)ch);
}
return false;
}
}
void LineEditor_updateScroll(LineEditor* this, int fieldWidth) {
if (fieldWidth <= 0)
return;
size_t fw = (size_t)fieldWidth;
/* Ensure cursor is visible */
if (this->cursor < this->scroll) {
this->scroll = this->cursor;
} else if (this->cursor >= this->scroll + fw) {
this->scroll = this->cursor - fw + 1;
}
}
int LineEditor_draw(LineEditor* this, int startX, int fieldWidth, int attr) {
if (attr == -1) {
attrset(CRT_colors[FUNCTION_BAR]);
} else {
attrset(attr);
}
/* Display the visible portion of the buffer */
const char* visibleStart = this->buffer + this->scroll;
int visibleLen = (int)this->len - (int)this->scroll;
if (visibleLen < 0)
visibleLen = 0;
if (visibleLen > fieldWidth)
visibleLen = fieldWidth;
mvaddnstr(LINES - 1, startX, visibleStart, visibleLen);
/* Pad remaining field with spaces */
for (int i = visibleLen; i < fieldWidth; i++) {
mvaddch(LINES - 1, startX + i, ' ');
}
int cursorX = startX + (int)(this->cursor - this->scroll);
return cursorX;
}
void LineEditor_click(LineEditor* this, int clickX, int fieldStartX) {
int offset = clickX - fieldStartX;
if (offset < 0)
offset = 0;
size_t newCursor = this->scroll + (size_t)offset;
if (newCursor > this->len)
newCursor = this->len;
this->cursor = newCursor;
}