💾 Archived View for gemini.rmf-dev.com › repo › Vaati › mz › files › 9f90752434064fbcdcf741f555480c1… captured on 2023-07-22 at 16:57:52. Gemini links have been rewritten to link to archived content

View Raw

More Information

-=-=-=-=-=-=-

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 #ifdef __linux__

16 #define _GNU_SOURCE

17 #else

18 #define _BSD_SOURCE

19 #endif

20 #include <stdio.h>

21 #include <stdlib.h>

22 #include <stdint.h>

23 #include <unistd.h>

24 #include <string.h>

25 #include <errno.h>

26 #include "termbox.h"

27 #include "view.h"

28 #include "client.h"

29 #include "file.h"

30 #include "strlcpy.h"

31 #include "wcwidth.h"

32 #include "utf8.h"

33 #include "trash.h"

34 #include "util.h"

35

36 #define TAB_WIDTH_LIMIT 20

37

38 struct client client;

39

40 static void display_errno() {

41 sstrcpy(client.info, strerror(errno));

42 client.error = 1;

43 }

44

45 static int display_tab(struct view *view, int x) {

46 char *ptr = strrchr(view->path, '/');

47 size_t length;

48 char buf[1024];

49 if (!ptr) ptr = view->path;

50 else ptr++;

51 length = AZ(utf8_width(ptr, sizeof(view->path) - (ptr - view->path)));

52 if (length > TAB_WIDTH_LIMIT) {

53 length = TAB_WIDTH_LIMIT;

54 strlcpy(buf, ptr, length + 1); /* sizeof buf > length */

55 ptr = buf;

56 }

57 tb_printf(x, 0, (client.view == view ? TB_DEFAULT : TB_BLACK),

58 (client.view == view ? TB_DEFAULT : TB_WHITE), " %s ", ptr);

59 return length + 2;

60 }

61

62 static int name_length(struct view *view) {

63 char *ptr = strrchr(view->path, '/');

64 if (!ptr) return -1;

65 ptr++;

66 return MAX(AZ(

67 utf8_width(ptr, sizeof(view->path) - (ptr - view->path))),

68 TAB_WIDTH_LIMIT);

69 }

70

71 int client_init() {

72

73 PZERO(&client);

74

75 client.view = view_init(getenv("PWD"));

76 if (!client.view || file_ls(client.view)) return -1;

77

78 client.trash = trash_init();

79 if (client.trash < 0) return -1;

80

81 if (tb_init()) return -1;

82 client.width = tb_width();

83 client.height = tb_height();

84

85 setenv("EDITOR", "vi", 0);

86

87 return 0;

88 }

89

90 int client_clean() {

91 free(client.copy);

92 free(client.view);

93 return tb_shutdown();

94 }

95

96 static void client_tabbar(struct view *view) {

97 struct view *ptr, *start;

98 int x, width, prev_sum, next_sum;

99

100 width = client.width - name_length(view) - 2;

101 if (width <= 0) {

102 start = view;

103 goto draw;

104 }

105

106 ptr = view;

107 if (!ptr->prev) {

108 start = ptr;

109 goto draw;

110 }

111 prev_sum = 0;

112 while (ptr->prev) {

113 ptr = ptr->prev;

114 prev_sum += name_length(ptr) + 2;

115 }

116

117 if (prev_sum < width/2) {

118 start = ptr;

119 goto draw;

120 }

121

122 ptr = view;

123 next_sum = 0;

124 while (ptr->next) {

125 ptr = ptr->next;

126 next_sum += name_length(ptr) + 2;

127 }

128

129 if (next_sum < width/2) {

130 x = client.width;

131 while (ptr) {

132 int len = name_length(ptr) + 2;

133 if (x - len < 0) {

134 ptr = ptr->next;

135 break;

136 }

137 x -= len;

138 if (!ptr->prev) break;

139 ptr = ptr->prev;

140 }

141 start = ptr;

142 goto draw;

143 }

144

145 ptr = view;

146 x = width/2;

147 while (ptr) {

148 int len = name_length(ptr) + 2;

149 if (x - len < 0) {

150 ptr = ptr->next;

151 break;

152 }

153 x -= len;

154 if (!ptr->prev) break;

155 ptr = ptr->prev;

156 }

157 start = ptr;

158 draw:

159 ptr = start;

160 x = 0;

161 while (ptr && x < (signed)client.width) {

162 x += display_tab(ptr, x);

163 ptr = ptr->next;

164 }

165

166 while (x < (signed)client.width) {

167 tb_set_cell(x, 0, ' ', TB_DEFAULT, TB_WHITE);

168 x++;

169 }

170 return;

171 }

