stagit

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 163796828c599ae93fd57574fd2b7559920041f4
parent 53804cf99c498fff23b1da692a6eec221e899df7
Author: Jack Sangdahl <jack@pngu.org>
Date:   Fri, 10 Jul 2026 09:22:34 -0600

Add syntax highlighting

Diffstat:
A.gitignore | 4++++
Ahighlight | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Arequirements.txt | 3+++
Mstagit.c | 65+++++++++++++++++++++++++++++++++++++++--------------------------
Mstyle.css | 159++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
5 files changed, 205 insertions(+), 76 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +.venv +*.o +stagit +stagit-index diff --git a/highlight b/highlight @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +from sys import stdin + +import pygments +from markdown import markdown +from pygments import highlight +from pygments.formatters import HtmlFormatter +from pygments.lexers import TextLexer, guess_lexer, guess_lexer_for_filename + +filename = stdin.readline().strip() +contents = stdin.read() +lexer = None +rendered = None + +try: + lexer = guess_lexer_for_filename(filename, contents) +except pygments.util.ClassNotFound: + try: + lexer = guess_lexer(contents) + except pygments.util.ClassNotFound: + pass + +if lexer is None: + lexer = TextLexer() + +if lexer.__class__ is pygments.lexers.MarkdownLexer: + rendered = markdown( + contents, + extensions=["codehilite", "extra", "sane_lists", "smarty", "pymdownx.tasklist"], + ) + +format = HtmlFormatter( + style="murphy", + cssclass="highlight", + linenos="table", + lineanchors="loc", + anchorlinenos=True, +) + +if rendered: + print('<article class="markup markdown">') + print(rendered) + print("</article>") +print('<div id="blob">') +print(highlight(contents, lexer, format)) +print("</div>") +print("<style>") +print(format.get_style_defs("")) +print("</style>") diff --git a/requirements.txt b/requirements.txt @@ -0,0 +1,3 @@ +markdown +pygments +pymdown-extensions diff --git a/stagit.c b/stagit.c @@ -69,7 +69,7 @@ static char cloneurl[1024]; static char *submodules; static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:LICENSE.txt", "HEAD:COPYING" }; static char *license; -static char *readmefiles[] = { "HEAD:README", "HEAD:README.md" }; +static char *readmefiles[] = { "HEAD:readme.md" }; static char *readme; static long long nlogcommits = -1; /* -1 indicates not used */ @@ -556,37 +556,50 @@ writefooter(FILE *fp) fputs("</div>\n</body>\n</html>\n", fp); } + size_t -writeblobhtml(FILE *fp, const git_blob *blob) +syntax_highlight(const char *filename, FILE *fp, const char *s, size_t len) { - size_t n = 0, i, len, prev; - const char *nfmt = "<a href=\"#l%zu\" class=\"line\" id=\"l%zu\">%7zu</a> "; - const char *s = git_blob_rawcontent(blob); + fflush(fp); + int stdout_copy = dup(1); + dup2(fileno(fp), 1); + + FILE *child = popen("./highlight", "w"); + if (child == NULL) { + fprintf(stderr, "child is null: %s\n", strerror(errno)); + exit(1); + } - len = git_blob_rawsize(blob); - fputs("<pre id=\"blob\">\n", fp); + // Pass filename first so Python knows the extension + fprintf(child, "%s\n", filename); - if (len > 0) { - for (i = 0, prev = 0; i < len; i++) { - if (s[i] != '\n') - continue; - n++; - fprintf(fp, nfmt, n, n, n); - xmlencodeline(fp, &s[prev], i - prev + 1); - putc('\n', fp); - prev = i + 1; - } - /* trailing data */ - if ((len - prev) > 0) { - n++; - fprintf(fp, nfmt, n, n, n); - xmlencodeline(fp, &s[prev], len - prev); - } + // Feed the raw code block to Python + size_t lc = 0; + for (size_t i = 0; *s && i < len; s++, i++) { + if (*s == '\n') lc++; + fprintf(child, "%c", *s); } - fputs("</pre>\n", fp); + pclose(child); + fflush(stdout); + dup2(stdout_copy, 1); + close(stdout_copy); + + return lc; +} + +size_t +writeblobhtml(const char *filename, FILE *fp, const git_blob *blob) +{ + size_t lc = 0; + const char *s = git_blob_rawcontent(blob); + git_off_t len = git_blob_rawsize(blob); + + if (len > 0) { + lc = syntax_highlight(filename, fp, s, len); + } - return n; + return lc; } void @@ -977,7 +990,7 @@ writeblob(git_object *obj, const char *fpath, const char *filename, size_t files if (git_blob_is_binary((git_blob *)obj)) fputs("<p>Binary file.</p>\n", fp); else - lc = writeblobhtml(fp, (git_blob *)obj); + lc = writeblobhtml(filename, fp, (git_blob *)obj); writefooter(fp); checkfileerror(fp, fpath, 'w'); diff --git a/style.css b/style.css @@ -1,9 +1,19 @@ +html { + font-size: 12px; + height: 100%; +} + body { - color: #000; + margin: 5rem auto; + color: #222; background-color: #fff; font-family: monospace; + width: 66rem; } +pre { -moz-tab-size: 4; } +pre { tab-size: 4; } + h1, h2, h3, h4, h5, h6 { font-size: 1em; margin: 0; @@ -28,8 +38,89 @@ a.line { text-decoration: none; } +#blob { + display: block; + max-width: 100%; + overflow-x: scroll; +} + +article.markup { + font-size: 15px; + border: 2px solid #00000017; + border-radius: 10px; + font-family: sans-serif; + padding: 2.5em; + margin: 2em 0; +} + +article.markup code { + font-size: 0.9em; + border: 1px solid #dbdbdb; + background-color: #f7f7f7; + padding: 0 0.3em; + border-radius: 0.3em +} + +article.markup pre code { + border: none; + background: none; + padding: 0; + border-radius: 0; +} + +article.markup pre { + background-color: #f7f7f7; + padding: 1em; + border: 1px solid #dbdbdb; + border-radius: 0.3em; +} + +article.markup h1 { + font-size: 2.4em; + padding-bottom: 6px; + border-bottom: 5px solid #0000000a; +} + +article.markup h2 { + font-size: 1.9em; + padding-bottom: 5px; + border-bottom: 2px solid #00000014; +} + +article.markup h3 { + font-size: 1.5em; +} + +article.markup h4 { + font-size: 1.3em; +} + +article.markup h5 { + font-size: 1.1em; +} + +article.markup h6 { + font-size: 1em; +} + +article img { + max-width: 100%; +} + +.linenos { + margin-right: 0; + border-right: 1px solid rgb(0 0 0 / 8%); + user-select: none; +} + +.linenos a { + margin-right: 0.9em; + user-select: none; + text-decoration: none; +} + #blob a { - color: #555; + color: #777; } #blob a:hover { @@ -70,12 +161,12 @@ td.num { } .desc { - color: #555; + color: #777; } hr { border: 0; - border-top: 1px solid #555; + border-top: 1px solid #777; height: 1px; } @@ -105,50 +196,18 @@ pre a.d:hover { text-decoration: none; } -@media (prefers-color-scheme: dark) { - body { - background-color: #000; - color: #bdbdbd; - } - hr { - border-color: #222; - } - a { - color: #56c8ff; - } - a:target { - background-color: #222; - } - .desc { - color: #aaa; - } - #blob a { - color: #555; - } - #blob a:target { - color: #eee; - } - #blob a:hover { - color: #56c8ff; - } - pre a.h { - color: #00cdcd; - } - .A, - span.i, - pre a.i { - color: #00cd00; - } - .D, - span.d, - pre a.d { - color: #cd0000; - } - #branches tr:hover td, - #tags tr:hover td, - #index tr:hover td, - #log tr:hover td, - #files tr:hover td { - background-color: #111; - } +.url td:nth-child(2) { + padding-top: 0.2em; + padding-bottom: 0.9em; +} + +.url td:nth-child(2) span { + padding: 1px 5px; + background-color: #eee; + border: 1px solid #ddd; + border-radius: 5px; +} + +.url td:nth-child(2) span a { + color: #444; }