0 /*
1 * Copyright (c) 2023 RMF <rawmonk@firemail.cc>
2 *
3 * Permission to use, copy, modify, and distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 */
15 #include <stdlib.h>
16 #include "termbox.h"
17 #include "view.h"
18 #include "client.h"
19 #include "file.h"
20 #include "strlcpy.h"
21 #include "util.h"
22
23 struct client client;
24
25 static char *fetch_name(struct view *view) {
26 char *ptr = strrchr(view->path, '/');
27 if (!ptr) return NULL;
28 return ptr + 1;
29 }
30
31 static int name_length(struct view *view) {
32 char *ptr = strrchr(view->path, '/');
33 if (!ptr) return -1;
34 ptr++;
35 return strnlen(ptr, sizeof(view->path) - (ptr - view->path));
36 }
37
38 int client_init() {
39 client.view = view_init();
40 if (!client.view || file_ls(client.view)) return -1;
41 if (tb_init()) return -1;
42 client.width = tb_width();
43 client.height = tb_height();
44 return 0;
45 }
46
47 int client_clean() {
48 free(client.view);
49 return tb_shutdown();
50 }
51
52 static void client_tabbar(struct view *view) {
53 size_t sum_next, sum_prev;
54 struct view *ptr = view;
55
56 sum_next = 0;
57 while (ptr) {
58 sum_next += name_length(ptr) + 2;
59 ptr = ptr->next;
60 }
61
62 ptr = view->prev;
63 sum_prev = 0;
64 while (ptr) {
65 sum_prev += name_length(ptr) + 2;
66 if (!ptr->prev) break;
67 ptr = ptr->prev;
68 }
69 if (!ptr) ptr = view;
70
71 /* most likely scenario */
72 if (sum_prev + sum_next < (unsigned)client.width) {
73 size_t x = 0;
74 while (ptr) {
75 tb_printf(x, 0,
76 (ptr == view ? TB_DEFAULT : TB_UNDERLINE),
77 (ptr == view ? TB_DEFAULT : TB_WHITE),
78 " %s ", fetch_name(ptr));
79 x += name_length(ptr) + 2;
80 ptr = ptr->next;
81 }
82 while (x < (unsigned)client.width) {
83 tb_set_cell(x, 0, ' ', TB_DEFAULT, TB_WHITE);
84 x++;
85 }
86 } else if (sum_prev > client.width/2 && sum_next > client.width/2) {
87 /* draw to the left till it reach half width
88 * then start drawing to the right till it full width */
89 }
90
91 }
92
93 int client_update() {
94
95 char counter[32];
96 struct view *view = client.view;
97 size_t i;
98
99 tb_clear();
100
101 /* display list view */
102 view_draw(view);
103
104 /* display input field, error and counter */
105 tb_print(0, client.height - 1, TB_DEFAULT,
106 client.error ? TB_RED : TB_DEFAULT,
107 client.error ? client.info : client.field);
108
109 snprintf(V(counter), "%d", client.counter);
110 if (client.counter)
111 tb_print(client.width - 8, client.height - 1,
112 TB_DEFAULT, TB_DEFAULT, counter);
113
114 /* display white status bar */
115 i = 0;
116 while (i < client.width) {
117 tb_set_cell(i, client.height - 2, ' ', TB_BLACK, TB_WHITE);
118 i++;
119 }
120 tb_print(0, client.height - 2, TB_BLACK, TB_WHITE, view->path);
121
122
123 /* display tabs bar if there's more than one tab */
124 if (TABS) {
125 /*
126 * count the sum of file name length on prev tabs and the sum
127 * of those on next tabs,
128 * - if the sum of both sums is less than the client width than
129 * print all from x:0
130 * - else if the sum of the next tabs is less than half width
131 * but the sum of the prev tabs is more than half width than
132 * start by print from the last tab on the right
133 *
134 * (the current tab is counted as a next tab)
135 */
136 client_tabbar(view);
137 }
138
139 tb_present();
140
141 if (client_input()) return 1;
142 return 0;
143 }
144
145 static int newtab() {
146 struct view *new = view_init();
147
148 if (!new || file_ls(new)) {
149 snprintf(V(client.info), "%s", strerror(errno));
150 return -1;
151 }
152 if (client.view->next) {
153 new->next = client.view->next;
154 client.view->next->prev = new;
155 }
156 new->prev = client.view;
157 client.view->next = new;
158 new->selected = client.view->selected;
159 client.view = client.view->next;
160 return 0;
161 }
162
163 static int closetab() {
164
165 struct view *view = client.view;
166
167 if (!view) return -1;
168 client.view = NULL;
169 if (view->prev) {
170 view->prev->next = view->next;
171 client.view = view->prev;
172 }
173 if (view->next) {
174 view->next->prev = view->prev;
175 client.view = view->next;
176 }
177 if (client.view && file_ls(client.view)) {
178 snprintf(V(client.info), "%s", strerror(errno));
179 return -1;
180 }
181
182 free(view);
183 return client.view == NULL;
184 }
185
186 int parse_command() {
187
188 char *cmd = &client.field[1];
189
190 if (cmd[0] == 'q' && cmd[1] == '\0')
191 return closetab();
192 if (cmd[0] == 'q' && cmd[1] == 'a' && cmd[2] == '\0') {
193 while (!closetab()) ;
194 return 1;
195 }
196 if ((cmd[0] == 'n' && cmd[1] == 't' && cmd[2] == '\0') ||
197 !strncmp(cmd, "tabnew", sizeof(client.field) - 1))
198 return newtab();
199
200 snprintf(V(client.info), "Not a command: %s", cmd);
201 client.error = 1;
202 return 0;
203 }
204
205 int client_command(struct tb_event ev) {
206
207 int pos;
208
209 switch (ev.key) {
210 case TB_KEY_ESC:
211 client.command = 0;
212 client.field[0] = '\0';
213 return 0;
214 case TB_KEY_BACKSPACE2:
215 case TB_KEY_BACKSPACE:
216 pos = strnlen(client.field, sizeof(client.field));
217 if (pos > 1)
218 client.field[pos - 1] = '\0';
219 else
220 client.command = 0;
221 return 0;
222 case TB_KEY_ENTER:
223 pos = parse_command();
224 client.command = 0;
225 client.field[0] = '\0';
226 return pos;
227 }
228
229 if (!ev.ch) return 0;
230
231 pos = strnlen(client.field, sizeof(client.field));
232 client.field[pos] = ev.ch;
233 client.field[pos + 1] = '\0';
234
235 return 0;
236 }
237
238 void client_reset() {
239 client.counter = client.g = 0;
240 }
241
242 int client_input() {
243 struct tb_event ev;
244 struct view *view = client.view;
245
246 if (tb_poll_event(&ev) != TB_OK) {
247 return -1;
248 }
249
250 if (ev.type == TB_EVENT_RESIZE) {
251 client.width = ev.w;
252 client.height = ev.h;
253 return 0;
254 }
255
256 if (ev.type != TB_EVENT_KEY) {
257 return 0;
258 }
259
260 if (client.command) {
261 return client_command(ev);
262 }
263
264 if (ev.key == TB_KEY_ESC) {
265 client_reset();
266 return 0;
267 }
268
269 if (ev.ch >= (client.counter ? '0' : '1') && ev.ch <= '9') {
270 int i = ev.ch - '0';
271 client.g = 0;
272 if (client.counter >= 100000000) return 0;
273 if (client.counter == 0 && i == 0) return 0;
274 client.counter = client.counter * 10 + i;
275 return 0;
276 }
277
278 switch (ev.ch) {
279 case 'j':
280 ADDMAX(view->selected, AZ(client.counter), view->length - 1);
281 client.counter = 0;
282 break;
283 case 'k':
284 SUBMIN(client.view->selected, AZ(client.counter), 0);
285 client.counter = 0;
286 break;
287 case 'l':
288 view_open(view);
289 break;
290 case 'h':
291 {
292 char name[1024];
293 char *ptr = strrchr(view->path, '/');
294 if (!ptr)
295 break;
296 sstrcpy(name, ptr + 1);
297 if (file_up(view))
298 break;
299 file_ls(view);
300 view_select(view, name);
301 }
302 break;
303 case 'T':
304 case 't':
305 if (!client.g) break;
306 if (ev.ch == 'T') { /* backward */
307 if (view->prev) client.view = view->prev;
308 else {
309 while (view->next) view = view->next;
310 client.view = view;
311 }
312 } else { /* forward */
313 if (view->next) client.view = view->next;
314 else {
315 while (view->prev) view = view->prev;
316 client.view = view;
317 }
318 }
319 file_ls(client.view);
320 client_reset();
321 break;
322 case '.':
323 view->showhidden = !view->showhidden;
324 file_ls(view);
325 break;
326 case ':':
327 client.command = 1;
328 client.error = 0;
329 client_reset();
330 client.field[0] = ':';
331 client.field[1] = '\0';
332 break;
333 case 'e':
334 {
335 char buf[1024];
336 tb_shutdown();
337 snprintf(V(buf), "$EDITOR %s/%s", view->path, SELECTED(view));
338 system(buf);
339 tb_init();
340 }
341 break;
342 case 'G':
343 view->selected = view->length - 1;
344 break;
345 case 'g':
346 if (client.g) {
347 view->selected = 0;
348 client.g = 0;
349 break;
350 }
351 client.g = 1;
352 break;
353 }
354
355 return 0;
356 }
357