5 Commits

Author SHA1 Message Date
b52969be59 update font 2025-09-20 14:11:02 +02:00
760a7b8a69 fix: agree st-autocomplete with st-scrollback-ringbuffer 2025-09-20 14:07:36 +02:00
27c9d92844 Terminal scrollback with ring buffer
(apply st-scrollback-ringbuffer-0.9.2.diff)

from the patch header:

commit 0663bdf11a409961da5b1120741a69814da8ce65
Author: Timo Röhling <timo@gaussglocke.de>
Date:   Tue Nov 23 19:45:33 2021 +0100

    Terminal scrollback with ring buffer

    This patch adds a ring buffer for scrollback to the terminal.  The
    advantage of using a ring buffer is that the common case, scrolling with
    no static screen content, can be achieved very efficiently by
    incrementing and decrementing the starting line (modulo buffer size).

    The scrollback buffer is limited to HISTSIZE lines in order to bound
    memory usage. As the lines are allocated on demand, it is possible to
    implement unlimited scrollback with few changes.  If the terminal is
    reset, the scroll back buffer is reset, too.
2025-09-20 14:05:49 +02:00
wael
82f477bd90 fix xresources with signal reloading removing arg.h and st.h & remove unneccesary xresources variables(?) 2025-09-20 14:01:29 +02:00
elbachir-one
ef88bcb749 The use of mkstemp' 2025-09-14 01:51:43 +02:00
7 changed files with 581 additions and 168 deletions

View File

@@ -38,6 +38,8 @@ install: st
mkdir -p $(DESTDIR)$(PREFIX)/bin mkdir -p $(DESTDIR)$(PREFIX)/bin
cp -f st $(DESTDIR)$(PREFIX)/bin cp -f st $(DESTDIR)$(PREFIX)/bin
chmod 755 $(DESTDIR)$(PREFIX)/bin/st chmod 755 $(DESTDIR)$(PREFIX)/bin/st
cp -f st-autocomplete $(DESTDIR)$(PREFIX)/bin
chmod 755 $(DESTDIR)$(PREFIX)/bin/st-autocomplete
mkdir -p $(DESTDIR)$(MANPREFIX)/man1 mkdir -p $(DESTDIR)$(MANPREFIX)/man1
sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1
chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1
@@ -46,6 +48,7 @@ install: st
uninstall: uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/st rm -f $(DESTDIR)$(PREFIX)/bin/st
rm -f $(DESTDIR)$(PREFIX)/bin/st-autocomplete
rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1
.PHONY: all clean dist install uninstall .PHONY: all clean dist install uninstall

16
autocomplete.h Normal file
View File

@@ -0,0 +1,16 @@
# ifndef __ST_AUTOCOMPLETE_H
# define __ST_AUTOCOMPLETE_H
enum {
ACMPL_DEACTIVATE,
ACMPL_WORD,
ACMPL_WWORD,
ACMPL_FUZZY_WORD,
ACMPL_FUZZY_WWORD,
ACMPL_FUZZY,
ACMPL_SUFFIX,
ACMPL_SURROUND,
ACMPL_UNDO,
};
# endif // __ST_AUTOCOMPLETE_H

View File