172

173 int client_update() {

174

175 char counter[32];

176 struct view *view = client.view;

177 size_t i;

178

179 tb_clear();

180

181 /* display list view */

182 view_draw(view);

183

184 /* display input field, error and counter */

185 tb_print(0, client.height - 1, TB_DEFAULT,

186 client.error ? TB_RED : TB_DEFAULT,

187 client.error ? client.info : client.field);

188

189 snprintf(V(counter), "%d", client.counter);

190 if (client.counter)

191 tb_print(client.width - 8, client.height - 1,

192 TB_DEFAULT, TB_DEFAULT, counter);

193

194 /* display white status bar */

195 i = 0;

196 while (i < client.width) {

197 tb_set_cell(i, client.height - 2, ' ', TB_BLACK, TB_WHITE);

198 i++;

199 }

200 tb_print(0, client.height - 2, TB_BLACK, TB_WHITE, view->path);

201

202

203 /* display tabs bar if there's more than one tab */

204 if (TABS)

205 client_tabbar(view);

206

207 tb_present();

208

209 if (client_input()) return 1;

210 return 0;

211 }

212

213 static void addtab(struct view *new) {

214 if (client.view->next) {

215 new->next = client.view->next;

216 client.view->next->prev = new;

217 }

218 new->prev = client.view;

219 client.view->next = new;

220 if (new->fd != TRASH_FD)

221 new->selected = client.view->selected;

222 client.view = client.view->next;

223 }

224

225 static int newtab() {

226 struct view *new;

227 char *path;

228

229 path = client.view->path;

230 if (client.view->fd == TRASH_FD)

231 path = NULL;

232

233 if (path)

234 chdir(path);

235 new = view_init(path);

236

237 if (!new || file_ls(new)) {

238 display_errno();

239 return -1;

240 }

241 addtab(new);

242

243 return 0;

244 }

245

246 static int closetab() {

247

248 struct view *view = client.view;

249

250 if (!view) return -1;

251 client.view = NULL;

252 if (view->prev) {

253 view->prev->next = view->next;

254 client.view = view->prev;

255 }

256 if (view->next) {

257 view->next->prev = view->prev;

258 client.view = view->next;

259 }

260 if (client.view && client.view->fd != TRASH_FD &&

261 file_ls(client.view)) display_errno();

262

263 if (view->fd > 0)

264 close(view->fd);

265 file_free(view);

266 free(view->other);

267 free(view);

268 return client.view == NULL;

269 }

270

271 int parse_command() {

272

273 char *cmd = &client.field[1];

274

275 if (cmd[0] == 'q' && cmd[1] == '\0')

276 return closetab();

277 if (cmd[0] == 'q' && cmd[1] == 'a' && cmd[2] == '\0') {

278 while (!closetab()) ;

279 return 1;

280 }

281 if ((cmd[0] == 'n' && cmd[1] == 't' && cmd[2] == '\0') ||

282 !strncmp(cmd, "tabnew", sizeof(client.field) - 1))

283 return newtab();

284 if (cmd[0] == '!') {

285 fchdir(client.view->fd);

286 tb_shutdown();

287 if (system(&cmd[1])) sleep(1);

288 tb_init();

289 file_ls(client.view);

290 return 0;

291 }

292 if (!strncmp(cmd, "sh", sizeof(client.field) - 1)) {

293 fchdir(client.view->fd);

294 tb_shutdown();

295 system("$SHELL");

296 tb_init();

297 file_ls(client.view);

298 return 0;

299 }

300 if (!strncmp(cmd, "trash", sizeof(client.field) - 1)) {

301 struct view *v = malloc(sizeof(struct view));

302 if (!v || trash_view(v)) display_errno();

303 else addtab(v);

304 return 0;

305 }

306 if (!strncmp(cmd, "trash clear", sizeof(client.field) - 1)) {

307 if (trash_clear()) display_errno();

308 return 0;

309 }

310

311 snprintf(V(client.info), "Not a command: %s", cmd);

312 client.error = 1;

313 return 0;

314 }

