💾 Archived View for gemini.rmf-dev.com › repo › Vaati › mz › files › 4d4cfd44d9c0cf3228b1230bba4a2f6… captured on 2023-12-28 at 15:51:32. Gemini links have been rewritten to link to archived content

View Raw

More Information

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

Go Back

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 STRCPY(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);

267 return client.view == NULL;

268 }

269

270 int parse_command() {

271

272 /* trim */

273 char *cmd = client.field + strnlen(V(client.field)) - 1;

274 for (; cmd >= client.field && (*cmd == ' ' || *cmd == '\t'); cmd--)

275 *cmd = '\0';

276

277 if (!STRCMP(client.field, ":q"))

278 return closetab();

279 if (!STRCMP(client.field, ":qa")) {

280 while (!closetab()) ;

281 return 1;

282 }

283 if (!STRCMP(client.field, ":nt") || !STRCMP(client.field, ":tabnew"))

284 return newtab();

285 if (!strncmp(client.field, V(":!") - 1)) { /* start with ":!" */

286 fchdir(client.view->fd);

287 tb_shutdown();

288 if (system(&client.field[2])) sleep(1);

289 tb_init();

290 file_ls(client.view);

291 return 0;

292 }

293 if (!STRCMP(client.field, ":sh")) {

294 fchdir(client.view->fd);

295 tb_shutdown();

296 system("$SHELL");

297 tb_init();

298 file_ls(client.view);

299 return 0;

300 }

301 if (!STRCMP(client.field, ":trash")) {

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

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

304 else addtab(v);

305 return 0;

306 }

307 if (!STRCMP(client.field, ":trash clear")) {

308 if (trash_clear()) display_errno();

309 return 0;

310 }

311

312 snprintf(V(client.info), "Not a command: %s", &client.field[1]);

313 client.error = 1;

314 return 0;

315 }

316

317 static void client_select(int next) {

318

319 struct view *view = client.view;

320 size_t i;

321 int reset;

322

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

324 return;

325

326 reset = 0;

327 i = view->selected + next;

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

329 if (!next) next = 1;

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

331 i = 0;

332 if (reset) break;

333 reset = 1;

334 }

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

336 view->selected = i;

337 break;

338 }

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

340 i += next;

341 }

342 }

343

344 int client_command(struct tb_event ev) {

345

346 int pos;

347

348 switch (ev.key) {

349 case TB_KEY_ESC:

350 client.mode = MODE_NORMAL;

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

352 return 0;

353 case TB_KEY_BACKSPACE2:

354 case TB_KEY_BACKSPACE:

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

356 if (pos > 1)

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

358 else {

359 client.mode = MODE_NORMAL;

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

361 }

362 return 0;

363 case TB_KEY_ENTER:

364 pos = 0;

365 if (client.mode == MODE_COMMAND)

366 pos = parse_command();

367 client.mode = MODE_NORMAL;

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

369 return pos;

370 }

371

372 if (!ev.ch) return 0;

373

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

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

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

377 if (client.mode == MODE_SEARCH) {

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

379 sizeof(client.search) - 2);

380 client_select(0);

381 }

382

383 return 0;

384 }

385

386 void client_reset() {

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

388 }

389

390 int client_input() {

391

392 struct tb_event ev;

393 struct view *view = client.view;

394 size_t i = 0;

395 int ret;

396

397 ret = tb_poll_event(&ev);

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

399 return -1;

400 }

401

402 switch (ev.type) {

403 case TB_EVENT_RESIZE:

404 client.width = ev.w;

405 client.height = ev.h;

406 return 0;

407 case TB_EVENT_KEY:

408 break;

409 default:

410 return 0;

411 }

412

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

414 return client_command(ev);

415 }

416

