Ajla 0.1.0
[ajla.git] / programs / acmd / view.ajla
blob18f27f0713c9f00e6167c9b0c7b9fe9a2a106921
1 {*
2  * Copyright (C) 2024 Mikulas Patocka
3  *
4  * This file is part of Ajla.
5  *
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
9  * version.
10  *
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.
14  *
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/>.
17  *}
19 unit view;
21 uses ui.widget;
23 uses defs;
24 uses common;
26 type view_state;
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.[
34         t : view_state,
35         name : "acmd view",
36         is_selectable : true,
37         redraw : view_redraw,
38         get_cursor : view_get_cursor,
39         process_event : view_process_event,
42 implementation
44 uses error;
45 uses exception;
47 const buffer_size := 65536;
49 record line_cache_entry [
50         length : int;
51         line : string;
52         highlight_map : int;
55 option search_string [
56         b : bytes;
57         s : string;
60 record search_request [
61         str : search_string;
62         pos : int;
63         case_sensitive : bool;
64         backwards : bool;
65         whole_words : bool;
68 record view_async_state [
69         ro : acmd_ro;
70         loc : locale;
71         file : handle;
73         wrap : bool;
74         hex : bool;
75         size_x : int;
76         size_y : int;
77         hex_chars : int;
79         block_cache : bytes;
80         block_cache_pos : int;
82         line_cache : list(line_cache_entry);
84         file_pos_changed : bool;
85         file_pos : int;
86         end_pos : int;
87         b_size : int;
88         lines : list(string);
89         lines_hi : list(int);
91         lines_offset : int;
92         lines_offset_goto_line : bool;
93         x_offset : int;
95         srch : search_request;
97         highlight_pos : int;
98         highlight_len : int;
100         w : world;
101         async_event_fn : fn(w : world, wev : wevent) : world;
102         async_last_position_percent : int;
105 record view_state [
106         ro : acmd_ro;
107         loc : locale;
108         file_name : string;
109         file : handle;
111         async_seq : int;
112         async_active : bool;
113         async_search_active : bool;
114         async_position_percent : int;
115         async : view_async_state;
117         file_pos : int;
118         end_pos : int;
119         lines : list(string);
120         lines_hi : list(int);
121         x_offset : int;
123         whence : int;
124         lines_delta : int;
125         lines_delta_goto_line : bool;
126         x_delta : int;
128         srch : search_request;
130         highlight_pos : int;
131         highlight_len : int;
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.[
142                 ro : st.ro,
143                 loc : st.loc,
144                 file : st.file,
145                 block_cache : "",
146                 block_cache_pos : 0,
147                 line_cache : infinite_uninitialized(line_cache_entry),
149                 file_pos_changed : true,
151                 file_pos : 0,
152                 end_pos : 0,
153                 lines : empty(string),
154                 lines_hi : empty(int),
156                 lines_offset : 0,
157                 lines_offset_goto_line : false,
158                 x_offset : 0,
160                 highlight_pos : st.highlight_pos,
161                 highlight_len : st.highlight_len,
163                 async_last_position_percent : -1,
164         ];
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
172                 loc := ro.loc;
173         st.loc := loc;
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.[
180                 ro : ro,
181                 file_name : file_name,
182                 file : file,
183                 async_seq : 0,
185                 file_pos : 0,
186                 end_pos : 0,
187                 lines : empty(string),
188                 lines_hi : empty(int),
189                 x_offset : 0,
191                 whence : -1,
192                 lines_delta : 0,
193                 lines_delta_goto_line : false,
194                 x_delta : 0,
196                 highlight_pos : 0,
197                 highlight_len : 0,
199                 last_key : event_keyboard. [ key : 0, flags : 0, rep : 1 ],
200         ];
201         view_set_charset();
202         view_init_async_state();
205 fn hex_chars_per_line(x : int) : int
207         var res := 4;
208         while true do [
209                 var n := res + 4;
210                 var l := 8 + n * 3 + ((n shr 2) - 1) * 2 + 2 + n;
211                 if l > x then
212                         break;
213                 res := n;
214         ]
215         return res;
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.([
224                         property.s.(``),
225                         property.s.(select(hex, select(wrap, `Wrap`, `Unwrap`), ``)),
226                         property.s.(`Quit`),
227                         property.s.(select(hex, `Hex`, `Ascii`)),
228                         property.s.(`Goto`),
229                         property.s.(`Charset`),
230                         property.s.(`Search`),
231                         property.s.(``),
232                         property.s.(``),
233                         property.s.(`Quit`),
234                 ]));
235         ]
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;
244         ]
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));
254         ]
256         var bc := bread_lazy(as.file, pos);
257         if is_exception len_at_least(bc, buffer_size) then
258                 goto no_cache;
259         if not len_greater_than(bc, 0) then
260                 return "";
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);
265         ] else [
266                 as.block_cache := bc;
267                 as.block_cache_pos := pos;
268                 return as.block_cache;
269         ]
271 no_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'));
281         var rline := ``;
282         var i := 0;
283         var uni_pos := 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;
294                         ]
295                         lline += string.[ ' ', #2502 ];
296                 ]
297                 if prev_hl and hl then [
298                         highlight_map bts= len(lline);
299                 ]
300                 lline +<= ' ';
301                 var hx := b[i];
302                 if hl then [
303                         highlight_map bts= len(lline);
304                         highlight_map bts= len(lline) + 1;
305                 ]
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 [
309                         if hl then
310                                 highlight_map_2 bts= i;
311                         if i < uni_pos then [
312                                 rline +<= ' ';
313                         ] else [
314                                 var c, cl := locale_get_char(as.loc, b[i .. ]);
315                                 if c = error_char then
316                                         c := error_ascii;
317                                 if char_to_unicode(c) < ' ' or char_to_unicode(c) = 127 then
318                                         c := '.';
319                                 uni_pos := i + cl;
320                                 var chl := char_length(c);
321                                 if chl = 0 then [
322                                         rline +<= ' ';
323                                 ] else [
324                                         rline +<= c;
325                                         if chl = 2 then [
326                                         if hl then
327                                                 highlight_map_2 bts= i + 1;
328                                                 skip_next_byte := true;
329                                         ]
330                                 ]
331                         ]
332                 ] else [
333                         skip_next_byte := false;
334                 ]
335                 i += 1;
336                 prev_hl := hl;
337         ]
338         var j := i;
339         while j < as.hex_chars do [
340                 if j > 0, (j and 3) = 0 then
341                         lline += `  `;
342                 lline += `   `;
343                 j += 1;
344         ]
345         var line := lline + `  `;
346         highlight_map_2 shl= len(line);
347         line += rline;
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)
353         if as.hex then [
354                 return view_get_line_hex(b, file_pos);
355         ]
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;
359         ]
360         var bytes_processed := 0;
361         var line_out := empty(char);
362         var line_pos := 0;
363         var highlight_map := 0;
364         while len_greater_than(b, 0) do [
365                 var c, i := locale_get_char(as.loc, b);
366                 if c = 9 then [
367                         var n := 8 - (line_pos and 7);
368                         if wrap, line_pos > 0, line_pos + n > as.size_x then
369                                 break;
370                         line_out += list_repeat(` `, n);
371                         line_pos += n;
372                         goto next_ch;
373                 ]
374                 if c = 10 then [
375                         b := b[i .. ];
376                         bytes_processed += i;
377                         break;
378                 ]
379                 if c = 13 then [
380                         if len_greater_than(b, 1), b[1] = 10 then [
381                                 b := b[2 .. ];
382                                 bytes_processed += 2;
383                                 break;
384                         ]
385                 ]
386                 if c = error_char then
387                         c := error_ascii;
388                 if char_to_unicode(c) < ' ' or char_to_unicode(c) = 127 then
389                         c := '.';
390                 var cl := char_length(c);
391                 if cl = 0 then
392                         goto next_ch;
393                 if wrap, line_pos > 0, line_pos + cl > as.size_x then
394                         break;
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
397                                 for x := 0 to cl do
398                                         highlight_map bts= line_pos + x;
399                 ]
400                 line_out +<= c;
401                 line_pos += cl;
402 next_ch:
403                 b := b[i .. ];
404                 bytes_processed += i;
405                 if report then
406                         view_report_position(file_pos + bytes_processed);
407         ]
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
423                                         return;
424                         ] else [
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
427                                         return;
428                         ]
429                 ]
430                 if p > 0 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
437                                                 return;
438                                 ]
439                         ] else [
440                                 var prev := 256;
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
449                                                 return;
450                                 ]
451                         ]
452                 ]
453         ]
454         as.file_pos_changed := true;
455         as.file_pos := p;
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 [
466                 var offs := 0;
467                 var b := as.srch.str.b;
468                 var ln := len(b);
469                 while len_at_least(stream, ln) do [
470                         if cs then [
471                                 for i := 0 to ln do [
472                                         if stream[i] <> b[i] then
473                                                 goto next_b;
474                                 ]
475                         ] else [
476                                 for i := 0 to ln do [
477                                         if ascii_upcase(stream[i]) <> ascii_upcase(b[i]) then
478                                                 goto next_b;
479                                 ]
480                         ]
481                         as_set_pos(as.srch.pos + offs, ln);
482                         if as.file_pos_changed then
483                                 return;
484 next_b:
485                         offs += 1;
486                         stream := stream[1 .. ];
487                         view_report_position(as.srch.pos + offs);
488                 ]
489         ] else if as.srch.str is s then [
490                 var offs := 0;
491                 var b := as.srch.str.s;
492                 var ln := len(b);
493                 var cc := empty(char);
494                 var ll := empty(int);
495                 while true do [
496                         var c, l := locale_get_char(as.loc, stream);
497                         if l = 0 then
498                                 break;
499                         stream := stream[l .. ];
500                         cc +<= c;
501                         ll +<= l;
502                         if len(cc) < ln then
503                                 continue;
504                         if cs then [
505                                 for i := 0 to ln do [
506                                         if cc[i] <> b[i] then
507                                                 goto next_c;
508                                         if cc[i] = error_char then
509                                                 goto next_c;
510                                 ]
511                         ] else [
512                                 for i := 0 to ln do [
513                                         if char_upcase(cc[i]) <> char_upcase(b[i]) then
514                                                 goto next_c;
515                                         if cc[i] = error_char then
516                                                 goto next_c;
517                                 ]
518                         ]
519                         as_set_pos(as.srch.pos + offs, list_fold_monoid(ll));
520                         if as.file_pos_changed then
521                                 return;
522 next_c:
523                         offs += ll[0];
524                         cc := cc[1 .. ];
525                         ll := ll[1 .. ];
526                         view_report_position(as.srch.pos + offs);
527                 ]
528         ]
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;
538                 var ln := len(b);
539                 while pos > 0 do [
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
544                                 break;
545                         var stream := view_read_cached(pos - this_step);
546                         if not len_at_least(stream, this_step) then
547                                 break;
548                         stream := stream[ .. this_step];
549                         for offs := 0 to this_step - ln + 1 do [
550                                 if cs then [
551                                         for i := 0 to ln do [
552                                                 if stream[offs + i] <> b[i] then
553                                                         goto next_b;
554                                         ]
555                                 ] else [
556                                         for i := 0 to ln do [
557                                                 if ascii_upcase(stream[offs + i]) <> ascii_upcase(b[i]) then
558                                                         goto next_b;
559                                         ]
560                                 ]
561                                 as_set_pos(pos - this_step + offs, ln);
562 next_b:
563                         ]
564                         if as.file_pos_changed then
565                                 return;
566                         pos -= this_step - ln + 1;
567                 ]
568         ] else if as.srch.str is s then [
569                 var b := as.srch.str.s;
570                 var ln := len(b);
571                 var bin_ln := len(string_to_locale(as.loc, b));
572                 while pos > 0 do [
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
578                                 break;
579                         stream := stream[ .. this_step];
580                         var cc := empty(char);
581                         var ll := empty(int);
582                         var offs := 0;
583                         while true do [
584                                 var c, l := locale_get_char(as.loc, stream);
585                                 if l = 0 then
586                                         break;
587                                 stream := stream[l .. ];
588                                 cc +<= c;
589                                 ll +<= l;
590                                 if len(cc) < ln then
591                                         continue;
592                                 if cs then [
593                                         for i := 0 to ln do [
594                                                 if cc[i] <> b[i] then
595                                                         goto next_c;
596                                                 if cc[i] = error_char then
597                                                         goto next_c;
598                                         ]
599                                 ] else [
600                                         for i := 0 to ln do [
601                                                 if char_upcase(cc[i]) <> char_upcase(b[i]) then
602                                                         goto next_c;
603                                                 if cc[i] = error_char then
604                                                         goto next_c;
605                                         ]
606                                 ]
607                                 as_set_pos(pos - this_step + offs, list_fold_monoid(ll));
608 next_c:
609                                 offs += ll[0];
610                                 cc := cc[1 .. ];
611                                 ll := ll[1 .. ];
612                         ]
613                         if as.file_pos_changed then
614                                 return;
615                         if this_step = bin_ln - 1 then
616                                 break;
617                         pos -= this_step - bin_ln + 1;
618                 ]
619         ]
622 fn view_file_pos_changed(implicit as : view_async_state) : view_async_state
624         if as.hex then
625                 return;
626         var pos := as.file_pos;
627         while pos > 0 do [
628                 var bs := min(pos, buffer_size);
629                 var str := view_read_cached(pos - bs);
630                 str := str[ .. bs];
631                 var i := bs - 1;
632                 while i >= -1 do [
633                         if (i = -1 and pos = bs) or (i >= 0 and str[i] = 10) then [
634                                 if as.wrap then [
635 have_line:
636                                         var fpos := pos - bs + i + 1;
637                                         var new_pos := fpos;
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
644                                                         new_pos := fpos;
645                                         ]
646                                         as.file_pos := new_pos;
647                                         return;
648                                 ] else [
649                                         as.file_pos := pos - bs + i + 1;
650                                         return;
651                                 ]
652                         ] else if as.wrap, not is_uninitialized(as.line_cache[pos - bs + i + 1]) then [
653                                 goto have_line;
654                         ]
655                         i -= 1;
656                 ]
657                 pos -= bs;
658         ]
659         as.file_pos := 0;
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;
669                 pos += as.x_offset;
670                 as.x_offset := 0;
671                 if pos > as.b_size then
672                         pos -= (pos - as.b_size) div as.hex_chars * as.hex_chars;
673                 pos := max(pos, 0);
674                 as.file_pos := pos;
675                 return;
676         ]
677         var wrap := as.wrap and not as.lines_offset_goto_line;
678         if n_lines > 0 then [
679                 if wrap 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);
685                                 n_lines -= 1;
686                         ]
687                         goto done;
688                 ]
689                 var last_line := pos;
690                 while true do [
691                         var str := view_read_cached(pos);
692                         var bs := buffer_size;
693                         if not len_at_least(str, bs) then
694                                 bs := len(str);
695                         if bs = 0 then [
696                                 pos := last_line;
697                                 goto done;
698                         ]
699                         str := str[ .. bs];
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 [
704                                                 pos := last_line;
705                                                 goto done;
706                                         ]
707                                         n_lines -= 1;
708                                 ]
709                         ]
710                         pos += bs;
711                 ]
712         ] else if n_lines < 0 then [
713                 n_lines := -n_lines;
714                 var last_pos := pos;
715                 while pos > 0 do [
716                         var bs := min(pos, buffer_size);
717                         var str := view_read_cached(pos - bs);
718                         str := str[ .. bs];
719                         var i := bs - 1;
720                         while i >= -1 do [
721                                 if (i = -1 and pos = bs) or (i >= 0 and str[i] = 10) then [
722                                         if wrap then [
723 have_line:
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);
732                                                 ]
733                                                 if len(line_positions) >= n_lines then [
734                                                         pos := line_positions[len(line_positions) - n_lines];
735                                                         goto done;
736                                                 ]
737                                                 n_lines -= len(line_positions);
738                                                 last_pos := pos - bs + i + 1;
739                                         ] else [
740                                                 if n_lines = 0 then [
741                                                         pos := pos - bs + i + 1;
742                                                         goto done;
743                                                 ]
744                                                 n_lines -= 1;
745                                         ]
746                                 ] else if wrap, not is_uninitialized(as.line_cache[pos - bs + i + 1]) then [
747                                         goto have_line;
748                                 ]
749                                 i -= 1;
750                         ]
751                         pos -= bs;
752                 ]
753         ]
754 done:
755         as.file_pos := pos;
758 fn view_do_format(implicit as : view_async_state) : view_async_state
760 re_format:
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);
771                 result +<= line_out;
772                 result_hi +<= highlight_map;
773         ]
775         if len(result) < as.size_y - 1, as.file_pos > 0 then [
776                 as.lines_offset := -(as.size_y - 1 - len(result));
777                 view_do_find_line();
778                 goto re_format;
779         ]
781         as.end_pos := file_pos;
782         as.lines := result;
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];
790                                 var offs := 0;
791                                 if en > as.size_x then [
792                                         offs := en - as.size_x;
793                                         if offs > st then
794                                                 offs := st;
795                                 ]
796                                 as.x_offset += offs;
797                                 goto brk;
798                         ]
799                 ]
800 brk:
801         ]
804 fn view_format_async(implicit w : world, implicit app : appstate, implicit as : view_async_state, self : wid, seq : int) : view_async_state
806         as.w := w;
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();
813                 else
814                         view_do_search_backward();
815         ]
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;
819                 ]
820                 view_file_pos_changed();
821                 as.file_pos_changed := false;
822         ]
823         view_do_find_line();
824         view_do_format();
825         eval len(as.lines);
826         eval len(as.lines_hi);
827         eval as.file_pos;
828         eval as.end_pos;
829         xeval as.async_event_fn(wevent.set_property.(event_set_property.[ prop : "view-async-finished", val : property.i.(seq) ]));
830         xeval len(as.lines);
831         xeval len(as.lines_hi);
832         xeval as.file_pos;
833         xeval as.end_pos;
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();
842         ]
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
852                 st.x_offset := 0;
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
860                 return;
862         var wrap := property_get("view-wrap").o;
863         var hex := property_get("view-hex").o;
865         st.async_seq += 1;
867         if st.whence <> -1 then [
868                 st.async.file_pos := st.whence;
869                 st.async.file_pos_changed := true;
870                 st.whence := -1;
871         ]
872         st.async.lines_offset := st.lines_delta;
873         st.lines_delta := 0;
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;
877         st.x_delta := 0;
878         st.async.srch := st.srch;
879         st.srch := uninitialized_record(search_request);
880         st.async.wrap := wrap;
881         st.async.hex := hex;
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();
893                 return;
894         ]
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)) + `  `;
913         var astr := ``;
914         if st.async_active then [
915                 astr := select(st.async_search_active, fmt1, fmt2);
916         ]
917         if string_length(astr) < fmt_len then [
918                 astr += list_repeat(` `, fmt_len - string_length(astr));
919         ]
920         cpos += astr;
921         cpos += `  `;
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, ' '));
924         else
925                 cpos += ascii_to_string(list_left_pad(ntos(select(b_size <> 0, 100, st.end_pos * 100 div b_size)), 3, ' '));
926         cpos += `%`;
927         curses_set_pos(com.size_x - string_length(cpos), 0);
928         curses_print(cpos);
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]);
935                 if hex then [
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));
939                 ]
940                 var hi := st.lines_hi[j];
941                 if hi <> 0 then [
942                         property_set_attrib(property_get_attrib("acmd-viewer-found", #ffff, #ffff, #0000, #0000, #aaaa, #aaaa, 0, 0));
943                         while hi <> 0 do [
944                                 var h := bsr hi;
945                                 hi btr= h;
946                                 curses_recolor_rect(h - st.x_offset, h - st.x_offset + 1, j + 1, j + 2);
947                         ]
948                         property_set_attrib(property_get_attrib("acmd-viewer", #aaaa, #aaaa, #aaaa, #0000, #0000, #aaaa, 0, 0));
949                 ]
950         ]
953 fn view_get_cursor(app : appstate, com : widget_common, st : view_state) : (int, int)
955         return -1, -1;
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 [
963 invl:
964                 acmd_error(`Invalid number`, ``);
965                 return -1, -1;
966         ]
967         if mode = view_goto_mode_line then [
968                 if num = 0 then
969                         goto invl;
970                 return 0, num - 1;
971         ] else if mode = view_goto_mode_percents then [
972                 if num > 100 then
973                         goto invl;
974                 return bsize_lazy(st.file) * num div 100, 0;
975         ] else [
976                 return num, 0;
977         ]
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.[
983                 prop : "view-goto",
984                 val : property.n,
985         ]));
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)
990         var e := len(ids);
991         var xs := 0;
993         if srch then
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);
1004         yp += 1;
1005         for i := 1 to e - 2 do
1006                 yp := widget_place(ids[i], offs_x, xs, yp);
1007         yp += 1;
1008         yp := widgets_place(ids[e - 2 .. ], widget_align.center, 2, 1, offs_x, xs, yp);
1010         return 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.[
1016                         cls : input_class,
1017                         init : input_init("", "view-goto-text", true,,,),
1018                 ], dialog_entry.[
1019                         cls : checkbox_class,
1020                         init : checkbox_init(`Line number`, "", "view-goto-mode", true, view_goto_mode_line,,,),
1021                 ], dialog_entry.[
1022                         cls : checkbox_class,
1023                         init : checkbox_init(`Percents`, "", "view-goto-mode", true, view_goto_mode_percents,,,),
1024                 ], dialog_entry.[
1025                         cls : checkbox_class,
1026                         init : checkbox_init(`Decimal offset`, "", "view-goto-mode", true, view_goto_mode_decimal_offset,,,),
1027                 ], dialog_entry.[
1028                         cls : checkbox_class,
1029                         init : checkbox_init(`Hexadecimal offset`, "", "view-goto-mode", true, view_goto_mode_hexadecimal_offset,,,),
1030                 ], dialog_entry.[
1031                         cls : button_class,
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 ]),
1034                 ], dialog_entry.[
1035                         cls : button_class,
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 ]),
1038                 ],
1039         ];
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)
1050         var e := len(ids);
1051         var xs := 0;
1053         for i := 0 to e do
1054                 xs := max(xs, widget_get_width(ids[i], pref_x));
1056         var yp := offs_y + 1;
1057         for i := 0 to e do
1058                 yp := widget_place(ids[i], offs_x, xs, yp);
1060         yp += 1;
1062         return 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)
1067         var idx : int;
1068         if wev is keyboard then [
1069                 if wev.keyboard.key = key_home then [
1070                         idx := 0;
1071                         goto activate;
1072                 ]
1073                 if wev.keyboard.key = key_end then [
1074                         idx := len(ids) - 1;
1075                         goto activate;
1076                 ]
1077                 if wev.keyboard.key = key_left or
1078                    wev.keyboard.key = key_right or
1079                    wev.keyboard.key = key_tab then [
1080                         return true;
1081                 ]
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 [
1086                         idx := 0;
1087                         var top := com.sub_widgets[len(com.sub_widgets) - 1];
1088                         while true do [
1089                                 if ids[idx] = top then
1090                                         break;
1091                                 idx += 1;
1092                         ]
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);
1101                         goto activate;
1102                 ]
1103         ]
1104         return false;
1106 activate:
1107         widget_activate(ids[idx]);
1108         return true;
1111 fn view_charset(implicit w : world, app : appstate, st : view_state) : (world, appstate)
1113         var charsets := charset_list;
1114         var m := 0;
1115         for i := 0 to len(charsets) do [
1116                 m := max(m, string_length(charsets[i].label));
1117         ]
1118         var init := 0;
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
1125                         chs := "";
1126                 var de := dialog_entry.[
1127                         cls : button_class,
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); ],
1130                                 //button_no_action,
1131                                 lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ view_send_charset(chs); widget_destroy_onclick(id); ],,,),
1132                 ];
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);
1136                 ]
1137                 entries +<= de;
1138         ]
1139         var winid : wid;
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);
1141         return app;
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
1149                 abort;
1150         var b := empty(byte);
1151         while len(a) >= 2 do [
1152                 var c := ston_base(a[ .. 2], 16);
1153                 xeval c;
1154                 b +<= c;
1155                 a := a[2 .. ];
1156         ]
1157         return b;
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",
1164                 val : property.n,
1165         ]));
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;
1173                 if key = '/' then
1174                         flags btr= view_search_flag_backwards;
1175                 else
1176                         flags bts= view_search_flag_backwards;
1177                 property_set("view-search-flags", property.i.(flags));
1178         ]
1179         var entries := [ dialog_entry.[
1180                         cls : input_class,
1181                         init : input_init("", "view-search-text", true,,,),
1182                 ], dialog_entry.[
1183                         cls : checkbox_class,
1184                         init : checkbox_init(`String`, "", "view-search-mode", true, view_search_mode_string,,,),
1185                 ], dialog_entry.[
1186                         cls : checkbox_class,
1187                         init : checkbox_init(`Hexadecimal`, "", "view-search-mode", true, view_search_mode_hexadecimal,,,),
1188                 ], dialog_entry.[
1189                         cls : checkbox_class,
1190                         init : checkbox_init(`Case sensitive`, "", "view-search-flags", false, view_search_flag_case_sensitive,,,),
1191                 ], dialog_entry.[
1192                         cls : checkbox_class,
1193                         init : checkbox_init(`Backwards`, "", "view-search-flags", false, view_search_flag_backwards,,,),
1194                 ], dialog_entry.[
1195                         cls : checkbox_class,
1196                         init : checkbox_init(`Whole words`, "", "view-search-flags", false, view_search_flag_whole_words,,,),
1197                 ], dialog_entry.[
1198                         cls : button_class,
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 ]),
1201                 ], dialog_entry.[
1202                         cls : button_class,
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 ]),
1205                 ],
1206         ];
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 [
1213                 com.x := 0;
1214                 com.y := 0;
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;
1220                 st.x_delta := 0;
1221                 st.x_offset := 0;
1222                 view_format();
1223                 goto redraw;
1224         ]
1225         if wev is change_focus then [
1226                 view_set_fkeys();
1227                 return;
1228         ]
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);
1233                         return;
1234                 ]
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;
1238                 ]
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;
1242                 ]
1243                 st.last_key := k;
1244                 if k.key = key_f2 then [
1245                         var hex := property_get("view-hex").o;
1246                         if hex then
1247                                 return;
1248                         var wrap := not property_get("view-wrap").o;
1249                         property_set("view-wrap", property.o.(wrap));
1250                         view_set_fkeys();
1251                         st.whence := st.file_pos;
1252                         st.lines_delta := 0;
1253                         st.x_delta := -st.x_offset;
1254                         view_init_async_state();
1255                         goto do_scroll;
1256                 ]
1257                 if k.key = key_f3 or k.key = key_f10 then [
1258                         widget_enqueue_event(com.self, wevent.close);
1259                         return;
1260                 ]
1261                 if k.key = key_f4 then [
1262                         var hex := not property_get("view-hex").o;
1263                         property_set("view-hex", property.o.(hex));
1264                         view_set_fkeys();
1265                         st.whence := st.file_pos;
1266                         st.lines_delta := 0;
1267                         st.x_delta := -st.x_offset;
1268                         view_init_async_state();
1269                         goto do_scroll;
1270                 ]
1271                 if k.key = key_f5 then [
1272                         view_goto();
1273                         return;
1274                 ]
1275                 if k.key = key_f6 then [
1276                         view_charset();
1277                         return;
1278                 ]
1279                 if k.key = key_f7 or k.key = '/' or k.key = '?' then [
1280                         view_search(k.key);
1281                         return;
1282                 ]
1283                 if k.key = key_left or k.key = key_right then [
1284                         if k.rep = 0 then
1285                                 return;
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;
1291                                 ]
1292                                 goto do_scroll;
1293                         ]
1294                         return;
1295                 ]
1296                 if k.key = key_home or k.key = 'A' and k.flags = key_flag_ctrl then [
1297                         if k.rep = 0 then
1298                                 return;
1299                         st.whence := 0;
1300                         st.lines_delta := 0;
1301                         st.x_delta := -st.x_offset;
1302                         if st.async_active then
1303                                 view_init_async_state();
1304                         goto do_scroll;
1305                 ]
1306                 if k.key = key_end or k.key = 'E' and k.flags = key_flag_ctrl then [
1307                         if k.rep = 0 then
1308                                 return;
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();
1315                         goto do_scroll;
1316                 ]
1317                 if k.key = key_up then [
1318                         if k.rep = 0 then
1319                                 return;
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;
1324                         ]
1325                         goto do_scroll;
1326                 ]
1327                 if k.key = key_down then [
1328                         if k.rep = 0 then
1329                                 return;
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;
1334                         ]
1335                         goto do_scroll;
1336                 ]
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 [
1338                         if k.rep = 0 then
1339                                 return;
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;
1344                         ]
1345                         goto do_scroll;
1346                 ]
1347                 if k.key = key_page_down or k.key = ' ' or k.key = 'F' and k.flags = key_flag_ctrl then [
1348                         if k.rep = 0 then
1349                                 return;
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;
1354                         ]
1355                         goto do_scroll;
1356                 ]
1357                 if k.key = 'n' or k.key = 'N' then [
1358                         goto do_search;
1359                 ]
1360                 return;
1361         ]
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;
1368                         ]
1369                         goto do_scroll;
1370                 ]
1371                 return;
1372         ]
1373         if wev is property_changed then [
1374                 view_set_charset(st.ro);
1375                 st.whence := st.file_pos;
1376                 st.lines_delta := 0;
1377                 st.x_delta := 0;
1378                 st.x_offset := 0;
1379                 view_init_async_state();
1380                 goto do_scroll;
1381         ]
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);
1387                         if new_pos < 0 then
1388                                 return;
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();
1395                         goto do_scroll;
1396                 ]
1397                 if wev.set_property.prop = "view-search" then [
1398 do_search:
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.[
1403                                 pos : st.file_pos,
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,
1407                         ];
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;
1414                                         else
1415                                                 srch.pos := st.highlight_pos + st.highlight_len;
1416                                 ]
1417                         ] else [
1418                                 st.highlight_pos := 0;
1419                                 st.highlight_len := 0;
1420                         ]
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`, ``);
1426                                         return;
1427                                 ]
1428                         ] else [
1429                                 var hx := view_search_decode_hex(text);
1430                                 if is_exception hx then [
1431                                         acmd_error(`Invalid hex string`, ``);
1432                                         return;
1433                                 ]
1434                                 if len(hx) = 0 then
1435                                         goto empty_search_string;
1436                                 srch.str := search_string.b.(hx);
1437                         ]
1438                         if st.async_active then [
1439                                 view_init_async_state();
1440                                 st.async.file_pos := st.file_pos;
1441                         ]
1442                         st.whence := -1;
1443                         st.lines_delta := 0;
1444                         st.x_delta := -st.x_offset;
1445                         st.srch := srch;
1446                         st.async_search_active := true;
1447                         goto do_scroll;
1448                 ]
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
1453                                         view_format();
1454                                 goto redraw;
1455                         ]
1456                 ]
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;
1460                         goto redraw_top;
1461                 ]
1462         ]
1463         return;
1465 do_scroll:
1466         var was_async := st.async_active;
1467         view_format();
1468         if not st.async_active then
1469                 goto redraw;
1470         if not was_async, st.async_active then [
1471 redraw_top:
1472                 widget_enqueue_event(com.self, wevent.redraw.(event_redraw.[
1473                         x1 : 0,
1474                         x2 : com.size_x,
1475                         y1 : 0,
1476                         y2 : 1,
1477                 ]));
1478         ]
1479         return;
1481 redraw:
1482         widget_enqueue_event(com.self, wevent.redraw.(event_redraw.[
1483                 x1 : 0,
1484                 x2 : com.size_x,
1485                 y1 : 0,
1486                 y2 : com.size_y,
1487         ]));