315

316 static void client_select(int next) {

317

318 struct view *view = client.view;

319 size_t i;

320 int reset;

321

322 if (view->length < 1 || !*client.search)

323 return;

324

325 reset = 0;

326 i = view->selected + next;

327 while (i != view->selected || !next) {

328 if (!next) next = 1;

329 if (i == view->length) {

330 i = 0;

331 if (reset) break;

332 reset = 1;

333 }

334 if (strcasestr(view->entries[i].name, client.search)) {

335 view->selected = i;

336 break;

337 }

338 if (next < 0 && i == 0) i = view->length;

339 i += next;

340 }

341 }

342

343 int client_command(struct tb_event ev) {

344

345 int pos;

346

347 switch (ev.key) {

348 case TB_KEY_ESC:

349 client.mode = MODE_NORMAL;

350 client.field[0] = '\0';

351 return 0;

352 case TB_KEY_BACKSPACE2:

353 case TB_KEY_BACKSPACE:

354 pos = utf8_len(V(client.field));

355 if (pos > 1)

356 client.field[pos - utf8_last_len(V(client.field))] = 0;

357 else {

358 client.mode = MODE_NORMAL;

359 client.field[0] = '\0';

360 }

361 return 0;

362 case TB_KEY_ENTER:

363 pos = 0;

364 if (client.mode == MODE_COMMAND)

365 pos = parse_command();

366 client.mode = MODE_NORMAL;

367 client.field[0] = '\0';

368 return pos;

369 }

370

371 if (!ev.ch) return 0;

372

373 pos = utf8_len(client.field, sizeof(client.field) - 2);

374 pos += tb_utf8_unicode_to_char(&client.field[pos], ev.ch);

375 client.field[pos] = '\0';

376 if (client.mode == MODE_SEARCH) {

377 strlcpy(client.search, &client.field[1],

378 sizeof(client.search) - 2);

379 client_select(0);

380 }

381

382 return 0;

383 }

384

385 void client_reset() {

386 client.counter = client.g = client.y = 0;

387 }

388

