sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | 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