@@ -8,40 +8,6 @@
static char *font = "FiraCode Nerd Font:size=10"; static char *font = "FiraCode Nerd Font:size=10";
static int borderpx = 2; static int borderpx = 2;
static char *externaleditcmd[] = { "/bin/sh", "-c",
"tmpfile=$(mktemp /tmp/st-edit.XXXXXX)\n"
"trap 'rm \"$tmpfile\"' 0 1 15\n"
"cat > \"$tmpfile\"\n"
"st -e vis \"$tmpfile\"\n",
NULL};
int lightmode = 0;
#define COL_GRAY1 "'#222222'"
#define COL_GRAY2 "'#444444'"
#define COL_GRAY3 "'#bbbbbb'"
#define COL_GRAY4 "'#eeeeee'"
#define COL_CYAN "'#005577'"
#define DMENU_DARK "-l 10 -fn 'FiraCode Nerd Font:size=10' -nb " COL_GRAY1 " -nf " COL_GRAY3 " -sb " COL_CYAN " -sf " COL_GRAY4
#define DMENU_LIGHT "-l 10 -fn 'FiraCode Nerd Font:size=10' -nb " COL_GRAY3 " -nf " COL_GRAY1 " -sb " COL_CYAN " -sf " COL_GRAY4
static char *externalcopydarkcmd[] = { "/bin/sh", "-c",
"sed 's/[ \t][ \t]*/\\n/g' | tac | awk 'BEGIN{x[\"\"]++} !x[$0]++' | dmenu " DMENU_DARK " -w $WINDOWID -p Copy | tr -d '\\n' | vis-clipboard --copy",
NULL};
static char *externalpastedarkcmd[] = { "/bin/sh", "-c",
"sed 's/[ \t][ \t]*/\\n/g' | tac | awk 'BEGIN{x[\"\"]++} !x[$0]++' | dmenu " DMENU_DARK " -w $WINDOWID -p Paste | tr -d '\\n'",
NULL};
static char *externalcopylightcmd[] = { "/bin/sh", "-c",
"sed 's/[ \t][ \t]*/\\n/g' | tac | awk 'BEGIN{x[\"\"]++} !x[$0]++' | dmenu " DMENU_LIGHT " -w $WINDOWID -p Copy | tr -d '\\n' | vis-clipboard --copy",
NULL};
static char *externalpastelightcmd[] = { "/bin/sh", "-c",
"sed 's/[ \t][ \t]*/\\n/g' | tac | awk 'BEGIN{x[\"\"]++} !x[$0]++' | dmenu " DMENU_LIGHT " -w $WINDOWID -p Paste | tr -d '\\n'",
NULL};
/* /*
* What program is execed by st depends of these precedence rules: * What program is execed by st depends of these precedence rules:
* 1: program passed with -e * 1: program passed with -e
@@ -204,6 +170,8 @@ static unsigned int defaultattr = 11;
*/ */
static uint forcemousemod = ShiftMask; static uint forcemousemod = ShiftMask;
#include "autocomplete.h"
/* /*
* Internal mouse shortcuts. * Internal mouse shortcuts.
* Beware that overloading Button1 will disable the selection. * Beware that overloading Button1 will disable the selection.
@@ -221,6 +189,8 @@ static MouseShortcut mshortcuts[] = {
#define MODKEY Mod1Mask #define MODKEY Mod1Mask
#define TERMMOD (ControlMask|ShiftMask) #define TERMMOD (ControlMask|ShiftMask)
#define ACMPL_MOD ControlMask|Mod1Mask
static Shortcut shortcuts[] = { static Shortcut shortcuts[] = {
/* mask keysym function argument */ /* mask keysym function argument */
{ XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} },
@@ -233,11 +203,16 @@ static Shortcut shortcuts[] = {
{ TERMMOD, XK_C, clipcopy, {.i = 0} }, { TERMMOD, XK_C, clipcopy, {.i = 0} },
{ TERMMOD, XK_V, clippaste, {.i = 0} }, { TERMMOD, XK_V, clippaste, {.i = 0} },
{ TERMMOD, XK_Y, selpaste, {.i = 0} }, { TERMMOD, XK_Y, selpaste, {.i = 0} },
{ TERMMOD, XK_E, externalpipe, { .v = externaleditcmd } },
{ TERMMOD, XK_K, externalpipecopy, { 0 } },
{ TERMMOD, XK_I, externalpipepaste, { 0 } },
{ ShiftMask, XK_Insert, selpaste, {.i = 0} }, { ShiftMask, XK_Insert, selpaste, {.i = 0} },
{ TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, { TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
{ ACMPL_MOD, XK_slash, autocomplete, { .i = ACMPL_WORD } },
{ ACMPL_MOD, XK_period, autocomplete, { .i = ACMPL_FUZZY_WORD } },
{ ACMPL_MOD, XK_comma, autocomplete, { .i = ACMPL_FUZZY } },
{ ACMPL_MOD, XK_apostrophe, autocomplete, { .i = ACMPL_SUFFIX } },
{ ACMPL_MOD, XK_semicolon, autocomplete, { .i = ACMPL_SURROUND } },
{ ACMPL_MOD, XK_bracketright,autocomplete, { .i = ACMPL_WWORD } },
{ ACMPL_MOD, XK_bracketleft, autocomplete, { .i = ACMPL_FUZZY_WWORD } },
{ ACMPL_MOD, XK_equal, autocomplete, { .i = ACMPL_UNDO } },
{ ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} },
{ ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} },
}; };

