💾 Archived View for gemini.rmf-dev.com › repo › Vaati › mz › files › e26eecc3b5e79a273a6f9d91383a976… captured on 2023-09-08 at 16:43:35. 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 struct tb_event ev;

392 struct view *view = client.view;

393 size_t i = 0;

394 int ret;

395

396 ret = tb_poll_event(&ev);

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

398 return -1;

399 }

400

401 if (ev.type == TB_EVENT_RESIZE) {

402 client.width = ev.w;

403 client.height = ev.h;

404 return 0;

405 }

406

407 if (ev.type != TB_EVENT_KEY) {

408 return 0;

409 }

410

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

412 return client_command(ev);

413 }

414

415 if (ev.key == TB_KEY_ESC) {

416 client_reset();

417 return 0;

418 }

419

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

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

422 client.g = 0;

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

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

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

426 return 0;

427 }

428

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

430

431 switch (ev.ch) {

432 case 'j':

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

434 client.counter = 0;

435 break;

436 case 'k':

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

438 client.counter = 0;

439 break;

440 case 'l':

441 open:

442 view_open(view);

443 break;

444 case 'h':

445 {

446 char name[1024];

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

448 if (!ptr)

449 break;

450 STRCPY(name, ptr + 1);

451 if (file_up(view))

452 break;

453 file_ls(view);

454 view_select(view, name);

455 }

456 break;

457 case 'T':

458 case 't':

459 if (!client.g) break;

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

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

462 else {

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

464 client.view = view;

465 }

466 } else { /* forward */

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

468 else {

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

470 client.view = view;

471 }

472 }

473 file_ls(client.view);

474 client_reset();

475 break;

476 case '.':

477 TOGGLE(view->showhidden);

478 file_ls(view);

479 break;

480 case '/': /* search */

481 case ':': /* command */

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

483 client.error = 0;

484 client_reset();

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

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

487 break;

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

489 client_select(1);

490 break;

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

492 client_select(-1);

493 break;

494 case 'e':

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

496 break;

497 {

498 char buf[PATH_MAX + 256];

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

500 char path[PATH_MAX];

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

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

503 } else {

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

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

506 chdir(view->path);

507 }

508 tb_shutdown();

509 system(buf);

510 tb_init();

511 }

512 break;

513 case 'G':

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

515 break;

516 case 'g':

517 if (client.g) {

518 view->selected = 0;

519 client.g = 0;

520 break;

521 }

522 client.g = 1;

523 break;

524 case 'r': /* restore */

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

526 if (trash_restore(view)) display_errno();

527 if (trash_refresh(view)) display_errno();

528 break;

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

530 {

531 size_t i = 0;

532 while (i < view->length) {

533 size_t j = i++;

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

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

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

537 display_errno();

538 view_unselect(view);

539 break;

540 }

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

542 }

543 }

544 file_ls(view);

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

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

547 break;

548 case 'p': /* paste */

549 if (!client.copy_length) break;

550 i = 0;

551 while (i < client.copy_length) {

552 if (client.cut ?

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

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

555 display_errno();

556 i++;

557 }

558 free(client.copy);

559 client.copy = NULL;

560 client.copy_length = 0;

561 file_ls(view);

562 file_reload(view);

563 break;

564 case 'x': /* cut */

565 case 'c': /* copy */

566 {

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

568 while (i < view->length) {

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

570 i++;

571 }

572 if (!length) break;

573 free(client.copy);

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

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

576 client.copy_length = length;

577 i = 0;

578 while (i < view->length) {

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

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

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

582 j++;

583 }

584 i++;

585 }

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

587 }

588 break;

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

590 if (EMPTY(view))

591 break;

592 if (!client.y) {

593 client.y = 1;

594 break;

595 }

596 {

597 char buf[2048];

598 client.y = 0;

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

600 snprintf(V(buf),

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

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

603 if (!system(buf)) break;

604 }

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

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

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

608 system(buf);

609 }

610 }

611 break;

612 case ' ': /* select */

613 if (!EMPTY(view))

614 TOGGLE(SELECTED(view).selected);

615 break;

616 }

617

618 return 0;

619 }

620