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