about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Makefile28
-rw-r--r--README.md5
-rwxr-xr-xadapter/protocol/man241
-rw-r--r--adapter/tools/mancha.nim76
-rw-r--r--doc/cha.16
-rw-r--r--doc/mancha.151
-rw-r--r--doc/protocols.md10
-rw-r--r--res/license.html22
-rw-r--r--res/urimethodmap3
-rw-r--r--todo3
10 files changed, 434 insertions, 11 deletions
diff --git a/Makefile b/Makefile
index 58557e06..7b13deb1 100644
--- a/Makefile
+++ b/Makefile
@@ -39,21 +39,26 @@ FLAGS += -d:release --debugger:native
 endif
 
 .PHONY: all
-all: $(OUTDIR_BIN)/cha $(OUTDIR_CGI_BIN)/http \
+all: $(OUTDIR_BIN)/cha $(OUTDIR_BIN)/mancha $(OUTDIR_CGI_BIN)/http \
 	$(OUTDIR_CGI_BIN)/gmifetch $(OUTDIR_LIBEXEC)/gmi2html \
 	$(OUTDIR_CGI_BIN)/gopher $(OUTDIR_LIBEXEC)/gopher2html \
 	$(OUTDIR_CGI_BIN)/cha-finger $(OUTDIR_CGI_BIN)/about \
 	$(OUTDIR_CGI_BIN)/data $(OUTDIR_CGI_BIN)/file $(OUTDIR_CGI_BIN)/ftp \
-	$(OUTDIR_CGI_BIN)/spartan \
+	$(OUTDIR_CGI_BIN)/man $(OUTDIR_CGI_BIN)/spartan \
 	$(OUTDIR_LIBEXEC)/urldec $(OUTDIR_LIBEXEC)/urlenc
 
 $(OUTDIR_BIN)/cha: lib/libquickjs.a src/*.nim src/**/*.nim src/**/*.c res/* \
 		res/**/* res/map/idna_gen.nim
-	@mkdir -p "$(OUTDIR)/$(TARGET)/bin"
+	@mkdir -p "$(OUTDIR_BIN)"
 	$(NIMC) --nimcache:"$(OBJDIR)/$(TARGET)/cha" -d:libexecPath=$(LIBEXECDIR) \
 		$(FLAGS) -o:"$(OUTDIR_BIN)/cha" src/main.nim
 	ln -sf "$(OUTDIR)/$(TARGET)/bin/cha" cha
 
+$(OUTDIR_BIN)/mancha: adapter/tools/mancha.nim
+	@mkdir -p "$(OUTDIR_BIN)"
+	$(NIMC) --nimcache:"$(OBJDIR)/$(TARGET)/mancha" $(FLAGS) \
+		-o:"$(OUTDIR_BIN)/mancha" $(FLAGS) adapter/tools/mancha.nim
+
 $(OBJDIR)/genidna: res/genidna.nim
 	$(NIMC) --nimcache:"$(OBJDIR)/idna_gen_cache" -d:danger \
 		-o:"$(OBJDIR)/genidna" res/genidna.nim
@@ -90,6 +95,10 @@ $(OUTDIR_CGI_BIN)/cha-finger: adapter/protocol/cha-finger
 	@mkdir -p $(OUTDIR_CGI_BIN)
 	cp adapter/protocol/cha-finger $(OUTDIR_CGI_BIN)
 
+$(OUTDIR_CGI_BIN)/man: adapter/protocol/man
+	@mkdir -p $(OUTDIR_CGI_BIN)
+	cp adapter/protocol/man $(OUTDIR_CGI_BIN)
+
 $(OUTDIR_CGI_BIN)/spartan: adapter/protocol/spartan
 	@mkdir -p $(OUTDIR_CGI_BIN)
 	cp adapter/protocol/spartan $(OUTDIR_CGI_BIN)
@@ -177,6 +186,10 @@ $(OBJDIR)/man/cha.1: doc/cha.1
 	@mkdir -p "$(OBJDIR)/man"
 	cp doc/cha.1 "$(OBJDIR)/man/cha.1"
 
+$(OBJDIR)/man/mancha.1: doc/mancha.1
+	@mkdir -p "$(OBJDIR)/man"
+	cp doc/mancha.1 "$(OBJDIR)/man/mancha.1"
+
 .PHONY: clean
 clean:
 	rm -rf "$(OBJDIR)/$(TARGET)"
