Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ TIG_OBJS = \
src/grep.o \
src/ui.o \
src/apps.o \
src/ansi.o \
src/color_mode.o \
$(GRAPH_OBJS) \
$(COMPAT_OBJS)

Expand All @@ -374,10 +376,16 @@ src/tig: $(TIG_OBJS)
TEST_GRAPH_OBJS = test/tools/test-graph.o src/string.o src/util.o src/io.o $(GRAPH_OBJS) $(COMPAT_OBJS)
test/tools/test-graph: $(TEST_GRAPH_OBJS)

TEST_ANSI_OBJS = test/tools/test-ansi.o src/ansi.o $(COMPAT_OBJS)
test/tools/test-ansi: $(TEST_ANSI_OBJS)

TEST_COLOR_MODE_OBJS = test/tools/test-color-mode.o src/color_mode.o $(COMPAT_OBJS)
test/tools/test-color-mode: $(TEST_COLOR_MODE_OBJS)

DOC_GEN_OBJS = tools/doc-gen.o src/string.o src/types.o src/util.o src/request.o $(COMPAT_OBJS)
tools/doc-gen: $(DOC_GEN_OBJS)

OBJS = $(sort $(TIG_OBJS) $(TEST_GRAPH_OBJS) $(DOC_GEN_OBJS))
OBJS = $(sort $(TIG_OBJS) $(TEST_GRAPH_OBJS) $(TEST_ANSI_OBJS) $(TEST_COLOR_MODE_OBJS) $(DOC_GEN_OBJS))

DEPS_CFLAGS ?= -MMD -MP -MF .deps/$*.d

Expand Down
76 changes: 76 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,79 @@ link:INSTALL.{docext}[the installation instructions].

News about releases and latest features and bug fixes are found in
link:NEWS.{docext}[the release notes].

Fork-specific features
----------------------

This fork (`me/syntax-and-diff-bg-highlighting`) adds two diff-view changes
on top of upstream tig:

* Full-row red/green background bars on `-`/`+` lines (in place of upstream's
single-glyph coloring).
* Syntax-highlighted diff text, piped through https://github.com/sharkdp/bat[`bat`]
for the file's language. Requires `bat` on `$PATH`.

Recommended terminal setup
~~~~~~~~~~~~~~~~~~~~~~~~~~

The background bars look best in 24-bit direct color. The bar still works
at lower color tiers but uses coarser hues — see "Graceful degradation"
below.

In a normal terminal (alacritty, kitty, foot, xterm, wezterm, etc.):

[source,sh]
----
export TERM=xterm-direct
----

Inside neovim's `:terminal` (libvterm-based):

[source,sh]
----
export TERM=xterm-direct2
----

Why the split: stock `xterm-direct`'s `setab`/`setaf` strings emit SGR
sequences in the ITU T.416 pedantic form `CSI 48:2::R:G:B m` (two colons
after `2`). libvterm doesn't parse the empty colorspace-ID slot and
mis-reads the RGB triplet, producing garbled backgrounds. The
`xterm-direct2` entry drops that slot — `CSI 48:2:R:G:B m` — which every
libvterm-based terminal handles correctly. xterm and other emulators
parse either form.

If `xterm-direct`/`xterm-direct2` is not present on your system, the
ncurses package usually provides it under `/usr/share/terminfo` or
`/opt/homebrew/opt/ncurses/share/terminfo` (`infocmp xterm-direct` to
verify). On Linux, the brewed ncurses ships these:

[source,sh]
----
export TERMINFO_DIRS=/home/linuxbrew/.linuxbrew/share/terminfo:
----

Graceful degradation
~~~~~~~~~~~~~~~~~~~~

The diff-bg code picks colors at runtime based on ncurses' `COLORS`
report. There are three tiers:

[cols="1,2,3"]
|===
| Tier | Condition | Behaviour

| Truecolor | `COLORS >= 16777216`
| Renders the bar in the GitHub-style dark red / dark green RGB values
(`#5f0000` / `#005f00`). diff-highlight emphasis ranges get brighter
variants (`#870000` / `#5f875f`).
| 256-palette | `COLORS >= 256`
| Renders the bar using the xterm-256 palette indices the truecolor RGB
values are derived from (22, 52, 65, 88). Hues match exactly when the
terminal uses the canonical xterm color cube.
| Basic | `COLORS < 256`
| Falls back to the 8 basic ANSI colors. The dark/medium emphasis
distinction is lost (52 and 88 both render as red); the bar is still
full-row.
|===

The classification logic is exercised by `test/tools/test-color-mode.c`.
17 changes: 17 additions & 0 deletions doc/tigrc.5.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,23 @@ The following variables can be set:
regions are governed by `color diff-add-highlight` and
`color diff-del-highlight`.

'syntax-highlight' (mixed)::