310
st-autocomplete Normal file
View File

@@ -0,0 +1,310 @@
#!/usr/bin/perl
#########################################################################
# Copyright (C) 2012-2017 Wojciech Siewierski #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
#########################################################################
my ($cmd, $cursor_row, $cursor_column) = @ARGV;
my $lines = [];
my $lines1 = [];
my $last_line = -1;
my $lines_before_cursor = 0;
while (<stdin>)
{
$last_line++;
s/[^[:print:]]/?/g;
if ($last_line < $cursor_row)
{
unshift @{$lines1}, $_;
$lines_before_cursor++;
}
else
{
unshift @{$lines}, $_;
}
}
foreach (@{$lines1})
{
unshift @{$lines}, $_;
}
my $cursor_row_in = $cursor_row;
$cursor_row = $last_line;
$self = {};
# A reference to a function that transforms the completed word
# into a regex matching the completions. Usually generated by
# generate_matcher().
#
# For example
# $fun = generate_matcher(".*");
# $fun->("foo");
# would return "f.*o.*o"
#
# In other words, indirectly decides which characters can
# appear in the completion.
my $matcher;
# A regular expression matching a character before each match.
# For example, it you want to match the text after a
# whitespace, set it to "\s".
my $char_class_before;
# A regular expression matching every character in the entered
# text that will be used to find matching completions. Usually
# "\w" or similar.
my $char_class_to_complete;
# A regular expression matching every allowed last character
# of the completion (uses greedy matching).
my $char_class_at_end;
if ($cmd eq 'word-complete') {
# Basic word completion. Completes the current word
# without any special matching.
$char_class_before = '[^-\w]';
$matcher = sub { quotemeta shift }; # identity
$char_class_at_end = '[-\w]';
$char_class_to_complete = '[-\w]';
} elsif ($cmd eq 'WORD-complete') {
# The same as above but in the Vim meaning of a "WORD" --
# whitespace delimited.
$char_class_before = '\s';
$matcher = sub { quotemeta shift };
$char_class_at_end = '\S';
$char_class_to_complete = '\S';
} elsif ($cmd eq 'fuzzy-word-complete' ||
$cmd eq 'skeleton-word-complete') {
# Fuzzy completion of the current word.
$char_class_before = '[^-\w]';
$matcher = generate_matcher('[-\w]*');
$char_class_at_end = '[-\w]';
$char_class_to_complete = '[-\w]';
} elsif ($cmd eq 'fuzzy-WORD-complete') {
# Fuzzy completion of the current WORD.
$char_class_before = '\s';
$matcher = generate_matcher('\S*');
$char_class_at_end = '\S';
$char_class_to_complete = '\S';
} elsif ($cmd eq 'fuzzy-complete' ||
$cmd eq 'skeleton-complete') {
# Fuzzy completion of an arbitrary text.
$char_class_before = '\W';
$matcher = generate_matcher('.*?');
$char_class_at_end = '\w';
$char_class_to_complete = '\S';
} elsif ($cmd eq 'suffix-complete') {
# Fuzzy completion of an completing suffixes, like
# completing test=hello from /blah/hello.
$char_class_before = '\S';
$matcher = generate_matcher('\S*');
$char_class_at_end = '\S';
$char_class_to_complete = '\S';
} elsif ($cmd eq 'surround-complete') {
# Completing contents of quotes and braces.
# Here we are using three named groups: s, b, p for quotes, braces
# and parenthesis.
$char_class_before = '((?<q>["\'`])|(?<b>\[)|(?<p>\())';
$matcher = generate_matcher('.*?');
# Here we match text till enclosing pair, using perl conditionals in
# regexps (?(condition)yes-expression|no-expression).
# \0 is used to hack concatenation with '*' later in the code.
$char_class_at_end = '.*?(.(?=(?(<b>)\]|((?(<p>)\)|\g{q})))))\0';
$char_class_to_complete = '\S';
}
# use the last used word or read the word behind the cursor
my $word_to_complete = read_word_at_coord($self, $cursor_row, $cursor_column,
$char_class_to_complete);
print stdout "$word_to_complete\n";
if ($word_to_complete) {
while (1) {
# ignore the completed word itself
$self->{already_completed}{$word_to_complete} = 1;
# continue the last search or start from the current row
my $completion = find_match($self,
$word_to_complete,
$self->{next_row} // $cursor_row,
$matcher->($word_to_complete),
$char_class_before,
$char_class_at_end);
if ($completion) {
print stdout $completion."\n".join ("\n", @{$self->{highlight}})."\n";
}
else {
last;
}
}
}
######################################################################
sub highlight_match {
my ($self, $linenum, $completion) = @_;
# clear_highlight($self);
my $line = @{$lines}[$linenum];
my $re = quotemeta $completion;
$line =~ /$re/;
my $beg = $-[0];
my $end = $+[0];
if ($linenum >= $lines_before_cursor)
{
$lline = $last_line - $lines_before_cursor;
$linenum -= $lines_before_cursor;
$linenum = $lline - $linenum;
$linenum += $lines_before_cursor;
}
$self->{highlight} = [$linenum, $beg, $end];
}
######################################################################
sub read_word_at_coord {
my ($self, $row, $col, $char_class) = @_;
$_ = substr(@{$lines} [$row], 0, $col); # get the current line up to the cursor...
s/.*?($char_class*)$/$1/; # ...and read the last word from it
return $_;
}
######################################################################
# Returns a function that takes a string and returns that string with
# this function's argument inserted between its every two characters.
# The resulting string is used as a regular expression matching the
# completion candidates.
sub generate_matcher {
my $regex_between = shift;
sub {
$_ = shift;
# sorry for this lispy code, I couldn't resist ;)
(join "$regex_between",
(map quotemeta,
(split //)))
}
}
######################################################################
# Checks whether the completion found by find_match() was already
# found and if it was, calls find_match() again to find the next
# completion.
#
# Takes all the arguments that find_match() would take, to make a
# mutually recursive call.
sub skip_duplicates {
my ($self, $word_to_match, $current_row, $regexp, $char_class_before, $char_class_at_end) = @_;
my $completion;
if ($current_row <= $lines_before_cursor)
{
$completion = shift @{$self->{matches_in_row}}; # get the leftmost one
}
else
{
$completion = pop @{$self->{matches_in_row}}; # get the leftmost one
}
# check for duplicates
if (exists $self->{already_completed}{$completion}) {
# skip this completion
return find_match(@_);
} else {
$self->{already_completed}{$completion} = 1;
highlight_match($self,
$self->{next_row}+1,
$completion);
return $completion;
}
}
######################################################################
# Finds the next matching completion in the row current row or above
# while skipping duplicates using skip_duplicates().
sub find_match {
my ($self, $word_to_match, $current_row, $regexp, $char_class_before, $char_class_at_end) = @_;
$self->{matches_in_row} //= [];
# cycle through all the matches in the current row if not starting a new search
if (@{$self->{matches_in_row}}) {
return skip_duplicates($self, $word_to_match, $current_row, $regexp, $char_class_before, $char_class_at_end);
}
my $i;
# search through all the rows starting with current one or one above the last checked
for ($i = $current_row; $i >= 0; --$i) {
my $line = @{$lines}[$i]; # get the line of text from the row
# if ($i == $cursor_row) {
# $line = substr $line, 0, $cursor_column;
# }
$_ = $line;
# find all the matches in the current line
my $match;
push @{$self->{matches_in_row}}, $+{match} while ($_, $match) = /
(.*${char_class_before})
(?<match>
${regexp}
${char_class_at_end}*
)
/ix;
# corner case: match at the very beginning of line
push @{$self->{matches_in_row}}, $+{match} if $line =~ /^(${char_class_before}){0}(?<match>$regexp$char_class_at_end*)/i;
if (@{$self->{matches_in_row}}) {
# remember which row should be searched next
$self->{next_row} = --$i;
# arguments needed for find_match() mutual recursion
return skip_duplicates($self, $word_to_match, $i, $regexp, $char_class_before, $char_class_at_end);
}
}
# # no more possible completions, revert to the original word
# undo_completion($self) if $i < 0;
return undef;
}

342
st.c
View File

@@ -17,6 +17,7 @@
#include <unistd.h> #include <unistd.h>
#include <wchar.h> #include <wchar.h>
#include "autocomplete.h"
#include "st.h" #include "st.h"
#include "win.h" #include "win.h"
@@ -734,14 +735,8 @@ sigchld(int a)
if ((p = waitpid(pid, &stat, WNOHANG)) < 0) if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
if (pid != p) { if (pid != p)
if (p == 0 && wait(&stat) < 0)
die("wait: %s\n", strerror(errno));
/* reinstall sigchld handler */
signal(SIGCHLD, sigchld);
return; return;
}
if (WIFEXITED(stat) && WEXITSTATUS(stat)) if (WIFEXITED(stat) && WEXITSTATUS(stat))
die("child exited with status %d\n", WEXITSTATUS(stat)); die("child exited with status %d\n", WEXITSTATUS(stat));
@@ -825,7 +820,7 @@ ttynew(const char *line, char *cmd, const char *out, char **args)
break; break;
default: default:
#ifdef __OpenBSD__ #ifdef __OpenBSD__
if (pledge("stdio rpath tty proc exec", NULL) == -1) if (pledge("stdio rpath tty proc", NULL) == -1)
die("pledge\n"); die("pledge\n");
#endif #endif
close(s); close(s);
@@ -2116,111 +2111,6 @@ strparse(void)
} }
} }
void
externalpipeinout(const Arg *arg, void (*processout)(int fildes))
{
int to[2], from[2];
char buf[UTF_SIZ];
void (*oldsigpipe)(int);
Glyph *bp, *end;
int lastpos, n, newline;
if (pipe(to) == -1)
return;
if (processout != NULL && pipe(from) == -1)
return;
switch (fork()) {
case -1:
close(to[0]);
close(to[1]);
if (processout != NULL) {
close(from[0]);
close(from[1]);
}
return;
case 0:
dup2(to[0], STDIN_FILENO);
close(to[0]);
close(to[1]);
if (processout != NULL) {
dup2(from[1], STDOUT_FILENO);
close(from[0]);
close(from[1]);
}
execvp(((char **)arg->v)[0], (char **)arg->v);
fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]);
perror("failed");
exit(0);
}
close(to[0]);
if (processout != NULL)
close(from[1]);
/* ignore sigpipe for now, in case child exists early */
oldsigpipe = signal(SIGPIPE, SIG_IGN);
newline = 0;
for (n = 0; n < term.row; n++) {
bp = TLINE(n);
lastpos = MIN(tlinelen(n) + 1, term.col) - 1;
if (lastpos < 0)
break;
end = &bp[lastpos + 1];
for (; bp < end; ++bp)
if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0)
break;
if ((newline = TLINE(n)[lastpos].mode & ATTR_WRAP))
continue;
if (xwrite(to[1], "\n", 1) < 0)
break;
newline = 0;
}
if (newline)
(void)xwrite(to[1], "\n", 1);
close(to[1]);
if (processout != NULL) {
processout(from[0]);
close(from[0]);
}
/* restore */
signal(SIGPIPE, oldsigpipe);
}
void
externalpipe(const Arg *arg)
{
externalpipeinout(arg, NULL);
}
void
ttycopy(int fildes)
{
char buf[256];
int len;
len = read(fildes, buf, sizeof(buf));
if (len > 0 && len < sizeof(buf)) {
buf[len] = '\0';
ttywrite(buf, len, 1);
}
}
void
externalpipepaste(const Arg *)
{
Arg arg;
arg.v = externalpastecmd();
externalpipeinout(&arg, ttycopy);
}
void
externalpipecopy(const Arg *)
{
Arg arg;
arg.v = externalcopycmd();
externalpipeinout(&arg, NULL);
}
void void
strdump(void) strdump(void)
{ {
@@ -2809,6 +2699,8 @@ tresize(int col, int row)
return; return;
} }
autocomplete ((const Arg []) { ACMPL_DEACTIVATE });
/* Shift buffer to keep the cursor where we expect it */ /* Shift buffer to keep the cursor where we expect it */
if (row <= term.c.y) { if (row <= term.c.y) {
term.screen[0].cur = (term.screen[0].cur - row + term.c.y + 1) % term.screen[0].size; term.screen[0].cur = (term.screen[0].cur - row + term.c.y + 1) % term.screen[0].size;
@@ -2929,3 +2821,227 @@ redraw(void)
tfulldirt(); tfulldirt();
draw(); draw();
} }
void autocomplete (const Arg *arg) {
static _Bool active = 0;
int acmpl_cmdindex = arg->i;
static int acmpl_cmdindex_prev;
if (active == 0)
acmpl_cmdindex_prev = acmpl_cmdindex;
static const char * const acmpl_cmd[] = {
[ACMPL_DEACTIVATE] = "__DEACTIVATE__",
[ACMPL_WORD] = "word-complete",
[ACMPL_WWORD] = "WORD-complete",
[ACMPL_FUZZY_WORD] = "fuzzy-word-complete",
[ACMPL_FUZZY_WWORD] = "fuzzy-WORD-complete",
[ACMPL_FUZZY] = "fuzzy-complete",
[ACMPL_SUFFIX] = "suffix-complete",
[ACMPL_SURROUND] = "surround-complete",
[ACMPL_UNDO] = "__UNDO__",
};
static FILE *acmpl_exec = NULL;
static int acmpl_status;
static char *stbuffile;
static char *target = NULL;
static size_t targetlen;
static char *completion = NULL;
static size_t complen_prev = 0;
static int cx, cy;
if (acmpl_cmdindex == ACMPL_DEACTIVATE) {
if (active) {
active = 0;
pclose(acmpl_exec);
unlink(stbuffile);
free(stbuffile);
stbuffile = NULL;
if (complen_prev) {
selclear();
complen_prev = 0;
}
}
return;
}
if (acmpl_cmdindex == ACMPL_UNDO) {
if (active) {
active = 0;
pclose(acmpl_exec);
unlink(stbuffile);
free(stbuffile);
stbuffile = NULL;
if (complen_prev) {
selclear();
for (size_t i = 0; i < complen_prev; i++)
ttywrite((char[]) {'\b'}, 1, 1);
complen_prev = 0;
ttywrite(target, targetlen, 0);
}
}
return;
}
if (acmpl_cmdindex != acmpl_cmdindex_prev) {
if (active) {
acmpl_cmdindex_prev = acmpl_cmdindex;
goto acmpl_begin;
}
}
if (active == 0) {
acmpl_cmdindex_prev = acmpl_cmdindex;
cx = term.c.x;
cy = term.c.y;
char filename[] = "/tmp/st-autocomplete-XXXXXX";
int fd = mkstemp(filename);
if (fd == -1) {
perror("mkstemp");
return;
}
stbuffile = strdup(filename);
FILE *stbuf = fdopen(fd, "w");
if (!stbuf) {
perror("fdopen");
close(fd);
unlink(stbuffile);
free(stbuffile);
stbuffile = NULL;
return;
}
char *stbufline = malloc(term.col + 2);
if (!stbufline) {
perror("malloc");
fclose(stbuf);
unlink(stbuffile);
free(stbuffile);
stbuffile = NULL;
return;
}
int cxp = 0;
for (size_t y = 0; y < term.row; y++) {
if (y == term.c.y) cx += cxp * term.col;
size_t x = 0;
for (; x < term.col; x++)
utf8encode(TLINE(y)[x].u, stbufline + x);
if (TLINE(y)[x - 1].mode & ATTR_WRAP) {
x--;
if (y <= term.c.y) cy--;
cxp++;
} else {
stbufline[x] = '\n';
cxp = 0;
}
stbufline[x + 1] = 0;
fputs(stbufline, stbuf);
}
free(stbufline);
fclose(stbuf);
acmpl_begin:
target = malloc(term.col + 1);
completion = malloc(term.col + 1);
if (!target || !completion) {
perror("malloc");
free(target);
free(completion);
unlink(stbuffile);
free(stbuffile);
stbuffile = NULL;
return;
}
char acmpl[1500];
snprintf(acmpl, sizeof(acmpl),
"cat %s | st-autocomplete %s %d %d",
stbuffile, acmpl_cmd[acmpl_cmdindex], cy, cx);
acmpl_exec = popen(acmpl, "r");
if (!acmpl_exec) {
perror("popen");
free(target);
free(completion);
unlink(stbuffile);
free(stbuffile);
stbuffile = NULL;
return;
}
if (fscanf(acmpl_exec, "%s\n", target) != 1) {
perror("fscanf");
pclose(acmpl_exec);
free(target);
free(completion);
unlink(stbuffile);
free(stbuffile);
stbuffile = NULL;
return;
}
targetlen = strlen(target);
}
unsigned line, beg, end;
acmpl_status = fscanf(acmpl_exec, "%[^\n]\n%u\n%u\n%u\n", completion, &line, &beg, &end);
if (acmpl_status == EOF) {
if (active == 0) {
pclose(acmpl_exec);
free(target);
free(completion);
unlink(stbuffile);
free(stbuffile);
stbuffile = NULL;
return;
}
active = 0;
pclose(acmpl_exec);
ttywrite(target, targetlen, 0);
goto acmpl_begin;
}
active = 1;
if (complen_prev == 0) {
for (size_t i = 0; i < targetlen; i++)
ttywrite((char[]) {'\b'}, 1, 1);
} else {
selclear();
for (size_t i = 0; i < complen_prev; i++)
ttywrite((char[]) {'\b'}, 1, 1);
complen_prev = 0;
}
complen_prev = strlen(completion);
ttywrite(completion, complen_prev, 0);
if (line == cy && beg > cx) {
beg += complen_prev - targetlen;
end += complen_prev - targetlen;
}
end--;
int wl = 0;
int tl = line;
for (int l = 0; l < tl; l++)
if (TLINE(l)[term.col - 1].mode & ATTR_WRAP) {
wl++;
tl++;
}
selstart(beg % term.col, line + wl + beg / term.col, 0);
selextend(end % term.col, line + wl + end / term.col, 1, 0);
xsetsel(getsel());
}

