2 * Copyright (C) 2024 Mikulas Patocka
4 * This file is part of Ajla.
6 * Ajla is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
11 * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * Ajla. If not, see <https://www.gnu.org/licenses/>.
28 fn view_init(ro : acmd_ro, file_name : string, file : handle, w : world, app : appstate, id : wid) : (world, appstate, view_state);
29 fn view_redraw(app : appstate, curs : curses, com : widget_common, st : view_state) : curses;
30 fn view_get_cursor(app : appstate, com : widget_common, st : view_state) : (int, int);
31 fn view_process_event(w : world, app : appstate, com : widget_common, st : view_state, wev : wevent) : (world, appstate, widget_common, view_state);
33 const view_class ~flat := widget_class.[
38 get_cursor : view_get_cursor,
39 process_event : view_process_event,
47 const buffer_size := 65536;
49 record line_cache_entry [
55 option search_string [
60 record search_request [
63 case_sensitive : bool;
68 record view_async_state [
80 block_cache_pos : int;
82 line_cache : list(line_cache_entry);
84 file_pos_changed : bool;
92 lines_offset_goto_line : bool;
95 srch : search_request;
101 async_event_fn : fn(w : world, wev : wevent) : world;
102 async_last_position_percent : int;
113 async_search_active : bool;
114 async_position_percent : int;
115 async : view_async_state;
119 lines : list(string);
120 lines_hi : list(int);
125 lines_delta_goto_line : bool;
128 srch : search_request;
133 last_key : event_keyboard;
136 fn view_init_async_state(implicit st : view_state) : view_state
138 st.async_active := false;
139 st.async_search_active := false;
140 st.async_position_percent := -1;
141 st.async := view_async_state.[
147 line_cache : infinite_uninitialized(line_cache_entry),
149 file_pos_changed : true,
153 lines : empty(string),
154 lines_hi : empty(int),
157 lines_offset_goto_line : false,
160 highlight_pos : st.highlight_pos,
161 highlight_len : st.highlight_len,
163 async_last_position_percent : -1,
167 fn view_set_charset(ro : acmd_ro, implicit app : appstate, implicit st : view_state) : view_state
169 var b := property_get(app, "view-charset").b;
170 var loc := locale_get("." + b);
171 if is_exception loc then
176 fn view_init(implicit ro : acmd_ro, file_name : string, file : handle, implicit w : world, implicit app : appstate, id : wid) : (world, appstate, view_state)
178 property_observe(id, "view-charset");
179 implicit var st := view_state.[
181 file_name : file_name,
187 lines : empty(string),
188 lines_hi : empty(int),
193 lines_delta_goto_line : false,
199 last_key : event_keyboard. [ key : 0, flags : 0, rep : 1 ],
202 view_init_async_state();
205 fn hex_chars_per_line(x : int) : int
210 var l := 8 + n * 3 + ((n shr 2) - 1) * 2 + 2 + n;
218 fn view_set_fkeys(implicit app : appstate, implicit com : widget_common) : appstate
220 if widget_is_top(com.self) then [
221 var wrap := property_get("view-wrap").o;
222 var hex := property_get("view-hex").o;
223 property_set("fkeys", property.l.([
225 property.s.(select(hex, select(wrap, `Wrap`, `Unwrap`), ``)),
227 property.s.(select(hex, `Hex`, `Ascii`)),
229 property.s.(`Charset`),
230 property.s.(`Search`),
238 fn view_report_position~inline(implicit as : view_async_state, pos : int) : view_async_state
240 var percent := select(as.b_size <> 0, 100, pos * 100 div as.b_size);
241 if percent <> as.async_last_position_percent then [
242 xeval as.async_event_fn(as.w, wevent.set_property.(event_set_property.[ prop : "view-percent", val : property.i.(percent) ]));
243 as.async_last_position_percent := percent;
247 fn view_read_cached(implicit as : view_async_state, pos : int) : (view_async_state, bytes)
249 view_report_position(pos);
251 if pos >= as.block_cache_pos, pos < as.block_cache_pos + len(as.block_cache) then [
252 var offset := pos - as.block_cache_pos;
253 return as.block_cache[offset .. ] + bread_lazy~lazy(as.file, as.block_cache_pos + len(as.block_cache));
256 var bc := bread_lazy(as.file, pos);
257 if is_exception len_at_least(bc, buffer_size) then
259 if not len_greater_than(bc, 0) then
261 if len_at_least(bc, buffer_size) then [
262 as.block_cache := bc[ .. buffer_size];
263 as.block_cache_pos := pos;
264 return as.block_cache + bread_lazy~lazy(as.file, pos + buffer_size);
266 as.block_cache := bc;
267 as.block_cache_pos := pos;
268 return as.block_cache;
272 return bread_lazy(as.file, pos);
275 fn view_get_line_hex(implicit as : view_async_state, b : bytes, file_pos : int) : (view_async_state, string, int, bytes, int)
277 var xpos := ntos_base(file_pos, 16);
278 if len(xpos) > 8 then
279 xpos := xpos[len(xpos) - 8 .. ];
280 var lline := ascii_to_string(list_left_pad(xpos, 8, '0'));
284 var skip_next_byte := false;
285 var highlight_map := 0;
286 var highlight_map_2 := 0;
287 var prev_hl := false;
288 while len_greater_than(b, i), i < as.hex_chars do [
289 var hl := file_pos + i >= as.highlight_pos and file_pos + i < as.highlight_pos + as.highlight_len;
290 if i > 0, (i and 3) = 0 then [
291 if prev_hl and hl then [
292 highlight_map bts= len(lline);
293 highlight_map bts= len(lline) + 1;
295 lline += string.[ ' ', #2502 ];
297 if prev_hl and hl then [
298 highlight_map bts= len(lline);
303 highlight_map bts= len(lline);
304 highlight_map bts= len(lline) + 1;
306 lline +<= select(hx shr 4 >= 10, (hx shr 4) + '0', (hx shr 4) + ('A' - 10));
307 lline +<= select((hx and 15) >= 10, (hx and 15) + '0', (hx and 15) + ('A' - 10));
308 if not skip_next_byte then [
310 highlight_map_2 bts= i;
311 if i < uni_pos then [
314 var c, cl := locale_get_char(as.loc, b[i .. ]);
315 if c = error_char then
317 if char_to_unicode(c) < ' ' or char_to_unicode(c) = 127 then
320 var chl := char_length(c);
327 highlight_map_2 bts= i + 1;
328 skip_next_byte := true;
333 skip_next_byte := false;
339 while j < as.hex_chars do [
340 if j > 0, (j and 3) = 0 then
345 var line := lline + ` `;
346 highlight_map_2 shl= len(line);
348 return line, highlight_map or highlight_map_2, b[i .. ], file_pos + i;
351 fn view_get_line(implicit as : view_async_state, b : bytes, file_pos : int, wrap : bool, report : bool) : (view_async_state, string, int, bytes, int)
354 return view_get_line_hex(b, file_pos);
356 var tm := as.line_cache[file_pos];
357 if not is_uninitialized(tm) then [
358 return tm.line, tm.highlight_map, b[tm.length .. ], file_pos + tm.length;
360 var bytes_processed := 0;
361 var line_out := empty(char);
363 var highlight_map := 0;
364 while len_greater_than(b, 0) do [
365 var c, i := locale_get_char(as.loc, b);
367 var n := 8 - (line_pos and 7);
368 if wrap, line_pos > 0, line_pos + n > as.size_x then
370 line_out += list_repeat(` `, n);
376 bytes_processed += i;
380 if len_greater_than(b, 1), b[1] = 10 then [
382 bytes_processed += 2;
386 if c = error_char then
388 if char_to_unicode(c) < ' ' or char_to_unicode(c) = 127 then
390 var cl := char_length(c);
393 if wrap, line_pos > 0, line_pos + cl > as.size_x then
395 if as.highlight_len <> 0 then [
396 if file_pos + bytes_processed >= as.highlight_pos, file_pos + bytes_processed + i <= as.highlight_pos + as.highlight_len then
398 highlight_map bts= line_pos + x;
404 bytes_processed += i;
406 view_report_position(file_pos + bytes_processed);
408 as.line_cache[file_pos] := line_cache_entry.[ length : bytes_processed, line : line_out, highlight_map : highlight_map ];
409 return line_out, highlight_map, b, file_pos + bytes_processed;
412 fn is_word~inline(c : char) := c >= '0' and c <= '9' or c = '_';
414 fn as_set_pos(implicit as : view_async_state, p : int, l : int) : view_async_state
416 if as.srch.whole_words then [
417 var after := view_read_cached(p + l);
418 if len_greater_than(after, 0) then [
419 if as.srch.str is b then [
420 if is_word(after[0]) or
421 after[0] >= 'A' and after[0] <= 'Z' or
422 after[0] >= 'a' and after[0] <= 'z' then
425 var c, l := locale_get_char(as.loc, after);
426 if is_word(char_to_unicode(c)) or char_upcase(c) <> c or char_locase(c) <> c then
431 if as.srch.str is b then [
432 var before := view_read_cached(p - 1);
433 if len_greater_than(before, 0) then [
434 if is_word(before[0]) or
435 before[0] >= 'A' and before[0] <= 'Z' or
436 before[0] >= 'a' and before[0] <= 'z' then
441 prev := min(p, prev);
442 var before := view_read_cached(p - prev);
443 if len_greater_than(before, prev) then
444 before := before[ .. prev];
445 var s := locale_to_string(as.loc, before);
446 if len_greater_than(s, 1) then [
447 var c := s[len(s) - 1];
448 if is_word(char_to_unicode(c)) or char_upcase(c) <> c or char_locase(c) <> c then
454 as.file_pos_changed := true;
456 as.highlight_pos := p;
457 as.highlight_len := l;
460 fn view_do_search_forward(implicit as : view_async_state) : view_async_state
462 as.file_pos_changed := false;
463 var cs := as.srch.case_sensitive;
464 var stream := view_read_cached(as.srch.pos);
465 if as.srch.str is b then [
467 var b := as.srch.str.b;
469 while len_at_least(stream, ln) do [
471 for i := 0 to ln do [
472 if stream[i] <> b[i] then
476 for i := 0 to ln do [
477 if ascii_upcase(stream[i]) <> ascii_upcase(b[i]) then
481 as_set_pos(as.srch.pos + offs, ln);
482 if as.file_pos_changed then
486 stream := stream[1 .. ];
487 view_report_position(as.srch.pos + offs);
489 ] else if as.srch.str is s then [
491 var b := as.srch.str.s;
493 var cc := empty(char);
494 var ll := empty(int);
496 var c, l := locale_get_char(as.loc, stream);
499 stream := stream[l .. ];
505 for i := 0 to ln do [
506 if cc[i] <> b[i] then
508 if cc[i] = error_char then
512 for i := 0 to ln do [
513 if char_upcase(cc[i]) <> char_upcase(b[i]) then
515 if cc[i] = error_char then
519 as_set_pos(as.srch.pos + offs, list_fold_monoid(ll));
520 if as.file_pos_changed then
526 view_report_position(as.srch.pos + offs);
531 fn view_do_search_backward(implicit as : view_async_state) : view_async_state
533 as.file_pos_changed := false;
534 var cs := as.srch.case_sensitive;
535 var pos := as.srch.pos;
536 if as.srch.str is b then [
537 var b := as.srch.str.b;
540 var this_step := buffer_size;
541 this_step := max(this_step, ln);
542 this_step := min(this_step, pos);
543 if this_step < ln then
545 var stream := view_read_cached(pos - this_step);
546 if not len_at_least(stream, this_step) then
548 stream := stream[ .. this_step];
549 for offs := 0 to this_step - ln + 1 do [
551 for i := 0 to ln do [
552 if stream[offs + i] <> b[i] then
556 for i := 0 to ln do [
557 if ascii_upcase(stream[offs + i]) <> ascii_upcase(b[i]) then
561 as_set_pos(pos - this_step + offs, ln);
564 if as.file_pos_changed then
566 pos -= this_step - ln + 1;
568 ] else if as.srch.str is s then [
569 var b := as.srch.str.s;
571 var bin_ln := len(string_to_locale(as.loc, b));
573 var this_step := buffer_size;
574 this_step := max(this_step, bin_ln);
575 this_step := min(this_step, pos);
576 var stream := view_read_cached(pos - this_step);
577 if not len_at_least(stream, this_step) then
579 stream := stream[ .. this_step];
580 var cc := empty(char);
581 var ll := empty(int);
584 var c, l := locale_get_char(as.loc, stream);
587 stream := stream[l .. ];
593 for i := 0 to ln do [
594 if cc[i] <> b[i] then
596 if cc[i] = error_char then
600 for i := 0 to ln do [
601 if char_upcase(cc[i]) <> char_upcase(b[i]) then
603 if cc[i] = error_char then
607 as_set_pos(pos - this_step + offs, list_fold_monoid(ll));
613 if as.file_pos_changed then
615 if this_step = bin_ln - 1 then
617 pos -= this_step - bin_ln + 1;
622 fn view_file_pos_changed(implicit as : view_async_state) : view_async_state
626 var pos := as.file_pos;
628 var bs := min(pos, buffer_size);
629 var str := view_read_cached(pos - bs);
633 if (i = -1 and pos = bs) or (i >= 0 and str[i] = 10) then [
636 var fpos := pos - bs + i + 1;
638 var fstr := view_read_cached(fpos);
639 while fpos < as.file_pos do [
640 var line_out : string;
641 var highlight_map : int;
642 line_out, highlight_map, fstr, fpos := view_get_line(fstr, fpos, true, true);
643 if fpos <= as.file_pos then
646 as.file_pos := new_pos;
649 as.file_pos := pos - bs + i + 1;
652 ] else if as.wrap, not is_uninitialized(as.line_cache[pos - bs + i + 1]) then [
662 fn view_do_find_line(implicit as : view_async_state) : view_async_state
664 var n_lines := as.lines_offset;
665 as.lines_offset := 0;
666 var pos := as.file_pos;
667 if as.hex, not as.lines_offset_goto_line then [
668 pos += n_lines * as.hex_chars;
671 if pos > as.b_size then
672 pos -= (pos - as.b_size) div as.hex_chars * as.hex_chars;
677 var wrap := as.wrap and not as.lines_offset_goto_line;
678 if n_lines > 0 then [
680 var str := view_read_cached(pos);
681 while n_lines > 0 do [
682 var line_out : string;
683 var highlight_map : int;
684 line_out, highlight_map, str, pos := view_get_line(str, pos, true, true);
689 var last_line := pos;
691 var str := view_read_cached(pos);
692 var bs := buffer_size;
693 if not len_at_least(str, bs) then
700 for i := 0 to bs do [
701 if str[i] = 10 then [
702 last_line := pos + i + 1;
703 if n_lines = 1 then [
712 ] else if n_lines < 0 then [
716 var bs := min(pos, buffer_size);
717 var str := view_read_cached(pos - bs);
721 if (i = -1 and pos = bs) or (i >= 0 and str[i] = 10) then [
724 var fpos := pos - bs + i + 1;
725 var fstr := view_read_cached(fpos);
726 var line_positions := empty(int);
727 while fpos < last_pos do [
728 var line_out : string;
729 var highlight_map : int;
730 line_positions +<= fpos;
731 line_out, highlight_map, fstr, fpos := view_get_line(fstr, fpos, true, false);
733 if len(line_positions) >= n_lines then [
734 pos := line_positions[len(line_positions) - n_lines];
737 n_lines -= len(line_positions);
738 last_pos := pos - bs + i + 1;
740 if n_lines = 0 then [
741 pos := pos - bs + i + 1;
746 ] else if wrap, not is_uninitialized(as.line_cache[pos - bs + i + 1]) then [
758 fn view_do_format(implicit as : view_async_state) : view_async_state
761 var b := view_read_cached(as.file_pos);
762 var file_pos := as.file_pos;
764 var result := empty(string);
765 var result_hi := empty(int);
767 while len_greater_than(b, 0), len(result) < as.size_y - 1 do [
768 var line_out : string;
769 var highlight_map : int;
770 line_out, highlight_map, b, file_pos := view_get_line(b, file_pos, as.wrap, true);
772 result_hi +<= highlight_map;
775 if len(result) < as.size_y - 1, as.file_pos > 0 then [
776 as.lines_offset := -(as.size_y - 1 - len(result));
781 as.end_pos := file_pos;
783 as.lines_hi := result_hi;
785 if not as.wrap, not as.hex, not is_uninitialized_record(as.srch) then [
786 for i := 0 to len(result_hi) do [
787 if result_hi[i] <> 0 then [
788 var st := bsf result_hi[i];
789 var en := st + popcnt result_hi[i];
791 if en > as.size_x then [
792 offs := en - as.size_x;
804 fn view_format_async(implicit w : world, implicit app : appstate, implicit as : view_async_state, self : wid, seq : int) : view_async_state
807 as.async_event_fn := widget_get_async_event_function(self);
808 as.b_size := bsize_lazy(as.file);
809 if not is_uninitialized_record(as.srch) then [
810 as.line_cache := infinite_uninitialized(line_cache_entry);
811 if not as.srch.backwards then
812 view_do_search_forward();
814 view_do_search_backward();
816 if as.file_pos_changed then [
817 if not is_uninitialized_record(as.srch), as.hex then [
818 as.file_pos -= as.file_pos mod as.hex_chars;
820 view_file_pos_changed();
821 as.file_pos_changed := false;
826 eval len(as.lines_hi);
829 xeval as.async_event_fn(wevent.set_property.(event_set_property.[ prop : "view-async-finished", val : property.i.(seq) ]));
831 xeval len(as.lines_hi);
836 fn view_format_finish(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : view_state) : (world, appstate, view_state)
838 if is_exception st.async then [
839 if widget_is_top(com.self) then
840 acmd_error(`Error reading the file ` + st.file_name, locale_to_string(st.ro.loc, exception_string(st.async)));
841 view_init_async_state();
843 st.async_active := false;
844 st.async_search_active := false;
845 st.async_position_percent := -1;
846 st.lines := st.async.lines;
847 st.lines_hi := st.async.lines_hi;
848 st.file_pos := st.async.file_pos;
849 st.end_pos := st.async.end_pos;
850 st.x_offset += st.async.x_offset;
851 if st.x_offset < 0 then
853 st.highlight_pos := st.async.highlight_pos;
854 st.highlight_len := st.async.highlight_len;
857 fn view_format(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : view_state) : (world, appstate, view_state)
859 if st.async_active then
862 var wrap := property_get("view-wrap").o;
863 var hex := property_get("view-hex").o;
867 if st.whence <> -1 then [
868 st.async.file_pos := st.whence;
869 st.async.file_pos_changed := true;
872 st.async.lines_offset := st.lines_delta;
874 st.async.lines_offset_goto_line := st.lines_delta_goto_line;
875 st.lines_delta_goto_line := false;
876 st.async.x_offset := st.x_delta;
878 st.async.srch := st.srch;
879 st.srch := uninitialized_record(search_request);
880 st.async.wrap := wrap;
882 st.async.size_x := com.size_x;
883 st.async.size_y := com.size_y;
884 st.async.hex_chars := hex_chars_per_line(com.size_x);
886 st.async.highlight_pos := st.highlight_pos;
887 st.async.highlight_len := st.highlight_len;
889 st.async := view_format_async~spark(st.async, com.self, st.async_seq);
890 var timer := sleep~lazy(unit_value, 20000);
891 if not any(st.async, timer) then [
892 view_format_finish();
895 st.async_active := true;
898 fn view_redraw(implicit app : appstate, implicit curs : curses, com : widget_common, st : view_state) : curses
900 var hex := property_get("view-hex").o;
902 property_set_attrib(property_get_attrib("acmd-viewer-caption", #0000, #0000, #0000, #0000, #aaaa, #aaaa, 0, curses_invert));
903 curses_fill_rect(0, com.size_x, 0, 1, ' ');
904 curses_set_pos(0, 0);
905 curses_print(st.file_name);
907 var b_size := bsize_lazy(st.file);
909 var fmt1 := `formatting`;
910 var fmt2 := `searching`;
911 var fmt_len := max(string_length(fmt1), string_length(fmt2));;
912 var cpos := ascii_to_string(ntos(st.end_pos)) + `/` + ascii_to_string(ntos(b_size)) + ` `;
914 if st.async_active then [
915 astr := select(st.async_search_active, fmt1, fmt2);
917 if string_length(astr) < fmt_len then [
918 astr += list_repeat(` `, fmt_len - string_length(astr));
922 if st.async_active, st.async_position_percent <> -1 then
923 cpos += ascii_to_string(list_left_pad(ntos(st.async_position_percent), 3, ' '));
925 cpos += ascii_to_string(list_left_pad(ntos(select(b_size <> 0, 100, st.end_pos * 100 div b_size)), 3, ' '));
927 curses_set_pos(com.size_x - string_length(cpos), 0);
930 property_set_attrib(property_get_attrib("acmd-viewer", #aaaa, #aaaa, #aaaa, #0000, #0000, #aaaa, 0, 0));
931 curses_fill_rect(0, com.size_x, 1, com.size_y, ' ');
932 for j := 0 to min(com.size_y - 1, len(st.lines)) do [
933 curses_set_pos(-st.x_offset, j + 1);
934 curses_print(st.lines[j]);
936 property_set_attrib(property_get_attrib("acmd-viewer-hexpos", #ffff, #ffff, #0000, #0000, #0000, #aaaa, 0, 0));
937 curses_recolor_rect(-st.x_offset, -st.x_offset + 8, j + 1, j + 2);
938 property_set_attrib(property_get_attrib("acmd-viewer", #aaaa, #aaaa, #aaaa, #0000, #0000, #aaaa, 0, 0));
940 var hi := st.lines_hi[j];
942 property_set_attrib(property_get_attrib("acmd-viewer-found", #ffff, #ffff, #0000, #0000, #aaaa, #aaaa, 0, 0));
946 curses_recolor_rect(h - st.x_offset, h - st.x_offset + 1, j + 1, j + 2);
948 property_set_attrib(property_get_attrib("acmd-viewer", #aaaa, #aaaa, #aaaa, #0000, #0000, #aaaa, 0, 0));
953 fn view_get_cursor(app : appstate, com : widget_common, st : view_state) : (int, int)
958 fn view_do_goto(implicit w : world, implicit app : appstate, implicit st : view_state, text : string, mode : int) : (world, appstate, int, int)
960 var bstr := string_to_locale(st.ro.loc, text);
961 var num := ston_base(bstr, select(mode = view_goto_mode_hexadecimal_offset, 10, 16));
962 if is_exception num or num < 0 then [
964 acmd_error(`Invalid number`, ``);
967 if mode = view_goto_mode_line then [
971 ] else if mode = view_goto_mode_percents then [
974 return bsize_lazy(st.file) * num div 100, 0;
980 fn view_send_goto(implicit app : appstate, implicit com : widget_common) : appstate
982 widget_enqueue_event(com.self, wevent.set_property.(event_set_property.[
988 fn view_goto_dialog_layout(srch : bool, implicit app : appstate, ids : list(wid), offs_x offs_y min_x pref_x max_x : int) : (appstate, int, int)
994 xs := max(xs, widget_get_width(ids[0], pref_x));
995 for i := 1 to e - 2 do
996 xs := max(xs, widget_get_width(ids[i], pref_x));
997 xs := max(xs, widgets_get_width(ids[e - 2 .. ], 2, pref_x));
999 xs := min(xs, max_x);
1000 xs := max(xs, min_x);
1002 var yp := offs_y + 1;
1003 yp := widget_place(ids[0], offs_x, xs, yp);
1005 for i := 1 to e - 2 do
1006 yp := widget_place(ids[i], offs_x, xs, yp);
1008 yp := widgets_place(ids[e - 2 .. ], widget_align.center, 2, 1, offs_x, xs, yp);
1013 fn view_goto(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : view_state) : (world, appstate, widget_common, view_state)
1015 var entries := [ dialog_entry.[
1017 init : input_init("", "view-goto-text", true,,,),
1019 cls : checkbox_class,
1020 init : checkbox_init(`Line number`, "", "view-goto-mode", true, view_goto_mode_line,,,),
1022 cls : checkbox_class,
1023 init : checkbox_init(`Percents`, "", "view-goto-mode", true, view_goto_mode_percents,,,),
1025 cls : checkbox_class,
1026 init : checkbox_init(`Decimal offset`, "", "view-goto-mode", true, view_goto_mode_decimal_offset,,,),
1028 cls : checkbox_class,
1029 init : checkbox_init(`Hexadecimal offset`, "", "view-goto-mode", true, view_goto_mode_hexadecimal_offset,,,),
1032 init : button_init(`OK`, true, "", button_no_action, lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ view_send_goto(); widget_destroy_onclick(id); ],,,),
1033 hotkeys : treeset_from_list([ key_enter ]),
1036 init : button_init(`Cancel`, true, "", button_no_action, lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ widget_destroy_onclick(id); ],,,),
1037 hotkeys : treeset_from_list([ key_esc, key_f10 ]),
1040 var winid := widget_new_window(dialog_class, dialog_init(`Go to`, entries, 0, dialog_no_event, view_goto_dialog_layout(false,,,,,,,), "",,,), false);
1043 fn view_send_charset(chs : bytes, implicit app : appstate) : appstate
1045 property_set("view-charset", property.b.(chs));
1048 fn view_charset_dialog_layout(implicit app : appstate, ids : list(wid), offs_x offs_y min_x pref_x max_x : int) : (appstate, int, int)
1054 xs := max(xs, widget_get_width(ids[i], pref_x));
1056 var yp := offs_y + 1;
1058 yp := widget_place(ids[i], offs_x, xs, yp);
1065 fn view_charset_dialog_event(implicit w : world, implicit app : appstate, implicit com : widget_common, wev : wevent, ids : list(wid)) : (world, appstate, widget_common, bool)
1068 if wev is keyboard then [
1069 if wev.keyboard.key = key_home then [
1073 if wev.keyboard.key = key_end then [
1074 idx := len(ids) - 1;
1077 if wev.keyboard.key = key_left or
1078 wev.keyboard.key = key_right or
1079 wev.keyboard.key = key_tab then [
1082 if wev.keyboard.key = key_up or
1083 wev.keyboard.key = key_down or
1084 wev.keyboard.key = key_page_up or
1085 wev.keyboard.key = key_page_down then [
1087 var top := com.sub_widgets[len(com.sub_widgets) - 1];
1089 if ids[idx] = top then
1093 if wev.keyboard.key = key_up then
1094 idx := max(idx - 1, 0);
1095 if wev.keyboard.key = key_down then
1096 idx := min(idx + 1, len(ids) - 1);
1097 if wev.keyboard.key = key_page_up then
1098 idx := max(idx - (com.size_y - 4), 0);
1099 if wev.keyboard.key = key_page_down then
1100 idx := min(idx + (com.size_y - 4), len(ids) - 1);
1107 widget_activate(ids[idx]);
1111 fn view_charset(implicit w : world, app : appstate, st : view_state) : (world, appstate)
1113 var charsets := charset_list;
1115 for i := 0 to len(charsets) do [
1116 m := max(m, string_length(charsets[i].label));
1119 var entries := empty(dialog_entry);
1120 for i := 0 to len(charsets) do [
1121 var padding := m - string_length(charsets[i].label);
1122 var label := charsets[i].label + fill(char, ' ', padding);
1123 var chs := charsets[i].mime_name;
1124 if chs = locale_get_charset(st.ro.loc).mime_name then
1126 var de := dialog_entry.[
1128 init : button_init(label, false, "",
1129 lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ if widget_is_top(id) then view_send_charset(chs); ],
1131 lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ view_send_charset(chs); widget_destroy_onclick(id); ],,,),
1133 if charsets[i].mime_name = locale_get_charset(st.loc).mime_name then [
1134 de.hotkeys := treeset_from_list([ key_esc, key_f10 ]);
1135 init := len(entries);
1140 app, winid := widget_new_window(w, app, dialog_class, dialog_init(`Character set`, entries, init, view_charset_dialog_event, view_charset_dialog_layout, "",,,), false);
1144 fn view_search_decode_hex(s : string) : bytes
1146 s := list_replace_substring(s, ` `, ``);
1147 var a := string_to_ascii(s);
1148 if len(a) bt 0 or list_search(a, '-') >= 0 then
1150 var b := empty(byte);
1151 while len(a) >= 2 do [
1152 var c := ston_base(a[ .. 2], 16);
1160 fn view_send_search(implicit app : appstate, implicit com : widget_common) : appstate
1162 widget_enqueue_event(com.self, wevent.set_property.(event_set_property.[
1163 prop : "view-search",
1168 fn view_search(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : view_state, key : char) : (world, appstate, widget_common, view_state)
1170 properties_backup([ "view-search-text", "view-search-mode", "view-search-flags" ]);
1171 if key = '/' or key = '?' then [
1172 var flags := property_get("view-search-flags").i;
1174 flags btr= view_search_flag_backwards;
1176 flags bts= view_search_flag_backwards;
1177 property_set("view-search-flags", property.i.(flags));
1179 var entries := [ dialog_entry.[
1181 init : input_init("", "view-search-text", true,,,),
1183 cls : checkbox_class,
1184 init : checkbox_init(`String`, "", "view-search-mode", true, view_search_mode_string,,,),
1186 cls : checkbox_class,
1187 init : checkbox_init(`Hexadecimal`, "", "view-search-mode", true, view_search_mode_hexadecimal,,,),
1189 cls : checkbox_class,
1190 init : checkbox_init(`Case sensitive`, "", "view-search-flags", false, view_search_flag_case_sensitive,,,),
1192 cls : checkbox_class,
1193 init : checkbox_init(`Backwards`, "", "view-search-flags", false, view_search_flag_backwards,,,),
1195 cls : checkbox_class,
1196 init : checkbox_init(`Whole words`, "", "view-search-flags", false, view_search_flag_whole_words,,,),
1199 init : button_init(`OK`, true, "", button_no_action, lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ view_send_search(); widget_destroy_onclick(id); ],,,),
1200 hotkeys : treeset_from_list([ key_enter ]),
1203 init : button_init(`Cancel`, true, "", button_no_action, lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ properties_revert([ "view-search-text", "view-search-mode", "view-search-flags" ]); widget_destroy_onclick(id); ],,,),
1204 hotkeys : treeset_from_list([ key_esc, key_f10 ]),
1207 var winid := widget_new_window(dialog_class, dialog_init(`Search`, entries, 0, dialog_no_event, view_goto_dialog_layout(true,,,,,,,), "",,,), false);
1210 fn view_process_event(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : view_state, wev : wevent) : (world, appstate, widget_common, view_state)
1212 if wev is resize then [
1215 com.size_x := wev.resize.x;
1216 com.size_y := wev.resize.y - 1;
1217 view_init_async_state();
1218 st.whence := st.file_pos;
1219 st.lines_delta := 0;
1225 if wev is change_focus then [
1229 if wev is keyboard then [
1230 var k := wev.keyboard;
1231 if st.last_key.key = key_esc, k.key = key_esc then [
1232 widget_enqueue_event(com.self, wevent.close);
1235 if st.last_key.key = key_esc, k.key >= '0', k.key <= '9', k.flags = 0 then [
1236 var f := (k.key - '1' + 10) mod 10;
1237 k.key := key_f1 - f;
1239 if k.key >= '0', k.key <= '9', k.flags = key_flag_alt then [
1240 var f := (k.key - '1' + 10) mod 10;
1241 k.key := key_f1 - f;
1244 if k.key = key_f2 then [
1245 var hex := property_get("view-hex").o;
1248 var wrap := not property_get("view-wrap").o;
1249 property_set("view-wrap", property.o.(wrap));
1251 st.whence := st.file_pos;
1252 st.lines_delta := 0;
1253 st.x_delta := -st.x_offset;
1254 view_init_async_state();
1257 if k.key = key_f3 or k.key = key_f10 then [
1258 widget_enqueue_event(com.self, wevent.close);
1261 if k.key = key_f4 then [
1262 var hex := not property_get("view-hex").o;
1263 property_set("view-hex", property.o.(hex));
1265 st.whence := st.file_pos;
1266 st.lines_delta := 0;
1267 st.x_delta := -st.x_offset;
1268 view_init_async_state();
1271 if k.key = key_f5 then [
1275 if k.key = key_f6 then [
1279 if k.key = key_f7 or k.key = '/' or k.key = '?' then [
1283 if k.key = key_left or k.key = key_right then [
1286 if not property_get("view-wrap").o or property_get("view-hex").o then [
1287 st.x_delta += select(k.key = key_left, 1, -1) * k.rep;
1288 if st.async_active, st.async_search_active then [
1289 view_init_async_state();
1290 st.async.file_pos := st.file_pos;
1296 if k.key = key_home or k.key = 'A' and k.flags = key_flag_ctrl then [
1300 st.lines_delta := 0;
1301 st.x_delta := -st.x_offset;
1302 if st.async_active then
1303 view_init_async_state();
1306 if k.key = key_end or k.key = 'E' and k.flags = key_flag_ctrl then [
1309 st.x_delta := -st.x_offset;
1310 st.whence := bsize_lazy(st.file);
1311 if property_get("view-hex").o then
1312 st.whence -= st.whence mod hex_chars_per_line(com.size_x);
1313 if st.async_active, st.async_search_active then
1314 view_init_async_state();
1317 if k.key = key_up then [
1320 st.lines_delta -= k.rep;
1321 if st.async_active, st.async_search_active then [
1322 view_init_async_state();
1323 st.async.file_pos := st.file_pos;
1327 if k.key = key_down then [
1330 st.lines_delta += k.rep;
1331 if st.async_active, st.async_search_active then [
1332 view_init_async_state();
1333 st.async.file_pos := st.file_pos;
1337 if k.key = key_page_up or (k.key and not #20) = 'B' or k.key = 'B' and k.flags = key_flag_ctrl then [
1340 st.lines_delta -= (com.size_y - 1) * k.rep;
1341 if st.async_active, st.async_search_active then [
1342 view_init_async_state();
1343 st.async.file_pos := st.file_pos;
1347 if k.key = key_page_down or k.key = ' ' or k.key = 'F' and k.flags = key_flag_ctrl then [
1350 st.lines_delta += (com.size_y - 1) * k.rep;
1351 if st.async_active, st.async_search_active then [
1352 view_init_async_state();
1353 st.async.file_pos := st.file_pos;
1357 if k.key = 'n' or k.key = 'N' then [
1362 if wev is mouse then [
1363 if wev.mouse.wy <> 0 then [
1364 st.lines_delta += wev.mouse.wy * 5;
1365 if st.async_active, st.async_search_active then [
1366 view_init_async_state();
1367 st.async.file_pos := st.file_pos;
1373 if wev is property_changed then [
1374 view_set_charset(st.ro);
1375 st.whence := st.file_pos;
1376 st.lines_delta := 0;
1379 view_init_async_state();
1382 if wev is set_property then [
1383 if wev.set_property.prop = "view-goto" then [
1384 var text := property_get("view-goto-text").s;
1385 var mode := property_get("view-goto-mode").i;
1386 var new_pos, lines_delta := view_do_goto(text, mode);
1389 st.whence := new_pos;
1390 st.lines_delta := lines_delta;
1391 st.lines_delta_goto_line := lines_delta > 0;
1392 st.x_delta := -st.x_offset;
1393 if st.async_active then
1394 view_init_async_state();
1397 if wev.set_property.prop = "view-search" then [
1399 var text := property_get("view-search-text").s;
1400 var mode := property_get("view-search-mode").i;
1401 var flags := property_get("view-search-flags").i;
1402 var srch := search_request.[
1404 case_sensitive : flags bt view_search_flag_case_sensitive,
1405 backwards : flags bt view_search_flag_backwards,
1406 whole_words : flags bt view_search_flag_whole_words,
1408 if wev is keyboard then [
1409 if wev.keyboard.key = 'N' then
1410 srch.backwards := not srch.backwards;
1411 if st.highlight_len > 0 then [
1412 if srch.backwards then
1413 srch.pos := st.highlight_pos;
1415 srch.pos := st.highlight_pos + st.highlight_len;
1418 st.highlight_pos := 0;
1419 st.highlight_len := 0;
1421 if mode = view_search_mode_string then [
1422 srch.str := search_string.s.(text);
1423 if len(text) = 0 then [
1424 empty_search_string:
1425 acmd_error(`Empty search string`, ``);
1429 var hx := view_search_decode_hex(text);
1430 if is_exception hx then [
1431 acmd_error(`Invalid hex string`, ``);
1435 goto empty_search_string;
1436 srch.str := search_string.b.(hx);
1438 if st.async_active then [
1439 view_init_async_state();
1440 st.async.file_pos := st.file_pos;
1443 st.lines_delta := 0;
1444 st.x_delta := -st.x_offset;
1446 st.async_search_active := true;
1449 if wev.set_property.prop = "view-async-finished", st.async_active then [
1450 if wev.set_property.val.i = st.async_seq then [
1451 view_format_finish();
1452 if st.whence <> -1 or st.lines_delta <> 0 or st.x_delta <> 0 then
1457 if wev.set_property.prop = "view-percent", st.async_active then [
1458 var val := wev.set_property.val.i;
1459 st.async_position_percent := val;
1466 var was_async := st.async_active;
1468 if not st.async_active then
1470 if not was_async, st.async_active then [
1472 widget_enqueue_event(com.self, wevent.redraw.(event_redraw.[
1482 widget_enqueue_event(com.self, wevent.redraw.(event_redraw.[