417 switch (ev.key) {

418 case TB_KEY_ARROW_DOWN:

419 ev.ch = 'j';

420 break;

421 case TB_KEY_ARROW_UP:

422 ev.ch = 'k';

423 break;

424 case TB_KEY_ARROW_LEFT:

425 ev.ch = 'h';

426 break;

427 case TB_KEY_ARROW_RIGHT:

428 ev.ch = 'l';

429 break;

430 case TB_KEY_PGDN:

431 client.counter = AZ(client.counter) * client.height;

432 ev.ch = 'j';

433 break;

434 case TB_KEY_PGUP:

435 client.counter = AZ(client.counter) * client.height;

436 ev.ch = 'k';

437 break;

438 case TB_KEY_ESC:

439 client_reset();

440 return 0;

441 }

442

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

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

445 client.g = 0;

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

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

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

449 return 0;

450 }

451

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

453

454 switch (ev.ch) {

455 case 'j':

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

457 client.counter = 0;

458 break;

459 case 'k':

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

461 client.counter = 0;

462 break;

463 case 'l':

464 open:

465 view_open(view);

466 break;

467 case 'h':

468 {

469 char name[1024];

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

471 if (!ptr)

472 break;

473 STRCPY(name, ptr + 1);

474 if (file_up(view))

475 break;

476 file_ls(view);

477 view_select(view, name);

478 }

479 break;

480 case 'T':

481 case 't':

482 if (!client.g) break;

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

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

485 else {

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

487 client.view = view;

488 }

489 } else { /* forward */

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

491 else {

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

493 client.view = view;

494 }

495 }

496 file_ls(client.view);

497 client_reset();

498 break;

499 case '.':

500 TOGGLE(view->showhidden);

501 file_ls(view);

502 break;

503 case '/': /* search */

504 case ':': /* command */

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

506 client.error = 0;

507 client_reset();

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

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

510 break;

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

512 client_select(1);

513 break;

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

515 client_select(-1);

516 break;

517 case 'e':

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

519 break;

520 {

521 char buf[PATH_MAX + 256];

522 if (view->fd == TRASH_FD) {

523 char path[PATH_MAX];

524 if (trash_rawpath(view, V(path))) break;

525 snprintf(V(buf), "$EDITOR \"%s\"", path);

526 } else {

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

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

529 chdir(view->path);

530 }

531 tb_shutdown();

532 system(buf);

533 tb_init();

534 }

535 break;

536 case 'G':

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

538 break;

539 case 'g':

540 if (client.g) {

541 view->selected = 0;

542 client.g = 0;

543 break;

544 }

545 client.g = 1;

546 break;

547 case 'r': /* restore */

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

549 if (trash_restore(view)) display_errno();

550 if (trash_refresh(view)) display_errno();

551 break;

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

553 {

554 size_t i = 0;

555 while (i < view->length) {

556 size_t j = i++;

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

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

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

560 display_errno();

561 view_unselect(view);

562 break;

563 }

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

565 }

566 }

567 file_ls(view);

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

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

570 break;

571 case 'p': /* paste */

572 if (!client.copy_length) break;

573 i = 0;

574 while (i < client.copy_length) {

575 if (client.cut ?

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

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

578 display_errno();

579 i++;

580 }

581 free(client.copy);

582 client.copy = NULL;

583 client.copy_length = 0;

584 file_ls(view);

585 file_reload(view);

586 break;

587 case 'x': /* cut */

588 case 'c': /* copy */

589 {

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

591 while (i < view->length) {

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

593 i++;

594 }

595 if (!length) break;

596 free(client.copy);

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

598 STRCPY(client.copy_path, view->path);

599 client.copy_length = length;

600 i = 0;

601 while (i < view->length) {

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

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

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

605 j++;

606 }

607 i++;

608 }

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

610 }

611 break;

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

613 if (EMPTY(view))

614 break;

615 if (!client.y) {

616 client.y = 1;

617 break;

618 }

619 {

620 char buf[2048];

621 client.y = 0;

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

623 snprintf(V(buf),

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

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

626 if (!system(buf)) break;

627 }

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

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

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

631 system(buf);

632 }

633 }

634 break;

635 case ' ': /* select */

636 if (!EMPTY(view))

637 TOGGLE(SELECTED(view).selected);

638 break;

639 }

640

641 return 0;

642 }

643