@@ -187,12 +200,13 @@ clean:
 manpage: $(OBJDIR)/man/cha-config.5 $(OBJDIR)/man/cha-mailcap.5 \
 	$(OBJDIR)/man/cha-mime.types.5 $(OBJDIR)/man/cha-localcgi.5 \
 	$(OBJDIR)/man/cha-urimethodmap.5 $(OBJDIR)/man/cha-protocols.5 \
-	$(OBJDIR)/man/cha.1
+	$(OBJDIR)/man/cha.1 $(OBJDIR)/man/mancha.1
 
 .PHONY: install
 install:
 	mkdir -p "$(DESTDIR)$(PREFIX)/bin"
 	install -m755 "$(OUTDIR_BIN)/cha" "$(DESTDIR)$(PREFIX)/bin"
+	install -m755 "$(OUTDIR_BIN)/mancha" "$(DESTDIR)$(PREFIX)/bin"
 	@# intentionally not quoted
 	mkdir -p $(LIBEXECDIR_CHAWAN)/cgi-bin
 	install -m755 "$(OUTDIR_CGI_BIN)/http" $(LIBEXECDIR_CHAWAN)/cgi-bin
@@ -205,6 +219,7 @@ install:
 	install -m755 "$(OUTDIR_LIBEXEC)/gmi2html" $(LIBEXECDIR_CHAWAN)
 	install -m755 "$(OUTDIR_CGI_BIN)/gmifetch" $(LIBEXECDIR_CHAWAN)/cgi-bin
 	install -m755 "$(OUTDIR_CGI_BIN)/cha-finger" $(LIBEXECDIR_CHAWAN)/cgi-bin
+	install -m755 "$(OUTDIR_CGI_BIN)/man" $(LIBEXECDIR_CHAWAN)/cgi-bin
 	install -m755 "$(OUTDIR_CGI_BIN)/spartan" $(LIBEXECDIR_CHAWAN)/cgi-bin
 	install -m755 "$(OUTDIR_LIBEXEC)/urldec" $(LIBEXECDIR_CHAWAN)/urldec
 	install -m755 "$(OUTDIR_LIBEXEC)/urlenc" $(LIBEXECDIR_CHAWAN)/urlenc
@@ -218,19 +233,23 @@ install:
 	install -m644 "$(OBJDIR)/man/cha-urimethodmap.5" "$(DESTDIR)$(MANPREFIX5)"; \
 	install -m644 "$(OBJDIR)/man/cha-protocols.5" "$(DESTDIR)$(MANPREFIX5)"; \
 	install -m644 "$(OBJDIR)/man/cha.1" "$(DESTDIR)$(MANPREFIX1)"; \
+	install -m644 "$(OBJDIR)/man/mancha.1" "$(DESTDIR)$(MANPREFIX1)"; \
 	fi
 
 .PHONY: uninstall
 uninstall:
 	rm -f "$(DESTDIR)$(PREFIX)/bin/cha"
+	rm -f "$(DESTDIR)$(PREFIX)/bin/mancha"
 	@# intentionally not quoted
 	rm -f $(LIBEXECDIR_CHAWAN)/cgi-bin/http
 	rm -f $(LIBEXECDIR_CHAWAN)/cgi-bin/about
 	rm -f $(LIBEXECDIR_CHAWAN)/cgi-bin/data
+	rm -f $(LIBEXECDIR_CHAWAN)/cgi-bin/file
 	rm -f $(LIBEXECDIR_CHAWAN)/cgi-bin/ftp
 	rm -f $(LIBEXECDIR_CHAWAN)/cgi-bin/gopher
 	rm -f $(LIBEXECDIR_CHAWAN)/cgi-bin/gmifetch
 	rm -f $(LIBEXECDIR_CHAWAN)/cgi-bin/cha-finger
+	rm -f $(LIBEXECDIR_CHAWAN)/cgi-bin/man
 	rm -f $(LIBEXECDIR_CHAWAN)/cgi-bin/spartan
 	rmdir $(LIBEXECDIR_CHAWAN)/cgi-bin || true
 	rm -f $(LIBEXECDIR_CHAWAN)/gopher2html