Whether to syntax-highlight file contents in diff, stage, blob, and
pager views using an external highlighter. Defaults to false. When set
to true then 'bat' is used, else the option value is used as the path
to the highlighter command. Requires a terminal with 256-color support
(TERM=xterm-256color or equivalent); silently disabled on terminals
with fewer than 256 colors. +
+
When enabled, diff views show syntax-colored code on dark green
(additions) or dark red (deletions) backgrounds. Can be combined with
'diff-highlight' for intra-line change emphasis. Long lines are wrapped
with a 2-space indent on continuation lines. +
+
The highlighter must accept `--color=always --style=plain --paging=never
--file-name=<name> -` arguments and read from stdin.

'diff-indicator' (bool)::

Show +/- signs in the diff view. On by default.
Expand Down
69 changes: 69 additions & 0 deletions include/tig/ansi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* Copyright (c) 2006-2026 Jonas Fonseca <jonas.fonseca@gmail.com>
*
* 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 2 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.
*/

#ifndef TIG_ANSI_H
#define TIG_ANSI_H

#include "tig/tig.h"

#define ANSI_MAX_SPANS 512

/* Parameters in one SGR sequence. The colon form needs six per color, so
* a reset plus attributes plus fg and bg can reach the high teens. */
#define ANSI_MAX_SGR_CODES 32

enum ansi_color_type {
ANSI_COLOR_DEFAULT,
ANSI_COLOR_BASIC, /* 0-7: standard ANSI colors */
ANSI_COLOR_256, /* 0-255: extended palette */
ANSI_COLOR_RGB /* 24-bit truecolor */
};

struct ansi_color {
enum ansi_color_type type;
union {
int index;
struct { unsigned char r, g, b; } rgb;
};
};

struct ansi_span {
struct ansi_color fg;
struct ansi_color bg;
int attr; /* ncurses attributes: A_BOLD, A_UNDERLINE, etc. */
size_t offset; /* byte offset in stripped text */
size_t length; /* byte length of this span */
};

/*
* Parse a line containing ANSI escape sequences.
*
* Strips escape codes from `raw` and writes plain text to `stripped`.
* Records color/attribute spans in `spans`.
*
* Returns the number of spans written, or -1 on error.
*/
int ansi_parse_line(const char *raw, char *stripped, size_t stripped_size,
struct ansi_span *spans, int max_spans);

/*
* Returns true if the string contains ANSI escape sequences.
*/
static inline bool
ansi_has_escapes(const char *text)
{
return text && strchr(text, 0x1b) != NULL;
}

