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