389 int client_input() {

390 struct tb_event ev;

391 struct view *view = client.view;

392 size_t i = 0;

393 int ret;

394

395 ret = tb_poll_event(&ev);

396 if (ret != TB_OK && ret != TB_ERR_POLL) {

397 return -1;

398 }

399

400 if (ev.type == TB_EVENT_RESIZE) {

401 client.width = ev.w;

402 client.height = ev.h;

403 return 0;

404 }

405

406 if (ev.type != TB_EVENT_KEY) {

407 return 0;

408 }

409

410 if (client.mode == MODE_COMMAND || client.mode == MODE_SEARCH) {

411 return client_command(ev);

412 }

413

414 if (ev.key == TB_KEY_ESC) {

415 client_reset();

416 return 0;

417 }

418

419 if (ev.ch >= (client.counter ? '0' : '1') && ev.ch <= '9') {

420 int i = ev.ch - '0';

421 client.g = 0;

422 if (client.counter >= 100000000) return 0;

423 if (client.counter == 0 && i == 0) return 0;

424 client.counter = client.counter * 10 + i;

425 return 0;

426 }

427

428 if (ev.key == TB_KEY_ENTER) goto open;

429

430 switch (ev.ch) {

431 case 'j':

432 ADDMAX(view->selected, AZ(client.counter), view->length - 1);

433 client.counter = 0;

434 break;

435 case 'k':

436 SUBMIN(client.view->selected, AZ(client.counter), 0);

437 client.counter = 0;

438 break;

439 case 'l':

440 open:

441 view_open(view);

442 break;

443 case 'h':

444 {

445 char name[1024];

446 char *ptr = strrchr(view->path, '/');

447 if (!ptr)

448 break;

449 sstrcpy(name, ptr + 1);

450 if (file_up(view))

451 break;

452 file_ls(view);

453 view_select(view, name);

454 }

455 break;

456 case 'T':

457 case 't':

458 if (!client.g) break;

459 if (ev.ch == 'T') { /* backward */

460 if (view->prev) client.view = view->prev;

461 else {

462 while (view->next) view = view->next;

463 client.view = view;

464 }

465 } else { /* forward */

466 if (view->next) client.view = view->next;

467 else {

468 while (view->prev) view = view->prev;

469 client.view = view;

470 }

471 }

472 file_ls(client.view);

473 client_reset();

474 break;

475 case '.':

476 TOGGLE(view->showhidden);

477 file_ls(view);

478 break;

479 case '/': /* search */

480 case ':': /* command */

481 client.mode = ev.ch == '/' ? MODE_SEARCH: MODE_COMMAND;

482 client.error = 0;

483 client_reset();

484 client.field[0] = ev.ch;

485 client.field[1] = '\0';

486 break;

487 case 'n': /* next occurence */

488 client_select(1);

489 break;

490 case 'N': /* previous occurence */

491 client_select(-1);

492 break;

493 case 'e':

494 if (EMPTY(view) || SELECTED(view).type == DT_DIR)

495 break;

496 {

497 char buf[2048];

498 tb_shutdown();

499 chdir(view->path);

500 snprintf(V(buf), "$EDITOR \"%s/%s\"",

501 view->path, SELECTED(view).name);

502 system(buf);

503 tb_init();

504 }

505 break;

506 case 'G':

507 view->selected = view->length - 1;

508 break;

509 case 'g':

510 if (client.g) {

511 view->selected = 0;

512 client.g = 0;

513 break;

514 }

515 client.g = 1;

516 break;

517 case 'r': /* restore */

518 if (view->fd != TRASH_FD) break;

519 if (trash_restore(view)) display_errno();

520 if (trash_refresh(view)) display_errno();

521 break;

522 case 'd': /* delete (move to trash) */

523 {

524 size_t i = 0;

525 while (i < view->length) {

526 size_t j = i++;

527 if (!view->entries[j].selected) continue;

528 if (trash_send(view->fd, view->path,

529 view->entries[j].name)) {

530 display_errno();

531 view_unselect(view);

532 break;

533 }

534 view->entries[j].selected = 0;

535 }

536 }

537 file_ls(view);

538 if (view->selected >= view->length)

539 view->selected = view->length - 1;

540 break;

541 case 'p': /* paste */

542 if (!client.copy_length) break;

543 i = 0;

544 while (i < client.copy_length) {

545 if (client.cut ?

546 file_move_entry(view, &client.copy[i]) :

547 file_copy_entry(view, &client.copy[i]))

548 display_errno();

549 i++;

550 }

551 free(client.copy);

552 client.copy = NULL;

553 client.copy_length = 0;

554 file_ls(view);

555 file_reload(view);

556 break;

557 case 'x': /* cut */

558 case 'c': /* copy */

559 {

560 size_t i = 0, j = 0, length = 0;

561 while (i < view->length) {

562 if (view->entries[i].selected) length++;

563 i++;

564 }

565 if (!length) break;

566 free(client.copy);

567 client.copy = malloc(sizeof(struct entry) * length);

568 sstrcpy(client.copy_path, view->path);

569 client.copy_length = length;

570 i = 0;

571 while (i < view->length) {

572 if (view->entries[i].selected) {

573 client.copy[j] = view->entries[i];

574 view->entries[i].selected = 0;

575 j++;

576 }

577 i++;

578 }

579 client.cut = ev.ch == 'x';

580 }

581 break;

582 case 'y': /* copy selection path to clipboard */

583 if (EMPTY(view))

584 break;

585 if (!client.y) {

586 client.y = 1;

587 break;

588 }

589 {

590 char buf[2048];

591 client.y = 0;

592 if (!system("xclip >/dev/null 2>&1")) {

593 snprintf(V(buf),

594 "printf \"%s/%s\" | xclip -sel clip >/dev/null 2>&1",

595 view->path, SELECTED(view).name);

596 if (!system(buf)) break;

597 }

598 if (getenv("TMUX")) { /* try tmux if no xclip */

599 snprintf(V(buf), "printf \"%s/%s\" | tmux load-buffer -",

600 view->path, SELECTED(view).name);

601 system(buf);

602 }

603 }

604 break;

605 case ' ': /* select */

606 if (!EMPTY(view))

607 TOGGLE(SELECTED(view).selected);

608 break;

609 }

610

611 return 0;

612 }

613