#endif
/* vim: set ts=8 sw=8 noexpandtab: */
6 changes: 6 additions & 0 deletions include/tig/apps.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ struct app_external {

struct app_external *app_diff_highlight_load(const char *query);

/*
* syntax-highlight (bat)
*/

struct app_external *app_syntax_highlight_load(const char *query, const char *filename);

#endif

/* vim: set ts=8 sw=8 noexpandtab: */
57 changes: 57 additions & 0 deletions include/tig/color_mode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* Copyright (c) 2006-2026 Jonas Fonseca <jonas.fonseca@gmail.com>
*
* 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 2 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.
*/

#ifndef TIG_COLOR_MODE_H
#define TIG_COLOR_MODE_H

#include "tig/ansi.h"

/*
* Runtime color-support tier.
*
* In direct-color mode (TERM=*-direct) ncurses treats color *numbers*
* as packed 24-bit RGB. Passing a 256-palette index like 52 yields
* RGB(0,0,52) — nearly black — instead of dark red. Code that wants
* a specific hue must translate palette indices to real RGB.
*/
enum tig_color_mode {
TIG_COLOR_BASIC, /* < 256: coarse 8-color ANSI fallback */
TIG_COLOR_256, /* 256-palette indices work as-is */
TIG_COLOR_TRUECOLOR /* numbers are packed 24-bit RGB */
};

/*
* Pure classification of an ncurses COLORS count into a tier.
* Separated from the ncurses dependency so it is unit-testable.
*/
enum tig_color_mode tig_classify_color_mode(int ncurses_colors);

/*
* Translate an ANSI color descriptor to the int value that should be
* handed to init_extended_pair under the given color mode.
*
* - Basic ANSI 0-7 are returned unchanged in every mode (terminfo
* setaf/setab entries special-case them even in direct mode).
* - 256-palette indices are returned unchanged in TIG_COLOR_256,
* converted to packed RGB in TIG_COLOR_TRUECOLOR, and approximated
* to a basic ANSI color in TIG_COLOR_BASIC.
* - Truecolor (24-bit) input is packed into 0xRRGGBB in TRUECOLOR
* mode, mapped to the nearest xterm-256 index in PALETTE_256, and
* coarsened to basic ANSI in BASIC mode.
* - ANSI_COLOR_DEFAULT always returns -1 (ncurses default-color).
*/
int ansi_color_to_ncurses_for_mode(const struct ansi_color *color,
enum tig_color_mode mode);

#endif
/* vim: set ts=8 sw=8 noexpandtab: */
67 changes: 67 additions & 0 deletions include/tig/diff.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,47 @@

#include "tig/view.h"

/*
* Deferred syntax highlighting.
*
* Running the highlighter while the diff is read costs a fork+exec per file,
* which dominates the load time of a diff touching many files. Instead each
* content line is recorded and rendered plain (keeping the diff background so
* only foregrounds change later), and the highlighter runs when a line is
* about to be drawn. Files nobody scrolls to never spawn a process.
*
* Each file keeps its own highlighter and is fed strictly in order, so the
* lexer state always matches the real file. `fed` counts lines written to the
* current process and resets when it is recycled; `applied` counts lines whose
* colors have been installed and only ever grows.
*/
struct diff_lazy_line {
char *data; /* original diff line, prefix included */
unsigned long lineno; /* first view line for this diff line */
unsigned int segs; /* view lines it wrapped into */
enum line_type type;
bool indicator_stripped; /* caller already removed the +/- */
};

struct diff_lazy_file {
char path[SIZEOF_STR];
pid_t pid;
int write_fd;
FILE *read_fp;
bool failed; /* highlighter gone; leave rest plain */
unsigned int used; /* LRU stamp for pipe recycling */
unsigned int fed; /* lines written to the current pipe */
unsigned int applied; /* lines whose colors are installed */
struct diff_lazy_line *lines;
size_t nlines;
};

/* Reverse map from a view line back to the diff line that produced it */
struct diff_lazy_map {
int file; /* index into syntax_files, or -1 */
unsigned int index; /* line index within that file */
};

struct diff_state {
bool after_commit_title;
bool after_diff;
Expand All @@ -30,6 +71,15 @@ struct diff_state {
unsigned int lineno;
struct position pos;
struct io view_io;
/* Syntax highlighting state */
bool syntax_highlight;
char syntax_file[SIZEOF_STR]; /* Current file from diff +++ header */
struct diff_lazy_file *syntax_files;
size_t syntax_nfiles;
int syntax_current; /* file being read, or -1 */
unsigned int syntax_clock; /* ticks the LRU stamps */
struct diff_lazy_map *syntax_map;
size_t syntax_map_size; /* view lines covered by syntax_map */
};

enum request diff_common_edit(struct view *view, enum request request, struct line *line);
Expand All @@ -41,6 +91,23 @@ void diff_save_line(struct view *view, struct diff_state *state, enum open_flags
void diff_restore_line(struct view *view, struct diff_state *state);
enum status_code diff_init_highlight(struct view *view, struct diff_state *state);
bool diff_done_highlight(struct diff_state *state);
void diff_init_syntax_highlight(struct diff_state *state);
void diff_done_syntax_highlight(struct diff_state *state);

/*
* Draw and teardown hooks shared by the diff-like views, which defer syntax
* highlighting until a line is about to be drawn.
*/
bool diff_draw(struct view *view, struct line *line, unsigned int lineno);
void diff_done(struct view *view);

/*
* A wrapped diff line occupies several view lines. Code that turns view lines
* back into a patch must skip the continuations and use the original text.
*/
bool diff_is_wrapped_continuation(struct view *view, struct line *line);
const char *diff_original_text(struct view *view, struct line *line,
char *buf, size_t bufsize);

unsigned int diff_get_lineno(struct view *view, struct line *line, bool old);
const char *diff_get_pathname(struct view *view, struct line *line, bool old);
Expand Down
7 changes: 7 additions & 0 deletions include/tig/line.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,12 @@ get_line_attr(const char *prefix, enum line_type type)
return COLOR_PAIR(COLOR_ID(info->color_pair)) | info->attr;
}

/*
* Dynamic color pair allocation for syntax highlighting.
*/
struct ansi_color;
int ansi_color_to_ncurses(const struct ansi_color *color);
int get_dynamic_color_pair(const struct ansi_color *fg, const struct ansi_color *bg);

#endif
/* vim: set ts=8 sw=8 noexpandtab: */
1 change: 1 addition & 0 deletions include/tig/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ typedef struct view_column *view_settings;
_(status_show_untracked_dirs, bool, VIEW_STATUS_LIKE) \
_(status_show_untracked_files, bool, VIEW_STATUS_LIKE) \
_(status_view, view_settings, VIEW_NO_FLAGS) \
_(syntax_highlight, const char *, VIEW_NO_FLAGS) \
_(tab_size, int, VIEW_NO_FLAGS) \
_(tree_view, view_settings, VIEW_NO_FLAGS) \
_(truncation_delimiter, const char *, VIEW_NO_FLAGS) \
Expand Down
1 change: 1 addition & 0 deletions include/tig/pager.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

bool pager_get_column_data(struct view *view, const struct line *line, struct view_column_data *column_data);
bool pager_common_read(struct view *view, const char *data, enum line_type type, struct line **line);
bool pager_add_ansi_line(struct view *view, const char *data, enum line_type type);
enum request pager_request(struct view *view, enum request request, struct line *line);
void pager_select(struct view *view, struct line *line);

Expand Down
3 changes: 3 additions & 0 deletions include/tig/view.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ struct view_ops;
struct box_cell {
enum line_type type;
size_t length;
unsigned int direct : 1; /* If set, use color_pair/attr directly */
int color_pair; /* Dynamic ncurses color pair ID */
int attr; /* A_BOLD | A_UNDERLINE | ... */
};

struct box {
Expand Down
Loading
Loading