@@ -245,6 +264,7 @@ uninstall:
 	rm -f "$(DESTDIR)$(MANPREFIX5)/cha-urimethodmap.5"
 	rm -f "$(DESTDIR)$(MANPREFIX5)/cha-cha-protocols.5"
 	rm -f "$(DESTDIR)$(MANPREFIX1)/cha.1"
+	rm -f "$(DESTDIR)$(MANPREFIX1)/mancha.1"
 
 .PHONY: submodule
 submodule:
diff --git a/README.md b/README.md
index bd746c48..696ed4ec 100644
--- a/README.md
+++ b/README.md
@@ -32,13 +32,15 @@ supported yet.)
 5. Run `make`. (By default, this will build in release mode; for development,
    use `make TARGET=debug`. For details, see [doc/build.md](doc/build.md).)
 6. If you want manpages, run `make manpage`.
-7. Finally, install using `make install` (e.g. `sudo make install`)
+7. Finally, install using `make install` (e.g. `sudo make install`).
+8. (Optional): install Perl so that the man page viewer (`mancha`) works too.
 
 Then, try:
 
 ```bash
 $ cha -V # open in visual mode for a list of default keybindings
 $ cha example.org # open your favorite website directly from the shell
+$ mancha cha # read the cha(1) man page using `mancha' (requires Perl)
 ```
 
 ## Features
@@ -63,6 +65,7 @@ Currently implemented features are:
 * Supports several protocols: HTTP(S), FTP, Gopher, Gemini, Finger
 * Can load user-defined protocols/file formats using [local CGI](doc/localcgi.md),
   [urimethodmap](doc/urimethodmap.md) and [mailcap](doc/mailcap.md)
+* Man page viewer (based on w3mman)
 
 ...with a lot more [planned](todo).
 
diff --git a/adapter/protocol/man b/adapter/protocol/man
new file mode 100755
index 00000000..7008a95b
--- /dev/null
+++ b/adapter/protocol/man
@@ -0,0 +1,241 @@
+#!/usr/bin/perl
+#
+# From w3m.
+#
+# Note that this script has licensing terms different from those of Chawan.
+# See /res/license.html#w3m for details.
+#
+# Usage: install perl, then look up man pages using:
+#
+# $ cha man:cha # view in any manual (man cha)
+# $ cha 'man:cha(1)' # view in a specific manual (man -s 1 cha)
+# $ cha man-k:cha # search in any manual (man -k cha)
+# $ cha 'man-k:cha(1)' # search in a specific manual (man -k cha -s 1)
+#
+# You may also use the `mancha` wrapper.
+
+$MAN = $ENV{'MANCHA_MAN'} || '/usr/bin/man';
+$QUERY = $ENV{'QUERY_STRING'} || $ARGV[0];
+$SCRIPT_NAME = $ENV{'SCRIPT_NAME'} || $0;
+$CGI = "man:";
+$CGI2 = "file:";
+# $CGI2 = "file:///\$LIB/hlink.cgi?";
+$SQUEEZE = 1;
+$ENV{'PAGER'} = 'cat';
+
+if ($QUERY =~ /^man-k:/) {
+  $QUERY =~ s/^man-k://;
+  $query{"keyword"} = &form_decode($QUERY);
+  if ($query{"keyword"} =~ s/(.*)\((\w+)\)$//) {
+    $query{"keyword"} = $1;
+    $query{"section"} = $2;
+  }
+} elsif ($QUERY =~ /^man-l:/) {
+  $QUERY =~ s/^man-l://;
+  $query{"local"} = &form_decode($QUERY);
+} else {
+  $QUERY =~ s/^man://;
+  $query{"man"} = &form_decode($QUERY);
+}
+
+if ($query{"local"}) {
+  $file = $query{"local"};
+  if (! ($file =~ /^\//)) {
+    $file = $query{"pwd"} . '/' . $file;
+  }
+  open(F, "GROFF_NO_SGR=1 MAN_KEEP_FORMATTING=1 $MAN $file 2> /dev/null |");
+} else {
+  $man = $query{"man"};
+  if ($man =~ s/\((\w+)\)$//) {
+    $section = $1;
+    $man_section = "$man($1)";
+  } elsif ($query{"section"}) {
+    $section = $query{"section"};
+    $man_section = "$man($section)";
+  } else {
+    $section = "";
+    $man_section = "$man";
+  }
+
+  $section =~ s:([^-\w\200-\377.,])::g;
+  $man =~ s:([^-\w\200-\377.,])::g;
+  open(F, "GROFF_NO_SGR=1 MAN_KEEP_FORMATTING=1 $MAN $section $man 2> /dev/null |");
+}
+$ok = 0;
+undef $header;
+$blank = -1;
+$cmd = "";
+$prev = "";
+while(<F>) {
+  if (! defined($header)) {
+    /^\s*$/ && next;
+    $header = $_;
+    $space = $header;
+    chop $space;
+    $space =~ s/\S.*//;
+  } elsif ($_ eq $header) {		# delete header
+    $blank = -1;
+    next;
+  } elsif (!/\010/ && /^$space[\w\200-\377].*\s\S/o) {	# delete footer
+    $blank = -1;
+    next;
+  }
+  if ($SQUEEZE) {
+    if (/^\s*$/) {
+      $blank || $blank++;
+      next;
+    } elsif ($blank) {
+      $blank > 0 && print "\n";
+      $blank = 0;
+    }
+  }
+
+  s/\&/\&amp;/g;
+  s/\</\&lt;/g;
+  s/\>/\&gt;/g;
+  # non ASCII UTF-8 codepoint
+  my $utf8="[\300-\337][\200-\277]|[\340-\357][\200-\277]{2}|[\360-\367][\200-\277]{3}|[\370-\373][\200-\277]{4}|[\374\375][\200-\277]{5}";
+
+  s@($utf8)(\010\1)+@<b>$1</b>@g;
+  s@(\&\w+;|.)(\010\1)+@<b>$1</b>@g;
+  s@_\010((\<b\>)?($utf8)(\</b\>)?)@<u>$1</u>@g;
+  s@_\010((\<b\>)?(\&\w+\;|.)(\</b\>)?)@<u>$1</u>@g;
+  s@((\<b\>)?($utf8)(\</b\>)?)\010_@<u>$1</u>@g;
+  s@((\<b\>)?(\&\w+\;|.)(\</b\>)?)\010_@<u>$1</u>@g;
+  s@.\010(.)@$1@g;
+
+  s@\</b\>\</u\>\<b\>_\</b\>\<u\>\<b\>@_@g;
+  s@\</u\>\<b\>_\</b\>\<u\>@_@g;
+  s@\</u\>\<u\>@@g;
+  s@\</b\>\<b\>@@g;
+
+  if (! $ok) {
+    /^No/ && last;
+    print <<EOF;
+Content-Type: text/html
+
+<html>
+<head><title>man $man_section</title></head>
+<body>
+<pre>
+EOF
+    print;
+    $ok = 1;
+    next;
+  }
+
+  s@(https?|ftp)://[\w.\-/~]+[\w/]@<a href="$&">$&</a>@g;
+  s@\b(mailto:|)(\w[\w.\-]*\@\w[\w.\-]*\.[\w.\-]*\w)@<a href="mailto:$2">$1$2</a>@g;
+  s@(\W)(\~?/[\w.][\w.\-/~]*)@$1 . &file_ref($2)@ge;
+  s@(include(<\/?[bu]\>|\s)*\&lt;)([\w.\-/]+)@$1 . &include_ref($3)@ge;
+  if ($prev && m@^\s*(\<[bu]\>)*(\w[\w.\-]*)(\</[bu]\>)*(\([\dm]\w*\))@) {
+    $cmd .= "$2$4";
+    $prev =~ s@(\w[\w.\-]*-)((\</[bu]\>)*\s*)$@<a href="$CGI$cmd">$1</a>$2@;
+    print $prev;
+    $prev = '';
+    s@^(\s*(\<[bu]\>)*)(\w[\w.\-]*)@@;
+    print "$1<a href=\"$CGI$cmd\">$3</a>";
+  } elsif ($prev) {
+    print $prev;
+    $prev = '';
+  }
+  s@(\w[\w.\-]*)((\</[bu]\>)*)(\([\dm]\w*\))@<a href="$CGI$1$4">$1</a>$2$4@g;
+  if (m@(\w[\w.\-]*)-(\</[bu]\>)*\s*$@) {
+    $cmd = $1;
+    $prev = $_;
+    next;
+  }
+  print;
+}
+if ($prev) {
+  print $prev;
+}
+close(F);
+if (! $ok) {
+  if ($query{'local'}) {
+    print "Cha-Control: ConnectionError 4 file $file not found";
+  } else {
+    print "Cha-Control: ConnectionError 4 no manual entry for $man_section";
+  }
+  exit 1;
+}
+print <<EOF;
+</pre>
+</body>
+</html>
+EOF
+
+sub is_command {
+  local($_) = @_;
+  local($p);
+
+  (! -d && -x) || return 0;
+  if (! %PATH) {
+    for $p (split(":", $ENV{'PATH'})) {
+      $p =~ s@/+$@@;
+      $PATH{$p} = 1;
+    }
+  }
+  s@/[^/]*$@@;
+  return defined($PATH{$_});
+}
+
+sub file_ref {
+  local($_) = @_;
+
+  if (&is_command($_)) {
+    ($man = $_) =~ s@.*/@@;
+    return "<a href=\"$CGI$man\">$_</a>";
+  }
+  if (/^\~/ || -f || -d) {
+    ($file = $_) =~ s/^\~/$ENV{"HOME"}/;
+    return "<a href=\"$CGI2$file\">$_</a>";
+  }
+  return $_;
+}
+
+sub include_ref {
+  local($_) = @_;
+  local($d);
+
+  for $d (
+	"/usr/include",
+	"/usr/local/include",
+	"/usr/X11R6/include",
+	"/usr/X11/include",
+	"/usr/X/include",
+	"/usr/include/X11"
+  ) {
+    -f "$d/$_" && return "<a href=\"$CGI2$d/$_\">$_</a>";
+  }
+  return $_;
+}
+
+sub keyword_ref {
+  local($_, $s) = @_;
+  local(@a) = ();
+
+  for (split(/\s*,\s*/)) {
+    push(@a, "<a href=\"$CGI$_$s\">$_</a>");
+  }
+  return join(", ", @a) . $s;
+}
+
+sub html_quote {
+  local($_) = @_;
+  local(%QUOTE) = (
+    '<', '&lt;',
+    '>', '&gt;',
+    '&', '&amp;',
+    '"', '&quot;',
+  );
+  s/[<>&"]/$QUOTE{$&}/g;
+  return $_;
+}
+
+sub form_decode {
+  local($_) = @_;
+  s/\+/ /g;
+  s/%([\da-f][\da-f])/pack('c', hex($1))/egi;
+  return $_;
+}
diff --git a/adapter/tools/mancha.nim b/adapter/tools/mancha.nim
new file mode 100644
index 00000000..2e09b51f
--- /dev/null
+++ b/adapter/tools/mancha.nim
@@ -0,0 +1,76 @@
+import std/os
+import std/osproc
+
+proc help(i: int) =
+    let s = """
+Usage:
+mancha [-M path] [[-s] section] -k keyword
+mancha [-M path] [[-s] section] name
+mancha -l file"""
+    if i == 0:
+      stdout.write(s & '\n')
+    else:
+      stderr.write(s & '\n')
+    quit(i)
+
+var i = 1
+let n = paramCount()
+
+proc getnext(): string =
+  inc i
+  if i <= n:
+    return paramStr(i)
+  help(1)
+  ""
+
+proc main() =
+  let n = paramCount()
+  var section = ""
+  var local = ""
+  var man = ""
+  var keyword = ""
+  while i <= n:
+    let s = paramStr(i)
+    if s == "":
+      inc i
+      continue
+    let c = s[0]
+    let L = s.len
+    if c == '-':
+      if s.len != 2:
+        help(1)
+      case s[1]
+      of 'h': help(0)
+      of 'M': putEnv("MANPATH", getnext())
+      of 's': section = getnext()
+      of 'l': local = getnext()
+      of 'k': keyword = getnext()
+      else: help(1)
+    elif section == "" and (c in {'0'..'9'} or L == 1 and c in {'n', 'l', 'x'}):
+      section = s
+    elif man == "":
+      man = s
+    else:
+      help(1)
+    inc i
+  if not ((local != "") != (man != "") != (keyword != "")):
+    help(1)
+  if local != "" and section != "":
+    help(1)
+  let qsec = if section != "": '(' & section & ')' else: ""
+  let query = if local != "":
+    if local[0] == '~':
+      local = expandTilde(local)
+    elif local[0] != '/':
+      local = (getCurrentDir() / local)
+    "man-l:" & local
+  elif keyword != "":
+    "man-k:" & keyword & qsec
+  else:
+    "man:" & man & qsec
+  var cha = getEnv("MANCHA_CHA")
+  if cha == "":
+    cha = "cha"
+  quit(execCmd(cha & " " & quoteShellPosix(query)))
+
+main()
diff --git a/doc/cha.1 b/doc/cha.1
index a657d963..3bb9fc60 100644
--- a/doc/cha.1
+++ b/doc/cha.1
@@ -101,7 +101,7 @@ Start in visual mode: the page specified in \fIstart.visual-home\fR is opened.
 Interpret all following arguments as files. (e.g. if you have a file named
 \fI\-o\fR, open it as \fIcha \fB--\fR \fI-o\fR.
 
-.SH ENVIRONMENT VARIABLES
+.SH ENVIRONMENT
 Certain environment variables are read and used by Chawan.
 
 .TP
@@ -127,6 +127,6 @@ configuration option is not set.
 Configuration options are described in \fBcha-config\fR(5).
 
 .SH SEE ALSO
-\fBcha-mailcap\fR(5), \fBcha-mime.types\fR(5), \fBcha-config\fR(5),
+\fBmancha\fR(1), \fBcha-mailcap\fR(5), \fBcha-mime.types\fR(5), \fBcha-config\fR(5),
 .br
-\fBcha-localcgi\fR(5) \fBcha-urimethodmap\fR(5) \fBcha-protocols\fR(5)
+\fBcha-localcgi\fR(5), \fBcha-urimethodmap\fR(5), \fBcha-protocols\fR(5)
diff --git a/doc/mancha.1 b/doc/mancha.1
new file mode 100644
index 00000000..5a5dae4f
--- /dev/null
+++ b/doc/mancha.1
@@ -0,0 +1,51 @@
+.TH MANCHA 1
+
+.SH NAME
+mancha - view manual pages via cha(1)
+
+.SH SYNOPSIS
+.B mancha
+[\fB\-M \fIpath\fR] [\fIsection\fR] \fIname\fR
+.br
+.B mancha
+[\fB\-M \fIpath\fR] [\fIsection\fR] \-k \fIkeyword\fR
+.br
+.B mancha
+-l \fIfile\fR
+
+.SH DESCRIPTION
+\fBmancha\fR enables viewing man pages using the Chawan browser. It is analogous
+to the \fBw3mman\fR(1) utility.
+
+\fBmancha\fR will call \fBcha\fR(1) with the appropriate \fIman:\fR,
+\fIman-k:\fR or \fIman-l:\fR URLs. The protocol adapter then opens the man page
+and injects markup into it, e.g. man page references are converted into
+\fIman:\fR links.
+
+.SH OPTIONS
+Command line options are:
+
+.TP
+\fB\-M \fIpath\fR
+Set \fIpath\fR as the MANPATH environment variable. See \fBman\fR(1) for
+details of how this is interpreted.
+.TP
+\fB\-k \fIkeyword\fR
+Use \fIkeyword\fR for keyword-based man page search.
+.TP
+\fB\-l \fIfile\fR
+Open the specified local \fIfile\fR as a man page.
+
+.SH ENVIRONMENT
+Following environment variables are used:
+
+.TP
+.B MANCHA_CHA
+If set, the contents of the variable are used instead of \fIcha\fR.
+
+.TP
+.B MANCHA_MAN
+If set, the contents of the variable are used instead of \fI/usr/bin/man\fR.
+
+.SH SEE ALSO
+\fBman\fR(1), \fBcha\fR(1), \fBcha-localcgi\fR(5), \fBw3mman\fR(1)
diff --git a/doc/protocols.md b/doc/protocols.md
index e4982944..8a1d642f 100644
--- a/doc/protocols.md
+++ b/doc/protocols.md
@@ -18,7 +18,7 @@ this document.
 * [Gemini](#gemini)
 * [Finger](#finger)
 * [Spartan](#spartan)
-* [Local schemes: file:, about:, data:, cgi-bin:](#local-schemes-file-about-data-cgi-bin)
+* [Local schemes: file:, about:, man:, data:, cgi-bin:](#local-schemes-file-about-man-data-cgi-bin)
 * [Custom protocols](#custom-protocols)
 
 <!-- MANON -->
@@ -112,7 +112,7 @@ protocol-specific line type. This is sort of supported through a sed filter
 for gemtext outputs in the CGI script (in other words, no modification to
 gmi2html was done to support this).
 
-## Local schemes: file:, about:, data:, cgi-bin:
+## Local schemes: file:, about:, man:, data:, cgi-bin:
 
 While these are not necessarily *protocols*, they are implemented similarly
 to the protocols listed above (and thus can also be replaced, if the user
@@ -125,6 +125,12 @@ shows the directory listing like the FTP protocol does.
 writing, the following pages are available: `about:chawan`, `about:blank`
 and `about:license`.
 
+`man:`, `man-k:` and `man-l:` are wrappers around the commands `man`, `man -k`
+and `man -l`. These look up man pages using `/usr/bin/man` and turn on-page
+references into links. A wrapper command `mancha` also exists; this has an
+interface similar to `man`. Note that Perl is required for these protocols
+to work.
+
 `data:` decodes a data URL as defined in RFC 2397.
 
 Finally, `cgi-bin:` executes a local CGI script. This scheme is used for
diff --git a/res/license.html b/res/license.html
index 93a830c2..fc62d77f 100644
--- a/res/license.html
+++ b/res/license.html
@@ -4,6 +4,7 @@
 <TITLE>Licensing</TITLE>
 </HEAD>
 <BODY>
+<P>
 Chawan itself is dedicated to the public domain. However, it contains and
 depends on projects with different licensing terms.
 <P>
@@ -22,6 +23,7 @@ Table of contents:
 <LI><A HREF="#chawan">Chawan</A>
 <LI><A HREF="#quickjs">QuickJS</A>
 <LI><A HREF="#punycode">Punycode library</A>
+<LI><A HREF="#w3mman">w3mman</A>
 </UL>
 <H2 id=chawan>Chawan</H2>
 <PRE>
@@ -87,7 +89,7 @@ THE SOFTWARE.
 </PRE>
 
 <H2 id=punycode>Punycode library</H2>
-We also vendor the punycode library, which is no longer included in the Nim
+We vendor the punycode library, which is no longer included in the Nim
 standard library. This library is distributed under the following terms:
 <PRE>
 MIT License
@@ -112,5 +114,23 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
 </PRE>
+
+<H2 id=w3mman>w3mman</H2>
+Chawan uses a customized version of w3mman2html.cgi from w3m for viewing man
+pages. This script distributed under the following terms:
+<PRE>
+(C) Copyright 1994-2002 by Akinori Ito
+(C) Copyright 2002-2011 by Akinori Ito, Hironori Sakamoto, Fumitoshi Ukai
+
+Use, modification and redistribution of this software is hereby granted,
+provided that this entire copyright notice is included on any copies of
+this software and applications and derivations thereof.
+
+This software is provided on an "as is" basis, without warranty of any
+kind, either expressed or implied, as to any matter including, but not
+limited to warranty of fitness of purpose, or merchantability, or
+results obtained from use of this software.
+</PRE>
+
 </BODY>
 </HTML>
diff --git a/res/urimethodmap b/res/urimethodmap
index 47de1364..ae82d648 100644
--- a/res/urimethodmap
+++ b/res/urimethodmap
@@ -13,3 +13,6 @@ ftps:		cgi-bin:ftps
 gopher:		cgi-bin:gopher
 gophers:	cgi-bin:gophers
 spartan:	cgi-bin:spartan
+man:		cgi-bin:man?%s
+man-k:		cgi-bin:man?%s
+man-l:		cgi-bin:man?%s
diff --git a/todo b/todo
index 8f119aca..68776e04 100644
--- a/todo
+++ b/todo
@@ -88,6 +88,9 @@ images:
 - more formats (apng, gif: write own decoders, jpeg: use libjpeg, webp: ?)
 - incremental decoding (maybe implement streams first?)
 - separate image decoder process? or just run on a different thread?
+man:
+- detect man directory automatically
+- eventually rewrite in Nim
 etc:
 - important: replace fastRuneAt with qjs libunicode (fastRuneAt has no error handling...)
 - tests (including aforementioned fuzzer)