summaryrefslogtreecommitdiffstats
path: root/lib/IkiWiki/Plugin/highlight.pm
blob: 860b9c30a5a52b5de77faa3e1e0fced2e97dfa88 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/usr/bin/perl
use strict;
use warnings;
package IkiWiki::Plugin::highlight;
use IkiWiki '3.00';
use Syntax::Highlight::Engine::Kate;

# variables {{{
my %hl_args = (
    substitutions => {
        '<' => '&lt;',
        '>' => '&gt;',
        '&' => '&amp;',
        '[' => '&#91;', # wow is this a huge hack
    },
    format_table  => {
        Alert        => ["<span class='synAlert'>",        "</span>"],
        BaseN        => ["<span class='synBaseN'>",        "</span>"],
        BString      => ["<span class='synBString'>",      "</span>"],
        Char         => ["<span class='synChar'>",         "</span>"],
        Comment      => ["<span class='synComment'>",      "</span>"],
        DataType     => ["<span class='synDataType'>",     "</span>"],
        DecVal       => ["<span class='synDecVal'>",       "</span>"],
        Error        => ["<span class='synError'>",        "</span>"],
        Float        => ["<span class='synFloat'>",        "</span>"],
        Function     => ["<span class='synFunction'>",     "</span>"],
        IString      => ["<span class='synIString'>",      "</span>"],
        Keyword      => ["<span class='synKeyword'>",      "</span>"],
        Normal       => ["",                               ""       ],
        Operator     => ["<span class='synOperator'>",     "</span>"],
        Others       => ["<span class='synOthers'>",       "</span>"],
        RegionMarker => ["<span class='synRegionMarker'>", "</span>"],
        Reserved     => ["<span class='synReserved'>",     "</span>"],
        String       => ["<span class='synString'>",       "</span>"],
        Variable     => ["<span class='synVariable'>",     "</span>"],
        Warning      => ["<span class='synWarning'>",      "</span>"],
    },
);
my $syntaxes;
# }}}

sub import { # {{{
    my $hl = Syntax::Highlight::Engine::Kate->new(%hl_args);
    $syntaxes = { map { +lc($_) => $hl->syntaxes->{$_} }
                      keys %{ $hl->syntaxes } };
    $syntaxes->{c}     = 'ANSI C89';
    $syntaxes->{cpp}   = 'C++';
    $syntaxes->{ocaml} = 'Objective Caml';
    hook(type => 'filter', id => __PACKAGE__, call => \&filter);
} # }}}

sub filter { # {{{
    my %args = @_;
    my $content = '';
    my $code_block = '';
    my $in_code = 0;
    my $code_re = qr/^(?: {4}|\t)/;
    my $empty_re = qr/^\s*$/;
    for my $line (split /^/m, $args{content}) {
        if ($in_code) {
            if ($line =~ $code_re || $line =~ $empty_re) {
                $line =~ s/^(?: ? ? ? ?|\t)//;
                $code_block .= $line;
            }
            else {
                $code_block =~ s/(\s*)$//;
                my $ws = $1;
                $content .= highlight($code_block);
                $in_code = 0;
                $code_block = '';
                $content .= $ws;
                $content .= $line;
            }
        }
        else {
            if ($line =~ $code_re && $line !~ $empty_re) {
                $in_code = 1;
                $line =~ s/^(?: ? ? ? ?|\t)//;
                $code_block .= $line;
            }
            else {
                $content .= $line;
            }
        }
    }
    $content .= $code_block;
    return $content;
} # }}}

sub highlight { # {{{
    my ($code_block) = @_;
    my $filetype = guess_filetype($code_block);
    return $code_block unless $filetype;
    my $hl = Syntax::Highlight::Engine::Kate->new(%hl_args,
                                                  language => $filetype);
    $code_block =~ s/[^\n]*\n//s;
    $filetype =~ s/\s/_/g;
    return "<pre><code class='lang$filetype'>" .
           $hl->highlightText($code_block) .
           '</code></pre>';
} # }}}

sub guess_filetype { # {{{
    my ($code_block) = @_;
    my ($first_line) = split /\n/, $code_block;
    if ($first_line =~ m{^#!\s*(?:.+[\s/]+)*(\w+)}) {
        return $syntaxes->{$1};
    }
    elsif ($first_line =~ m{^[/(]\*\s*(\w+)\s*\*[/)]}) {
        return $syntaxes->{$1};
    }
    elsif ($first_line =~ m{^[-/]{2}\s*(\w+)}) {
        return $syntaxes->{$1};
    }
    elsif ($first_line =~ m{^<!--\s*(\w+)\s*-->}) {
        return $syntaxes->{$1};
    }
} # }}}

1;