st-0.8.5-autocomplete-20220314-032311.diff (20434B)
1 diff -uraN st-0.8.5/autocomplete.h st-autocomplete/autocomplete.h 2 --- st-0.8.5/autocomplete.h 1970-01-01 04:00:00.000000000 +0400 3 +++ st-autocomplete/autocomplete.h 2022-03-13 02:45:34.586842452 +0400 4 @@ -0,0 +1,16 @@ 5 +# ifndef __ST_AUTOCOMPLETE_H 6 +# define __ST_AUTOCOMPLETE_H 7 + 8 +enum { 9 + ACMPL_DEACTIVATE, 10 + ACMPL_WORD, 11 + ACMPL_WWORD, 12 + ACMPL_FUZZY_WORD, 13 + ACMPL_FUZZY_WWORD, 14 + ACMPL_FUZZY, 15 + ACMPL_SUFFIX, 16 + ACMPL_SURROUND, 17 + ACMPL_UNDO, 18 +}; 19 + 20 +# endif // __ST_AUTOCOMPLETE_H 21 diff -uraN st-0.8.5/config.def.h st-autocomplete/config.def.h 22 --- st-0.8.5/config.def.h 2022-03-13 02:45:34.586842452 +0400 23 +++ st-autocomplete/config.def.h 2022-03-13 02:45:34.586842452 +0400 24 @@ -170,6 +170,8 @@ 25 */ 26 static uint forcemousemod = ShiftMask; 27 28 +#include "autocomplete.h" 29 + 30 /* 31 * Internal mouse shortcuts. 32 * Beware that overloading Button1 will disable the selection. 33 @@ -187,6 +189,8 @@ 34 #define MODKEY Mod1Mask 35 #define TERMMOD (ControlMask|ShiftMask) 36 37 +#define ACMPL_MOD ControlMask|Mod1Mask 38 + 39 static Shortcut shortcuts[] = { 40 /* mask keysym function argument */ 41 { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, 42 @@ -201,6 +205,14 @@ 43 { TERMMOD, XK_Y, selpaste, {.i = 0} }, 44 { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 45 { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 46 + { ACMPL_MOD, XK_slash, autocomplete, { .i = ACMPL_WORD } }, 47 + { ACMPL_MOD, XK_period, autocomplete, { .i = ACMPL_FUZZY_WORD } }, 48 + { ACMPL_MOD, XK_comma, autocomplete, { .i = ACMPL_FUZZY } }, 49 + { ACMPL_MOD, XK_apostrophe, autocomplete, { .i = ACMPL_SUFFIX } }, 50 + { ACMPL_MOD, XK_semicolon, autocomplete, { .i = ACMPL_SURROUND } }, 51 + { ACMPL_MOD, XK_bracketright,autocomplete, { .i = ACMPL_WWORD } }, 52 + { ACMPL_MOD, XK_bracketleft, autocomplete, { .i = ACMPL_FUZZY_WWORD } }, 53 + { ACMPL_MOD, XK_equal, autocomplete, { .i = ACMPL_UNDO } }, 54 }; 55 56 /* 57 diff -uraN st-0.8.5/Makefile st-autocomplete/Makefile 58 --- st-0.8.5/Makefile 2022-03-13 02:45:34.586842452 +0400 59 +++ st-autocomplete/Makefile 2022-03-13 02:45:34.586842452 +0400 60 @@ -44,6 +44,8 @@ 61 mkdir -p $(DESTDIR)$(PREFIX)/bin 62 cp -f st $(DESTDIR)$(PREFIX)/bin 63 chmod 755 $(DESTDIR)$(PREFIX)/bin/st 64 + cp -f st-autocomplete $(DESTDIR)$(PREFIX)/bin 65 + chmod 755 $(DESTDIR)$(PREFIX)/bin/st-autocomplete 66 mkdir -p $(DESTDIR)$(MANPREFIX)/man1 67 sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 68 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 69 @@ -52,6 +54,7 @@ 70 71 uninstall: 72 rm -f $(DESTDIR)$(PREFIX)/bin/st 73 + rm -f $(DESTDIR)$(PREFIX)/bin/st-autocomplete 74 rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 75 76 .PHONY: all options clean dist install uninstall 77 diff -uraN st-0.8.5/st-autocomplete st-autocomplete/st-autocomplete 78 --- st-0.8.5/st-autocomplete 1970-01-01 04:00:00.000000000 +0400 79 +++ st-autocomplete/st-autocomplete 2022-03-14 03:23:02.995498786 +0400 80 @@ -0,0 +1,292 @@ 81 +#!/usr/bin/perl 82 +######################################################################### 83 +# Copyright (C) 2012-2017 Wojciech Siewierski # 84 +# # 85 +# This program is free software: you can redistribute it and/or modify # 86 +# it under the terms of the GNU General Public License as published by # 87 +# the Free Software Foundation, either version 3 of the License, or # 88 +# (at your option) any later version. # 89 +# # 90 +# This program is distributed in the hope that it will be useful, # 91 +# but WITHOUT ANY WARRANTY; without even the implied warranty of # 92 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 93 +# GNU General Public License for more details. # 94 +# # 95 +# You should have received a copy of the GNU General Public License # 96 +# along with this program. If not, see <http://www.gnu.org/licenses/>. # 97 +######################################################################### 98 + 99 +my ($cmd, $cursor_row, $cursor_column) = @ARGV; 100 + 101 +my $lines = []; 102 + 103 +my $last_line = -1; 104 +my $lines_after_cursor = 0; 105 + 106 +while (<stdin>) 107 +{ 108 + $last_line++; 109 + 110 + if ($last_line <= $cursor_row) 111 + { 112 + push @{$lines}, $_; 113 + } 114 + else 115 + { 116 + unshift @{$lines}, $_; 117 + $lines_after_cursor++; 118 + } 119 +} 120 + 121 +$cursor_row = $last_line; 122 + 123 + 124 +$self = {}; 125 + 126 +# A reference to a function that transforms the completed word 127 +# into a regex matching the completions. Usually generated by 128 +# generate_matcher(). 129 +# 130 +# For example 131 +# $fun = generate_matcher(".*"); 132 +# $fun->("foo"); 133 +# would return "f.*o.*o" 134 +# 135 +# In other words, indirectly decides which characters can 136 +# appear in the completion. 137 +my $matcher; 138 + 139 +# A regular expression matching a character before each match. 140 +# For example, it you want to match the text after a 141 +# whitespace, set it to "\s". 142 +my $char_class_before; 143 + 144 +# A regular expression matching every character in the entered 145 +# text that will be used to find matching completions. Usually 146 +# "\w" or similar. 147 +my $char_class_to_complete; 148 + 149 +# A regular expression matching every allowed last character 150 +# of the completion (uses greedy matching). 151 +my $char_class_at_end; 152 + 153 +if ($cmd eq 'word-complete') { 154 + # Basic word completion. Completes the current word 155 + # without any special matching. 156 + $char_class_before = '[^-\w]'; 157 + $matcher = sub { quotemeta shift }; # identity 158 + $char_class_at_end = '[-\w]'; 159 + $char_class_to_complete = '[-\w]'; 160 +} elsif ($cmd eq 'WORD-complete') { 161 + # The same as above but in the Vim meaning of a "WORD" -- 162 + # whitespace delimited. 163 + $char_class_before = '\s'; 164 + $matcher = sub { quotemeta shift }; 165 + $char_class_at_end = '\S'; 166 + $char_class_to_complete = '\S'; 167 +} elsif ($cmd eq 'fuzzy-word-complete' || 168 + $cmd eq 'skeleton-word-complete') { 169 + # Fuzzy completion of the current word. 170 + $char_class_before = '[^-\w]'; 171 + $matcher = generate_matcher('[-\w]*'); 172 + $char_class_at_end = '[-\w]'; 173 + $char_class_to_complete = '[-\w]'; 174 +} elsif ($cmd eq 'fuzzy-WORD-complete') { 175 + # Fuzzy completion of the current WORD. 176 + $char_class_before = '\s'; 177 + $matcher = generate_matcher('\S*'); 178 + $char_class_at_end = '\S'; 179 + $char_class_to_complete = '\S'; 180 +} elsif ($cmd eq 'fuzzy-complete' || 181 + $cmd eq 'skeleton-complete') { 182 + # Fuzzy completion of an arbitrary text. 183 + $char_class_before = '\W'; 184 + $matcher = generate_matcher('.*?'); 185 + $char_class_at_end = '\w'; 186 + $char_class_to_complete = '\S'; 187 +} elsif ($cmd eq 'suffix-complete') { 188 + # Fuzzy completion of an completing suffixes, like 189 + # completing test=hello from /blah/hello. 190 + $char_class_before = '\S'; 191 + $matcher = generate_matcher('\S*'); 192 + $char_class_at_end = '\S'; 193 + $char_class_to_complete = '\S'; 194 +} elsif ($cmd eq 'surround-complete') { 195 + # Completing contents of quotes and braces. 196 + 197 + # Here we are using three named groups: s, b, p for quotes, braces 198 + # and parenthesis. 199 + $char_class_before = '((?<q>["\'`])|(?<b>\[)|(?<p>\())'; 200 + 201 + $matcher = generate_matcher('.*?'); 202 + 203 + # Here we match text till enclosing pair, using perl conditionals in 204 + # regexps (?(condition)yes-expression|no-expression). 205 + # \0 is used to hack concatenation with '*' later in the code. 206 + $char_class_at_end = '.*?(.(?=(?(<b>)\]|((?(<p>)\)|\g{q})))))\0'; 207 + $char_class_to_complete = '\S'; 208 +} 209 + 210 + 211 +# use the last used word or read the word behind the cursor 212 +my $word_to_complete = read_word_at_coord($self, $cursor_row, $cursor_column, 213 + $char_class_to_complete); 214 + 215 +print stdout "$word_to_complete\n"; 216 + 217 +if ($word_to_complete) { 218 + while (1) { 219 + # ignore the completed word itself 220 + $self->{already_completed}{$word_to_complete} = 1; 221 + 222 + # continue the last search or start from the current row 223 + my $completion = find_match($self, 224 + $word_to_complete, 225 + $self->{next_row} // $cursor_row, 226 + $matcher->($word_to_complete), 227 + $char_class_before, 228 + $char_class_at_end); 229 + if ($completion) { 230 + # save the last completed word unless continuing the last search 231 + highlight_match($self, 232 + $self->{next_row}+1, 233 + $completion); 234 + 235 + print stdout $completion." ".join (" ", @{$self->{highlight}})."\n"; 236 + } 237 + else { 238 + last; 239 + } 240 + } 241 +} 242 + 243 +###################################################################### 244 + 245 +sub highlight_match { 246 + my ($self, $linenum, $completion) = @_; 247 + 248 + # clear_highlight($self); 249 + 250 + my $line = @{$lines}[$linenum]; 251 + my $re = quotemeta $completion; 252 + 253 + $line =~ /$re/; 254 + 255 + my $beg = $-[0]; 256 + my $end = $+[0]; 257 + 258 + if ($linenum >= $lines_after_cursor) 259 + { 260 + $linenum -= $lines_after_cursor; 261 + } 262 + else 263 + { 264 + $linenum = $last_line - $linenum; 265 + } 266 + 267 + 268 + $self->{highlight} = [$linenum, $beg, $end]; 269 +} 270 + 271 +###################################################################### 272 + 273 +sub read_word_at_coord { 274 + my ($self, $row, $col, $char_class) = @_; 275 + 276 + $_ = substr(@{$lines} [$row], 0, $col); # get the current line up to the cursor... 277 + s/.*?($char_class*)$/$1/; # ...and read the last word from it 278 + return $_; 279 +} 280 + 281 +###################################################################### 282 + 283 +# Returns a function that takes a string and returns that string with 284 +# this function's argument inserted between its every two characters. 285 +# The resulting string is used as a regular expression matching the 286 +# completion candidates. 287 +sub generate_matcher { 288 + my $regex_between = shift; 289 + 290 + sub { 291 + $_ = shift; 292 + 293 + # sorry for this lispy code, I couldn't resist ;) 294 + (join "$regex_between", 295 + (map quotemeta, 296 + (split //))) 297 + } 298 +} 299 + 300 +###################################################################### 301 + 302 +# Checks whether the completion found by find_match() was already 303 +# found and if it was, calls find_match() again to find the next 304 +# completion. 305 +# 306 +# Takes all the arguments that find_match() would take, to make a 307 +# mutually recursive call. 308 +sub skip_duplicates { 309 + my $self = $_[0]; 310 + my $completion = shift @{$self->{matches_in_row}}; # get the rightmost one 311 + 312 + # check for duplicates 313 + if (exists $self->{already_completed}{$completion}) { 314 + # skip this completion 315 + return find_match(@_); 316 + } else { 317 + $self->{already_completed}{$completion} = 1; 318 + return $completion; 319 + } 320 +} 321 + 322 +###################################################################### 323 + 324 +# Finds the next matching completion in the row current row or above 325 +# while skipping duplicates using skip_duplicates(). 326 +sub find_match { 327 + my ($self, $word_to_match, $current_row, $regexp, $char_class_before, $char_class_at_end) = @_; 328 + $self->{matches_in_row} //= []; 329 + 330 + # cycle through all the matches in the current row if not starting a new search 331 + if (@{$self->{matches_in_row}}) { 332 + return skip_duplicates($self, $word_to_match, $current_row, $regexp, $char_class_before, $char_class_at_end); 333 + } 334 + 335 + 336 + my $i; 337 + # search through all the rows starting with current one or one above the last checked 338 + for ($i = $current_row; $i >= 0; --$i) { 339 + my $line = @{$lines}[$i]; # get the line of text from the row 340 + 341 + # if ($i == $cursor_row) { 342 + # $line = substr $line, 0, $cursor_column; 343 + # } 344 + 345 + $_ = $line; 346 + 347 + # find all the matches in the current line 348 + my $match; 349 + push @{$self->{matches_in_row}}, $+{match} while ($_, $match) = / 350 + (.*${char_class_before}) 351 + (?<match> 352 + ${regexp} 353 + ${char_class_at_end}* 354 + ) 355 + /ix; 356 + # corner case: match at the very beginning of line 357 + push @{$self->{matches_in_row}}, $+{match} if $line =~ /^(${char_class_before}){0}(?<match>$regexp$char_class_at_end*)/i; 358 + 359 + if (@{$self->{matches_in_row}}) { 360 + # remember which row should be searched next 361 + $self->{next_row} = --$i; 362 + 363 + # arguments needed for find_match() mutual recursion 364 + return skip_duplicates($self, $word_to_match, $i, $regexp, $char_class_before, $char_class_at_end); 365 + } 366 + } 367 + 368 + # # no more possible completions, revert to the original word 369 + # undo_completion($self) if $i < 0; 370 + 371 + return undef; 372 +} 373 diff -uraN st-0.8.5/st.c st-autocomplete/st.c 374 --- st-0.8.5/st.c 2022-03-13 02:45:34.586842452 +0400 375 +++ st-autocomplete/st.c 2022-03-14 03:23:02.995498786 +0400 376 @@ -17,6 +17,7 @@ 377 #include <unistd.h> 378 #include <wchar.h> 379 380 +#include "autocomplete.h" 381 #include "st.h" 382 #include "win.h" 383 384 @@ -2569,6 +2570,8 @@ 385 return; 386 } 387 388 + autocomplete ((const Arg []) { ACMPL_DEACTIVATE }); 389 + 390 /* 391 * slide screen to keep cursor where we expect it - 392 * tscrollup would work here, but we can optimize to 393 @@ -2688,3 +2691,250 @@ 394 tfulldirt(); 395 draw(); 396 } 397 + 398 +void autocomplete (const Arg * arg) 399 +{ 400 + static _Bool active = 0; 401 + 402 + int acmpl_cmdindex = arg -> i; 403 + 404 + static int acmpl_cmdindex_prev; 405 + 406 + if (active == 0) 407 + acmpl_cmdindex_prev = acmpl_cmdindex; 408 + 409 + static const char * const (acmpl_cmd []) = { 410 + [ACMPL_DEACTIVATE] = "__DEACTIVATE__", 411 + [ACMPL_WORD] = "word-complete", 412 + [ACMPL_WWORD] = "WORD-complete", 413 + [ACMPL_FUZZY_WORD] = "fuzzy-word-complete", 414 + [ACMPL_FUZZY_WWORD] = "fuzzy-WORD-complete", 415 + [ACMPL_FUZZY] = "fuzzy-complete", 416 + [ACMPL_SUFFIX] = "suffix-complete", 417 + [ACMPL_SURROUND] = "surround-complete", 418 + [ACMPL_UNDO] = "__UNDO__", 419 + }; 420 + 421 + static char acmpl [1000]; // ACMPL_ISSUE: why 1000? 422 + 423 + static FILE * acmpl_exec = NULL; 424 + static int acmpl_status; 425 + 426 + static const char * stbuffile; 427 + static char target [1000]; // ACMPL_ISSUE: why 1000? dynamically allocate char array of size term.col 428 + static size_t targetlen; 429 + 430 + static char completion [1000] = {0}; // ACMPL_ISSUE: why 1000? dynamically allocate char array of size term.col 431 + static size_t complen_prev = 0; // NOTE: always clear this variable after clearing completion 432 + 433 + static int cx, cy; 434 + 435 + // ACMPL_ISSUE: crashes when term.row is too small 436 + 437 +// Check for deactivation 438 + 439 + if (acmpl_cmdindex == ACMPL_DEACTIVATE) 440 + { 441 + 442 +// Deactivate autocomplete mode keeping current completion 443 + 444 + if (active) 445 + { 446 + active = 0; 447 + pclose (acmpl_exec); 448 + remove (stbuffile); 449 + 450 + if (complen_prev) 451 + { 452 + selclear (); 453 + complen_prev = 0; 454 + } 455 + } 456 + 457 + return; 458 + } 459 + 460 +// Check for undo 461 + 462 + if (acmpl_cmdindex == ACMPL_UNDO) 463 + { 464 + 465 +// Deactivate autocomplete mode recovering target 466 + 467 + if (active) 468 + { 469 + active = 0; 470 + pclose (acmpl_exec); 471 + remove (stbuffile); 472 + 473 + if (complen_prev) 474 + { 475 + selclear (); 476 + for (size_t i = 0; i < complen_prev; i++) 477 + ttywrite ((char []) { '\b' }, 1, 1); // ACMPL_ISSUE: I'm not sure that this is the right way 478 + complen_prev = 0; 479 + ttywrite (target, targetlen, 0); // ACMPL_ISSUE: I'm not sure that this is a right solution 480 + } 481 + } 482 + 483 + return; 484 + } 485 + 486 +// Check for command change 487 + 488 + if (acmpl_cmdindex != acmpl_cmdindex_prev) 489 + { 490 + 491 +// If command is changed, goto acmpl_begin avoiding rewriting st buffer 492 + 493 + if (active) 494 + { 495 + acmpl_cmdindex_prev = acmpl_cmdindex; 496 + 497 + goto acmpl_begin; 498 + } 499 + } 500 + 501 +// If not active 502 + 503 + if (active == 0) 504 + { 505 + acmpl_cmdindex_prev = acmpl_cmdindex; 506 + cx = term.c.x; 507 + cy = term.c.y; 508 + 509 +// Write st buffer to a temp file 510 + 511 + stbuffile = tmpnam (NULL); // ACMPL_ISSUE: check for return value ... 512 + // ACMPL_ISSUE: use coprocesses instead of temp files 513 + 514 + FILE * stbuf = fopen (stbuffile, "w"); // ACMPL_ISSUE: check for opening error ... 515 + char * stbufline = malloc (term.col + 2); // ACMPL_ISSUE: check for allocating error ... 516 + 517 + for (size_t y = 0; y < term.row; y++) 518 + { 519 + size_t x = 0; 520 + for (; x < term.col; x++) 521 + utf8encode (term.line [y] [x].u, stbufline + x); 522 + if (term.line [y] [x - 1].mode & ATTR_WRAP) 523 + { 524 + x--; 525 + if (y <= cy) cy--; 526 + } 527 + else 528 + { 529 + stbufline [x] = '\n'; 530 + } 531 + stbufline [x + 1] = 0; 532 + fputs (stbufline, stbuf); 533 + } 534 + 535 + free (stbufline); 536 + fclose (stbuf); 537 + 538 +acmpl_begin: 539 + 540 +// Run st-autocomplete 541 + 542 + sprintf ( 543 + acmpl, 544 + "cat %100s | st-autocomplete %500s %d %d", // ACMPL_ISSUE: why 100 and 500? 545 + stbuffile, 546 + acmpl_cmd [acmpl_cmdindex], 547 + cy, 548 + cx 549 + ); 550 + 551 + acmpl_exec = popen (acmpl, "r"); // ACMPL_ISSUE: popen isn't defined by The Standard. Does it work in BSDs for example? 552 + // ACMPL_ISSUE: check for popen error ... 553 + 554 +// Read the target, targetlen 555 + 556 + fscanf (acmpl_exec, "%500s\n", target); // ACMPL_ISSUE: check for scanning error ... 557 + targetlen = strlen (target); 558 + } 559 + 560 +// Read a completion if exists (acmpl_status) 561 + 562 + unsigned line, beg, end; 563 + 564 + acmpl_status = fscanf (acmpl_exec, "%500s %u %u %u\n", completion, & line, & beg, & end); 565 + // ACMPL_ISSUE: why 500? use term.col instead 566 + 567 +// Exit if no completions found 568 + 569 + if (active == 0 && acmpl_status == EOF) 570 + { 571 + 572 +// Close st-autocomplete and exit without activating the autocomplete mode 573 + 574 + pclose (acmpl_exec); 575 + remove (stbuffile); 576 + return; 577 + } 578 + 579 +// If completions found, enable autocomplete mode and autocomplete the target 580 + 581 + active = 1; 582 + 583 +// Clear target before first completion 584 + 585 + if (complen_prev == 0) 586 + { 587 + for (size_t i = 0; i < targetlen; i++) 588 + ttywrite ((char []) { '\b' }, 1, 1); // ACMPL_ISSUE: I'm not sure that this is a right solution 589 + } 590 + 591 +// Clear previuos completion if this is not the first 592 + 593 + else 594 + { 595 + selclear (); 596 + for (size_t i = 0; i < complen_prev; i++) 597 + ttywrite ((char []) { '\b' }, 1, 1); // ACMPL_ISSUE: I'm not sure that this is a right solution 598 + complen_prev = 0; 599 + } 600 + 601 +// If no more completions found, reset and restart 602 + 603 + if (acmpl_status == EOF) 604 + { 605 + active = 0; 606 + pclose (acmpl_exec); 607 + ttywrite (target, targetlen, 0); 608 + goto acmpl_begin; 609 + } 610 + 611 +// Count wrapped lines before the current line 612 + 613 + int wl = 0; 614 + 615 + int tl = line; 616 + 617 + for (int l = 0; l < tl; l++) 618 + if (term.line [l] [term.col - 1].mode & ATTR_WRAP) 619 + { 620 + wl++; 621 + tl++; 622 + } 623 + 624 +// Autcomplete 625 + 626 + complen_prev = strlen (completion); 627 + ttywrite (completion, complen_prev, 0); 628 + 629 + if (line == cy && beg > cx) 630 + { 631 + beg += complen_prev - targetlen; 632 + end += complen_prev - targetlen; 633 + 634 + // ACMPL_ISSUE: highlignthing doesn't work when "line == cy && beg > cx", 635 + // but coordinates are correct... 636 + } 637 + 638 + end--; 639 + 640 + selstart (beg, line + wl, 0); 641 + selextend (end % term.col, line + wl + end / term.col, 1, 0); 642 + xsetsel (getsel ()); 643 +} 644 diff -uraN st-0.8.5/st.h st-autocomplete/st.h 645 --- st-0.8.5/st.h 2022-03-13 02:45:34.586842452 +0400 646 +++ st-autocomplete/st.h 2022-03-13 02:45:34.586842452 +0400 647 @@ -77,6 +77,8 @@ 648 const char *s; 649 } Arg; 650 651 +void autocomplete (const Arg *); 652 + 653 void die(const char *, ...); 654 void redraw(void); 655 void draw(void); 656 diff -uraN st-0.8.5/x.c st-autocomplete/x.c 657 --- st-0.8.5/x.c 2022-03-13 02:45:34.586842452 +0400 658 +++ st-autocomplete/x.c 2022-03-13 02:45:34.590175835 +0400 659 @@ -1834,11 +1834,20 @@ 660 /* 1. shortcuts */ 661 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 662 if (ksym == bp->keysym && match(bp->mod, e->state)) { 663 + if (bp -> func != autocomplete) 664 + autocomplete ((const Arg []) { ACMPL_DEACTIVATE }); 665 bp->func(&(bp->arg)); 666 return; 667 } 668 } 669 670 + if (!( 671 + len == 0 && 672 + e -> state & ~ignoremod // ACMPL_ISSUE: I'm not sure that this is the right way 673 + | ACMPL_MOD == ACMPL_MOD 674 + )) 675 + autocomplete ((const Arg []) { ACMPL_DEACTIVATE }); 676 + 677 /* 2. custom keys from config.h */ 678 if ((customkey = kmap(ksym, e->state))) { 679 ttywrite(customkey, strlen(customkey), 1);