| st-autocomplete-20240703-6508693.diff - sites - public wiki contents of suckles… | |
| 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 <[email protected]> | |
| 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)/ma… | |
| 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 argumen… | |
| 82 { XK_ANY_MOD, XK_Break, sendbreak, {.i = … | |
| 83 @@ -201,6 +205,14 @@ static Shortcut shortcuts[] = { | |
| 84 { TERMMOD, XK_Y, selpaste, {.i = … | |
| 85 { ShiftMask, XK_Insert, selpaste, {.i = … | |
| 86 { TERMMOD, XK_Num_Lock, numlock, {.i = … | |
| 87 + { ACMPL_MOD, XK_slash, autocomplete, { .i = … | |
| 88 + { ACMPL_MOD, XK_period, autocomplete, { .i = … | |
| 89 + { ACMPL_MOD, XK_comma, autocomplete, { .i = … | |
| 90 + { ACMPL_MOD, XK_apostrophe, autocomplete, { .i = … | |
| 91 + { ACMPL_MOD, XK_semicolon, autocomplete, { .i = … | |
| 92 + { ACMPL_MOD, XK_bracketright,autocomplete, { .i = … | |
| 93 + { ACMPL_MOD, XK_bracketleft, autocomplete, { .i = … | |
| 94 + { ACMPL_MOD, XK_equal, autocomplete, { .i = … | |
| 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, bra… | |
| 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 conditiona… | |
| 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}))… | |
| 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_c… | |
| 246 + … | |
| 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 + … | |
| 258 + … | |
| 259 + … | |
| 260 + … | |
| 261 + … | |
| 262 + if ($completion) { | |
| 263 + print stdout $completion."\n".join ("\n", @{$se… | |
| 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 t… | |
| 304 + s/.*?($char_class*)$/$1/; # ...and read the last word… | |
| 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_befor… | |
| 337 + my $completion; | |
| 338 + | |
| 339 + if ($current_row <= $lines_before_cursor) | |
| 340 + { | |
| 341 + $completion = shift @{$self->{matches_in_row}}; # get t… | |
| 342 + } | |
| 343 + else | |
| 344 + { | |
| 345 + $completion = pop @{$self->{matches_in_row}}; # get the… | |
| 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_befor… | |
| 369 + $self->{matches_in_row} //= []; | |
| 370 + | |
| 371 + # cycle through all the matches in the current row if not starting … | |
| 372 + if (@{$self->{matches_in_row}}) { | |
| 373 + return skip_duplicates($self, $word_to_match, $current_row, $re… | |
| 374 + } | |
| 375 + | |
| 376 + | |
| 377 + my $i; | |
| 378 + # search through all the rows starting with current one or one abov… | |
| 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 + … | |
| 392 + … | |
| 393 + … | |
| 394 + … | |
| 395 + … | |
| 396 + /i… | |
| 397 + # corner case: match at the very beginning of line | |
| 398 + push @{$self->{matches_in_row}}, $+{match} if $line =~ /^(${cha… | |
| 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, … | |
| 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", completio… | |
| 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_DE… | |
| 686 bp->func(&(bp->arg)); | |
| 687 return; | |
| 688 } | |
| 689 } | |
| 690 | |
| 691 + if (!( | |
| 692 + len == 0 && | |
| 693 + e -> state & ~ignoremod // ACMPL_ISSUE: … | |
| 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 |