#!/bin/sh -e
# Generated by patch2vi from unified diff
# Available separators: 0003-0010 0013-0014 0016-0037 0177-0377

# Pass any argument to use patch(1) instead of nextvi ex commands
if [ -n "$1" ]; then
    sed '1,/^=== PATCH2VI PATCH ===$/d' "$0" | patch -p1 --merge=diff3
    exit $?
fi

VI=${VI:-vi}
if ! $VI -? 2>&1 | grep -q 'Nextvi'; then
    echo "Error: $VI is not nextvi" >&2
    echo "Set VI environment variable to point to nextvi binary" >&2
    exit 1
fi

SEP="$(printf '\001')"
ESC="$(printf '\002')"
# Command that handles readability line breaks
LB="0?"
# Phase 1 (search/mark): errors disabled by default,
# DBG1=1 enables error reporting, QF1=1 quits on failure
# OK1: with DBG1=1 also report fallback anchor successes
[ "$DBG1" = "1" ] && OK1= || OK1="0?"
[ "$DBG1" = "1" ] && DBG1= || DBG1="0?"
[ "$QF1" = "1" ] && QF1="${ESC}${SEP}vis 2${ESC}${SEP}q!1" || QF1=
# Phase 2 (edits): DBG2=1 disables errors, QF2=1 ignores them
# OK2: with DBG2= also report fallback substitute successes
[ "$DBG2" = "1" ] && OK2="0?" || OK2=
[ "$DBG2" = "1" ] && DBG2="0?" || DBG2=
[ "$QF2" = "1" ] && QF2= || QF2="${ESC}${SEP}vis 2${ESC}${SEP}q!1"
# Enters vi at failing code line in this script
# Designed for state inspection mid execution
[ "$INTR" = "1" ] && INTR="${ESC}${SEP}|sc|${ESC}${SEP}vis 2:fr 0:e $0:83reg %@47:%f> %@112:&Q:b0:|sc! ${ESC}${ESC}${ESC}${SEP}|:vis 3${ESC}${SEP}q1" || INTR=

# Patch: conf.c ex.c jsmn.h lbuf.c lsp.c term.c vi.c vi.h
# Body too large for EXINIT/argv: stage it in a file
( : > /tmp/p2vi.$$ ) 2>/dev/null && P2VIF=/tmp/p2vi.$$ || P2VIF=./p2vi.$$
trap 'rm -f "$P2VIF"' EXIT
printf '%s\n' "|sc! ${ESC}${SEP}|:vis 3${SEP}fr 98${SEP}b0${SEP}%ya 98${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f> char bar_ft\\[] = \"/-\";	/\\* status bar \\(is never '\\\\n' terminated\\) \\*/
char fuzz_ft\\[] = \"/f\";	/\\* fuzzy search prompt \\(is never '\\\\n' terminated\\) \\*/
char msg_ft\\[] = \"/>\";	/\\* ex message \\(is never '\\\\n' terminated\\) \\*/