9
st.h
View File

@@ -78,13 +78,12 @@ typedef union {
const char *s; const char *s;
} Arg; } Arg;
void autocomplete (const Arg *);
void die(const char *, ...); void die(const char *, ...);
void redraw(void); void redraw(void);
void draw(void); void draw(void);
void externalpipe(const Arg *);
void externalpipecopy(const Arg *);
void externalpipepaste(const Arg *);
void printscreen(const Arg *); void printscreen(const Arg *);
void printsel(const Arg *); void printsel(const Arg *);
void sendbreak(const Arg *); void sendbreak(const Arg *);
@@ -123,12 +122,8 @@ extern char *vtiden;
extern wchar_t *worddelimiters; extern wchar_t *worddelimiters;
extern int allowaltscreen; extern int allowaltscreen;
extern int allowwindowops; extern int allowwindowops;
extern int lightmode;
extern char *termname; extern char *termname;
extern unsigned int tabspaces; extern unsigned int tabspaces;
extern unsigned int defaultfg; extern unsigned int defaultfg;
extern unsigned int defaultbg; extern unsigned int defaultbg;
extern unsigned int defaultcs; extern unsigned int defaultcs;
char **externalcopycmd();
char **externalpastecmd();

20
x.c
View File

@@ -66,16 +66,6 @@ void kscrolldown(const Arg *);
/* config.h for applying patches and the configuration. */ /* config.h for applying patches and the configuration. */
#include "config.h" #include "config.h"
char **externalcopycmd()
{
return (lightmode) ? externalcopylightcmd : externalcopydarkcmd;
}
char **externalpastecmd()
{
return (lightmode) ? externalpastelightcmd : externalpastedarkcmd;
}
/* XEMBED messages */ /* XEMBED messages */
#define XEMBED_FOCUS_IN 4 #define XEMBED_FOCUS_IN 4
#define XEMBED_FOCUS_OUT 5 #define XEMBED_FOCUS_OUT 5
@@ -1875,11 +1865,20 @@ kpress(XEvent *ev)
/* 1. shortcuts */ /* 1. shortcuts */
for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
if (ksym == bp->keysym && match(bp->mod, e->state)) { if (ksym == bp->keysym && match(bp->mod, e->state)) {
if (bp -> func != autocomplete)
autocomplete ((const Arg []) { ACMPL_DEACTIVATE });
bp->func(&(bp->arg)); bp->func(&(bp->arg));
return; return;
} }
} }
if (!(
len == 0 &&
e -> state & ~ignoremod // ACMPL_ISSUE: I'm not sure that this is the right way
| ACMPL_MOD == ACMPL_MOD
))
autocomplete ((const Arg []) { ACMPL_DEACTIVATE });
/* 2. custom keys from config.h */ /* 2. custom keys from config.h */
if ((customkey = kmap(ksym, e->state))) { if ((customkey = kmap(ksym, e->state))) {
ttywrite(customkey, strlen(customkey), 1); ttywrite(customkey, strlen(customkey), 1);
@@ -2119,7 +2118,6 @@ xrdb_load(void)
XRESOURCE_LOAD_INTEGER("bellvolume", bellvolume); XRESOURCE_LOAD_INTEGER("bellvolume", bellvolume);
XRESOURCE_LOAD_INTEGER("borderpx", borderpx); XRESOURCE_LOAD_INTEGER("borderpx", borderpx);
XRESOURCE_LOAD_INTEGER("cursorshape", cursorshape); XRESOURCE_LOAD_INTEGER("cursorshape", cursorshape);
XRESOURCE_LOAD_INTEGER("lightmode", lightmode);
XRESOURCE_LOAD_FLOAT("cwscale", cwscale); XRESOURCE_LOAD_FLOAT("cwscale", cwscale);
XRESOURCE_LOAD_FLOAT("chscale", chscale); XRESOURCE_LOAD_FLOAT("chscale", chscale);