7 Commits

Author SHA1 Message Date
419c7d37db fix: agree st-autocomplete with st-scrollback-ringbuffer 2025-09-14 09:57:42 +02:00
elbachir-one
5da81b3f13 apply patch st-autocomplete-20240703-6508693.diff 2025-09-14 09:57:42 +02:00
5a04771aba st: replace Solarize with my own themes 2025-09-14 09:51:39 +02:00
b92efccbab change font and color scheme keys 2025-09-11 22:26:09 +02:00
57940e9403 fix: agree st-colorschemes with st-scrollback-ringbuffer 2025-09-10 23:41:52 +02:00
6ba0170dd6 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-10 23:20:16 +02:00
Max Schillinger
04ea60ca8e Add multiple color schemes and key bindings to change them
This commits adds these color schemes:

- the default (dark) st color scheme
- the default (dark) alacritty color scheme
- One Half (dark & light)
- Solarized (dark & light)
- Gruvbox (dark & light)

Select one with Alt+1..8.
Select the next one with Alt+0.
Select the previous one with Ctrl+Alt+0.
2025-09-10 23:16:05 +02:00
7 changed files with 982 additions and 154 deletions

View File

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

@@ -5,7 +5,7 @@
*
* font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
*/
static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true";
static char *font = "FiraCode Nerd Font:size=10";
static int borderpx = 2;
/*
@@ -93,46 +93,87 @@ char *termname = "st-256color";
*/
unsigned int tabspaces = 8;
/* Terminal colors (16 first used in escape sequence) */
static const char *colorname[] = {
/* 8 normal colors */
"black",
"red3",
"green3",
"yellow3",
"blue2",
"magenta3",
"cyan3",
"gray90",
typedef struct {
const char* const colors[258]; /* terminal colors */
unsigned int fg; /* foreground */
unsigned int bg; /* background */
unsigned int cs; /* cursor */
unsigned int rcs; /* reverse cursor */
} ColorScheme;
/*
* Terminal colors (16 first used in escape sequence,
* 2 last for custom cursor color),
* foreground, background, cursor, reverse cursor
*/
static const ColorScheme schemes[] = {
// st (dark)
{{"black", "red3", "green3", "yellow3",
"blue2", "magenta3", "cyan3", "gray90",
"gray50", "red", "green", "yellow",
"#5c5cff", "magenta", "cyan", "white",
[256]="#cccccc", "#555555"}, 7, 0, 256, 257},
/* 8 bright colors */
"gray50",
"red",
"green",
"yellow",
"#5c5cff",
"magenta",
"cyan",
"white",
// Alacritty (dark)
{{"#1d1f21", "#cc6666", "#b5bd68", "#f0c674",
"#81a2be", "#b294bb", "#8abeb7", "#c5c8c6",
"#666666", "#d54e53", "#b9ca4a", "#e7c547",
"#7aa6da", "#c397d8", "#70c0b1", "#eaeaea",
[256]="#cccccc", "#555555"}, 7, 0, 256, 257},
[255] = 0,
// One Half dark
{{"#282c34", "#e06c75", "#98c379", "#e5c07b",
"#61afef", "#c678dd", "#56b6c2", "#dcdfe4",
"#282c34", "#e06c75", "#98c379", "#e5c07b",
"#61afef", "#c678dd", "#56b6c2", "#dcdfe4",
[256]="#cccccc", "#555555"}, 7, 0, 256, 257},
/* more colors can be added after 255 to use with DefaultXX */
"#cccccc",
"#555555",
"gray90", /* default foreground colour */
"black", /* default background colour */
// One Half light
{{"#fafafa", "#e45649", "#50a14f", "#c18401",
"#0184bc", "#a626a4", "#0997b3", "#383a42",
"#fafafa", "#e45649", "#50a14f", "#c18401",
"#0184bc", "#a626a4", "#0997b3", "#383a42",
[256]="#cccccc", "#555555"}, 7, 0, 256, 257},
// Lupan dark
{{"#1f212e","#862d2d", "#3a783a", "#707010",
"#345eb2","#cc66cc", "#3a7878", "#a1a3aa",
"#4d4d4d","#c27070", "#40bf40", "#acac53",
"#6b8ac7","#8f248f", "#509595", "#dbdff0",
[256]="#bf8040", "#555555"}, 7, 0, 256, 257},
// Lupan light
{{"#1f212e","#862d2d", "#3a783a", "#707010",
"#345eb2","#cc66cc", "#3a7878", "#a1a3aa",
"#4d4d4d","#c27070", "#40bf40", "#acac53",
"#6b8ac7","#8f248f", "#509595", "#dbdff0",
[256]="#bf8040", "#555555"}, 0, 15, 256, 257},
// Gruvbox dark
{{"#282828", "#cc241d", "#98971a", "#d79921",
"#458588", "#b16286", "#689d6a", "#a89984",
"#928374", "#fb4934", "#b8bb26", "#fabd2f",
"#83a598", "#d3869b", "#8ec07c", "#ebdbb2",
[256]="#ebdbb2", "#555555"}, 15, 0, 256, 257},
// Gruvbox light
{{"#fbf1c7", "#cc241d", "#98971a", "#d79921",
"#458588", "#b16286", "#689d6a", "#7c6f64",
"#928374", "#9d0006", "#79740e", "#b57614",
"#076678", "#8f3f71", "#427b58", "#3c3836",
[256]="#3c3836", "#555555"}, 15, 0, 256, 257},
};
static const char * const * colorname;
int colorscheme = 0;
/*
* Default colors (colorname index)
* foreground, background, cursor, reverse cursor
*/
unsigned int defaultfg = 258;
unsigned int defaultbg = 259;
unsigned int defaultcs = 256;
static unsigned int defaultrcs = 257;
unsigned int defaultfg;
unsigned int defaultbg;
unsigned int defaultcs;
static unsigned int defaultrcs;
/*
* Default shape of cursor
@@ -170,6 +211,8 @@ static unsigned int defaultattr = 11;
*/
static uint forcemousemod = ShiftMask;
#include "autocomplete.h"
/*
* Internal mouse shortcuts.
* Beware that overloading Button1 will disable the selection.
@@ -187,6 +230,8 @@ static MouseShortcut mshortcuts[] = {
#define MODKEY Mod1Mask
#define TERMMOD (ControlMask|ShiftMask)
#define ACMPL_MOD ControlMask|Mod1Mask
static Shortcut shortcuts[] = {
/* mask keysym function argument */
{ XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} },
@@ -201,6 +246,26 @@ static Shortcut shortcuts[] = {
{ TERMMOD, XK_Y, selpaste, {.i = 0} },
{ ShiftMask, XK_Insert, selpaste, {.i = 0} },
{ TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
{ MODKEY|ControlMask, XK_1, selectscheme, {.i = 0} },
{ MODKEY|ControlMask, XK_2, selectscheme, {.i = 1} },
{ MODKEY|ControlMask, XK_3, selectscheme, {.i = 2} },
{ MODKEY|ControlMask, XK_4, selectscheme, {.i = 3} },
{ MODKEY|ControlMask, XK_5, selectscheme, {.i = 4} },
{ MODKEY|ControlMask, XK_6, selectscheme, {.i = 5} },
{ MODKEY|ControlMask, XK_7, selectscheme, {.i = 6} },
{ MODKEY|ControlMask, XK_8, selectscheme, {.i = 7} },
{ MODKEY|ControlMask, XK_9, nextscheme, {.i = -1} },
{ MODKEY|ControlMask, XK_0, nextscheme, {.i = +1} },
{ ShiftMask, XK_Page_Up, kscrollup, {.i = -1} },
{ ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} },
{ 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 } },
};
/*

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;
}

612
st.c
View File

@@ -17,6 +17,7 @@
#include <unistd.h>
#include <wchar.h>
#include "autocomplete.h"
#include "st.h"
#include "win.h"
@@ -43,6 +44,10 @@
#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
#define ISDELIM(u) (u && wcschr(worddelimiters, u))
#define TSCREEN term.screen[IS_SET(MODE_ALTSCREEN)]
#define TLINEOFFSET(y) (((y) + TSCREEN.cur - TSCREEN.off + TSCREEN.size) % TSCREEN.size)
#define TLINE(y) (TSCREEN.buffer[TLINEOFFSET(y)])
enum term_mode {
MODE_WRAP = 1 << 0,
MODE_INSERT = 1 << 1,
@@ -109,12 +114,21 @@ typedef struct {
int alt;
} Selection;
/* Screen lines */
typedef struct {
Line* buffer; /* ring buffer */
int size; /* size of buffer */
int cur; /* start of active screen */
int off; /* scrollback line offset */
TCursor sc; /* saved cursor */
} LineBuffer;
/* Internal representation of the screen */
typedef struct {
int row; /* nb row */
int col; /* nb col */
Line *line; /* screen */
Line *alt; /* alternate screen */
LineBuffer screen[2]; /* screen and alternate screen */
int linelen; /* allocated line length */
int *dirty; /* dirtyness of lines */
TCursor c; /* cursor */
int ocx; /* old cursor col */
@@ -203,6 +217,8 @@ static void tdeftran(char);
static void tstrsequence(uchar);
static void drawregion(int, int, int, int);
static void clearline(Line, Glyph, int, int);
static Line ensureline(Line);
static void selnormalize(void);
static void selscroll(int, int);
@@ -408,11 +424,12 @@ int
tlinelen(int y)
{
int i = term.col;
Line line = TLINE(y);
if (term.line[y][i - 1].mode & ATTR_WRAP)
if (line[i - 1].mode & ATTR_WRAP)
return i;
while (i > 0 && term.line[y][i - 1].u == ' ')
while (i > 0 && line[i - 1].u == ' ')
--i;
return i;
@@ -521,7 +538,7 @@ selsnap(int *x, int *y, int direction)
* Snap around if the word wraps around at the end or
* beginning of a line.
*/
prevgp = &term.line[*y][*x];
prevgp = &TLINE(*y)[*x];
prevdelim = ISDELIM(prevgp->u);
for (;;) {
newx = *x + direction;
@@ -536,14 +553,14 @@ selsnap(int *x, int *y, int direction)
yt = *y, xt = *x;
else
yt = newy, xt = newx;
if (!(term.line[yt][xt].mode & ATTR_WRAP))
if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
break;
}
if (newx >= tlinelen(newy))
break;
gp = &term.line[newy][newx];
gp = &TLINE(newy)[newx];
delim = ISDELIM(gp->u);
if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
|| (delim && gp->u != prevgp->u)))
@@ -564,14 +581,14 @@ selsnap(int *x, int *y, int direction)
*x = (direction < 0) ? 0 : term.col - 1;
if (direction < 0) {
for (; *y > 0; *y += direction) {
if (!(term.line[*y-1][term.col-1].mode
if (!(TLINE(*y-1)[term.col-1].mode
& ATTR_WRAP)) {
break;
}
}
} else if (direction > 0) {
for (; *y < term.row-1; *y += direction) {
if (!(term.line[*y][term.col-1].mode
if (!(TLINE(*y)[term.col-1].mode
& ATTR_WRAP)) {
break;
}
@@ -602,13 +619,13 @@ getsel(void)
}
if (sel.type == SEL_RECTANGULAR) {
gp = &term.line[y][sel.nb.x];
gp = &TLINE(y)[sel.nb.x];
lastx = sel.ne.x;
} else {
gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
}
last = &term.line[y][MIN(lastx, linelen-1)];
last = &TLINE(y)[MIN(lastx, linelen-1)];
while (last >= gp && last->u == ' ')
--last;
@@ -949,12 +966,15 @@ int
tattrset(int attr)
{
int i, j;
int y = TLINEOFFSET(0);
for (i = 0; i < term.row-1; i++) {
Line line = TSCREEN.buffer[y];
for (j = 0; j < term.col-1; j++) {
if (term.line[i][j].mode & attr)
if (line[j].mode & attr)
return 1;
}
y = (y+1) % TSCREEN.size;
}
return 0;
@@ -976,14 +996,17 @@ void
tsetdirtattr(int attr)
{
int i, j;
int y = TLINEOFFSET(0);
for (i = 0; i < term.row-1; i++) {
Line line = TSCREEN.buffer[y];
for (j = 0; j < term.col-1; j++) {
if (term.line[i][j].mode & attr) {
if (line[j].mode & attr) {
tsetdirt(i, i);
break;
}
}
y = (y+1) % TSCREEN.size;
}
}
@@ -996,27 +1019,19 @@ tfulldirt(void)
void
tcursor(int mode)
{
static TCursor c[2];
int alt = IS_SET(MODE_ALTSCREEN);
if (mode == CURSOR_SAVE) {
c[alt] = term.c;
TSCREEN.sc = term.c;
} else if (mode == CURSOR_LOAD) {
term.c = c[alt];
tmoveto(c[alt].x, c[alt].y);
term.c = TSCREEN.sc;
tmoveto(term.c.x, term.c.y);
}
}
void
treset(void)
{
uint i;
term.c = (TCursor){{
.mode = ATTR_NULL,
.fg = defaultfg,
.bg = defaultbg
}, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
int i, j;
Glyph g = (Glyph){ .fg = defaultfg, .bg = defaultbg};
memset(term.tabs, 0, term.col * sizeof(*term.tabs));
for (i = tabspaces; i < term.col; i += tabspaces)
@@ -1028,17 +1043,37 @@ treset(void)
term.charset = 0;
for (i = 0; i < 2; i++) {
tmoveto(0, 0);
tcursor(CURSOR_SAVE);
tclearregion(0, 0, term.col-1, term.row-1);
tswapscreen();
term.screen[i].sc = (TCursor){{
.fg = defaultfg,
.bg = defaultbg
}};
term.screen[i].cur = 0;
term.screen[i].off = 0;
for (j = 0; j < term.row; ++j) {
if (term.col != term.linelen)
term.screen[i].buffer[j] = xrealloc(term.screen[i].buffer[j], term.col * sizeof(Glyph));
clearline(term.screen[i].buffer[j], g, 0, term.col);
}
for (j = term.row; j < term.screen[i].size; ++j) {
free(term.screen[i].buffer[j]);
term.screen[i].buffer[j] = NULL;
}
}
tcursor(CURSOR_LOAD);
term.linelen = term.col;
tfulldirt();
}
void
tnew(int col, int row)
{
term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
int i;
term = (Term){};
term.screen[0].buffer = xmalloc(HISTSIZE * sizeof(Line));
term.screen[0].size = HISTSIZE;
term.screen[1].buffer = NULL;
for (i = 0; i < HISTSIZE; ++i) term.screen[0].buffer[i] = NULL;
tresize(col, row);
treset();
}
@@ -1046,14 +1081,42 @@ tnew(int col, int row)
void
tswapscreen(void)
{
Line *tmp = term.line;
term.line = term.alt;
term.alt = tmp;
term.mode ^= MODE_ALTSCREEN;
tfulldirt();
}
void
kscrollup(const Arg *a)
{
int n = a->i;
if (IS_SET(MODE_ALTSCREEN))
return;
if (n < 0) n = (-n) * term.row;
if (n > TSCREEN.size - term.row - TSCREEN.off) n = TSCREEN.size - term.row - TSCREEN.off;
while (!TLINE(-n)) --n;
TSCREEN.off += n;
selscroll(0, n);
tfulldirt();
}
void
kscrolldown(const Arg *a)
{
int n = a->i;
if (IS_SET(MODE_ALTSCREEN))
return;
if (n < 0) n = (-n) * term.row;
if (n > TSCREEN.off) n = TSCREEN.off;
TSCREEN.off -= n;
selscroll(0, -n);
tfulldirt();
}
void
tscrolldown(int orig, int n)
{
@@ -1062,15 +1125,29 @@ tscrolldown(int orig, int n)
LIMIT(n, 0, term.bot-orig+1);
tsetdirt(orig, term.bot-n);
tclearregion(0, term.bot-n+1, term.col-1, term.bot);
for (i = term.bot; i >= orig+n; i--) {
temp = term.line[i];
term.line[i] = term.line[i-n];
term.line[i-n] = temp;
/* Ensure that lines are allocated */
for (i = -n; i < 0; i++) {
TLINE(i) = ensureline(TLINE(i));
}
/* Shift non-scrolling areas in ring buffer */
for (i = term.bot+1; i < term.row; i++) {
temp = TLINE(i);
TLINE(i) = TLINE(i-n);
TLINE(i-n) = temp;
}
for (i = 0; i < orig; i++) {
temp = TLINE(i);
TLINE(i) = TLINE(i-n);
TLINE(i-n) = temp;
}
/* Scroll buffer */
TSCREEN.cur = (TSCREEN.cur + TSCREEN.size - n) % TSCREEN.size;
/* Clear lines that have entered the view */
tclearregion(0, orig, term.linelen-1, orig+n-1);
/* Redraw portion of the screen that has scrolled */
tsetdirt(orig+n-1, term.bot);
selscroll(orig, n);
}
@@ -1082,15 +1159,29 @@ tscrollup(int orig, int n)
LIMIT(n, 0, term.bot-orig+1);
tclearregion(0, orig, term.col-1, orig+n-1);
tsetdirt(orig+n, term.bot);
for (i = orig; i <= term.bot-n; i++) {
temp = term.line[i];
term.line[i] = term.line[i+n];
term.line[i+n] = temp;
/* Ensure that lines are allocated */
for (i = term.row; i < term.row + n; i++) {
TLINE(i) = ensureline(TLINE(i));
}
/* Shift non-scrolling areas in ring buffer */
for (i = orig-1; i >= 0; i--) {
temp = TLINE(i);
TLINE(i) = TLINE(i+n);
TLINE(i+n) = temp;
}
for (i = term.row-1; i >term.bot; i--) {
temp = TLINE(i);
TLINE(i) = TLINE(i+n);
TLINE(i+n) = temp;
}
/* Scroll buffer */
TSCREEN.cur = (TSCREEN.cur + n) % TSCREEN.size;
/* Clear lines that have entered the view */
tclearregion(0, term.bot-n+1, term.linelen-1, term.bot);
/* Redraw portion of the screen that has scrolled */
tsetdirt(orig, term.bot-n+1);
selscroll(orig, -n);
}
@@ -1197,6 +1288,7 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
"", "", "", "", "", "", "", "", /* p - w */
"", "", "", "π", "", "£", "·", /* x - ~ */
};
Line line = TLINE(y);
/*
* The table is proudly stolen from rxvt.
@@ -1205,25 +1297,25 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
if (term.line[y][x].mode & ATTR_WIDE) {
if (line[x].mode & ATTR_WIDE) {
if (x+1 < term.col) {
term.line[y][x+1].u = ' ';
term.line[y][x+1].mode &= ~ATTR_WDUMMY;
line[x+1].u = ' ';
line[x+1].mode &= ~ATTR_WDUMMY;
}
} else if (term.line[y][x].mode & ATTR_WDUMMY) {
term.line[y][x-1].u = ' ';
term.line[y][x-1].mode &= ~ATTR_WIDE;
} else if (line[x].mode & ATTR_WDUMMY) {
line[x-1].u = ' ';
line[x-1].mode &= ~ATTR_WIDE;
}
term.dirty[y] = 1;
term.line[y][x] = *attr;
term.line[y][x].u = u;
line[x] = *attr;
line[x].u = u;
}
void
tclearregion(int x1, int y1, int x2, int y2)
{
int x, y, temp;
int x, y, L, S, temp;
Glyph *gp;
if (x1 > x2)
@@ -1231,15 +1323,16 @@ tclearregion(int x1, int y1, int x2, int y2)
if (y1 > y2)
temp = y1, y1 = y2, y2 = temp;
LIMIT(x1, 0, term.col-1);
LIMIT(x2, 0, term.col-1);
LIMIT(x1, 0, term.linelen-1);
LIMIT(x2, 0, term.linelen-1);
LIMIT(y1, 0, term.row-1);
LIMIT(y2, 0, term.row-1);
L = TLINEOFFSET(y1);
for (y = y1; y <= y2; y++) {
term.dirty[y] = 1;
for (x = x1; x <= x2; x++) {
gp = &term.line[y][x];
gp = &TSCREEN.buffer[L][x];
if (selected(x, y))
selclear();
gp->fg = term.c.attr.fg;
@@ -1247,6 +1340,7 @@ tclearregion(int x1, int y1, int x2, int y2)
gp->mode = 0;
gp->u = ' ';
}
L = (L + 1) % TSCREEN.size;
}
}
@@ -1261,7 +1355,7 @@ tdeletechar(int n)
dst = term.c.x;
src = term.c.x + n;
size = term.col - src;
line = term.line[term.c.y];
line = TLINE(term.c.y);
memmove(&line[dst], &line[src], size * sizeof(Glyph));
tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
@@ -1278,7 +1372,7 @@ tinsertblank(int n)
dst = term.c.x + n;
src = term.c.x;
size = term.col - dst;
line = term.line[term.c.y];
line = TLINE(term.c.y);
memmove(&line[dst], &line[src], size * sizeof(Glyph));
tclearregion(src, term.c.y, dst - 1, term.c.y);
@@ -2105,7 +2199,7 @@ tdumpline(int n)
char buf[UTF_SIZ];
const Glyph *bp, *end;
bp = &term.line[n][0];
bp = &TLINE(n)[0];
end = &bp[MIN(tlinelen(n), term.col) - 1];
if (bp != end || bp->u != ' ') {
for ( ; bp <= end; ++bp)
@@ -2198,6 +2292,28 @@ tstrsequence(uchar c)
term.esc |= ESC_STR;
}
void
tupdatebgcolor(int oldbg, int newbg)
{
for (int y = 0; y < term.row; y++) {
for (int x = 0; x < term.col; x++) {
if (TLINE(y)[x].bg == oldbg)
TLINE(y)[x].bg = newbg;
}
}
}
void
tupdatefgcolor(int oldfg, int newfg)
{
for (int y = 0; y < term.row; y++) {
for (int x = 0; x < term.col; x++) {
if (TLINE(y)[x].fg == oldfg)
TLINE(y)[x].fg = newfg;
}
}
}
void
tcontrolcode(uchar ascii)
{
@@ -2492,11 +2608,11 @@ check_control_code:
if (selected(term.c.x, term.c.y))
selclear();
gp = &term.line[term.c.y][term.c.x];
gp = &TLINE(term.c.y)[term.c.x];
if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
gp->mode |= ATTR_WRAP;
tnewline(1);
gp = &term.line[term.c.y][term.c.x];
gp = &TLINE(term.c.y)[term.c.x];
}
if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
@@ -2509,7 +2625,7 @@ check_control_code:
tnewline(1);
else
tmoveto(term.col - width, term.c.y);
gp = &term.line[term.c.y][term.c.x];
gp = &TLINE(term.c.y)[term.c.x];
}
tsetchar(u, &term.c.attr, term.c.x, term.c.y);
@@ -2540,6 +2656,11 @@ twrite(const char *buf, int buflen, int show_ctrl)
Rune u;
int n;
if (TSCREEN.off) {
TSCREEN.off = 0;
tfulldirt();
}
for (n = 0; n < buflen; n += charsize) {
if (IS_SET(MODE_UTF8)) {
/* process a complete utf8 char */
@@ -2566,56 +2687,87 @@ twrite(const char *buf, int buflen, int show_ctrl)
}
void
tresize(int col, int row)
clearline(Line line, Glyph g, int x, int xend)
{
int i;
g.mode = 0;
g.u = ' ';
for (i = x; i < xend; ++i) {
line[i] = g;
}
}
Line
ensureline(Line line)
{
if (!line) {
line = xmalloc(term.linelen * sizeof(Glyph));
}
return line;
}
void
tresize(int col, int row)
{
int i, j;
int minrow = MIN(row, term.row);
int mincol = MIN(col, term.col);
int linelen = MAX(col, term.linelen);
int *bp;
TCursor c;
if (col < 1 || row < 1) {
if (col < 1 || row < 1 || row > HISTSIZE) {
fprintf(stderr,
"tresize: error resizing to %dx%d\n", col, row);
return;
}
/*
* slide screen to keep cursor where we expect it -
* tscrollup would work here, but we can optimize to
* memmove because we're freeing the earlier lines
*/
for (i = 0; i <= term.c.y - row; i++) {
free(term.line[i]);
free(term.alt[i]);
autocomplete ((const Arg []) { ACMPL_DEACTIVATE });
/* Shift buffer to keep the cursor where we expect it */
if (row <= term.c.y) {
term.screen[0].cur = (term.screen[0].cur - row + term.c.y + 1) % term.screen[0].size;
}
/* ensure that both src and dst are not NULL */
if (i > 0) {
memmove(term.line, term.line + i, row * sizeof(Line));
memmove(term.alt, term.alt + i, row * sizeof(Line));
/* Resize and clear line buffers as needed */
if (linelen > term.linelen) {
for (i = 0; i < term.screen[0].size; ++i) {
if (term.screen[0].buffer[i]) {
term.screen[0].buffer[i] = xrealloc(term.screen[0].buffer[i], linelen * sizeof(Glyph));
clearline(term.screen[0].buffer[i], term.c.attr, term.linelen, linelen);
}
}
for (i = 0; i < minrow; ++i) {
term.screen[1].buffer[i] = xrealloc(term.screen[1].buffer[i], linelen * sizeof(Glyph));
clearline(term.screen[1].buffer[i], term.c.attr, term.linelen, linelen);
}
}
for (i += row; i < term.row; i++) {
free(term.line[i]);
free(term.alt[i]);
/* Allocate all visible lines for regular line buffer */
for (j = term.screen[0].cur, i = 0; i < row; ++i, j = (j + 1) % term.screen[0].size)
{
if (!term.screen[0].buffer[j]) {
term.screen[0].buffer[j] = xmalloc(linelen * sizeof(Glyph));
}
if (i >= term.row) {
clearline(term.screen[0].buffer[j], term.c.attr, 0, linelen);
}
}
/* Resize alt screen */
term.screen[1].cur = 0;
term.screen[1].size = row;
for (i = row; i < term.row; ++i) {
free(term.screen[1].buffer[i]);
}
term.screen[1].buffer = xrealloc(term.screen[1].buffer, row * sizeof(Line));
for (i = term.row; i < row; ++i) {
term.screen[1].buffer[i] = xmalloc(linelen * sizeof(Glyph));
clearline(term.screen[1].buffer[i], term.c.attr, 0, linelen);
}
/* resize to new height */
term.line = xrealloc(term.line, row * sizeof(Line));
term.alt = xrealloc(term.alt, row * sizeof(Line));
term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
/* resize each row to new width, zero-pad if needed */
for (i = 0; i < minrow; i++) {
term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
}
/* allocate any new rows */
for (/* i = minrow */; i < row; i++) {
term.line[i] = xmalloc(col * sizeof(Glyph));
term.alt[i] = xmalloc(col * sizeof(Glyph));
}
/* fix tabstops */
if (col > term.col) {
bp = term.tabs + term.col;
@@ -2625,26 +2777,16 @@ tresize(int col, int row)
for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
*bp = 1;
}
/* update terminal size */
term.col = col;
term.row = row;
term.linelen = linelen;
/* reset scrolling region */
tsetscroll(0, row-1);
/* make use of the LIMIT in tmoveto */
tmoveto(term.c.x, term.c.y);
/* Clearing both screens (it makes dirty all lines) */
c = term.c;
for (i = 0; i < 2; i++) {
if (mincol < col && 0 < minrow) {
tclearregion(mincol, 0, col - 1, minrow - 1);
}
if (0 < col && minrow < row) {
tclearregion(0, minrow, col - 1, row - 1);
}
tswapscreen();
tcursor(CURSOR_LOAD);
}
term.c = c;
tfulldirt();
}
void
@@ -2656,14 +2798,15 @@ resettitle(void)
void
drawregion(int x1, int y1, int x2, int y2)
{
int y;
int y, L;
L = TLINEOFFSET(y1);
for (y = y1; y < y2; y++) {
if (!term.dirty[y])
continue;
term.dirty[y] = 0;
xdrawline(term.line[y], x1, y, x2);
if (term.dirty[y]) {
term.dirty[y] = 0;
xdrawline(TSCREEN.buffer[L], x1, y, x2);
}
L = (L + 1) % TSCREEN.size;
}
}
@@ -2678,14 +2821,15 @@ draw(void)
/* adjust cursor position */
LIMIT(term.ocx, 0, term.col-1);
LIMIT(term.ocy, 0, term.row-1);
if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
if (TLINE(term.ocy)[term.ocx].mode & ATTR_WDUMMY)
term.ocx--;
if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
if (TLINE(term.c.y)[cx].mode & ATTR_WDUMMY)
cx--;
drawregion(0, 0, term.col, term.row);
xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
if (TSCREEN.off == 0)
xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx],
term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]);
term.ocx = cx;
term.ocy = term.c.y;
xfinishdraw();
@@ -2699,3 +2843,227 @@ redraw(void)
tfulldirt();
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());
}

5
st.h
View File

@@ -19,6 +19,7 @@
#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
#define IS_TRUECOL(x) (1 << 24 & (x))
#define HISTSIZE 2000
enum glyph_attribute {
ATTR_NULL = 0,
@@ -77,6 +78,8 @@ typedef union {
const char *s;
} Arg;
void autocomplete (const Arg *);
void die(const char *, ...);
void redraw(void);
void draw(void);
@@ -90,6 +93,8 @@ int tattrset(int);
void tnew(int, int);
void tresize(int, int);
void tsetdirtattr(int);
void tupdatebgcolor(int, int);
void tupdatefgcolor(int, int);
void ttyhangup(void);
int ttynew(const char *, char *, const char *, char **);
size_t ttyread(void);

63
x.c
View File

@@ -59,6 +59,10 @@ static void zoom(const Arg *);
static void zoomabs(const Arg *);
static void zoomreset(const Arg *);
static void ttysend(const Arg *);
static void nextscheme(const Arg *);
static void selectscheme(const Arg *);
void kscrollup(const Arg *);
void kscrolldown(const Arg *);
/* config.h for applying patches and the configuration. */
#include "config.h"
@@ -185,6 +189,7 @@ static void mousesel(XEvent *, int);
static void mousereport(XEvent *);
static char *kmap(KeySym, uint);
static int match(uint, uint);
static void updatescheme(void);
static void run(void);
static void usage(void);
@@ -801,7 +806,7 @@ xloadcols(void)
for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
} else {
dc.collen = MAX(LEN(colorname), 256);
dc.collen = 258;
dc.col = xmalloc(dc.collen * sizeof(Color));
}
@@ -1862,11 +1867,20 @@ kpress(XEvent *ev)
/* 1. shortcuts */
for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
if (ksym == bp->keysym && match(bp->mod, e->state)) {
if (bp -> func != autocomplete)
autocomplete ((const Arg []) { ACMPL_DEACTIVATE });
bp->func(&(bp->arg));
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 */
if ((customkey = kmap(ksym, e->state))) {
ttywrite(customkey, strlen(customkey), 1);
@@ -2036,6 +2050,47 @@ usage(void)
" [stty_args ...]\n", argv0, argv0);
}
void
nextscheme(const Arg *arg)
{
colorscheme += arg->i;
if (colorscheme >= (int)LEN(schemes))
colorscheme = 0;
else if (colorscheme < 0)
colorscheme = LEN(schemes) - 1;
updatescheme();
}
void
selectscheme(const Arg *arg)
{
if (BETWEEN(arg->i, 0, LEN(schemes)-1)) {
colorscheme = arg->i;
updatescheme();
}
}
void
updatescheme(void)
{
int oldbg, oldfg;
oldbg = defaultbg;
oldfg = defaultfg;
colorname = schemes[colorscheme].colors;
defaultbg = schemes[colorscheme].bg;
defaultfg = schemes[colorscheme].fg;
defaultcs = schemes[colorscheme].cs;
defaultrcs = schemes[colorscheme].rcs;
xloadcols();
if (defaultbg != oldbg)
tupdatebgcolor(oldbg, defaultbg);
if (defaultfg != oldfg)
tupdatefgcolor(oldfg, defaultfg);
cresize(win.w, win.h);
redraw();
}
int
main(int argc, char *argv[])
{
@@ -2088,6 +2143,12 @@ main(int argc, char *argv[])
} ARGEND;
run:
colorname = schemes[colorscheme].colors;
defaultbg = schemes[colorscheme].bg;
defaultfg = schemes[colorscheme].fg;
defaultcs = schemes[colorscheme].cs;
defaultrcs = schemes[colorscheme].rcs;
if (argc > 0) /* eat all remaining arguments */
opt_cmd = argv;