struct filetype fts\\[] = \\{
	\\{FT\\(c\\), \"\\\\\\\\\\.\\(c\\|h\\|cpp\\|hpp\\|cc\\|cs\\)\\\$\"},			/\\* C \\*/${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 1${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f> char bar_ft\\[] = \"/-\";	/\\* status bar \\(is never '\\\\n' terminated\\) \\*/
char fuzz_ft\\[] = \"/f\";	/\\* fuzzy search prompt \\(is never '\\\\n' terminated\\) \\*/
char msg_ft\\[] = \"/>\";	/\\* ex message \\(is never '\\\\n' terminated\\) \\*/${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:20:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f> char bar_ft\\[] = \"/-\";	/\\* status bar \\(is never '\\\\n' terminated\\) \\*/.*?
char fuzz_ft\\[] = \"/f\";	/\\* fuzzy search prompt \\(is never '\\\\n' terminated\\) \\*/.*?
(char msg_ft\\[] = \"/>\";	/\\* ex message \\(is never '\\\\n' terminated\\) \\*/)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:20:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> char ac_ft\\[] = \"/ac\";	/\\* autocomplete dropdown \\*/
char ex_ft\\[] = \"/ex\";	/\\* ex mode \\(is never '\\\\n' terminated\\) \\*/
char vs_ft\\[] = \"/vs\";	/\\* vi search prompt \\(is never '\\\\n' terminated\\) \\*/.*(	\\{FT\\(roff\\), \"\\\\\\\\\\.\\(ms\\|tr\\|roff\\|tmac\\|txt\\|\\[1-9]\\)\\\$\"},		/\\* troff \\*/)
	\\{FT\\(tex\\), \"\\\\\\\\\\.tex\\\$\"},					/\\* tex \\*/
	\\{FT\\(mbox\\), \"letter\\\$\\|mbox\\\$\\|mail\\\$\"},			/\\* email \\*/${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:20:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> char fm_ft\\[] = \"/fm\";	/\\* file manager \\*/
char n_ft\\[] = \"/#\";	/\\* numbers highlight for \\^v \\*/
char nn_ft\\[] = \"/##\";	/\\* numbers highlight for # \\*/.*(	\\{FT\\(mk\\), \"\\[Mm]akefile\\\$\\|\\\\\\\\\\.mk\\\$\"},			/\\* makefile \\*/)
	\\{FT\\(sh\\), \"\\\\\\\\\\.\\(ba\\|z\\)\\?sh\\\$\\|\\(ba\\|z\\|k\\)shrc\\\$\\|profile\\\$\"},	/\\* shell script \\*/
	\\{FT\\(py\\), \"\\\\\\\\\\.py\\\$\"},					/\\* python \\*/${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:20:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL conf.c:20${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	\\{vs_ft, NULL},
	\\{bar_ft, NULL},
	\\{fuzz_ft, NULL},
	\\{msg_ft, NULL}
};
const int ftslen = LEN\\(fts\\);

${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+3m 2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	\\{msg_ft, NULL}
};
const int ftslen = LEN\\(fts\\);

${ESC}${SEP}2??${ESC}${SEP}${LB}
${ESC}${SEP}2??m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:44:a2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	\\{vs_ft, NULL},
	\\{bar_ft, NULL},
	\\{fuzz_ft, NULL},${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+3m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:44:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP};0${ESC}${SEP}fr${ESC}${SEP}.,\$f+ ^	\\{msg_ft, NULL}\$${ESC}${SEP}4??${ESC}${SEP}${LB}
${ESC}${SEP}4??m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:44:a4${ESC}${ESC}${ESC}${SEP}fr 98${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}fr 98${ESC}${SEP}${LB}
${ESC}${SEP}%f+ };
const int ftslen = LEN\\(fts\\);

${ESC}${SEP}5??${ESC}${SEP}${LB}
${ESC}${SEP}5??-1m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:44:a5${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ ...s.f....ULL..
	.......,......,
	...z....,....L}.
	..............
}.
..n.t......t.... . .....t..;

${ESC}${SEP}6??${ESC}${SEP}${LB}
${ESC}${SEP}6??+3m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:44:a6${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 	\\{vs_ft, NULL},.*?
	\\{bar_ft, NULL},.*?
	\\{fuzz_ft, NULL},.*?
(	\\{msg_ft, NULL})${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:44:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	\\{nn_ft, NULL},
	\\{ac_ft, NULL},
	\\{ex_ft, NULL},.*(#define NA	0	/\\* no attribute \\*/)
#define RE	1	/\\* red \\*/
#define GR	2	/\\* green \\*/${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:44:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	\\{_ft, NULL},
	\\{fm_ft, NULL},
	\\{n_ft, NULL},.*(#define YE	3	/\\* yellow \\*/)
#define BL	4	/\\* blue \\*/
#define MA	5	/\\* magenta \\*/${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:44:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;2;3;4;5;6;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL conf.c:44${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ \\(\\?:\\(\\[,;]#\\?\\)\\[ \\\\t]\\*\\(\\(\\?:\\\\\\\\\\|\\(\\?:\\[\\^\\|\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*\\\\\\\\\\|\\?\\[ \\\\t]\\*\\)\\*\\(\\?:\\(\\?:<\\(\\?:\\[\\^<\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*<\\?\\|>\\(\\?:\\[\\^>\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*>\\?\\)\\|\\\\
\\(\\?:'\\[0-9]\\+\\)\\|\\(\\[\\.\\\$]\\|\\[0-9 \\\\t]\\*\\)\\?\\)\\)\\(\\?:\\(\\[-\\*-\\+/%]\\)\\[ \\\\t]\\*\\(\\[0-9]\\+\\)\\[ \\\\t]\\*\\)\\*\\(\\?:\\[ \\\\t]\\*\\\\\\\\\\|\\(\\?:\\[\\^\\|\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*\\\\\\\\\\|\\?\\)\\*\\[ \\\\t]\\*\\)\\*\\)\\\\
\\(\\(pac\\|pr\\|ai\\|ish\\|err\\|fr\\|ic\\|grp\\|mpt\\|rr\\|shape\\|seq\\|ts\\|td\\|order\\|hl\\[lwpr]\\?\\|left\\|lim\\|led\\|vis\\)\\\\
\\|\\[@&!dmj]\\|=\\\\\\\\\\?\\{0,1}\\|\\\\\\\\\\?\\{1,2}\\[\\?!]\\?\\|b\\[psx]\\?\\|p\\[uh]\\?\\|ac\\|e\\[f!]\\?!\\?\\|f\\[-\\+><tdp]\\?\\|inc\\|i\\|sc!\\?\\|\\\\
\\(\\?:g!\\?\\|s\\)\\[ \\\\t]\\?\\(\\.\\)\\?\\|q!\\?\\|reg\\?\\\\\\\\\\+\\?\\|rd\\?\\|w\\(\\?:q!\\|\\[q!]\\)\\?\\|u\\[czbd]\\|x!\\?\\|ya\\[!\\+]\\?\\|cm!\\?\\|cd\\?\\)\\?\",
		A\\(BL1 \\| SYN_BD, RE, RE, RE, RE, WH1, MA1, RE, RE, WH1, RE, GR1, CY1, MA1\\)},
	\\{ex_ft, \"\\\\\\\\\\\\\\\\\\(\\.\\)\", A\\(AY1 \\| SYN_BD, YE\\)},${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+3m 3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ \\|\\[@&!dmj]\\|=\\\\\\\\\\?\\{0,1}\\|\\\\\\\\\\?\\{1,2}\\[\\?!]\\?\\|b\\[psx]\\?\\|p\\[uh]\\?\\|ac\\|e\\[f!]\\?!\\?\\|f\\[-\\+><tdp]\\?\\|inc\\|i\\|sc!\\?\\|\\\\
\\(\\?:g!\\?\\|s\\)\\[ \\\\t]\\?\\(\\.\\)\\?\\|q!\\?\\|reg\\?\\\\\\\\\\+\\?\\|rd\\?\\|w\\(\\?:q!\\|\\[q!]\\)\\?\\|u\\[czbd]\\|x!\\?\\|ya\\[!\\+]\\?\\|cm!\\?\\|cd\\?\\)\\?\",
		A\\(BL1 \\| SYN_BD, RE, RE, RE, RE, WH1, MA1, RE, RE, WH1, RE, GR1, CY1, MA1\\)},
	\\{ex_ft, \"\\\\\\\\\\\\\\\\\\(\\.\\)\", A\\(AY1 \\| SYN_BD, YE\\)},${ESC}${SEP}2??${ESC}${SEP}${LB}
${ESC}${SEP}2??m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:298:a2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ \\(\\?:\\(\\[,;]#\\?\\)\\[ \\\\t]\\*\\(\\(\\?:\\\\\\\\\\|\\(\\?:\\[\\^\\|\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*\\\\\\\\\\|\\?\\[ \\\\t]\\*\\)\\*\\(\\?:\\(\\?:<\\(\\?:\\[\\^<\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*<\\?\\|>\\(\\?:\\[\\^>\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*>\\?\\)\\|\\\\
\\(\\?:'\\[0-9]\\+\\)\\|\\(\\[\\.\\\$]\\|\\[0-9 \\\\t]\\*\\)\\?\\)\\)\\(\\?:\\(\\[-\\*-\\+/%]\\)\\[ \\\\t]\\*\\(\\[0-9]\\+\\)\\[ \\\\t]\\*\\)\\*\\(\\?:\\[ \\\\t]\\*\\\\\\\\\\|\\(\\?:\\[\\^\\|\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*\\\\\\\\\\|\\?\\)\\*\\[ \\\\t]\\*\\)\\*\\)\\\\
\\(\\(pac\\|pr\\|ai\\|ish\\|err\\|fr\\|ic\\|grp\\|mpt\\|rr\\|shape\\|seq\\|ts\\|td\\|order\\|hl\\[lwpr]\\?\\|left\\|lim\\|led\\|vis\\)\\\\${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+3m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:298:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP};0${ESC}${SEP}fr${ESC}${SEP}.,\$f+ ^\\|\\[@&!dmj]\\|=\\\\\\\\\\?\\{0,1}\\|\\\\\\\\\\?\\{1,2}\\[\\?!]\\?\\|b\\[psx]\\?\\|p\\[uh]\\?\\|ac\\|e\\[f!]\\?!\\?\\|f\\[-\\+><tdp]\\?\\|inc\\|i\\|sc!\\?\\|\\\\\$${ESC}${SEP}4??${ESC}${SEP}${LB}
${ESC}${SEP}4??m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:298:a4${ESC}${ESC}${ESC}${SEP}fr 98${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}fr 98${ESC}${SEP}${LB}
${ESC}${SEP}%f+ \\(\\?:g!\\?\\|s\\)\\[ \\\\t]\\?\\(\\.\\)\\?\\|q!\\?\\|reg\\?\\\\\\\\\\+\\?\\|rd\\?\\|w\\(\\?:q!\\|\\[q!]\\)\\?\\|u\\[czbd]\\|x!\\?\\|ya\\[!\\+]\\?\\|cm!\\?\\|cd\\?\\)\\?\",
		A\\(BL1 \\| SYN_BD, RE, RE, RE, RE, WH1, MA1, RE, RE, WH1, RE, GR1, CY1, MA1\\)},
	\\{ex_ft, \"\\\\\\\\\\\\\\\\\\(\\.\\)\", A\\(AY1 \\| SYN_BD, YE\\)},${ESC}${SEP}5??${ESC}${SEP}${LB}
${ESC}${SEP}5??-1m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:298:a5${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ .\\?...............................\\\\].......\\)\\*..\\|.\\[..t...\\*.\\?..\\?:........\\\\.......\\\\.............>\\\\.\\\\....\\\\...\\?.\\*.....
......-..........\\|......t............\\*../........................\\*.....\\\\..\\*\\\\\\\\...........]\\|....\\.\\?...\\\\\\|..\\*...t]\\*.\\*..
.....\\|p.....is.....\\|........p............e.........d......\\|..\\[l.pr...l.....im......i...
.\\[..!...........,1.......,.........\\[..x]..........c....!]\\?......\\+...d...\\|...\\|i.......
\\(\\?..!\\?..\\).................g.\\\\.\\+\\?..d.\\|w...........\\?.....b.....\\?...\\[...\\?........\\?....
	.A\\(......S.._.D..RE, ... R.,..., .....M.........E. .H..............1......},
	....f.......\\\\\\(.\\).....A...\\|........ ....,${ESC}${SEP}6??${ESC}${SEP}${LB}
${ESC}${SEP}6??+3m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:298:a6${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ \\(\\?:\\(\\[,;]#\\?\\)\\[ \\\\t]\\*\\(\\(\\?:\\\\\\\\\\|\\(\\?:\\[\\^\\|\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*\\\\\\\\\\|\\?\\[ \\\\t]\\*\\)\\*\\(\\?:\\(\\?:<\\(\\?:\\[\\^<\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*<\\?\\|>\\(\\?:\\[\\^>\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*>\\?\\)\\|\\\\.*?
\\(\\?:'\\[0-9]\\+\\)\\|\\(\\[\\.\\\$]\\|\\[0-9 \\\\t]\\*\\)\\?\\)\\)\\(\\?:\\(\\[-\\*-\\+/%]\\)\\[ \\\\t]\\*\\(\\[0-9]\\+\\)\\[ \\\\t]\\*\\)\\*\\(\\?:\\[ \\\\t]\\*\\\\\\\\\\|\\(\\?:\\[\\^\\|\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*\\\\\\\\\\|\\?\\)\\*\\[ \\\\t]\\*\\)\\*\\)\\\\.*?
\\(\\(pac\\|pr\\|ai\\|ish\\|err\\|fr\\|ic\\|grp\\|mpt\\|rr\\|shape\\|seq\\|ts\\|td\\|order\\|hl\\[lwpr]\\?\\|left\\|lim\\|led\\|vis\\)\\\\.*?
(\\|\\[@&!dmj]\\|=\\\\\\\\\\?\\{0,1}\\|\\\\\\\\\\?\\{1,2}\\[\\?!]\\?\\|b\\[psx]\\?\\|p\\[uh]\\?\\|ac\\|e\\[f!]\\?!\\?\\|f\\[-\\+><tdp]\\?\\|inc\\|i\\|sc!\\?\\|\\\\)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:298:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	\\{ex_ft, \"\\.\\+\", A\\(AY1 \\| SYN_BD\\), 1},
	\\{ex_ft, \":\\[ \\\\t]\\*\\(\\(\\(\\(\\?:\\\\\\\\\\|\\(\\?:\\[\\^\\|\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*\\\\\\\\\\|\\?\\[ \\\\t]\\*\\)\\*\\(\\?:\\(\\?:<\\(\\?:\\[\\^<\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*<\\?\\|>\\(\\?:\\[\\^>\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*>\\?\\)\\|\\\\
\\(\\?:'\\[0-9]\\+\\)\\|\\(\\[\\.%\\\$]\\|\\[0-9 \\\\t]\\*\\)\\?\\)\\)\\(\\?:\\(\\[-\\*-\\+/%]\\)\\[ \\\\t]\\*\\[0-9]\\+\\[ \\\\t]\\*\\)\\*\\(\\?:\\[ \\\\t]\\*\\\\\\\\\\|\\(\\?:\\[\\^\\|\\\\\\\\\\\\\\\\]\\|\\\\\\\\\\\\\\\\\\.\\?\\)\\*\\\\\\\\\\|\\?\\[ \\\\t]\\*\\)\\*\\)\\[ \\\\t]\\*\\\\.*(	\\{vs_ft, \"\\.\\+\", A\\(AY1 \\| SYN_BD\\), 1},)
	\\{vs_ft, \"\\(\\^\\[\\?/]\\)\\|\\(\\\\\\\\\\\\\\\\\\[<>]\\|\\\\\\\\\\(\\\\\\\\\\?\\[:=!<>#]\\|\\\\\\\\\\[\\\\\\\\\\^\\?\\(\\(\\?:\\\\\\\\\\\\\\\\\\.\\?\\|\\[\\^\\\\\\\\]]\\)\\*\\)]\\|\\\\\\\\\\[\\|\\[\\.\\^\\\$\\\\\\\\\\(\\)\\*\\+\\|\\?]\\|\\\\\\\\\\{\\(\\[0-9]\\*\\(,\\)\\?\\[0-9]\\*\\)\\?}\\?\\)\\|\\\\\\\\\\\\\\\\\\(\\.\\)\",
		A\\(SYN_BD, BL1, WH1, GR1, RE1, WH1, YE\\)},${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-6m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:298:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	\\{ac_ft, \"\\[\\^ \\\\t-/:-@\\[-\\^\\{-~]\\+\\(\\?:\\(\\\\n\\\$\\)\\|\\\\n\\)\\|\\\\n\\|\\(\\[\\^\\\\n]\\+\\(\\\\n\\)\\)\",
		A\\(NA, SYN_BGMK\\(RE1\\), SYN_BGMK\\(AY1\\), SYN_BGMK\\(AY\\)\\)},
	\\{ac_ft, \"\\[\\^ \\\\t-/:-@\\[-\\^\\{-~]\\+\\\$\\|\\(\\.\\+\\\$\\)\", A\\(NA, SYN_BGMK\\(AY1\\)\\)},.*(	\\{vs_ft, \"\\(\\\\\\\\\\\\\\\\\\)\\(\\.\\)\\(\\?:\\(-\\)\\(\\?:\\(\\\\\\\\\\\\\\\\\\(\\.\\)\\)\\|\\[\\^\\\\\\\\\\\\\\\\\\\\\\\\]]\\)\\)\\?\\|\\[\\^\\\\\\\\\\\\\\\\\\[\\^]\\(-\\)\\(\\?:\\(\\\\\\\\\\\\\\\\\\(\\.\\)\\)\\|\\.\\)\\|\\\\)
\\[\\^\\\\\\\\\\\\\\\\\\[\\^]\\\\\\\\\\^\\(-\\)\\(\\?:\\(\\\\\\\\\\\\\\\\\\(\\.\\)\\)\\|\\[\\^\\\\\\\\\\\\\\\\]\\)\\|\\\\\\\\\\^\\(-\\)\\[\\^\\\\\\\\\\\\\\\\]\",
		A\\(GR1 \\| SYN_BD \\| SYN_ATT, 1, GR1, AY1, YE, WH1, AY1, YE, WH1, AY1, YE, WH1, AY1, YE, WH1\\), 2},${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-9m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:298:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;2;3;4;5;6;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL conf.c:298${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	\\{fuzz_ft, NULL, A\\(RE1 \\| SYN_BD\\), 1, 1},

	\\{msg_ft, \"\\.\\+\", A\\(AY1 \\| SYN_BD\\)},
};
const int hlslen = LEN\\(hls\\);

${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 4${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	\\{fuzz_ft, NULL, A\\(RE1 \\| SYN_BD\\), 1, 1},

	\\{msg_ft, \"\\.\\+\", A\\(AY1 \\| SYN_BD\\)},${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:321:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 	\\{fuzz_ft, NULL, A\\(RE1 \\| SYN_BD\\), 1, 1},.*?
.*?
(	\\{msg_ft, \"\\.\\+\", A\\(AY1 \\| SYN_BD\\)},)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:321:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	\\{bar_ft, \"\\^\\(\\\\\"\\.\\*\\\\\"\\)\\.\\* \\(\\[0-9]\\{1,3}%\\) \\(L\\[0-9]\\+\\) \\(C\\[0-9]\\+\\) \\(B-\\?\\[0-9]\\+\\)\\?\\.\\*\\\$\",
		A\\(AY1 \\| SYN_BD, BL, RE1, BL, YE1, GR\\)},
	\\{bar_ft, \"\\^\\.\\*\\\$\", A\\(AY1 \\| SYN_BD\\)},.*(/\\* right-to-left characters \\*/)
#define CR2L		\"ء-يپچژکگی‌-‍؛،»«؟ً-ْٔ\"
/\\* neutral characters \\*/${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-7m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:321:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	\\{bar_ft, \"\\^\\(\\\\\"\\.\\*\\\\\"\\)\\.\\*\\(\\\\\\\\\\[\\[wrf]\\\\\\\\]\\)\\.\\*\\\$\", A\\(AY1 \\| SYN_BD, BL, RE\\)},
	\\{bar_ft, \"\\^<\\(\\.\\+\\)> \\(\\?:\\[\\^ ]\\+ \\)\\*\\(\\[0-9]\\+L\\) \\(\\[0-9]\\+W\\) \\(S\\[0-9]\\+\\) \\(O\\[0-9]\\+\\) \\(C\\[0-9]\\+\\)\\\$\",
		A\\(AY1 \\| SYN_BD, RE1, BL, YE, MA, CY1, YE1\\)},.*(struct dircontext dctxs\\[] = \\{)
	\\{\"\\^\\[\" CR2L \"]\", -1},
	\\{\"\\^\\[a-zA-Z_0-9]\", \\+1},${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-12m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK conf.c:321:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL conf.c:321${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}${LB}
${SEP}'1i char lsp_ft[] = \"/lsp\";	/* lsp diagnostic virtual text */
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL conf.c:20:m1${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'2c 	{msg_ft, NULL},
	{lsp_ft, NULL}
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL conf.c:44:m2${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}?'3s/\\?\\|\\\\\\\\/?|lsp|\\\\\\\\/${ESC}${SEP}1??${ESC}${SEP}1??1q${ESC}${SEP}'3s/\\|\\\\\$/|lsp|\\\\\\\\/${ESC}${SEP}2??${ESC}${SEP}2??'3${ESC}${ESC}${ESC}${SEP}${OK2}p OK conf.c:298:s2${SEP}${LB}
${SEP}1;2??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL conf.c:298:m3${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}${LB}
${SEP}'4i 
	{lsp_ft, \"^.*error.*\$\", A(RE1 | SYN_IT)},
	{lsp_ft, \"^.*warning.*\$\", A(YE1 | SYN_IT)},
	{lsp_ft, \"^.*\$\", A(BL1 | SYN_IT)},
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL conf.c:321:m4${ESC}${SEP}pr${INTR}${QF2}}${SEP}b1${SEP}%ya 98${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f> 	if \\(cd == 3 \\|\\| \\(!rd && fd >= 0\\)\\) \\{
		ex_bufpostfix\\(ex_buf, arg\\[0]\\);
		syn_setft\\(xb_ft\\);
	}
	snprintf\\(msg, sizeof\\(msg\\), \"\\\\\"%s\\\\\" %dL \\[%c]\",
			\\*xb_path \\? xb_path : \"unnamed\", lbuf_len\\(xb\\),${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 1${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f> 	if \\(cd == 3 \\|\\| \\(!rd && fd >= 0\\)\\) \\{
		ex_bufpostfix\\(ex_buf, arg\\[0]\\);
		syn_setft\\(xb_ft\\);${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:433:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f> 	if \\(cd == 3 \\|\\| \\(!rd && fd >= 0\\)\\) \\{.*?
		ex_bufpostfix\\(ex_buf, arg\\[0]\\);.*?
(		syn_setft\\(xb_ft\\);)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:433:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		cd = 3; /\\* XXX: quick hack to indicate new lbuf \\*/
	}
	readfile\\(rd =\\).*(			fd < 0 \\|\\| rd \\? 'f' : 'r'\\);)
	if \\(!\\(xvis & 4\\)\\)
		ex_print\\(msg, bar_ft\\)${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:433:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		return \"last buffer modified\";
	} else if \\(len \\|\\| !xbufcur \\|\\| !strchr\\(cmd, '!'\\)\\) \\{
		bufs_switch\\(bufs_open\\(arg\\+cd, len\\)\\);.*(static void \\*ec_fuzz\\(char \\*loc, char \\*cmd, char \\*arg\\))
\\{
	rset \\*rs;${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-10m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:433:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL ex.c:433${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		ec_setpath\\(NULL, NULL, path\\);
	lbuf_saved\\(xb, 0\\);
	ex_buf->mtime = mtime\\(path\\);
	xquit = quit;
	return NULL;
}${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		ec_setpath\\(NULL, NULL, path\\);
	lbuf_saved\\(xb, 0\\);
	ex_buf->mtime = mtime\\(path\\);${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:803:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 		ec_setpath\\(NULL, NULL, path\\);.*?
	lbuf_saved\\(xb, 0\\);.*?
(	ex_buf->mtime = mtime\\(path\\);)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:803:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 			path, end - beg\\);
	ex_print\\(msg, bar_ft\\)
	if \\(strcmp\\(xb_path, path\\)\\).*(static void \\*ec_termexec\\(char \\*loc, char \\*cmd, char \\*arg\\))
\\{
	if \\(\\*arg && term_sbuf\\)${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-5m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:803:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	if \\(o1 < 0\\)
		return \"write failed\";
	snprintf\\(msg, sizeof\\(msg\\), \"\\\\\"%s\\\\\" %dL \\[w]\",.*(		term_exec\\(arg, strlen\\(arg\\), cmd\\[0]\\))
	return term_sbuf \\? NULL : \"unsupported command\";
}${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-8m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:803:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL ex.c:803${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	return NULL;
\\)

#undef EO
#define EO\\(opt\\) \\{#opt, eo_##opt}

${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	return NULL;
\\)

${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:1598:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		xleft = atoi\\(arg\\);
	else if \\(lbuf_get\\(xb, xrow\\)\\)
		xleft = ren_position\\(lbuf_get\\(xb, xrow\\)\\)->pos\\[MIN\\(xoff, rstate->n\\)];.*(/\\* commands & opts must be sorted longest of its kind topmost \\*/)
static struct excmd \\{
	char \\*name;${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:1598:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	if \\(\\*loc\\)
		xleft = \\(xcols / 2\\) \\* atoi\\(loc\\);
	else if \\(\\*arg\\).*(	void \\*\\(\\*ec\\)\\(char \\*loc, char \\*cmd, char \\*arg\\);)
} excmds\\[] = \\{
	\\{\"@\", ec_termexec},${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:1598:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL ex.c:1598${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	EO\\(hlp\\),
	EO\\(hlr\\),
	EO\\(hl\\),
	EO\\(left\\),
	EO\\(lim\\),
	EO\\(led\\),${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 4${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	EO\\(hlp\\),
	EO\\(hlr\\),
	EO\\(hl\\),${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:1690:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 	EO\\(hlp\\),.*?
	EO\\(hlr\\),.*?
(	EO\\(hl\\),)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:1690:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	EO\\(order\\),
	EO\\(hll\\),
	EO\\(hlw\\),.*(	EO\\(vis\\),)
	\\{\"\", ec_print}, /\\* do not remove \\*/
	\\{\"\", ec_print}, /\\* do not remove \\*/${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:1690:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	\\{\"j\", ec_join},
	EO\\(ts\\),
	EO\\(td\\),.*(/\\* parse command argument expanding % and ! \\*/)
static const char \\*ex_arg\\(const char \\*src, sbuf \\*sb, int \\*arg\\)
\\{${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-9m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK ex.c:1690:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL ex.c:1690${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}${LB}
${SEP}'1i 		if (*xb_path && xb_ft)
			lsp_open(xb_path, xb_ft);
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL ex.c:433:m1${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'2i 	if (*xb_path)
		lsp_save(xb_path);
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL ex.c:803:m2${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'3i static void *ec_lsp(char *loc, char *cmd, char *arg)
{
	char ft[32];
	int n = 0;
	if (!*arg) {
		lsp_list();
		return NULL;
	}
	while (arg[n] && arg[n] != ' ' && n < (int)sizeof(ft) - 1) {
		ft[n] = arg[n];
		n++;
	}
	ft[n] = '\\0';
	while (arg[n] == ' ')
		n++;
	if (!arg[n])
		return \"lsp: missing server command\";
	lsp_register(ft, arg + n);
	lsp_dirty = 1;
	return NULL;
}

${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL ex.c:1598:m3${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'4i 	{\"lsp\", ec_lsp},
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL ex.c:1690:m4${ESC}${SEP}pr${INTR}${QF2}}${SEP}b2${SEP}${LB}
${SEP}i /*
 * MIT License
 *
 * Copyright (c) 2010 Serge Zaitsev
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the \"Software\"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * 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.
 */
#ifndef JSMN_H
#define JSMN_H

#include <stddef.h>

#ifdef __cplusplus
extern \"C\" {
#endif

#ifdef JSMN_STATIC
#define JSMN_API static
#else
#define JSMN_API extern
#endif

/**
 * JSON type identifier. Basic types are:
 * 	o Object
 * 	o Array
 * 	o String
 * 	o Other primitive: number, boolean (true/false) or null
 */
typedef enum {
  JSMN_UNDEFINED = 0,
  JSMN_OBJECT = 1 << 0,
  JSMN_ARRAY = 1 << 1,
  JSMN_STRING = 1 << 2,
  JSMN_PRIMITIVE = 1 << 3
} jsmntype_t;

enum jsmnerr {
  /* Not enough tokens were provided */
  JSMN_ERROR_NOMEM = -1,
  /* Invalid character inside JSON string */
  JSMN_ERROR_INVAL = -2,
  /* The string is not a full JSON packet, more bytes expected */
  JSMN_ERROR_PART = -3
};

/**
 * JSON token description.
 * type		type (object, array, string etc.)
 * start	start position in JSON data string
 * end		end position in JSON data string
 */
typedef struct jsmntok {
  jsmntype_t type;
  int start;
  int end;
  int size;
#ifdef JSMN_PARENT_LINKS
  int parent;
#endif
} jsmntok_t;

/**
 * JSON parser. Contains an array of token blocks available. Also stores
 * the string being parsed now and current position in that string.
 */
typedef struct jsmn_parser {
  unsigned int pos;     /* offset in the JSON string */
  unsigned int toknext; /* next token to allocate */
  int toksuper;         /* superior token node, e.g. parent object or array */
} jsmn_parser;

/**
 * Create JSON parser over an array of tokens
 */
JSMN_API void jsmn_init(jsmn_parser *parser);

/**
 * Run JSON parser. It parses a JSON data string into and array of tokens, each
 * describing
 * a single JSON object.
 */
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
                        jsmntok_t *tokens, const unsigned int num_tokens);

#ifndef JSMN_HEADER
/**
 * Allocates a fresh unused token from the token pool.
 */
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
                                   const size_t num_tokens) {
  jsmntok_t *tok;
  if (parser->toknext >= num_tokens) {
    return NULL;
  }
  tok = &tokens[parser->toknext++];
  tok->start = tok->end = -1;
  tok->size = 0;
#ifdef JSMN_PARENT_LINKS
  tok->parent = -1;
#endif
  return tok;
}

/**
 * Fills token type and boundaries.
 */
static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
                            const int start, const int end) {
  token->type = type;
  token->start = start;
  token->end = end;
  token->size = 0;
}

/**
 * Fills next available token with JSON primitive.
 */
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
                                const size_t len, jsmntok_t *tokens,
                                const size_t num_tokens) {
  jsmntok_t *token;
  int start;

  start = parser->pos;

  for (; parser->pos < len && js[parser->pos] != '\\0'; parser->pos++) {
    switch (js[parser->pos]) {
#ifndef JSMN_STRICT
    /* In strict mode primitive must be followed by \",\" or \"}\" or \"]\" */
    case ':':
#endif
    case '\\t':
    case '\\r':
    case '\\n':
    case ' ':
    case ',':
    case ']':
    case '}':
      goto found;
    default:
                   /* to quiet a warning from gcc*/
      break;
    }
    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
      parser->pos = start;
      return JSMN_ERROR_INVAL;
    }
  }
#ifdef JSMN_STRICT
  /* In strict mode primitive must be followed by a comma/object/array */
  parser->pos = start;
  return JSMN_ERROR_PART;
#endif

found:
  if (tokens == NULL) {
    parser->pos--;
    return 0;
  }
  token = jsmn_alloc_token(parser, tokens, num_tokens);
  if (token == NULL) {
    parser->pos = start;
    return JSMN_ERROR_NOMEM;
  }
  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
#ifdef JSMN_PARENT_LINKS
  token->parent = parser->toksuper;
#endif
  parser->pos--;
  return 0;
}

/**
 * Fills next token with JSON string.
 */
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
                             const size_t len, jsmntok_t *tokens,
                             const size_t num_tokens) {
  jsmntok_t *token;

  int start = parser->pos;
  
  /* Skip starting quote */
  parser->pos++;
  
  for (; parser->pos < len && js[parser->pos] != '\\0'; parser->pos++) {
    char c = js[parser->pos];

    /* Quote: end of string */
    if (c == '\\\"') {
      if (tokens == NULL) {
        return 0;
      }
      token = jsmn_alloc_token(parser, tokens, num_tokens);
      if (token == NULL) {
        parser->pos = start;
        return JSMN_ERROR_NOMEM;
      }
      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
#ifdef JSMN_PARENT_LINKS
      token->parent = parser->toksuper;
#endif
      return 0;
    }

    /* Backslash: Quoted symbol expected */
    if (c == '\\\\' && parser->pos + 1 < len) {
      int i;
      parser->pos++;
      switch (js[parser->pos]) {
      /* Allowed escaped symbols */
      case '\\\"':
      case '/':
      case '\\\\':
      case 'b':
      case 'f':
      case 'r':
      case 'n':
      case 't':
        break;
      /* Allows escaped symbol \\uXXXX */
      case 'u':
        parser->pos++;
        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\\0';
             i++) {
          /* If it isn't a hex character we have an error */
          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
                (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
                (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
            parser->pos = start;
            return JSMN_ERROR_INVAL;
          }
          parser->pos++;
        }
        parser->pos--;
        break;
      /* Unexpected symbol */
      default:
        parser->pos = start;
        return JSMN_ERROR_INVAL;
      }
    }
  }
  parser->pos = start;
  return JSMN_ERROR_PART;
}

/**
 * Parse JSON string and fill tokens.
 */
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
                        jsmntok_t *tokens, const unsigned int num_tokens) {
  int r;
  int i;
  jsmntok_t *token;
  int count = parser->toknext;

  for (; parser->pos < len && js[parser->pos] != '\\0'; parser->pos++) {
    char c;
    jsmntype_t type;

    c = js[parser->pos];
    switch (c) {
    case '{':
    case '[':
      count++;
      if (tokens == NULL) {
        break;
      }
      token = jsmn_alloc_token(parser, tokens, num_tokens);
      if (token == NULL) {
        return JSMN_ERROR_NOMEM;
      }
      if (parser->toksuper != -1) {
        jsmntok_t *t = &tokens[parser->toksuper];
#ifdef JSMN_STRICT
        /* In strict mode an object or array can't become a key */
        if (t->type == JSMN_OBJECT) {
          return JSMN_ERROR_INVAL;
        }
#endif
        t->size++;
#ifdef JSMN_PARENT_LINKS
        token->parent = parser->toksuper;
#endif
      }
      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
      token->start = parser->pos;
      parser->toksuper = parser->toknext - 1;
      break;
    case '}':
    case ']':
      if (tokens == NULL) {
        break;
      }
      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
      if (parser->toknext < 1) {
        return JSMN_ERROR_INVAL;
      }
      token = &tokens[parser->toknext - 1];
      for (;;) {
        if (token->start != -1 && token->end == -1) {
          if (token->type != type) {
            return JSMN_ERROR_INVAL;
          }
          token->end = parser->pos + 1;
          parser->toksuper = token->parent;
          break;
        }
        if (token->parent == -1) {
          if (token->type != type || parser->toksuper == -1) {
            return JSMN_ERROR_INVAL;
          }
          break;
        }
        token = &tokens[token->parent];
      }
#else
      for (i = parser->toknext - 1; i >= 0; i--) {
        token = &tokens[i];
        if (token->start != -1 && token->end == -1) {
          if (token->type != type) {
            return JSMN_ERROR_INVAL;
          }
          parser->toksuper = -1;
          token->end = parser->pos + 1;
          break;
        }
      }
      /* Error if unmatched closing bracket */
      if (i == -1) {
        return JSMN_ERROR_INVAL;
      }
      for (; i >= 0; i--) {
        token = &tokens[i];
        if (token->start != -1 && token->end == -1) {
          parser->toksuper = i;
          break;
        }
      }
#endif
      break;
    case '\\\"':
      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
      if (r < 0) {
        return r;
      }
      count++;
      if (parser->toksuper != -1 && tokens != NULL) {
        tokens[parser->toksuper].size++;
      }
      break;
    case '\\t':
    case '\\r':
    case '\\n':
    case ' ':
      break;
    case ':':
      parser->toksuper = parser->toknext - 1;
      break;
    case ',':
      if (tokens != NULL && parser->toksuper != -1 &&
          tokens[parser->toksuper].type != JSMN_ARRAY &&
          tokens[parser->toksuper].type != JSMN_OBJECT) {
#ifdef JSMN_PARENT_LINKS
        parser->toksuper = tokens[parser->toksuper].parent;
#else
        for (i = parser->toknext - 1; i >= 0; i--) {
          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
            if (tokens[i].start != -1 && tokens[i].end == -1) {
              parser->toksuper = i;
              break;
            }
          }
        }
#endif
      }
      break;
#ifdef JSMN_STRICT
    /* In strict mode primitives are: numbers and booleans */
    case '-':
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
    case 't':
    case 'f':
    case 'n':
      /* And they must not be keys of the object */
      if (tokens != NULL && parser->toksuper != -1) {
        const jsmntok_t *t = &tokens[parser->toksuper];
        if (t->type == JSMN_OBJECT ||
            (t->type == JSMN_STRING && t->size != 0)) {
          return JSMN_ERROR_INVAL;
        }
      }
#else
    /* In non-strict mode every unquoted value is a primitive */
    default:
#endif
      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
      if (r < 0) {
        return r;
      }
      count++;
      if (parser->toksuper != -1 && tokens != NULL) {
        tokens[parser->toksuper].size++;
      }
      break;

#ifdef JSMN_STRICT
    /* Unexpected char in strict mode */
    default:
      return JSMN_ERROR_INVAL;
#endif
    }
  }

  if (tokens != NULL) {
    for (i = parser->toknext - 1; i >= 0; i--) {
      /* Unmatched opened object or array */
      if (tokens[i].start != -1 && tokens[i].end == -1) {
        return JSMN_ERROR_PART;
      }
    }
  }

  return count;
}

/**
 * Creates a new parser based over a given buffer with an array of tokens
 * available.
 */
JSMN_API void jsmn_init(jsmn_parser *parser) {
  parser->pos = 0;
  parser->toknext = 0;
  parser->toksuper = -1;
}

#endif /* JSMN_HEADER */

#ifdef __cplusplus
}
#endif

#endif /* JSMN_H */
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL jsmn.h:-1:m${ESC}${SEP}pr${INTR}${QF2}}${SEP}b3${SEP}%ya 98${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f> 			\\(lb->ln_n - pos - n_del\\) \\* sizeof\\(lb->ln\\[0]\\)\\);
	}
	lb->ln_n \\+= n_ins - n_del;
	for \\(i = 0; i < n_ins; i\\+\\+\\)
		lb->ln\\[pos \\+ i] = \\*\\(\\(char\\*\\*\\)sb->s \\+ i\\);
	for \\(i = 0; i < lb->mark_n; i\\+\\+\\) \\{	/\\* updating marks \\*/${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 1${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f> 			\\(lb->ln_n - pos - n_del\\) \\* sizeof\\(lb->ln\\[0]\\)\\);
	}
	lb->ln_n \\+= n_ins - n_del;${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK lbuf.c:129:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f> 			\\(lb->ln_n - pos - n_del\\) \\* sizeof\\(lb->ln\\[0]\\)\\);.*?
	}.*?
(	lb->ln_n \\+= n_ins - n_del;)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK lbuf.c:129:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	}
	if \\(n_ins != n_del\\) \\{
		memmove\\(lb->ln \\+ pos \\+ n_ins, lb->ln \\+ pos \\+ n_del,.*(		int \\*m = lb->mark \\+ i \\* 3, \\*lm;)
		if \\(m\\[1] >= pos \\+ n_ins && m\\[1] < pos \\+ n_del\\) \\{
			mark_set\\(&lo->mark, &lo->mark_n, m\\[0], m\\[1], m\\[2]\\);${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK lbuf.c:129:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		free\\(lb->ln\\);
		lb->ln = nln;
		lb->ln_sz = nsz;.*(			m\\[1] = n_ins \\? pos \\+ n_ins - 1 : -1;)
		} else if \\(m\\[1] >= pos \\+ n_del\\) \\{
			m\\[1] \\+= n_ins - n_del;${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK lbuf.c:129:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL lbuf.c:129${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}${LB}
${SEP}'1i 	lb->edseq++;
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL lbuf.c:129:m1${ESC}${SEP}pr${INTR}${QF2}}${SEP}b4${SEP}${LB}
${SEP}i /* lsp.c - Language Server Protocol client for nextvi */
#include \"jsmn.h\"
#include <errno.h>

#define LSP_DIAG_MAX	64
#define LSP_SRV_MAX	8
#define LSP_RBUF_MAX	65536
#define LSP_DOCS_MAX	16

typedef struct {
	int line, col, severity;
	char msg[256];
} lsp_diag;

typedef struct {
	char path[1024];
	lsp_diag *d;
	int n, cap;
} lsp_diagfile;

typedef struct {
	char path[1024];
	int version;
	int seq;		/* lbuf edseq at last sync */
} lsp_doc;

typedef struct {
	char ft[32], cmd[256];
	int pid, in_fd, out_fd;
	char rbuf[LSP_RBUF_MAX];
	int rbuf_n;
	int response_ready, pending_id;
	char *response_json;
	int initialized, next_id;
	lsp_doc docs[LSP_DOCS_MAX];
	int ndocs;
} lsp_server;

int lsp_nfds = 0;
int lsp_fds[LSP_NFDS_MAX];
int lsp_dirty = 0;		/* diagnostics changed; main loop must redraw */
int lsp_wake = 0;		/* term_read may return 0 to report lsp_dirty */

static lsp_server lsp_srvs[LSP_SRV_MAX];
static int lsp_nsrvs = 0;
static lsp_diagfile lsp_diagfiles[LSP_SRV_MAX * 4];
static int lsp_ndiagfiles = 0;

static void lsp_open_lb(const char *path, const char *ft, struct lbuf *lb);

void lsp_register(const char *ft, const char *cmd)
{
	int i;
	for (i = 0; i < lsp_nsrvs; i++) {
		if (!strcmp(lsp_srvs[i].ft, ft)) {
			snprintf(lsp_srvs[i].cmd, sizeof(lsp_srvs[i].cmd), \"%s\", cmd);
			return;
		}
	}
	if (lsp_nsrvs >= LSP_SRV_MAX)
		return;
	snprintf(lsp_srvs[lsp_nsrvs].ft, sizeof(lsp_srvs[0].ft), \"%s\", ft);
	snprintf(lsp_srvs[lsp_nsrvs].cmd, sizeof(lsp_srvs[0].cmd), \"%s\", cmd);
	lsp_srvs[lsp_nsrvs].pid = -1;
	lsp_srvs[lsp_nsrvs].in_fd = -1;
	lsp_srvs[lsp_nsrvs].out_fd = -1;
	lsp_nsrvs++;
	/* open every already-loaded buffer of this filetype */
	for (i = 0; i < xbufcur; i++)
		if (bufs[i].ft && bufs[i].path[0] && !strcmp(bufs[i].ft, ft))
			lsp_open_lb(bufs[i].path, bufs[i].ft, bufs[i].lb);
}

void lsp_list(void)
{
	int i;
	char buf[512];
	for (i = 0; i < lsp_nsrvs; i++) {
		snprintf(buf, sizeof(buf), \"lsp %.31s %.255s [%s]\",
			lsp_srvs[i].ft, lsp_srvs[i].cmd,
			lsp_srvs[i].pid > 0 ? \"running\" : \"stopped\");
		ex_print(buf, bar_ft)
	}
	if (!lsp_nsrvs)
		ex_print(\"lsp: no servers registered\", bar_ft)
}

static lsp_server *lsp_srv_for_ft(const char *ft)
{
	int i;
	for (i = 0; i < lsp_nsrvs; i++)
		if (!strcmp(lsp_srvs[i].ft, ft))
			return &lsp_srvs[i];
	return NULL;
}

static int lsp_doc_find(lsp_server *srv, const char *path)
{
	int i;
	for (i = 0; i < srv->ndocs; i++)
		if (!strcmp(srv->docs[i].path, path))
			return i;
	return -1;
}

static lsp_server *lsp_srv_for_path(const char *path)
{
	int i;
	for (i = 0; i < lsp_nsrvs; i++)
		if (lsp_srvs[i].pid > 0 && lsp_doc_find(&lsp_srvs[i], path) >= 0)
			return &lsp_srvs[i];
	return NULL;
}

static lsp_diagfile *lsp_diagfile_for_path(const char *path)
{
	int i;
	for (i = 0; i < lsp_ndiagfiles; i++)
		if (!strcmp(lsp_diagfiles[i].path, path))
			return &lsp_diagfiles[i];
	if (lsp_ndiagfiles >= LSP_SRV_MAX * 4)
		return NULL;
	snprintf(lsp_diagfiles[lsp_ndiagfiles].path,
		sizeof(lsp_diagfiles[0].path), \"%s\", path);
	lsp_diagfiles[lsp_ndiagfiles].d = NULL;
	lsp_diagfiles[lsp_ndiagfiles].n = 0;
	lsp_diagfiles[lsp_ndiagfiles].cap = 0;
	return &lsp_diagfiles[lsp_ndiagfiles++];
}

static lsp_server *lsp_find_srv_for_fd(int fd)
{
	int i;
	for (i = 0; i < lsp_nsrvs; i++)
		if (lsp_srvs[i].out_fd == fd)
			return &lsp_srvs[i];
	return NULL;
}

static void lsp_json_escape(const char *src, sbuf *sb)
{
	while (*src) {
		unsigned char c = (unsigned char)*src;
		if (c == '\"') {
			sbuf_mem(sb, \"\\\\\\\"\", 2)
		} else if (c == '\\\\') {
			sbuf_mem(sb, \"\\\\\\\\\", 2)
		} else if (c == '\\n') {
			sbuf_mem(sb, \"\\\\n\", 2)
		} else if (c == '\\r') {
			sbuf_mem(sb, \"\\\\r\", 2)
		} else if (c == '\\t') {
			sbuf_mem(sb, \"\\\\t\", 2)
		} else if (c < 0x20) {
			char esc[7];
			snprintf(esc, sizeof(esc), \"\\\\u%04x\", c);
			sbuf_mem(sb, esc, 6)
		} else {
			sbuf_chr(sb, *src)
		}
		src++;
	}
}

static void lsp_uri_from_path(const char *path, char *out, int n)
{
	if (path[0] == '/') {
		snprintf(out, n, \"file://%s\", path);
	} else {
		char cwd[1024];
		if (getcwd(cwd, sizeof(cwd)))
			snprintf(out, n, \"file://%s/%s\", cwd, path);
		else
			snprintf(out, n, \"file://%s\", path);
	}
}

static void lsp_path_from_uri(const char *uri, char *out, int n)
{
	if (!strncmp(uri, \"file://\", 7))
		snprintf(out, n, \"%.*s\", n - 1, uri + 7);
	else
		snprintf(out, n, \"%.*s\", n - 1, uri);
}

/* strip the cwd prefix so the path matches an already-open buffer */
static void lsp_relpath(char *path)
{
	char cwd[1024];
	int n;
	if (path[0] != '/' || !getcwd(cwd, sizeof(cwd)))
		return;
	n = strlen(cwd);
	if (!strncmp(path, cwd, n) && path[n] == '/')
		memmove(path, path + n + 1, strlen(path + n + 1) + 1);
}

static void lsp_print_hover(const char *s)
{
	char line[512];
	int i = 0;
	while (*s) {
		if (s[0] == '\\\\' && s[1]) {
			char c;
			switch (s[1]) {
			case 'n':  c = '\\n'; break;
			case 'r':  c = '\\r'; break;
			case 't':  c = '\\t'; break;
			case '\\\\': c = '\\\\'; break;
			case '\"':  c = '\"';  break;
			default:   c = s[1]; break;
			}
			s += 2;
			if (c == '\\n' || c == '\\r') {
				line[i] = '\\0';
				if (i > 0)
					ex_print(line, bar_ft)
				i = 0;
			} else if (i < (int)sizeof(line) - 1) {
				line[i++] = c;
			}
		} else {
			if (i < (int)sizeof(line) - 1)
				line[i++] = *s;
			s++;
		}
	}
	if (i > 0) {
		line[i] = '\\0';
		ex_print(line, bar_ft)
	}
}

static void lsp_send(lsp_server *srv, const char *json, int len)
{
	char hdr[64];
	int hlen = snprintf(hdr, sizeof(hdr),
		\"Content-Length: %d\\r\\n\\r\\n\", len);
	write(srv->in_fd, hdr, hlen);
	write(srv->in_fd, json, len);
}

static void lsp_send_sb(lsp_server *srv, sbuf *sb)
{
	sbuf_null(sb)
	lsp_send(srv, sb->s, sb->s_n);
}

static void lsp_sbuf_int(sbuf *sb, int n)
{
	char s[32];
	itoa(n, s);
	sbuf_str(sb, s)
}

static void lsp_buf_content(struct lbuf *lb, sbuf *sb)
{
	int i;
	for (i = 0; i < lbuf_len(lb); i++) {
		char *ln = lbuf_get(lb, i);
		lsp_json_escape(ln, sb);
	}
}

static int lsp_fmt_init(lsp_server *srv, sbuf *sb)
{
	int id = ++srv->next_id;
	sbuf_str(sb, \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":\")
	lsp_sbuf_int(sb, id);
	sbuf_str(sb, \",\\\"method\\\":\\\"initialize\\\",\\\"params\\\":{\\\"processId\\\":\")
	lsp_sbuf_int(sb, getpid());
	sbuf_str(sb, \",\\\"clientInfo\\\":{\\\"name\\\":\\\"nextvi\\\"},\\\"capabilities\\\":{\")
	sbuf_str(sb, \"\\\"textDocument\\\":{\\\"synchronization\\\":{\\\"didSave\\\":true},\")
	sbuf_str(sb, \"\\\"hover\\\":{\\\"contentFormat\\\":[\\\"plaintext\\\",\\\"markdown\\\"]},\")
	sbuf_str(sb, \"\\\"definition\\\":{}},\")
	sbuf_str(sb, \"\\\"general\\\":{\\\"positionEncodings\\\":[\\\"utf-8\\\"]}},\")
	sbuf_str(sb, \"\\\"rootUri\\\":null}}\")
	return id;
}

static void lsp_fmt_initialized(sbuf *sb)
{
	sbuf_str(sb, \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"method\\\":\\\"initialized\\\",\\\"params\\\":{}}\")
}

static void lsp_fmt_didopen(sbuf *sb, const char *path, const char *ft,
		struct lbuf *lb)
{
	char uri[1280];
	lsp_uri_from_path(path, uri, sizeof(uri));
	sbuf_str(sb, \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"method\\\":\\\"textDocument/didOpen\\\",\")
	sbuf_str(sb, \"\\\"params\\\":{\\\"textDocument\\\":{\\\"uri\\\":\\\"\")
	sbuf_str(sb, uri)
	sbuf_str(sb, \"\\\",\\\"languageId\\\":\\\"\")
	sbuf_str(sb, ft)
	sbuf_str(sb, \"\\\",\\\"version\\\":1,\\\"text\\\":\\\"\")
	lsp_buf_content(lb, sb);
	sbuf_str(sb, \"\\\"}}}\")
}

static void lsp_fmt_didchange(sbuf *sb, const char *path, int ver,
		struct lbuf *lb)
{
	char uri[1280];
	lsp_uri_from_path(path, uri, sizeof(uri));
	sbuf_str(sb, \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"method\\\":\\\"textDocument/didChange\\\",\")
	sbuf_str(sb, \"\\\"params\\\":{\\\"textDocument\\\":{\\\"uri\\\":\\\"\")
	sbuf_str(sb, uri)
	sbuf_str(sb, \"\\\",\\\"version\\\":\")
	lsp_sbuf_int(sb, ver);
	sbuf_str(sb, \"},\\\"contentChanges\\\":[{\\\"text\\\":\\\"\")
	lsp_buf_content(lb, sb);
	sbuf_str(sb, \"\\\"}]}}\")
}

static void lsp_fmt_didsave(sbuf *sb, const char *path)
{
	char uri[1280];
	lsp_uri_from_path(path, uri, sizeof(uri));
	sbuf_str(sb, \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"method\\\":\\\"textDocument/didSave\\\",\")
	sbuf_str(sb, \"\\\"params\\\":{\\\"textDocument\\\":{\\\"uri\\\":\\\"\")
	sbuf_str(sb, uri)
	sbuf_str(sb, \"\\\"}}}\")
}

/* format a textDocument/<method> request that carries a cursor position */
static int lsp_fmt_pos(lsp_server *srv, sbuf *sb, const char *method,
		const char *path, int line, int col)
{
	char uri[1280];
	int id = ++srv->next_id;
	lsp_uri_from_path(path, uri, sizeof(uri));
	sbuf_str(sb, \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":\")
	lsp_sbuf_int(sb, id);
	sbuf_str(sb, \",\\\"method\\\":\\\"textDocument/\")
	sbuf_str(sb, method)
	sbuf_str(sb, \"\\\",\\\"params\\\":{\\\"textDocument\\\":{\\\"uri\\\":\\\"\")
	sbuf_str(sb, uri)
	sbuf_str(sb, \"\\\"},\\\"position\\\":{\\\"line\\\":\")
	lsp_sbuf_int(sb, line);
	sbuf_str(sb, \",\\\"character\\\":\")
	lsp_sbuf_int(sb, col);
	sbuf_str(sb, \"}}}\")
	return id;
}

/* jsmn helpers */

static int lsp_tok_eq(const char *json, jsmntok_t *tok, const char *s)
{
	int len = tok->end - tok->start;
	return tok->type == JSMN_STRING &&
		(int)strlen(s) == len &&
		!strncmp(json + tok->start, s, len);
}

static void lsp_tok_str(const char *json, jsmntok_t *tok, char *out, int n)
{
	int len = tok->end - tok->start;
	if (len >= n)
		len = n - 1;
	memcpy(out, json + tok->start, len);
	out[len] = '\\0';
}

static int lsp_tok_int(const char *json, jsmntok_t *tok)
{
	char buf[32];
	lsp_tok_str(json, tok, buf, sizeof(buf));
	return atoi(buf);
}

/* skip token at index i and all its children; returns next index */
static int lsp_skip(jsmntok_t *toks, int n, int i)
{
	int end, j;
	if (i >= n)
		return n;
	if (toks[i].type == JSMN_OBJECT || toks[i].type == JSMN_ARRAY) {
		end = i + 1;
		for (j = 0; j < toks[i].size; j++) {
			if (toks[i].type == JSMN_OBJECT)
				end = lsp_skip(toks, n, end); /* key */
			end = lsp_skip(toks, n, end); /* value */
		}
		return end;
	}
	return i + 1;
}

/* find value token index for key in object at toks[obj] */
static int lsp_find_key(const char *json, jsmntok_t *toks, int n,
		int obj, const char *key)
{
	int i, j;
	if (obj >= n || toks[obj].type != JSMN_OBJECT)
		return -1;
	i = obj + 1;
	for (j = 0; j < toks[obj].size; j++) {
		if (i >= n)
			break;
		int val = i + 1;
		if (lsp_tok_eq(json, &toks[i], key))
			return val < n ? val : -1;
		i = lsp_skip(toks, n, val);
	}
	return -1;
}

/* collapse runs of escaped newlines (\\n / \\r) in place into a single space */
static void lsp_collapse_nl(char *s)
{
	char *w = s, *start = s;
	while (*s) {
		if (s[0] == '\\\\' && (s[1] == 'n' || s[1] == 'r')) {
			while (s[0] == '\\\\' && (s[1] == 'n' || s[1] == 'r'))
				s += 2;
			if (w != start && *s)	/* no leading/trailing space */
				*w++ = ' ';
		} else {
			*w++ = *s++;
		}
	}
	*w = '\\0';
}

static void lsp_handle_diagnostics(lsp_server *srv, const char *json,
		jsmntok_t *toks, int n)
{
	int params, uri_tok, diags_tok, i, di;
	char path[1024];
	lsp_diagfile *df;

	params = lsp_find_key(json, toks, n, 0, \"params\");
	if (params < 0)
		return;
	uri_tok = lsp_find_key(json, toks, n, params, \"uri\");
	if (uri_tok < 0)
		return;
	{
		char uri[1280];
		lsp_tok_str(json, &toks[uri_tok], uri, sizeof(uri));
		lsp_path_from_uri(uri, path, sizeof(path));
		lsp_relpath(path);
	}
	diags_tok = lsp_find_key(json, toks, n, params, \"diagnostics\");
	if (diags_tok < 0 || toks[diags_tok].type != JSMN_ARRAY)
		return;
	df = lsp_diagfile_for_path(path);
	if (!df)
		return;
	df->n = 0;
	/* iterate diagnostics array */
	i = diags_tok + 1;
	for (di = 0; di < toks[diags_tok].size && i < n; di++) {
		if (toks[i].type != JSMN_OBJECT) {
			i = lsp_skip(toks, n, i);
			continue;
		}
		int diag_obj = i;
		int range_tok = lsp_find_key(json, toks, n, diag_obj, \"range\");
		int sev_tok = lsp_find_key(json, toks, n, diag_obj, \"severity\");
		int msg_tok = lsp_find_key(json, toks, n, diag_obj, \"message\");
		int line = 0, col = 0, sev = 1;
		if (range_tok >= 0) {
			int start_tok = lsp_find_key(json, toks, n, range_tok, \"start\");
			if (start_tok >= 0) {
				int lt = lsp_find_key(json, toks, n, start_tok, \"line\");
				int ct = lsp_find_key(json, toks, n, start_tok, \"character\");
				if (lt >= 0)
					line = lsp_tok_int(json, &toks[lt]);
				if (ct >= 0)
					col = lsp_tok_int(json, &toks[ct]);
			}
		}
		if (sev_tok >= 0)
			sev = lsp_tok_int(json, &toks[sev_tok]);
		if (df->n >= df->cap) {
			df->cap = df->cap ? df->cap * 2 : 8;
			if (df->cap > LSP_DIAG_MAX)
				df->cap = LSP_DIAG_MAX;
			df->d = erealloc(df->d, df->cap * sizeof(lsp_diag));
		}
		if (df->n < LSP_DIAG_MAX) {
			df->d[df->n].line = line;
			df->d[df->n].col = col;
			df->d[df->n].severity = sev;
			if (msg_tok >= 0) {
				lsp_tok_str(json, &toks[msg_tok],
					df->d[df->n].msg, sizeof(df->d[0].msg));
				lsp_collapse_nl(df->d[df->n].msg);
			} else
				df->d[df->n].msg[0] = '\\0';
			df->n++;
		}
		i = lsp_skip(toks, n, diag_obj);
	}
	lsp_dirty = 1;
	(void)srv;
}

static void lsp_handle_notification(lsp_server *srv, const char *json,
		jsmntok_t *toks, int n, const char *method)
{
	if (!strcmp(method, \"textDocument/publishDiagnostics\"))
		lsp_handle_diagnostics(srv, json, toks, n);
}

static void lsp_handle_response(lsp_server *srv, const char *json, int id)
{
	if (srv->pending_id == id) {
		free(srv->response_json);
		srv->response_json = uc_dup(json);
		srv->response_ready = 1;
		srv->pending_id = 0;
	}
}

static void lsp_handle_message(lsp_server *srv, char *json, int len)
{
	jsmn_parser p;
	jsmntok_t toks[512];
	int n;
	char method[128] = \"\";
	int id = -1;

	jsmn_init(&p);
	n = jsmn_parse(&p, json, len, toks, 512);
	if (n < 1 || toks[0].type != JSMN_OBJECT)
		return;

	/* extract method */
	int mtok = lsp_find_key(json, toks, n, 0, \"method\");
	if (mtok >= 0 && toks[mtok].type == JSMN_STRING)
		lsp_tok_str(json, &toks[mtok], method, sizeof(method));

	/* extract id */
	int itok = lsp_find_key(json, toks, n, 0, \"id\");
	if (itok >= 0)
		id = lsp_tok_int(json, &toks[itok]);

	/* check if initialized response */
	if (id >= 0 && !method[0]) {
		if (!srv->initialized && id == 1) {
			/* this is the initialize response */
			sbuf_smake(sb2, 128)
			lsp_fmt_initialized(sb2);
			lsp_send_sb(srv, sb2);
			free(sb2->s);
			srv->initialized = 1;
		}
		lsp_handle_response(srv, json, id);
	} else if (method[0]) {
		lsp_handle_notification(srv, json, toks, n, method);
	}
}

static void lsp_dispatch_messages(lsp_server *srv)
{
	while (1) {
		/* find \\r\\n\\r\\n */
		char *hdr_end = NULL;
		int i;
		for (i = 0; i + 3 < srv->rbuf_n; i++) {
			if (srv->rbuf[i] == '\\r' && srv->rbuf[i+1] == '\\n' &&
					srv->rbuf[i+2] == '\\r' && srv->rbuf[i+3] == '\\n') {
				hdr_end = srv->rbuf + i + 4;
				break;
			}
		}
		if (!hdr_end)
			break;

		/* parse Content-Length */
		int clen = 0;
		char *cl = strstr(srv->rbuf, \"Content-Length:\");
		if (!cl || cl >= hdr_end)
			break;
		cl += 15;
		while (*cl == ' ')
			cl++;
		clen = atoi(cl);
		if (clen <= 0)
			break;

		int hdr_len = hdr_end - srv->rbuf;
		int total = hdr_len + clen;
		if (srv->rbuf_n < total)
			break; /* wait for more data */
		if (total >= LSP_RBUF_MAX)
			break; /* message too large */

		/* process message */
		hdr_end[clen] = '\\0';
		lsp_handle_message(srv, hdr_end, clen);

		/* shift buffer */
		srv->rbuf_n -= total;
		memmove(srv->rbuf, srv->rbuf + total, srv->rbuf_n);
	}
}

void lsp_process_fd(int fd)
{
	lsp_server *srv = lsp_find_srv_for_fd(fd);
	if (!srv)
		return;
	int space = LSP_RBUF_MAX - srv->rbuf_n - 1;
	if (space <= 0)
		return;
	int r = read(fd, srv->rbuf + srv->rbuf_n, space);
	if (r > 0) {
		srv->rbuf_n += r;
		lsp_dispatch_messages(srv);
	}
}

static int lsp_wait_response(lsp_server *srv, int id, int ms)
{
	int elapsed = 0;
	srv->pending_id = id;
	srv->response_ready = 0;
	while (elapsed < ms) {
		struct pollfd pfd;
		pfd.fd = srv->out_fd;
		pfd.events = POLLIN;
		int r = poll(&pfd, 1, 10);
		if (r > 0 && (pfd.revents & POLLIN))
			lsp_process_fd(srv->out_fd);
		if (srv->response_ready)
			return 1;
		elapsed += 10;
	}
	srv->pending_id = 0;
	return 0;
}

static int lsp_srv_ensure(lsp_server *srv)
{
	if (srv->pid > 0)
		return 1;
	char cmd_redir[sizeof(srv->cmd) + 16];
	snprintf(cmd_redir, sizeof(cmd_redir), \"%s 2>/dev/null\", srv->cmd);
	char *argv[] = {\"/bin/sh\", \"-c\", cmd_redir, NULL};
	srv->pid = cmd_make(argv, &srv->in_fd, &srv->out_fd);
	if (srv->pid <= 0) {
		srv->pid = -1;
		return 0;
	}
	fcntl(srv->out_fd, F_SETFL,
		fcntl(srv->out_fd, F_GETFL, 0) | O_NONBLOCK);
	/* register fd */
	if (lsp_nfds < LSP_NFDS_MAX)
		lsp_fds[lsp_nfds++] = srv->out_fd;
	srv->rbuf_n = 0;
	srv->initialized = 0;
	srv->next_id = 0;
	srv->response_ready = 0;
	srv->pending_id = 0;
	free(srv->response_json);
	srv->response_json = NULL;
	/* send initialize */
	sbuf_smake(sb, 512)
	int id = lsp_fmt_init(srv, sb);
	lsp_send_sb(srv, sb);
	free(sb->s);
	/* wait for initialized response */
	lsp_wait_response(srv, id, 5000);
	return srv->initialized;
}

static void lsp_open_lb(const char *path, const char *ft, struct lbuf *lb)
{
	lsp_server *srv = lsp_srv_for_ft(ft);
	if (!srv || !lb)
		return;
	if (!lsp_srv_ensure(srv) || !srv->initialized)
		return;
	int di = lsp_doc_find(srv, path);
	if (di >= 0) {
		sbuf_smake(sb, 4096)
		lsp_fmt_didchange(sb, path, ++srv->docs[di].version, lb);
		lsp_send_sb(srv, sb);
		free(sb->s);
	} else {
		if (srv->ndocs >= LSP_DOCS_MAX)
			return;
		snprintf(srv->docs[srv->ndocs].path,
			sizeof(srv->docs[0].path), \"%s\", path);
		srv->docs[srv->ndocs].version = 1;
		srv->docs[srv->ndocs].seq = lb->edseq;
		srv->ndocs++;
		sbuf_smake(sb, 4096)
		lsp_fmt_didopen(sb, path, ft, lb);
		lsp_send_sb(srv, sb);
		free(sb->s);
	}
}

void lsp_open(const char *path, const char *ft)
{
	lsp_open_lb(path, ft, ex_buf ? ex_buf->lb : NULL);
}

/* push buffer edits to the servers when the undo head changed (live diagnostics) */
void lsp_sync(const char *path, struct lbuf *lb)
{
	int i, di;
	if (!lb)
		return;
	for (i = 0; i < lsp_nsrvs; i++) {
		lsp_server *srv = &lsp_srvs[i];
		if (srv->pid <= 0 || (di = lsp_doc_find(srv, path)) < 0)
			continue;
		if (srv->docs[di].seq == lb->edseq)
			continue;
		srv->docs[di].seq = lb->edseq;
		sbuf_smake(sb, 4096)
		lsp_fmt_didchange(sb, path, ++srv->docs[di].version, lb);
		lsp_send_sb(srv, sb);
		free(sb->s);
	}
}

void lsp_save(const char *path)
{
	int i, di;
	for (i = 0; i < lsp_nsrvs; i++) {
		lsp_server *srv = &lsp_srvs[i];
		if (srv->pid <= 0 || (di = lsp_doc_find(srv, path)) < 0)
			continue;
		/* clangd lints its in-memory copy, so resync it before saving */
		if (ex_buf && ex_buf->lb) {
			srv->docs[di].seq = ex_buf->lb->edseq;
			sbuf_smake(sb, 4096)
			lsp_fmt_didchange(sb, path, ++srv->docs[di].version, ex_buf->lb);
			lsp_send_sb(srv, sb);
			free(sb->s);
		}
		sbuf_smake(sb, 256)
		lsp_fmt_didsave(sb, path);
		lsp_send_sb(srv, sb);
		free(sb->s);
	}
}

/* compute byte offset for row,off in lbuf (UTF-8 byte position in line) */
static int lsp_byte_offset(struct lbuf *lb, int row, int off)
{
	if (row < 0 || row >= lbuf_len(lb))
		return 0;
	char *ln = lbuf_get(lb, row);
	if (!ln)
		return 0;
	/* off is a visual column index; uc_off converts it to byte offset */
	return uc_off(ln, off);
}

/* send a position request and parse the reply; report and return -1 on error,
 * else return the \"result\" token index and fill json/toks/n for the caller */
static int lsp_request(const char *method, const char *path, int row, int off,
		char **json_out, jsmntok_t *toks, int ntoks, int *n_out)
{
	char buf[64];
	lsp_server *srv = lsp_srv_for_path(path);
	if (!srv) {
		lsp_show_msg(\"lsp: no server for file\");
		return -1;
	}
	int col = lsp_byte_offset(ex_buf ? ex_buf->lb : NULL, row, off);
	sbuf_smake(sb, 256)
	int id = lsp_fmt_pos(srv, sb, method, path, row, col);
	lsp_send_sb(srv, sb);
	free(sb->s);
	if (!lsp_wait_response(srv, id, 2000) || !srv->response_json) {
		snprintf(buf, sizeof(buf), \"lsp: %s timeout\", method);
		lsp_show_msg(buf);
		return -1;
	}
	char *json = srv->response_json;
	jsmn_parser p;
	jsmn_init(&p);
	int n = jsmn_parse(&p, json, strlen(json), toks, ntoks);
	if (n < 1) {
		snprintf(buf, sizeof(buf), \"lsp: %s parse error\", method);
		lsp_show_msg(buf);
		return -1;
	}
	int errtok = lsp_find_key(json, toks, n, 0, \"error\");
	if (errtok >= 0) {
		char errmsg[256] = \"lsp: error\";
		int msgtok = lsp_find_key(json, toks, n, errtok, \"message\");
		if (msgtok >= 0)
			lsp_tok_str(json, &toks[msgtok], errmsg, sizeof(errmsg));
		lsp_show_msg(errmsg);
		return -1;
	}
	int result = lsp_find_key(json, toks, n, 0, \"result\");
	if (result < 0 || toks[result].type == JSMN_PRIMITIVE) {
		snprintf(buf, sizeof(buf), \"lsp: no %s result\", method);
		lsp_show_msg(buf);
		return -1;
	}
	*json_out = json;
	*n_out = n;
	return result;
}

void lsp_hover(const char *path, int row, int off)
{
	char *json;
	jsmntok_t toks[1024];
	int n, result = lsp_request(\"hover\", path, row, off,
		&json, toks, 1024, &n);
	if (result < 0)
		return;
	int contents = lsp_find_key(json, toks, n, result, \"contents\");
	if (contents < 0) {
		lsp_show_msg(\"lsp: no hover contents\");
		return;
	}
	char msg[4096] = \"\";
	if (toks[contents].type == JSMN_STRING) {
		lsp_tok_str(json, &toks[contents], msg, sizeof(msg));
	} else if (toks[contents].type == JSMN_OBJECT) {
		int val = lsp_find_key(json, toks, n, contents, \"value\");
		if (val >= 0)
			lsp_tok_str(json, &toks[val], msg, sizeof(msg));
	} else if (toks[contents].type == JSMN_ARRAY && toks[contents].size > 0) {
		int first = contents + 1;
		if (toks[first].type == JSMN_STRING) {
			lsp_tok_str(json, &toks[first], msg, sizeof(msg));
		} else if (toks[first].type == JSMN_OBJECT) {
			int val = lsp_find_key(json, toks, n, first, \"value\");
			if (val >= 0)
				lsp_tok_str(json, &toks[val], msg, sizeof(msg));
		}
	}
	if (msg[0])
		lsp_print_hover(msg);
	else
		lsp_show_msg(\"lsp: empty hover\");
}

void lsp_definition(const char *path, int row, int off)
{
	char *json;
	jsmntok_t toks[1024];
	int n, result = lsp_request(\"definition\", path, row, off,
		&json, toks, 1024, &n);
	if (result < 0)
		return;
	char uri[1280] = \"\";
	int target_line = 0;
	/* result can be object (Location), array of Location/LocationLink */
	int obj = result;
	if (toks[result].type == JSMN_ARRAY) {
		if (toks[result].size == 0) {
			lsp_show_msg(\"lsp: definition not found\");
			return;
		}
		obj = result + 1;
	}
	if (toks[obj].type == JSMN_OBJECT) {
		/* try Location: uri + range */
		int uri_tok = lsp_find_key(json, toks, n, obj, \"uri\");
		if (uri_tok < 0) /* try LocationLink: targetUri + targetRange */
			uri_tok = lsp_find_key(json, toks, n, obj, \"targetUri\");
		if (uri_tok >= 0)
			lsp_tok_str(json, &toks[uri_tok], uri, sizeof(uri));
		int range_tok = lsp_find_key(json, toks, n, obj, \"range\");
		if (range_tok < 0)
			range_tok = lsp_find_key(json, toks, n, obj, \"targetRange\");
		if (range_tok >= 0) {
			int start = lsp_find_key(json, toks, n, range_tok, \"start\");
			if (start >= 0) {
				int lt = lsp_find_key(json, toks, n, start, \"line\");
				if (lt >= 0)
					target_line = lsp_tok_int(json, &toks[lt]);
			}
		}
	}
	if (!uri[0]) {
		lsp_show_msg(\"lsp: definition not found\");
		return;
	}
	char fpath[1024];
	lsp_path_from_uri(uri, fpath, sizeof(fpath));
	lsp_relpath(fpath);
	if (strcmp(fpath, path)) {
		int already_open = ex_edit(fpath, strlen(fpath));
		if (!already_open) {
			ex_bufpostfix(ex_buf, 0);
			syn_setft(xb_ft);
			if (*xb_path && xb_ft)
				lsp_open(xb_path, xb_ft);
		}
	}
	xrow = target_line;
	if (xrow >= lbuf_len(xb))
		xrow = lbuf_len(xb) - 1;
	if (xrow < 0)
		xrow = 0;
	xoff = 0;
	xtop = xrow > xrows / 2 ? xrow - xrows / 2 : 0;
}

const char *lsp_diag_for_line(const char *path, int line, int *sev)
{
	int i, j;
	for (i = 0; i < lsp_ndiagfiles; i++) {
		if (!strcmp(lsp_diagfiles[i].path, path)) {
			for (j = 0; j < lsp_diagfiles[i].n; j++)
				if (lsp_diagfiles[i].d[j].line == line) {
					if (sev)
						*sev = lsp_diagfiles[i].d[j].severity;
					return lsp_diagfiles[i].d[j].msg;
				}
			return NULL;
		}
	}
	return NULL;
}
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL lsp.c:-1:m${ESC}${SEP}pr${INTR}${QF2}}${SEP}b5${SEP}%ya 98${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f> 
int term_read\\(int winch\\)
\\{
	static struct pollfd ufd = \\{STDIN_FILENO, POLLIN};
	int cw;
	if \\(ibuf_pos >= ibuf_cnt\\) \\{
		if \\(texec\\) \\{
			xquit = !xquit \\? 1 : xquit;${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+3m 1${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f> 	static struct pollfd ufd = \\{STDIN_FILENO, POLLIN};
	int cw;
	if \\(ibuf_pos >= ibuf_cnt\\) \\{
		if \\(texec\\) \\{
			xquit = !xquit \\? 1 : xquit;${ESC}${SEP}2??${ESC}${SEP}${LB}
${ESC}${SEP}2??m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:142:a2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f> 
int term_read\\(int winch\\)
\\{${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+3m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:142:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f> 	static struct pollfd ufd = \\{STDIN_FILENO, POLLIN};
	int cw;${ESC}${SEP}4??${ESC}${SEP}${LB}
${ESC}${SEP}4??m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:142:a4${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f> 	if \\(ibuf_pos >= ibuf_cnt\\) \\{
		if \\(texec\\) \\{
			xquit = !xquit \\? 1 : xquit;${ESC}${SEP}5??${ESC}${SEP}${LB}
${ESC}${SEP}5??-2m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:142:a5${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f> 
..t....m...a..... w.....
\\{
....ti. s....t.....f. ....=...T..N.......,...LLI...
	.......
	i.....u..po...= ..u.......\\{
	.............
	....ui. ...................t.${ESC}${SEP}6??${ESC}${SEP}${LB}
${ESC}${SEP}6??+3m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:142:a6${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f> .*?
int term_read\\(int winch\\).*?
\\{.*?
(	static struct pollfd ufd = \\{STDIN_FILENO, POLLIN};)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:142:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		memcpy\\(ibuf \\+ ibuf_cnt, s, n\\);
	ibuf_cnt \\+= n;
}.*(			if \\(texec == '&'\\))
				goto err;
		}${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-5m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:142:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		tn \\+= n;
		tibuf_pos = ibuf_pos;
	} else.*(		if \\(term_winch && winch\\) \\{)
			\\*ibuf = winch;	/\\* yield until term_winch is cleared \\*/
			goto ret;${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-8m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:142:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;2;3;4;5;6;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL term.c:142${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		}
		cw = 0;
		re:
		/\\* read a single input character \\*/
		if \\(xquit < 0 \\|\\| poll\\(&ufd, 1, -1\\) <= 0 \\|\\|
				read\\(STDIN_FILENO, ibuf, 1\\) <= 0\\) \\{
			xquit = !isatty\\(STDIN_FILENO\\) \\? -1 : xquit;
			if \\(term_winch && winch && xquit >= 0\\) \\{
				\\*ibuf = winch;
				goto ret;
			} else if \\(term_winch != cw && !winch && xquit >= 0\\) \\{
				cw = term_winch;
				goto re;
			}${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+3m 2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		/\\* read a single input character \\*/
		if \\(xquit < 0 \\|\\| poll\\(&ufd, 1, -1\\) <= 0 \\|\\|
				read\\(STDIN_FILENO, ibuf, 1\\) <= 0\\) \\{
			xquit = !isatty\\(STDIN_FILENO\\) \\? -1 : xquit;
			if \\(term_winch && winch && xquit >= 0\\) \\{
				\\*ibuf = winch;
				goto ret;
			} else if \\(term_winch != cw && !winch && xquit >= 0\\) \\{
				cw = term_winch;
				goto re;
			}${ESC}${SEP}2??${ESC}${SEP}${LB}
${ESC}${SEP}2??m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:156:a2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		}
		cw = 0;
		re:${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+3m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:156:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		/\\* read a single input character \\*/
		if \\(xquit < 0 \\|\\| poll\\(&ufd, 1, -1\\) <= 0 \\|\\|
				read\\(STDIN_FILENO, ibuf, 1\\) <= 0\\) \\{
			xquit = !isatty\\(STDIN_FILENO\\) \\? -1 : xquit;
			if \\(term_winch && winch && xquit >= 0\\) \\{
				\\*ibuf = winch;
				goto ret;
			} else if \\(term_winch != cw && !winch && xquit >= 0\\) \\{
				cw = term_winch;${ESC}${SEP}4??${ESC}${SEP}${LB}
${ESC}${SEP}4??m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:156:a4${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 				goto re;
			}${ESC}${SEP}5??${ESC}${SEP}${LB}
${ESC}${SEP}5??-9m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:156:a5${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	..
.	.w.. .;
		...
.... r... a...ng.. ..... c........ ..
..i.......... . .\\|.....\\(.u.......-..........
.................LE..,...........=.0...
........ =.!......\\(..D.N_......\\)....1.:..q..t.
............w..ch.&..w.........q..t.>......
.	.	.i.u....wi....
..	....o.....
......ls. .........w.n.h ............n..... ...it... ...\\{
..	... .....m_......
....g.......
	.	.${ESC}${SEP}6??${ESC}${SEP}${LB}
${ESC}${SEP}6??+3m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:156:a6${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 		}.*?
		cw = 0;.*?
		re:.*?
(		/\\* read a single input character \\*/)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:156:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		if \\(term_winch && winch\\) \\{
			\\*ibuf = winch;	/\\* yield until term_winch is cleared \\*/
			goto ret;.*(	if \\(icmd_pos < sizeof\\(icmd\\)\\))
		icmd\\[icmd_pos\\+\\+] = ibuf\\[ibuf_pos];
	return ibuf\\[ibuf_pos\\+\\+];${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-22m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:156:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 			if \\(texec == '&'\\)
				goto err;
		}.*(/\\* return a static string that changes text attributes to att \\*/)
char \\*term_att\\(int att\\)
\\{${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-27m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:156:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;2;3;4;5;6;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL term.c:156${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 				goto re;
			}
			err:
			\\*ibuf = 0;
		} else if \\(xrr\\) \\{
			static char buf\\[2];
			buf\\[0] = \\*ibuf;${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 			err:
			\\*ibuf = 0;
		} else if \\(xrr\\) \\{
			static char buf\\[2];
			buf\\[0] = \\*ibuf;${ESC}${SEP}2??${ESC}${SEP}${LB}
${ESC}${SEP}2??m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:167:a2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 				goto re;
			}${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:167:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 			err:
			\\*ibuf = 0;${ESC}${SEP}4??${ESC}${SEP}${LB}
${ESC}${SEP}4??m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:167:a4${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		} else if \\(xrr\\) \\{
			static char buf\\[2];
			buf\\[0] = \\*ibuf;${ESC}${SEP}5??${ESC}${SEP}${LB}
${ESC}${SEP}5??-2m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:167:a5${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ ....g....re.
			.
..	.rr.
.	..ibu.....;
	....... .....r...\\{
..	.t...c .h.r.b...2..
......\\[.. = ..b...${ESC}${SEP}6??${ESC}${SEP}${LB}
${ESC}${SEP}6??+2m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:167:a6${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 				goto re;.*?
			}.*?
(			err:)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:167:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		if \\(term_winch && winch\\) \\{
			\\*ibuf = winch;	/\\* yield until term_winch is cleared \\*/
			goto ret;.*(	if \\(icmd_pos < sizeof\\(icmd\\)\\))
		icmd\\[icmd_pos\\+\\+] = ibuf\\[ibuf_pos];
	return ibuf\\[ibuf_pos\\+\\+];${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-11m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:167:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 			if \\(texec == '&'\\)
				goto err;
		}.*(/\\* return a static string that changes text attributes to att \\*/)
char \\*term_att\\(int att\\)
\\{${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-16m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:167:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;2;3;4;5;6;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL term.c:167${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 			buf\\[0] = \\*ibuf;
			ex_regput\\(xrr, buf, 1\\);
		}
		ret:
		ibuf_cnt = 1;
		ibuf_pos = 0;${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 4${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 			buf\\[0] = \\*ibuf;
			ex_regput\\(xrr, buf, 1\\);
		}${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:173:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 			buf\\[0] = \\*ibuf;.*?
			ex_regput\\(xrr, buf, 1\\);.*?
(		})${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:173:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		if \\(term_winch && winch\\) \\{
			\\*ibuf = winch;	/\\* yield until term_winch is cleared \\*/
			goto ret;.*(	if \\(icmd_pos < sizeof\\(icmd\\)\\))
		icmd\\[icmd_pos\\+\\+] = ibuf\\[ibuf_pos];
	return ibuf\\[ibuf_pos\\+\\+];${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-5m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:173:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 			if \\(texec == '&'\\)
				goto err;
		}.*(/\\* return a static string that changes text attributes to att \\*/)
char \\*term_att\\(int att\\)
\\{${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-10m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK term.c:173:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL term.c:173${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}${LB}
${SEP}'1,#+1c 	static struct pollfd ufd[1 + LSP_NFDS_MAX];
	int cw, i;
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL term.c:142:m1${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'2,#+8c 		ufd[0].fd = STDIN_FILENO;
		ufd[0].events = POLLIN;
		for (i = 0; i < lsp_nfds; i++) {
			ufd[i+1].fd = lsp_fds[i];
			ufd[i+1].events = POLLIN;
		}
		/* read a single input character, servicing lsp fds */
		if (xquit >= 0 && poll(ufd, 1 + lsp_nfds, -1) > 0) {
			for (i = 0; i < lsp_nfds; i++)
				if (ufd[i+1].revents & POLLIN)
					lsp_process_fd(lsp_fds[i]);
			if (!(ufd[0].revents & POLLIN)) {
				if (term_winch && winch) {
					*ibuf = winch;
					goto ret;
				}
				if (lsp_dirty && lsp_wake)
					goto err;	/* yield so vi redraws diagnostics */
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL term.c:156:m2${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'3,#+1c 			if (read(STDIN_FILENO, ibuf, 1) > 0)
				goto ret;
		}
		xquit = !isatty(STDIN_FILENO) ? -1 : xquit;
		if (term_winch && winch && xquit >= 0) {
			*ibuf = winch;
			goto ret;
		} else if (term_winch != cw && !winch && xquit >= 0) {
			cw = term_winch;
			goto re;
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL term.c:167:m3${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'4i 		err:
		*ibuf = 0;
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL term.c:173:m4${ESC}${SEP}pr${INTR}${QF2}}${SEP}b6${SEP}%ya 98${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f> #include \"ren\\.c\"
#include \"term\\.c\"
#include \"uc\\.c\"

int vi_hidch;			/\\* show hidden chars \\*/
int vi_lncol;			/\\* line numbers cursor offset \\*/${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 1${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f> #include \"ren\\.c\"
#include \"term\\.c\"
#include \"uc\\.c\"${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:24:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f> #include \"ren\\.c\".*?
#include \"term\\.c\".*?
(#include \"uc\\.c\")${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:24:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> #include \"lbuf\\.c\"
#include \"led\\.c\"
#include \"regex\\.c\".*(static int vi_lnnum;		/\\* line numbers \\*/)
/\\* screen redraw - bit 1: whole screen, bit 2: current line, bit 3: update vi_col \\*/
static int vi_mod;${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:24:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> #include \"vi\\.h\"
#include \"conf\\.c\"
#include \"ex\\.c\".*(static char vi_word_m\\[] = \"\\\\0leEwW\";	/\\* line word navigation \\*/)
static char \\*vi_word = vi_word_m;
static char \\*_vi_word = vi_word_m;${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:24:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:24${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ }
#define vi_drawmsg_mpt\\(msg\\) \\{ vi_drawmsg\\(msg\\); if \\(!xmpt\\) xmpt = 1; }

static int vi_nextcol\\(char \\*ln, int dir, int \\*off\\)
\\{
	int o = ren_off\\(ln, ren_next\\(ln, ren_pos\\(ln, \\*off\\), dir\\)\\);${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ }
#define vi_drawmsg_mpt\\(msg\\) \\{ vi_drawmsg\\(msg\\); if \\(!xmpt\\) xmpt = 1; }

${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:104:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	RS\\(2, led_crender\\(msg, xrows, 0, 0, xcols\\)\\)
	restore\\(xtd\\)
	restore\\(ftidx\\).*(	if \\(\\*rstate->chrs\\[o] == '\\\\n'\\))
		return -1;
	\\*off = o;${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:104:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	preserve\\(int, xtd, xtd = 2;\\)
	preserve\\(int, ftidx,\\)
	syn_setft\\(bar_ft\\);.*(#define vi_drawnum\\(func\\) \\\\)
\\{ \\\\
nrow = xrow; \\\\${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-10m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:104:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:104${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	ret = func; \\\\
} } \\\\

static void vi_drawrow\\(int row\\)
\\{${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	ret = func; \\\\
} } \\\\

${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:127:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	i = i > 99 \\? i % 100 : i; \\\\
	itoa\\(i%10 \\? i%10 : i, snum\\); \\\\
	tmp\\[l1] = \\*snum; \\\\.*(	if \\(xmpt == 1 && !vi_status && row == xtop \\+ xrows - 1\\))
		return;
	if \\(\\*vi_word && xled\\) \\{${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-6m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:127:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	l1 = ren_next\\(c, ren_pos\\(c, noff\\), 1\\)-1-xleft\\+vi_lncol; \\\\
	if \\(l1 > xcols \\|\\| l1 < 0 \\|\\| ret \\|\\| l1 >= rstate->cmax \\+ vi_lncol\\) \\\\
		break; \\\\.*(		int noff, nrow, ret;)
		c = lbuf_get\\(xb, xrow\\);
		if \\(row != xrow\\+1 \\|\\| !c \\|\\| \\*c == '\\\\n'\\) \\{${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-9m 3${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:127:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:127${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ static void vi_drawrow\\(int row\\)
\\{
	int l1, i, i1, lnnum = vi_lnnum;
	char \\*c, \\*s;
	static char ch\\[5] = \"~\";${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+1m 4${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ static void vi_drawrow\\(int row\\)
\\{${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+1m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:129:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ static void vi_drawrow\\(int row\\).*?
(\\{)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:129:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	i = i > 99 \\? i % 100 : i; \\\\
	itoa\\(i%10 \\? i%10 : i, snum\\); \\\\
	tmp\\[l1] = \\*snum; \\\\.*(	if \\(xmpt == 1 && !vi_status && row == xtop \\+ xrows - 1\\))
		return;
	if \\(\\*vi_word && xled\\) \\{${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:129:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	l1 = ren_next\\(c, ren_pos\\(c, noff\\), 1\\)-1-xleft\\+vi_lncol; \\\\
	if \\(l1 > xcols \\|\\| l1 < 0 \\|\\| ret \\|\\| l1 >= rstate->cmax \\+ vi_lncol\\) \\\\
		break; \\\\.*(		int noff, nrow, ret;)
		c = lbuf_get\\(xb, xrow\\);
		if \\(row != xrow\\+1 \\|\\| !c \\|\\| \\*c == '\\\\n'\\) \\{${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 4${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:129:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:129${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		return;
	}
	s = lbuf_get\\(xb, row\\);
	skip:
	rstate \\+= row != xrow;
	if \\(!s\\)${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 5${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		return;
	}
	s = lbuf_get\\(xb, row\\);${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 5${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:172:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 		return;.*?
	}.*?
(	s = lbuf_get\\(xb, row\\);)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 5${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:172:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		restore\\(syn_blockhl\\)
		restore\\(xtd\\)
		restore\\(ftidx\\).*(		s = row \\? ch : ch\\+1;)
	else if \\(lnnum && xled\\) \\{
		char tmp\\[32], tmp1\\[32], \\*p;${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 5${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:172:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		syn_setft\\(n_ft\\);
		RS\\(2, led_crender\\(tmp, row - xtop, 0, 0, xcols\\)\\)
		restore\\(xorder\\).*(		c = tmp, i = 0, i1 = 0;)
		if \\(lnnum == 1 \\|\\| lnnum & 2\\) \\{
			c = itoa\\(row\\+1-vi_rshift, tmp\\);${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 5${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:172:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:172${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		memset\\(c, ' ', l1 - \\(c - tmp\\)\\);
		c\\[l1 - \\(c - tmp\\)] = '\\\\0';
		led_crender\\(s, row - xtop, l1, xleft, xleft \\+ xcols - l1\\)
		preserve\\(int, syn_blockhl, syn_blockhl = -1;\\)
		preserve\\(int, ftidx,\\)
		syn_setft\\(nn_ft\\);${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 6${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		memset\\(c, ' ', l1 - \\(c - tmp\\)\\);
		c\\[l1 - \\(c - tmp\\)] = '\\\\0';
		led_crender\\(s, row - xtop, l1, xleft, xleft \\+ xcols - l1\\)${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 6${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:196:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 		memset\\(c, ' ', l1 - \\(c - tmp\\)\\);.*?
		c\\[l1 - \\(c - tmp\\)] = '\\\\0';.*?
(		led_crender\\(s, row - xtop, l1, xleft, xleft \\+ xcols - l1\\))${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 6${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:196:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		\\*c = '\\\\0';
		l1 = \\(c - tmp\\) \\+ \\(i\\+i1 - \\(strlen\\(tmp\\) - !!i - !!i1\\)\\);
		vi_lncol = dir_context\\(s\\) < 0 \\? 0 : l1;.*(		if \\(\\(lnnum == 1 \\|\\| lnnum & 4\\) && !xleft && vi_lncol\\) \\{)
			for \\(i1 = 0; i1 < rstate->cmax &&
					memchr\\(\" \\\\t\", \\*rstate->chrs\\[ren_off\\(s, i1\\)], 2\\);\\)${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 6${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:196:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 			\\*c\\+\\+ = ' ';
			i1 = itoalen\\(xrows\\);
		}.*(				i1 = ren_next\\(s, i1, 1\\);)
			i1 -= \\(itoa\\(abs\\(xrow-row\\+vi_rshift\\), tmp1\\) - tmp1\\)\\+1;
			if \\(i1 >= 0\\) \\{${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 6${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:196:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:196${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		RS\\(2, led_prender\\(tmp, row - xtop, 0, 0, l1\\)\\)
		restore\\(syn_blockhl\\)
		restore\\(ftidx\\)
		return;
	}
	led_crender\\(s, row - xtop, 0, xleft, xleft \\+ xcols\\)${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		RS\\(2, led_prender\\(tmp, row - xtop, 0, 0, l1\\)\\)
		restore\\(syn_blockhl\\)
		restore\\(ftidx\\)${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 7${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:212:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 		RS\\(2, led_prender\\(tmp, row - xtop, 0, 0, l1\\)\\).*?
		restore\\(syn_blockhl\\).*?
(		restore\\(ftidx\\))${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 7${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:212:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 				RS\\(2, led_prender\\(tmp1, row - xtop, l1\\+i1, 0, l1\\)\\)
			}
		}.*(static void vi_drawagain\\(int i\\))
\\{
	syn_scdir\\(0\\);${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-8m 7${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:212:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 			i1 -= \\(itoa\\(abs\\(xrow-row\\+vi_rshift\\), tmp1\\) - tmp1\\)\\+1;
			if \\(i1 >= 0\\) \\{
				memset\\(p, ' ', strlen\\(p\\)\\);.*(	for \\(; i < xtop \\+ xrows; i\\+\\+\\))
		vi_drawrow\\(i\\);
}${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-11m 7${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:212:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:212${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		return;
	}
	led_crender\\(s, row - xtop, 0, xleft, xleft \\+ xcols\\)
	rstate = rstates;${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 8${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		return;
	}
	led_crender\\(s, row - xtop, 0, xleft, xleft \\+ xcols\\)${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 8${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:215:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 		return;.*?
	}.*?
(	led_crender\\(s, row - xtop, 0, xleft, xleft \\+ xcols\\))${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 8${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:215:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 				RS\\(2, led_prender\\(tmp1, row - xtop, l1\\+i1, 0, l1\\)\\)
			}
		}.*(static void vi_drawagain\\(int i\\))
\\{
	syn_scdir\\(0\\);${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-5m 8${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:215:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 			i1 -= \\(itoa\\(abs\\(xrow-row\\+vi_rshift\\), tmp1\\) - tmp1\\)\\+1;
			if \\(i1 >= 0\\) \\{
				memset\\(p, ' ', strlen\\(p\\)\\);.*(	for \\(; i < xtop \\+ xrows; i\\+\\+\\))
		vi_drawrow\\(i\\);
}${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-8m 8${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:215:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:215${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 	rstate = rstates;
}

/\\* redraw the screen \\*/${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??m 9${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP};0${ESC}${SEP}fr${ESC}${SEP}.,\$f+ ^	rstate = rstates;\$${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??m 9${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:216:a3${ESC}${ESC}${ESC}${SEP}fr 98${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}fr 98${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 				RS\\(2, led_prender\\(tmp1, row - xtop, l1\\+i1, 0, l1\\)\\)
			}
		}.*(static void vi_drawagain\\(int i\\))
\\{
	syn_scdir\\(0\\);${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 9${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:216:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 			i1 -= \\(itoa\\(abs\\(xrow-row\\+vi_rshift\\), tmp1\\) - tmp1\\)\\+1;
			if \\(i1 >= 0\\) \\{
				memset\\(p, ' ', strlen\\(p\\)\\);.*(	for \\(; i < xtop \\+ xrows; i\\+\\+\\))
		vi_drawrow\\(i\\);
}${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 9${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:216:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:216${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		int oleft = xleft;
		icmd_pos = 0;
		vi_mod = 0;
		vi_ybuf = vi_yankbuf\\(TK_CTL\\('l'\\)\\);${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 10${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		int oleft = xleft;
		icmd_pos = 0;
		vi_mod = 0;${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 10${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1188:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 		int oleft = xleft;.*?
		icmd_pos = 0;.*?
(		vi_mod = 0;)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 10${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1188:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		int orow = nrow;
		int ooff = noff;
		int otop = xtop;.*(			vi_lnnum = 0;)
			vi_lncol = 0;
			vi_mod \\|= 1;${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-5m 10${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1188:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	while \\(!xquit\\) \\{
		int nrow = xrow;
		int noff = xoff;.*(		if \\(xmpt == 1\\) \\{)
			xmpt = 0;
			vi_drawrow\\(otop \\+ xrows - 1\\);${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-9m 10${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1188:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1188${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 		vi_ybuf = vi_yankbuf\\(TK_CTL\\('l'\\)\\);
		vi_arg = vi_prefix\\(\\);
		term_dec\\(\\)
		if \\(vi_lnnum == 1\\) \\{${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??m 11${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP};0${ESC}${SEP}fr${ESC}${SEP}.,\$f+ ^		vi_ybuf = vi_yankbuf\\(TK_CTL\\('l'\\)\\);\$${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??m 11${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1189:a3${ESC}${ESC}${ESC}${SEP}fr 98${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}fr 98${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 		int orow = nrow;
		int ooff = noff;
		int otop = xtop;.*(			vi_lnnum = 0;)
			vi_lncol = 0;
			vi_mod \\|= 1;${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 11${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1189:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	while \\(!xquit\\) \\{
		int nrow = xrow;
		int noff = xoff;.*(		if \\(xmpt == 1\\) \\{)
			xmpt = 0;
			vi_drawrow\\(otop \\+ xrows - 1\\);${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-8m 11${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1189:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1189${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 					ex_command\\(cmd\\)
					restore\\(xled\\)
					vi_mod \\|= 1;
				} else if \\(k == '~' \\|\\| k == 'u' \\|\\| k == 'U'\\)
					vc_motion\\(k\\);${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+3m 12${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 				} else if \\(k == '~' \\|\\| k == 'u' \\|\\| k == 'U'\\)
					vc_motion\\(k\\);${ESC}${SEP}2??${ESC}${SEP}${LB}
${ESC}${SEP}2??m 12${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1645:a2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 					ex_command\\(cmd\\)
					restore\\(xled\\)
					vi_mod \\|= 1;${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+3m 12${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1645:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP};0${ESC}${SEP}fr${ESC}${SEP}.,\$f+ ^				} else if \\(k == '~' \\|\\| k == 'u' \\|\\| k == 'U'\\)\$${ESC}${SEP}4??${ESC}${SEP}${LB}
${ESC}${SEP}4??m 12${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1645:a4${ESC}${ESC}${ESC}${SEP}fr 98${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}fr 98${ESC}${SEP}${LB}
${ESC}${SEP};0${ESC}${SEP}fr${ESC}${SEP}.,\$f+ ^					vc_motion\\(k\\);\$${ESC}${SEP}5??${ESC}${SEP}${LB}
${ESC}${SEP}5??-1m 12${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1645:a5${ESC}${ESC}${ESC}${SEP}fr 98${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}fr 98${ESC}${SEP}${LB}
${ESC}${SEP}%f+ ....	.x....m......d.
.....r.........e..
	.......... .....
..		....s..i...k.=....'..... ...... \\|........U'\\)
.	..	vc...t......;${ESC}${SEP}6??${ESC}${SEP}${LB}
${ESC}${SEP}6??+3m 12${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1645:a6${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ 					ex_command\\(cmd\\).*?
					restore\\(xled\\).*?
					vi_mod \\|= 1;.*?
(				} else if \\(k == '~' \\|\\| k == 'u' \\|\\| k == 'U'\\))${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 12${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1645:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 					preserve\\(int, xled, xled = 0;\\)
					char cmd\\[64] = \"g/\\./& \";
					memcpy\\(itoa\\(vi_arg, cmd\\+5\\), \"gw\", sizeof\\(\"gw\"\\)\\);.*(			case 'X':)
				term_push\\(\"d\", 2\\);
				goto motion;${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-6m 12${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1645:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 					restore\\(xvis\\)
					vi_mod \\|= !texec;
				} else if \\(k == 'q'\\) \\{.*(			case 'D':)
				term_push\\(\"d\\\$\", 2\\);
				goto motion;${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-9m 12${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1645:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;2;3;4;5;6;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1645${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 					vc_motion\\(k\\);
				break;
			case 'x':
				term_push\\(\"d \", 2\\);${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??m 13${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP};0${ESC}${SEP}fr${ESC}${SEP}.,\$f+ ^					vc_motion\\(k\\);\$${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??m 13${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1646:a3${ESC}${ESC}${ESC}${SEP}fr 98${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}fr 98${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 					preserve\\(int, xled, xled = 0;\\)
					char cmd\\[64] = \"g/\\./& \";
					memcpy\\(itoa\\(vi_arg, cmd\\+5\\), \"gw\", sizeof\\(\"gw\"\\)\\);.*(			case 'X':)
				term_push\\(\"d\", 2\\);
				goto motion;${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-5m 13${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1646:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 					restore\\(xvis\\)
					vi_mod \\|= !texec;
				} else if \\(k == 'q'\\) \\{.*(			case 'D':)
				term_push\\(\"d\\\$\", 2\\);
				goto motion;${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-8m 13${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1646:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1646${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 				vc_status\\(0\\);
				vi_mod \\|= 1;
				break;
			default:
				continue;
			}${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 14${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 				vc_status\\(0\\);
				vi_mod \\|= 1;
				break;${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 14${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1721:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 					ex_exec\\(\"b-2:%d:fd:b-2\"\\);
				else
					ex_exec\\(\"%d:fd\"\\);.*(		topfix\\(\\))
		ln = lbuf_get\\(xb, xrow\\);
		xoff = ren_noeol\\(ln, xoff\\);${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-5m 14${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1721:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 				if \\(!vi_arg\\)
					ex_exec\\(\"b-2\"\\);
				else if \\(xb != tempbufs\\[1]\\.lb\\).*(		if \\(ln && !rstate->wid\\[xoff]\\) \\{)
			for \\(n = xoff, k = n; k < rstate->n && !rstate->wid\\[k];\\) \\{
				if \\(!k\\)${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-8m 14${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1721:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1721${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 				}
			}
		}
		term_record = 1;
		if \\(vi_mod & 1 \\|\\| xleft != oleft
				\\|\\| \\(vi_lnnum && orow != xrow && !\\(vi_lnnum == 2\\)\\)${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 15${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f+ 				}
			}
		}${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 15${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1781:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 					la\\.off = off1;
					sbuf_mem\\(led_attsb, &la, sizeof\\(la\\)\\)
					vi_mod \\|= row1 == row && orow == xrow \\? 2 : 1;.*(				\\|\\| \\(\\*vi_word && orow != xrow\\)\\))
			vi_drawagain\\(xtop\\);
		else if \\(\\*vi_word && \\(ooff != xoff \\|\\| vi_mod & 2\\)${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 15${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1781:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 					la\\.att = hls\\[k]\\.att\\[0];
					sbuf_mem\\(led_attsb, &la, sizeof\\(la\\)\\)
					la\\.s = lbuf_get\\(xb, row1\\);.*(				&& xrow\\+1 < xtop \\+ xrows\\))
			vi_drawrow\\(xrow\\+1\\);
		else if \\(xtop != otop\\)${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 15${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.c:1781:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1781${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}${LB}
${SEP}'1i #include \"lsp.c\"
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:24:m1${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'2i void lsp_show_msg(char *msg) { vi_drawmsg_mpt(msg) }

${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:104:m2${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'3i /* render an lsp diagnostic as virtual text starting at screen column col */
static void vi_drawdiag(const char *diag, int sev, int r, int col)
{
	static const char *sevname[] = {\"info\", \"error\", \"warning\", \"info\", \"hint\"};
	if (col < 0 || col >= xcols)
		return;
	sbuf_smake(sb, 256)
	sbuf_str(sb, \"  \")
	sbuf_str(sb, sevname[sev > 0 && sev < 5 ? sev : 1])
	sbuf_str(sb, \": \")
	sbuf_str(sb, diag)
	sbuf_chr(sb, '\\n')
	sbuf_null(sb)
	preserve(int, syn_blockhl, syn_blockhl = -1;)
	preserve(int, ftidx,)
	syn_setft(lsp_ft);
	RS(2, led_prender(sb->s, r, col, 0, xcols - col))
	restore(syn_blockhl)
	restore(ftidx)
	free(sb->s);
}

${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:127:m3${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'4i 	const char *diag = NULL;
	int dsev = 1;
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:129:m4${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'5i 	if (s && xb_path && xb_path[0])
		diag = lsp_diag_for_line(xb_path, row, &dsev);
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:172:m5${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'6i 		int dcol = l1 + rstate->cmax - xleft;
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:196:m6${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'7i 		if (diag)
			vi_drawdiag(diag, dsev, row - xtop, dcol);
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:212:m7${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'8i 	int dcol = rstate->cmax - xleft;
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:215:m8${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'9i 	if (diag)
		vi_drawdiag(diag, dsev, row - xtop, dcol);
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:216:m9${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'10i 		lsp_wake = 1;
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1188:m10${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'11i 		lsp_wake = 0;
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1189:m11${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'12s/\\)/) {/${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1645:m12${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'13i 				} else if (k == 'K') {
					if (xb_path && xb_path[0])
						lsp_hover(xb_path, xrow, xoff);
				} else if (k == 'd') {
					if (xb_path && xb_path[0]) {
						lsp_definition(xb_path, xrow, xoff);
						vc_status(0);
						vi_mod |= 1;
					}
				}
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1646:m13${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'14i 			case 0:	/* lsp_wake yield; redraw via lsp_dirty */
				break;
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1721:m14${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'15i 		if (xb_path && xb_path[0])
			lsp_sync(xb_path, xb);
		if (lsp_dirty) {
			lsp_dirty = 0;
			vi_mod |= 1;
		}
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.c:1781:m15${ESC}${SEP}pr${INTR}${QF2}}${SEP}b7${SEP}%ya 98${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f> 	int hist_sz;			/\\* size of hist\\[] \\*/
	int hist_n;			/\\* current history head in hist\\[] \\*/
	int hist_u;			/\\* current undo head in hist\\[] \\*/
};
#define lbuf_len\\(lb\\) lb->ln_n
#define lbuf_s\\(ln\\) \\(\\(struct linfo\\*\\)\\(ln - sizeof\\(struct linfo\\)\\)\\)${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 1${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}%f> 	int hist_sz;			/\\* size of hist\\[] \\*/
	int hist_n;			/\\* current history head in hist\\[] \\*/
	int hist_u;			/\\* current undo head in hist\\[] \\*/${ESC}${SEP}3??${ESC}${SEP}${LB}
${ESC}${SEP}3??+2m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.h:152:a3${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f> 	int hist_sz;			/\\* size of hist\\[] \\*/.*?
	int hist_n;			/\\* current history head in hist\\[] \\*/.*?
(	int hist_u;			/\\* current undo head in hist\\[] \\*/)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.h:152:a7${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	int useq;			/\\* current operation sequence \\*/
	int modified;			/\\* modification state \\*/
	int saved;			/\\* save state \\*/.*(#define lbuf_i\\(lb, pos\\) \\(\\(struct linfo\\*\\)\\(lb->ln\\[pos] - sizeof\\(struct linfo\\)\\)\\))
struct lbuf \\*lbuf_make\\(void\\);
void lbuf_free\\(struct lbuf \\*lb\\);${ESC}${SEP}8??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}8??-4m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.h:152:a8${ESC}${SEP}'0${ESC}${SEP}8??${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}m 0${ESC}${SEP}1;0${ESC}${SEP}grp 1${ESC}${SEP}%f> 	int tmp_mark\\[4];		/\\* aux mark state \\*/
	int ln_n;			/\\* number of lines in ln\\[] \\*/
	int ln_sz;			/\\* size of ln\\[] \\*/.*(int lbuf_rd\\(struct lbuf \\*lb, int fd, int beg, int end\\);)
int lbuf_wr\\(struct lbuf \\*lb, int fd, int beg, int end\\);
void lbuf_edit\\(struct lbuf \\*lb, char \\*s, int beg, int end, int o1, int o2\\);${ESC}${SEP}9??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}9??-7m 1${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.h:152:a9${ESC}${SEP}'0${SEP}${LB}
${SEP}1;3;7;8;9??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.h:152${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}?${ESC}${SEP}${LB}
${ESC}${SEP}%f+ /\\* filesystem \\*/
extern rset \\*fsincl;
void dir_calc\\(char \\*path\\);${ESC}${SEP}1??${ESC}${SEP}${LB}
${ESC}${SEP}1??+2m 2${ESC}${ESC}${ESC}${SEP}1q${ESC}${SEP}${LB}
${ESC}${SEP}grp 1${ESC}${SEP}%f+ /\\* filesystem \\*/.*?
extern rset \\*fsincl;.*?
(void dir_calc\\(char \\*path\\);)${ESC}${SEP}7??${ESC}${SEP}${LB}
${ESC}${SEP}grp 0${ESC}${SEP}7??m 2${ESC}${ESC}${ESC}${SEP}${OK1}p OK vi.h:550:a7${SEP}${LB}
${SEP}1;7??!${DBG1:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.h:550${ESC}${SEP}pr${INTR}${QF1}}${SEP}${LB}
${SEP}${LB}
${SEP}'1i 	int edseq;			/* monotonic content mutation counter */
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.h:152:m1${ESC}${SEP}pr${INTR}${QF2}}${SEP}${LB}
${SEP}'2i 
/* lsp.c */
#define LSP_NFDS_MAX	8
extern int lsp_nfds;
extern int lsp_dirty;
extern int lsp_wake;
extern int lsp_fds[LSP_NFDS_MAX];
void lsp_process_fd(int fd);
void lsp_register(const char *ft, const char *cmd);
void lsp_open(const char *path, const char *ft);
void lsp_save(const char *path);
void lsp_sync(const char *path, struct lbuf *lb);
void lsp_hover(const char *path, int row, int off);
void lsp_definition(const char *path, int row, int off);
const char *lsp_diag_for_line(const char *path, int line, int *sev);
void lsp_list(void);
void lsp_show_msg(char *msg);
${SEP}??!${DBG2:-ya!112${ESC}${SEP}prp${ESC}${SEP}p FAIL vi.h:550:m2${ESC}${SEP}pr${INTR}${QF2}}${SEP}vis 2${SEP}b0${SEP}w${SEP}b1${SEP}w${SEP}b2${SEP}w${SEP}b3${SEP}w${SEP}b4${SEP}w${SEP}b5${SEP}w${SEP}b6${SEP}w${SEP}b7${SEP}w${SEP}2q" > "$P2VIF"
EXINIT='%ya 97:? %@97' $VI -e 'conf.c' 'ex.c' 'jsmn.h' 'lbuf.c' 'lsp.c' 'term.c' 'vi.c' 'vi.h' "$P2VIF"

exit 0
=== PATCH2VI DELTA ===
=== DELTA conf.c ===
=== GROUP 3 ===
-|[@&!dmj]|=\\?{0,1}|\\?{1,2}[?!]?|b[psx]?|p[uh]?|ac|e[f!]?!?|f[-+><tdp]?|inc|i|sc!?|\
+|[@&!dmj]|=\\?{0,1}|\\?{1,2}[?!]?|b[psx]?|p[uh]?|ac|e[f!]?!?|f[-+><tdp]?|inc|i|sc!?|lsp|\
=== END ===
=== LEVEL 2 ===
=== pre_ctx ===
(?:([,;]#?)[ \t]*((?:\\|(?:[^|\\\\]|\\\\.?)*\\|?[ \t]*)*(?:(?:<(?:[^<\\\\]|\\\\.?)*<?|>(?:[^>\\\\]|\\\\.?)*>?)|\
(?:'[0-9]+)|([.$]|[0-9 \t]*)?))(?:([-*-+/%])[ \t]*([0-9]+)[ \t]*)*(?:[ \t]*\\|(?:[^|\\\\]|\\\\.?)*\\|?)*[ \t]*)*)\
((pac|pr|ai|ish|err|fr|ic|grp|mpt|rr|shape|seq|ts|td|order|hl[lwpr]?|left|lim|led|vis)\
=== END ===
=== post_ctx ===
(?:g!?|s)[ \t]?(.)?|q!?|reg?\\+?|rd?|w(?:q!|[q!])?|u[czbd]|x!?|ya[!+]?|cm!?|cd?)?",
		A(BL1 | SYN_BD, RE, RE, RE, RE, WH1, MA1, RE, RE, WH1, RE, GR1, CY1, MA1)},
	{ex_ft, "\\\\(.)", A(AY1 | SYN_BD, YE)},
=== END ===
=== edit_cmd_rel ===
s/\?\|\\\\/?|lsp|\\\\/
s/\|\\$/|lsp|\\\\/
=== END ===
=== END ===
=== PATCH2VI PATCH ===
diff --git a/conf.c b/conf.c
index 70157040..2164933c 100644
--- a/conf.c
+++ b/conf.c
@@ -18,6 +18,7 @@ char vs_ft[] = "/vs";	/* vi search prompt (is never '\n' terminated) */
 char bar_ft[] = "/-";	/* status bar (is never '\n' terminated) */
 char fuzz_ft[] = "/f";	/* fuzzy search prompt (is never '\n' terminated) */
 char msg_ft[] = "/>";	/* ex message (is never '\n' terminated) */
+char lsp_ft[] = "/lsp";	/* lsp diagnostic virtual text */
 
 struct filetype fts[] = {
 	{FT(c), "\\.(c|h|cpp|hpp|cc|cs)$"},			/* C */
@@ -41,7 +42,8 @@ struct filetype fts[] = {
 	{vs_ft, NULL},
 	{bar_ft, NULL},
 	{fuzz_ft, NULL},
-	{msg_ft, NULL}
+	{msg_ft, NULL},
+	{lsp_ft, NULL}
 };
 const int ftslen = LEN(fts);
 
@@ -295,7 +297,7 @@ return|select|switch|type|var))\\>", A(GR1, BL1 | SYN_BD, YE1)},
 (?:([,;]#?)[ \t]*((?:\\|(?:[^|\\\\]|\\\\.?)*\\|?[ \t]*)*(?:(?:<(?:[^<\\\\]|\\\\.?)*<?|>(?:[^>\\\\]|\\\\.?)*>?)|\
 (?:'[0-9]+)|([.$]|[0-9 \t]*)?))(?:([-*-+/%])[ \t]*([0-9]+)[ \t]*)*(?:[ \t]*\\|(?:[^|\\\\]|\\\\.?)*\\|?)*[ \t]*)*)\
 ((pac|pr|ai|ish|err|fr|ic|grp|mpt|rr|shape|seq|ts|td|order|hl[lwpr]?|left|lim|led|vis)\
-|[@&!dmj]|=\\?{0,1}|\\?{1,2}[?!]?|b[psx]?|p[uh]?|ac|e[f!]?!?|f[-+><tdp]?|inc|i|sc!?|\
+|[@&!dmj]|=\\?{0,1}|\\?{1,2}[?!]?|b[psx]?|p[uh]?|ac|e[f!]?!?|f[-+><tdp]?|inc|i|sc!?|lsp|\
 (?:g!?|s)[ \t]?(.)?|q!?|reg?\\+?|rd?|w(?:q!|[q!])?|u[czbd]|x!?|ya[!+]?|cm!?|cd?)?",
 		A(BL1 | SYN_BD, RE, RE, RE, RE, WH1, MA1, RE, RE, WH1, RE, GR1, CY1, MA1)},
 	{ex_ft, "\\\\(.)", A(AY1 | SYN_BD, YE)},
@@ -319,6 +321,10 @@ return|select|switch|type|var))\\>", A(GR1, BL1 | SYN_BD, YE1)},
 	{fuzz_ft, NULL, A(RE1 | SYN_BD), 1, 1},
 
 	{msg_ft, ".+", A(AY1 | SYN_BD)},
+
+	{lsp_ft, "^.*error.*$", A(RE1 | SYN_IT)},
+	{lsp_ft, "^.*warning.*$", A(YE1 | SYN_IT)},
+	{lsp_ft, "^.*$", A(BL1 | SYN_IT)},
 };
 const int hlslen = LEN(hls);
 
diff --git a/ex.c b/ex.c
index 67e5e1a6..998a214c 100644
--- a/ex.c
+++ b/ex.c
@@ -431,6 +431,8 @@ static void *ec_edit(char *loc, char *cmd, char *arg)
 	if (cd == 3 || (!rd && fd >= 0)) {
 		ex_bufpostfix(ex_buf, arg[0]);
 		syn_setft(xb_ft);
+		if (*xb_path && xb_ft)
+			lsp_open(xb_path, xb_ft);
 	}
 	snprintf(msg, sizeof(msg), "\"%s\" %dL [%c]",
 			*xb_path ? xb_path : "unnamed", lbuf_len(xb),
@@ -801,6 +803,8 @@ static void *ec_write(char *loc, char *cmd, char *arg)
 		ec_setpath(NULL, NULL, path);
 	lbuf_saved(xb, 0);
 	ex_buf->mtime = mtime(path);
+	if (*xb_path)
+		lsp_save(xb_path);
 	xquit = quit;
 	return NULL;
 }
@@ -1596,6 +1600,28 @@ _EO(left,
 	return NULL;
 )
 
+static void *ec_lsp(char *loc, char *cmd, char *arg)
+{
+	char ft[32];
+	int n = 0;
+	if (!*arg) {
+		lsp_list();
+		return NULL;
+	}
+	while (arg[n] && arg[n] != ' ' && n < (int)sizeof(ft) - 1) {
+		ft[n] = arg[n];
+		n++;
+	}
+	ft[n] = '\0';
+	while (arg[n] == ' ')
+		n++;
+	if (!arg[n])
+		return "lsp: missing server command";
+	lsp_register(ft, arg + n);
+	lsp_dirty = 1;
+	return NULL;
+}
+
 #undef EO
 #define EO(opt) {#opt, eo_##opt}
 
@@ -1688,6 +1714,7 @@ static struct excmd {
 	EO(hlp),
 	EO(hlr),
 	EO(hl),
+	{"lsp", ec_lsp},
 	EO(left),
 	EO(lim),
 	EO(led),
diff --git a/jsmn.h b/jsmn.h
new file mode 100644
index 00000000..8ac14c1b
--- /dev/null
+++ b/jsmn.h
@@ -0,0 +1,471 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * 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.
+ */
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * 	o Object
+ * 	o Array
+ * 	o String
+ * 	o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+  JSMN_UNDEFINED = 0,
+  JSMN_OBJECT = 1 << 0,
+  JSMN_ARRAY = 1 << 1,
+  JSMN_STRING = 1 << 2,
+  JSMN_PRIMITIVE = 1 << 3
+} jsmntype_t;
+
+enum jsmnerr {
+  /* Not enough tokens were provided */
+  JSMN_ERROR_NOMEM = -1,
+  /* Invalid character inside JSON string */
+  JSMN_ERROR_INVAL = -2,
+  /* The string is not a full JSON packet, more bytes expected */
+  JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * type		type (object, array, string etc.)
+ * start	start position in JSON data string
+ * end		end position in JSON data string
+ */
+typedef struct jsmntok {
+  jsmntype_t type;
+  int start;
+  int end;
+  int size;
+#ifdef JSMN_PARENT_LINKS
+  int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string.
+ */
+typedef struct jsmn_parser {
+  unsigned int pos;     /* offset in the JSON string */
+  unsigned int toknext; /* next token to allocate */
+  int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each
+ * describing
+ * a single JSON object.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens) {
+  jsmntok_t *tok;
+  if (parser->toknext >= num_tokens) {
+    return NULL;
+  }
+  tok = &tokens[parser->toknext++];
+  tok->start = tok->end = -1;
+  tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+  tok->parent = -1;
+#endif
+  return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end) {
+  token->type = type;
+  token->start = start;
+  token->end = end;
+  token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens) {
+  jsmntok_t *token;
+  int start;
+
+  start = parser->pos;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+    /* In strict mode primitive must be followed by "," or "}" or "]" */
+    case ':':
+#endif
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+    case ',':
+    case ']':
+    case '}':
+      goto found;
+    default:
+                   /* to quiet a warning from gcc*/
+      break;
+    }
+    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+      parser->pos = start;
+      return JSMN_ERROR_INVAL;
+    }
+  }
+#ifdef JSMN_STRICT
+  /* In strict mode primitive must be followed by a comma/object/array */
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+#endif
+
+found:
+  if (tokens == NULL) {
+    parser->pos--;
+    return 0;
+  }
+  token = jsmn_alloc_token(parser, tokens, num_tokens);
+  if (token == NULL) {
+    parser->pos = start;
+    return JSMN_ERROR_NOMEM;
+  }
+  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+  token->parent = parser->toksuper;
+#endif
+  parser->pos--;
+  return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens) {
+  jsmntok_t *token;
+
+  int start = parser->pos;
+  
+  /* Skip starting quote */
+  parser->pos++;
+  
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c = js[parser->pos];
+
+    /* Quote: end of string */
+    if (c == '\"') {
+      if (tokens == NULL) {
+        return 0;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+      }
+      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+      token->parent = parser->toksuper;
+#endif
+      return 0;
+    }
+
+    /* Backslash: Quoted symbol expected */
+    if (c == '\\' && parser->pos + 1 < len) {
+      int i;
+      parser->pos++;
+      switch (js[parser->pos]) {
+      /* Allowed escaped symbols */
+      case '\"':
+      case '/':
+      case '\\':
+      case 'b':
+      case 'f':
+      case 'r':
+      case 'n':
+      case 't':
+        break;
+      /* Allows escaped symbol \uXXXX */
+      case 'u':
+        parser->pos++;
+        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
+             i++) {
+          /* If it isn't a hex character we have an error */
+          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
+                (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
+                (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+          }
+          parser->pos++;
+        }
+        parser->pos--;
+        break;
+      /* Unexpected symbol */
+      default:
+        parser->pos = start;
+        return JSMN_ERROR_INVAL;
+      }
+    }
+  }
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens) {
+  int r;
+  int i;
+  jsmntok_t *token;
+  int count = parser->toknext;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c;
+    jsmntype_t type;
+
+    c = js[parser->pos];
+    switch (c) {
+    case '{':
+    case '[':
+      count++;
+      if (tokens == NULL) {
+        break;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        return JSMN_ERROR_NOMEM;
+      }
+      if (parser->toksuper != -1) {
+        jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+        /* In strict mode an object or array can't become a key */
+        if (t->type == JSMN_OBJECT) {
+          return JSMN_ERROR_INVAL;
+        }
+#endif
+        t->size++;
+#ifdef JSMN_PARENT_LINKS
+        token->parent = parser->toksuper;
+#endif
+      }
+      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+      token->start = parser->pos;
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case '}':
+    case ']':
+      if (tokens == NULL) {
+        break;
+      }
+      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+      if (parser->toknext < 1) {
+        return JSMN_ERROR_INVAL;
+      }
+      token = &tokens[parser->toknext - 1];
+      for (;;) {
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          token->end = parser->pos + 1;
+          parser->toksuper = token->parent;
+          break;
+        }
+        if (token->parent == -1) {
+          if (token->type != type || parser->toksuper == -1) {
+            return JSMN_ERROR_INVAL;
+          }
+          break;
+        }
+        token = &tokens[token->parent];
+      }
+#else
+      for (i = parser->toknext - 1; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          parser->toksuper = -1;
+          token->end = parser->pos + 1;
+          break;
+        }
+      }
+      /* Error if unmatched closing bracket */
+      if (i == -1) {
+        return JSMN_ERROR_INVAL;
+      }
+      for (; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          parser->toksuper = i;
+          break;
+        }
+      }
+#endif
+      break;
+    case '\"':
+      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+      break;
+    case ':':
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case ',':
+      if (tokens != NULL && parser->toksuper != -1 &&
+          tokens[parser->toksuper].type != JSMN_ARRAY &&
+          tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+        parser->toksuper = tokens[parser->toksuper].parent;
+#else
+        for (i = parser->toknext - 1; i >= 0; i--) {
+          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+            if (tokens[i].start != -1 && tokens[i].end == -1) {
+              parser->toksuper = i;
+              break;
+            }
+          }
+        }
+#endif
+      }
+      break;
+#ifdef JSMN_STRICT
+    /* In strict mode primitives are: numbers and booleans */
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case 't':
+    case 'f':
+    case 'n':
+      /* And they must not be keys of the object */
+      if (tokens != NULL && parser->toksuper != -1) {
+        const jsmntok_t *t = &tokens[parser->toksuper];
+        if (t->type == JSMN_OBJECT ||
+            (t->type == JSMN_STRING && t->size != 0)) {
+          return JSMN_ERROR_INVAL;
+        }
+      }
+#else
+    /* In non-strict mode every unquoted value is a primitive */
+    default:
+#endif
+      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+
+#ifdef JSMN_STRICT
+    /* Unexpected char in strict mode */
+    default:
+      return JSMN_ERROR_INVAL;
+#endif
+    }
+  }
+
+  if (tokens != NULL) {
+    for (i = parser->toknext - 1; i >= 0; i--) {
+      /* Unmatched opened object or array */
+      if (tokens[i].start != -1 && tokens[i].end == -1) {
+        return JSMN_ERROR_PART;
+      }
+    }
+  }
+
+  return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser) {
+  parser->pos = 0;
+  parser->toknext = 0;
+  parser->toksuper = -1;
+}
+
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
diff --git a/lbuf.c b/lbuf.c
index 75dd0ce8..1460c2d5 100644
--- a/lbuf.c
+++ b/lbuf.c
@@ -127,6 +127,7 @@ static int lbuf_replace(struct lbuf *lb, sbuf *sb, char *s, struct lopt *lo, int
 			(lb->ln_n - pos - n_del) * sizeof(lb->ln[0]));
 	}
 	lb->ln_n += n_ins - n_del;
+	lb->edseq++;
 	for (i = 0; i < n_ins; i++)
 		lb->ln[pos + i] = *((char**)sb->s + i);
 	for (i = 0; i < lb->mark_n; i++) {	/* updating marks */
diff --git a/lsp.c b/lsp.c
new file mode 100644
index 00000000..6e0e378b
--- /dev/null
+++ b/lsp.c
@@ -0,0 +1,923 @@
+/* lsp.c - Language Server Protocol client for nextvi */
+#include "jsmn.h"
+#include <errno.h>
+
+#define LSP_DIAG_MAX	64
+#define LSP_SRV_MAX	8
+#define LSP_RBUF_MAX	65536
+#define LSP_DOCS_MAX	16
+
+typedef struct {
+	int line, col, severity;
+	char msg[256];
+} lsp_diag;
+
+typedef struct {
+	char path[1024];
+	lsp_diag *d;
+	int n, cap;
+} lsp_diagfile;
+
+typedef struct {
+	char path[1024];
+	int version;
+	int seq;		/* lbuf edseq at last sync */
+} lsp_doc;
+
+typedef struct {
+	char ft[32], cmd[256];
+	int pid, in_fd, out_fd;
+	char rbuf[LSP_RBUF_MAX];
+	int rbuf_n;
+	int response_ready, pending_id;
+	char *response_json;
+	int initialized, next_id;
+	lsp_doc docs[LSP_DOCS_MAX];
+	int ndocs;
+} lsp_server;
+
+int lsp_nfds = 0;
+int lsp_fds[LSP_NFDS_MAX];
+int lsp_dirty = 0;		/* diagnostics changed; main loop must redraw */
+int lsp_wake = 0;		/* term_read may return 0 to report lsp_dirty */
+
+static lsp_server lsp_srvs[LSP_SRV_MAX];
+static int lsp_nsrvs = 0;
+static lsp_diagfile lsp_diagfiles[LSP_SRV_MAX * 4];
+static int lsp_ndiagfiles = 0;
+
+static void lsp_open_lb(const char *path, const char *ft, struct lbuf *lb);
+
+void lsp_register(const char *ft, const char *cmd)
+{
+	int i;
+	for (i = 0; i < lsp_nsrvs; i++) {
+		if (!strcmp(lsp_srvs[i].ft, ft)) {
+			snprintf(lsp_srvs[i].cmd, sizeof(lsp_srvs[i].cmd), "%s", cmd);
+			return;
+		}
+	}
+	if (lsp_nsrvs >= LSP_SRV_MAX)
+		return;
+	snprintf(lsp_srvs[lsp_nsrvs].ft, sizeof(lsp_srvs[0].ft), "%s", ft);
+	snprintf(lsp_srvs[lsp_nsrvs].cmd, sizeof(lsp_srvs[0].cmd), "%s", cmd);
+	lsp_srvs[lsp_nsrvs].pid = -1;
+	lsp_srvs[lsp_nsrvs].in_fd = -1;
+	lsp_srvs[lsp_nsrvs].out_fd = -1;
+	lsp_nsrvs++;
+	/* open every already-loaded buffer of this filetype */
+	for (i = 0; i < xbufcur; i++)
+		if (bufs[i].ft && bufs[i].path[0] && !strcmp(bufs[i].ft, ft))
+			lsp_open_lb(bufs[i].path, bufs[i].ft, bufs[i].lb);
+}
+
+void lsp_list(void)
+{
+	int i;
+	char buf[512];
+	for (i = 0; i < lsp_nsrvs; i++) {
+		snprintf(buf, sizeof(buf), "lsp %.31s %.255s [%s]",
+			lsp_srvs[i].ft, lsp_srvs[i].cmd,
+			lsp_srvs[i].pid > 0 ? "running" : "stopped");
+		ex_print(buf, bar_ft)
+	}
+	if (!lsp_nsrvs)
+		ex_print("lsp: no servers registered", bar_ft)
+}
+
+static lsp_server *lsp_srv_for_ft(const char *ft)
+{
+	int i;
+	for (i = 0; i < lsp_nsrvs; i++)
+		if (!strcmp(lsp_srvs[i].ft, ft))
+			return &lsp_srvs[i];
+	return NULL;
+}
+
+static int lsp_doc_find(lsp_server *srv, const char *path)
+{
+	int i;
+	for (i = 0; i < srv->ndocs; i++)
+		if (!strcmp(srv->docs[i].path, path))
+			return i;
+	return -1;
+}
+
+static lsp_server *lsp_srv_for_path(const char *path)
+{
+	int i;
+	for (i = 0; i < lsp_nsrvs; i++)
+		if (lsp_srvs[i].pid > 0 && lsp_doc_find(&lsp_srvs[i], path) >= 0)
+			return &lsp_srvs[i];
+	return NULL;
+}
+
+static lsp_diagfile *lsp_diagfile_for_path(const char *path)
+{
+	int i;
+	for (i = 0; i < lsp_ndiagfiles; i++)
+		if (!strcmp(lsp_diagfiles[i].path, path))
+			return &lsp_diagfiles[i];
+	if (lsp_ndiagfiles >= LSP_SRV_MAX * 4)
+		return NULL;
+	snprintf(lsp_diagfiles[lsp_ndiagfiles].path,
+		sizeof(lsp_diagfiles[0].path), "%s", path);
+	lsp_diagfiles[lsp_ndiagfiles].d = NULL;
+	lsp_diagfiles[lsp_ndiagfiles].n = 0;
+	lsp_diagfiles[lsp_ndiagfiles].cap = 0;
+	return &lsp_diagfiles[lsp_ndiagfiles++];
+}
+
+static lsp_server *lsp_find_srv_for_fd(int fd)
+{
+	int i;
+	for (i = 0; i < lsp_nsrvs; i++)
+		if (lsp_srvs[i].out_fd == fd)
+			return &lsp_srvs[i];
+	return NULL;
+}
+
+static void lsp_json_escape(const char *src, sbuf *sb)
+{
+	while (*src) {
+		unsigned char c = (unsigned char)*src;
+		if (c == '"') {
+			sbuf_mem(sb, "\\\"", 2)
+		} else if (c == '\\') {
+			sbuf_mem(sb, "\\\\", 2)
+		} else if (c == '\n') {
+			sbuf_mem(sb, "\\n", 2)
+		} else if (c == '\r') {
+			sbuf_mem(sb, "\\r", 2)
+		} else if (c == '\t') {
+			sbuf_mem(sb, "\\t", 2)
+		} else if (c < 0x20) {
+			char esc[7];
+			snprintf(esc, sizeof(esc), "\\u%04x", c);
+			sbuf_mem(sb, esc, 6)
+		} else {
+			sbuf_chr(sb, *src)
+		}
+		src++;
+	}
+}
+
+static void lsp_uri_from_path(const char *path, char *out, int n)
+{
+	if (path[0] == '/') {
+		snprintf(out, n, "file://%s", path);
+	} else {
+		char cwd[1024];
+		if (getcwd(cwd, sizeof(cwd)))
+			snprintf(out, n, "file://%s/%s", cwd, path);
+		else
+			snprintf(out, n, "file://%s", path);
+	}
+}
+
+static void lsp_path_from_uri(const char *uri, char *out, int n)
+{
+	if (!strncmp(uri, "file://", 7))
+		snprintf(out, n, "%.*s", n - 1, uri + 7);
+	else
+		snprintf(out, n, "%.*s", n - 1, uri);
+}
+
+/* strip the cwd prefix so the path matches an already-open buffer */
+static void lsp_relpath(char *path)
+{
+	char cwd[1024];
+	int n;
+	if (path[0] != '/' || !getcwd(cwd, sizeof(cwd)))
+		return;
+	n = strlen(cwd);
+	if (!strncmp(path, cwd, n) && path[n] == '/')
+		memmove(path, path + n + 1, strlen(path + n + 1) + 1);
+}
+
+static void lsp_print_hover(const char *s)
+{
+	char line[512];
+	int i = 0;
+	while (*s) {
+		if (s[0] == '\\' && s[1]) {
+			char c;
+			switch (s[1]) {
+			case 'n':  c = '\n'; break;
+			case 'r':  c = '\r'; break;
+			case 't':  c = '\t'; break;
+			case '\\': c = '\\'; break;
+			case '"':  c = '"';  break;
+			default:   c = s[1]; break;
+			}
+			s += 2;
+			if (c == '\n' || c == '\r') {
+				line[i] = '\0';
+				if (i > 0)
+					ex_print(line, bar_ft)
+				i = 0;
+			} else if (i < (int)sizeof(line) - 1) {
+				line[i++] = c;
+			}
+		} else {
+			if (i < (int)sizeof(line) - 1)
+				line[i++] = *s;
+			s++;
+		}
+	}
+	if (i > 0) {
+		line[i] = '\0';
+		ex_print(line, bar_ft)
+	}
+}
+
+static void lsp_send(lsp_server *srv, const char *json, int len)
+{
+	char hdr[64];
+	int hlen = snprintf(hdr, sizeof(hdr),
+		"Content-Length: %d\r\n\r\n", len);
+	write(srv->in_fd, hdr, hlen);
+	write(srv->in_fd, json, len);
+}
+
+static void lsp_send_sb(lsp_server *srv, sbuf *sb)
+{
+	sbuf_null(sb)
+	lsp_send(srv, sb->s, sb->s_n);
+}
+
+static void lsp_sbuf_int(sbuf *sb, int n)
+{
+	char s[32];
+	itoa(n, s);
+	sbuf_str(sb, s)
+}
+
+static void lsp_buf_content(struct lbuf *lb, sbuf *sb)
+{
+	int i;
+	for (i = 0; i < lbuf_len(lb); i++) {
+		char *ln = lbuf_get(lb, i);
+		lsp_json_escape(ln, sb);
+	}
+}
+
+static int lsp_fmt_init(lsp_server *srv, sbuf *sb)
+{
+	int id = ++srv->next_id;
+	sbuf_str(sb, "{\"jsonrpc\":\"2.0\",\"id\":")
+	lsp_sbuf_int(sb, id);
+	sbuf_str(sb, ",\"method\":\"initialize\",\"params\":{\"processId\":")
+	lsp_sbuf_int(sb, getpid());
+	sbuf_str(sb, ",\"clientInfo\":{\"name\":\"nextvi\"},\"capabilities\":{")
+	sbuf_str(sb, "\"textDocument\":{\"synchronization\":{\"didSave\":true},")
+	sbuf_str(sb, "\"hover\":{\"contentFormat\":[\"plaintext\",\"markdown\"]},")
+	sbuf_str(sb, "\"definition\":{}},")
+	sbuf_str(sb, "\"general\":{\"positionEncodings\":[\"utf-8\"]}},")
+	sbuf_str(sb, "\"rootUri\":null}}")
+	return id;
+}
+
+static void lsp_fmt_initialized(sbuf *sb)
+{
+	sbuf_str(sb, "{\"jsonrpc\":\"2.0\",\"method\":\"initialized\",\"params\":{}}")
+}
+
+static void lsp_fmt_didopen(sbuf *sb, const char *path, const char *ft,
+		struct lbuf *lb)
+{
+	char uri[1280];
+	lsp_uri_from_path(path, uri, sizeof(uri));
+	sbuf_str(sb, "{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",")
+	sbuf_str(sb, "\"params\":{\"textDocument\":{\"uri\":\"")
+	sbuf_str(sb, uri)
+	sbuf_str(sb, "\",\"languageId\":\"")
+	sbuf_str(sb, ft)
+	sbuf_str(sb, "\",\"version\":1,\"text\":\"")
+	lsp_buf_content(lb, sb);
+	sbuf_str(sb, "\"}}}")
+}
+
+static void lsp_fmt_didchange(sbuf *sb, const char *path, int ver,
+		struct lbuf *lb)
+{
+	char uri[1280];
+	lsp_uri_from_path(path, uri, sizeof(uri));
+	sbuf_str(sb, "{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",")
+	sbuf_str(sb, "\"params\":{\"textDocument\":{\"uri\":\"")
+	sbuf_str(sb, uri)
+	sbuf_str(sb, "\",\"version\":")
+	lsp_sbuf_int(sb, ver);
+	sbuf_str(sb, "},\"contentChanges\":[{\"text\":\"")
+	lsp_buf_content(lb, sb);
+	sbuf_str(sb, "\"}]}}")
+}
+
+static void lsp_fmt_didsave(sbuf *sb, const char *path)
+{
+	char uri[1280];
+	lsp_uri_from_path(path, uri, sizeof(uri));
+	sbuf_str(sb, "{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didSave\",")
+	sbuf_str(sb, "\"params\":{\"textDocument\":{\"uri\":\"")
+	sbuf_str(sb, uri)
+	sbuf_str(sb, "\"}}}")
+}
+
+/* format a textDocument/<method> request that carries a cursor position */
+static int lsp_fmt_pos(lsp_server *srv, sbuf *sb, const char *method,
+		const char *path, int line, int col)
+{
+	char uri[1280];
+	int id = ++srv->next_id;
+	lsp_uri_from_path(path, uri, sizeof(uri));
+	sbuf_str(sb, "{\"jsonrpc\":\"2.0\",\"id\":")
+	lsp_sbuf_int(sb, id);
+	sbuf_str(sb, ",\"method\":\"textDocument/")
+	sbuf_str(sb, method)
+	sbuf_str(sb, "\",\"params\":{\"textDocument\":{\"uri\":\"")
+	sbuf_str(sb, uri)
+	sbuf_str(sb, "\"},\"position\":{\"line\":")
+	lsp_sbuf_int(sb, line);
+	sbuf_str(sb, ",\"character\":")
+	lsp_sbuf_int(sb, col);
+	sbuf_str(sb, "}}}")
+	return id;
+}
+
+/* jsmn helpers */
+
+static int lsp_tok_eq(const char *json, jsmntok_t *tok, const char *s)
+{
+	int len = tok->end - tok->start;
+	return tok->type == JSMN_STRING &&
+		(int)strlen(s) == len &&
+		!strncmp(json + tok->start, s, len);
+}
+
+static void lsp_tok_str(const char *json, jsmntok_t *tok, char *out, int n)
+{
+	int len = tok->end - tok->start;
+	if (len >= n)
+		len = n - 1;
+	memcpy(out, json + tok->start, len);
+	out[len] = '\0';
+}
+
+static int lsp_tok_int(const char *json, jsmntok_t *tok)
+{
+	char buf[32];
+	lsp_tok_str(json, tok, buf, sizeof(buf));
+	return atoi(buf);
+}
+
+/* skip token at index i and all its children; returns next index */
+static int lsp_skip(jsmntok_t *toks, int n, int i)
+{
+	int end, j;
+	if (i >= n)
+		return n;
+	if (toks[i].type == JSMN_OBJECT || toks[i].type == JSMN_ARRAY) {
+		end = i + 1;
+		for (j = 0; j < toks[i].size; j++) {
+			if (toks[i].type == JSMN_OBJECT)
+				end = lsp_skip(toks, n, end); /* key */
+			end = lsp_skip(toks, n, end); /* value */
+		}
+		return end;
+	}
+	return i + 1;
+}
+
+/* find value token index for key in object at toks[obj] */
+static int lsp_find_key(const char *json, jsmntok_t *toks, int n,
+		int obj, const char *key)
+{
+	int i, j;
+	if (obj >= n || toks[obj].type != JSMN_OBJECT)
+		return -1;
+	i = obj + 1;
+	for (j = 0; j < toks[obj].size; j++) {
+		if (i >= n)
+			break;
+		int val = i + 1;
+		if (lsp_tok_eq(json, &toks[i], key))
+			return val < n ? val : -1;
+		i = lsp_skip(toks, n, val);
+	}
+	return -1;
+}
+
+/* collapse runs of escaped newlines (\n / \r) in place into a single space */
+static void lsp_collapse_nl(char *s)
+{
+	char *w = s, *start = s;
+	while (*s) {
+		if (s[0] == '\\' && (s[1] == 'n' || s[1] == 'r')) {
+			while (s[0] == '\\' && (s[1] == 'n' || s[1] == 'r'))
+				s += 2;
+			if (w != start && *s)	/* no leading/trailing space */
+				*w++ = ' ';
+		} else {
+			*w++ = *s++;
+		}
+	}
+	*w = '\0';
+}
+
+static void lsp_handle_diagnostics(lsp_server *srv, const char *json,
+		jsmntok_t *toks, int n)
+{
+	int params, uri_tok, diags_tok, i, di;
+	char path[1024];
+	lsp_diagfile *df;
+
+	params = lsp_find_key(json, toks, n, 0, "params");
+	if (params < 0)
+		return;
+	uri_tok = lsp_find_key(json, toks, n, params, "uri");
+	if (uri_tok < 0)
+		return;
+	{
+		char uri[1280];
+		lsp_tok_str(json, &toks[uri_tok], uri, sizeof(uri));
+		lsp_path_from_uri(uri, path, sizeof(path));
+		lsp_relpath(path);
+	}
+	diags_tok = lsp_find_key(json, toks, n, params, "diagnostics");
+	if (diags_tok < 0 || toks[diags_tok].type != JSMN_ARRAY)
+		return;
+	df = lsp_diagfile_for_path(path);
+	if (!df)
+		return;
+	df->n = 0;
+	/* iterate diagnostics array */
+	i = diags_tok + 1;
+	for (di = 0; di < toks[diags_tok].size && i < n; di++) {
+		if (toks[i].type != JSMN_OBJECT) {
+			i = lsp_skip(toks, n, i);
+			continue;
+		}
+		int diag_obj = i;
+		int range_tok = lsp_find_key(json, toks, n, diag_obj, "range");
+		int sev_tok = lsp_find_key(json, toks, n, diag_obj, "severity");
+		int msg_tok = lsp_find_key(json, toks, n, diag_obj, "message");
+		int line = 0, col = 0, sev = 1;
+		if (range_tok >= 0) {
+			int start_tok = lsp_find_key(json, toks, n, range_tok, "start");
+			if (start_tok >= 0) {
+				int lt = lsp_find_key(json, toks, n, start_tok, "line");
+				int ct = lsp_find_key(json, toks, n, start_tok, "character");
+				if (lt >= 0)
+					line = lsp_tok_int(json, &toks[lt]);
+				if (ct >= 0)
+					col = lsp_tok_int(json, &toks[ct]);
+			}
+		}
+		if (sev_tok >= 0)
+			sev = lsp_tok_int(json, &toks[sev_tok]);
+		if (df->n >= df->cap) {
+			df->cap = df->cap ? df->cap * 2 : 8;
+			if (df->cap > LSP_DIAG_MAX)
+				df->cap = LSP_DIAG_MAX;
+			df->d = erealloc(df->d, df->cap * sizeof(lsp_diag));
+		}
+		if (df->n < LSP_DIAG_MAX) {
+			df->d[df->n].line = line;
+			df->d[df->n].col = col;
+			df->d[df->n].severity = sev;
+			if (msg_tok >= 0) {
+				lsp_tok_str(json, &toks[msg_tok],
+					df->d[df->n].msg, sizeof(df->d[0].msg));
+				lsp_collapse_nl(df->d[df->n].msg);
+			} else
+				df->d[df->n].msg[0] = '\0';
+			df->n++;
+		}
+		i = lsp_skip(toks, n, diag_obj);
+	}
+	lsp_dirty = 1;
+	(void)srv;
+}
+
+static void lsp_handle_notification(lsp_server *srv, const char *json,
+		jsmntok_t *toks, int n, const char *method)
+{
+	if (!strcmp(method, "textDocument/publishDiagnostics"))
+		lsp_handle_diagnostics(srv, json, toks, n);
+}
+
+static void lsp_handle_response(lsp_server *srv, const char *json, int id)
+{
+	if (srv->pending_id == id) {
+		free(srv->response_json);
+		srv->response_json = uc_dup(json);
+		srv->response_ready = 1;
+		srv->pending_id = 0;
+	}
+}
+
+static void lsp_handle_message(lsp_server *srv, char *json, int len)
+{
+	jsmn_parser p;
+	jsmntok_t toks[512];
+	int n;
+	char method[128] = "";
+	int id = -1;
+
+	jsmn_init(&p);
+	n = jsmn_parse(&p, json, len, toks, 512);
+	if (n < 1 || toks[0].type != JSMN_OBJECT)
+		return;
+
+	/* extract method */
+	int mtok = lsp_find_key(json, toks, n, 0, "method");
+	if (mtok >= 0 && toks[mtok].type == JSMN_STRING)
+		lsp_tok_str(json, &toks[mtok], method, sizeof(method));
+
+	/* extract id */
+	int itok = lsp_find_key(json, toks, n, 0, "id");
+	if (itok >= 0)
+		id = lsp_tok_int(json, &toks[itok]);
+
+	/* check if initialized response */
+	if (id >= 0 && !method[0]) {
+		if (!srv->initialized && id == 1) {
+			/* this is the initialize response */
+			sbuf_smake(sb2, 128)
+			lsp_fmt_initialized(sb2);
+			lsp_send_sb(srv, sb2);
+			free(sb2->s);
+			srv->initialized = 1;
+		}
+		lsp_handle_response(srv, json, id);
+	} else if (method[0]) {
+		lsp_handle_notification(srv, json, toks, n, method);
+	}
+}
+
+static void lsp_dispatch_messages(lsp_server *srv)
+{
+	while (1) {
+		/* find \r\n\r\n */
+		char *hdr_end = NULL;
+		int i;
+		for (i = 0; i + 3 < srv->rbuf_n; i++) {
+			if (srv->rbuf[i] == '\r' && srv->rbuf[i+1] == '\n' &&
+					srv->rbuf[i+2] == '\r' && srv->rbuf[i+3] == '\n') {
+				hdr_end = srv->rbuf + i + 4;
+				break;
+			}
+		}
+		if (!hdr_end)
+			break;
+
+		/* parse Content-Length */
+		int clen = 0;
+		char *cl = strstr(srv->rbuf, "Content-Length:");
+		if (!cl || cl >= hdr_end)
+			break;
+		cl += 15;
+		while (*cl == ' ')
+			cl++;
+		clen = atoi(cl);
+		if (clen <= 0)
+			break;
+
+		int hdr_len = hdr_end - srv->rbuf;
+		int total = hdr_len + clen;
+		if (srv->rbuf_n < total)
+			break; /* wait for more data */
+		if (total >= LSP_RBUF_MAX)
+			break; /* message too large */
+
+		/* process message */
+		hdr_end[clen] = '\0';
+		lsp_handle_message(srv, hdr_end, clen);
+
+		/* shift buffer */
+		srv->rbuf_n -= total;
+		memmove(srv->rbuf, srv->rbuf + total, srv->rbuf_n);
+	}
+}
+
+void lsp_process_fd(int fd)
+{
+	lsp_server *srv = lsp_find_srv_for_fd(fd);
+	if (!srv)
+		return;
+	int space = LSP_RBUF_MAX - srv->rbuf_n - 1;
+	if (space <= 0)
+		return;
+	int r = read(fd, srv->rbuf + srv->rbuf_n, space);
+	if (r > 0) {
+		srv->rbuf_n += r;
+		lsp_dispatch_messages(srv);
+	}
+}
+
+static int lsp_wait_response(lsp_server *srv, int id, int ms)
+{
+	int elapsed = 0;
+	srv->pending_id = id;
+	srv->response_ready = 0;
+	while (elapsed < ms) {
+		struct pollfd pfd;
+		pfd.fd = srv->out_fd;
+		pfd.events = POLLIN;
+		int r = poll(&pfd, 1, 10);
+		if (r > 0 && (pfd.revents & POLLIN))
+			lsp_process_fd(srv->out_fd);
+		if (srv->response_ready)
+			return 1;
+		elapsed += 10;
+	}
+	srv->pending_id = 0;
+	return 0;
+}
+
+static int lsp_srv_ensure(lsp_server *srv)
+{
+	if (srv->pid > 0)
+		return 1;
+	char cmd_redir[sizeof(srv->cmd) + 16];
+	snprintf(cmd_redir, sizeof(cmd_redir), "%s 2>/dev/null", srv->cmd);
+	char *argv[] = {"/bin/sh", "-c", cmd_redir, NULL};
+	srv->pid = cmd_make(argv, &srv->in_fd, &srv->out_fd);
+	if (srv->pid <= 0) {
+		srv->pid = -1;
+		return 0;
+	}
+	fcntl(srv->out_fd, F_SETFL,
+		fcntl(srv->out_fd, F_GETFL, 0) | O_NONBLOCK);
+	/* register fd */
+	if (lsp_nfds < LSP_NFDS_MAX)
+		lsp_fds[lsp_nfds++] = srv->out_fd;
+	srv->rbuf_n = 0;
+	srv->initialized = 0;
+	srv->next_id = 0;
+	srv->response_ready = 0;
+	srv->pending_id = 0;
+	free(srv->response_json);
+	srv->response_json = NULL;
+	/* send initialize */
+	sbuf_smake(sb, 512)
+	int id = lsp_fmt_init(srv, sb);
+	lsp_send_sb(srv, sb);
+	free(sb->s);
+	/* wait for initialized response */
+	lsp_wait_response(srv, id, 5000);
+	return srv->initialized;
+}
+
+static void lsp_open_lb(const char *path, const char *ft, struct lbuf *lb)
+{
+	lsp_server *srv = lsp_srv_for_ft(ft);
+	if (!srv || !lb)
+		return;
+	if (!lsp_srv_ensure(srv) || !srv->initialized)
+		return;
+	int di = lsp_doc_find(srv, path);
+	if (di >= 0) {
+		sbuf_smake(sb, 4096)
+		lsp_fmt_didchange(sb, path, ++srv->docs[di].version, lb);
+		lsp_send_sb(srv, sb);
+		free(sb->s);
+	} else {
+		if (srv->ndocs >= LSP_DOCS_MAX)
+			return;
+		snprintf(srv->docs[srv->ndocs].path,
+			sizeof(srv->docs[0].path), "%s", path);
+		srv->docs[srv->ndocs].version = 1;
+		srv->docs[srv->ndocs].seq = lb->edseq;
+		srv->ndocs++;
+		sbuf_smake(sb, 4096)
+		lsp_fmt_didopen(sb, path, ft, lb);
+		lsp_send_sb(srv, sb);
+		free(sb->s);
+	}
+}
+
+void lsp_open(const char *path, const char *ft)
+{
+	lsp_open_lb(path, ft, ex_buf ? ex_buf->lb : NULL);
+}
+
+/* push buffer edits to the servers when the undo head changed (live diagnostics) */
+void lsp_sync(const char *path, struct lbuf *lb)
+{
+	int i, di;
+	if (!lb)
+		return;
+	for (i = 0; i < lsp_nsrvs; i++) {
+		lsp_server *srv = &lsp_srvs[i];
+		if (srv->pid <= 0 || (di = lsp_doc_find(srv, path)) < 0)
+			continue;
+		if (srv->docs[di].seq == lb->edseq)
+			continue;
+		srv->docs[di].seq = lb->edseq;
+		sbuf_smake(sb, 4096)
+		lsp_fmt_didchange(sb, path, ++srv->docs[di].version, lb);
+		lsp_send_sb(srv, sb);
+		free(sb->s);
+	}
+}
+
+void lsp_save(const char *path)
+{
+	int i, di;
+	for (i = 0; i < lsp_nsrvs; i++) {
+		lsp_server *srv = &lsp_srvs[i];
+		if (srv->pid <= 0 || (di = lsp_doc_find(srv, path)) < 0)
+			continue;
+		/* clangd lints its in-memory copy, so resync it before saving */
+		if (ex_buf && ex_buf->lb) {
+			srv->docs[di].seq = ex_buf->lb->edseq;
+			sbuf_smake(sb, 4096)
+			lsp_fmt_didchange(sb, path, ++srv->docs[di].version, ex_buf->lb);
+			lsp_send_sb(srv, sb);
+			free(sb->s);
+		}
+		sbuf_smake(sb, 256)
+		lsp_fmt_didsave(sb, path);
+		lsp_send_sb(srv, sb);
+		free(sb->s);
+	}
+}
+
+/* compute byte offset for row,off in lbuf (UTF-8 byte position in line) */
+static int lsp_byte_offset(struct lbuf *lb, int row, int off)
+{
+	if (row < 0 || row >= lbuf_len(lb))
+		return 0;
+	char *ln = lbuf_get(lb, row);
+	if (!ln)
+		return 0;
+	/* off is a visual column index; uc_off converts it to byte offset */
+	return uc_off(ln, off);
+}
+
+/* send a position request and parse the reply; report and return -1 on error,
+ * else return the "result" token index and fill json/toks/n for the caller */
+static int lsp_request(const char *method, const char *path, int row, int off,
+		char **json_out, jsmntok_t *toks, int ntoks, int *n_out)
+{
+	char buf[64];
+	lsp_server *srv = lsp_srv_for_path(path);
+	if (!srv) {
+		lsp_show_msg("lsp: no server for file");
+		return -1;
+	}
+	int col = lsp_byte_offset(ex_buf ? ex_buf->lb : NULL, row, off);
+	sbuf_smake(sb, 256)
+	int id = lsp_fmt_pos(srv, sb, method, path, row, col);
+	lsp_send_sb(srv, sb);
+	free(sb->s);
+	if (!lsp_wait_response(srv, id, 2000) || !srv->response_json) {
+		snprintf(buf, sizeof(buf), "lsp: %s timeout", method);
+		lsp_show_msg(buf);
+		return -1;
+	}
+	char *json = srv->response_json;
+	jsmn_parser p;
+	jsmn_init(&p);
+	int n = jsmn_parse(&p, json, strlen(json), toks, ntoks);
+	if (n < 1) {
+		snprintf(buf, sizeof(buf), "lsp: %s parse error", method);
+		lsp_show_msg(buf);
+		return -1;
+	}
+	int errtok = lsp_find_key(json, toks, n, 0, "error");
+	if (errtok >= 0) {
+		char errmsg[256] = "lsp: error";
+		int msgtok = lsp_find_key(json, toks, n, errtok, "message");
+		if (msgtok >= 0)
+			lsp_tok_str(json, &toks[msgtok], errmsg, sizeof(errmsg));
+		lsp_show_msg(errmsg);
+		return -1;
+	}
+	int result = lsp_find_key(json, toks, n, 0, "result");
+	if (result < 0 || toks[result].type == JSMN_PRIMITIVE) {
+		snprintf(buf, sizeof(buf), "lsp: no %s result", method);
+		lsp_show_msg(buf);
+		return -1;
+	}
+	*json_out = json;
+	*n_out = n;
+	return result;
+}
+
+void lsp_hover(const char *path, int row, int off)
+{
+	char *json;
+	jsmntok_t toks[1024];
+	int n, result = lsp_request("hover", path, row, off,
+		&json, toks, 1024, &n);
+	if (result < 0)
+		return;
+	int contents = lsp_find_key(json, toks, n, result, "contents");
+	if (contents < 0) {
+		lsp_show_msg("lsp: no hover contents");
+		return;
+	}
+	char msg[4096] = "";
+	if (toks[contents].type == JSMN_STRING) {
+		lsp_tok_str(json, &toks[contents], msg, sizeof(msg));
+	} else if (toks[contents].type == JSMN_OBJECT) {
+		int val = lsp_find_key(json, toks, n, contents, "value");
+		if (val >= 0)
+			lsp_tok_str(json, &toks[val], msg, sizeof(msg));
+	} else if (toks[contents].type == JSMN_ARRAY && toks[contents].size > 0) {
+		int first = contents + 1;
+		if (toks[first].type == JSMN_STRING) {
+			lsp_tok_str(json, &toks[first], msg, sizeof(msg));
+		} else if (toks[first].type == JSMN_OBJECT) {
+			int val = lsp_find_key(json, toks, n, first, "value");
+			if (val >= 0)
+				lsp_tok_str(json, &toks[val], msg, sizeof(msg));
+		}
+	}
+	if (msg[0])
+		lsp_print_hover(msg);
+	else
+		lsp_show_msg("lsp: empty hover");
+}
+
+void lsp_definition(const char *path, int row, int off)
+{
+	char *json;
+	jsmntok_t toks[1024];
+	int n, result = lsp_request("definition", path, row, off,
+		&json, toks, 1024, &n);
+	if (result < 0)
+		return;
+	char uri[1280] = "";
+	int target_line = 0;
+	/* result can be object (Location), array of Location/LocationLink */
+	int obj = result;
+	if (toks[result].type == JSMN_ARRAY) {
+		if (toks[result].size == 0) {
+			lsp_show_msg("lsp: definition not found");
+			return;
+		}
+		obj = result + 1;
+	}
+	if (toks[obj].type == JSMN_OBJECT) {
+		/* try Location: uri + range */
+		int uri_tok = lsp_find_key(json, toks, n, obj, "uri");
+		if (uri_tok < 0) /* try LocationLink: targetUri + targetRange */
+			uri_tok = lsp_find_key(json, toks, n, obj, "targetUri");
+		if (uri_tok >= 0)
+			lsp_tok_str(json, &toks[uri_tok], uri, sizeof(uri));
+		int range_tok = lsp_find_key(json, toks, n, obj, "range");
+		if (range_tok < 0)
+			range_tok = lsp_find_key(json, toks, n, obj, "targetRange");
+		if (range_tok >= 0) {
+			int start = lsp_find_key(json, toks, n, range_tok, "start");
+			if (start >= 0) {
+				int lt = lsp_find_key(json, toks, n, start, "line");
+				if (lt >= 0)
+					target_line = lsp_tok_int(json, &toks[lt]);
+			}
+		}
+	}
+	if (!uri[0]) {
+		lsp_show_msg("lsp: definition not found");
+		return;
+	}
+	char fpath[1024];
+	lsp_path_from_uri(uri, fpath, sizeof(fpath));
+	lsp_relpath(fpath);
+	if (strcmp(fpath, path)) {
+		int already_open = ex_edit(fpath, strlen(fpath));
+		if (!already_open) {
+			ex_bufpostfix(ex_buf, 0);
+			syn_setft(xb_ft);
+			if (*xb_path && xb_ft)
+				lsp_open(xb_path, xb_ft);
+		}
+	}
+	xrow = target_line;
+	if (xrow >= lbuf_len(xb))
+		xrow = lbuf_len(xb) - 1;
+	if (xrow < 0)
+		xrow = 0;
+	xoff = 0;
+	xtop = xrow > xrows / 2 ? xrow - xrows / 2 : 0;
+}
+
+const char *lsp_diag_for_line(const char *path, int line, int *sev)
+{
+	int i, j;
+	for (i = 0; i < lsp_ndiagfiles; i++) {
+		if (!strcmp(lsp_diagfiles[i].path, path)) {
+			for (j = 0; j < lsp_diagfiles[i].n; j++)
+				if (lsp_diagfiles[i].d[j].line == line) {
+					if (sev)
+						*sev = lsp_diagfiles[i].d[j].severity;
+					return lsp_diagfiles[i].d[j].msg;
+				}
+			return NULL;
+		}
+	}
+	return NULL;
+}
diff --git a/term.c b/term.c
index 75ada7cc..42db40f0 100644
--- a/term.c
+++ b/term.c
@@ -139,8 +139,8 @@ void term_push(char *s, unsigned int n)
 
 int term_read(int winch)
 {
-	static struct pollfd ufd = {STDIN_FILENO, POLLIN};
-	int cw;
+	static struct pollfd ufd[1 + LSP_NFDS_MAX];
+	int cw, i;
 	if (ibuf_pos >= ibuf_cnt) {
 		if (texec) {
 			xquit = !xquit ? 1 : xquit;
@@ -153,24 +153,43 @@ int term_read(int winch)
 		}
 		cw = 0;
 		re:
-		/* read a single input character */
-		if (xquit < 0 || poll(&ufd, 1, -1) <= 0 ||
-				read(STDIN_FILENO, ibuf, 1) <= 0) {
-			xquit = !isatty(STDIN_FILENO) ? -1 : xquit;
-			if (term_winch && winch && xquit >= 0) {
-				*ibuf = winch;
-				goto ret;
-			} else if (term_winch != cw && !winch && xquit >= 0) {
-				cw = term_winch;
+		ufd[0].fd = STDIN_FILENO;
+		ufd[0].events = POLLIN;
+		for (i = 0; i < lsp_nfds; i++) {
+			ufd[i+1].fd = lsp_fds[i];
+			ufd[i+1].events = POLLIN;
+		}
+		/* read a single input character, servicing lsp fds */
+		if (xquit >= 0 && poll(ufd, 1 + lsp_nfds, -1) > 0) {
+			for (i = 0; i < lsp_nfds; i++)
+				if (ufd[i+1].revents & POLLIN)
+					lsp_process_fd(lsp_fds[i]);
+			if (!(ufd[0].revents & POLLIN)) {
+				if (term_winch && winch) {
+					*ibuf = winch;
+					goto ret;
+				}
+				if (lsp_dirty && lsp_wake)
+					goto err;	/* yield so vi redraws diagnostics */
 				goto re;
 			}
-			err:
-			*ibuf = 0;
+			if (read(STDIN_FILENO, ibuf, 1) > 0)
+				goto ret;
+		}
+		xquit = !isatty(STDIN_FILENO) ? -1 : xquit;
+		if (term_winch && winch && xquit >= 0) {
+			*ibuf = winch;
+			goto ret;
+		} else if (term_winch != cw && !winch && xquit >= 0) {
+			cw = term_winch;
+			goto re;
 		} else if (xrr) {
 			static char buf[2];
 			buf[0] = *ibuf;
 			ex_regput(xrr, buf, 1);
 		}
+		err:
+		*ibuf = 0;
 		ret:
 		ibuf_cnt = 1;
 		ibuf_pos = 0;
diff --git a/vi.c b/vi.c
index 357df5ff..67a9f8d8 100644
--- a/vi.c
+++ b/vi.c
@@ -22,6 +22,7 @@
 #include "ren.c"
 #include "term.c"
 #include "uc.c"
+#include "lsp.c"
 
 int vi_hidch;			/* show hidden chars */
 int vi_lncol;			/* line numbers cursor offset */
@@ -102,6 +103,8 @@ static void vi_drawmsg(char *msg)
 }
 #define vi_drawmsg_mpt(msg) { vi_drawmsg(msg); if (!xmpt) xmpt = 1; }
 
+void lsp_show_msg(char *msg) { vi_drawmsg_mpt(msg) }
+
 static int vi_nextcol(char *ln, int dir, int *off)
 {
 	int o = ren_off(ln, ren_next(ln, ren_pos(ln, *off), dir));
@@ -125,8 +128,32 @@ for (i = 0, ret = 0;; i++) { \
 	ret = func; \
 } } \
 
+/* render an lsp diagnostic as virtual text starting at screen column col */
+static void vi_drawdiag(const char *diag, int sev, int r, int col)
+{
+	static const char *sevname[] = {"info", "error", "warning", "info", "hint"};
+	if (col < 0 || col >= xcols)
+		return;
+	sbuf_smake(sb, 256)
+	sbuf_str(sb, "  ")
+	sbuf_str(sb, sevname[sev > 0 && sev < 5 ? sev : 1])
+	sbuf_str(sb, ": ")
+	sbuf_str(sb, diag)
+	sbuf_chr(sb, '\n')
+	sbuf_null(sb)
+	preserve(int, syn_blockhl, syn_blockhl = -1;)
+	preserve(int, ftidx,)
+	syn_setft(lsp_ft);
+	RS(2, led_prender(sb->s, r, col, 0, xcols - col))
+	restore(syn_blockhl)
+	restore(ftidx)
+	free(sb->s);
+}
+
 static void vi_drawrow(int row)
 {
+	const char *diag = NULL;
+	int dsev = 1;
 	int l1, i, i1, lnnum = vi_lnnum;
 	char *c, *s;
 	static char ch[5] = "~";
@@ -170,6 +197,8 @@ static void vi_drawrow(int row)
 		return;
 	}
 	s = lbuf_get(xb, row);
+	if (s && xb_path && xb_path[0])
+		diag = lsp_diag_for_line(xb_path, row, &dsev);
 	skip:
 	rstate += row != xrow;
 	if (!s)
@@ -194,6 +223,7 @@ static void vi_drawrow(int row)
 		memset(c, ' ', l1 - (c - tmp));
 		c[l1 - (c - tmp)] = '\0';
 		led_crender(s, row - xtop, l1, xleft, xleft + xcols - l1)
+		int dcol = l1 + rstate->cmax - xleft;
 		preserve(int, syn_blockhl, syn_blockhl = -1;)
 		preserve(int, ftidx,)
 		syn_setft(nn_ft);
@@ -210,10 +240,15 @@ static void vi_drawrow(int row)
 		RS(2, led_prender(tmp, row - xtop, 0, 0, l1))
 		restore(syn_blockhl)
 		restore(ftidx)
+		if (diag)
+			vi_drawdiag(diag, dsev, row - xtop, dcol);
 		return;
 	}
 	led_crender(s, row - xtop, 0, xleft, xleft + xcols)
+	int dcol = rstate->cmax - xleft;
 	rstate = rstates;
+	if (diag)
+		vi_drawdiag(diag, dsev, row - xtop, dcol);
 }
 
 /* redraw the screen */
@@ -1186,7 +1221,9 @@ void vi(int init)
 		int oleft = xleft;
 		icmd_pos = 0;
 		vi_mod = 0;
+		lsp_wake = 1;
 		vi_ybuf = vi_yankbuf(TK_CTL('l'));
+		lsp_wake = 0;
 		vi_arg = vi_prefix();
 		term_dec()
 		if (vi_lnnum == 1) {
@@ -1642,8 +1679,18 @@ void vi(int init)
 					ex_command(cmd)
 					restore(xled)
 					vi_mod |= 1;
-				} else if (k == '~' || k == 'u' || k == 'U')
+				} else if (k == '~' || k == 'u' || k == 'U') {
 					vc_motion(k);
+				} else if (k == 'K') {
+					if (xb_path && xb_path[0])
+						lsp_hover(xb_path, xrow, xoff);
+				} else if (k == 'd') {
+					if (xb_path && xb_path[0]) {
+						lsp_definition(xb_path, xrow, xoff);
+						vc_status(0);
+						vi_mod |= 1;
+					}
+				}
 				break;
 			case 'x':
 				term_push("d ", 2);
@@ -1719,6 +1766,8 @@ void vi(int init)
 				vc_status(0);
 				vi_mod |= 1;
 				break;
+			case 0:	/* lsp_wake yield; redraw via lsp_dirty */
+				break;
 			default:
 				continue;
 			}
@@ -1779,6 +1828,12 @@ void vi(int init)
 				}
 			}
 		}
+		if (xb_path && xb_path[0])
+			lsp_sync(xb_path, xb);
+		if (lsp_dirty) {
+			lsp_dirty = 0;
+			vi_mod |= 1;
+		}
 		term_record = 1;
 		if (vi_mod & 1 || xleft != oleft
 				|| (vi_lnnum && orow != xrow && !(vi_lnnum == 2))
diff --git a/vi.h b/vi.h
index 6466711d..0e3045ba 100644
--- a/vi.h
+++ b/vi.h
@@ -150,6 +150,7 @@ struct lbuf {
 	int hist_sz;			/* size of hist[] */
 	int hist_n;			/* current history head in hist[] */
 	int hist_u;			/* current undo head in hist[] */
+	int edseq;			/* monotonic content mutation counter */
 };
 #define lbuf_len(lb) lb->ln_n
 #define lbuf_s(ln) ((struct linfo*)(ln - sizeof(struct linfo)))
@@ -548,3 +549,20 @@ extern int vi_lncol;
 /* filesystem */
 extern rset *fsincl;
 void dir_calc(char *path);
+
+/* lsp.c */
+#define LSP_NFDS_MAX	8
+extern int lsp_nfds;
+extern int lsp_dirty;
+extern int lsp_wake;
+extern int lsp_fds[LSP_NFDS_MAX];
+void lsp_process_fd(int fd);
+void lsp_register(const char *ft, const char *cmd);
+void lsp_open(const char *path, const char *ft);
+void lsp_save(const char *path);
+void lsp_sync(const char *path, struct lbuf *lb);
+void lsp_hover(const char *path, int row, int off);
+void lsp_definition(const char *path, int row, int off);
+const char *lsp_diag_for_line(const char *path, int line, int *sev);
+void lsp_list(void);
+void lsp_show_msg(char *msg);
